@djangocfg/ui-core 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +135 -0
  2. package/package.json +111 -0
  3. package/src/components/accordion.tsx +56 -0
  4. package/src/components/alert-dialog.tsx +142 -0
  5. package/src/components/alert.tsx +59 -0
  6. package/src/components/aspect-ratio.tsx +7 -0
  7. package/src/components/avatar.tsx +50 -0
  8. package/src/components/badge.tsx +36 -0
  9. package/src/components/button-group.tsx +85 -0
  10. package/src/components/button.tsx +111 -0
  11. package/src/components/calendar.tsx +213 -0
  12. package/src/components/card.tsx +76 -0
  13. package/src/components/carousel.tsx +261 -0
  14. package/src/components/chart.tsx +369 -0
  15. package/src/components/checkbox.tsx +29 -0
  16. package/src/components/collapsible.tsx +11 -0
  17. package/src/components/combobox.tsx +182 -0
  18. package/src/components/command.tsx +170 -0
  19. package/src/components/context-menu.tsx +200 -0
  20. package/src/components/copy.tsx +144 -0
  21. package/src/components/dialog.tsx +122 -0
  22. package/src/components/drawer.tsx +137 -0
  23. package/src/components/empty.tsx +104 -0
  24. package/src/components/field.tsx +244 -0
  25. package/src/components/form.tsx +178 -0
  26. package/src/components/hover-card.tsx +29 -0
  27. package/src/components/image-with-fallback.tsx +170 -0
  28. package/src/components/index.ts +86 -0
  29. package/src/components/input-group.tsx +170 -0
  30. package/src/components/input-otp.tsx +81 -0
  31. package/src/components/input.tsx +22 -0
  32. package/src/components/item.tsx +195 -0
  33. package/src/components/kbd.tsx +28 -0
  34. package/src/components/label.tsx +26 -0
  35. package/src/components/multi-select.tsx +222 -0
  36. package/src/components/og-image.tsx +47 -0
  37. package/src/components/popover.tsx +33 -0
  38. package/src/components/portal.tsx +106 -0
  39. package/src/components/preloader.tsx +250 -0
  40. package/src/components/progress.tsx +28 -0
  41. package/src/components/radio-group.tsx +43 -0
  42. package/src/components/resizable.tsx +111 -0
  43. package/src/components/scroll-area.tsx +102 -0
  44. package/src/components/section.tsx +58 -0
  45. package/src/components/select.tsx +158 -0
  46. package/src/components/separator.tsx +31 -0
  47. package/src/components/sheet.tsx +140 -0
  48. package/src/components/skeleton.tsx +15 -0
  49. package/src/components/slider.tsx +28 -0
  50. package/src/components/spinner.tsx +16 -0
  51. package/src/components/sticky.tsx +117 -0
  52. package/src/components/switch.tsx +29 -0
  53. package/src/components/table.tsx +120 -0
  54. package/src/components/tabs.tsx +238 -0
  55. package/src/components/textarea.tsx +22 -0
  56. package/src/components/toast.tsx +129 -0
  57. package/src/components/toaster.tsx +41 -0
  58. package/src/components/toggle-group.tsx +61 -0
  59. package/src/components/toggle.tsx +45 -0
  60. package/src/components/token-icon.tsx +156 -0
  61. package/src/components/tooltip-provider-safe.tsx +43 -0
  62. package/src/components/tooltip.tsx +32 -0
  63. package/src/hooks/index.ts +15 -0
  64. package/src/hooks/useCopy.ts +41 -0
  65. package/src/hooks/useCountdown.ts +73 -0
  66. package/src/hooks/useDebounce.ts +25 -0
  67. package/src/hooks/useDebouncedCallback.ts +58 -0
  68. package/src/hooks/useDebugTools.ts +52 -0
  69. package/src/hooks/useEventsBus.ts +53 -0
  70. package/src/hooks/useImageLoader.ts +95 -0
  71. package/src/hooks/useMediaQuery.ts +40 -0
  72. package/src/hooks/useMobile.tsx +22 -0
  73. package/src/hooks/useToast.ts +194 -0
  74. package/src/index.ts +14 -0
  75. package/src/lib/index.ts +2 -0
  76. package/src/lib/og-image.ts +151 -0
  77. package/src/lib/utils.ts +6 -0
  78. package/src/styles/base.css +20 -0
  79. package/src/styles/globals.css +12 -0
  80. package/src/styles/index.css +25 -0
  81. package/src/styles/sources.css +11 -0
  82. package/src/styles/theme/animations.css +65 -0
  83. package/src/styles/theme/dark.css +49 -0
  84. package/src/styles/theme/light.css +50 -0
  85. package/src/styles/theme/tokens.css +134 -0
  86. package/src/styles/theme.css +22 -0
  87. package/src/styles/utilities.css +187 -0
  88. package/src/types/index.ts +0 -0
@@ -0,0 +1,369 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode
14
+ icon?: React.ComponentType
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ )
19
+ }
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig
23
+ }
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext)
29
+
30
+ if (!context) {
31
+ throw new Error("useChart must be used within a <ChartContainer />")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ const ChartContainer = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.ComponentProps<"div"> & {
40
+ config: ChartConfig
41
+ children: React.ComponentProps<
42
+ typeof RechartsPrimitive.ResponsiveContainer
43
+ >["children"]
44
+ }
45
+ >(({ id, className, children, config, ...props }, ref) => {
46
+ const uniqueId = React.useId()
47
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48
+
49
+ return (
50
+ <ChartContext.Provider value={{ config }}>
51
+ <div
52
+ data-chart={chartId}
53
+ ref={ref}
54
+ className={cn(
55
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ <ChartStyle id={chartId} config={config} />
61
+ <RechartsPrimitive.ResponsiveContainer>
62
+ {children}
63
+ </RechartsPrimitive.ResponsiveContainer>
64
+ </div>
65
+ </ChartContext.Provider>
66
+ )
67
+ })
68
+ ChartContainer.displayName = "Chart"
69
+
70
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71
+ const colorConfig = Object.entries(config).filter(
72
+ ([, config]) => config.theme || config.color
73
+ )
74
+
75
+ if (!colorConfig.length) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <style
81
+ dangerouslySetInnerHTML={{
82
+ __html: Object.entries(THEMES)
83
+ .map(
84
+ ([theme, prefix]) => `
85
+ ${prefix} [data-chart=${id}] {
86
+ ${colorConfig
87
+ .map(([key, itemConfig]) => {
88
+ const color =
89
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
90
+ itemConfig.color
91
+ return color ? ` --color-${key}: ${color};` : null
92
+ })
93
+ .join("\n")}
94
+ }
95
+ `
96
+ )
97
+ .join("\n"),
98
+ }}
99
+ />
100
+ )
101
+ }
102
+
103
+ const ChartTooltip = RechartsPrimitive.Tooltip
104
+
105
+ const ChartTooltipContent = React.forwardRef<
106
+ HTMLDivElement,
107
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
108
+ React.ComponentProps<"div"> & {
109
+ hideLabel?: boolean
110
+ hideIndicator?: boolean
111
+ indicator?: "line" | "dot" | "dashed"
112
+ nameKey?: string
113
+ labelKey?: string
114
+ }
115
+ >(
116
+ (
117
+ {
118
+ active,
119
+ payload,
120
+ className,
121
+ indicator = "dot",
122
+ hideLabel = false,
123
+ hideIndicator = false,
124
+ label,
125
+ labelFormatter,
126
+ labelClassName,
127
+ formatter,
128
+ color,
129
+ nameKey,
130
+ labelKey,
131
+ },
132
+ ref
133
+ ) => {
134
+ const { config } = useChart()
135
+
136
+ const tooltipLabel = React.useMemo(() => {
137
+ if (hideLabel || !payload?.length) {
138
+ return null
139
+ }
140
+
141
+ const [item] = payload
142
+ const key = `${labelKey || item?.dataKey || item?.name || "value"}`
143
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
144
+ const value =
145
+ !labelKey && typeof label === "string"
146
+ ? config[label as keyof typeof config]?.label || label
147
+ : itemConfig?.label
148
+
149
+ if (labelFormatter) {
150
+ return (
151
+ <div className={cn("font-medium", labelClassName)}>
152
+ {labelFormatter(value, payload)}
153
+ </div>
154
+ )
155
+ }
156
+
157
+ if (!value) {
158
+ return null
159
+ }
160
+
161
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
162
+ }, [
163
+ label,
164
+ labelFormatter,
165
+ payload,
166
+ hideLabel,
167
+ labelClassName,
168
+ config,
169
+ labelKey,
170
+ ])
171
+
172
+ if (!active || !payload?.length) {
173
+ return null
174
+ }
175
+
176
+ const nestLabel = payload.length === 1 && indicator !== "dot"
177
+
178
+ return (
179
+ <div
180
+ ref={ref}
181
+ className={cn(
182
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
183
+ className
184
+ )}
185
+ >
186
+ {!nestLabel ? tooltipLabel : null}
187
+ <div className="grid gap-1.5">
188
+ {payload
189
+ .filter((item) => item.type !== "none")
190
+ .map((item, index) => {
191
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
192
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
193
+ const indicatorColor = color || item.payload.fill || item.color
194
+
195
+ return (
196
+ <div
197
+ key={item.dataKey}
198
+ className={cn(
199
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
200
+ indicator === "dot" && "items-center"
201
+ )}
202
+ >
203
+ {formatter && item?.value !== undefined && item.name ? (
204
+ formatter(item.value, item.name, item, index, item.payload)
205
+ ) : (
206
+ <>
207
+ {itemConfig?.icon ? (
208
+ <itemConfig.icon />
209
+ ) : (
210
+ !hideIndicator && (
211
+ <div
212
+ className={cn(
213
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
214
+ {
215
+ "h-2.5 w-2.5": indicator === "dot",
216
+ "w-1": indicator === "line",
217
+ "w-0 border-[1.5px] border-dashed bg-transparent":
218
+ indicator === "dashed",
219
+ "my-0.5": nestLabel && indicator === "dashed",
220
+ }
221
+ )}
222
+ style={
223
+ {
224
+ "--color-bg": indicatorColor,
225
+ "--color-border": indicatorColor,
226
+ } as React.CSSProperties
227
+ }
228
+ />
229
+ )
230
+ )}
231
+ <div
232
+ className={cn(
233
+ "flex flex-1 justify-between leading-none",
234
+ nestLabel ? "items-end" : "items-center"
235
+ )}
236
+ >
237
+ <div className="grid gap-1.5">
238
+ {nestLabel ? tooltipLabel : null}
239
+ <span className="text-muted-foreground">
240
+ {itemConfig?.label || item.name}
241
+ </span>
242
+ </div>
243
+ {item.value && (
244
+ <span className="font-mono font-medium tabular-nums text-foreground">
245
+ {item.value.toLocaleString()}
246
+ </span>
247
+ )}
248
+ </div>
249
+ </>
250
+ )}
251
+ </div>
252
+ )
253
+ })}
254
+ </div>
255
+ </div>
256
+ )
257
+ }
258
+ )
259
+ ChartTooltipContent.displayName = "ChartTooltip"
260
+
261
+ const ChartLegend = RechartsPrimitive.Legend
262
+
263
+ const ChartLegendContent = React.forwardRef<
264
+ HTMLDivElement,
265
+ React.ComponentProps<"div"> &
266
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
267
+ hideIcon?: boolean
268
+ nameKey?: string
269
+ }
270
+ >(
271
+ (
272
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
273
+ ref
274
+ ) => {
275
+ const { config } = useChart()
276
+
277
+ if (!payload?.length) {
278
+ return null
279
+ }
280
+
281
+ return (
282
+ <div
283
+ ref={ref}
284
+ className={cn(
285
+ "flex items-center justify-center gap-4",
286
+ verticalAlign === "top" ? "pb-3" : "pt-3",
287
+ className
288
+ )}
289
+ >
290
+ {payload
291
+ .filter((item) => item.type !== "none")
292
+ .map((item) => {
293
+ const key = `${nameKey || item.dataKey || "value"}`
294
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
295
+
296
+ return (
297
+ <div
298
+ key={item.value}
299
+ className={cn(
300
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
301
+ )}
302
+ >
303
+ {itemConfig?.icon && !hideIcon ? (
304
+ <itemConfig.icon />
305
+ ) : (
306
+ <div
307
+ className="h-2 w-2 shrink-0 rounded-[2px]"
308
+ style={{
309
+ backgroundColor: item.color,
310
+ }}
311
+ />
312
+ )}
313
+ {itemConfig?.label}
314
+ </div>
315
+ )
316
+ })}
317
+ </div>
318
+ )
319
+ }
320
+ )
321
+ ChartLegendContent.displayName = "ChartLegend"
322
+
323
+ // Helper to extract item config from a payload.
324
+ function getPayloadConfigFromPayload(
325
+ config: ChartConfig,
326
+ payload: unknown,
327
+ key: string
328
+ ) {
329
+ if (typeof payload !== "object" || payload === null) {
330
+ return undefined
331
+ }
332
+
333
+ const payloadPayload =
334
+ "payload" in payload &&
335
+ typeof payload.payload === "object" &&
336
+ payload.payload !== null
337
+ ? payload.payload
338
+ : undefined
339
+
340
+ let configLabelKey: string = key
341
+
342
+ if (
343
+ key in payload &&
344
+ typeof payload[key as keyof typeof payload] === "string"
345
+ ) {
346
+ configLabelKey = payload[key as keyof typeof payload] as string
347
+ } else if (
348
+ payloadPayload &&
349
+ key in payloadPayload &&
350
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
351
+ ) {
352
+ configLabelKey = payloadPayload[
353
+ key as keyof typeof payloadPayload
354
+ ] as string
355
+ }
356
+
357
+ return configLabelKey in config
358
+ ? config[configLabelKey]
359
+ : config[key as keyof typeof config]
360
+ }
361
+
362
+ export {
363
+ ChartContainer,
364
+ ChartTooltip,
365
+ ChartTooltipContent,
366
+ ChartLegend,
367
+ ChartLegendContent,
368
+ ChartStyle,
369
+ }
@@ -0,0 +1,29 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { cn } from "../lib/utils"
6
+ import { CheckIcon } from "@radix-ui/react-icons"
7
+
8
+ const Checkbox = React.forwardRef<
9
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <CheckboxPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ <CheckboxPrimitive.Indicator
21
+ className={cn("flex items-center justify-center text-current")}
22
+ >
23
+ <CheckIcon className="h-4 w-4" />
24
+ </CheckboxPrimitive.Indicator>
25
+ </CheckboxPrimitive.Root>
26
+ ))
27
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
28
+
29
+ export { Checkbox }
@@ -0,0 +1,11 @@
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
@@ -0,0 +1,182 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Check, ChevronsUpDown } from "lucide-react"
5
+ import { cn } from "../lib/utils"
6
+ import { Button } from "./button"
7
+ import {
8
+ Command,
9
+ CommandEmpty,
10
+ CommandGroup,
11
+ CommandInput,
12
+ CommandItem,
13
+ CommandList,
14
+ } from "./command"
15
+ import {
16
+ Popover,
17
+ PopoverContent,
18
+ PopoverTrigger,
19
+ } from "./popover"
20
+
21
+ export interface ComboboxOption {
22
+ value: string
23
+ label: string
24
+ description?: string
25
+ disabled?: boolean
26
+ }
27
+
28
+ export interface ComboboxProps {
29
+ options: ComboboxOption[]
30
+ value?: string
31
+ onValueChange?: (value: string) => void
32
+ placeholder?: string
33
+ searchPlaceholder?: string
34
+ emptyText?: string
35
+ className?: string
36
+ disabled?: boolean
37
+ renderOption?: (option: ComboboxOption) => React.ReactNode
38
+ renderValue?: (option: ComboboxOption | undefined) => React.ReactNode
39
+ }
40
+
41
+ export function Combobox({
42
+ options,
43
+ value,
44
+ onValueChange,
45
+ placeholder = "Select option...",
46
+ searchPlaceholder = "Search...",
47
+ emptyText = "No results found.",
48
+ className,
49
+ disabled = false,
50
+ renderOption,
51
+ renderValue,
52
+ }: ComboboxProps) {
53
+ const [open, setOpen] = React.useState(false)
54
+ const [search, setSearch] = React.useState("")
55
+ const scrollRef = React.useRef<HTMLDivElement>(null)
56
+
57
+ React.useEffect(() => {
58
+ if (scrollRef.current && open) {
59
+ // Force scrollable styles with !important
60
+ const el = scrollRef.current
61
+ el.style.cssText = `
62
+ max-height: 300px !important;
63
+ overflow-y: scroll !important;
64
+ overflow-x: hidden !important;
65
+ -webkit-overflow-scrolling: touch !important;
66
+ overscroll-behavior: contain !important;
67
+ `
68
+ }
69
+ }, [open])
70
+
71
+ const selectedOption = React.useMemo(
72
+ () => options.find((option) => option.value === value),
73
+ [options, value]
74
+ )
75
+
76
+ const filteredOptions = React.useMemo(() => {
77
+ if (!search) return options
78
+ const searchLower = search.toLowerCase()
79
+ return options.filter(
80
+ (option) =>
81
+ option.label.toLowerCase().includes(searchLower) ||
82
+ option.value.toLowerCase().includes(searchLower) ||
83
+ option.description?.toLowerCase().includes(searchLower)
84
+ )
85
+ }, [options, search])
86
+
87
+ return (
88
+ <Popover
89
+ open={open}
90
+ onOpenChange={(isOpen) => {
91
+ setOpen(isOpen)
92
+ if (!isOpen) {
93
+ setSearch("")
94
+ }
95
+ }}
96
+ >
97
+ <PopoverTrigger asChild>
98
+ <Button
99
+ variant="outline"
100
+ role="combobox"
101
+ aria-expanded={open}
102
+ className={cn(
103
+ "w-full justify-between",
104
+ !value && "text-muted-foreground",
105
+ className
106
+ )}
107
+ disabled={disabled}
108
+ >
109
+ {renderValue && selectedOption
110
+ ? renderValue(selectedOption)
111
+ : selectedOption
112
+ ? selectedOption.label
113
+ : placeholder}
114
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
115
+ </Button>
116
+ </PopoverTrigger>
117
+ <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0" align="start">
118
+ <Command shouldFilter={false} className="flex flex-col">
119
+ <CommandInput
120
+ placeholder={searchPlaceholder}
121
+ className="shrink-0"
122
+ value={search}
123
+ onValueChange={setSearch}
124
+ />
125
+ <div
126
+ ref={scrollRef}
127
+ tabIndex={-1}
128
+ className="overflow-y-scroll overflow-x-hidden"
129
+ style={{
130
+ maxHeight: '300px',
131
+ minHeight: '100px',
132
+ }}
133
+ onWheel={(e) => {
134
+ e.stopPropagation()
135
+ }}
136
+ >
137
+ <CommandList className="!max-h-none !overflow-visible" style={{ pointerEvents: 'auto' }}>
138
+ {filteredOptions.length === 0 ? (
139
+ <CommandEmpty>{emptyText}</CommandEmpty>
140
+ ) : (
141
+ <CommandGroup className="!overflow-visible" style={{ pointerEvents: 'auto' }}>
142
+ {filteredOptions.map((option) => (
143
+ <CommandItem
144
+ key={option.value}
145
+ value={option.value}
146
+ onSelect={(currentValue) => {
147
+ if (!option.disabled) {
148
+ onValueChange?.(currentValue === value ? "" : currentValue)
149
+ setOpen(false)
150
+ }
151
+ }}
152
+ disabled={option.disabled}
153
+ >
154
+ <Check
155
+ className={cn(
156
+ "mr-2 h-4 w-4 shrink-0",
157
+ value === option.value ? "opacity-100" : "opacity-0"
158
+ )}
159
+ />
160
+ {renderOption ? (
161
+ renderOption(option)
162
+ ) : (
163
+ <div className="flex flex-col flex-1 min-w-0">
164
+ <span className="truncate">{option.label}</span>
165
+ {option.description && (
166
+ <span className="text-xs text-muted-foreground truncate">
167
+ {option.description}
168
+ </span>
169
+ )}
170
+ </div>
171
+ )}
172
+ </CommandItem>
173
+ ))}
174
+ </CommandGroup>
175
+ )}
176
+ </CommandList>
177
+ </div>
178
+ </Command>
179
+ </PopoverContent>
180
+ </Popover>
181
+ )
182
+ }