@arcfusionz/arc-primitive-ui 0.2.12 → 0.2.14
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/dist/components/AILoader/AILoader.js +2 -2
- package/dist/components/Alert/Alert.js +2 -2
- package/dist/components/Avatar/Avatar.js +4 -4
- package/dist/components/Badge/Badge.js +2 -2
- package/dist/components/Button/Button.js +3 -3
- package/dist/components/Calendar/Calendar.js +2 -2
- package/dist/components/CodeBlock/CodeBlock.d.ts +69 -0
- package/dist/components/CodeBlock/CodeBlock.js +201 -0
- package/dist/components/CodeBlock/index.d.ts +2 -0
- package/dist/components/CodeBlock/index.js +2 -0
- package/dist/components/CodeBlock/sqlHighlight.js +218 -0
- package/dist/components/Field/Field.js +2 -2
- package/dist/components/FileUpload/FileUpload.d.ts +190 -0
- package/dist/components/FileUpload/FileUpload.js +515 -0
- package/dist/components/FileUpload/index.d.ts +2 -0
- package/dist/components/FileUpload/index.js +2 -0
- package/dist/components/Stepper/Stepper.js +2 -2
- package/dist/components/Switch/Switch.js +2 -2
- package/dist/components/SystemState/SystemState.d.ts +4 -3
- package/dist/components/SystemState/SystemState.js +15 -4
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -3
- package/dist/lib/cn.js +2 -1
- package/package.json +1 -1
- package/src/styles/theme.css +23 -2
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { Button } from "../Button/Button.js";
|
|
3
|
+
import { FieldContext } from "../Field/FieldContext.js";
|
|
4
|
+
import { Progress } from "../Progress/Progress.js";
|
|
5
|
+
import { SystemState } from "../SystemState/SystemState.js";
|
|
6
|
+
import { Fragment, createContext, forwardRef, useContext, useEffect, useId, useRef, useState } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
9
|
+
//#region src/components/FileUpload/FileUpload.tsx
|
|
10
|
+
/** `1023 B`, `1.5 KB`, `12 MB` — a compact, locale-neutral size readout. */
|
|
11
|
+
function formatFileSize(bytes) {
|
|
12
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "";
|
|
13
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
14
|
+
const units = [
|
|
15
|
+
"KB",
|
|
16
|
+
"MB",
|
|
17
|
+
"GB",
|
|
18
|
+
"TB"
|
|
19
|
+
];
|
|
20
|
+
let value = bytes / 1024;
|
|
21
|
+
let unit = 0;
|
|
22
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
23
|
+
value /= 1024;
|
|
24
|
+
unit += 1;
|
|
25
|
+
}
|
|
26
|
+
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
27
|
+
}
|
|
28
|
+
function matchesAccept(file, accept) {
|
|
29
|
+
if (!accept) return true;
|
|
30
|
+
const tokens = accept.split(",").map((token) => token.trim().toLowerCase()).filter(Boolean);
|
|
31
|
+
if (tokens.length === 0) return true;
|
|
32
|
+
const name = file.name.toLowerCase();
|
|
33
|
+
const mime = file.type.toLowerCase();
|
|
34
|
+
return tokens.some((token) => {
|
|
35
|
+
if (token.startsWith(".")) return name.endsWith(token);
|
|
36
|
+
if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
|
|
37
|
+
return mime === token;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function fileKey(file) {
|
|
41
|
+
return `${file.name}:${file.size}:${file.lastModified}`;
|
|
42
|
+
}
|
|
43
|
+
const FileUploadContext = createContext(null);
|
|
44
|
+
function useFileUploadContext(part) {
|
|
45
|
+
const context = useContext(FileUploadContext);
|
|
46
|
+
if (context == null) throw new Error(`${part} must be rendered inside <FileUpload>.`);
|
|
47
|
+
return context;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read the enclosing `FileUpload`'s state and controls — for building custom
|
|
51
|
+
* pieces the flat parts don't cover (a file counter, an "Upload all" button, a
|
|
52
|
+
* dropzone that hides once a file is chosen). Must be called under `FileUpload`.
|
|
53
|
+
*/
|
|
54
|
+
function useFileUpload() {
|
|
55
|
+
const context = useFileUploadContext("useFileUpload");
|
|
56
|
+
return {
|
|
57
|
+
files: context.files,
|
|
58
|
+
isDragging: context.isDragging,
|
|
59
|
+
disabled: context.disabled,
|
|
60
|
+
invalid: context.invalid,
|
|
61
|
+
open: context.openPicker,
|
|
62
|
+
remove: context.removeFile,
|
|
63
|
+
clear: context.clear
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const rootClasses = "flex w-full flex-col gap-3";
|
|
67
|
+
const stateAttributesMapping = {
|
|
68
|
+
dragging: (value) => value ? { "data-dragging": "" } : null,
|
|
69
|
+
disabled: (value) => value ? { "data-disabled": "" } : null,
|
|
70
|
+
invalid: (value) => value ? { "data-invalid": "" } : null,
|
|
71
|
+
empty: (value) => value ? { "data-empty": "" } : null
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Drag-and-drop / browse file selection built on a native input. Owns the
|
|
75
|
+
* selected-file list, drag interaction, and client-side validation (`accept`,
|
|
76
|
+
* `maxSize`/`minSize`, `maxFiles`, custom `validate`); the actual upload stays
|
|
77
|
+
* with the consumer, reflected per file through `FileUploadItem`. The empty
|
|
78
|
+
* dropzone is a `SystemState` (`upload` / `upload-error`).
|
|
79
|
+
*
|
|
80
|
+
* Accessibility: the input is visually hidden and the `FileUploadTrigger`
|
|
81
|
+
* button is the focusable, keyboard-operable control (drag-and-drop is a
|
|
82
|
+
* pointer enhancement). Announce added or rejected files yourself — the
|
|
83
|
+
* component is not a live region. Inside a `Field` it inherits
|
|
84
|
+
* `disabled`/`invalid`/`required`.
|
|
85
|
+
*/
|
|
86
|
+
const FileUpload = forwardRef(({ accept, multiple = false, maxFiles, maxSize, minSize, validate, allowDrop = true, preventDocumentDrop = true, directory = false, capture, value, defaultValue, onValueChange, onAccept, onReject, disabled, invalid, required, name, inputLabel, render, className, children, ...rest }, ref) => {
|
|
87
|
+
const fieldContext = useContext(FieldContext);
|
|
88
|
+
const resolvedDisabled = disabled ?? fieldContext?.control?.disabled ?? false;
|
|
89
|
+
const resolvedInvalid = invalid ?? fieldContext?.invalid ?? false;
|
|
90
|
+
const resolvedRequired = required ?? fieldContext?.required ?? false;
|
|
91
|
+
const inputId = `${useId()}-file-input`;
|
|
92
|
+
const inputRef = useRef(null);
|
|
93
|
+
const rootRef = useRef(null);
|
|
94
|
+
const dragDepth = useRef(0);
|
|
95
|
+
const isControlled = value !== void 0;
|
|
96
|
+
const [internalFiles, setInternalFiles] = useState(defaultValue ?? []);
|
|
97
|
+
const files = isControlled ? value : internalFiles;
|
|
98
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
99
|
+
const syncInputFiles = (list) => {
|
|
100
|
+
const input = inputRef.current;
|
|
101
|
+
if (!input || typeof DataTransfer === "undefined") return;
|
|
102
|
+
try {
|
|
103
|
+
const transfer = new DataTransfer();
|
|
104
|
+
for (const file of list) transfer.items.add(file);
|
|
105
|
+
input.files = transfer.files;
|
|
106
|
+
} catch {}
|
|
107
|
+
};
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
syncInputFiles(files);
|
|
110
|
+
}, [files]);
|
|
111
|
+
const commit = (next) => {
|
|
112
|
+
if (!isControlled) setInternalFiles(next);
|
|
113
|
+
onValueChange?.(next);
|
|
114
|
+
};
|
|
115
|
+
const getRejectionCodes = (file, projectedCount) => {
|
|
116
|
+
const codes = [];
|
|
117
|
+
if (!matchesAccept(file, accept)) codes.push("file-invalid-type");
|
|
118
|
+
if (typeof minSize === "number" && file.size < minSize) codes.push("file-too-small");
|
|
119
|
+
if (typeof maxSize === "number" && file.size > maxSize) codes.push("file-too-large");
|
|
120
|
+
if (projectedCount >= (multiple ? maxFiles ?? Infinity : 1)) codes.push("too-many-files");
|
|
121
|
+
const custom = validate?.(file);
|
|
122
|
+
if (custom) {
|
|
123
|
+
for (const code of custom) if (!codes.includes(code)) codes.push(code);
|
|
124
|
+
}
|
|
125
|
+
return codes;
|
|
126
|
+
};
|
|
127
|
+
const processIncoming = (incoming) => {
|
|
128
|
+
const kept = [...multiple ? files : []];
|
|
129
|
+
const accepted = [];
|
|
130
|
+
const rejections = [];
|
|
131
|
+
for (const file of incoming) {
|
|
132
|
+
if (kept.some((existing) => fileKey(existing) === fileKey(file))) continue;
|
|
133
|
+
const codes = getRejectionCodes(file, kept.length);
|
|
134
|
+
if (codes.length > 0) rejections.push({
|
|
135
|
+
file,
|
|
136
|
+
errors: codes
|
|
137
|
+
});
|
|
138
|
+
else {
|
|
139
|
+
kept.push(file);
|
|
140
|
+
accepted.push(file);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const next = multiple ? kept : accepted.length > 0 ? [accepted[0]] : files;
|
|
144
|
+
if (accepted.length > 0) onAccept?.(accepted);
|
|
145
|
+
if (rejections.length > 0) onReject?.(rejections);
|
|
146
|
+
if (next.length !== files.length || next.some((file, index) => file !== files[index])) commit(next);
|
|
147
|
+
syncInputFiles(next);
|
|
148
|
+
};
|
|
149
|
+
const openPicker = () => {
|
|
150
|
+
if (resolvedDisabled) return;
|
|
151
|
+
inputRef.current?.click();
|
|
152
|
+
};
|
|
153
|
+
const removeFile = (file) => {
|
|
154
|
+
commit(files.filter((existing) => fileKey(existing) !== fileKey(file)));
|
|
155
|
+
};
|
|
156
|
+
const clear = () => commit([]);
|
|
157
|
+
const dropHasFiles = (event) => Array.from(event.dataTransfer?.types ?? []).includes("Files");
|
|
158
|
+
const onDragEnter = (event) => {
|
|
159
|
+
if (resolvedDisabled || !allowDrop || !dropHasFiles(event)) return;
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
dragDepth.current += 1;
|
|
162
|
+
setIsDragging(true);
|
|
163
|
+
};
|
|
164
|
+
const onDragOver = (event) => {
|
|
165
|
+
if (resolvedDisabled || !allowDrop || !dropHasFiles(event)) return;
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
event.dataTransfer.dropEffect = "copy";
|
|
168
|
+
};
|
|
169
|
+
const onDragLeave = (event) => {
|
|
170
|
+
if (resolvedDisabled || !allowDrop) return;
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
173
|
+
if (dragDepth.current === 0) setIsDragging(false);
|
|
174
|
+
};
|
|
175
|
+
const onDrop = (event) => {
|
|
176
|
+
if (resolvedDisabled || !allowDrop) return;
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
dragDepth.current = 0;
|
|
179
|
+
setIsDragging(false);
|
|
180
|
+
const dropped = Array.from(event.dataTransfer?.files ?? []);
|
|
181
|
+
if (dropped.length > 0) processIncoming(dropped);
|
|
182
|
+
};
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (!preventDocumentDrop || !allowDrop || typeof document === "undefined") return;
|
|
185
|
+
const isOutside = (event) => {
|
|
186
|
+
const root = rootRef.current;
|
|
187
|
+
return !root || !(event.target instanceof Node) || !root.contains(event.target);
|
|
188
|
+
};
|
|
189
|
+
const prevent = (event) => {
|
|
190
|
+
if (isOutside(event)) event.preventDefault();
|
|
191
|
+
};
|
|
192
|
+
document.addEventListener("dragover", prevent);
|
|
193
|
+
document.addEventListener("drop", prevent);
|
|
194
|
+
return () => {
|
|
195
|
+
document.removeEventListener("dragover", prevent);
|
|
196
|
+
document.removeEventListener("drop", prevent);
|
|
197
|
+
};
|
|
198
|
+
}, [preventDocumentDrop, allowDrop]);
|
|
199
|
+
const contextValue = {
|
|
200
|
+
files,
|
|
201
|
+
disabled: resolvedDisabled,
|
|
202
|
+
invalid: resolvedInvalid,
|
|
203
|
+
multiple,
|
|
204
|
+
isDragging,
|
|
205
|
+
inputId,
|
|
206
|
+
openPicker,
|
|
207
|
+
removeFile,
|
|
208
|
+
clear,
|
|
209
|
+
dropzoneListeners: {
|
|
210
|
+
onDragEnter,
|
|
211
|
+
onDragOver,
|
|
212
|
+
onDragLeave,
|
|
213
|
+
onDrop
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const state = {
|
|
217
|
+
dragging: isDragging,
|
|
218
|
+
disabled: resolvedDisabled,
|
|
219
|
+
invalid: resolvedInvalid,
|
|
220
|
+
empty: files.length === 0
|
|
221
|
+
};
|
|
222
|
+
return useRender({
|
|
223
|
+
defaultTagName: "div",
|
|
224
|
+
render,
|
|
225
|
+
ref: [ref, rootRef],
|
|
226
|
+
state,
|
|
227
|
+
stateAttributesMapping,
|
|
228
|
+
props: {
|
|
229
|
+
className: cn(rootClasses, className),
|
|
230
|
+
children: /* @__PURE__ */ jsxs(FileUploadContext.Provider, {
|
|
231
|
+
value: contextValue,
|
|
232
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
233
|
+
ref: inputRef,
|
|
234
|
+
id: inputId,
|
|
235
|
+
type: "file",
|
|
236
|
+
accept,
|
|
237
|
+
multiple,
|
|
238
|
+
capture,
|
|
239
|
+
disabled: resolvedDisabled || void 0,
|
|
240
|
+
required: resolvedRequired || void 0,
|
|
241
|
+
name,
|
|
242
|
+
"aria-label": inputLabel ?? (multiple ? "Upload files" : "Upload file"),
|
|
243
|
+
"aria-invalid": resolvedInvalid || void 0,
|
|
244
|
+
tabIndex: -1,
|
|
245
|
+
className: "sr-only",
|
|
246
|
+
onChange: (event) => {
|
|
247
|
+
const list = event.target.files;
|
|
248
|
+
if (list && list.length > 0) processIncoming(Array.from(list));
|
|
249
|
+
},
|
|
250
|
+
...directory ? { webkitdirectory: "" } : null
|
|
251
|
+
}), children]
|
|
252
|
+
}),
|
|
253
|
+
...rest
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
FileUpload.displayName = "FileUpload";
|
|
258
|
+
const dropzoneClasses = "relative flex w-full flex-col items-center justify-center rounded-md border-2 border-dashed border-border bg-transparent cursor-pointer transition-colors duration-150 hover:border-slate-300 hover:bg-surface has-[:focus-visible]:border-primary has-[:focus-visible]:bg-primary-50 data-dragging:border-primary data-dragging:bg-primary-50 data-invalid:border-destructive data-disabled:cursor-default data-disabled:opacity-50 data-disabled:hover:border-border data-disabled:hover:bg-transparent";
|
|
259
|
+
/**
|
|
260
|
+
* Class list for an element styled as the dashed drop area — for building a
|
|
261
|
+
* bespoke dropzone while keeping the brand's border, hover, and drag/invalid
|
|
262
|
+
* looks. The drag state hooks are `data-dragging` / `data-invalid` /
|
|
263
|
+
* `data-disabled`.
|
|
264
|
+
*/
|
|
265
|
+
function fileUploadDropzoneVariants({ className } = {}) {
|
|
266
|
+
return cn(dropzoneClasses, className);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The dashed drop area. Clicking anywhere in it opens the picker (a pointer
|
|
270
|
+
* convenience); the `FileUploadTrigger` inside is the focusable control for
|
|
271
|
+
* keyboard and assistive tech. With no `children` it renders a `SystemState`
|
|
272
|
+
* (`upload`, or `upload-error` when invalid) with a browse button; pass
|
|
273
|
+
* `children` to supply your own prompt. Highlights via `data-dragging` while a
|
|
274
|
+
* file is dragged over it.
|
|
275
|
+
*/
|
|
276
|
+
const FileUploadDropzone = forwardRef(({ children, className, onClick, ...rest }, ref) => {
|
|
277
|
+
const context = useFileUploadContext("FileUploadDropzone");
|
|
278
|
+
return /* @__PURE__ */ jsx("div", {
|
|
279
|
+
ref,
|
|
280
|
+
"data-dragging": context.isDragging ? "" : void 0,
|
|
281
|
+
"data-invalid": context.invalid ? "" : void 0,
|
|
282
|
+
"data-disabled": context.disabled ? "" : void 0,
|
|
283
|
+
className: fileUploadDropzoneVariants({ className }),
|
|
284
|
+
onClick: (event) => {
|
|
285
|
+
onClick?.(event);
|
|
286
|
+
if (!event.defaultPrevented) context.openPicker();
|
|
287
|
+
},
|
|
288
|
+
...context.dropzoneListeners,
|
|
289
|
+
...rest,
|
|
290
|
+
children: children ?? /* @__PURE__ */ jsx(SystemState, {
|
|
291
|
+
variant: context.invalid ? "upload-error" : "upload",
|
|
292
|
+
bg: "transparent",
|
|
293
|
+
action: /* @__PURE__ */ jsx(FileUploadTrigger, {})
|
|
294
|
+
})
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
FileUploadDropzone.displayName = "FileUploadDropzone";
|
|
298
|
+
/**
|
|
299
|
+
* Button that opens the file picker. Rendered inside `FileUploadDropzone` by
|
|
300
|
+
* default; use it on its own for a compact "attach a file" control with no drop
|
|
301
|
+
* area. Defaults to the `outline` variant and an upload-icon + "Browse files"
|
|
302
|
+
* label. A custom label opts into an icon via `startIcon`; pass
|
|
303
|
+
* `startIcon={null}` to remove the default one. Inherits the upload's
|
|
304
|
+
* `disabled` state.
|
|
305
|
+
*/
|
|
306
|
+
const FileUploadTrigger = forwardRef(({ variant = "outline", disabled, onClick, startIcon, children, ...rest }, ref) => {
|
|
307
|
+
const context = useFileUploadContext("FileUploadTrigger");
|
|
308
|
+
const resolvedStartIcon = startIcon !== void 0 ? startIcon : children == null ? /* @__PURE__ */ jsx(UploadIcon, {}) : void 0;
|
|
309
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
310
|
+
ref,
|
|
311
|
+
type: "button",
|
|
312
|
+
variant,
|
|
313
|
+
disabled: disabled ?? context.disabled,
|
|
314
|
+
startIcon: resolvedStartIcon,
|
|
315
|
+
onClick: (event) => {
|
|
316
|
+
event.stopPropagation();
|
|
317
|
+
onClick?.(event);
|
|
318
|
+
if (!event.defaultPrevented) context.openPicker();
|
|
319
|
+
},
|
|
320
|
+
...rest,
|
|
321
|
+
children: children ?? "Browse files"
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
FileUploadTrigger.displayName = "FileUploadTrigger";
|
|
325
|
+
const listClasses = "flex w-full flex-col gap-2";
|
|
326
|
+
/**
|
|
327
|
+
* Container for the selected files. `role="list"` is set explicitly so the
|
|
328
|
+
* list semantics survive the `flex` reset in Safari/VoiceOver. Renders nothing
|
|
329
|
+
* while empty in the default (auto or render-prop) mode.
|
|
330
|
+
*/
|
|
331
|
+
const FileUploadList = forwardRef(({ children, className, ...rest }, ref) => {
|
|
332
|
+
const context = useFileUploadContext("FileUploadList");
|
|
333
|
+
const isRenderProp = typeof children === "function";
|
|
334
|
+
if ((children == null || isRenderProp) && context.files.length === 0) return null;
|
|
335
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
336
|
+
ref,
|
|
337
|
+
role: "list",
|
|
338
|
+
className: cn(listClasses, className),
|
|
339
|
+
...rest,
|
|
340
|
+
children: isRenderProp ? context.files.map((file, index) => /* @__PURE__ */ jsx(Fragment, { children: children(file, index) }, `${fileKey(file)}-${index}`)) : children ?? context.files.map((file, index) => /* @__PURE__ */ jsx(FileUploadItem, { file }, `${fileKey(file)}-${index}`))
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
FileUploadList.displayName = "FileUploadList";
|
|
344
|
+
const itemClasses = "flex items-center gap-3 rounded-md border border-border bg-background px-3 py-2.5 data-[status=error]:border-destructive";
|
|
345
|
+
const itemNameClasses = "truncate font-sans text-sm font-medium text-foreground";
|
|
346
|
+
const itemMetaClasses = "font-sans text-xs text-muted-foreground";
|
|
347
|
+
const itemErrorClasses = "font-sans text-xs font-medium text-error-700";
|
|
348
|
+
/**
|
|
349
|
+
* One selected file: a thumbnail (auto-generated for images) or file icon, the
|
|
350
|
+
* name, and — depending on `status` — its size, an upload progress bar, or an
|
|
351
|
+
* error message, plus a remove button. `status`/`progress`/`error` are
|
|
352
|
+
* consumer-driven so the row reflects real upload state.
|
|
353
|
+
*/
|
|
354
|
+
const FileUploadItem = forwardRef(({ file, status = "idle", progress, error, removable = true, onRemove, removeLabel, preview, children, className, ...rest }, ref) => {
|
|
355
|
+
const context = useFileUploadContext("FileUploadItem");
|
|
356
|
+
const handleRemove = onRemove ?? (() => context.removeFile(file));
|
|
357
|
+
return /* @__PURE__ */ jsx("li", {
|
|
358
|
+
ref,
|
|
359
|
+
"data-status": status,
|
|
360
|
+
className: cn(itemClasses, className),
|
|
361
|
+
...rest,
|
|
362
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
363
|
+
/* @__PURE__ */ jsx("span", {
|
|
364
|
+
className: "shrink-0",
|
|
365
|
+
children: preview ?? /* @__PURE__ */ jsx(FileUploadThumbnail, { file })
|
|
366
|
+
}),
|
|
367
|
+
/* @__PURE__ */ jsxs("div", {
|
|
368
|
+
className: "flex min-w-0 flex-1 flex-col gap-1",
|
|
369
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
370
|
+
className: "flex items-center gap-2",
|
|
371
|
+
children: [
|
|
372
|
+
/* @__PURE__ */ jsx("span", {
|
|
373
|
+
className: itemNameClasses,
|
|
374
|
+
children: file.name
|
|
375
|
+
}),
|
|
376
|
+
status === "success" && /* @__PURE__ */ jsx(CheckCircleIcon, {
|
|
377
|
+
className: "ms-auto size-4 shrink-0 text-success-700",
|
|
378
|
+
"aria-hidden": "true"
|
|
379
|
+
}),
|
|
380
|
+
status === "error" && /* @__PURE__ */ jsx(AlertCircleIcon, {
|
|
381
|
+
className: "ms-auto size-4 shrink-0 text-error-700",
|
|
382
|
+
"aria-hidden": "true"
|
|
383
|
+
})
|
|
384
|
+
]
|
|
385
|
+
}), status === "uploading" ? /* @__PURE__ */ jsx(Progress, {
|
|
386
|
+
size: "sm",
|
|
387
|
+
value: progress ?? null,
|
|
388
|
+
"aria-label": `Uploading ${file.name}`
|
|
389
|
+
}) : status === "error" ? /* @__PURE__ */ jsx("span", {
|
|
390
|
+
className: itemErrorClasses,
|
|
391
|
+
children: error ?? "Upload failed"
|
|
392
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
393
|
+
className: itemMetaClasses,
|
|
394
|
+
children: formatFileSize(file.size)
|
|
395
|
+
})]
|
|
396
|
+
}),
|
|
397
|
+
removable && /* @__PURE__ */ jsx(Button, {
|
|
398
|
+
iconOnly: true,
|
|
399
|
+
size: "sm",
|
|
400
|
+
variant: "ghost",
|
|
401
|
+
type: "button",
|
|
402
|
+
"aria-label": removeLabel ?? `Remove ${file.name}`,
|
|
403
|
+
disabled: context.disabled,
|
|
404
|
+
onClick: handleRemove,
|
|
405
|
+
className: "ms-auto shrink-0 self-start",
|
|
406
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
407
|
+
})
|
|
408
|
+
] })
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
FileUploadItem.displayName = "FileUploadItem";
|
|
412
|
+
function FileUploadThumbnail({ file }) {
|
|
413
|
+
const [url, setUrl] = useState(null);
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
if (typeof URL === "undefined" || !file.type.startsWith("image/")) {
|
|
416
|
+
setUrl(null);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const objectUrl = URL.createObjectURL(file);
|
|
420
|
+
setUrl(objectUrl);
|
|
421
|
+
return () => URL.revokeObjectURL(objectUrl);
|
|
422
|
+
}, [file]);
|
|
423
|
+
if (url != null) return /* @__PURE__ */ jsx("img", {
|
|
424
|
+
src: url,
|
|
425
|
+
alt: "",
|
|
426
|
+
"aria-hidden": "true",
|
|
427
|
+
className: "size-10 rounded-md object-cover",
|
|
428
|
+
draggable: false
|
|
429
|
+
});
|
|
430
|
+
return /* @__PURE__ */ jsx("span", {
|
|
431
|
+
className: "flex size-10 items-center justify-center rounded-md bg-muted text-muted-foreground",
|
|
432
|
+
children: /* @__PURE__ */ jsx(FileIcon, {
|
|
433
|
+
className: "size-5",
|
|
434
|
+
"aria-hidden": "true"
|
|
435
|
+
})
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function UploadIcon(props) {
|
|
439
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
440
|
+
viewBox: "0 0 24 24",
|
|
441
|
+
fill: "none",
|
|
442
|
+
stroke: "currentColor",
|
|
443
|
+
strokeWidth: 2,
|
|
444
|
+
strokeLinecap: "round",
|
|
445
|
+
strokeLinejoin: "round",
|
|
446
|
+
...props,
|
|
447
|
+
children: [
|
|
448
|
+
/* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
449
|
+
/* @__PURE__ */ jsx("path", { d: "m17 8-5-5-5 5" }),
|
|
450
|
+
/* @__PURE__ */ jsx("path", { d: "M12 3v12" })
|
|
451
|
+
]
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function FileIcon(props) {
|
|
455
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
456
|
+
viewBox: "0 0 24 24",
|
|
457
|
+
fill: "none",
|
|
458
|
+
stroke: "currentColor",
|
|
459
|
+
strokeWidth: 2,
|
|
460
|
+
strokeLinecap: "round",
|
|
461
|
+
strokeLinejoin: "round",
|
|
462
|
+
...props,
|
|
463
|
+
children: [/* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" }), /* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })]
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function XIcon(props) {
|
|
467
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
468
|
+
viewBox: "0 0 24 24",
|
|
469
|
+
fill: "none",
|
|
470
|
+
stroke: "currentColor",
|
|
471
|
+
strokeWidth: 2,
|
|
472
|
+
strokeLinecap: "round",
|
|
473
|
+
strokeLinejoin: "round",
|
|
474
|
+
...props,
|
|
475
|
+
children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function CheckCircleIcon(props) {
|
|
479
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
480
|
+
viewBox: "0 0 24 24",
|
|
481
|
+
fill: "none",
|
|
482
|
+
stroke: "currentColor",
|
|
483
|
+
strokeWidth: 2,
|
|
484
|
+
strokeLinecap: "round",
|
|
485
|
+
strokeLinejoin: "round",
|
|
486
|
+
...props,
|
|
487
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
488
|
+
cx: "12",
|
|
489
|
+
cy: "12",
|
|
490
|
+
r: "10"
|
|
491
|
+
}), /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })]
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
function AlertCircleIcon(props) {
|
|
495
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
496
|
+
viewBox: "0 0 24 24",
|
|
497
|
+
fill: "none",
|
|
498
|
+
stroke: "currentColor",
|
|
499
|
+
strokeWidth: 2,
|
|
500
|
+
strokeLinecap: "round",
|
|
501
|
+
strokeLinejoin: "round",
|
|
502
|
+
...props,
|
|
503
|
+
children: [
|
|
504
|
+
/* @__PURE__ */ jsx("circle", {
|
|
505
|
+
cx: "12",
|
|
506
|
+
cy: "12",
|
|
507
|
+
r: "10"
|
|
508
|
+
}),
|
|
509
|
+
/* @__PURE__ */ jsx("path", { d: "M12 8v4" }),
|
|
510
|
+
/* @__PURE__ */ jsx("path", { d: "M12 16h.01" })
|
|
511
|
+
]
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
//#endregion
|
|
515
|
+
export { FileUpload, FileUploadDropzone, FileUploadItem, FileUploadList, FileUploadTrigger, fileUploadDropzoneVariants, formatFileSize, useFileUpload };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FileUpload, FileUploadDropzone, FileUploadDropzoneProps, FileUploadDropzoneVariantsOptions, FileUploadHandle, FileUploadItem, FileUploadItemProps, FileUploadItemStatus, FileUploadList, FileUploadListProps, FileUploadProps, FileUploadRejection, FileUploadRejectionCode, FileUploadRenderState, FileUploadTrigger, FileUploadTriggerProps, fileUploadDropzoneVariants, formatFileSize, useFileUpload } from "./FileUpload.js";
|
|
2
|
+
export { FileUpload, FileUploadDropzone, type FileUploadDropzoneProps, type FileUploadDropzoneVariantsOptions, type FileUploadHandle, FileUploadItem, type FileUploadItemProps, type FileUploadItemStatus, FileUploadList, type FileUploadListProps, type FileUploadProps, type FileUploadRejection, type FileUploadRejectionCode, type FileUploadRenderState, FileUploadTrigger, type FileUploadTriggerProps, fileUploadDropzoneVariants, formatFileSize, useFileUpload };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FileUpload, FileUploadDropzone, FileUploadItem, FileUploadList, FileUploadTrigger, fileUploadDropzoneVariants, formatFileSize, useFileUpload } from "./FileUpload.js";
|
|
2
|
+
export { FileUpload, FileUploadDropzone, FileUploadItem, FileUploadList, FileUploadTrigger, fileUploadDropzoneVariants, formatFileSize, useFileUpload };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { createContext, forwardRef, useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
5
5
|
//#region src/components/Stepper/Stepper.tsx
|
|
6
6
|
function CheckIcon(props) {
|
|
@@ -272,7 +272,7 @@ const StepperIndicator = forwardRef(({ children, render, className, ...rest }, r
|
|
|
272
272
|
props: {
|
|
273
273
|
...stepStateAttributes(item, orientation),
|
|
274
274
|
...rest,
|
|
275
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [children ?? glyph, statusLabel != null && /* @__PURE__ */ jsx("span", {
|
|
275
|
+
children: /* @__PURE__ */ jsxs(Fragment$1, { children: [children ?? glyph, statusLabel != null && /* @__PURE__ */ jsx("span", {
|
|
276
276
|
className: "sr-only",
|
|
277
277
|
children: statusLabel
|
|
278
278
|
})] }),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { forwardRef, useId } from "react";
|
|
3
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import { Switch } from "@base-ui/react/switch";
|
|
5
5
|
//#region src/components/Switch/Switch.tsx
|
|
6
6
|
const trackClasses = "inline-flex shrink-0 cursor-pointer select-none items-center rounded-full border border-border bg-muted p-px transition-colors duration-150 hover:border-slate-300 hover:bg-slate-200 data-checked:border-primary data-checked:bg-primary hover:data-checked:border-primary-700 hover:data-checked:bg-primary-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-invalid:border-destructive data-invalid:border-destructive aria-invalid:focus-visible:outline-destructive data-invalid:focus-visible:outline-destructive data-readonly:cursor-default disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50";
|
|
@@ -74,7 +74,7 @@ const Switch$1 = forwardRef(({ size = "md", label, description, labelPosition =
|
|
|
74
74
|
className: cn("inline-flex max-w-full flex-col gap-1 font-sans", "has-data-disabled:pointer-events-none has-data-disabled:opacity-50"),
|
|
75
75
|
children: [hasLabel ? /* @__PURE__ */ jsx("label", {
|
|
76
76
|
className: cn("inline-flex cursor-pointer items-start gap-2", "has-data-readonly:cursor-default", labelPosition === "start" && "justify-between"),
|
|
77
|
-
children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment, { children: [labelText, control] }) : /* @__PURE__ */ jsxs(Fragment, { children: [control, labelText] })
|
|
77
|
+
children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [labelText, control] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [control, labelText] })
|
|
78
78
|
}) : control, description != null && /* @__PURE__ */ jsx("span", {
|
|
79
79
|
id: descriptionId,
|
|
80
80
|
className: cn("text-muted-foreground", descriptionTextClasses[size], labelPosition === "end" && descriptionIndentClasses[size]),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
2
|
import { useRender } from "@base-ui/react/use-render";
|
|
3
3
|
//#region src/components/SystemState/SystemState.d.ts
|
|
4
|
-
type SystemStateVariant = "first-use" | "empty" | "no-results" | "completed" | "setup-required" | "sign-in-required" | "access-denied" | "feature-locked" | "unsupported" | "not-found" | "resource-unavailable" | "conflict" | "offline" | "network-error" | "timeout" | "rate-limited" | "maintenance" | "service-unavailable" | "server-error";
|
|
4
|
+
type SystemStateVariant = "first-use" | "empty" | "no-results" | "completed" | "upload" | "upload-error" | "setup-required" | "sign-in-required" | "access-denied" | "feature-locked" | "unsupported" | "not-found" | "resource-unavailable" | "conflict" | "offline" | "network-error" | "timeout" | "rate-limited" | "maintenance" | "service-unavailable" | "server-error";
|
|
5
5
|
type SystemStateScope = "inline" | "full";
|
|
6
6
|
type SystemStateBackground = "transparent" | "background" | "surface" | "muted";
|
|
7
7
|
type SystemStateTitleLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
@@ -62,8 +62,9 @@ interface SystemStateProps extends Omit<ComponentPropsWithoutRef<"div">, "classN
|
|
|
62
62
|
className?: string;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* Communicates a non-ideal or milestone state — empty, error, restricted,
|
|
66
|
-
* completed — with a brand illustration, copy, and
|
|
65
|
+
* Communicates a non-ideal or milestone state — empty, error, restricted,
|
|
66
|
+
* completed, or a file-upload prompt — with a brand illustration, copy, and
|
|
67
|
+
* optional recovery actions.
|
|
67
68
|
*/
|
|
68
69
|
declare const SystemState: import("react").ForwardRefExoticComponent<SystemStateProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
69
70
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import { useRender } from "@base-ui/react/use-render";
|
|
5
5
|
import accessDeniedImage from "../../assets/system-states/access-denied.png";
|
|
6
6
|
import completedImage from "../../assets/system-states/completed.png";
|
|
@@ -32,6 +32,16 @@ const variantDefaults = {
|
|
|
32
32
|
title: "All done",
|
|
33
33
|
description: "Everything here is taken care of. Nice work."
|
|
34
34
|
},
|
|
35
|
+
upload: {
|
|
36
|
+
image: firstUseImage,
|
|
37
|
+
title: "Drag and drop files here",
|
|
38
|
+
description: "or browse to choose files from your device."
|
|
39
|
+
},
|
|
40
|
+
"upload-error": {
|
|
41
|
+
image: setupRequiredImage,
|
|
42
|
+
title: "Upload failed",
|
|
43
|
+
description: "Something went wrong while uploading. Check your files and try again."
|
|
44
|
+
},
|
|
35
45
|
"setup-required": {
|
|
36
46
|
image: setupRequiredImage,
|
|
37
47
|
title: "Finish setting up",
|
|
@@ -132,8 +142,9 @@ const descriptionClasses = {
|
|
|
132
142
|
full: "font-sans text-base text-muted-foreground"
|
|
133
143
|
};
|
|
134
144
|
/**
|
|
135
|
-
* Communicates a non-ideal or milestone state — empty, error, restricted,
|
|
136
|
-
* completed — with a brand illustration, copy, and
|
|
145
|
+
* Communicates a non-ideal or milestone state — empty, error, restricted,
|
|
146
|
+
* completed, or a file-upload prompt — with a brand illustration, copy, and
|
|
147
|
+
* optional recovery actions.
|
|
137
148
|
*/
|
|
138
149
|
const SystemState = forwardRef(({ variant, title, description, action, secondaryAction, scope = "inline", bg = "transparent", titleLevel, render, className, ...rest }, ref) => {
|
|
139
150
|
const defaults = variantDefaults[variant];
|
|
@@ -153,7 +164,7 @@ const SystemState = forwardRef(({ variant, title, description, action, secondary
|
|
|
153
164
|
},
|
|
154
165
|
props: {
|
|
155
166
|
className: cn(rootClasses, scopeClasses[scope], backgroundClasses[bg], className),
|
|
156
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
167
|
+
children: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
157
168
|
/* @__PURE__ */ jsx("img", {
|
|
158
169
|
src: defaults.image,
|
|
159
170
|
alt: "",
|