@inferencesh/ui 0.0.0-stage → 0.1.2

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 (165) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -2
  3. package/components.json +22 -0
  4. package/package.json +231 -4
  5. package/src/components/audio-player.tsx +549 -0
  6. package/src/components/button.tsx +13 -0
  7. package/src/components/chart.tsx +403 -0
  8. package/src/components/circuit.tsx +276 -0
  9. package/src/components/code-block/code-block.tsx +260 -0
  10. package/src/components/code-block/index.ts +18 -0
  11. package/src/components/code-block/languages.ts +311 -0
  12. package/src/components/code-block/styles.ts +39 -0
  13. package/src/components/code-block/tokenizer.ts +290 -0
  14. package/src/components/code-block/types.ts +38 -0
  15. package/src/components/code-block/utils.ts +39 -0
  16. package/src/components/code.tsx +48 -0
  17. package/src/components/cookie-consent.tsx +137 -0
  18. package/src/components/copy-button.tsx +44 -0
  19. package/src/components/country-dropdown.tsx +188 -0
  20. package/src/components/glow-loader-fast.tsx +32 -0
  21. package/src/components/glow-loader-mono.tsx +33 -0
  22. package/src/components/glow-loader-sm-fast.tsx +32 -0
  23. package/src/components/glow-loader-sm-mono.tsx +34 -0
  24. package/src/components/glow-loader-sm.tsx +32 -0
  25. package/src/components/glow-loader.tsx +32 -0
  26. package/src/components/glow-logo.tsx +32 -0
  27. package/src/components/gradient.tsx +80 -0
  28. package/src/components/index.ts +45 -0
  29. package/src/components/inline-sidebar.tsx +351 -0
  30. package/src/components/input-color.tsx +349 -0
  31. package/src/components/logo-base.tsx +213 -0
  32. package/src/components/logo-color.tsx +23 -0
  33. package/src/components/logo-full.tsx +71 -0
  34. package/src/components/logo-svg.tsx +23 -0
  35. package/src/components/menu-navigation.tsx +359 -0
  36. package/src/components/multi-select.tsx +721 -0
  37. package/src/components/pastel-gradient.tsx +25 -0
  38. package/src/components/pixel-symmetry.tsx +151 -0
  39. package/src/components/progress.tsx +28 -0
  40. package/src/components/scroll-more.tsx +131 -0
  41. package/src/components/select.tsx +85 -0
  42. package/src/components/shimmering-text.tsx +108 -0
  43. package/src/components/sidebar-light.tsx +102 -0
  44. package/src/components/sonner.tsx +38 -0
  45. package/src/components/theme-switcher-sidebar.tsx +56 -0
  46. package/src/components/theme-switcher-simple.tsx +110 -0
  47. package/src/components/theme-switcher.tsx +47 -0
  48. package/src/components/toggle-group.tsx +55 -0
  49. package/src/components/tooltip-button.tsx +91 -0
  50. package/src/components/tooltip.tsx +71 -0
  51. package/src/components/tree-view.tsx +499 -0
  52. package/src/components/unglow-loader.tsx +33 -0
  53. package/src/components/waveform.tsx +1632 -0
  54. package/src/components/zoomable-image.tsx +76 -0
  55. package/src/docs/api-docs.tsx +763 -0
  56. package/src/docs/callout.tsx +160 -0
  57. package/src/docs/code-group.tsx +215 -0
  58. package/src/docs/copy-menu.tsx +143 -0
  59. package/src/docs/doc-tabs.tsx +84 -0
  60. package/src/docs/index.ts +16 -0
  61. package/src/docs/steps.tsx +105 -0
  62. package/src/docs/table-of-contents.tsx +174 -0
  63. package/src/examples/guide-markdown.ts +129 -0
  64. package/src/examples/index.ts +1153 -0
  65. package/src/global.d.ts +1 -0
  66. package/src/hooks/use-copy-to-clipboard.ts +36 -0
  67. package/src/hooks/use-mobile.ts +19 -0
  68. package/src/lib/display-name.ts +15 -0
  69. package/src/lib/example-input.ts +130 -0
  70. package/src/lib/plans.ts +44 -0
  71. package/src/lib/safe-content.ts +137 -0
  72. package/src/lib/schema.ts +320 -0
  73. package/src/lib/storage.ts +108 -0
  74. package/src/markdown/docs-markdown-renderer.tsx +518 -0
  75. package/src/markdown/helpers.ts +95 -0
  76. package/src/markdown/index.ts +24 -0
  77. package/src/markdown/markdown-renderer.tsx +232 -0
  78. package/src/markdown/youtube-embed.tsx +43 -0
  79. package/src/og/image.ts +312 -0
  80. package/src/og/index.tsx +430 -0
  81. package/src/og/query.ts +26 -0
  82. package/src/shadcn/accordion.tsx +71 -0
  83. package/src/shadcn/alert-dialog.tsx +187 -0
  84. package/src/shadcn/alert.tsx +75 -0
  85. package/src/shadcn/aspect-ratio.tsx +22 -0
  86. package/src/shadcn/attachment.tsx +207 -0
  87. package/src/shadcn/avatar.tsx +106 -0
  88. package/src/shadcn/badge.tsx +51 -0
  89. package/src/shadcn/breadcrumb.tsx +124 -0
  90. package/src/shadcn/bubble.tsx +127 -0
  91. package/src/shadcn/button-group.tsx +87 -0
  92. package/src/shadcn/button.tsx +57 -0
  93. package/src/shadcn/calendar.tsx +219 -0
  94. package/src/shadcn/card.tsx +102 -0
  95. package/src/shadcn/carousel.tsx +242 -0
  96. package/src/shadcn/chart.tsx +372 -0
  97. package/src/shadcn/checkbox.tsx +28 -0
  98. package/src/shadcn/collapsible.tsx +19 -0
  99. package/src/shadcn/combobox.tsx +297 -0
  100. package/src/shadcn/command.tsx +194 -0
  101. package/src/shadcn/context-menu.tsx +270 -0
  102. package/src/shadcn/dialog.tsx +158 -0
  103. package/src/shadcn/direction.tsx +4 -0
  104. package/src/shadcn/drawer.tsx +227 -0
  105. package/src/shadcn/dropdown-menu.tsx +265 -0
  106. package/src/shadcn/empty.tsx +103 -0
  107. package/src/shadcn/field.tsx +238 -0
  108. package/src/shadcn/form.tsx +173 -0
  109. package/src/shadcn/hover-card.tsx +50 -0
  110. package/src/shadcn/index.ts +53 -0
  111. package/src/shadcn/input-group.tsx +156 -0
  112. package/src/shadcn/input-otp.tsx +84 -0
  113. package/src/shadcn/input.tsx +19 -0
  114. package/src/shadcn/item.tsx +201 -0
  115. package/src/shadcn/kbd.tsx +26 -0
  116. package/src/shadcn/label.tsx +19 -0
  117. package/src/shadcn/marker.tsx +70 -0
  118. package/src/shadcn/menubar.tsx +278 -0
  119. package/src/shadcn/message-scroller.tsx +131 -0
  120. package/src/shadcn/message.tsx +91 -0
  121. package/src/shadcn/native-select.tsx +60 -0
  122. package/src/shadcn/navigation-menu.tsx +167 -0
  123. package/src/shadcn/pagination.tsx +130 -0
  124. package/src/shadcn/popover.tsx +87 -0
  125. package/src/shadcn/progress.tsx +82 -0
  126. package/src/shadcn/radio-group.tsx +35 -0
  127. package/src/shadcn/resizable.tsx +49 -0
  128. package/src/shadcn/scroll-area.tsx +52 -0
  129. package/src/shadcn/select.tsx +200 -0
  130. package/src/shadcn/separator.tsx +22 -0
  131. package/src/shadcn/sheet.tsx +136 -0
  132. package/src/shadcn/sidebar.tsx +721 -0
  133. package/src/shadcn/skeleton.tsx +13 -0
  134. package/src/shadcn/slider.tsx +51 -0
  135. package/src/shadcn/sonner.tsx +49 -0
  136. package/src/shadcn/spinner.tsx +10 -0
  137. package/src/shadcn/switch.tsx +29 -0
  138. package/src/shadcn/table.tsx +115 -0
  139. package/src/shadcn/tabs.tsx +79 -0
  140. package/src/shadcn/textarea.tsx +17 -0
  141. package/src/shadcn/toggle-group.tsx +89 -0
  142. package/src/shadcn/toggle.tsx +44 -0
  143. package/src/shadcn/tooltip.tsx +63 -0
  144. package/src/theme/config.ts +48 -0
  145. package/src/theme/css/base.css +92 -0
  146. package/src/theme/css/bitboy.css +75 -0
  147. package/src/theme/css/dark.css +47 -0
  148. package/src/theme/css/designer.css +122 -0
  149. package/src/theme/css/fonts.css +33 -0
  150. package/src/theme/css/globals.css +14 -0
  151. package/src/theme/css/hackintosh.css +57 -0
  152. package/src/theme/css/llama.css +103 -0
  153. package/src/theme/css/safelist.css +39 -0
  154. package/src/theme/css/shadcn.css +77 -0
  155. package/src/theme/css/theme.css +216 -0
  156. package/src/theme/embed-theme.ts +146 -0
  157. package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
  158. package/src/theme/fonts/Hack-Regular.woff2 +0 -0
  159. package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
  160. package/src/theme/fonts/LICENSE-Hack.txt +45 -0
  161. package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
  162. package/src/theme/fonts/Mona-Sans.woff2 +0 -0
  163. package/src/theme/index.ts +15 -0
  164. package/src/utils/cn.ts +3 -0
  165. package/src/utils/index.ts +2 -0
@@ -0,0 +1,721 @@
1
+ // src/components/multi-select.tsx
2
+
3
+ import { cva } from "class-variance-authority";
4
+ import {
5
+ CheckIcon,
6
+ ChevronDown,
7
+ ChevronRight
8
+ } from "lucide-react";
9
+ import * as React from "react";
10
+
11
+ import {
12
+ Command,
13
+ CommandEmpty,
14
+ CommandGroup,
15
+ CommandInput,
16
+ CommandItem,
17
+ CommandList,
18
+ CommandSeparator
19
+ } from "../shadcn/command";
20
+ import {
21
+ Popover,
22
+ PopoverContent,
23
+ PopoverTrigger
24
+ } from "../shadcn/popover";
25
+ import {
26
+ Tooltip,
27
+ TooltipContent,
28
+ TooltipProvider,
29
+ TooltipTrigger
30
+ } from "./tooltip";
31
+ import { cn } from "cn";
32
+
33
+ /**
34
+ * Variants for the multi-select component to handle different styles.
35
+ * Uses class-variance-authority (cva) to define different styles based on "variant" prop.
36
+ */
37
+ const multiSelectVariants = cva(
38
+ "m-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300",
39
+ {
40
+ variants: {
41
+ variant: {
42
+ default:
43
+ "border-foreground/10 text-foreground bg-card hover:bg-card/80",
44
+ secondary:
45
+ "border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80",
46
+ destructive:
47
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
48
+ inverted: "inverted",
49
+ },
50
+ },
51
+ defaultVariants: {
52
+ variant: "default",
53
+ },
54
+ }
55
+ );
56
+
57
+ export interface MultiSelectOption {
58
+ label: React.ReactNode;
59
+ value: string;
60
+ description?: React.ReactNode;
61
+ icon?: React.ReactNode;
62
+ children?: MultiSelectOption[];
63
+ disabled?: boolean;
64
+ tooltip?: React.ReactNode;
65
+ isSection?: boolean;
66
+ hideChildren?: boolean;
67
+ }
68
+
69
+ interface MultiSelectContextValue {
70
+ value: string[];
71
+ options: MultiSelectOption[];
72
+ isOpen: boolean;
73
+ onOpenChange: (open: boolean) => void;
74
+ findOptionByValue: (value: string) => MultiSelectOption | undefined;
75
+ placeholder?: string;
76
+ }
77
+
78
+ const MultiSelectContext = React.createContext<MultiSelectContextValue | undefined>(undefined);
79
+
80
+ function useMultiSelect() {
81
+ const context = React.useContext(MultiSelectContext);
82
+ if (!context) {
83
+ throw new Error("useMultiSelect must be used within a MultiSelect");
84
+ }
85
+ return context;
86
+ }
87
+
88
+ export interface MultiSelectTriggerProps extends React.HTMLAttributes<HTMLDivElement> {
89
+ children?: React.ReactNode;
90
+ placeholder?: string;
91
+ }
92
+
93
+ export const MultiSelectTrigger = React.forwardRef<HTMLDivElement, MultiSelectTriggerProps>(
94
+ ({ className, children, placeholder, ...props }, ref) => {
95
+ const { isOpen } = useMultiSelect();
96
+
97
+ return (
98
+ <PopoverTrigger>
99
+ <div
100
+ ref={ref}
101
+ className={cn(
102
+ "flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer",
103
+ className
104
+ )}
105
+ {...props}
106
+ >
107
+ {children}
108
+ <ChevronDown className={cn("h-4 w-4 shrink-0 opacity-50", isOpen && "rotate-180 transition-transform")} />
109
+ </div>
110
+ </PopoverTrigger>
111
+ );
112
+ }
113
+ );
114
+ MultiSelectTrigger.displayName = "MultiSelectTrigger";
115
+
116
+ export const MultiSelectValue = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
117
+ ({ className, ...props }, ref) => {
118
+ const { value, findOptionByValue, placeholder, options } = useMultiSelect();
119
+
120
+ // Helper function to find parent option
121
+ const findParentOption = (childValue: string): MultiSelectOption | undefined => {
122
+ for (const option of options) {
123
+ if (option.isSection) {
124
+ for (const child of option.children || []) {
125
+ if (child.children?.some(c => c.value === childValue)) {
126
+ return child;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ return undefined;
132
+ };
133
+
134
+ // Check if all selected values are from the same parent
135
+ const getCommonParent = () => {
136
+ const firstParent = findParentOption(value[0]);
137
+ if (!firstParent) return null;
138
+
139
+ if (value.length <= 1) return firstParent;
140
+
141
+ const allFromSameParent = value.every(val => {
142
+ const parent = findParentOption(val);
143
+ return parent?.value === firstParent.value;
144
+ });
145
+
146
+ return allFromSameParent ? firstParent : null;
147
+ };
148
+
149
+ return (
150
+ <span ref={ref} className={cn("w-full", className)} {...props}>
151
+ {value.length === 0 && placeholder}
152
+ {/* {value.length === 1 && (findOptionByValue(value[0])?.label || value[0])} */}
153
+ {value.length > 0 && (() => {
154
+ const commonParent = getCommonParent();
155
+ return commonParent
156
+ ? `${commonParent.label} ${value.length > 1 ? `(${value.length})` : ""}`
157
+ : `${value.length} selected`;
158
+ })()}
159
+ </span>
160
+ );
161
+ }
162
+ );
163
+ MultiSelectValue.displayName = "MultiSelectValue";
164
+
165
+ /**
166
+ * Props for MultiSelect component
167
+ */
168
+ interface MultiSelectProps {
169
+ options: MultiSelectOption[];
170
+ value: string[];
171
+ onValueChange: (value: string[]) => void;
172
+ placeholder?: string;
173
+ maxCount?: number;
174
+ modalPopover?: boolean;
175
+ children: React.ReactNode;
176
+ }
177
+
178
+ export const MultiSelect = React.forwardRef<HTMLDivElement, MultiSelectProps>(
179
+ ({
180
+ options,
181
+ value,
182
+ onValueChange,
183
+ placeholder = "Select options",
184
+ maxCount = 3,
185
+ modalPopover = false,
186
+ children,
187
+ }, ref) => {
188
+ const [isOpen, setIsOpen] = React.useState(false);
189
+ const [expandedItems, setExpandedItems] = React.useState<Set<string>>(new Set());
190
+
191
+ // Controlled: selectedValues comes from props
192
+ const selectedValues = value;
193
+
194
+ const handleInputKeyDown = (
195
+ event: React.KeyboardEvent<HTMLInputElement>
196
+ ) => {
197
+ if (event.key === "Enter") {
198
+ setIsOpen(true);
199
+ } else if (event.key === "Backspace" && !event.currentTarget.value) {
200
+ const newSelectedValues = [...selectedValues];
201
+ newSelectedValues.pop();
202
+ onValueChange(newSelectedValues);
203
+ }
204
+ };
205
+
206
+ // Helper: flatten all child values for easy lookup
207
+ const getAllChildValues = (option: MultiSelectOption): string[] => {
208
+ if (option.children && option.children.length > 0) {
209
+ return option.children.map((child) => child.value);
210
+ }
211
+ return [];
212
+ };
213
+
214
+ // Helper: recursively find an option by value
215
+ const findOptionByValue = React.useCallback(
216
+ (val: string): MultiSelectOption | undefined => {
217
+ const findInOptions = (opts: MultiSelectOption[]): MultiSelectOption | undefined => {
218
+ for (const opt of opts) {
219
+ if (opt.value === val) return opt;
220
+ if (opt.children) {
221
+ const found = findInOptions(opt.children);
222
+ if (found) return found;
223
+ }
224
+ }
225
+ return undefined;
226
+ };
227
+ return findInOptions(options);
228
+ },
229
+ [options]
230
+ );
231
+
232
+ // Helper: flatten all values (for select all)
233
+ const getAllValues = (opts: MultiSelectOption[]): string[] => {
234
+ let values: string[] = [];
235
+ for (const opt of opts) {
236
+ if (opt.children && opt.children.length > 0) {
237
+ values = values.concat(opt.children.map((c) => c.value));
238
+ } else {
239
+ values.push(opt.value);
240
+ }
241
+ }
242
+ return values;
243
+ };
244
+
245
+ // Updated toggleOption to handle parent/child
246
+ const toggleOption = (option: MultiSelectOption, isParent = false) => {
247
+ let newSelectedValues = [...selectedValues];
248
+ if (option.children && option.children.length > 0) {
249
+ // Parent: toggle all children
250
+ const childValues = getAllChildValues(option);
251
+ const allSelected = childValues.every((v) => newSelectedValues.includes(v));
252
+ if (allSelected) {
253
+ newSelectedValues = newSelectedValues.filter((v) => !childValues.includes(v));
254
+ } else {
255
+ newSelectedValues = Array.from(new Set([...newSelectedValues, ...childValues]));
256
+ }
257
+ } else {
258
+ // Child or flat
259
+ if (newSelectedValues.includes(option.value)) {
260
+ newSelectedValues = newSelectedValues.filter((v) => v !== option.value);
261
+ } else {
262
+ newSelectedValues.push(option.value);
263
+ }
264
+ // If this is a child, check if all siblings are selected, then select parent
265
+ if (isParent && option.children && option.children.length > 0) {
266
+ // handled above
267
+ }
268
+ }
269
+ onValueChange(newSelectedValues);
270
+ };
271
+
272
+ const handleClear = () => {
273
+ onValueChange([]);
274
+ };
275
+
276
+ const handleTogglePopover = () => {
277
+ setIsOpen((prev) => !prev);
278
+ };
279
+
280
+ const clearExtraOptions = () => {
281
+ const newSelectedValues = selectedValues.slice(0, maxCount);
282
+ onValueChange(newSelectedValues);
283
+ };
284
+
285
+ // Updated toggleAll for nested
286
+ const toggleAll = () => {
287
+ const allValues = getAllValues(options);
288
+ if (selectedValues.length === allValues.length) {
289
+ handleClear();
290
+ } else {
291
+ onValueChange(allValues);
292
+ }
293
+ };
294
+
295
+ const toggleExpanded = (optionValue: string, event: React.MouseEvent) => {
296
+ event.stopPropagation();
297
+ const option = options.find(opt => opt.value === optionValue) ||
298
+ options.flatMap(opt => opt.children || []).find(opt => opt.value === optionValue);
299
+
300
+ if (option?.hideChildren) return;
301
+
302
+ setExpandedItems(prev => {
303
+ const next = new Set(prev);
304
+ if (next.has(optionValue)) {
305
+ next.delete(optionValue);
306
+ } else {
307
+ next.add(optionValue);
308
+ }
309
+ return next;
310
+ });
311
+ };
312
+
313
+ return (
314
+ <MultiSelectContext.Provider
315
+ value={{
316
+ value,
317
+ options,
318
+ isOpen,
319
+ onOpenChange: setIsOpen,
320
+ findOptionByValue,
321
+ placeholder,
322
+ }}
323
+ >
324
+ <TooltipProvider delay={100}>
325
+ <Popover open={isOpen} onOpenChange={setIsOpen} modal={modalPopover}>
326
+ {children}
327
+ <PopoverContent
328
+ className="w-auto p-0"
329
+ align="start"
330
+ >
331
+ <Command>
332
+ <CommandInput
333
+ placeholder="search..."
334
+ onKeyDown={handleInputKeyDown}
335
+ />
336
+ <CommandList>
337
+ <CommandEmpty>no results found.</CommandEmpty>
338
+ {options.map((section) => {
339
+ if (section.isSection) {
340
+ // Render section
341
+ return (
342
+ <React.Fragment key={section.value}>
343
+ <CommandGroup heading={section.label}>
344
+ {section.children?.map((option) => {
345
+ if (option.children && option.children.length > 0) {
346
+ // Parent with children
347
+ const childValues = getAllChildValues(option);
348
+ const allChildrenSelected = childValues.every((v) => selectedValues.includes(v));
349
+ const someChildrenSelected = childValues.some((v) => selectedValues.includes(v));
350
+ const isExpanded = expandedItems.has(option.value);
351
+ const allChildrenDisabled = option.children?.every(child => child.disabled) ?? false;
352
+ let parentTooltip = option.tooltip;
353
+ if (allChildrenDisabled && (!parentTooltip || parentTooltip === "")) {
354
+ parentTooltip = "no option in this group meets requirements";
355
+ }
356
+ const parentContent = (
357
+ <CommandItem
358
+ onSelect={() => !allChildrenDisabled && toggleOption(option, true)}
359
+ className={cn(
360
+ "cursor-pointer font-semibold",
361
+ allChildrenDisabled && "opacity-50 cursor-not-allowed"
362
+ )}
363
+ >
364
+ <div className="flex flex-col w-full">
365
+ <div className="flex items-center">
366
+ <div
367
+ className={cn(
368
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
369
+ allChildrenSelected
370
+ ? "bg-primary text-primary-foreground"
371
+ : someChildrenSelected
372
+ ? "bg-primary/30 text-primary-foreground"
373
+ : "opacity-50 [&_svg]:invisible",
374
+ allChildrenDisabled && "opacity-30"
375
+ )}
376
+ >
377
+ <CheckIcon className="h-4 w-4" />
378
+ </div>
379
+
380
+ {option.icon && option.icon}
381
+ <span>{option.label}</span>
382
+ {option.children && option.children.length > 0 && !option.hideChildren && (
383
+ <button
384
+ onClick={(e) => toggleExpanded(option.value, e)}
385
+ className="p-1 ml-1 hover:bg-foreground/10 rounded-sm cursor-pointer"
386
+ >
387
+ {isExpanded ? (
388
+ <ChevronDown className="h-4 w-4" />
389
+ ) : (
390
+ <ChevronRight className="h-4 w-4" />
391
+ )}
392
+ </button>
393
+ )}
394
+ </div>
395
+ {option.description && (
396
+ <div className="text-xs text-muted-foreground pl-6">
397
+ {option.description}
398
+ </div>
399
+ )}
400
+ </div>
401
+ </CommandItem>
402
+ );
403
+
404
+ return (
405
+ <React.Fragment key={option.value}>
406
+ {parentTooltip ? (
407
+ <Tooltip>
408
+ <TooltipTrigger>
409
+ <div>
410
+ {parentContent}
411
+ </div>
412
+ </TooltipTrigger>
413
+ <TooltipContent className="max-w-sm border-brand-orange">
414
+ {parentTooltip}
415
+ </TooltipContent>
416
+ </Tooltip>
417
+ ) : parentContent}
418
+ {/* Render children if expanded and not hidden */}
419
+ {isExpanded && !option.hideChildren && option.children.map((child) => {
420
+ const isSelected = selectedValues.includes(child.value);
421
+ const childContent = (
422
+ <CommandItem
423
+ key={child.value}
424
+ onSelect={() => !child.disabled && toggleOption(child)}
425
+ className={cn(
426
+ "cursor-pointer relative pl-6",
427
+ child.disabled && "opacity-50 cursor-not-allowed"
428
+ )}
429
+ >
430
+ <div className="absolute left-4 top-0 bottom-0 w-px bg-muted-foreground/20" />
431
+ <div className="flex flex-col w-full">
432
+ <div className="flex items-center">
433
+ <div
434
+ className={cn(
435
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
436
+ isSelected
437
+ ? "bg-primary text-primary-foreground"
438
+ : "opacity-50 [&_svg]:invisible",
439
+ child.disabled && "opacity-30"
440
+ )}
441
+ >
442
+ <CheckIcon className="h-4 w-4" />
443
+ </div>
444
+ {child.icon && child.icon}
445
+ <span>{child.label}</span>
446
+ </div>
447
+ {child.description && (
448
+ <div className="text-xs text-muted-foreground pl-6">
449
+ {child.description}
450
+ </div>
451
+ )}
452
+ </div>
453
+ </CommandItem>
454
+ );
455
+
456
+ return child.tooltip ? (
457
+ <Tooltip key={child.value}>
458
+ <TooltipTrigger>
459
+ <div>
460
+ {childContent}
461
+ </div>
462
+ </TooltipTrigger>
463
+ <TooltipContent className="max-w-sm border-brand-orange">
464
+ {child.tooltip}
465
+ </TooltipContent>
466
+ </Tooltip>
467
+ ) : childContent;
468
+ })}
469
+ </React.Fragment>
470
+ );
471
+ } else {
472
+ // Flat option
473
+ const isSelected = selectedValues.includes(option.value);
474
+ const optionContent = (
475
+ <CommandItem
476
+ key={option.value}
477
+ onSelect={() => !option.disabled && toggleOption(option)}
478
+ className={cn(
479
+ "cursor-pointer",
480
+ option.disabled && "opacity-50 cursor-not-allowed"
481
+ )}
482
+ >
483
+ <div className="flex flex-col w-full">
484
+ <div className="flex items-center">
485
+ <div
486
+ className={cn(
487
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
488
+ isSelected
489
+ ? "bg-primary text-primary-foreground"
490
+ : "opacity-50 [&_svg]:invisible",
491
+ option.disabled && "opacity-30"
492
+ )}
493
+ >
494
+ <CheckIcon className="h-4 w-4" />
495
+ </div>
496
+ {option.icon && option.icon}
497
+ <span>{option.label}</span>
498
+ </div>
499
+ {option.description && (
500
+ <div className="text-xs text-muted-foreground pl-6">
501
+ {option.description}
502
+ </div>
503
+ )}
504
+ </div>
505
+ </CommandItem>
506
+ );
507
+
508
+ return option.tooltip ? (
509
+ <Tooltip key={option.value}>
510
+ <TooltipTrigger>
511
+ <div>
512
+ {optionContent}
513
+ </div>
514
+ </TooltipTrigger>
515
+ <TooltipContent className="max-w-sm border-brand-orange">
516
+ {option.tooltip}
517
+ </TooltipContent>
518
+ </Tooltip>
519
+ ) : optionContent;
520
+ }
521
+ })}
522
+ </CommandGroup>
523
+ <CommandSeparator />
524
+ </React.Fragment>
525
+ );
526
+ } else {
527
+ // Render regular options (existing code)
528
+ return (
529
+ <CommandGroup key={section.value}>
530
+ {options.map((option) => {
531
+ if (option.children && option.children.length > 0) {
532
+ // Parent with children
533
+ const childValues = getAllChildValues(option);
534
+ const allChildrenSelected = childValues.every((v) => selectedValues.includes(v));
535
+ const someChildrenSelected = childValues.some((v) => selectedValues.includes(v));
536
+ const isExpanded = expandedItems.has(option.value);
537
+ const allChildrenDisabled = option.children?.every(child => child.disabled) ?? false;
538
+ let parentTooltip = option.tooltip;
539
+ if (allChildrenDisabled && (!parentTooltip || parentTooltip === "")) {
540
+ parentTooltip = "no option in this group meets requirements";
541
+ }
542
+ const parentContent = (
543
+ <CommandItem
544
+ onSelect={() => !allChildrenDisabled && toggleOption(option, true)}
545
+ className={cn(
546
+ "cursor-pointer font-semibold",
547
+ allChildrenDisabled && "opacity-50 cursor-not-allowed"
548
+ )}
549
+ >
550
+ <div className="flex flex-col w-full">
551
+ <div className="flex items-center">
552
+ <div
553
+ className={cn(
554
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
555
+ allChildrenSelected
556
+ ? "bg-primary text-primary-foreground"
557
+ : someChildrenSelected
558
+ ? "bg-primary/30 text-primary-foreground"
559
+ : "opacity-50 [&_svg]:invisible",
560
+ allChildrenDisabled && "opacity-30"
561
+ )}
562
+ >
563
+ <CheckIcon className="h-4 w-4" />
564
+ </div>
565
+ {option.children && option.children.length > 0 && !option.hideChildren && (
566
+ <button
567
+ onClick={(e) => toggleExpanded(option.value, e)}
568
+ className="p-1 hover:bg-accent rounded-sm"
569
+ >
570
+ {isExpanded ? (
571
+ <ChevronDown className="h-4 w-4" />
572
+ ) : (
573
+ <ChevronRight className="h-4 w-4" />
574
+ )}
575
+ </button>
576
+ )}
577
+ {option.icon && option.icon}
578
+ <span>{option.label}</span>
579
+ </div>
580
+ {option.description && (
581
+ <div className="text-xs text-muted-foreground pl-6">
582
+ {option.description}
583
+ </div>
584
+ )}
585
+ </div>
586
+ </CommandItem>
587
+ );
588
+
589
+ return (
590
+ <React.Fragment key={option.value}>
591
+ {parentTooltip ? (
592
+ <Tooltip>
593
+ <TooltipTrigger>
594
+ <div>
595
+ {parentContent}
596
+ </div>
597
+ </TooltipTrigger>
598
+ <TooltipContent className="max-w-sm border-brand-orange">
599
+ {parentTooltip}
600
+ </TooltipContent>
601
+ </Tooltip>
602
+ ) : parentContent}
603
+ {/* Render children if expanded and not hidden */}
604
+ {isExpanded && !option.hideChildren && option.children.map((child) => {
605
+ const isSelected = selectedValues.includes(child.value);
606
+ const childContent = (
607
+ <CommandItem
608
+ key={child.value}
609
+ onSelect={() => !child.disabled && toggleOption(child)}
610
+ className={cn(
611
+ "cursor-pointer relative pl-6",
612
+ child.disabled && "opacity-50 cursor-not-allowed"
613
+ )}
614
+ >
615
+ <div className="absolute left-4 top-0 bottom-0 w-px bg-muted-foreground/20" />
616
+ <div className="flex flex-col w-full">
617
+ <div className="flex items-center">
618
+ <div
619
+ className={cn(
620
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
621
+ isSelected
622
+ ? "bg-primary text-primary-foreground"
623
+ : "opacity-50 [&_svg]:invisible",
624
+ child.disabled && "opacity-30"
625
+ )}
626
+ >
627
+ <CheckIcon className="h-4 w-4" />
628
+ </div>
629
+ {child.icon && child.icon}
630
+ <span>{child.label}</span>
631
+ </div>
632
+ {child.description && (
633
+ <div className="text-xs text-muted-foreground pl-6">
634
+ {child.description}
635
+ </div>
636
+ )}
637
+ </div>
638
+ </CommandItem>
639
+ );
640
+
641
+ return child.tooltip ? (
642
+ <Tooltip key={child.value}>
643
+ <TooltipTrigger>
644
+ <div>
645
+ {childContent}
646
+ </div>
647
+ </TooltipTrigger>
648
+ <TooltipContent className="max-w-sm border-brand-orange">
649
+ {child.tooltip}
650
+ </TooltipContent>
651
+ </Tooltip>
652
+ ) : childContent;
653
+ })}
654
+ </React.Fragment>
655
+ );
656
+ } else {
657
+ // Flat option
658
+ const isSelected = selectedValues.includes(option.value);
659
+ const optionContent = (
660
+ <CommandItem
661
+ key={option.value}
662
+ onSelect={() => !option.disabled && toggleOption(option)}
663
+ className={cn(
664
+ "cursor-pointer",
665
+ option.disabled && "opacity-50 cursor-not-allowed"
666
+ )}
667
+ >
668
+ <div className="flex flex-col w-full">
669
+ <div className="flex items-center">
670
+ <div
671
+ className={cn(
672
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
673
+ isSelected
674
+ ? "bg-primary text-primary-foreground"
675
+ : "opacity-50 [&_svg]:invisible",
676
+ option.disabled && "opacity-30"
677
+ )}
678
+ >
679
+ <CheckIcon className="h-4 w-4" />
680
+ </div>
681
+ {option.icon && option.icon}
682
+ <span>{option.label}</span>
683
+ </div>
684
+ {option.description && (
685
+ <div className="text-xs text-muted-foreground pl-6">
686
+ {option.description}
687
+ </div>
688
+ )}
689
+ </div>
690
+ </CommandItem>
691
+ );
692
+
693
+ return option.tooltip ? (
694
+ <Tooltip key={option.value}>
695
+ <TooltipTrigger>
696
+ <div>
697
+ {optionContent}
698
+ </div>
699
+ </TooltipTrigger>
700
+ <TooltipContent className="max-w-sm border-brand-orange">
701
+ {option.tooltip}
702
+ </TooltipContent>
703
+ </Tooltip>
704
+ ) : optionContent;
705
+ }
706
+ })}
707
+ </CommandGroup>
708
+ );
709
+ }
710
+ })}
711
+ </CommandList>
712
+ </Command>
713
+ </PopoverContent>
714
+ </Popover>
715
+ </TooltipProvider>
716
+ </MultiSelectContext.Provider>
717
+ );
718
+ }
719
+ );
720
+
721
+ MultiSelect.displayName = "MultiSelect";