'use client'; import * as React from 'react'; import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XIcon } from '@/icons'; import * as SelectPrimitive from 'radix-ui/select'; import { cn } from '@/lib/utils'; import type { VariantProps } from '@/lib/cva'; import { focusRing, iconSizing } from '@/lib/cva-presets'; import { useControllableState } from '@/hooks/use-controllable-state'; import { inputVariants } from '@/components/input'; export type SelectProps = React.ComponentProps; /** * The trigger is a field, so it takes Input's two axes — `variant` (`outline` / * `flushed`) and its 32 / 36 / 44 / 56px `size` ladder. * * Those two come from `VariantProps`, the same way * `InputProps` and `TextareaProps` take them, rather than being written out * here. The trigger already *renders* through `inputVariants` below, so a * hand-written union meant the behaviour was shared and the type was not: a * third shape added to `fieldShape` would reach Input and Textarea * automatically and stop at Select. */ export interface SelectTriggerProps extends Omit, 'size'>, VariantProps { /** * Show a reset control once something is chosen — CBAR's `.isClearable?`. * * Clearing sets the value to `''`, which is how Radix returns a Select to its * placeholder. That is also why an item may not use `''` as its own value. */ clearable?: boolean; /** Accessible name for that control. */ clearLabel?: string; /** * Classes for the trigger itself when `clearable` is on. * * With it on, the trigger gains a wrapper and `className` lands there — that * is the element a parent lays out, so `className="w-56"` should size it. * Same split as Input's adornment wrapper. */ classNames?: { trigger?: string }; } /** * Tracks the selected value so a trigger can decide whether to offer a reset. * * Radix keeps its value in a context this package cannot read, so `Select` * mirrors it. `Root` ends up controlled either way — transparently, since the * mirror follows `defaultValue` when the caller does not control it. */ const SelectValueContext = React.createContext<{ value: string; clear: () => void } | null>(null); /** * Choose one value from a list. * * Renders a real listbox with type-ahead and keyboard navigation. Reach for * Combobox instead once the list is long enough to need filtering, and for * DropdownMenu when the items are actions rather than values. */ function Select({ value, defaultValue, onValueChange, children, ...props }: SelectProps) { const [current, handleValueChange] = useControllableState({ value, defaultValue: defaultValue ?? '', onChange: onValueChange, }); const context = React.useMemo( () => ({ value: current, clear: () => handleValueChange('') }), [current, handleValueChange] ); return ( {children} ); } function SelectGroup(props: React.ComponentProps) { return ; } function SelectValue(props: React.ComponentProps) { return ; } function SelectTrigger({ className, classNames, variant, size = 'md', clearable = false, clearLabel = 'Clear selection', children, ...props }: SelectTriggerProps) { const context = React.useContext(SelectValueContext); const trigger = ( {children} {/* The gap goes on the chevron rather than on the trigger's padding: padding would push the chevron inward too, and the reset control has to sit *between* the value and the chevron, not outside it. */} ); if (!clearable) return trigger; /* * The reset button is a *sibling* of the trigger, positioned over it. * `SelectPrimitive.Trigger` renders a real ) : null} ); } function SelectContent({ className, children, position = 'item-aligned', align = 'center', ...props }: React.ComponentProps) { return ( {children} ); } function SelectLabel({ className, ...props }: React.ComponentProps) { return ( ); } function SelectItem({ className, children, ...props }: React.ComponentProps) { return ( {children} ); } function SelectSeparator({ className, ...props }: React.ComponentProps) { return ( ); } function SelectScrollUpButton({ className, ...props }: React.ComponentProps) { return ( ); } function SelectScrollDownButton({ className, ...props }: React.ComponentProps) { return ( ); } export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };