'use client' import type { ChangeEvent, Key, ReactElement, ReactNode, Ref } from 'react' import { Children, isValidElement } from 'react' import { Description, FieldError, Label, ListBox, Select as HeroSelect } from '@heroui/react' import { cn } from '@/lib/cn' type SelectKey = string | number export interface SelectItemProps { children?: ReactNode classNames?: { base?: string } key?: SelectKey textValue?: string } export function SelectItem(_props: SelectItemProps) { return null } interface SelectProps { 'aria-label'?: string autoFocus?: boolean children?: ReactNode | ((item: T) => ReactElement) className?: string classNames?: { base?: string; label?: string; trigger?: string; value?: string; placeholder?: string } description?: ReactNode descriptionClassName?: string disabledKeys?: Iterable endContent?: ReactNode errorMessage?: ReactNode fullWidth?: boolean isDisabled?: boolean isInvalid?: boolean items?: Iterable label?: ReactNode labelPlacement?: 'inside' | 'outside' | 'outside-left' listboxProps?: object name?: string placeholder?: string popoverProps?: { classNames?: { content?: string } placement?: string portalContainer?: Element | null shouldBlockScroll?: boolean } radius?: 'none' | 'sm' | 'md' | 'lg' | 'full' selectedKeys?: 'all' | Iterable selectionMode?: 'single' | 'multiple' size?: 'sm' | 'md' | 'lg' startContent?: ReactNode ref?: Ref validationBehavior?: 'native' | 'aria' variant?: 'flat' | 'bordered' | 'faded' | 'underlined' onBlur?: () => void onChange?: (event: ChangeEvent) => void onSelectionChange?: (keys: 'all' | Set) => void } function normalizeKey(key: ReactElement['key'], index: number): string { if (key == null) return String(index) const value = String(key) return value.startsWith('.$') ? value.slice(2) : value } function getItems(children: SelectProps['children'], sourceItems?: Iterable) { const resolvedChildren = typeof children === 'function' ? Array.from(sourceItems ?? [], (item) => children(item)) : children return Children.toArray(resolvedChildren) .filter((child): child is ReactElement => isValidElement(child)) .map((child, index) => ({ id: normalizeKey(child.key, index), label: child.props.children, textValue: child.props.textValue ?? (typeof child.props.children === 'string' ? child.props.children : normalizeKey(child.key, index)), })) } export function Select({ 'aria-label': ariaLabel, autoFocus, children, className, classNames, description, descriptionClassName, disabledKeys, endContent, errorMessage, fullWidth: _fullWidth, isDisabled, isInvalid, items: sourceItems, label, labelPlacement: _labelPlacement, listboxProps: _listboxProps, name, placeholder, popoverProps, radius, selectedKeys, selectionMode = 'single', size, startContent, ref, validationBehavior, variant, onBlur, onChange, onSelectionChange, }: SelectProps) { const items = getItems(children, sourceItems) const selected = selectedKeys === 'all' ? 'all' : new Set(Array.from(selectedKeys ?? [], String)) const rootClassName = cn('flex w-full flex-col gap-1', classNames?.base, className) const radiusClass = variant === 'underlined' ? 'rounded-none' : !radius ? '' : radius === 'none' ? 'rounded-none' : radius === 'full' ? 'rounded-full' : radius === 'lg' ? 'rounded-2xl' : radius === 'sm' ? 'rounded-md' : 'rounded-lg' const variantClass = isInvalid ? 'border-fourth' : variant === 'underlined' ? 'rounded-none border-x-0 border-t-0' : variant === 'flat' || variant === 'faded' ? 'border-transparent bg-default-100' : 'border-divider bg-surface' const triggerClassName = cn( 'flex w-full items-center gap-2', !classNames?.trigger && cn('border px-3', size === 'sm' ? 'min-h-8' : size === 'lg' ? 'min-h-12' : 'min-h-10', radiusClass, variantClass), classNames?.trigger ) if (selectionMode === 'multiple') { const selectedValues = selected === 'all' ? items.map((item) => item.id) : Array.from(selected) return (
{label ? : null} {description ? {description} : null} {isInvalid && errorMessage ? {errorMessage} : null}
) } const selectedKey = selected === 'all' ? null : (Array.from(selected)[0] ?? null) return ( { const value = key == null ? '' : String(key) onSelectionChange?.(new Set(value ? [value] : [])) onChange?.({ target: { value } } as ChangeEvent) }} > {label ? : null} {startContent} {({ defaultChildren, isPlaceholder }) => ( {isPlaceholder ? placeholder : defaultChildren} )} {endContent ?? } {description ? {description} : null} {isInvalid && errorMessage ? {errorMessage} : null} {items.map((item) => ( ))} ) }