// Copyright (c) Meta Platforms, Inc. and affiliates.
'use client';
/**
* @file DropdownMenuCheckboxItem.tsx
* @input React, stylex, Item + CheckboxInput + DropdownMenu context + tokens from core
* @output DropdownMenuCheckboxItem — a standalone checkable menu item.
* @position Sub-component; used inside DropdownMenu.
*
* A menu item that toggles an independent boolean (role="menuitemcheckbox").
* Unlike CheckboxInput, there is no nested native that participates in
* accessibility: the row itself owns the role and aria-checked, per the
* WAI-ARIA menuitemcheckbox pattern. Keyboard navigation (arrows/typeahead) and
* Enter/Space activation come from the parent DropdownMenu's useListFocus +
* activation path, which matches menuitemcheckbox alongside plain menuitem rows.
*
* The checkbox visual is the shared checkbox indicator, decorative
* (aria-hidden) — the row owns the role, checked state, and accessible name, so
* there is no nested native to shim out of the accessibility tree. It
* picks up the same `checkbox` theming (and any theme replacement) as
* CheckboxInput. The control size is derived from the menu's item size (a `sm`
* menu gets the compact control; `md`/`lg` get the standard one) and the marker
* box swaps to the inline-end of the row on coarse-pointer (touch) devices via
* CSS `order`, so it lands where selection toggles are conventionally placed on
* mobile.
*/
import { useCallback, type PointerEvent, type ReactNode } from 'react';
import * as stylex from "../style-runtime.js";
import { renderIconSlot, type IconType } from "../Icon/index.js";
import { useIndicator } from "../Indicator/index.js";
import { Item } from "../Item/index.js";
import { useDropdownMenuContext } from "./DropdownMenuContext.js";
import { focusMenuItemOnHover } from "./menuItemHover.js";
import "../theme/tokens.stylex.js";
import { colorVars, spacingVars } from "../theme/tokens.stylex.js";
import { mergeProps, themeProps } from "../utils/index.js";
import type { BaseProps } from "../BaseProps.js";
const styles = {
root: {
kzqmXN: "xh8yej3",
kaIpWk: "x1ws5lxm",
kMwMTN: "x1tgivj0",
kWkggS: "xjbqb8w x1c52tdz",
kkrTdU: "x1ypdohk x16khyan",
kI3sdo: "x1a2a7pz",
$$css: true as const
},
disabled: {
kSiTet: "xbyyjgo",
kkrTdU: "xt0e3qv",
$$css: true as const
},
marker: {
kfzvcC: "x47corl",
kayTVb: "x1g77sc7 xozvky",
keTefX: "x1lziwak x1jymrmb",
$$css: true as const
}
};
// Matches the control sizes CheckboxInput uses, so a checkbox reads the same in
// a menu row as it does in a form.
export interface DropdownMenuCheckboxItemProps extends Omit {
/**
* Primary label text identifying the item.
*/
label: ReactNode;
/**
* Secondary description text displayed below the label.
*/
description?: ReactNode;
/**
* Icon to display before the label. Accepts a semantic icon name (see
* `npx astryx docs icons`) or a rendered node.
*/
icon?: ReactNode | IconType;
/**
* Whether the item is checked. Controlled — pair with `onChange`.
*/
value: boolean;
/**
* Callback fired with the next checked state when the item is toggled.
*/
onChange?: (checked: boolean) => void;
/**
* Whether the item is disabled. Disabled items stay focusable (via
* `aria-disabled`) so they remain discoverable by keyboard and assistive
* technology, but activation is blocked.
* @default false
*/
isDisabled?: boolean;
/**
* Whether toggling the item closes the menu. Checkbox items default to
* staying open so several can be toggled in a single session, unlike radio
* items which default to closing on selection.
* @default false
*/
hasCloseOnSelect?: boolean;
/**
* Content to render after the label and description, such as a keyboard
* shortcut hint or badge.
*/
endContent?: ReactNode;
}
/**
* A checkable dropdown menu item (role="menuitemcheckbox").
*
* Must be used inside a DropdownMenu. Toggles an independent boolean; for a
* one-of-N choice use DropdownMenuRadioGroup + DropdownMenuRadioItem instead.
*
* @example
* ```
* import {DropdownMenuCheckboxItem} from '@astryxdesign/core/DropdownMenu';
*
*
*
* ```
*/
export function DropdownMenuCheckboxItem({
label,
description,
icon,
value,
onChange,
isDisabled = false,
hasCloseOnSelect = false,
endContent,
xstyle,
className,
style,
...rest
}: DropdownMenuCheckboxItemProps) {
const ctx = useDropdownMenuContext();
const menuSize = ctx?.menuSize ?? 'md';
const controlSize = menuSize === 'sm' ? 'sm' : 'md';
const CheckboxControl = useIndicator('checkbox');
const handleClick = useCallback(() => {
if (isDisabled) {
return;
}
onChange?.(!value);
if (hasCloseOnSelect) {
ctx?.closeMenu();
}
}, [isDisabled, onChange, value, hasCloseOnSelect, ctx]);
const handlePointerMove = useCallback((e: PointerEvent) => focusMenuItemOnHover(e, isDisabled), [isDisabled]);
return } startContent={icon ? renderIconSlot(icon, {
size: 'sm',
color: 'secondary'
}) : undefined} label={label} description={description} endContent={endContent} onClick={handleClick} isDisabled={isDisabled} xstyle={[styles.root, isDisabled && styles.disabled, xstyle]} {...mergeProps(themeProps('dropdown-menu-item', {
size: menuSize
}), {
className,
style
})} />;
}
DropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem';