import "./select.css"; import type * as React from "react"; import { type ReactNode } from "react"; import { Combobox } from "@base-ui/react/combobox"; import { useRender } from "@base-ui/react/use-render"; import type { Controlled, SelfCommitting } from "./commit_mode"; import { type FieldOptionsSource } from "./option_picker"; import { type StyleProps } from "./style_props"; /** What `chrome="cell"` adds around the value it draws — its inset (6) and hairline (1) on either side (`select.css`) — * for a column budgeting the cell by its reading's ink. */ export declare const SELECT_CELL_CHROME: number; export interface SelectOption { label?: string; value: T; disabled?: boolean; testID?: string; /** Arbitrary payload, so `renderOptionContent` reads the source row without a lookup. */ data?: D; /** Options sharing a group draw together, each group where it first appears. */ group?: string; } export type SelectValue = MULTI extends true ? T[] : T; /** The single arm answers `null` for nothing chosen, never `""`, which a field could hold. */ export type SelectAnswer = MULTI extends true ? T[] : T | null; export type SelectOnValueChange = (value: SelectAnswer) => void; interface SelectBase extends StyleProps { native?: never; options?: (SelectOption | undefined | false)[]; /** A select field from the SDK (`useFieldOptions().fields.`), keys and colours intact; `options` * wins where both are stated. */ fieldOptions?: FieldOptionsSource; /** Draw each option as its mark — `dot` in a list of choices, `tonal` for the surface's one prominent * status. */ badge?: "tonal" | "dot"; /** How each option renders in the menu, and in the trigger when `renderSelected` is omitted. */ renderOptionContent?: (option: SelectOption) => ReactNode; getOptionDescription?: (option: SelectOption) => string | undefined; /** A node renders as-is; a string as muted placeholder text. Default in a field: "Choose…", so an empty * select reads as one rather than a blank box; a cell rests on nothing. */ placeholder?: string | ReactNode; /** Default: "No results". */ emptyText?: string; /** Single-select only: a named empty row (`emptyLabel`) heads the list. */ clearable?: boolean; /** That row's name (locale default "None"), distinct from `placeholder`. */ emptyLabel?: string; searchable?: boolean; /** Under `allowCustom`, the one always-visible place to say a new value is allowed. */ searchPlaceholder?: string; /** The caller narrows the set: the debounced query arrives and `options` answers it. Implies * `searchable`. */ onSearchChange?: (query: string) => void; /** A create row when nothing matches; implies `searchable`. */ allowCustom?: boolean; customOptionLabel?: (query: string) => string | null; /** Default "bottom"; "top" for a find-or-create picker over a long registry, where the highlight stays * on the first match so Enter attaches rather than duplicates. */ customOptionPlacement?: "top" | "bottom"; /** `grid` draws each option as a tile of `renderOptionContent`, for a choice made by shape; the label is * the tile's name and the caption under the grid. */ layout?: "list" | "grid"; /** `lg` (default) is the 40px control height, `md` a dense document toolbar. */ size?: "md" | "lg"; /** `field` (default): the boxed form editor. `cell`: the register's — no box, chevron or band, just the * value ruled on reach, since a register is read and a column of dropdowns reads as a form. */ chrome?: "field" | "cell"; disabled?: boolean; /** Focuses the field; `defaultOpen` opens the list. */ autoFocus?: boolean; defaultOpen?: boolean; /** Needed with no visible label: the selected text describes the value, not the control. */ accessibilityLabel?: string; testID?: string; /** Base UI's trigger ref — a `
`, see the trigger below. */ ref?: React.Ref; render?: Combobox.Trigger.Props["render"]; } interface SelectArity { /** The value becomes a set; rows carry checkboxes and the list stays open between ticks. */ multi?: MULTI; /** `controls.remove` detaches that one. Omitted: the option's content, then a comma summary. */ renderSelected?: (item: SelectOption, controls: { remove: () => void; }) => ReactNode; enableSelectAll?: boolean; selectAllLabel?: string; deselectAllLabel?: string; } /** Who owns an open list's draft; `onClose` is the controlled commit, as `onSave` already is. */ export type SelectCommit = (Controlled & { onClose?: (value: V) => void; }) | (SelfCommitting & { onClose?: never; controls?: never; }); /** The platform's dropdown — the OS popup, typeahead and phone wheel; no multi, search, create or * self-committing arm. */ export interface SelectNativeProps extends StyleProps { native: true; options?: (SelectOption | undefined | false)[]; fieldOptions?: FieldOptionsSource; value: T | null; onValueChange: (value: T | null) => void; placeholder?: string; clearable?: boolean; emptyLabel?: string; size?: "md" | "lg"; disabled?: boolean; autoFocus?: boolean; accessibilityLabel?: string; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; } export type SelectProps = (SelectBase & SelectArity & SelectCommit>) | SelectNativeProps; /** Pick one from a known set — single or `multi`, in a form or writing for itself. Default is the kit's * listbox (a Base UI `Combobox` with the input inside the popup), a bottom sheet under 768px; `native` is * the platform `