| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- "use client"
- import * as React from "react"
- import { CheckIcon, ChevronsUpDownIcon } from "lucide-react"
- import { cn } from "@/client/lib/utils"
- import { Button } from "@/client/components/ui/button"
- import {
- Command,
- CommandEmpty,
- CommandGroup,
- CommandInput,
- CommandItem,
- CommandList,
- } from "@/client/components/ui/command"
- import {
- Popover,
- PopoverContent,
- PopoverTrigger,
- } from "@/client/components/ui/popover"
- export interface ComboboxOption {
- value: string
- label: string
- }
- interface ComboboxProps {
- options: ComboboxOption[]
- value?: string
- onValueChange?: (value: string) => void
- onSearchChange?: (search: string) => void
- placeholder?: string
- searchPlaceholder?: string
- emptyMessage?: string
- className?: string
- disabled?: boolean
- 'data-testid'?: string
- }
- // 将 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,
- onValueChange,
- onSearchChange,
- placeholder = "请选择...",
- searchPlaceholder = "搜索...",
- emptyMessage = "未找到匹配项",
- className,
- disabled = false,
- "data-testid": dataTestId,
- }: ComboboxProps) {
- const [open, setOpen] = React.useState(false)
- const handleClose = () => {
- setOpen(false)
- }
- return (
- <Popover open={open} onOpenChange={setOpen}>
- <PopoverTrigger asChild>
- <Button
- variant="outline"
- role="combobox"
- aria-expanded={open}
- className={cn("w-full justify-between", className)}
- disabled={disabled}
- data-testid={dataTestId}
- >
- {value
- ? options.find((option) => option.value === value)?.label
- : placeholder}
- <ChevronsUpDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
- </Button>
- </PopoverTrigger>
- <PopoverContent className="w-full p-0" align="start">
- <ComboboxCommand
- options={options}
- value={value}
- onValueChange={onValueChange}
- onSearchChange={onSearchChange}
- searchPlaceholder={searchPlaceholder}
- emptyMessage={emptyMessage}
- onClose={handleClose}
- />
- </PopoverContent>
- </Popover>
- )
- }
|