import type { ControlSize } from "@charcuterie/tokens" import type { ReactNode } from "react" import { useEffect } from "react" import { PANEL_ITEM_SIZE_CLASS } from "../controlStyles.ts" import { FOCUS_RING_CLASS } from "../intentStyles.ts" import { toClassName } from "../toClassName.ts" import type { MenuItem } from "./Menu.tsx" export type MenuActionProps = { /** Already stepped down for a short window by `usePanelItemSize`. */ itemSize: ControlSize item: MenuItem onDismiss: () => void /** `RovingFocus.register` — membership of the arrow-key group. */ register: (value: string) => () => void tabIndex: number trackElement: ( key: string, element: HTMLButtonElement | null, ) => void } /** * Its own file because **registration is an effect**, and an effect * cannot run in a loop — the same reason `TabTrigger` and * `AccordionSection` are separate. Any member of a registering group * ends up shaped like this. * * A disabled item is simply not registered, so the arrow keys skip * it without any command in `RovingFocus` knowing what "disabled" * means. It stays in the DOM and stays announced, which is the * difference between "you cannot do this right now" and "this does * not exist". */ export const MenuAction = ({ item, itemSize, onDismiss, register, tabIndex, trackElement, }: MenuActionProps): ReactNode => { const { icon, isDisabled = false, key, label, onSelect, } = item useEffect(() => { if (isDisabled) { return } return register(key) }, [isDisabled, key, register]) return ( ) }