@roy-ui/ui 0.0.14 → 0.0.16

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.
@@ -1,364 +0,0 @@
1
- "use client";
2
- import { TextMorph } from './chunk-MO7UPMW7.js';
3
- import { Button } from './chunk-B6LXWGX5.js';
4
- import { forwardRef, useRef, useState, useMemo, useEffect } from 'react';
5
- import './UploadFiles-VBIEAV7L.css';
6
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
-
8
- var DEFAULT_STATUS_WORDS = [
9
- "Uploading\u2026",
10
- "Transferring\u2026",
11
- "Beaming bytes\u2026",
12
- "Encrypting\u2026",
13
- "Securing\u2026",
14
- "Packing bits\u2026",
15
- "Syncing\u2026",
16
- "Crunching data\u2026",
17
- "Pushing pixels\u2026",
18
- "Almost there\u2026",
19
- "Hang tight\u2026",
20
- "Wrangling chunks\u2026",
21
- "Verifying\u2026",
22
- "Finalizing\u2026",
23
- "Just a sec\u2026"
24
- ];
25
- var EXT_META = {
26
- pdf: { label: "PDF", color: "#f0524b" },
27
- doc: { label: "DOC", color: "#3b82f6" },
28
- docx: { label: "DOCX", color: "#3b82f6" },
29
- fig: { label: "FIG", color: "#a259ff" },
30
- png: { label: "PNG", color: "#10b981" },
31
- jpg: { label: "JPG", color: "#10b981" },
32
- jpeg: { label: "JPG", color: "#10b981" },
33
- gif: { label: "GIF", color: "#10b981" },
34
- svg: { label: "SVG", color: "#f59e0b" },
35
- zip: { label: "ZIP", color: "#f59e0b" },
36
- mp4: { label: "MP4", color: "#ec4899" },
37
- csv: { label: "CSV", color: "#22c55e" },
38
- json: { label: "JSON", color: "#eab308" },
39
- default: { label: "FILE", color: "#8b8b94" }
40
- };
41
- function extMeta(name) {
42
- const ext = name.split(".").pop()?.toLowerCase() ?? "";
43
- return EXT_META[ext] ?? EXT_META.default;
44
- }
45
- function trimNum(n) {
46
- return (Math.round(n * 10) / 10).toString();
47
- }
48
- function formatBytes(bytes) {
49
- if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
50
- if (bytes < 1024) return `${Math.round(bytes)} B`;
51
- const kb = bytes / 1024;
52
- if (kb < 1024) return `${trimNum(kb)} KB`;
53
- const mb = kb / 1024;
54
- if (mb < 1024) return `${trimNum(mb)} MB`;
55
- return `${trimNum(mb / 1024)} GB`;
56
- }
57
- function shuffle(input) {
58
- const a = input.slice();
59
- for (let i = a.length - 1; i > 0; i--) {
60
- const j = Math.floor(Math.random() * (i + 1));
61
- [a[i], a[j]] = [a[j], a[i]];
62
- }
63
- return a;
64
- }
65
- var cn = (...classes) => classes.filter(Boolean).join(" ");
66
- var UploadGlyph = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "22", height: "22", fill: "none", "aria-hidden": "true", children: [
67
- /* @__PURE__ */ jsx(
68
- "path",
69
- {
70
- d: "M12 15V4m0 0L7.5 8.5M12 4l4.5 4.5",
71
- stroke: "currentColor",
72
- strokeWidth: "1.6",
73
- strokeLinecap: "round",
74
- strokeLinejoin: "round"
75
- }
76
- ),
77
- /* @__PURE__ */ jsx(
78
- "path",
79
- {
80
- d: "M4 14v3.5A2.5 2.5 0 0 0 6.5 20h11a2.5 2.5 0 0 0 2.5-2.5V14",
81
- stroke: "currentColor",
82
- strokeWidth: "1.6",
83
- strokeLinecap: "round"
84
- }
85
- )
86
- ] });
87
- var CloseGlyph = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
88
- "path",
89
- {
90
- d: "M6 6l12 12M18 6L6 18",
91
- stroke: "currentColor",
92
- strokeWidth: "1.6",
93
- strokeLinecap: "round"
94
- }
95
- ) });
96
- var TrashGlyph = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "17", height: "17", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
97
- "path",
98
- {
99
- d: "M4 7h16M9 7V5.5A1.5 1.5 0 0 1 10.5 4h3A1.5 1.5 0 0 1 15 5.5V7m2 0v11.5A1.5 1.5 0 0 1 15.5 20h-7A1.5 1.5 0 0 1 7 18.5V7m3 3.5v6m4-6v6",
100
- stroke: "currentColor",
101
- strokeWidth: "1.5",
102
- strokeLinecap: "round",
103
- strokeLinejoin: "round"
104
- }
105
- ) });
106
- var CheckGlyph = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
107
- "path",
108
- {
109
- d: "M4 12.5l5 5 11-12",
110
- stroke: "currentColor",
111
- strokeWidth: "2.2",
112
- strokeLinecap: "round",
113
- strokeLinejoin: "round"
114
- }
115
- ) });
116
- var KebabGlyph = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: [
117
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "5", r: "1.7" }),
118
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1.7" }),
119
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "19", r: "1.7" })
120
- ] });
121
- function FileRow({
122
- file,
123
- word,
124
- onRemove
125
- }) {
126
- const meta = extMeta(file.name);
127
- const pct = file.progress != null ? Math.max(0, Math.min(100, Math.round(file.progress))) : file.size > 0 ? Math.max(0, Math.min(100, Math.round((file.uploaded ?? 0) / file.size * 100))) : 0;
128
- const isUploading = file.status === "uploading";
129
- const isError = file.status === "error";
130
- return /* @__PURE__ */ jsxs("div", { className: "royui-upload__row", "data-status": file.status, children: [
131
- /* @__PURE__ */ jsx(
132
- "span",
133
- {
134
- className: "royui-upload__badge",
135
- style: { ["--royui-upload-badge"]: file.icon ? "transparent" : meta.color },
136
- "aria-hidden": "true",
137
- children: file.icon ?? /* @__PURE__ */ jsx("span", { className: "royui-upload__badge-label", children: meta.label })
138
- }
139
- ),
140
- /* @__PURE__ */ jsxs("div", { className: "royui-upload__row-main", children: [
141
- /* @__PURE__ */ jsxs("div", { className: "royui-upload__row-top", children: [
142
- /* @__PURE__ */ jsx("span", { className: "royui-upload__name", title: file.name, children: file.name }),
143
- /* @__PURE__ */ jsx(
144
- "button",
145
- {
146
- type: "button",
147
- className: "royui-upload__row-action",
148
- "aria-label": isUploading ? `Cancel ${file.name}` : `Remove ${file.name}`,
149
- onClick: () => onRemove?.(file.id),
150
- children: isUploading ? /* @__PURE__ */ jsx(CloseGlyph, {}) : /* @__PURE__ */ jsx(TrashGlyph, {})
151
- }
152
- )
153
- ] }),
154
- /* @__PURE__ */ jsxs("div", { className: "royui-upload__meta", children: [
155
- file.status === "complete" && /* @__PURE__ */ jsxs(Fragment, { children: [
156
- /* @__PURE__ */ jsx("span", { children: formatBytes(file.size) }),
157
- /* @__PURE__ */ jsx("span", { className: "royui-upload__meta-sep", children: "/" }),
158
- /* @__PURE__ */ jsxs("span", { className: "royui-upload__complete", children: [
159
- /* @__PURE__ */ jsx(CheckGlyph, {}),
160
- "Complete"
161
- ] })
162
- ] }),
163
- isUploading && /* @__PURE__ */ jsxs(Fragment, { children: [
164
- /* @__PURE__ */ jsxs("span", { children: [
165
- /* @__PURE__ */ jsx(TextMorph, { value: formatBytes(file.uploaded ?? 0) }),
166
- " of",
167
- " ",
168
- formatBytes(file.size)
169
- ] }),
170
- /* @__PURE__ */ jsx("span", { className: "royui-upload__meta-sep", children: "/" }),
171
- /* @__PURE__ */ jsx(
172
- TextMorph,
173
- {
174
- className: "royui-upload__status",
175
- value: word,
176
- renderText: (t) => /* @__PURE__ */ jsx("span", { className: "royui-upload__shimmer", children: t })
177
- }
178
- )
179
- ] }),
180
- isError && /* @__PURE__ */ jsxs(Fragment, { children: [
181
- /* @__PURE__ */ jsx("span", { children: formatBytes(file.size) }),
182
- /* @__PURE__ */ jsx("span", { className: "royui-upload__meta-sep", children: "/" }),
183
- /* @__PURE__ */ jsx("span", { className: "royui-upload__error", children: "Failed" })
184
- ] })
185
- ] }),
186
- isUploading && /* @__PURE__ */ jsxs("div", { className: "royui-upload__progress", children: [
187
- /* @__PURE__ */ jsx(
188
- "div",
189
- {
190
- className: "royui-upload__bar",
191
- role: "progressbar",
192
- "aria-valuenow": pct,
193
- "aria-valuemin": 0,
194
- "aria-valuemax": 100,
195
- children: /* @__PURE__ */ jsx("div", { className: "royui-upload__bar-fill", style: { width: `${pct}%` } })
196
- }
197
- ),
198
- /* @__PURE__ */ jsxs("span", { className: "royui-upload__pct", children: [
199
- pct,
200
- "%"
201
- ] })
202
- ] })
203
- ] })
204
- ] });
205
- }
206
- var UploadFiles = forwardRef(
207
- function UploadFiles2({
208
- files,
209
- title = "Upload files",
210
- maxSizeLabel = "MAX FILE SIZE: 20 MB",
211
- accept,
212
- multiple = true,
213
- theme = "dark",
214
- statusWords = DEFAULT_STATUS_WORDS,
215
- actionLabel,
216
- onFilesSelected,
217
- onRemove,
218
- onRemoveAll,
219
- onClose,
220
- onAction,
221
- className = "",
222
- ...rest
223
- }, ref) {
224
- const inputRef = useRef(null);
225
- const [isDragging, setDragging] = useState(false);
226
- const [tick, setTick] = useState(0);
227
- const anyUploading = files.some((f) => f.status === "uploading");
228
- const words = useMemo(
229
- () => statusWords.length ? shuffle(statusWords) : DEFAULT_STATUS_WORDS,
230
- // eslint-disable-next-line react-hooks/exhaustive-deps
231
- []
232
- );
233
- useEffect(() => {
234
- if (!anyUploading) return;
235
- const id = setInterval(() => setTick((t) => t + 1), 1900);
236
- return () => clearInterval(id);
237
- }, [anyUploading]);
238
- const emit = (list) => {
239
- if (!list || list.length === 0) return;
240
- onFilesSelected?.(Array.from(list));
241
- };
242
- const classes = cn(
243
- "royui-upload",
244
- theme === "dark" && "royui-upload--dark",
245
- theme === "light" && "royui-upload--light",
246
- theme === "auto" && "royui-upload--auto",
247
- className
248
- );
249
- return /* @__PURE__ */ jsxs("div", { ref, className: classes, ...rest, children: [
250
- /* @__PURE__ */ jsxs("header", { className: "royui-upload__header", children: [
251
- /* @__PURE__ */ jsx("h2", { className: "royui-upload__title", children: title }),
252
- onClose && /* @__PURE__ */ jsx(
253
- "button",
254
- {
255
- type: "button",
256
- className: "royui-upload__close",
257
- "aria-label": "Close",
258
- onClick: onClose,
259
- children: /* @__PURE__ */ jsx(CloseGlyph, {})
260
- }
261
- )
262
- ] }),
263
- /* @__PURE__ */ jsxs(
264
- "div",
265
- {
266
- className: cn("royui-upload__dropzone", isDragging && "is-dragging"),
267
- onDragOver: (e) => {
268
- e.preventDefault();
269
- setDragging(true);
270
- },
271
- onDragLeave: (e) => {
272
- e.preventDefault();
273
- setDragging(false);
274
- },
275
- onDrop: (e) => {
276
- e.preventDefault();
277
- setDragging(false);
278
- emit(e.dataTransfer.files);
279
- },
280
- children: [
281
- /* @__PURE__ */ jsx("span", { className: "royui-upload__drop-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
282
- /* @__PURE__ */ jsxs("p", { className: "royui-upload__drop-text", children: [
283
- "Drag and drop or",
284
- " ",
285
- /* @__PURE__ */ jsx(
286
- "button",
287
- {
288
- type: "button",
289
- className: "royui-upload__browse",
290
- onClick: () => inputRef.current?.click(),
291
- children: "browse files"
292
- }
293
- )
294
- ] }),
295
- /* @__PURE__ */ jsx("p", { className: "royui-upload__drop-hint", children: maxSizeLabel }),
296
- /* @__PURE__ */ jsx(
297
- "input",
298
- {
299
- ref: inputRef,
300
- type: "file",
301
- accept,
302
- multiple,
303
- className: "royui-upload__input",
304
- onChange: (e) => {
305
- emit(e.target.files);
306
- e.target.value = "";
307
- }
308
- }
309
- )
310
- ]
311
- }
312
- ),
313
- files.length > 0 && /* @__PURE__ */ jsx("div", { className: "royui-upload__list", children: files.map((file, i) => /* @__PURE__ */ jsx(
314
- FileRow,
315
- {
316
- file,
317
- word: words[(tick + i) % words.length],
318
- onRemove
319
- },
320
- file.id
321
- )) }),
322
- files.length > 0 && /* @__PURE__ */ jsxs("footer", { className: "royui-upload__footer", children: [
323
- /* @__PURE__ */ jsx(
324
- "button",
325
- {
326
- type: "button",
327
- className: "royui-upload__icon-btn",
328
- "aria-label": "More options",
329
- children: /* @__PURE__ */ jsx(KebabGlyph, {})
330
- }
331
- ),
332
- /* @__PURE__ */ jsxs(
333
- "button",
334
- {
335
- type: "button",
336
- className: "royui-upload__remove-all",
337
- onClick: onRemoveAll,
338
- children: [
339
- /* @__PURE__ */ jsx(TrashGlyph, {}),
340
- "Remove all"
341
- ]
342
- }
343
- ),
344
- /* @__PURE__ */ jsx("span", { className: "royui-upload__footer-spacer" }),
345
- /* @__PURE__ */ jsx(
346
- Button,
347
- {
348
- size: "sm",
349
- variant: "secondary",
350
- className: "royui-upload__action",
351
- disabled: anyUploading,
352
- onClick: onAction,
353
- children: actionLabel ?? (anyUploading ? /* @__PURE__ */ jsx(TextMorph, { value: "Uploading\u2026" }) : "Done")
354
- }
355
- )
356
- ] })
357
- ] });
358
- }
359
- );
360
- UploadFiles.displayName = "UploadFiles";
361
-
362
- export { UploadFiles };
363
- //# sourceMappingURL=chunk-6JYEHTQR.js.map
364
- //# sourceMappingURL=chunk-6JYEHTQR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/upload-files/UploadFiles.tsx"],"names":["UploadFiles"],"mappings":"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","file":"chunk-6JYEHTQR.js","sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { TextMorph } from '../text-morph/TextMorph';\nimport { Button } from '../button/Button';\nimport './UploadFiles.css';\n\nexport type UploadStatus = 'uploading' | 'complete' | 'error';\n\nexport interface UploadFile {\n /** Stable id — used as the React key and passed back to onRemove. */\n id: string;\n /** File name including extension; drives the type badge. */\n name: string;\n /** Total size in bytes. */\n size: number;\n /** Bytes transferred so far. Drives the \"X OF Y\" label and, when\n * `progress` is omitted, the percentage. */\n uploaded?: number;\n /** Progress 0–100. Falls back to uploaded / size when omitted. */\n progress?: number;\n /** Lifecycle state. */\n status: UploadStatus;\n /** Optional override for the file-type badge. */\n icon?: ReactNode;\n}\n\nexport type UploadTheme = 'auto' | 'light' | 'dark';\n\nexport interface UploadFilesProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** The files to render. The component is controlled — it draws this\n * list and emits events; the consumer owns the upload + progress. */\n files: UploadFile[];\n /** Panel heading. Defaults to \"Upload files\". */\n title?: ReactNode;\n /** Caption under the dropzone. Defaults to \"MAX FILE SIZE: 20 MB\". */\n maxSizeLabel?: string;\n /** Forwarded to the hidden file input's `accept`. */\n accept?: string;\n /** Allow selecting multiple files. Defaults to true. */\n multiple?: boolean;\n /** Color scheme. Dark by default; \"auto\" follows the OS. */\n theme?: UploadTheme;\n /** Words cycled (and morphed) in the per-file status while uploading.\n * Defaults to a long, varied upload vocabulary so the wait stays lively. */\n statusWords?: string[];\n /** Footer action button content. Defaults to a state-derived label\n * (\"Uploading…\" while in flight, \"Done\" otherwise). */\n actionLabel?: ReactNode;\n /** Fired with the dropped / browsed File objects. */\n onFilesSelected?: (files: File[]) => void;\n /** Fired when a single row's trash / cancel control is pressed. */\n onRemove?: (id: string) => void;\n /** Fired by the \"Remove all\" footer control. */\n onRemoveAll?: () => void;\n /** When provided, renders the header close (×) button. */\n onClose?: () => void;\n /** Fired by the footer action button. */\n onAction?: () => void;\n}\n\nconst DEFAULT_STATUS_WORDS = [\n 'Uploading…',\n 'Transferring…',\n 'Beaming bytes…',\n 'Encrypting…',\n 'Securing…',\n 'Packing bits…',\n 'Syncing…',\n 'Crunching data…',\n 'Pushing pixels…',\n 'Almost there…',\n 'Hang tight…',\n 'Wrangling chunks…',\n 'Verifying…',\n 'Finalizing…',\n 'Just a sec…',\n];\n\nconst EXT_META: Record<string, { label: string; color: string }> = {\n pdf: { label: 'PDF', color: '#f0524b' },\n doc: { label: 'DOC', color: '#3b82f6' },\n docx: { label: 'DOCX', color: '#3b82f6' },\n fig: { label: 'FIG', color: '#a259ff' },\n png: { label: 'PNG', color: '#10b981' },\n jpg: { label: 'JPG', color: '#10b981' },\n jpeg: { label: 'JPG', color: '#10b981' },\n gif: { label: 'GIF', color: '#10b981' },\n svg: { label: 'SVG', color: '#f59e0b' },\n zip: { label: 'ZIP', color: '#f59e0b' },\n mp4: { label: 'MP4', color: '#ec4899' },\n csv: { label: 'CSV', color: '#22c55e' },\n json: { label: 'JSON', color: '#eab308' },\n default: { label: 'FILE', color: '#8b8b94' },\n};\n\nfunction extMeta(name: string) {\n const ext = name.split('.').pop()?.toLowerCase() ?? '';\n return EXT_META[ext] ?? EXT_META.default!;\n}\n\nfunction trimNum(n: number): string {\n return (Math.round(n * 10) / 10).toString();\n}\n\n/** Human-readable byte size, e.g. 2.7 MB / 14 MB. */\nfunction formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '0 B';\n if (bytes < 1024) return `${Math.round(bytes)} B`;\n const kb = bytes / 1024;\n if (kb < 1024) return `${trimNum(kb)} KB`;\n const mb = kb / 1024;\n if (mb < 1024) return `${trimNum(mb)} MB`;\n return `${trimNum(mb / 1024)} GB`;\n}\n\n/** Deterministic-ish shuffle (Fisher–Yates) so the word order varies per\n * session without two adjacent entries being the same word. */\nfunction shuffle<T>(input: T[]): T[] {\n const a = input.slice();\n for (let i = a.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [a[i], a[j]] = [a[j]!, a[i]!];\n }\n return a;\n}\n\nconst cn = (...classes: (string | false | undefined)[]) =>\n classes.filter(Boolean).join(' ');\n\n/* ── Icons ──────────────────────────────────────────────────────────────── */\n\nconst UploadGlyph = () => (\n <svg viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M12 15V4m0 0L7.5 8.5M12 4l4.5 4.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M4 14v3.5A2.5 2.5 0 0 0 6.5 20h11a2.5 2.5 0 0 0 2.5-2.5V14\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n\nconst CloseGlyph = () => (\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M6 6l12 12M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n\nconst TrashGlyph = () => (\n <svg viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M4 7h16M9 7V5.5A1.5 1.5 0 0 1 10.5 4h3A1.5 1.5 0 0 1 15 5.5V7m2 0v11.5A1.5 1.5 0 0 1 15.5 20h-7A1.5 1.5 0 0 1 7 18.5V7m3 3.5v6m4-6v6\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nconst CheckGlyph = () => (\n <svg viewBox=\"0 0 24 24\" width=\"13\" height=\"13\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M4 12.5l5 5 11-12\"\n stroke=\"currentColor\"\n strokeWidth=\"2.2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nconst KebabGlyph = () => (\n <svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"5\" r=\"1.7\" />\n <circle cx=\"12\" cy=\"12\" r=\"1.7\" />\n <circle cx=\"12\" cy=\"19\" r=\"1.7\" />\n </svg>\n);\n\n/* ── File row ───────────────────────────────────────────────────────────── */\n\nfunction FileRow({\n file,\n word,\n onRemove,\n}: {\n file: UploadFile;\n word: string;\n onRemove?: (id: string) => void;\n}) {\n const meta = extMeta(file.name);\n const pct =\n file.progress != null\n ? Math.max(0, Math.min(100, Math.round(file.progress)))\n : file.size > 0\n ? Math.max(0, Math.min(100, Math.round(((file.uploaded ?? 0) / file.size) * 100)))\n : 0;\n\n const isUploading = file.status === 'uploading';\n const isError = file.status === 'error';\n\n return (\n <div className=\"royui-upload__row\" data-status={file.status}>\n <span\n className=\"royui-upload__badge\"\n style={{ ['--royui-upload-badge' as string]: file.icon ? 'transparent' : meta.color }}\n aria-hidden=\"true\"\n >\n {file.icon ?? <span className=\"royui-upload__badge-label\">{meta.label}</span>}\n </span>\n\n <div className=\"royui-upload__row-main\">\n <div className=\"royui-upload__row-top\">\n <span className=\"royui-upload__name\" title={file.name}>\n {file.name}\n </span>\n <button\n type=\"button\"\n className=\"royui-upload__row-action\"\n aria-label={isUploading ? `Cancel ${file.name}` : `Remove ${file.name}`}\n onClick={() => onRemove?.(file.id)}\n >\n {isUploading ? <CloseGlyph /> : <TrashGlyph />}\n </button>\n </div>\n\n <div className=\"royui-upload__meta\">\n {file.status === 'complete' && (\n <>\n <span>{formatBytes(file.size)}</span>\n <span className=\"royui-upload__meta-sep\">/</span>\n <span className=\"royui-upload__complete\">\n <CheckGlyph />\n Complete\n </span>\n </>\n )}\n {isUploading && (\n <>\n <span>\n <TextMorph value={formatBytes(file.uploaded ?? 0)} /> of{' '}\n {formatBytes(file.size)}\n </span>\n <span className=\"royui-upload__meta-sep\">/</span>\n <TextMorph\n className=\"royui-upload__status\"\n value={word}\n renderText={(t) => (\n <span className=\"royui-upload__shimmer\">{t}</span>\n )}\n />\n </>\n )}\n {isError && (\n <>\n <span>{formatBytes(file.size)}</span>\n <span className=\"royui-upload__meta-sep\">/</span>\n <span className=\"royui-upload__error\">Failed</span>\n </>\n )}\n </div>\n\n {isUploading && (\n <div className=\"royui-upload__progress\">\n <div\n className=\"royui-upload__bar\"\n role=\"progressbar\"\n aria-valuenow={pct}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n <div className=\"royui-upload__bar-fill\" style={{ width: `${pct}%` }} />\n </div>\n <span className=\"royui-upload__pct\">{pct}%</span>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/* ── Component ──────────────────────────────────────────────────────────── */\n\nexport const UploadFiles = forwardRef<HTMLDivElement, UploadFilesProps>(\n function UploadFiles(\n {\n files,\n title = 'Upload files',\n maxSizeLabel = 'MAX FILE SIZE: 20 MB',\n accept,\n multiple = true,\n theme = 'dark',\n statusWords = DEFAULT_STATUS_WORDS,\n actionLabel,\n onFilesSelected,\n onRemove,\n onRemoveAll,\n onClose,\n onAction,\n className = '',\n ...rest\n },\n ref,\n ) {\n const inputRef = useRef<HTMLInputElement>(null);\n const [isDragging, setDragging] = useState(false);\n const [tick, setTick] = useState(0);\n\n const anyUploading = files.some((f) => f.status === 'uploading');\n\n // Shuffle once per mount so the word order varies between sessions.\n const words = useMemo(\n () => (statusWords.length ? shuffle(statusWords) : DEFAULT_STATUS_WORDS),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n\n // Advance the shared cycle only while something is in flight.\n useEffect(() => {\n if (!anyUploading) return;\n const id = setInterval(() => setTick((t) => t + 1), 1900);\n return () => clearInterval(id);\n }, [anyUploading]);\n\n const emit = (list: FileList | null) => {\n if (!list || list.length === 0) return;\n onFilesSelected?.(Array.from(list));\n };\n\n const classes = cn(\n 'royui-upload',\n theme === 'dark' && 'royui-upload--dark',\n theme === 'light' && 'royui-upload--light',\n theme === 'auto' && 'royui-upload--auto',\n className,\n );\n\n return (\n <div ref={ref} className={classes} {...rest}>\n <header className=\"royui-upload__header\">\n <h2 className=\"royui-upload__title\">{title}</h2>\n {onClose && (\n <button\n type=\"button\"\n className=\"royui-upload__close\"\n aria-label=\"Close\"\n onClick={onClose}\n >\n <CloseGlyph />\n </button>\n )}\n </header>\n\n <div\n className={cn('royui-upload__dropzone', isDragging && 'is-dragging')}\n onDragOver={(e) => {\n e.preventDefault();\n setDragging(true);\n }}\n onDragLeave={(e) => {\n e.preventDefault();\n setDragging(false);\n }}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n emit(e.dataTransfer.files);\n }}\n >\n <span className=\"royui-upload__drop-icon\" aria-hidden=\"true\">\n <UploadGlyph />\n </span>\n <p className=\"royui-upload__drop-text\">\n Drag and drop or{' '}\n <button\n type=\"button\"\n className=\"royui-upload__browse\"\n onClick={() => inputRef.current?.click()}\n >\n browse files\n </button>\n </p>\n <p className=\"royui-upload__drop-hint\">{maxSizeLabel}</p>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n className=\"royui-upload__input\"\n onChange={(e) => {\n emit(e.target.files);\n e.target.value = '';\n }}\n />\n </div>\n\n {files.length > 0 && (\n <div className=\"royui-upload__list\">\n {files.map((file, i) => (\n <FileRow\n key={file.id}\n file={file}\n word={words[(tick + i) % words.length]!}\n onRemove={onRemove}\n />\n ))}\n </div>\n )}\n\n {files.length > 0 && (\n <footer className=\"royui-upload__footer\">\n <button\n type=\"button\"\n className=\"royui-upload__icon-btn\"\n aria-label=\"More options\"\n >\n <KebabGlyph />\n </button>\n <button\n type=\"button\"\n className=\"royui-upload__remove-all\"\n onClick={onRemoveAll}\n >\n <TrashGlyph />\n Remove all\n </button>\n <span className=\"royui-upload__footer-spacer\" />\n <Button\n size=\"sm\"\n variant=\"secondary\"\n className=\"royui-upload__action\"\n disabled={anyUploading}\n onClick={onAction}\n >\n {actionLabel ??\n (anyUploading ? (\n <TextMorph value=\"Uploading…\" />\n ) : (\n 'Done'\n ))}\n </Button>\n </footer>\n )}\n </div>\n );\n },\n);\n\nUploadFiles.displayName = 'UploadFiles';\n"]}