SchemaComboBox.tsx139 lines · main
1import { Check, ChevronsUpDown } from 'lucide-react'
2import { useState } from 'react'
3import {
4 Alert,
5 AlertDescription,
6 AlertTitle,
7 Button,
8 Command,
9 CommandEmpty,
10 CommandGroup,
11 CommandInput,
12 CommandItem,
13 CommandList,
14 Popover,
15 PopoverContent,
16 PopoverTrigger,
17 ScrollArea,
18} from 'ui'
19
20import { useSchemasQuery } from '@/data/database/schemas-query'
21import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
22
23interface SchemaComboBoxProps {
24 className?: string
25 disabled?: boolean
26 size?: 'tiny' | 'small'
27 showError?: boolean
28 selectedSchemas: string[]
29 supportSelectAll?: boolean
30 excludedSchemas?: string[]
31 onSelectSchemas: (schemas: string[]) => void
32 label: string
33}
34
35export const SchemaComboBox = ({
36 className,
37 disabled = false,
38 size = 'tiny',
39 showError = true,
40 selectedSchemas = [],
41 excludedSchemas = [],
42 label,
43 onSelectSchemas,
44}: SchemaComboBoxProps) => {
45 const [open, setOpen] = useState(false)
46
47 const { data: project } = useSelectedProjectQuery()
48 const {
49 data,
50 isPending: isSchemasLoading,
51 isSuccess: isSchemasSuccess,
52 isError: isSchemasError,
53 error: schemasError,
54 refetch: refetchSchemas,
55 } = useSchemasQuery({
56 projectRef: project?.ref,
57 connectionString: project?.connectionString,
58 })
59
60 const schemas = (data || [])
61 .filter((schema) => !excludedSchemas.includes(schema.name))
62 .sort((a, b) => a.name.localeCompare(b.name))
63
64 const toggleSchema = (schema: string) => {
65 if (selectedSchemas.includes(schema)) {
66 onSelectSchemas(selectedSchemas.filter((s) => s !== schema))
67 } else {
68 const newSelectedSchemas = [...selectedSchemas, schema].sort((a, b) => a.localeCompare(b))
69 onSelectSchemas(newSelectedSchemas)
70 }
71 }
72
73 return (
74 <div className={className}>
75 {isSchemasLoading && (
76 <Button type="default" className="justify-start" block size={size} loading>
77 Loading schemas...
78 </Button>
79 )}
80
81 {showError && isSchemasError && (
82 <Alert variant="warning" className="px-3! py-3!">
83 <AlertTitle className="text-xs text-amber-900">Failed to load schemas</AlertTitle>
84 <AlertDescription className="text-xs mb-2 wrap-break-word">
85 Error: {(schemasError as any)?.message}
86 </AlertDescription>
87 <Button type="default" size="tiny" onClick={() => refetchSchemas()}>
88 Reload schemas
89 </Button>
90 </Alert>
91 )}
92
93 {isSchemasSuccess && (
94 <Popover open={open} onOpenChange={setOpen} modal={false}>
95 <PopoverTrigger asChild>
96 <Button
97 size={size}
98 disabled={disabled}
99 type="default"
100 className={`w-full [&>span]:w-full`}
101 iconRight={
102 <ChevronsUpDown className="text-foreground-muted" strokeWidth={2} size={14} />
103 }
104 >
105 <div className="w-full flex">
106 <p className="text-foreground">{label}</p>
107 </div>
108 </Button>
109 </PopoverTrigger>
110 <PopoverContent className="p-0 w-56" side="bottom" align="start">
111 <Command>
112 <CommandInput placeholder="Find schema..." />
113 <CommandList>
114 <CommandEmpty>No schemas found</CommandEmpty>
115 <CommandGroup>
116 <ScrollArea className={(schemas || []).length > 7 ? 'h-[210px]' : ''}>
117 {schemas?.map((schema) => (
118 <CommandItem
119 key={schema.id}
120 className="cursor-pointer flex items-center justify-between space-x-2 w-full"
121 onSelect={() => toggleSchema(schema.name)}
122 onClick={() => toggleSchema(schema.name)}
123 >
124 <span>{schema.name}</span>
125 {selectedSchemas.includes(schema.name) && (
126 <Check className="text-brand" strokeWidth={2} size={16} />
127 )}
128 </CommandItem>
129 ))}
130 </ScrollArea>
131 </CommandGroup>
132 </CommandList>
133 </Command>
134 </PopoverContent>
135 </Popover>
136 )}
137 </div>
138 )
139}