"use client"; import * as SelectPrimitive from "@radix-ui/react-select"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon, X } from "lucide-react"; import { ComponentProps, createContext, KeyboardEvent, MouseEvent, useContext, useId, } from "react"; import { cn } from "@/lib/utils"; // Context for managing multi-select state type SelectContextValue = { multiple?: boolean; selectedValues?: string[]; onMultiValueChange?: (values: string[]) => void; ariaLabel?: string; liveRegionId?: string; }; const SelectContext = createContext({}); function Select({ allowDeselect = false, multiple = false, value, onValueChange, selectedValues = [], onMultiValueChange, ariaLabel, ...props }: Omit, "onValueChange"> & { allowDeselect?: boolean; multiple?: boolean; selectedValues?: string[]; onValueChange?: (value: string) => void; onMultiValueChange?: (values: string[]) => void; ariaLabel?: string; }) { const liveRegionId = useId(); const handleValueChange = (nextValue: string) => { if (multiple && onMultiValueChange) { // Multi-select: toggle the value const newValues = selectedValues.includes(nextValue) ? selectedValues.filter((v) => v !== nextValue) : [...selectedValues, nextValue]; onMultiValueChange(newValues); } else if ( allowDeselect && typeof value === "string" && value === nextValue ) { // Single-select with deselect onValueChange?.(""); } else { // Single-select onValueChange?.(nextValue); } }; const contextValue = { multiple, selectedValues, onMultiValueChange, ariaLabel, liveRegionId, }; return ( {/* Live region for screen reader announcements */} {multiple && (
{selectedValues.length > 0 ? `${selectedValues.length} ${selectedValues.length === 1 ? "item" : "items"} selected` : "No items selected"}
)}
); } function SelectGroup({ ...props }: ComponentProps) { return ; } function SelectValue({ placeholder, children, ...props }: ComponentProps) { const { multiple, selectedValues } = useContext(SelectContext); // For multi-select, render custom children or placeholder if (multiple) { return ( {selectedValues && selectedValues.length > 0 ? children : placeholder} ); } // For single-select, use default Radix behavior return ( {children} ); } function SelectTrigger({ className, size = "default", children, ...props }: ComponentProps & { size?: "sm" | "default"; }) { const { multiple, selectedValues, onMultiValueChange, ariaLabel } = useContext(SelectContext); const hasSelection = multiple && selectedValues && selectedValues.length > 0; const handleClear = ( e: MouseEvent | KeyboardEvent, ) => { e.stopPropagation(); if (onMultiValueChange) { onMultiValueChange([]); } }; const clearButtonLabel = `Clear ${ariaLabel || "selection"}${hasSelection ? ` (${selectedValues.length} selected)` : ""}`; return ( {children}
{hasSelection && ( { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); e.stopPropagation(); handleClear(e); } }} className="pointer-events-auto cursor-pointer rounded-sm p-0.5 opacity-70 transition-opacity hover:opacity-100 focus:opacity-100 focus:ring-2 focus:ring-slate-600 focus:ring-offset-2 focus:outline-none dark:focus:ring-slate-400" aria-label={clearButtonLabel} > )}
); } function SelectContent({ className, children, position = "popper", align = "center", ...props }: ComponentProps) { return ( {children} ); } function SelectLabel({ className, ...props }: ComponentProps) { return ( ); } function SelectItem({ className, children, value, ...props }: ComponentProps) { const { multiple, selectedValues } = useContext(SelectContext); const isSelected = multiple && selectedValues?.includes(value); return ( {children} ); } function SelectSeparator({ className, ...props }: ComponentProps) { return ( ); } function SelectScrollUpButton({ className, ...props }: ComponentProps) { return ( ); } function SelectScrollDownButton({ className, ...props }: ComponentProps) { return ( ); } export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };