"use client" import * as React from "react" import { Select as SelectPrimitive } from "@base-ui/react/select" import type { ValueOf } from "@/types/utils" import { cn } from "@/lib/utils" import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react" const SELECT_TRIGGER_SIZE = { SMALL: "sm", DEFAULT: "default", } as const type SelectTriggerSize = ValueOf type SelectItemEntry = { value: unknown label: React.ReactNode } type SelectItemsProp = React.ComponentProps["items"] const SelectValueLabelContext = React.createContext | null>( null ) function collectSelectItems(children: React.ReactNode, entries: SelectItemEntry[] = []) { React.Children.forEach(children, (child) => { if (!React.isValidElement(child)) { return } if (child.type === SelectItem) { const itemProps = child.props as { value?: unknown; children?: React.ReactNode } if (itemProps.value !== undefined) { entries.push({ value: itemProps.value, label: itemProps.children ?? null, }) } } const nestedChildren = (child.props as { children?: React.ReactNode }).children if (nestedChildren) { collectSelectItems(nestedChildren, entries) } }) return entries } function normalizeSelectItems(items: SelectItemsProp): SelectItemEntry[] { if (!items) { return [] } if (Array.isArray(items)) { return items.map((item) => ({ value: item.value, label: item.label, })) } return Object.entries(items).map(([value, label]) => ({ value, label, })) } function Select({ children, items, ...props }: React.ComponentProps) { const inferredItems = items ? [] : collectSelectItems(children) const resolvedItems = items ?? (inferredItems.length > 0 ? inferredItems : undefined) const labelEntries = items ? normalizeSelectItems(items) : inferredItems const labelMap = new Map( labelEntries.map((entry) => [entry.value, entry.label]) ) return ( {children} ) } function SelectGroup({ className, ...props }: React.ComponentProps) { return ( ) } function SelectValue({ placeholder, children, ...props }: React.ComponentProps & { placeholder?: React.ReactNode }) { const labelMap = React.useContext(SelectValueLabelContext) if (children !== undefined) { return ( {children} ) } return ( {(value: unknown) => { if ( value === null || value === undefined || value === "" || (Array.isArray(value) && value.length === 0) ) { return placeholder ?? null } if (Array.isArray(value)) { return value.map((item, index) => ( {index > 0 ? ", " : null} {labelMap?.get(item) ?? (item as React.ReactNode)} )) } return labelMap?.get(value) ?? (value as React.ReactNode) }} ) } function SelectTrigger({ className, size = SELECT_TRIGGER_SIZE.DEFAULT, children, ...props }: React.ComponentProps & { size?: SelectTriggerSize }) { return ( {children} } /> ) } type SelectContentPosition = "item-aligned" | "popper" function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }: React.ComponentProps & { position?: SelectContentPosition align?: React.ComponentProps["align"] }) { const alignItemWithTrigger = position === "item-aligned" 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, }