"use client" import * as React from 'react'; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; import * as SelectPrimitive from '@radix-ui/react-select'; import { cn } from '../../lib/utils'; import { Badge } from '../data/badge'; /** * Radix `` is reserved (it's how Radix represents * "cleared selection"), so passing an empty string at the consumer level * throws. This wrapper transparently substitutes empty strings with a * sentinel for the Radix layer and unwraps it again on `onValueChange`, * letting consumers use `''` for "none" without inventing their own marker. * * Applies to `Select` (controlled value/defaultValue/onValueChange) and * `SelectItem` (item value). */ const EMPTY_SENTINEL = '__empty__'; const toRadix = (raw: string | undefined): string | undefined => { if (raw === undefined) return undefined; return raw === '' ? EMPTY_SENTINEL : raw; }; const fromRadix = (raw: string): string => (raw === EMPTY_SENTINEL ? '' : raw); function Select( props: React.ComponentPropsWithoutRef, ): React.ReactElement { const { value, defaultValue, onValueChange, ...rest } = props; const handleChange = React.useCallback( (next: string) => { onValueChange?.(fromRadix(next)); }, [onValueChange], ); return ( ); } const SelectGroup = SelectPrimitive.Group const SelectValue = SelectPrimitive.Value const SelectTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { icon?: React.ComponentType<{ className?: string }>; /** * `ghost` strips the bordered field chrome (border, bg, shadow, fixed * height) for use as a flat inline control — e.g. a compact dropdown in a * status bar or toolbar where the trigger should read like a plain button. */ variant?: "default" | "ghost"; } >(({ className, children, icon: Icon, variant = "default", ...props }, ref) => ( {/* * Outer wrapper is a flex row so a leading `icon` (any size) stays * vertically centered with the value text. `line-clamp-1` is applied * to the value child, NOT this wrapper — putting it here would set * `display:-webkit-box` and silently kill the flex centering. */} {Icon && ( )} {children} )) SelectTrigger.displayName = SelectPrimitive.Trigger.displayName const SelectScrollUpButton = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName const SelectScrollDownButton = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName const SelectContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, position = "popper", ...props }, ref) => ( {children} )) SelectContent.displayName = SelectPrimitive.Content.displayName const SelectLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) SelectLabel.displayName = SelectPrimitive.Label.displayName const SelectItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { key?: React.Key; icon?: React.ComponentType<{ className?: string }>; badge?: string | React.ReactNode; /** * Optional secondary line shown under the label in the dropdown. * Rendered OUTSIDE `SelectPrimitive.ItemText`, so it does NOT * appear in the trigger — only `children` (the label) does. Use * this for two-line options whose trigger must stay single-line. */ description?: React.ReactNode; } >(({ className, children, icon: Icon, badge, description, value, ...props }, ref) => (
{Icon && }
{children} {description && ( {description} )}
{badge && ( {badge} )}
)) SelectItem.displayName = SelectPrimitive.Item.displayName const SelectSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { key?: React.Key } >(({ className, ...props }, ref) => ( )) SelectSeparator.displayName = SelectPrimitive.Separator.displayName export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, }