Blame view

美国版/Food Labeling Management Platform/src/components/ui/searchable-select.tsx 3.47 KB
0e27ddc8   杨鑫   标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
  "use client";
  
  import * as React from "react";
  import { useState } from "react";
  import { ChevronsUpDown } from "lucide-react";
  import { cn } from "./utils";
  import { Button } from "./button";
  import {
    Command,
    CommandEmpty,
    CommandGroup,
    CommandInput,
    CommandItem,
    CommandList,
  } from "./command";
  import { Popover, PopoverContent, PopoverTrigger } from "./popover";
  
  export type SearchableSelectOption = {
    value: string;
    label: string;
  };
  
  export function SearchableSelect({
    value,
    onValueChange,
    options,
    placeholder = "Select…",
    searchPlaceholder = "Search…",
    emptyText = "No matching results.",
    disabled,
    className,
  }: {
    /** 空字符串表示未选择 */
    value: string;
    onValueChange: (next: string) => void;
    options: SearchableSelectOption[];
    placeholder?: string;
    searchPlaceholder?: string;
    emptyText?: string;
    disabled?: boolean;
    className?: string;
  }) {
    const [open, setOpen] = useState(false);
    const hit = value ? options.find((o) => o.value === value) : undefined;
    const selectedLabel = value ? hit?.label ?? value : null;
  
    return (
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>
          <Button
            type="button"
            variant="outline"
            role="combobox"
            aria-expanded={open}
            disabled={disabled}
            className={cn(
              "w-full justify-between h-10 px-3 font-normal border border-gray-300 bg-white",
              className,
            )}
          >
            <span
              className={cn(
                "truncate text-left text-sm",
                !selectedLabel && "text-gray-500",
              )}
            >
              {selectedLabel ?? placeholder}
            </span>
            <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
          </Button>
        </PopoverTrigger>
        <PopoverContent
          className="p-0 w-[var(--radix-popover-trigger-width)] max-w-[min(100vw-2rem,400px)]"
          align="start"
        >
          <Command>
            <CommandInput placeholder={searchPlaceholder} />
            <CommandList>
              <CommandEmpty>{emptyText}</CommandEmpty>
              <CommandGroup>
                {options.map((opt) => (
                  <CommandItem
                    key={opt.value}
                    value={`${opt.label} ${opt.value}`}
                    onSelect={() => {
                      onValueChange(opt.value);
                      setOpen(false);
                    }}
                    className={cn(
                      "cursor-pointer rounded-md px-2 py-2 transition-colors",
                      "hover:bg-gray-100 hover:text-gray-900",
                      "data-[selected=true]:bg-gray-100",
                      value === opt.value &&
                        "bg-blue-50 text-gray-900 font-medium data-[selected=true]:bg-blue-100",
                    )}
                  >
                    <span className="truncate">{opt.label}</span>
                  </CommandItem>
                ))}
              </CommandGroup>
            </CommandList>
          </Command>
          {value ? (
            <div className="border-t border-gray-100 px-2 py-1.5">
              <button
                type="button"
                className="text-xs text-gray-500 hover:text-gray-900 underline-offset-2 hover:underline"
                onClick={() => {
                  onValueChange("");
                  setOpen(false);
                }}
              >
                Clear selection
              </button>
            </div>
          ) : null}
        </PopoverContent>
      </Popover>
    );
  }