@hex-core/components 1.3.1 → 1.5.0
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/README.md +183 -9
- package/dist/_tsup-dts-rollup.d.ts +3105 -0
- package/dist/accordion.d.ts +4 -0
- package/dist/accordion.js +62 -0
- package/dist/accordion.js.map +1 -0
- package/dist/alert-dialog.d.ts +11 -0
- package/dist/alert-dialog.js +125 -0
- package/dist/alert-dialog.js.map +1 -0
- package/dist/alert.d.ts +4 -0
- package/dist/alert.js +54 -0
- package/dist/alert.js.map +1 -0
- package/dist/aspect-ratio.d.ts +1 -0
- package/dist/aspect-ratio.js +8 -0
- package/dist/aspect-ratio.js.map +1 -0
- package/dist/avatar.d.ts +3 -0
- package/dist/avatar.js +44 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.d.ts +3 -0
- package/dist/badge.js +36 -0
- package/dist/badge.js.map +1 -0
- package/dist/breadcrumb.d.ts +7 -0
- package/dist/breadcrumb.js +120 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/button.d.ts +3 -0
- package/dist/button.js +113 -0
- package/dist/button.js.map +1 -0
- package/dist/calendar.d.ts +1 -0
- package/dist/calendar.js +126 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card.d.ts +6 -0
- package/dist/card.js +68 -0
- package/dist/card.js.map +1 -0
- package/dist/checkbox.d.ts +2 -0
- package/dist/checkbox.js +65 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/citation.d.ts +2 -0
- package/dist/citation.js +70 -0
- package/dist/citation.js.map +1 -0
- package/dist/cluster.d.ts +3 -0
- package/dist/cluster.js +50 -0
- package/dist/cluster.js.map +1 -0
- package/dist/code-block-copy.d.ts +2 -0
- package/dist/code-block-copy.js +108 -0
- package/dist/code-block-copy.js.map +1 -0
- package/dist/code-block.d.ts +3 -0
- package/dist/code-block.js +90 -0
- package/dist/code-block.js.map +1 -0
- package/dist/collapsible.d.ts +3 -0
- package/dist/collapsible.js +10 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/color-picker.d.ts +2 -0
- package/dist/color-picker.js +321 -0
- package/dist/color-picker.js.map +1 -0
- package/dist/combobox.d.ts +3 -0
- package/dist/combobox.js +226 -0
- package/dist/combobox.js.map +1 -0
- package/dist/command.d.ts +9 -0
- package/dist/command.js +232 -0
- package/dist/command.js.map +1 -0
- package/dist/composer.d.ts +2 -0
- package/dist/composer.js +75 -0
- package/dist/composer.js.map +1 -0
- package/dist/container.d.ts +3 -0
- package/dist/container.js +39 -0
- package/dist/container.js.map +1 -0
- package/dist/context-menu.d.ts +12 -0
- package/dist/context-menu.js +130 -0
- package/dist/context-menu.js.map +1 -0
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.js +103 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +2 -0
- package/dist/date-picker.js +221 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/dialog.d.ts +11 -0
- package/dist/dialog.js +125 -0
- package/dist/dialog.js.map +1 -0
- package/dist/drawer.d.ts +10 -0
- package/dist/drawer.js +82 -0
- package/dist/drawer.js.map +1 -0
- package/dist/dropdown-menu.d.ts +13 -0
- package/dist/dropdown-menu.js +133 -0
- package/dist/dropdown-menu.js.map +1 -0
- package/dist/dropzone.d.ts +3 -0
- package/dist/dropzone.js +194 -0
- package/dist/dropzone.js.map +1 -0
- package/dist/file-tree.d.ts +3 -0
- package/dist/file-tree.js +322 -0
- package/dist/file-tree.js.map +1 -0
- package/dist/form.d.ts +8 -0
- package/dist/form.js +114 -0
- package/dist/form.js.map +1 -0
- package/dist/grid.d.ts +3 -0
- package/dist/grid.js +58 -0
- package/dist/grid.js.map +1 -0
- package/dist/hover-card.d.ts +3 -0
- package/dist/hover-card.js +34 -0
- package/dist/hover-card.js.map +1 -0
- package/dist/index.d.ts +298 -1652
- package/dist/index.js +1157 -5493
- package/dist/index.js.map +1 -1
- package/dist/input-otp.d.ts +5 -0
- package/dist/input-otp.js +71 -0
- package/dist/input-otp.js.map +1 -0
- package/dist/input.d.ts +2 -0
- package/dist/input.js +40 -0
- package/dist/input.js.map +1 -0
- package/dist/label.d.ts +2 -0
- package/dist/label.js +22 -0
- package/dist/label.js.map +1 -0
- package/dist/loading-indicator.d.ts +3 -0
- package/dist/loading-indicator.js +64 -0
- package/dist/loading-indicator.js.map +1 -0
- package/dist/markdown.d.ts +2 -0
- package/dist/markdown.js +28 -0
- package/dist/markdown.js.map +1 -0
- package/dist/menubar.d.ts +11 -0
- package/dist/menubar.js +106 -0
- package/dist/menubar.js.map +1 -0
- package/dist/message-actions.d.ts +2 -0
- package/dist/message-actions.js +28 -0
- package/dist/message-actions.js.map +1 -0
- package/dist/message-list.d.ts +2 -0
- package/dist/message-list.js +49 -0
- package/dist/message-list.js.map +1 -0
- package/dist/message.d.ts +3 -0
- package/dist/message.js +35 -0
- package/dist/message.js.map +1 -0
- package/dist/multi-combobox.d.ts +3 -0
- package/dist/multi-combobox.js +258 -0
- package/dist/multi-combobox.js.map +1 -0
- package/dist/navigation-menu.d.ts +9 -0
- package/dist/navigation-menu.js +108 -0
- package/dist/navigation-menu.js.map +1 -0
- package/dist/pagination.d.ts +7 -0
- package/dist/pagination.js +195 -0
- package/dist/pagination.js.map +1 -0
- package/dist/popover.d.ts +4 -0
- package/dist/popover.js +35 -0
- package/dist/popover.js.map +1 -0
- package/dist/progress.d.ts +1 -0
- package/dist/progress.js +38 -0
- package/dist/progress.js.map +1 -0
- package/dist/radio-group.d.ts +2 -0
- package/dist/radio-group.js +44 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/reasoning.d.ts +2 -0
- package/dist/reasoning.js +90 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/resizable.d.ts +3 -0
- package/dist/resizable.js +66 -0
- package/dist/resizable.js.map +1 -0
- package/dist/schemas.d.ts +72 -0
- package/dist/schemas.js +5491 -0
- package/dist/schemas.js.map +1 -0
- package/dist/scroll-area.d.ts +3 -0
- package/dist/scroll-area.js +55 -0
- package/dist/scroll-area.js.map +1 -0
- package/dist/select.d.ts +8 -0
- package/dist/select.js +136 -0
- package/dist/select.js.map +1 -0
- package/dist/separator.d.ts +2 -0
- package/dist/separator.js +29 -0
- package/dist/separator.js.map +1 -0
- package/dist/sheet.d.ts +10 -0
- package/dist/sheet.js +140 -0
- package/dist/sheet.js.map +1 -0
- package/dist/sidebar.d.ts +8 -0
- package/dist/sidebar.js +201 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/skeleton.d.ts +1 -0
- package/dist/skeleton.js +21 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/slider.d.ts +2 -0
- package/dist/slider.js +55 -0
- package/dist/slider.js.map +1 -0
- package/dist/sonner.d.ts +2 -0
- package/dist/sonner.js +27 -0
- package/dist/sonner.js.map +1 -0
- package/dist/spacer.d.ts +3 -0
- package/dist/spacer.js +43 -0
- package/dist/spacer.js.map +1 -0
- package/dist/stack.d.ts +3 -0
- package/dist/stack.js +49 -0
- package/dist/stack.js.map +1 -0
- package/dist/stepper.d.ts +4 -0
- package/dist/stepper.js +226 -0
- package/dist/stepper.js.map +1 -0
- package/dist/suggestion.d.ts +2 -0
- package/dist/suggestion.js +55 -0
- package/dist/suggestion.js.map +1 -0
- package/dist/switch.d.ts +2 -0
- package/dist/switch.js +47 -0
- package/dist/switch.js.map +1 -0
- package/dist/table.d.ts +8 -0
- package/dist/table.js +85 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.d.ts +4 -0
- package/dist/tabs.js +57 -0
- package/dist/tabs.js.map +1 -0
- package/dist/textarea.d.ts +2 -0
- package/dist/textarea.js +36 -0
- package/dist/textarea.js.map +1 -0
- package/dist/time-picker.d.ts +2 -0
- package/dist/time-picker.js +50 -0
- package/dist/time-picker.js.map +1 -0
- package/dist/timeline.d.ts +4 -0
- package/dist/timeline.js +84 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toggle-group.d.ts +2 -0
- package/dist/toggle-group.js +83 -0
- package/dist/toggle-group.js.map +1 -0
- package/dist/toggle.d.ts +2 -0
- package/dist/toggle.js +49 -0
- package/dist/toggle.js.map +1 -0
- package/dist/tool-call.d.ts +2 -0
- package/dist/tool-call.js +133 -0
- package/dist/tool-call.js.map +1 -0
- package/dist/tooltip.d.ts +4 -0
- package/dist/tooltip.js +33 -0
- package/dist/tooltip.js.map +1 -0
- package/package.json +71 -16
package/dist/dropzone.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
function filterFiles(files, { accept, maxSize, maxFiles }) {
|
|
11
|
+
const list = Array.from(files);
|
|
12
|
+
const acceptList = accept ? accept.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
13
|
+
const matchAccept = (file) => {
|
|
14
|
+
if (!acceptList) return true;
|
|
15
|
+
return acceptList.some((entry) => {
|
|
16
|
+
if (entry.startsWith(".")) {
|
|
17
|
+
return file.name.toLowerCase().endsWith(entry.toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
if (entry.endsWith("/*")) {
|
|
20
|
+
const prefix = entry.slice(0, -1);
|
|
21
|
+
return file.type.startsWith(prefix);
|
|
22
|
+
}
|
|
23
|
+
return file.type === entry;
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const sized = typeof maxSize === "number" ? list.filter((f) => f.size <= maxSize) : list;
|
|
27
|
+
const accepted = sized.filter(matchAccept);
|
|
28
|
+
if (typeof maxFiles === "number") return accepted.slice(0, maxFiles);
|
|
29
|
+
return accepted;
|
|
30
|
+
}
|
|
31
|
+
function Dropzone({
|
|
32
|
+
onFilesSelected,
|
|
33
|
+
onFilesRejected,
|
|
34
|
+
accept,
|
|
35
|
+
multiple = true,
|
|
36
|
+
maxFiles,
|
|
37
|
+
maxSize,
|
|
38
|
+
disabled = false,
|
|
39
|
+
children,
|
|
40
|
+
className,
|
|
41
|
+
"aria-label": ariaLabel,
|
|
42
|
+
...rest
|
|
43
|
+
}) {
|
|
44
|
+
const inputRef = React.useRef(null);
|
|
45
|
+
const [isDragOver, setIsDragOver] = React.useState(false);
|
|
46
|
+
const dragCounter = React.useRef(0);
|
|
47
|
+
const emit = React.useCallback(
|
|
48
|
+
(files) => {
|
|
49
|
+
if (!files || disabled) return;
|
|
50
|
+
const all = Array.from(files);
|
|
51
|
+
if (all.length === 0) return;
|
|
52
|
+
const accepted = filterFiles(all, { accept, maxSize, maxFiles });
|
|
53
|
+
if (accepted.length === 0) {
|
|
54
|
+
onFilesRejected?.(all);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const finalAccepted = !multiple ? accepted.slice(0, 1) : accepted;
|
|
58
|
+
const rejected = all.filter((f) => !finalAccepted.includes(f));
|
|
59
|
+
onFilesSelected?.(finalAccepted);
|
|
60
|
+
if (rejected.length > 0) onFilesRejected?.(rejected);
|
|
61
|
+
},
|
|
62
|
+
[
|
|
63
|
+
accept,
|
|
64
|
+
disabled,
|
|
65
|
+
maxFiles,
|
|
66
|
+
maxSize,
|
|
67
|
+
multiple,
|
|
68
|
+
onFilesSelected,
|
|
69
|
+
onFilesRejected
|
|
70
|
+
]
|
|
71
|
+
);
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
const reset = () => {
|
|
74
|
+
dragCounter.current = 0;
|
|
75
|
+
setIsDragOver(false);
|
|
76
|
+
};
|
|
77
|
+
window.addEventListener("dragend", reset);
|
|
78
|
+
window.addEventListener("drop", reset);
|
|
79
|
+
return () => {
|
|
80
|
+
window.removeEventListener("dragend", reset);
|
|
81
|
+
window.removeEventListener("drop", reset);
|
|
82
|
+
};
|
|
83
|
+
}, []);
|
|
84
|
+
const openFileDialog = React.useCallback(() => {
|
|
85
|
+
if (disabled) return;
|
|
86
|
+
inputRef.current?.click();
|
|
87
|
+
}, [disabled]);
|
|
88
|
+
const handleDragEnter = (e) => {
|
|
89
|
+
if (disabled) return;
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
dragCounter.current += 1;
|
|
92
|
+
if (e.dataTransfer.types.includes("Files")) setIsDragOver(true);
|
|
93
|
+
};
|
|
94
|
+
const handleDragOver = (e) => {
|
|
95
|
+
if (disabled) return;
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
e.dataTransfer.dropEffect = "copy";
|
|
98
|
+
};
|
|
99
|
+
const handleDragLeave = (e) => {
|
|
100
|
+
if (disabled) return;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
dragCounter.current = Math.max(0, dragCounter.current - 1);
|
|
103
|
+
if (dragCounter.current === 0) setIsDragOver(false);
|
|
104
|
+
};
|
|
105
|
+
const handleDrop = (e) => {
|
|
106
|
+
if (disabled) return;
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
dragCounter.current = 0;
|
|
109
|
+
setIsDragOver(false);
|
|
110
|
+
emit(e.dataTransfer.files);
|
|
111
|
+
};
|
|
112
|
+
const handleKeyDown = (e) => {
|
|
113
|
+
if (disabled) return;
|
|
114
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
115
|
+
e.preventDefault();
|
|
116
|
+
openFileDialog();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const renderState = {
|
|
120
|
+
isDragOver,
|
|
121
|
+
isDisabled: disabled,
|
|
122
|
+
openFileDialog
|
|
123
|
+
};
|
|
124
|
+
return /* @__PURE__ */ jsxs(
|
|
125
|
+
"div",
|
|
126
|
+
{
|
|
127
|
+
role: "button",
|
|
128
|
+
tabIndex: disabled ? -1 : 0,
|
|
129
|
+
"aria-label": ariaLabel,
|
|
130
|
+
"aria-disabled": disabled || void 0,
|
|
131
|
+
"data-drag-over": isDragOver || void 0,
|
|
132
|
+
onClick: openFileDialog,
|
|
133
|
+
onKeyDown: handleKeyDown,
|
|
134
|
+
onDragEnter: handleDragEnter,
|
|
135
|
+
onDragOver: handleDragOver,
|
|
136
|
+
onDragLeave: handleDragLeave,
|
|
137
|
+
onDrop: handleDrop,
|
|
138
|
+
className: cn(
|
|
139
|
+
"flex w-full cursor-pointer select-none flex-col items-center justify-center gap-[var(--space-2,0.5rem)] rounded-md border-2 border-dashed border-input bg-background px-[var(--space-6,1.5rem)] py-[var(--space-8,2rem)] text-center text-sm transition-all duration-[var(--duration-normal,200ms)] ease-out",
|
|
140
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
141
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
142
|
+
isDragOver && "border-primary bg-accent text-accent-foreground",
|
|
143
|
+
disabled && "pointer-events-none opacity-50",
|
|
144
|
+
className
|
|
145
|
+
),
|
|
146
|
+
...rest,
|
|
147
|
+
children: [
|
|
148
|
+
typeof children === "function" ? children(renderState) : children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
149
|
+
/* @__PURE__ */ jsxs(
|
|
150
|
+
"svg",
|
|
151
|
+
{
|
|
152
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
153
|
+
viewBox: "0 0 24 24",
|
|
154
|
+
fill: "none",
|
|
155
|
+
stroke: "currentColor",
|
|
156
|
+
strokeWidth: "2",
|
|
157
|
+
strokeLinecap: "round",
|
|
158
|
+
strokeLinejoin: "round",
|
|
159
|
+
className: "h-6 w-6 text-muted-foreground",
|
|
160
|
+
"aria-hidden": "true",
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
163
|
+
/* @__PURE__ */ jsx("polyline", { points: "17 8 12 3 7 8" }),
|
|
164
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
),
|
|
168
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: isDragOver ? "Drop files to upload" : "Drag files here or click to browse" }),
|
|
169
|
+
accept ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: accept }) : null
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsx(
|
|
172
|
+
"input",
|
|
173
|
+
{
|
|
174
|
+
ref: inputRef,
|
|
175
|
+
type: "file",
|
|
176
|
+
accept,
|
|
177
|
+
multiple,
|
|
178
|
+
disabled,
|
|
179
|
+
className: "sr-only",
|
|
180
|
+
onChange: (e) => {
|
|
181
|
+
emit(e.target.files);
|
|
182
|
+
e.target.value = "";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
Dropzone.displayName = "Dropzone";
|
|
191
|
+
|
|
192
|
+
export { Dropzone };
|
|
193
|
+
//# sourceMappingURL=dropzone.js.map
|
|
194
|
+
//# sourceMappingURL=dropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/components/dropzone/dropzone.tsx"],"names":[],"mappings":"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","file":"dropzone.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\ninterface DropzoneProps\n\textends Omit<\n\t\tReact.HTMLAttributes<HTMLDivElement>,\n\t\t\"onChange\" | \"onDrop\" | \"children\"\n\t> {\n\t/** Fired with the accepted file list every time the user picks or drops files. */\n\tonFilesSelected?: (files: File[]) => void;\n\t/**\n\t * Fired when files are dropped/picked but ALL of them are filtered out by\n\t * `accept` / `maxSize` / `maxFiles`. Useful for surfacing \"file too large\"\n\t * or \"wrong type\" toasts to the user. Receives the rejected File[].\n\t */\n\tonFilesRejected?: (files: File[]) => void;\n\t/** `accept` attribute forwarded to the hidden file input (e.g. \"image/*\", \".csv\"). */\n\taccept?: string;\n\t/** Allow multiple files. Default true. */\n\tmultiple?: boolean;\n\t/** Maximum total file count (after dedupe). Excess files are dropped silently — surface in your handler. */\n\tmaxFiles?: number;\n\t/** Maximum size per file in bytes. Files over the cap are filtered before onFilesSelected fires. */\n\tmaxSize?: number;\n\t/** Disable interaction. */\n\tdisabled?: boolean;\n\t/** Optional render override for the dropzone body. Receives drag state. */\n\tchildren?: React.ReactNode | ((state: DropzoneRenderState) => React.ReactNode);\n\t/** Required accessible name for the drop area. */\n\t\"aria-label\": string;\n}\n\ninterface DropzoneRenderState {\n\tisDragOver: boolean;\n\tisDisabled: boolean;\n\topenFileDialog: () => void;\n}\n\n/** Apply `accept` / `maxSize` / `maxFiles` filters before emitting to onFilesSelected. */\nfunction filterFiles(\n\tfiles: FileList | File[],\n\t{ accept, maxSize, maxFiles }: { accept?: string; maxSize?: number; maxFiles?: number },\n): File[] {\n\tconst list = Array.from(files);\n\tconst acceptList = accept\n\t\t? accept\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim())\n\t\t\t\t.filter(Boolean)\n\t\t: undefined;\n\n\tconst matchAccept = (file: File): boolean => {\n\t\tif (!acceptList) return true;\n\t\treturn acceptList.some((entry) => {\n\t\t\tif (entry.startsWith(\".\")) {\n\t\t\t\treturn file.name.toLowerCase().endsWith(entry.toLowerCase());\n\t\t\t}\n\t\t\tif (entry.endsWith(\"/*\")) {\n\t\t\t\tconst prefix = entry.slice(0, -1); // \"image/\"\n\t\t\t\treturn file.type.startsWith(prefix);\n\t\t\t}\n\t\t\treturn file.type === entry;\n\t\t});\n\t};\n\n\tconst sized =\n\t\ttypeof maxSize === \"number\" ? list.filter((f) => f.size <= maxSize) : list;\n\tconst accepted = sized.filter(matchAccept);\n\tif (typeof maxFiles === \"number\") return accepted.slice(0, maxFiles);\n\treturn accepted;\n}\n\n/**\n * Drag-and-drop file input built on the native HTML5 drag-drop API plus a\n * visually-hidden (sr-only) `<input type=\"file\">` for screen-reader and\n * keyboard access.\n *\n * Two interaction surfaces:\n * - The visible drop area is a `role=\"button\"` div with `tabIndex=0` and the\n * required `aria-label`. Click, Enter, or Space proxies through to click the\n * hidden input, opening the system file dialog.\n * - The hidden input itself remains in the accessibility tree (sr-only, NOT\n * `aria-hidden`) so AT-driven file pickers can find it directly.\n *\n * Pass `children` as a node (default placeholder) or a function receiving\n * `{ isDragOver, isDisabled, openFileDialog }` for full layout control.\n * @returns A drop area + hidden file input pair that yields a File[].\n */\nfunction Dropzone({\n\tonFilesSelected,\n\tonFilesRejected,\n\taccept,\n\tmultiple = true,\n\tmaxFiles,\n\tmaxSize,\n\tdisabled = false,\n\tchildren,\n\tclassName,\n\t\"aria-label\": ariaLabel,\n\t...rest\n}: DropzoneProps) {\n\tconst inputRef = React.useRef<HTMLInputElement>(null);\n\tconst [isDragOver, setIsDragOver] = React.useState(false);\n\tconst dragCounter = React.useRef(0);\n\n\tconst emit = React.useCallback(\n\t\t(files: FileList | File[] | null | undefined) => {\n\t\t\tif (!files || disabled) return;\n\t\t\tconst all = Array.from(files);\n\t\t\tif (all.length === 0) return;\n\t\t\tconst accepted = filterFiles(all, { accept, maxSize, maxFiles });\n\t\t\tif (accepted.length === 0) {\n\t\t\t\tonFilesRejected?.(all);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst finalAccepted = !multiple ? accepted.slice(0, 1) : accepted;\n\t\t\tconst rejected = all.filter((f) => !finalAccepted.includes(f));\n\t\t\tonFilesSelected?.(finalAccepted);\n\t\t\tif (rejected.length > 0) onFilesRejected?.(rejected);\n\t\t},\n\t\t[\n\t\t\taccept,\n\t\t\tdisabled,\n\t\t\tmaxFiles,\n\t\t\tmaxSize,\n\t\t\tmultiple,\n\t\t\tonFilesSelected,\n\t\t\tonFilesRejected,\n\t\t],\n\t);\n\n\t/*\n\t * Reset the drag counter + isDragOver when the user cancels a drag outside\n\t * the dropzone (Esc, drag off the page, switch tab). Without this, the\n\t * counter can stay >0 and the dropzone gets stuck in its hover style.\n\t */\n\tReact.useEffect(() => {\n\t\tconst reset = () => {\n\t\t\tdragCounter.current = 0;\n\t\t\tsetIsDragOver(false);\n\t\t};\n\t\twindow.addEventListener(\"dragend\", reset);\n\t\twindow.addEventListener(\"drop\", reset);\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"dragend\", reset);\n\t\t\twindow.removeEventListener(\"drop\", reset);\n\t\t};\n\t}, []);\n\n\tconst openFileDialog = React.useCallback(() => {\n\t\tif (disabled) return;\n\t\tinputRef.current?.click();\n\t}, [disabled]);\n\n\tconst handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n\t\tif (disabled) return;\n\t\te.preventDefault();\n\t\tdragCounter.current += 1;\n\t\tif (e.dataTransfer.types.includes(\"Files\")) setIsDragOver(true);\n\t};\n\tconst handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n\t\tif (disabled) return;\n\t\te.preventDefault();\n\t\te.dataTransfer.dropEffect = \"copy\";\n\t};\n\tconst handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n\t\tif (disabled) return;\n\t\te.preventDefault();\n\t\tdragCounter.current = Math.max(0, dragCounter.current - 1);\n\t\tif (dragCounter.current === 0) setIsDragOver(false);\n\t};\n\tconst handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n\t\tif (disabled) return;\n\t\te.preventDefault();\n\t\tdragCounter.current = 0;\n\t\tsetIsDragOver(false);\n\t\temit(e.dataTransfer.files);\n\t};\n\tconst handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n\t\tif (disabled) return;\n\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\te.preventDefault();\n\t\t\topenFileDialog();\n\t\t}\n\t};\n\n\tconst renderState: DropzoneRenderState = {\n\t\tisDragOver,\n\t\tisDisabled: disabled,\n\t\topenFileDialog,\n\t};\n\n\treturn (\n\t\t<div\n\t\t\trole=\"button\"\n\t\t\ttabIndex={disabled ? -1 : 0}\n\t\t\taria-label={ariaLabel}\n\t\t\taria-disabled={disabled || undefined}\n\t\t\tdata-drag-over={isDragOver || undefined}\n\t\t\tonClick={openFileDialog}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t\tonDragEnter={handleDragEnter}\n\t\t\tonDragOver={handleDragOver}\n\t\t\tonDragLeave={handleDragLeave}\n\t\t\tonDrop={handleDrop}\n\t\t\tclassName={cn(\n\t\t\t\t\"flex w-full cursor-pointer select-none flex-col items-center justify-center gap-[var(--space-2,0.5rem)] rounded-md border-2 border-dashed border-input bg-background px-[var(--space-6,1.5rem)] py-[var(--space-8,2rem)] text-center text-sm transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\"hover:bg-accent hover:text-accent-foreground\",\n\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\tisDragOver && \"border-primary bg-accent text-accent-foreground\",\n\t\t\t\tdisabled && \"pointer-events-none opacity-50\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...rest}\n\t\t>\n\t\t\t{typeof children === \"function\"\n\t\t\t\t? children(renderState)\n\t\t\t\t: (children ?? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\tclassName=\"h-6 w-6 text-muted-foreground\"\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n\t\t\t\t\t\t\t\t<polyline points=\"17 8 12 3 7 8\" />\n\t\t\t\t\t\t\t\t<line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\" />\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t<span className=\"font-medium\">\n\t\t\t\t\t\t\t\t{isDragOver ? \"Drop files to upload\" : \"Drag files here or click to browse\"}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{accept ? (\n\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">{accept}</span>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t</>\n\t\t\t\t\t))}\n\t\t\t<input\n\t\t\t\tref={inputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept={accept}\n\t\t\t\tmultiple={multiple}\n\t\t\t\tdisabled={disabled}\n\t\t\t\tclassName=\"sr-only\"\n\t\t\t\t/*\n\t\t\t\t * Intentionally NOT aria-hidden + NOT tabIndex=-1: the input\n\t\t\t\t * stays in the a11y tree so AT-driven file pickers (NVDA's\n\t\t\t\t * forms mode, JAWS) can find it. Visually hidden via sr-only.\n\t\t\t\t */\n\t\t\t\tonChange={(e) => {\n\t\t\t\t\temit(e.target.files);\n\t\t\t\t\t// Reset so picking the same file twice still fires onChange\n\t\t\t\t\te.target.value = \"\";\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>\n\t);\n}\nDropzone.displayName = \"Dropzone\";\n\nexport { Dropzone };\nexport type { DropzoneProps, DropzoneRenderState };\n"]}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function cn(...inputs) {
|
|
8
|
+
return twMerge(clsx(inputs));
|
|
9
|
+
}
|
|
10
|
+
function flatten(nodes, expandedSet, level = 1, parentId = null) {
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const node of nodes) {
|
|
13
|
+
const hasChildren = Array.isArray(node.children);
|
|
14
|
+
out.push({
|
|
15
|
+
id: node.id,
|
|
16
|
+
name: node.name,
|
|
17
|
+
level,
|
|
18
|
+
hasChildren,
|
|
19
|
+
disabled: !!node.disabled,
|
|
20
|
+
parentId,
|
|
21
|
+
icon: node.icon
|
|
22
|
+
});
|
|
23
|
+
if (hasChildren && expandedSet.has(node.id) && node.children) {
|
|
24
|
+
out.push(...flatten(node.children, expandedSet, level + 1, node.id));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function FolderIcon({ open }) {
|
|
30
|
+
return /* @__PURE__ */ jsx(
|
|
31
|
+
"svg",
|
|
32
|
+
{
|
|
33
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
34
|
+
viewBox: "0 0 24 24",
|
|
35
|
+
fill: "none",
|
|
36
|
+
stroke: "currentColor",
|
|
37
|
+
strokeWidth: "2",
|
|
38
|
+
strokeLinecap: "round",
|
|
39
|
+
strokeLinejoin: "round",
|
|
40
|
+
className: "h-4 w-4 shrink-0",
|
|
41
|
+
"aria-hidden": "true",
|
|
42
|
+
children: open ? /* @__PURE__ */ jsx("path", { d: "M3 7v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-7l-2-2H5a2 2 0 0 0-2 2z" }) : /* @__PURE__ */ jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" })
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function FileIcon() {
|
|
47
|
+
return /* @__PURE__ */ jsxs(
|
|
48
|
+
"svg",
|
|
49
|
+
{
|
|
50
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
51
|
+
viewBox: "0 0 24 24",
|
|
52
|
+
fill: "none",
|
|
53
|
+
stroke: "currentColor",
|
|
54
|
+
strokeWidth: "2",
|
|
55
|
+
strokeLinecap: "round",
|
|
56
|
+
strokeLinejoin: "round",
|
|
57
|
+
className: "h-4 w-4 shrink-0",
|
|
58
|
+
"aria-hidden": "true",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
|
|
61
|
+
/* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" })
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function Chevron({ expanded }) {
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
"svg",
|
|
69
|
+
{
|
|
70
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
71
|
+
viewBox: "0 0 24 24",
|
|
72
|
+
fill: "none",
|
|
73
|
+
stroke: "currentColor",
|
|
74
|
+
strokeWidth: "2",
|
|
75
|
+
strokeLinecap: "round",
|
|
76
|
+
strokeLinejoin: "round",
|
|
77
|
+
className: cn(
|
|
78
|
+
"h-3 w-3 shrink-0 text-muted-foreground transition-transform duration-[var(--duration-normal,200ms)] ease-out",
|
|
79
|
+
expanded ? "rotate-90" : ""
|
|
80
|
+
),
|
|
81
|
+
"aria-hidden": "true",
|
|
82
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "9 18 15 12 9 6" })
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
function TreeItem({
|
|
87
|
+
node,
|
|
88
|
+
level,
|
|
89
|
+
expandedSet,
|
|
90
|
+
selected,
|
|
91
|
+
onToggle,
|
|
92
|
+
onSelect,
|
|
93
|
+
onKeyDown,
|
|
94
|
+
registerRef,
|
|
95
|
+
tabbableId
|
|
96
|
+
}) {
|
|
97
|
+
const hasChildren = Array.isArray(node.children);
|
|
98
|
+
const isExpanded = hasChildren && expandedSet.has(node.id);
|
|
99
|
+
const isSelected = selected === node.id;
|
|
100
|
+
return /* @__PURE__ */ jsxs("li", { role: "none", children: [
|
|
101
|
+
/* @__PURE__ */ jsxs(
|
|
102
|
+
"div",
|
|
103
|
+
{
|
|
104
|
+
role: "treeitem",
|
|
105
|
+
"aria-level": level,
|
|
106
|
+
"aria-expanded": hasChildren ? isExpanded : void 0,
|
|
107
|
+
"aria-selected": isSelected,
|
|
108
|
+
"aria-disabled": node.disabled || void 0,
|
|
109
|
+
tabIndex: tabbableId === node.id ? 0 : -1,
|
|
110
|
+
ref: (el) => registerRef(node.id, el),
|
|
111
|
+
onClick: (e) => {
|
|
112
|
+
if (node.disabled) return;
|
|
113
|
+
e.stopPropagation();
|
|
114
|
+
onSelect(node.id);
|
|
115
|
+
},
|
|
116
|
+
onKeyDown: (e) => onKeyDown(e, node.id),
|
|
117
|
+
className: cn(
|
|
118
|
+
"flex items-center gap-[var(--space-2,0.5rem)] rounded-md px-[var(--space-2,0.5rem)] py-[var(--space-1,0.25rem)] text-sm cursor-pointer select-none transition-colors duration-[var(--duration-normal,200ms)] ease-out",
|
|
119
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
120
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
|
121
|
+
isSelected && "bg-accent text-accent-foreground",
|
|
122
|
+
node.disabled && "opacity-50 cursor-not-allowed pointer-events-none"
|
|
123
|
+
),
|
|
124
|
+
style: { paddingInlineStart: `calc(${level - 1} * 1rem + var(--space-2, 0.5rem))` },
|
|
125
|
+
children: [
|
|
126
|
+
hasChildren ? /* @__PURE__ */ jsx(
|
|
127
|
+
"button",
|
|
128
|
+
{
|
|
129
|
+
type: "button",
|
|
130
|
+
tabIndex: -1,
|
|
131
|
+
"aria-hidden": "true",
|
|
132
|
+
onClick: (e) => {
|
|
133
|
+
e.stopPropagation();
|
|
134
|
+
if (node.disabled) return;
|
|
135
|
+
onToggle(node.id);
|
|
136
|
+
},
|
|
137
|
+
className: "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-sm hover:bg-accent-foreground/10",
|
|
138
|
+
children: /* @__PURE__ */ jsx(Chevron, { expanded: isExpanded })
|
|
139
|
+
}
|
|
140
|
+
) : /* @__PURE__ */ jsx("span", { className: "w-3 shrink-0", "aria-hidden": "true" }),
|
|
141
|
+
node.icon ?? (hasChildren ? /* @__PURE__ */ jsx(FolderIcon, { open: isExpanded }) : /* @__PURE__ */ jsx(FileIcon, {})),
|
|
142
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: node.name })
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
hasChildren && isExpanded && node.children ? /* @__PURE__ */ jsx("ul", { role: "group", className: "m-0 list-none p-0", children: node.children.map((child) => /* @__PURE__ */ jsx(
|
|
147
|
+
TreeItem,
|
|
148
|
+
{
|
|
149
|
+
node: child,
|
|
150
|
+
level: level + 1,
|
|
151
|
+
expandedSet,
|
|
152
|
+
selected,
|
|
153
|
+
onToggle,
|
|
154
|
+
onSelect,
|
|
155
|
+
onKeyDown,
|
|
156
|
+
registerRef,
|
|
157
|
+
tabbableId
|
|
158
|
+
},
|
|
159
|
+
child.id
|
|
160
|
+
)) }) : null
|
|
161
|
+
] });
|
|
162
|
+
}
|
|
163
|
+
function FileTree({
|
|
164
|
+
nodes,
|
|
165
|
+
defaultExpanded,
|
|
166
|
+
expanded: expandedProp,
|
|
167
|
+
onExpandedChange,
|
|
168
|
+
selected,
|
|
169
|
+
onSelect,
|
|
170
|
+
"aria-label": ariaLabel,
|
|
171
|
+
className
|
|
172
|
+
}) {
|
|
173
|
+
const isControlled = expandedProp !== void 0;
|
|
174
|
+
const [internalExpanded, setInternalExpanded] = React.useState(
|
|
175
|
+
defaultExpanded ?? []
|
|
176
|
+
);
|
|
177
|
+
const expanded = isControlled ? expandedProp : internalExpanded;
|
|
178
|
+
const expandedSet = React.useMemo(() => new Set(expanded), [expanded]);
|
|
179
|
+
const itemRefs = React.useRef(/* @__PURE__ */ new Map());
|
|
180
|
+
const registerRef = React.useCallback(
|
|
181
|
+
(id, el) => {
|
|
182
|
+
if (el) itemRefs.current.set(id, el);
|
|
183
|
+
else itemRefs.current.delete(id);
|
|
184
|
+
},
|
|
185
|
+
[]
|
|
186
|
+
);
|
|
187
|
+
const flat = React.useMemo(
|
|
188
|
+
() => flatten(nodes, expandedSet),
|
|
189
|
+
[nodes, expandedSet]
|
|
190
|
+
);
|
|
191
|
+
const firstId = flat[0]?.id ?? null;
|
|
192
|
+
const [focusedId, setFocusedId] = React.useState(null);
|
|
193
|
+
const visibleIds = React.useMemo(
|
|
194
|
+
() => new Set(flat.map((n) => n.id)),
|
|
195
|
+
[flat]
|
|
196
|
+
);
|
|
197
|
+
const candidate = focusedId ?? selected ?? firstId;
|
|
198
|
+
const tabbableId = candidate && visibleIds.has(candidate) ? candidate : firstId;
|
|
199
|
+
const setExpanded = React.useCallback(
|
|
200
|
+
(next) => {
|
|
201
|
+
if (!isControlled) setInternalExpanded(next);
|
|
202
|
+
onExpandedChange?.(next);
|
|
203
|
+
},
|
|
204
|
+
[isControlled, onExpandedChange]
|
|
205
|
+
);
|
|
206
|
+
const toggle = React.useCallback(
|
|
207
|
+
(id) => {
|
|
208
|
+
const set = new Set(expanded);
|
|
209
|
+
if (set.has(id)) set.delete(id);
|
|
210
|
+
else set.add(id);
|
|
211
|
+
setExpanded(Array.from(set));
|
|
212
|
+
},
|
|
213
|
+
[expanded, setExpanded]
|
|
214
|
+
);
|
|
215
|
+
const handleSelect = React.useCallback(
|
|
216
|
+
(id) => {
|
|
217
|
+
onSelect?.(id);
|
|
218
|
+
setFocusedId(id);
|
|
219
|
+
},
|
|
220
|
+
[onSelect]
|
|
221
|
+
);
|
|
222
|
+
const focusNode = (id) => {
|
|
223
|
+
setFocusedId(id);
|
|
224
|
+
requestAnimationFrame(() => itemRefs.current.get(id)?.focus());
|
|
225
|
+
};
|
|
226
|
+
const handleKeyDown = (e, id) => {
|
|
227
|
+
const flatNodes = flat;
|
|
228
|
+
const idx = flatNodes.findIndex((n) => n.id === id);
|
|
229
|
+
const node = flatNodes[idx];
|
|
230
|
+
if (!node) return;
|
|
231
|
+
const findEnabled = (start, dir) => {
|
|
232
|
+
let i = start;
|
|
233
|
+
while (i >= 0 && i < flatNodes.length) {
|
|
234
|
+
if (!flatNodes[i].disabled) return flatNodes[i];
|
|
235
|
+
i += dir;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
};
|
|
239
|
+
switch (e.key) {
|
|
240
|
+
case "ArrowDown": {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
const next = findEnabled(idx + 1, 1);
|
|
243
|
+
if (next) focusNode(next.id);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case "ArrowUp": {
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
const prev = findEnabled(idx - 1, -1);
|
|
249
|
+
if (prev) focusNode(prev.id);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case "ArrowRight": {
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
if (node.hasChildren && !expandedSet.has(node.id)) {
|
|
255
|
+
toggle(node.id);
|
|
256
|
+
} else if (node.hasChildren) {
|
|
257
|
+
const firstChild = flatNodes[idx + 1];
|
|
258
|
+
if (firstChild && firstChild.parentId === node.id)
|
|
259
|
+
focusNode(firstChild.id);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case "ArrowLeft": {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
if (node.hasChildren && expandedSet.has(node.id)) {
|
|
266
|
+
toggle(node.id);
|
|
267
|
+
} else if (node.parentId) {
|
|
268
|
+
focusNode(node.parentId);
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "Home": {
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
if (flatNodes[0]) focusNode(flatNodes[0].id);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "End": {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
const last = flatNodes[flatNodes.length - 1];
|
|
280
|
+
if (last) focusNode(last.id);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "Enter":
|
|
284
|
+
case " ": {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
if (!node.disabled) {
|
|
287
|
+
if (node.hasChildren) toggle(node.id);
|
|
288
|
+
handleSelect(node.id);
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
return /* @__PURE__ */ jsx(
|
|
295
|
+
"ul",
|
|
296
|
+
{
|
|
297
|
+
role: "tree",
|
|
298
|
+
"aria-label": ariaLabel,
|
|
299
|
+
className: cn("list-none p-0 m-0", className),
|
|
300
|
+
children: nodes.map((node) => /* @__PURE__ */ jsx(
|
|
301
|
+
TreeItem,
|
|
302
|
+
{
|
|
303
|
+
node,
|
|
304
|
+
level: 1,
|
|
305
|
+
expandedSet,
|
|
306
|
+
selected,
|
|
307
|
+
onToggle: toggle,
|
|
308
|
+
onSelect: handleSelect,
|
|
309
|
+
onKeyDown: handleKeyDown,
|
|
310
|
+
registerRef,
|
|
311
|
+
tabbableId
|
|
312
|
+
},
|
|
313
|
+
node.id
|
|
314
|
+
))
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
FileTree.displayName = "FileTree";
|
|
319
|
+
|
|
320
|
+
export { FileTree };
|
|
321
|
+
//# sourceMappingURL=file-tree.js.map
|
|
322
|
+
//# sourceMappingURL=file-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/components/file-tree/file-tree.tsx"],"names":[],"mappings":"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","file":"file-tree.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\ninterface FileTreeNode {\n\t/** Stable unique id used as React key + ARIA target. */\n\tid: string;\n\t/** Display name (file or folder). */\n\tname: string;\n\t/** Nested children. Presence (even if empty array) marks the node as a folder. */\n\tchildren?: FileTreeNode[];\n\t/** Optional icon override. Default chooses folder/file based on `children`. */\n\ticon?: React.ReactNode;\n\t/** Disable selection + expand toggle. */\n\tdisabled?: boolean;\n}\n\ninterface FileTreeProps {\n\t/** Root nodes. */\n\tnodes: FileTreeNode[];\n\t/** Uncontrolled initial expanded ids. */\n\tdefaultExpanded?: string[];\n\t/** Controlled expanded ids. */\n\texpanded?: string[];\n\t/** Fired when expanded set changes (array of ids). */\n\tonExpandedChange?: (ids: string[]) => void;\n\t/** Controlled selected node id. */\n\tselected?: string;\n\t/** Fired when the user activates a node (click, Enter, or Space). */\n\tonSelect?: (id: string) => void;\n\t/** Required accessible name for the tree container. */\n\t\"aria-label\": string;\n\t/** Extra class names on the root tree element. */\n\tclassName?: string;\n}\n\ninterface FlatNode {\n\tid: string;\n\tname: string;\n\tlevel: number;\n\thasChildren: boolean;\n\tdisabled: boolean;\n\tparentId: string | null;\n\ticon: React.ReactNode | undefined;\n}\n\n/** Walk the tree once, emitting every visible node in document order. */\nfunction flatten(\n\tnodes: FileTreeNode[],\n\texpandedSet: Set<string>,\n\tlevel = 1,\n\tparentId: string | null = null,\n): FlatNode[] {\n\tconst out: FlatNode[] = [];\n\tfor (const node of nodes) {\n\t\tconst hasChildren = Array.isArray(node.children);\n\t\tout.push({\n\t\t\tid: node.id,\n\t\t\tname: node.name,\n\t\t\tlevel,\n\t\t\thasChildren,\n\t\t\tdisabled: !!node.disabled,\n\t\t\tparentId,\n\t\t\ticon: node.icon,\n\t\t});\n\t\tif (hasChildren && expandedSet.has(node.id) && node.children) {\n\t\t\tout.push(...flatten(node.children, expandedSet, level + 1, node.id));\n\t\t}\n\t}\n\treturn out;\n}\n\n/** Default folder glyph; flips between open and closed shapes via `open`. */\nfunction FolderIcon({ open }: { open: boolean }) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName=\"h-4 w-4 shrink-0\"\n\t\t\taria-hidden=\"true\"\n\t\t>\n\t\t\t{open ? (\n\t\t\t\t<path d=\"M3 7v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-7l-2-2H5a2 2 0 0 0-2 2z\" />\n\t\t\t) : (\n\t\t\t\t<path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\" />\n\t\t\t)}\n\t\t</svg>\n\t);\n}\n\n/** Default leaf-node glyph (generic file icon). */\nfunction FileIcon() {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName=\"h-4 w-4 shrink-0\"\n\t\t\taria-hidden=\"true\"\n\t\t>\n\t\t\t<path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\" />\n\t\t\t<polyline points=\"14 2 14 8 20 8\" />\n\t\t</svg>\n\t);\n}\n\n/** Disclosure chevron — rotates 90° when the folder is expanded. */\nfunction Chevron({ expanded }: { expanded: boolean }) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName={cn(\n\t\t\t\t\"h-3 w-3 shrink-0 text-muted-foreground transition-transform duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\texpanded ? \"rotate-90\" : \"\",\n\t\t\t)}\n\t\t\taria-hidden=\"true\"\n\t\t>\n\t\t\t<polyline points=\"9 18 15 12 9 6\" />\n\t\t</svg>\n\t);\n}\n\ninterface TreeItemProps {\n\tnode: FileTreeNode;\n\tlevel: number;\n\texpandedSet: Set<string>;\n\tselected?: string;\n\tonToggle: (id: string) => void;\n\tonSelect: (id: string) => void;\n\tonKeyDown: (e: React.KeyboardEvent<HTMLDivElement>, id: string) => void;\n\tregisterRef: (id: string, el: HTMLDivElement | null) => void;\n\ttabbableId: string | null;\n}\n\n/** Recursive single-node renderer; chevron toggles, row body selects. */\nfunction TreeItem({\n\tnode,\n\tlevel,\n\texpandedSet,\n\tselected,\n\tonToggle,\n\tonSelect,\n\tonKeyDown,\n\tregisterRef,\n\ttabbableId,\n}: TreeItemProps) {\n\tconst hasChildren = Array.isArray(node.children);\n\tconst isExpanded = hasChildren && expandedSet.has(node.id);\n\tconst isSelected = selected === node.id;\n\n\treturn (\n\t\t<li role=\"none\">\n\t\t\t<div\n\t\t\t\trole=\"treeitem\"\n\t\t\t\taria-level={level}\n\t\t\t\taria-expanded={hasChildren ? isExpanded : undefined}\n\t\t\t\taria-selected={isSelected}\n\t\t\t\taria-disabled={node.disabled || undefined}\n\t\t\t\ttabIndex={tabbableId === node.id ? 0 : -1}\n\t\t\t\tref={(el) => registerRef(node.id, el)}\n\t\t\t\tonClick={(e) => {\n\t\t\t\t\tif (node.disabled) return;\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t/*\n\t\t\t\t\t * WAI-ARIA tree pattern: row click selects only. Toggling\n\t\t\t\t\t * a folder is the chevron's job (or ArrowRight/Left, or\n\t\t\t\t\t * Enter/Space when the row is focused).\n\t\t\t\t\t */\n\t\t\t\t\tonSelect(node.id);\n\t\t\t\t}}\n\t\t\t\tonKeyDown={(e) => onKeyDown(e, node.id)}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex items-center gap-[var(--space-2,0.5rem)] rounded-md px-[var(--space-2,0.5rem)] py-[var(--space-1,0.25rem)] text-sm cursor-pointer select-none transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"hover:bg-accent hover:text-accent-foreground\",\n\t\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n\t\t\t\t\tisSelected && \"bg-accent text-accent-foreground\",\n\t\t\t\t\tnode.disabled && \"opacity-50 cursor-not-allowed pointer-events-none\",\n\t\t\t\t)}\n\t\t\t\tstyle={{ paddingInlineStart: `calc(${level - 1} * 1rem + var(--space-2, 0.5rem))` }}\n\t\t\t>\n\t\t\t\t{hasChildren ? (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\ttabIndex={-1}\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t * Decorative button — toggling is also reachable via\n\t\t\t\t\t\t * Enter/Space on the treeitem and ArrowRight/Left, so\n\t\t\t\t\t\t * we don't add this to the keyboard tour.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\tif (node.disabled) return;\n\t\t\t\t\t\t\tonToggle(node.id);\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-sm hover:bg-accent-foreground/10\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<Chevron expanded={isExpanded} />\n\t\t\t\t\t</button>\n\t\t\t\t) : (\n\t\t\t\t\t<span className=\"w-3 shrink-0\" aria-hidden=\"true\" />\n\t\t\t\t)}\n\t\t\t\t{node.icon ?? (hasChildren ? <FolderIcon open={isExpanded} /> : <FileIcon />)}\n\t\t\t\t<span className=\"truncate\">{node.name}</span>\n\t\t\t</div>\n\t\t\t{hasChildren && isExpanded && node.children ? (\n\t\t\t\t<ul role=\"group\" className=\"m-0 list-none p-0\">\n\t\t\t\t\t{node.children.map((child) => (\n\t\t\t\t\t\t<TreeItem\n\t\t\t\t\t\t\tkey={child.id}\n\t\t\t\t\t\t\tnode={child}\n\t\t\t\t\t\t\tlevel={level + 1}\n\t\t\t\t\t\t\texpandedSet={expandedSet}\n\t\t\t\t\t\t\tselected={selected}\n\t\t\t\t\t\t\tonToggle={onToggle}\n\t\t\t\t\t\t\tonSelect={onSelect}\n\t\t\t\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\t\t\t\tregisterRef={registerRef}\n\t\t\t\t\t\t\ttabbableId={tabbableId}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</ul>\n\t\t\t) : null}\n\t\t</li>\n\t);\n}\n\n/**\n * Hierarchical tree view for files, folders, settings sections, or any nested\n * navigation. Built on the WAI-ARIA tree pattern: `role=\"tree\"` root,\n * `role=\"treeitem\"` per node, `role=\"group\"` per child group, with\n * `aria-level` / `aria-expanded` / `aria-selected` reflecting state.\n *\n * Keyboard: Up/Down move between visible items; Right expands a folder or\n * moves to the first child; Left collapses or moves to the parent;\n * Enter/Space activate the focused node; Home/End jump to the first/last.\n *\n * Expanded state is uncontrolled by default (`defaultExpanded`). Pass\n * `expanded` + `onExpandedChange` for controlled mode.\n * @returns A keyboard-accessible nested tree.\n */\nfunction FileTree({\n\tnodes,\n\tdefaultExpanded,\n\texpanded: expandedProp,\n\tonExpandedChange,\n\tselected,\n\tonSelect,\n\t\"aria-label\": ariaLabel,\n\tclassName,\n}: FileTreeProps) {\n\tconst isControlled = expandedProp !== undefined;\n\tconst [internalExpanded, setInternalExpanded] = React.useState<string[]>(\n\t\tdefaultExpanded ?? [],\n\t);\n\tconst expanded = isControlled ? expandedProp : internalExpanded;\n\tconst expandedSet = React.useMemo(() => new Set(expanded), [expanded]);\n\n\tconst itemRefs = React.useRef(new Map<string, HTMLDivElement>());\n\tconst registerRef = React.useCallback(\n\t\t(id: string, el: HTMLDivElement | null) => {\n\t\t\tif (el) itemRefs.current.set(id, el);\n\t\t\telse itemRefs.current.delete(id);\n\t\t},\n\t\t[],\n\t);\n\n\tconst flat = React.useMemo(\n\t\t() => flatten(nodes, expandedSet),\n\t\t[nodes, expandedSet],\n\t);\n\n\tconst firstId = flat[0]?.id ?? null;\n\tconst [focusedId, setFocusedId] = React.useState<string | null>(null);\n\t/*\n\t * Resolve the roving-tabindex target against the *visible* (flattened)\n\t * node set. If `selected` lives inside a collapsed branch its <treeitem>\n\t * doesn't render, and pointing tabIndex=0 at it would silently skip the\n\t * whole tree from Tab navigation.\n\t */\n\tconst visibleIds = React.useMemo(\n\t\t() => new Set(flat.map((n) => n.id)),\n\t\t[flat],\n\t);\n\tconst candidate = focusedId ?? selected ?? firstId;\n\tconst tabbableId =\n\t\tcandidate && visibleIds.has(candidate) ? candidate : firstId;\n\n\tconst setExpanded = React.useCallback(\n\t\t(next: string[]) => {\n\t\t\tif (!isControlled) setInternalExpanded(next);\n\t\t\tonExpandedChange?.(next);\n\t\t},\n\t\t[isControlled, onExpandedChange],\n\t);\n\n\tconst toggle = React.useCallback(\n\t\t(id: string) => {\n\t\t\tconst set = new Set(expanded);\n\t\t\tif (set.has(id)) set.delete(id);\n\t\t\telse set.add(id);\n\t\t\tsetExpanded(Array.from(set));\n\t\t},\n\t\t[expanded, setExpanded],\n\t);\n\n\tconst handleSelect = React.useCallback(\n\t\t(id: string) => {\n\t\t\tonSelect?.(id);\n\t\t\tsetFocusedId(id);\n\t\t},\n\t\t[onSelect],\n\t);\n\n\tconst focusNode = (id: string) => {\n\t\tsetFocusedId(id);\n\t\t// Defer to next paint so the new tabbable element is in the DOM.\n\t\trequestAnimationFrame(() => itemRefs.current.get(id)?.focus());\n\t};\n\n\tconst handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, id: string) => {\n\t\tconst flatNodes = flat;\n\t\tconst idx = flatNodes.findIndex((n) => n.id === id);\n\t\tconst node = flatNodes[idx];\n\t\tif (!node) return;\n\n\t\t// Walk past disabled neighbours so arrow keys never park focus on a\n\t\t// non-actionable node — matches the convention used elsewhere in the\n\t\t// repo (see <Select> / <Combobox> disabled item handling in cmdk).\n\t\tconst findEnabled = (start: number, dir: 1 | -1) => {\n\t\t\tlet i = start;\n\t\t\twhile (i >= 0 && i < flatNodes.length) {\n\t\t\t\tif (!flatNodes[i].disabled) return flatNodes[i];\n\t\t\t\ti += dir;\n\t\t\t}\n\t\t\treturn null;\n\t\t};\n\n\t\tswitch (e.key) {\n\t\t\tcase \"ArrowDown\": {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst next = findEnabled(idx + 1, 1);\n\t\t\t\tif (next) focusNode(next.id);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"ArrowUp\": {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst prev = findEnabled(idx - 1, -1);\n\t\t\t\tif (prev) focusNode(prev.id);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"ArrowRight\": {\n\t\t\t\te.preventDefault();\n\t\t\t\tif (node.hasChildren && !expandedSet.has(node.id)) {\n\t\t\t\t\ttoggle(node.id);\n\t\t\t\t} else if (node.hasChildren) {\n\t\t\t\t\tconst firstChild = flatNodes[idx + 1];\n\t\t\t\t\tif (firstChild && firstChild.parentId === node.id)\n\t\t\t\t\t\tfocusNode(firstChild.id);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"ArrowLeft\": {\n\t\t\t\te.preventDefault();\n\t\t\t\tif (node.hasChildren && expandedSet.has(node.id)) {\n\t\t\t\t\ttoggle(node.id);\n\t\t\t\t} else if (node.parentId) {\n\t\t\t\t\tfocusNode(node.parentId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"Home\": {\n\t\t\t\te.preventDefault();\n\t\t\t\tif (flatNodes[0]) focusNode(flatNodes[0].id);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"End\": {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst last = flatNodes[flatNodes.length - 1];\n\t\t\t\tif (last) focusNode(last.id);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"Enter\":\n\t\t\tcase \" \": {\n\t\t\t\te.preventDefault();\n\t\t\t\tif (!node.disabled) {\n\t\t\t\t\tif (node.hasChildren) toggle(node.id);\n\t\t\t\t\thandleSelect(node.id);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t<ul\n\t\t\trole=\"tree\"\n\t\t\taria-label={ariaLabel}\n\t\t\tclassName={cn(\"list-none p-0 m-0\", className)}\n\t\t>\n\t\t\t{nodes.map((node) => (\n\t\t\t\t<TreeItem\n\t\t\t\t\tkey={node.id}\n\t\t\t\t\tnode={node}\n\t\t\t\t\tlevel={1}\n\t\t\t\t\texpandedSet={expandedSet}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\tonToggle={toggle}\n\t\t\t\t\tonSelect={handleSelect}\n\t\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t\t\tregisterRef={registerRef}\n\t\t\t\t\ttabbableId={tabbableId}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</ul>\n\t);\n}\nFileTree.displayName = \"FileTree\";\n\nexport { FileTree };\nexport type { FileTreeNode, FileTreeProps };\n"]}
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useFormField_alias_1 as useFormField } from './_tsup-dts-rollup.js';
|
|
2
|
+
export { Form_alias_1 as Form } from './_tsup-dts-rollup.js';
|
|
3
|
+
export { FormItem_alias_1 as FormItem } from './_tsup-dts-rollup.js';
|
|
4
|
+
export { FormLabel_alias_1 as FormLabel } from './_tsup-dts-rollup.js';
|
|
5
|
+
export { FormControl_alias_1 as FormControl } from './_tsup-dts-rollup.js';
|
|
6
|
+
export { FormDescription_alias_1 as FormDescription } from './_tsup-dts-rollup.js';
|
|
7
|
+
export { FormMessage_alias_1 as FormMessage } from './_tsup-dts-rollup.js';
|
|
8
|
+
export { FormField_alias_1 as FormField } from './_tsup-dts-rollup.js';
|