您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

select-with-search.tsx 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. 'use client';
  2. import { CheckIcon, ChevronDownIcon } from 'lucide-react';
  3. import {
  4. Fragment,
  5. forwardRef,
  6. useCallback,
  7. useEffect,
  8. useId,
  9. useState,
  10. } from 'react';
  11. import { Button } from '@/components/ui/button';
  12. import {
  13. Command,
  14. CommandEmpty,
  15. CommandGroup,
  16. CommandInput,
  17. CommandItem,
  18. CommandList,
  19. } from '@/components/ui/command';
  20. import {
  21. Popover,
  22. PopoverContent,
  23. PopoverTrigger,
  24. } from '@/components/ui/popover';
  25. import { RAGFlowSelectOptionType } from '../ui/select';
  26. const countries = [
  27. {
  28. label: 'America',
  29. options: [
  30. { value: 'United States', label: '🇺🇸' },
  31. { value: 'Canada', label: '🇨🇦' },
  32. { value: 'Mexico', label: '🇲🇽' },
  33. ],
  34. },
  35. {
  36. label: 'Africa',
  37. options: [
  38. { value: 'South Africa', label: '🇿🇦' },
  39. { value: 'Nigeria', label: '🇳🇬' },
  40. { value: 'Morocco', label: '🇲🇦' },
  41. ],
  42. },
  43. {
  44. label: 'Asia',
  45. options: [
  46. { value: 'China', label: '🇨🇳' },
  47. { value: 'Japan', label: '🇯🇵' },
  48. { value: 'India', label: '🇮🇳' },
  49. ],
  50. },
  51. {
  52. label: 'Europe',
  53. options: [
  54. { value: 'United Kingdom', label: '🇬🇧' },
  55. { value: 'France', label: '🇫🇷' },
  56. { value: 'Germany', label: '🇩🇪' },
  57. ],
  58. },
  59. {
  60. label: 'Oceania',
  61. options: [
  62. { value: 'Australia', label: '🇦🇺' },
  63. { value: 'New Zealand', label: '🇳🇿' },
  64. ],
  65. },
  66. ];
  67. export type SelectWithSearchFlagOptionType = {
  68. label: string;
  69. options: RAGFlowSelectOptionType[];
  70. };
  71. export type SelectWithSearchFlagProps = {
  72. options?: SelectWithSearchFlagOptionType[];
  73. value?: string;
  74. onChange?(value: string): void;
  75. };
  76. export const SelectWithSearch = forwardRef<
  77. React.ElementRef<typeof Button>,
  78. SelectWithSearchFlagProps
  79. >(({ value: val = '', onChange, options = countries }, ref) => {
  80. const id = useId();
  81. const [open, setOpen] = useState<boolean>(false);
  82. const [value, setValue] = useState<string>('');
  83. const handleSelect = useCallback(
  84. (val: string) => {
  85. setValue(val);
  86. setOpen(false);
  87. onChange?.(val);
  88. },
  89. [onChange],
  90. );
  91. useEffect(() => {
  92. setValue(val);
  93. }, [val]);
  94. return (
  95. <Popover open={open} onOpenChange={setOpen}>
  96. <PopoverTrigger asChild>
  97. <Button
  98. id={id}
  99. variant="outline"
  100. role="combobox"
  101. aria-expanded={open}
  102. ref={ref}
  103. className="bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]"
  104. >
  105. {value ? (
  106. <span className="flex min-w-0 options-center gap-2">
  107. <span className="text-lg leading-none truncate">
  108. {
  109. options
  110. .map((group) =>
  111. group.options.find((item) => item.value === value),
  112. )
  113. .filter(Boolean)[0]?.label
  114. }
  115. </span>
  116. </span>
  117. ) : (
  118. <span className="text-muted-foreground">Select value</span>
  119. )}
  120. <ChevronDownIcon
  121. size={16}
  122. className="text-muted-foreground/80 shrink-0"
  123. aria-hidden="true"
  124. />
  125. </Button>
  126. </PopoverTrigger>
  127. <PopoverContent
  128. className="border-input w-full min-w-[var(--radix-popper-anchor-width)] p-0"
  129. align="start"
  130. >
  131. <Command>
  132. <CommandInput placeholder="Search ..." />
  133. <CommandList>
  134. <CommandEmpty>No data found.</CommandEmpty>
  135. {options.map((group) => (
  136. <Fragment key={group.label}>
  137. <CommandGroup heading={group.label}>
  138. {group.options.map((option) => (
  139. <CommandItem
  140. key={option.value}
  141. value={option.value}
  142. onSelect={handleSelect}
  143. >
  144. <span className="text-lg leading-none">
  145. {option.label}
  146. </span>
  147. {option.value}
  148. {value === option.value && (
  149. <CheckIcon size={16} className="ml-auto" />
  150. )}
  151. </CommandItem>
  152. ))}
  153. </CommandGroup>
  154. </Fragment>
  155. ))}
  156. </CommandList>
  157. </Command>
  158. </PopoverContent>
  159. </Popover>
  160. );
  161. });