@inferencesh/ui 0.0.0-stage → 0.1.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.
- package/LICENSE +22 -0
- package/README.md +68 -2
- package/components.json +22 -0
- package/package.json +231 -4
- package/src/components/audio-player.tsx +549 -0
- package/src/components/button.tsx +13 -0
- package/src/components/chart.tsx +403 -0
- package/src/components/circuit.tsx +276 -0
- package/src/components/code-block/code-block.tsx +260 -0
- package/src/components/code-block/index.ts +18 -0
- package/src/components/code-block/languages.ts +311 -0
- package/src/components/code-block/styles.ts +39 -0
- package/src/components/code-block/tokenizer.ts +290 -0
- package/src/components/code-block/types.ts +38 -0
- package/src/components/code-block/utils.ts +39 -0
- package/src/components/code.tsx +48 -0
- package/src/components/cookie-consent.tsx +137 -0
- package/src/components/copy-button.tsx +44 -0
- package/src/components/country-dropdown.tsx +188 -0
- package/src/components/glow-loader-fast.tsx +32 -0
- package/src/components/glow-loader-mono.tsx +33 -0
- package/src/components/glow-loader-sm-fast.tsx +32 -0
- package/src/components/glow-loader-sm-mono.tsx +34 -0
- package/src/components/glow-loader-sm.tsx +32 -0
- package/src/components/glow-loader.tsx +32 -0
- package/src/components/glow-logo.tsx +32 -0
- package/src/components/gradient.tsx +80 -0
- package/src/components/index.ts +45 -0
- package/src/components/inline-sidebar.tsx +351 -0
- package/src/components/input-color.tsx +349 -0
- package/src/components/logo-base.tsx +213 -0
- package/src/components/logo-color.tsx +23 -0
- package/src/components/logo-full.tsx +71 -0
- package/src/components/logo-svg.tsx +23 -0
- package/src/components/menu-navigation.tsx +359 -0
- package/src/components/multi-select.tsx +721 -0
- package/src/components/pastel-gradient.tsx +25 -0
- package/src/components/pixel-symmetry.tsx +151 -0
- package/src/components/progress.tsx +28 -0
- package/src/components/scroll-more.tsx +131 -0
- package/src/components/select.tsx +85 -0
- package/src/components/shimmering-text.tsx +108 -0
- package/src/components/sidebar-light.tsx +102 -0
- package/src/components/sonner.tsx +38 -0
- package/src/components/theme-switcher-sidebar.tsx +56 -0
- package/src/components/theme-switcher-simple.tsx +110 -0
- package/src/components/theme-switcher.tsx +47 -0
- package/src/components/toggle-group.tsx +55 -0
- package/src/components/tooltip-button.tsx +91 -0
- package/src/components/tooltip.tsx +71 -0
- package/src/components/tree-view.tsx +499 -0
- package/src/components/unglow-loader.tsx +33 -0
- package/src/components/waveform.tsx +1632 -0
- package/src/components/zoomable-image.tsx +76 -0
- package/src/docs/api-docs.tsx +763 -0
- package/src/docs/callout.tsx +160 -0
- package/src/docs/code-group.tsx +215 -0
- package/src/docs/copy-menu.tsx +143 -0
- package/src/docs/doc-tabs.tsx +84 -0
- package/src/docs/index.ts +16 -0
- package/src/docs/steps.tsx +105 -0
- package/src/docs/table-of-contents.tsx +174 -0
- package/src/examples/guide-markdown.ts +129 -0
- package/src/examples/index.ts +1153 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/use-copy-to-clipboard.ts +36 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/lib/display-name.ts +15 -0
- package/src/lib/example-input.ts +130 -0
- package/src/lib/plans.ts +44 -0
- package/src/lib/safe-content.ts +137 -0
- package/src/lib/schema.ts +320 -0
- package/src/lib/storage.ts +108 -0
- package/src/markdown/docs-markdown-renderer.tsx +518 -0
- package/src/markdown/helpers.ts +95 -0
- package/src/markdown/index.ts +24 -0
- package/src/markdown/markdown-renderer.tsx +232 -0
- package/src/markdown/youtube-embed.tsx +43 -0
- package/src/og/image.ts +312 -0
- package/src/og/index.tsx +430 -0
- package/src/og/query.ts +26 -0
- package/src/shadcn/accordion.tsx +71 -0
- package/src/shadcn/alert-dialog.tsx +187 -0
- package/src/shadcn/alert.tsx +75 -0
- package/src/shadcn/aspect-ratio.tsx +22 -0
- package/src/shadcn/attachment.tsx +207 -0
- package/src/shadcn/avatar.tsx +106 -0
- package/src/shadcn/badge.tsx +51 -0
- package/src/shadcn/breadcrumb.tsx +124 -0
- package/src/shadcn/bubble.tsx +127 -0
- package/src/shadcn/button-group.tsx +87 -0
- package/src/shadcn/button.tsx +57 -0
- package/src/shadcn/calendar.tsx +219 -0
- package/src/shadcn/card.tsx +102 -0
- package/src/shadcn/carousel.tsx +242 -0
- package/src/shadcn/chart.tsx +372 -0
- package/src/shadcn/checkbox.tsx +28 -0
- package/src/shadcn/collapsible.tsx +19 -0
- package/src/shadcn/combobox.tsx +297 -0
- package/src/shadcn/command.tsx +194 -0
- package/src/shadcn/context-menu.tsx +270 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/direction.tsx +4 -0
- package/src/shadcn/drawer.tsx +227 -0
- package/src/shadcn/dropdown-menu.tsx +265 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +238 -0
- package/src/shadcn/form.tsx +173 -0
- package/src/shadcn/hover-card.tsx +50 -0
- package/src/shadcn/index.ts +53 -0
- package/src/shadcn/input-group.tsx +156 -0
- package/src/shadcn/input-otp.tsx +84 -0
- package/src/shadcn/input.tsx +19 -0
- package/src/shadcn/item.tsx +201 -0
- package/src/shadcn/kbd.tsx +26 -0
- package/src/shadcn/label.tsx +19 -0
- package/src/shadcn/marker.tsx +70 -0
- package/src/shadcn/menubar.tsx +278 -0
- package/src/shadcn/message-scroller.tsx +131 -0
- package/src/shadcn/message.tsx +91 -0
- package/src/shadcn/native-select.tsx +60 -0
- package/src/shadcn/navigation-menu.tsx +167 -0
- package/src/shadcn/pagination.tsx +130 -0
- package/src/shadcn/popover.tsx +87 -0
- package/src/shadcn/progress.tsx +82 -0
- package/src/shadcn/radio-group.tsx +35 -0
- package/src/shadcn/resizable.tsx +49 -0
- package/src/shadcn/scroll-area.tsx +52 -0
- package/src/shadcn/select.tsx +200 -0
- package/src/shadcn/separator.tsx +22 -0
- package/src/shadcn/sheet.tsx +136 -0
- package/src/shadcn/sidebar.tsx +721 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/slider.tsx +51 -0
- package/src/shadcn/sonner.tsx +49 -0
- package/src/shadcn/spinner.tsx +10 -0
- package/src/shadcn/switch.tsx +29 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +79 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +89 -0
- package/src/shadcn/toggle.tsx +44 -0
- package/src/shadcn/tooltip.tsx +63 -0
- package/src/theme/config.ts +48 -0
- package/src/theme/css/base.css +92 -0
- package/src/theme/css/bitboy.css +75 -0
- package/src/theme/css/dark.css +47 -0
- package/src/theme/css/designer.css +122 -0
- package/src/theme/css/fonts.css +33 -0
- package/src/theme/css/globals.css +14 -0
- package/src/theme/css/hackintosh.css +57 -0
- package/src/theme/css/llama.css +103 -0
- package/src/theme/css/safelist.css +39 -0
- package/src/theme/css/shadcn.css +77 -0
- package/src/theme/css/theme.css +216 -0
- package/src/theme/embed-theme.ts +146 -0
- package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
- package/src/theme/fonts/Hack-Regular.woff2 +0 -0
- package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
- package/src/theme/fonts/LICENSE-Hack.txt +45 -0
- package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
- package/src/theme/fonts/Mona-Sans.woff2 +0 -0
- package/src/theme/index.ts +15 -0
- package/src/utils/cn.ts +3 -0
- 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";
|