import { selectTabIndex, useRovingFocus, useSinglePicker, } from "@charcuterie/logic" import type { ControlSize } from "@charcuterie/tokens" import type { Placement } from "@floating-ui/react" import { FloatingFocusManager, FloatingPortal, } from "@floating-ui/react" import type { ReactElement, ReactNode } from "react" import { useEffect, useRef } from "react" import { PANEL_SURFACE_CLASS } from "../Overlay/overlayPanelClass.ts" import { useAnchoredOverlay } from "../Overlay/useAnchoredOverlay.ts" import { usePanelItemSize } from "../Overlay/usePanelItemSize.ts" import { toClassName } from "../toClassName.ts" import { ListboxOption } from "./ListboxOption.tsx" 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 } const getOptionText = (item: ListboxItem) => ( item.textValue ?? (typeof item.label === "string" ? item.label : "") ).toLowerCase() /** * 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 `