|
|
@@ -36,6 +36,61 @@ interface ComboboxProps {
|
|
|
disabled?: boolean
|
|
|
}
|
|
|
|
|
|
+// 将 Command 逻辑封装为独立组件,确保 options 更新时重新渲染
|
|
|
+interface ComboboxCommandProps {
|
|
|
+ options: ComboboxOption[]
|
|
|
+ value?: string
|
|
|
+ onValueChange?: (value: string) => void
|
|
|
+ onSearchChange?: (search: string) => void
|
|
|
+ searchPlaceholder?: string
|
|
|
+ emptyMessage?: string
|
|
|
+ onClose: () => void
|
|
|
+}
|
|
|
+
|
|
|
+function ComboboxCommand({
|
|
|
+ options,
|
|
|
+ value,
|
|
|
+ onValueChange,
|
|
|
+ onSearchChange,
|
|
|
+ searchPlaceholder = "搜索...",
|
|
|
+ emptyMessage = "未找到匹配项",
|
|
|
+ onClose,
|
|
|
+}: ComboboxCommandProps) {
|
|
|
+ console.debug('ComboboxCommand options', options)
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Command shouldFilter={false}>
|
|
|
+ <CommandInput
|
|
|
+ placeholder={searchPlaceholder}
|
|
|
+ onValueChange={onSearchChange}
|
|
|
+ />
|
|
|
+ <CommandList>
|
|
|
+ <CommandEmpty>{emptyMessage}</CommandEmpty>
|
|
|
+ <CommandGroup>
|
|
|
+ {options.map((option) => (
|
|
|
+ <CommandItem
|
|
|
+ key={option.value}
|
|
|
+ value={option.value}
|
|
|
+ onSelect={(currentValue) => {
|
|
|
+ onValueChange?.(currentValue === value ? "" : currentValue)
|
|
|
+ onClose()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <CheckIcon
|
|
|
+ className={cn(
|
|
|
+ "mr-2 h-4 w-4",
|
|
|
+ value === option.value ? "opacity-100" : "opacity-0"
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ {option.label}
|
|
|
+ </CommandItem>
|
|
|
+ ))}
|
|
|
+ </CommandGroup>
|
|
|
+ </CommandList>
|
|
|
+ </Command>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
export function Combobox({
|
|
|
options,
|
|
|
value,
|
|
|
@@ -49,6 +104,10 @@ export function Combobox({
|
|
|
}: ComboboxProps) {
|
|
|
const [open, setOpen] = React.useState(false)
|
|
|
|
|
|
+ const handleClose = () => {
|
|
|
+ setOpen(false)
|
|
|
+ }
|
|
|
+
|
|
|
return (
|
|
|
<Popover open={open} onOpenChange={setOpen}>
|
|
|
<PopoverTrigger asChild>
|
|
|
@@ -66,35 +125,15 @@ export function Combobox({
|
|
|
</Button>
|
|
|
</PopoverTrigger>
|
|
|
<PopoverContent className="w-full p-0" align="start">
|
|
|
- <Command>
|
|
|
- <CommandInput
|
|
|
- placeholder={searchPlaceholder}
|
|
|
- onValueChange={onSearchChange}
|
|
|
- />
|
|
|
- <CommandList>
|
|
|
- <CommandEmpty>{emptyMessage}</CommandEmpty>
|
|
|
- <CommandGroup>
|
|
|
- {options.map((option) => (
|
|
|
- <CommandItem
|
|
|
- key={option.value}
|
|
|
- value={option.value}
|
|
|
- onSelect={(currentValue) => {
|
|
|
- onValueChange?.(currentValue === value ? "" : currentValue)
|
|
|
- setOpen(false)
|
|
|
- }}
|
|
|
- >
|
|
|
- <CheckIcon
|
|
|
- className={cn(
|
|
|
- "mr-2 h-4 w-4",
|
|
|
- value === option.value ? "opacity-100" : "opacity-0"
|
|
|
- )}
|
|
|
- />
|
|
|
- {option.label}
|
|
|
- </CommandItem>
|
|
|
- ))}
|
|
|
- </CommandGroup>
|
|
|
- </CommandList>
|
|
|
- </Command>
|
|
|
+ <ComboboxCommand
|
|
|
+ options={options}
|
|
|
+ value={value}
|
|
|
+ onValueChange={onValueChange}
|
|
|
+ onSearchChange={onSearchChange}
|
|
|
+ searchPlaceholder={searchPlaceholder}
|
|
|
+ emptyMessage={emptyMessage}
|
|
|
+ onClose={handleClose}
|
|
|
+ />
|
|
|
</PopoverContent>
|
|
|
</Popover>
|
|
|
)
|