"use client" import { useState, type ReactNode } from "react" import { CheckIcon, ChevronsUpDownIcon } from "lucide-react" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { cn } from "@/lib/utils" import { useI18n } from "@/i18n/provider" export type ComboboxOption = { value: string label: string disabled?: boolean group?: string subtitle?: string description?: string } type CommonOptionComboboxProps = { options: ComboboxOption[] placeholder: string searchPlaceholder?: string emptyText?: string disabled?: boolean triggerClassName?: string preserveExternalSelection?: boolean renderOptionAction?: (option: ComboboxOption) => ReactNode } type OptionComboboxProps = CommonOptionComboboxProps & ( | { multiple?: false value: string onChange: (value: string) => void values?: never onValuesChange?: never } | { multiple: true values: string[] onValuesChange: (values: string[]) => void value?: never onChange?: never } ) export function OptionCombobox(props: OptionComboboxProps) { const { options, placeholder, searchPlaceholder, emptyText, disabled = false, triggerClassName, preserveExternalSelection = false, renderOptionAction, } = props const t = useI18n() const [open, setOpen] = useState(false) const selectedValues = props.multiple ? props.values : [props.value] const selectedOptions = options.filter((option) => selectedValues.includes(option.value) ) const selectedLabel = selectedOptions.length === 0 ? placeholder : selectedOptions.length === 1 ? selectedOptions[0].label : `已选择 ${selectedOptions.length} 项` const optionGroups = Array.from( options.reduce((groups, option) => { const group = option.group ?? "" groups.set(group, [...(groups.get(group) ?? []), option]) return groups }, new Map()), ) function selectOption(optionValue: string) { const option = options.find((item) => item.value === optionValue) if (option?.disabled) { return } if (props.multiple) { props.onValuesChange( props.values.includes(optionValue) ? props.values.filter((value) => value !== optionValue) : [...props.values, optionValue] ) return } props.onChange(optionValue) setOpen(false) } return ( } > {selectedLabel} {emptyText ?? t("common.emptyOptions")} {optionGroups.map(([group, groupOptions]) => ( {groupOptions.map((option) => ( selectOption(option.value)} >
{props.multiple ? ( ) : ( )} {option.label} {option.subtitle ? ( {option.subtitle} ) : null} {option.description ? ( {option.description} ) : null}
{renderOptionAction ? (
event.preventDefault()} onClick={(event) => event.stopPropagation()} > {renderOptionAction(option)}
) : null}
))}
))}
) }