import type { ControlSize } from "@charcuterie/tokens"; import type { Placement } from "@floating-ui/react"; import type { ReactElement, ReactNode } from "react"; export type ListboxItem = { isDisabled?: boolean; label: ReactNode; /** * Required when `label` is rich (an icon, two lines, a badge): it is * the type-ahead target and the accessible name a screen reader * reads. When `label` is a plain string it is used as the fallback. */ textValue?: string; value: string; }; export type ListboxProps = { className?: string; isVisible: boolean; /** * How tall each option is, from the same density-aware tokens a * `Button` reads — so a `md` option is exactly as tall as the `md` * trigger that opened it. `lg` is the fat one: 2.75rem at * `comfortable` density, and a bigger target to aim at. * * A short window steps it back down on its own * (`usePanelItemSize`), so a `lg` list is never the reason the last * option is off-screen. */ itemSize?: ControlSize; /** Outside press and Escape both land here. */ onDismiss: () => void; onSelect: (value: string) => void; options: readonly ListboxItem[]; placement?: Placement; /** The initial seed. The live selection is the listbox's after that. */ selectedValue?: string; /** The control the listbox hangs off. **Cloned, not wrapped.** */ trigger: ReactElement; }; /** * The single-select fancy dropdown — the rich-option sibling of the * native `Select`. `Select` keeps type-ahead, the mobile wheel, form * submission and autofill; `Listbox` is for the cases `