@krizaka/ui 3.7.1 → 3.9.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 +14 -12
- package/dist/action-bar.js +2 -1
- package/dist/action-bar.js.map +1 -1
- package/dist/app-shell.d.ts +81 -3
- package/dist/app-shell.js +163 -35
- package/dist/app-shell.js.map +1 -1
- package/dist/audio-player.js +1 -1
- package/dist/badge.d.ts +36 -39
- package/dist/badge.js +11 -2
- package/dist/badge.js.map +1 -1
- package/dist/blocks.css +56 -4
- package/dist/button-BmCrLCq7.d.ts +114 -0
- package/dist/button.d.ts +48 -48
- package/dist/button.js +2 -1
- package/dist/card.d.ts +111 -111
- package/dist/card.js +10 -2
- package/dist/card.js.map +1 -1
- package/dist/chip.d.ts +24 -24
- package/dist/chip.js +10 -2
- package/dist/chip.js.map +1 -1
- package/dist/{chunk-UOMP6VOZ.js → chunk-276PTQMQ.js} +10 -4
- package/dist/chunk-276PTQMQ.js.map +1 -0
- package/dist/{chunk-Z6FUPB3Q.js → chunk-2NB27LM7.js} +11 -3
- package/dist/chunk-2NB27LM7.js.map +1 -0
- package/dist/chunk-2VNYHG4E.js +9 -0
- package/dist/chunk-2VNYHG4E.js.map +1 -0
- package/dist/{chunk-ND3L5VHT.js → chunk-5Z7FWCV2.js} +2 -2
- package/dist/{chunk-YH4LMWRU.js → chunk-7UCPGXKN.js} +11 -3
- package/dist/chunk-7UCPGXKN.js.map +1 -0
- package/dist/{chunk-AZPKXP3O.js → chunk-AFY35UCB.js} +6 -1
- package/dist/{chunk-AZPKXP3O.js.map → chunk-AFY35UCB.js.map} +1 -1
- package/dist/{chunk-LCZXS6AK.js → chunk-CKSHLSDH.js} +5 -5
- package/dist/chunk-CKSHLSDH.js.map +1 -0
- package/dist/chunk-D3T54RSL.js +11 -0
- package/dist/chunk-D3T54RSL.js.map +1 -0
- package/dist/{chunk-ZKVQGXLD.js → chunk-FKBHWS4N.js} +2 -2
- package/dist/{chunk-BRKMQ6JV.js → chunk-FZBWEDQI.js} +1 -23
- package/dist/chunk-FZBWEDQI.js.map +1 -0
- package/dist/{chunk-HCNOOPZE.js → chunk-KVDB4GXV.js} +2 -2
- package/dist/{chunk-RUPNPMP3.js → chunk-OWC2LZD2.js} +11 -3
- package/dist/chunk-OWC2LZD2.js.map +1 -0
- package/dist/{chunk-FDCMBWUU.js → chunk-REPTKUJ6.js} +4 -4
- package/dist/chunk-REPTKUJ6.js.map +1 -0
- package/dist/{chunk-JZPVUJIX.js → chunk-UB2BI3AW.js} +1 -1
- package/dist/{chunk-JZPVUJIX.js.map → chunk-UB2BI3AW.js.map} +1 -1
- package/dist/code-block.d.ts +32 -10
- package/dist/code-block.js +51 -43
- package/dist/code-block.js.map +1 -1
- package/dist/combobox.d.ts +3 -3
- package/dist/combobox.js +4 -3
- package/dist/command.js +3 -2
- package/dist/command.js.map +1 -1
- package/dist/component-page.js.map +1 -1
- package/dist/confirm-button.d.ts +1 -1
- package/dist/confirm-button.js +2 -1
- package/dist/confirm-button.js.map +1 -1
- package/dist/continue-prompt.js +11 -3
- package/dist/continue-prompt.js.map +1 -1
- package/dist/data-table.js +7 -6
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.js +1 -1
- package/dist/dialog.js +3 -2
- package/dist/dialog.js.map +1 -1
- package/dist/file-upload.d.ts +12 -1
- package/dist/file-upload.js +73 -89
- package/dist/file-upload.js.map +1 -1
- package/dist/filter-bar.js +3 -2
- package/dist/{geometry-CbOU4rtL.d.ts → geometry-B2E8oFhg.d.ts} +1 -1
- package/dist/image-cropper.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +26 -24
- package/dist/index.js.map +1 -1
- package/dist/list.js +1 -1
- package/dist/map.js +1 -1
- package/dist/map.js.map +1 -1
- package/dist/markdown.d.ts +38 -0
- package/dist/markdown.js +150 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mascot-quote.d.ts +1 -1
- package/dist/mascot-quote.js.map +1 -1
- package/dist/mascot-state.d.ts +1 -1
- package/dist/mascot-state.js +1 -1
- package/dist/mascots.d.ts +2 -2
- package/dist/mascots.js +1 -1
- package/dist/match-dialog.js +2 -1
- package/dist/match-dialog.js.map +1 -1
- package/dist/media-capture.js +1 -1
- package/dist/native/map.js +1 -1
- package/dist/native/map.js.map +1 -1
- package/dist/native/markdown.d.ts +24 -0
- package/dist/native/markdown.js +200 -0
- package/dist/native/markdown.js.map +1 -0
- package/dist/native.d.ts +10 -14
- package/dist/native.js +383 -119
- package/dist/native.js.map +1 -1
- package/dist/recipes.d.ts +143 -0
- package/dist/recipes.js +180 -0
- package/dist/recipes.js.map +1 -0
- package/dist/search-field.js +2 -2
- package/dist/status-page.d.ts +1 -1
- package/dist/status-page.js.map +1 -1
- package/dist/swipe-deck.js +1 -1
- package/dist/tag-input.js +4 -3
- package/dist/tag-input.js.map +1 -1
- package/dist/tag-selector.js +2 -1
- package/dist/tag-selector.js.map +1 -1
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +2 -1
- package/dist/theme.js.map +1 -1
- package/dist/tree-view.js +1 -1
- package/dist/vertical-video.js +2 -1
- package/dist/vertical-video.js.map +1 -1
- package/dist/video-player.js +1 -1
- package/dist/video-trimmer.js +1 -1
- package/dist/virtual.js +2 -2
- package/package.json +49 -18
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/activity-feed.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/app-shell.json +135 -12
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +2 -2
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +9 -3
- package/registry/button.json +7 -2
- package/registry/card-grid.json +1 -1
- package/registry/card.json +8 -3
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/chat.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +1 -1
- package/registry/chip.json +9 -3
- package/registry/choice-grid.json +1 -1
- package/registry/code-block.json +65 -21
- package/registry/combobox.json +4 -3
- package/registry/command.json +1 -1
- package/registry/component-page.json +4 -4
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +2 -2
- package/registry/date-picker.json +3 -3
- package/registry/decision-list.json +1 -1
- package/registry/description-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/app-shell/console.d.ts +4 -0
- package/registry/examples/app-shell/console.js +93 -0
- package/registry/examples/app-shell/console.tsx +103 -0
- package/registry/examples/badge/native/dot.dark.png +0 -0
- package/registry/examples/badge/native/dot.light.png +0 -0
- package/registry/examples/badge/native/scrim.dark.png +0 -0
- package/registry/examples/badge/native/scrim.light.png +0 -0
- package/registry/examples/badge/native/tones.dark.png +0 -0
- package/registry/examples/badge/native/tones.light.png +0 -0
- package/registry/examples/card/native/default.dark.png +0 -0
- package/registry/examples/card/native/default.light.png +0 -0
- package/registry/examples/card/native/elevated.light.png +0 -0
- package/registry/examples/chip/native/multiple.dark.png +0 -0
- package/registry/examples/chip/native/multiple.light.png +0 -0
- package/registry/examples/chip/native/removable.dark.png +0 -0
- package/registry/examples/chip/native/removable.light.png +0 -0
- package/registry/examples/chip/native/single.dark.png +0 -0
- package/registry/examples/chip/native/single.light.png +0 -0
- package/registry/examples/chip/native/toggle.dark.png +0 -0
- package/registry/examples/chip/native/toggle.light.png +0 -0
- package/registry/examples/code-block/annotated.d.ts +4 -0
- package/registry/examples/code-block/annotated.js +31 -0
- package/registry/examples/code-block/annotated.tsx +26 -0
- package/registry/examples/component-page/button.tsx +1 -1
- package/registry/examples/form/native/amount.dark.png +0 -0
- package/registry/examples/form/native/amount.light.png +0 -0
- package/registry/examples/landing-page/krizaka.js +1 -1
- package/registry/examples/landing-page/krizaka.tsx +1 -1
- package/registry/examples/markdown/ai-stream.d.ts +4 -0
- package/registry/examples/markdown/ai-stream.js +31 -0
- package/registry/examples/markdown/ai-stream.tsx +32 -0
- package/registry/examples/markdown/basic.d.ts +4 -0
- package/registry/examples/markdown/basic.js +26 -0
- package/registry/examples/markdown/basic.tsx +22 -0
- package/registry/examples/markdown/native/ai-stream.d.ts +4 -0
- package/registry/examples/markdown/native/ai-stream.dark.png +0 -0
- package/registry/examples/markdown/native/ai-stream.js +17 -0
- package/registry/examples/markdown/native/ai-stream.light.png +0 -0
- package/registry/examples/markdown/native/ai-stream.tsx +17 -0
- package/registry/examples/markdown/native/basic.d.ts +4 -0
- package/registry/examples/markdown/native/basic.dark.png +0 -0
- package/registry/examples/markdown/native/basic.js +28 -0
- package/registry/examples/markdown/native/basic.light.png +0 -0
- package/registry/examples/markdown/native/basic.tsx +27 -0
- package/registry/examples/mascots/flock.tsx +1 -1
- package/registry/examples/profile-card/match.js +1 -1
- package/registry/examples/profile-card/match.tsx +1 -1
- package/registry/examples/recipes/tone-chip.d.ts +4 -0
- package/registry/examples/recipes/tone-chip.js +46 -0
- package/registry/examples/recipes/tone-chip.tsx +32 -0
- package/registry/examples/screen/native/profile.dark.png +0 -0
- package/registry/examples/screen/native/profile.light.png +0 -0
- package/registry/examples/status-page/building.js +1 -1
- package/registry/examples/status-page/building.tsx +1 -1
- package/registry/examples/tag-selector/native/interests.dark.png +0 -0
- package/registry/examples/tag-selector/native/interests.light.png +0 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +8 -3
- package/registry/filter-bar.json +1 -1
- package/registry/form.json +2 -2
- package/registry/gallery-page.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +2 -2
- package/registry/index.json +77 -17
- package/registry/iso.json +1 -1
- package/registry/kanban.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +3 -3
- package/registry/landscape.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/list.json +2 -2
- package/registry/loader.json +4 -4
- package/registry/longform-page.json +1 -1
- package/registry/map.json +3 -3
- package/registry/markdown.json +266 -0
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +1 -1
- package/registry/mascots.json +5 -5
- package/registry/match-dialog.json +1 -1
- package/registry/media-capture.json +2 -2
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +1 -1
- package/registry/number-ticker.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/photo-grid.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +3 -3
- package/registry/profile-card.json +2 -2
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +1 -1
- package/registry/recipes.json +99 -0
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +4 -3
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/settings-layout.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/skip-link.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +7 -7
- package/registry/step-progress.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/swipe-deck.json +2 -2
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +2 -2
- package/registry/tag-selector.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/tree-view.json +2 -2
- package/registry/verified-badge.json +1 -1
- package/registry/vertical-video.json +2 -2
- package/registry/video-player.json +2 -2
- package/registry/video-trimmer.json +2 -2
- package/registry/virtual.json +2 -2
- package/dist/button-BMAO7dKF.d.ts +0 -114
- package/dist/chunk-BRKMQ6JV.js.map +0 -1
- package/dist/chunk-FDCMBWUU.js.map +0 -1
- package/dist/chunk-LCZXS6AK.js.map +0 -1
- package/dist/chunk-RUPNPMP3.js.map +0 -1
- package/dist/chunk-UOMP6VOZ.js.map +0 -1
- package/dist/chunk-YH4LMWRU.js.map +0 -1
- package/dist/chunk-Z6FUPB3Q.js.map +0 -1
- /package/dist/{chunk-ND3L5VHT.js.map → chunk-5Z7FWCV2.js.map} +0 -0
- /package/dist/{chunk-ZKVQGXLD.js.map → chunk-FKBHWS4N.js.map} +0 -0
- /package/dist/{chunk-HCNOOPZE.js.map → chunk-KVDB4GXV.js.map} +0 -0
package/dist/file-upload.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useLatest
|
|
4
|
+
} from "./chunk-FZBWEDQI.js";
|
|
2
5
|
import {
|
|
3
6
|
CloseGlyph,
|
|
4
7
|
FileGlyph,
|
|
@@ -10,6 +13,7 @@ import {
|
|
|
10
13
|
|
|
11
14
|
// src/file-upload/file-upload.tsx
|
|
12
15
|
import { getNumberFormat } from "@krizaka/intl";
|
|
16
|
+
import { useQueue, useQueueSnapshot } from "@krizaka/pacer/react";
|
|
13
17
|
import * as React2 from "react";
|
|
14
18
|
import { tv as tv2 } from "tailwind-variants";
|
|
15
19
|
|
|
@@ -327,103 +331,83 @@ function UploadList({ items, onPause, onResume, onRetry, onCancel, onRemove, loc
|
|
|
327
331
|
] }, item.id);
|
|
328
332
|
}) });
|
|
329
333
|
}
|
|
330
|
-
function
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
sync();
|
|
340
|
-
};
|
|
341
|
-
const pump = () => {
|
|
342
|
-
const { upload, concurrency = 2 } = options();
|
|
343
|
-
const all = [...entries.values()];
|
|
344
|
-
let running = all.filter((e) => e.item.status === "uploading").length;
|
|
345
|
-
for (const entry of all) {
|
|
346
|
-
if (running >= concurrency) break;
|
|
347
|
-
if (entry.item.status !== "queued") continue;
|
|
348
|
-
running += 1;
|
|
349
|
-
const controller = new AbortController();
|
|
350
|
-
const resumed = entry.started;
|
|
351
|
-
entry.controller = controller;
|
|
352
|
-
entry.started = true;
|
|
353
|
-
entry.last = { at: Date.now(), sent: entry.item.progress * entry.item.size };
|
|
354
|
-
patch(entry.item.id, { status: "uploading", error: void 0 });
|
|
355
|
-
upload(entry.file, {
|
|
356
|
-
signal: controller.signal,
|
|
357
|
-
resumed,
|
|
358
|
-
onProgress: (sent) => {
|
|
359
|
-
if (controller.signal.aborted) return;
|
|
360
|
-
const now = Date.now();
|
|
361
|
-
const last = entry.last ?? { at: now, sent };
|
|
362
|
-
const fresh = now - last.at > 400;
|
|
363
|
-
const speed = fresh ? (sent - last.sent) / (now - last.at) * 1e3 : entry.item.speed;
|
|
364
|
-
if (fresh) entry.last = { at: now, sent };
|
|
365
|
-
patch(entry.item.id, { progress: entry.item.size ? Math.min(1, sent / entry.item.size) : 0, speed });
|
|
366
|
-
}
|
|
367
|
-
}).then(
|
|
368
|
-
() => {
|
|
369
|
-
if (controller.signal.aborted) return;
|
|
370
|
-
patch(entry.item.id, { status: "done", progress: 1, speed: void 0 });
|
|
371
|
-
pump();
|
|
372
|
-
},
|
|
373
|
-
(error) => {
|
|
374
|
-
if (controller.signal.aborted) return;
|
|
375
|
-
patch(entry.item.id, { status: "error", error: error instanceof Error ? error.message : String(error), speed: void 0 });
|
|
376
|
-
pump();
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
const release = (entry) => {
|
|
382
|
-
entry.controller?.abort();
|
|
383
|
-
if (entry.item.previewUrl) URL.revokeObjectURL(entry.item.previewUrl);
|
|
384
|
-
};
|
|
385
|
-
return {
|
|
386
|
-
configure(next) {
|
|
387
|
-
current = next;
|
|
388
|
-
},
|
|
389
|
-
add(files) {
|
|
390
|
-
const { previews = true } = options();
|
|
334
|
+
function useUploadQueue(options) {
|
|
335
|
+
const { concurrency, retries, backoff, shouldRetry } = options;
|
|
336
|
+
const latest = useLatest(options);
|
|
337
|
+
const queue = useQueue({ concurrency: concurrency ?? 2, retries, backoff, shouldRetry });
|
|
338
|
+
const jobs = useQueueSnapshot(queue);
|
|
339
|
+
const metas = useLatest(new Map(jobs.map((job) => [job.id, job.meta])));
|
|
340
|
+
const add = React2.useCallback(
|
|
341
|
+
(files) => {
|
|
342
|
+
const { previews = true } = latest.current;
|
|
391
343
|
for (const file of files) {
|
|
392
344
|
const id = `${file.name}-${file.size}-${Math.random().toString(36).slice(2, 8)}`;
|
|
393
345
|
const previewUrl = previews && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : void 0;
|
|
394
|
-
|
|
346
|
+
queue.add(
|
|
347
|
+
async ({ signal, resumed }) => {
|
|
348
|
+
queue.update(id, (m) => ({ ...m, last: { at: Date.now(), sent: m.progress * file.size } }));
|
|
349
|
+
try {
|
|
350
|
+
await latest.current.upload(file, {
|
|
351
|
+
signal,
|
|
352
|
+
resumed,
|
|
353
|
+
onProgress: (sent) => {
|
|
354
|
+
if (signal.aborted) return;
|
|
355
|
+
queue.update(id, (m) => {
|
|
356
|
+
const now = Date.now();
|
|
357
|
+
const last = m.last ?? { at: now, sent };
|
|
358
|
+
const fresh = now - last.at > 400;
|
|
359
|
+
return { ...m, progress: file.size ? Math.min(1, sent / file.size) : 0, speed: fresh ? (sent - last.sent) / (now - last.at) * 1e3 : m.speed, last: fresh ? { at: now, sent } : last };
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
if (!signal.aborted) queue.update(id, (m) => ({ ...m, progress: 1, speed: void 0 }));
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (!signal.aborted) queue.update(id, (m) => ({ ...m, speed: void 0 }));
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
{ id, meta: { file, progress: 0, previewUrl } }
|
|
370
|
+
);
|
|
395
371
|
}
|
|
396
|
-
sync();
|
|
397
|
-
pump();
|
|
398
372
|
},
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
373
|
+
[latest, queue]
|
|
374
|
+
);
|
|
375
|
+
const forget = React2.useCallback(
|
|
376
|
+
(id) => {
|
|
377
|
+
const url = metas.current.get(id)?.previewUrl;
|
|
378
|
+
if (url) URL.revokeObjectURL(url);
|
|
379
|
+
queue.cancel(id);
|
|
403
380
|
},
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
381
|
+
[metas, queue]
|
|
382
|
+
);
|
|
383
|
+
React2.useEffect(
|
|
384
|
+
() => () => {
|
|
385
|
+
for (const meta of metas.current.values()) if (meta.previewUrl) URL.revokeObjectURL(meta.previewUrl);
|
|
407
386
|
},
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
pump();
|
|
387
|
+
[metas]
|
|
388
|
+
);
|
|
389
|
+
const pause = React2.useCallback(
|
|
390
|
+
(id) => {
|
|
391
|
+
queue.pause(id);
|
|
392
|
+
queue.update(id, (m) => ({ ...m, speed: void 0 }));
|
|
415
393
|
},
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
394
|
+
[queue]
|
|
395
|
+
);
|
|
396
|
+
const items = React2.useMemo(
|
|
397
|
+
() => jobs.map(({ id, status, error, meta }) => ({
|
|
398
|
+
id,
|
|
399
|
+
name: meta.file.name,
|
|
400
|
+
size: meta.file.size,
|
|
401
|
+
type: meta.file.type,
|
|
402
|
+
progress: meta.progress,
|
|
403
|
+
status: status === "running" ? "uploading" : status,
|
|
404
|
+
error: error?.message,
|
|
405
|
+
previewUrl: meta.previewUrl,
|
|
406
|
+
speed: status === "running" ? meta.speed : void 0
|
|
407
|
+
})),
|
|
408
|
+
[jobs]
|
|
409
|
+
);
|
|
410
|
+
return { items, add, pause, resume: queue.resume, retry: queue.retry, cancel: forget, remove: forget };
|
|
427
411
|
}
|
|
428
412
|
export {
|
|
429
413
|
Dropzone,
|
package/dist/file-upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/file-upload/file-upload.tsx","../src/progress/progress.tsx"],"sourcesContent":["// Client. Three parts that work alone or together:\n// - `Dropzone`: drop files on it or pick them with its button (a real `<input type=\"file\">`, so the keyboard and the\n// phone's camera / files sheet work); it checks the type, the size and the count, and hands over the accepted files\n// and the refused ones with their reason;\n// - `UploadList`: each file with its preview, size, progress, speed, and the actions its state allows (pause, resume,\n// retry, cancel, remove); the progress is a `progressbar`, a failure is announced;\n// - `useUploadQueue`: runs the app's `upload(file, { signal, onProgress, resumed })` with a concurrency, pauses by\n// aborting the signal and resumes by calling it again with `resumed: true` — what a Tus client (tus-js-client's\n// `abort()` / `start()`) or a multipart upload needs, without depending on either.\nimport { getNumberFormat } from \"@krizaka/intl\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CloseGlyph, FileGlyph, PauseGlyph, PlayGlyph, RetryGlyph, UploadGlyph } from \"../glyphs\";\nimport { Progress } from \"../progress\";\n\nexport const fileUpload = tv({\n slots: {\n zone:\n \"relative flex w-full flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed border-border-default bg-surface-1 px-6 py-10 text-center \" +\n \"transition-colors data-[dragging]:border-accent data-[dragging]:bg-accent-soft data-[disabled]:opacity-50 has-[:focus-visible]:border-accent\",\n zoneIcon: \"flex h-12 w-12 items-center justify-center rounded-full bg-surface-3 text-fg-secondary\",\n zoneTitle: \"text-sm font-semibold text-fg\",\n zoneHint: \"text-xs text-fg-secondary\",\n browse:\n \"inline-flex h-9 items-center justify-center rounded-lg bg-accent px-4 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-hover \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 disabled:pointer-events-none\",\n refusals: \"w-full text-left text-xs font-medium text-fg-danger\",\n list: \"flex w-full flex-col gap-2\",\n item: \"flex items-center gap-3 rounded-xl border border-border-default bg-surface-1 p-3 data-[status=error]:border-danger/60\",\n preview: \"flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-surface-3 text-fg-secondary\",\n body: \"flex min-w-0 flex-1 flex-col gap-1.5\",\n name: \"truncate text-sm font-semibold text-fg\",\n meta: \"flex flex-wrap gap-x-2 text-xs tabular-nums text-fg-secondary\",\n error: \"text-xs font-medium text-fg-danger\",\n actions: \"flex shrink-0 items-center gap-1\",\n action:\n \"inline-flex h-8 w-8 items-center justify-center rounded-md text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n});\n\n/** Why a file was refused. */\nexport type FileRejection = { file: File; reason: \"type\" | \"size\" | \"count\" };\n\n/** Whether a file matches an `accept` list (`\"video/*,.mov,image/png\"`). */\nexport function acceptsFile(file: File, accept?: string): boolean {\n if (!accept) return true;\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return accept\n .split(\",\")\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean)\n .some((rule) => (rule.startsWith(\".\") ? name.endsWith(rule) : rule.endsWith(\"/*\") ? type.startsWith(rule.slice(0, -1)) : type === rule));\n}\n\n/** A size in the locale's words (`\"1.4 GB\"`, `\"820 kB\"`), through `@krizaka/intl`. */\nexport function formatBytes(bytes: number, locale = \"en\"): string {\n const units = [\"byte\", \"kilobyte\", \"megabyte\", \"gigabyte\", \"terabyte\"] as const;\n let value = Math.max(0, bytes);\n let unit = 0;\n while (value >= 1000 && unit < units.length - 1) {\n value /= 1000;\n unit += 1;\n }\n return getNumberFormat(locale, { style: \"unit\", unit: units[unit], unitDisplay: \"short\", maximumFractionDigits: value < 10 && unit > 0 ? 1 : 0 }).format(value);\n}\n\nexport type DropzoneProps = {\n /** The title in the zone (\"Drop your video here\"), passed translated. */\n title: string;\n /** The button that opens the file picker (\"Choose a file\"), passed translated. */\n browseLabel: string;\n /** What is accepted, in words (\"MP4 or MOV, up to 4 GB\"). */\n hint?: string;\n /** The types accepted, as the `accept` attribute (`\"video/*\"`, `\"image/png,.pdf\"`). */\n accept?: string;\n /** The largest file, in bytes. */\n maxSize?: number;\n /** The most files at once (1 unless `multiple`). */\n maxFiles?: number;\n /** Several files at once. */\n multiple?: boolean;\n /** Not available (an upload is running). */\n disabled?: boolean;\n /** On a phone, opens the camera directly (`\"environment\"` the back one, `\"user\"` the front one). */\n capture?: \"user\" | \"environment\";\n /** Called with the files accepted (never empty). */\n onFiles: (files: File[]) => void;\n /** Called with the files refused and why (show `refusalLabel`'s words, or your own). */\n onReject?: (rejections: FileRejection[]) => void;\n /** The words of a refusal, shown in the zone: `(r) => r.file.name + \" is too large\"`. Without it, nothing is shown. */\n refusalLabel?: (rejection: FileRejection) => string;\n /** An icon instead of the upload glyph. */\n icon?: React.ReactNode;\n /** Classes merged on the zone. */\n className?: string;\n};\n\n/** A zone to drop files on, with a button that opens the picker: checks type, size and count. */\nexport function Dropzone({\n title,\n browseLabel,\n hint,\n accept,\n maxSize,\n maxFiles,\n multiple,\n disabled,\n capture,\n onFiles,\n onReject,\n refusalLabel,\n icon,\n className,\n}: DropzoneProps) {\n const s = fileUpload();\n const input = React.useRef<HTMLInputElement>(null);\n const [dragging, setDragging] = React.useState(false);\n const [refused, setRefused] = React.useState<FileRejection[]>([]);\n const depth = React.useRef(0);\n const id = React.useId();\n const limit = maxFiles ?? (multiple ? Infinity : 1);\n\n const take = (list: FileList | File[]) => {\n const files = [...list];\n const accepted: File[] = [];\n const rejections: FileRejection[] = [];\n for (const file of files) {\n if (!acceptsFile(file, accept)) rejections.push({ file, reason: \"type\" });\n else if (maxSize !== undefined && file.size > maxSize) rejections.push({ file, reason: \"size\" });\n else if (accepted.length >= limit) rejections.push({ file, reason: \"count\" });\n else accepted.push(file);\n }\n setRefused(rejections);\n if (accepted.length) onFiles(accepted);\n if (rejections.length) onReject?.(rejections);\n };\n\n return (\n <div\n className={s.zone({ className })}\n data-dragging={dragging ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n onDragEnter={(event) => {\n if (disabled) return;\n event.preventDefault();\n depth.current += 1;\n setDragging(true);\n }}\n onDragOver={(event) => {\n if (disabled) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = \"copy\";\n }}\n onDragLeave={() => {\n depth.current = Math.max(0, depth.current - 1);\n if (depth.current === 0) setDragging(false);\n }}\n onDrop={(event) => {\n if (disabled) return;\n event.preventDefault();\n depth.current = 0;\n setDragging(false);\n take(event.dataTransfer.files);\n }}\n >\n <span className={s.zoneIcon()} aria-hidden>\n {icon ?? <UploadGlyph className=\"h-6 w-6\" />}\n </span>\n <p id={`${id}-title`} className={s.zoneTitle()}>\n {title}\n </p>\n {hint ? (\n <p id={`${id}-hint`} className={s.zoneHint()}>\n {hint}\n </p>\n ) : null}\n <button\n type=\"button\"\n className={s.browse()}\n disabled={disabled}\n aria-describedby={[`${id}-title`, hint ? `${id}-hint` : null].filter(Boolean).join(\" \")}\n onClick={() => input.current?.click()}\n >\n {browseLabel}\n </button>\n <input\n ref={input}\n type=\"file\"\n tabIndex={-1}\n aria-hidden\n className=\"sr-only\"\n accept={accept}\n multiple={multiple}\n capture={capture}\n disabled={disabled}\n onChange={(event) => {\n if (event.target.files?.length) take(event.target.files);\n event.target.value = \"\";\n }}\n />\n {refusalLabel && refused.length > 0 ? (\n <ul role=\"alert\" className={s.refusals()}>\n {refused.map((r) => (\n <li key={`${r.file.name}-${r.reason}`}>{refusalLabel(r)}</li>\n ))}\n </ul>\n ) : null}\n </div>\n );\n}\n\nexport type UploadStatus = \"queued\" | \"uploading\" | \"paused\" | \"done\" | \"error\";\n\nexport type UploadItem = {\n /** A stable id. */\n id: string;\n /** The file's name. */\n name: string;\n /** Its size, in bytes. */\n size: number;\n /** Its MIME type (`video/mp4`): picks the preview. */\n type?: string;\n /** Sent so far, between 0 and 1. */\n progress: number;\n /** Where it stands. */\n status: UploadStatus;\n /** Why it failed, passed translated. */\n error?: string;\n /** A preview (an object URL of the file, a thumbnail from the server). */\n previewUrl?: string;\n /** Bytes per second, for the speed and the time left. */\n speed?: number;\n};\n\nexport type UploadListLabels = {\n /** The list's accessible name (\"Uploads\"). */\n list: string;\n pause: (name: string) => string;\n resume: (name: string) => string;\n retry: (name: string) => string;\n cancel: (name: string) => string;\n remove: (name: string) => string;\n /** A state in words: `(status) => \"Paused\"`… */\n status: (status: UploadStatus) => string;\n /** The time left: `(seconds) => \"2 min left\"`. */\n remaining: (seconds: number) => string;\n};\n\nexport const uploadListLabels: UploadListLabels = {\n list: \"Uploads\",\n pause: (name) => `Pause ${name}`,\n resume: (name) => `Resume ${name}`,\n retry: (name) => `Retry ${name}`,\n cancel: (name) => `Cancel ${name}`,\n remove: (name) => `Remove ${name}`,\n status: (status) => ({ queued: \"Waiting\", uploading: \"Uploading\", paused: \"Paused\", done: \"Uploaded\", error: \"Failed\" })[status],\n remaining: (seconds) => (seconds < 60 ? `${Math.ceil(seconds)} s left` : `${Math.ceil(seconds / 60)} min left`),\n};\n\nexport type UploadListProps = {\n /** The uploads, as `useUploadQueue` gives them (or your own state). */\n items: readonly UploadItem[];\n /** Pauses an upload (shown while it uploads, when given). */\n onPause?: (id: string) => void;\n /** Resumes a paused one. */\n onResume?: (id: string) => void;\n /** Tries a failed one again. */\n onRetry?: (id: string) => void;\n /** Stops an upload under way and drops it. */\n onCancel?: (id: string) => void;\n /** Drops a finished or failed one from the list. */\n onRemove?: (id: string) => void;\n /** The locale of sizes and speeds. Default `\"en\"`. */\n locale?: string;\n /** The words, passed translated; English by default. */\n labels?: Partial<UploadListLabels>;\n /** Classes merged on the list. */\n className?: string;\n};\n\nfunction Preview({ item }: { item: UploadItem }) {\n if (item.previewUrl && item.type?.startsWith(\"image/\")) return <img src={item.previewUrl} alt=\"\" className=\"h-full w-full object-cover\" />;\n if (item.previewUrl && item.type?.startsWith(\"video/\")) return <video src={item.previewUrl} muted playsInline preload=\"metadata\" className=\"h-full w-full object-cover\" aria-hidden />;\n return <FileGlyph className=\"h-5 w-5\" />;\n}\n\n/** The files being sent: preview, size, progress, speed and time left, and the actions each state allows. */\nexport function UploadList({ items, onPause, onResume, onRetry, onCancel, onRemove, locale = \"en\", labels: given, className }: UploadListProps) {\n const labels = { ...uploadListLabels, ...given };\n const s = fileUpload();\n const action = (label: string, run: () => void, glyph: React.ReactNode) => (\n <button type=\"button\" className={s.action()} aria-label={label} title={label} onClick={run}>\n {glyph}\n </button>\n );\n return (\n <ul aria-label={labels.list} className={s.list({ className })}>\n {items.map((item) => {\n const left = item.status === \"uploading\" && item.speed ? ((1 - item.progress) * item.size) / item.speed : null;\n return (\n <li key={item.id} className={s.item()} data-status={item.status}>\n <span className={s.preview()}>\n <Preview item={item} />\n </span>\n <div className={s.body()}>\n <p className={s.name()}>{item.name}</p>\n {item.status !== \"done\" && item.status !== \"error\" ? (\n <Progress size=\"sm\" label={item.name} value={item.status === \"queued\" ? null : Math.round(item.progress * 100)} />\n ) : null}\n <p className={s.meta()}>\n <span>{labels.status(item.status)}</span>\n <span>{item.status === \"done\" ? formatBytes(item.size, locale) : `${formatBytes(item.progress * item.size, locale)} / ${formatBytes(item.size, locale)}`}</span>\n {item.status === \"uploading\" && item.speed ? <span>{formatBytes(item.speed, locale)}/s</span> : null}\n {left !== null ? <span>{labels.remaining(left)}</span> : null}\n </p>\n {item.status === \"error\" && item.error ? (\n <p role=\"alert\" className={s.error()}>\n {item.error}\n </p>\n ) : null}\n </div>\n <div className={s.actions()}>\n {item.status === \"uploading\" && onPause && action(labels.pause(item.name), () => onPause(item.id), <PauseGlyph className=\"h-4 w-4\" />)}\n {item.status === \"paused\" && onResume && action(labels.resume(item.name), () => onResume(item.id), <PlayGlyph className=\"h-4 w-4\" />)}\n {item.status === \"error\" && onRetry && action(labels.retry(item.name), () => onRetry(item.id), <RetryGlyph className=\"h-4 w-4\" />)}\n {(item.status === \"done\" || item.status === \"error\") && onRemove\n ? action(labels.remove(item.name), () => onRemove(item.id), <CloseGlyph className=\"h-4 w-4\" />)\n : item.status !== \"done\" && onCancel\n ? action(labels.cancel(item.name), () => onCancel(item.id), <CloseGlyph className=\"h-4 w-4\" />)\n : null}\n </div>\n </li>\n );\n })}\n </ul>\n );\n}\n\nexport type UploadContext = {\n /** Aborted on pause and cancel: stop sending (`tusUpload.abort()`, `xhr.abort()`). */\n signal: AbortSignal;\n /** Report the bytes sent so far. */\n onProgress: (sent: number) => void;\n /** `true` when this call resumes a paused or failed upload (a Tus client then continues from its offset). */\n resumed: boolean;\n};\n\nexport type UploadQueueOptions = {\n /** Sends one file; resolves when it is done, rejects on failure (its `message` becomes the item's error). */\n upload: (file: File, context: UploadContext) => Promise<void>;\n /** How many files go at once. Default 2. */\n concurrency?: number;\n /** Makes an object URL for each file to preview (revoked when it leaves). Default `true`. */\n previews?: boolean;\n};\n\ntype Entry = { item: UploadItem; file: File; controller: AbortController | null; started: boolean; last?: { at: number; sent: number } };\n\n/** The queue itself, outside React: entries, the pump that starts the next uploads, and the actions. */\nfunction createQueue(initial: UploadQueueOptions, emit: (items: UploadItem[]) => void) {\n let current = initial;\n const options = () => current;\n const entries = new Map<string, Entry>();\n const sync = () => emit([...entries.values()].map((e) => e.item));\n const patch = (id: string, next: Partial<UploadItem>) => {\n const entry = entries.get(id);\n if (!entry) return;\n entry.item = { ...entry.item, ...next };\n sync();\n };\n const pump = () => {\n const { upload, concurrency = 2 } = options();\n const all = [...entries.values()];\n let running = all.filter((e) => e.item.status === \"uploading\").length;\n for (const entry of all) {\n if (running >= concurrency) break;\n if (entry.item.status !== \"queued\") continue;\n running += 1;\n const controller = new AbortController();\n const resumed = entry.started;\n entry.controller = controller;\n entry.started = true;\n entry.last = { at: Date.now(), sent: entry.item.progress * entry.item.size };\n patch(entry.item.id, { status: \"uploading\", error: undefined });\n upload(entry.file, {\n signal: controller.signal,\n resumed,\n onProgress: (sent) => {\n if (controller.signal.aborted) return;\n const now = Date.now();\n const last = entry.last ?? { at: now, sent };\n const fresh = now - last.at > 400;\n const speed = fresh ? ((sent - last.sent) / (now - last.at)) * 1000 : entry.item.speed;\n if (fresh) entry.last = { at: now, sent };\n patch(entry.item.id, { progress: entry.item.size ? Math.min(1, sent / entry.item.size) : 0, speed });\n },\n }).then(\n () => {\n if (controller.signal.aborted) return;\n patch(entry.item.id, { status: \"done\", progress: 1, speed: undefined });\n pump();\n },\n (error: unknown) => {\n if (controller.signal.aborted) return;\n patch(entry.item.id, { status: \"error\", error: error instanceof Error ? error.message : String(error), speed: undefined });\n pump();\n },\n );\n }\n };\n const release = (entry: Entry) => {\n entry.controller?.abort();\n if (entry.item.previewUrl) URL.revokeObjectURL(entry.item.previewUrl);\n };\n return {\n configure(next: UploadQueueOptions) {\n current = next;\n },\n add(files: readonly File[]) {\n const { previews = true } = options();\n for (const file of files) {\n const id = `${file.name}-${file.size}-${Math.random().toString(36).slice(2, 8)}`;\n const previewUrl = previews && typeof URL.createObjectURL === \"function\" ? URL.createObjectURL(file) : undefined;\n entries.set(id, { file, controller: null, started: false, item: { id, name: file.name, size: file.size, type: file.type, progress: 0, status: \"queued\", previewUrl } });\n }\n sync();\n pump();\n },\n pause(id: string) {\n entries.get(id)?.controller?.abort();\n patch(id, { status: \"paused\", speed: undefined });\n pump();\n },\n resume(id: string) {\n patch(id, { status: \"queued\" });\n pump();\n },\n remove(id: string) {\n const entry = entries.get(id);\n if (!entry) return;\n release(entry);\n entries.delete(id);\n sync();\n pump();\n },\n dispose() {\n for (const entry of entries.values()) release(entry);\n },\n };\n}\n\n/** The state of an upload queue around the app's `upload` function: `add`, `pause`, `resume`, `retry`, `cancel`, `remove`. */\nexport function useUploadQueue(options: UploadQueueOptions) {\n const [items, setItems] = React.useState<UploadItem[]>([]);\n const [queue] = React.useState(() => createQueue(options, setItems));\n React.useLayoutEffect(() => queue.configure(options));\n React.useEffect(() => () => queue.dispose(), [queue]);\n return { items, add: queue.add, pause: queue.pause, resume: queue.resume, retry: queue.resume, cancel: queue.remove, remove: queue.remove };\n}\n","// Server-safe: no hook, no context. role=\"progressbar\" with aria-valuenow / aria-valuemax (none when indeterminate),\n// named by `label`. A bar or a ring; the ring can hold content in its centre (an amount, a percentage).\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const progress = tv({\n slots: {\n root: \"relative\",\n track: \"\",\n indicator: \"\",\n center: \"absolute inset-0 flex flex-col items-center justify-center text-center\",\n buffer: \"absolute inset-y-0 left-0 -z-10 rounded-full bg-accent/25 transition-[width] duration-500 motion-reduce:transition-none\",\n frame: \"flex w-full flex-col gap-1.5\",\n header: \"flex items-baseline gap-2 text-xs\",\n caption: \"min-w-0 flex-1 truncate font-medium text-fg\",\n figure: \"shrink-0 tabular-nums text-fg-secondary\",\n },\n variants: {\n variant: {\n bar: {\n root: \"relative isolate w-full overflow-hidden rounded-full bg-surface-3\",\n indicator:\n \"kz-progress h-full rounded-full bg-linear-to-r from-accent to-accent-2 transition-[width] duration-500 motion-reduce:transition-none \" +\n \"data-[state=indeterminate]:w-2/5\",\n },\n ring: {\n root: \"inline-flex shrink-0 items-center justify-center\",\n track: \"stroke-border-default\",\n indicator:\n \"stroke-accent transition-[stroke-dashoffset] duration-1000 motion-reduce:transition-none \" +\n \"data-[state=indeterminate]:origin-center data-[state=indeterminate]:animate-spin motion-reduce:data-[state=indeterminate]:animate-none\",\n },\n },\n size: { sm: {}, md: {}, lg: {} },\n status: {\n active: {},\n // Paused: the fill rests, striped — the task is waiting for someone, not broken.\n paused: { indicator: \"kz-progress-paused opacity-70\" },\n // Failed: the fill turns to the danger colour where it stopped.\n error: { indicator: \"from-danger to-danger stroke-danger\" },\n },\n },\n compoundVariants: [\n { variant: \"bar\", size: \"sm\", class: { root: \"h-1\" } },\n { variant: \"bar\", size: \"md\", class: { root: \"h-2\" } },\n { variant: \"bar\", size: \"lg\", class: { root: \"h-3\" } },\n { variant: \"ring\", size: \"sm\", class: { root: \"h-10 w-10\" } },\n { variant: \"ring\", size: \"md\", class: { root: \"h-20 w-20\" } },\n { variant: \"ring\", size: \"lg\", class: { root: \"h-36 w-36\" } },\n ],\n defaultVariants: { variant: \"bar\", size: \"md\", status: \"active\" },\n});\n\nexport type ProgressVariants = VariantProps<typeof progress>;\n\nexport type ProgressProps = Omit<React.ComponentProps<\"div\">, \"children\"> &\n ProgressVariants & {\n /** bar · ring (a ring holds its `children` in the centre). */\n variant?: ProgressVariants[\"variant\"];\n /** sm · md · lg: the bar's thickness, the ring's diameter. */\n size?: ProgressVariants[\"size\"];\n /** How far, between 0 and `max`. `null` or absent: indeterminate (a wait of unknown length). */\n value?: number | null;\n /** The value that means complete. */\n max?: number;\n /** The accessible name — passed translated (\"Upload\", \"Raised towards the goal\"). */\n label: string;\n /** The value in words for assistive technology (\"$420 of $1,000\") — default: the percentage. */\n valueText?: string;\n /** A ring's centre: an amount, a percentage, an icon. */\n children?: React.ReactNode;\n /** How far is already available beyond the value (a video buffered ahead), between 0 and `max` — bar only. */\n buffer?: number;\n /** Splits the bar or the ring into this many segments: the steps of a task, the parts of a goal. */\n segments?: number;\n /**\n * active · paused (the fill rests, striped: waiting for someone) · error (the fill turns to the danger colour where it stopped).\n * @default \"active\"\n */\n status?: ProgressVariants[\"status\"];\n /** Shows the label above the bar, with the percentage (or `valueText`) and `eta` — bar only. */\n showLabel?: boolean;\n /** The time left, in words (“About 2 min left”), shown with `showLabel` and read with the value — passed translated. */\n eta?: string;\n };\n\nconst STROKE = { sm: 12, md: 9, lg: 7 } as const;\n\n/** A progress bar or ring: `value` / `max`, indeterminate without a value. State: `data-state` (loading · complete · indeterminate). */\nexport function Progress({\n variant = \"bar\",\n size = \"md\",\n value,\n max = 100,\n label,\n valueText,\n className,\n children,\n buffer,\n segments,\n status = \"active\",\n showLabel,\n eta,\n ...props\n}: ProgressProps) {\n const s = progress({ variant, size, status });\n const maskId = `kz-seg-${React.useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n const known = typeof value === \"number\" && Number.isFinite(value);\n const ratio = known ? Math.min(1, Math.max(0, value / (max || 1))) : 0;\n const percent = Math.round(ratio * 100);\n const state = !known ? \"indeterminate\" : ratio >= 1 ? \"complete\" : \"loading\";\n const aria = {\n role: \"progressbar\" as const,\n \"aria-label\": label,\n \"aria-valuemin\": known ? 0 : undefined,\n \"aria-valuemax\": known ? max : undefined,\n \"aria-valuenow\": known ? Math.min(max, Math.max(0, value)) : undefined,\n \"aria-valuetext\": [known ? (valueText ?? `${percent}%`) : valueText, eta].filter(Boolean).join(\", \") || undefined,\n \"data-state\": state,\n \"data-status\": status === \"active\" ? undefined : status,\n \"data-value\": known ? value : undefined,\n \"data-variant\": variant,\n };\n if (variant === \"ring\") {\n const stroke = STROKE[size];\n const r = 50 - stroke / 2;\n return (\n <div {...aria} className={s.root({ className })} {...props}>\n <svg viewBox=\"0 0 100 100\" className=\"h-full w-full -rotate-90\" aria-hidden>\n {segments && segments > 1 && (\n <mask id={maskId}>\n <circle cx=\"50\" cy=\"50\" r={r} fill=\"none\" stroke=\"white\" strokeWidth={stroke + 2} pathLength={100} strokeDasharray={`${100 / segments - 2} 2`} />\n </mask>\n )}\n <g mask={segments && segments > 1 ? `url(#${maskId})` : undefined}>\n <circle cx=\"50\" cy=\"50\" r={r} fill=\"none\" strokeWidth={stroke} className={s.track()} />\n <circle\n cx=\"50\"\n cy=\"50\"\n r={r}\n fill=\"none\"\n strokeWidth={stroke}\n strokeLinecap=\"round\"\n pathLength={100}\n strokeDasharray={100}\n strokeDashoffset={known ? 100 - percent : 75}\n data-state={state}\n className={s.indicator()}\n />\n </g>\n </svg>\n {children !== undefined && <div className={s.center()}>{children}</div>}\n </div>\n );\n }\n const segmented = segments && segments > 1 ? ({ \"--kz-segments\": segments } as React.CSSProperties) : undefined;\n const bufferRatio = typeof buffer === \"number\" && Number.isFinite(buffer) ? Math.min(1, Math.max(0, buffer / (max || 1))) : null;\n const bar = (\n <div {...aria} className={s.root({ className: [segmented && \"kz-progress-segmented\", className].filter(Boolean).join(\" \") || undefined })} style={segmented} {...props}>\n <div data-state={state} className={s.indicator()} style={known ? { width: `${ratio * 100}%` } : undefined} />\n {bufferRatio !== null && <div aria-hidden className={s.buffer()} style={{ width: `${bufferRatio * 100}%` }} />}\n </div>\n );\n if (!showLabel) return bar;\n const figure = [known ? (valueText ?? `${percent} %`) : valueText, eta].filter(Boolean).join(\" · \");\n return (\n <div className={s.frame()}>\n <div aria-hidden className={s.header()}>\n <span className={s.caption()}>{label}</span>\n {figure && <span className={s.figure()}>{figure}</span>}\n </div>\n {bar}\n </div>\n );\n}\n"],"mappings":"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","names":["React","tv","jsx","jsxs","tv"]}
|
|
1
|
+
{"version":3,"sources":["../src/file-upload/file-upload.tsx","../src/progress/progress.tsx"],"sourcesContent":["// Client. Three parts that work alone or together:\n// - `Dropzone`: drop files on it or pick them with its button (a real `<input type=\"file\">`, so the keyboard and the\n// phone's camera / files sheet work); it checks the type, the size and the count, and hands over the accepted files\n// and the refused ones with their reason;\n// - `UploadList`: each file with its preview, size, progress, speed, and the actions its state allows (pause, resume,\n// retry, cancel, remove); the progress is a `progressbar`, a failure is announced;\n// - `useUploadQueue`: runs the app's `upload(file, { signal, onProgress, resumed })` with a concurrency, pauses by\n// aborting the signal and resumes by calling it again with `resumed: true` — what a Tus client (tus-js-client's\n// `abort()` / `start()`) or a multipart upload needs, without depending on either. The line (concurrency, retries\n// with backoff, pause per file) is `createQueue` of @krizaka/pacer.\nimport { getNumberFormat } from \"@krizaka/intl\";\nimport { type QueueOptions } from \"@krizaka/pacer\";\nimport { useQueue, useQueueSnapshot } from \"@krizaka/pacer/react\";\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CloseGlyph, FileGlyph, PauseGlyph, PlayGlyph, RetryGlyph, UploadGlyph } from \"../glyphs\";\nimport { useLatest } from \"../media\";\nimport { Progress } from \"../progress\";\n\nexport const fileUpload = tv({\n slots: {\n zone:\n \"relative flex w-full flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed border-border-default bg-surface-1 px-6 py-10 text-center \" +\n \"transition-colors data-[dragging]:border-accent data-[dragging]:bg-accent-soft data-[disabled]:opacity-50 has-[:focus-visible]:border-accent\",\n zoneIcon: \"flex h-12 w-12 items-center justify-center rounded-full bg-surface-3 text-fg-secondary\",\n zoneTitle: \"text-sm font-semibold text-fg\",\n zoneHint: \"text-xs text-fg-secondary\",\n browse:\n \"inline-flex h-9 items-center justify-center rounded-lg bg-accent px-4 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-hover \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-0 disabled:pointer-events-none\",\n refusals: \"w-full text-left text-xs font-medium text-fg-danger\",\n list: \"flex w-full flex-col gap-2\",\n item: \"flex items-center gap-3 rounded-xl border border-border-default bg-surface-1 p-3 data-[status=error]:border-danger/60\",\n preview: \"flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-surface-3 text-fg-secondary\",\n body: \"flex min-w-0 flex-1 flex-col gap-1.5\",\n name: \"truncate text-sm font-semibold text-fg\",\n meta: \"flex flex-wrap gap-x-2 text-xs tabular-nums text-fg-secondary\",\n error: \"text-xs font-medium text-fg-danger\",\n actions: \"flex shrink-0 items-center gap-1\",\n action:\n \"inline-flex h-8 w-8 items-center justify-center rounded-md text-fg-secondary transition-colors hover:bg-surface-3 hover:text-fg \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n },\n});\n\n/** Why a file was refused. */\nexport type FileRejection = { file: File; reason: \"type\" | \"size\" | \"count\" };\n\n/** Whether a file matches an `accept` list (`\"video/*,.mov,image/png\"`). */\nexport function acceptsFile(file: File, accept?: string): boolean {\n if (!accept) return true;\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return accept\n .split(\",\")\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean)\n .some((rule) => (rule.startsWith(\".\") ? name.endsWith(rule) : rule.endsWith(\"/*\") ? type.startsWith(rule.slice(0, -1)) : type === rule));\n}\n\n/** A size in the locale's words (`\"1.4 GB\"`, `\"820 kB\"`), through `@krizaka/intl`. */\nexport function formatBytes(bytes: number, locale = \"en\"): string {\n const units = [\"byte\", \"kilobyte\", \"megabyte\", \"gigabyte\", \"terabyte\"] as const;\n let value = Math.max(0, bytes);\n let unit = 0;\n while (value >= 1000 && unit < units.length - 1) {\n value /= 1000;\n unit += 1;\n }\n return getNumberFormat(locale, { style: \"unit\", unit: units[unit], unitDisplay: \"short\", maximumFractionDigits: value < 10 && unit > 0 ? 1 : 0 }).format(value);\n}\n\nexport type DropzoneProps = {\n /** The title in the zone (\"Drop your video here\"), passed translated. */\n title: string;\n /** The button that opens the file picker (\"Choose a file\"), passed translated. */\n browseLabel: string;\n /** What is accepted, in words (\"MP4 or MOV, up to 4 GB\"). */\n hint?: string;\n /** The types accepted, as the `accept` attribute (`\"video/*\"`, `\"image/png,.pdf\"`). */\n accept?: string;\n /** The largest file, in bytes. */\n maxSize?: number;\n /** The most files at once (1 unless `multiple`). */\n maxFiles?: number;\n /** Several files at once. */\n multiple?: boolean;\n /** Not available (an upload is running). */\n disabled?: boolean;\n /** On a phone, opens the camera directly (`\"environment\"` the back one, `\"user\"` the front one). */\n capture?: \"user\" | \"environment\";\n /** Called with the files accepted (never empty). */\n onFiles: (files: File[]) => void;\n /** Called with the files refused and why (show `refusalLabel`'s words, or your own). */\n onReject?: (rejections: FileRejection[]) => void;\n /** The words of a refusal, shown in the zone: `(r) => r.file.name + \" is too large\"`. Without it, nothing is shown. */\n refusalLabel?: (rejection: FileRejection) => string;\n /** An icon instead of the upload glyph. */\n icon?: React.ReactNode;\n /** Classes merged on the zone. */\n className?: string;\n};\n\n/** A zone to drop files on, with a button that opens the picker: checks type, size and count. */\nexport function Dropzone({\n title,\n browseLabel,\n hint,\n accept,\n maxSize,\n maxFiles,\n multiple,\n disabled,\n capture,\n onFiles,\n onReject,\n refusalLabel,\n icon,\n className,\n}: DropzoneProps) {\n const s = fileUpload();\n const input = React.useRef<HTMLInputElement>(null);\n const [dragging, setDragging] = React.useState(false);\n const [refused, setRefused] = React.useState<FileRejection[]>([]);\n const depth = React.useRef(0);\n const id = React.useId();\n const limit = maxFiles ?? (multiple ? Infinity : 1);\n\n const take = (list: FileList | File[]) => {\n const files = [...list];\n const accepted: File[] = [];\n const rejections: FileRejection[] = [];\n for (const file of files) {\n if (!acceptsFile(file, accept)) rejections.push({ file, reason: \"type\" });\n else if (maxSize !== undefined && file.size > maxSize) rejections.push({ file, reason: \"size\" });\n else if (accepted.length >= limit) rejections.push({ file, reason: \"count\" });\n else accepted.push(file);\n }\n setRefused(rejections);\n if (accepted.length) onFiles(accepted);\n if (rejections.length) onReject?.(rejections);\n };\n\n return (\n <div\n className={s.zone({ className })}\n data-dragging={dragging ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n onDragEnter={(event) => {\n if (disabled) return;\n event.preventDefault();\n depth.current += 1;\n setDragging(true);\n }}\n onDragOver={(event) => {\n if (disabled) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = \"copy\";\n }}\n onDragLeave={() => {\n depth.current = Math.max(0, depth.current - 1);\n if (depth.current === 0) setDragging(false);\n }}\n onDrop={(event) => {\n if (disabled) return;\n event.preventDefault();\n depth.current = 0;\n setDragging(false);\n take(event.dataTransfer.files);\n }}\n >\n <span className={s.zoneIcon()} aria-hidden>\n {icon ?? <UploadGlyph className=\"h-6 w-6\" />}\n </span>\n <p id={`${id}-title`} className={s.zoneTitle()}>\n {title}\n </p>\n {hint ? (\n <p id={`${id}-hint`} className={s.zoneHint()}>\n {hint}\n </p>\n ) : null}\n <button\n type=\"button\"\n className={s.browse()}\n disabled={disabled}\n aria-describedby={[`${id}-title`, hint ? `${id}-hint` : null].filter(Boolean).join(\" \")}\n onClick={() => input.current?.click()}\n >\n {browseLabel}\n </button>\n <input\n ref={input}\n type=\"file\"\n tabIndex={-1}\n aria-hidden\n className=\"sr-only\"\n accept={accept}\n multiple={multiple}\n capture={capture}\n disabled={disabled}\n onChange={(event) => {\n if (event.target.files?.length) take(event.target.files);\n event.target.value = \"\";\n }}\n />\n {refusalLabel && refused.length > 0 ? (\n <ul role=\"alert\" className={s.refusals()}>\n {refused.map((r) => (\n <li key={`${r.file.name}-${r.reason}`}>{refusalLabel(r)}</li>\n ))}\n </ul>\n ) : null}\n </div>\n );\n}\n\nexport type UploadStatus = \"queued\" | \"uploading\" | \"paused\" | \"done\" | \"error\";\n\nexport type UploadItem = {\n /** A stable id. */\n id: string;\n /** The file's name. */\n name: string;\n /** Its size, in bytes. */\n size: number;\n /** Its MIME type (`video/mp4`): picks the preview. */\n type?: string;\n /** Sent so far, between 0 and 1. */\n progress: number;\n /** Where it stands. */\n status: UploadStatus;\n /** Why it failed, passed translated. */\n error?: string;\n /** A preview (an object URL of the file, a thumbnail from the server). */\n previewUrl?: string;\n /** Bytes per second, for the speed and the time left. */\n speed?: number;\n};\n\nexport type UploadListLabels = {\n /** The list's accessible name (\"Uploads\"). */\n list: string;\n pause: (name: string) => string;\n resume: (name: string) => string;\n retry: (name: string) => string;\n cancel: (name: string) => string;\n remove: (name: string) => string;\n /** A state in words: `(status) => \"Paused\"`… */\n status: (status: UploadStatus) => string;\n /** The time left: `(seconds) => \"2 min left\"`. */\n remaining: (seconds: number) => string;\n};\n\nexport const uploadListLabels: UploadListLabels = {\n list: \"Uploads\",\n pause: (name) => `Pause ${name}`,\n resume: (name) => `Resume ${name}`,\n retry: (name) => `Retry ${name}`,\n cancel: (name) => `Cancel ${name}`,\n remove: (name) => `Remove ${name}`,\n status: (status) => ({ queued: \"Waiting\", uploading: \"Uploading\", paused: \"Paused\", done: \"Uploaded\", error: \"Failed\" })[status],\n remaining: (seconds) => (seconds < 60 ? `${Math.ceil(seconds)} s left` : `${Math.ceil(seconds / 60)} min left`),\n};\n\nexport type UploadListProps = {\n /** The uploads, as `useUploadQueue` gives them (or your own state). */\n items: readonly UploadItem[];\n /** Pauses an upload (shown while it uploads, when given). */\n onPause?: (id: string) => void;\n /** Resumes a paused one. */\n onResume?: (id: string) => void;\n /** Tries a failed one again. */\n onRetry?: (id: string) => void;\n /** Stops an upload under way and drops it. */\n onCancel?: (id: string) => void;\n /** Drops a finished or failed one from the list. */\n onRemove?: (id: string) => void;\n /** The locale of sizes and speeds. Default `\"en\"`. */\n locale?: string;\n /** The words, passed translated; English by default. */\n labels?: Partial<UploadListLabels>;\n /** Classes merged on the list. */\n className?: string;\n};\n\nfunction Preview({ item }: { item: UploadItem }) {\n if (item.previewUrl && item.type?.startsWith(\"image/\")) return <img src={item.previewUrl} alt=\"\" className=\"h-full w-full object-cover\" />;\n if (item.previewUrl && item.type?.startsWith(\"video/\")) return <video src={item.previewUrl} muted playsInline preload=\"metadata\" className=\"h-full w-full object-cover\" aria-hidden />;\n return <FileGlyph className=\"h-5 w-5\" />;\n}\n\n/** The files being sent: preview, size, progress, speed and time left, and the actions each state allows. */\nexport function UploadList({ items, onPause, onResume, onRetry, onCancel, onRemove, locale = \"en\", labels: given, className }: UploadListProps) {\n const labels = { ...uploadListLabels, ...given };\n const s = fileUpload();\n const action = (label: string, run: () => void, glyph: React.ReactNode) => (\n <button type=\"button\" className={s.action()} aria-label={label} title={label} onClick={run}>\n {glyph}\n </button>\n );\n return (\n <ul aria-label={labels.list} className={s.list({ className })}>\n {items.map((item) => {\n const left = item.status === \"uploading\" && item.speed ? ((1 - item.progress) * item.size) / item.speed : null;\n return (\n <li key={item.id} className={s.item()} data-status={item.status}>\n <span className={s.preview()}>\n <Preview item={item} />\n </span>\n <div className={s.body()}>\n <p className={s.name()}>{item.name}</p>\n {item.status !== \"done\" && item.status !== \"error\" ? (\n <Progress size=\"sm\" label={item.name} value={item.status === \"queued\" ? null : Math.round(item.progress * 100)} />\n ) : null}\n <p className={s.meta()}>\n <span>{labels.status(item.status)}</span>\n <span>{item.status === \"done\" ? formatBytes(item.size, locale) : `${formatBytes(item.progress * item.size, locale)} / ${formatBytes(item.size, locale)}`}</span>\n {item.status === \"uploading\" && item.speed ? <span>{formatBytes(item.speed, locale)}/s</span> : null}\n {left !== null ? <span>{labels.remaining(left)}</span> : null}\n </p>\n {item.status === \"error\" && item.error ? (\n <p role=\"alert\" className={s.error()}>\n {item.error}\n </p>\n ) : null}\n </div>\n <div className={s.actions()}>\n {item.status === \"uploading\" && onPause && action(labels.pause(item.name), () => onPause(item.id), <PauseGlyph className=\"h-4 w-4\" />)}\n {item.status === \"paused\" && onResume && action(labels.resume(item.name), () => onResume(item.id), <PlayGlyph className=\"h-4 w-4\" />)}\n {item.status === \"error\" && onRetry && action(labels.retry(item.name), () => onRetry(item.id), <RetryGlyph className=\"h-4 w-4\" />)}\n {(item.status === \"done\" || item.status === \"error\") && onRemove\n ? action(labels.remove(item.name), () => onRemove(item.id), <CloseGlyph className=\"h-4 w-4\" />)\n : item.status !== \"done\" && onCancel\n ? action(labels.cancel(item.name), () => onCancel(item.id), <CloseGlyph className=\"h-4 w-4\" />)\n : null}\n </div>\n </li>\n );\n })}\n </ul>\n );\n}\n\nexport type UploadContext = {\n /** Aborted on pause and cancel: stop sending (`tusUpload.abort()`, `xhr.abort()`). */\n signal: AbortSignal;\n /** Report the bytes sent so far. */\n onProgress: (sent: number) => void;\n /** `true` when this call resumes a paused or failed upload (a Tus client then continues from its offset). */\n resumed: boolean;\n};\n\nexport type UploadQueueOptions = {\n /** Sends one file; resolves when it is done, rejects on failure (its `message` becomes the item's error). */\n upload: (file: File, context: UploadContext) => Promise<void>;\n /** How many files go at once. Default 2. */\n concurrency?: number;\n /** How many times a failed upload is tried again by itself (with `backoff`) before it shows its error. Default 0. */\n retries?: number;\n /** The wait between retries: exponential from 1 s by default (`@krizaka/pacer`). */\n backoff?: QueueOptions[\"backoff\"];\n /** Whether an error deserves another try (a 4xx never does). Default: every error does, up to `retries`. */\n shouldRetry?: QueueOptions[\"shouldRetry\"];\n /** Makes an object URL for each file to preview (revoked when it leaves). Default `true`. */\n previews?: boolean;\n};\n\n/** What the screen reads of an upload besides its status: the file, how far it got, how fast. */\ntype UploadMeta = { file: File; progress: number; speed?: number; previewUrl?: string; last?: { at: number; sent: number } };\n\n/**\n * The state of an upload queue around the app's `upload` function: `add`, `pause`, `resume`, `retry`, `cancel`, `remove`.\n * The line itself — concurrency, retries with backoff, pause by aborting the signal — is `createQueue` of\n * `@krizaka/pacer`; this hook adds the files, their previews, their progress and their speed.\n */\nexport function useUploadQueue(options: UploadQueueOptions) {\n const { concurrency, retries, backoff, shouldRetry } = options;\n const latest = useLatest(options);\n const queue = useQueue<UploadMeta>({ concurrency: concurrency ?? 2, retries, backoff, shouldRetry });\n const jobs = useQueueSnapshot(queue);\n const metas = useLatest(new Map(jobs.map((job) => [job.id, job.meta])));\n\n const add = React.useCallback(\n (files: readonly File[]) => {\n const { previews = true } = latest.current;\n for (const file of files) {\n const id = `${file.name}-${file.size}-${Math.random().toString(36).slice(2, 8)}`;\n const previewUrl = previews && typeof URL.createObjectURL === \"function\" ? URL.createObjectURL(file) : undefined;\n queue.add(\n async ({ signal, resumed }) => {\n queue.update(id, (m) => ({ ...m, last: { at: Date.now(), sent: m.progress * file.size } }));\n try {\n await latest.current.upload(file, {\n signal,\n resumed,\n onProgress: (sent) => {\n if (signal.aborted) return;\n queue.update(id, (m) => {\n const now = Date.now();\n const last = m.last ?? { at: now, sent };\n const fresh = now - last.at > 400;\n return { ...m, progress: file.size ? Math.min(1, sent / file.size) : 0, speed: fresh ? ((sent - last.sent) / (now - last.at)) * 1000 : m.speed, last: fresh ? { at: now, sent } : last };\n });\n },\n });\n if (!signal.aborted) queue.update(id, (m) => ({ ...m, progress: 1, speed: undefined }));\n } catch (error) {\n if (!signal.aborted) queue.update(id, (m) => ({ ...m, speed: undefined }));\n throw error;\n }\n },\n { id, meta: { file, progress: 0, previewUrl } },\n );\n }\n },\n [latest, queue],\n );\n const forget = React.useCallback(\n (id: string) => {\n const url = metas.current.get(id)?.previewUrl;\n if (url) URL.revokeObjectURL(url);\n queue.cancel(id);\n },\n [metas, queue],\n );\n React.useEffect(\n () => () => {\n for (const meta of metas.current.values()) if (meta.previewUrl) URL.revokeObjectURL(meta.previewUrl);\n },\n [metas],\n );\n const pause = React.useCallback(\n (id: string) => {\n queue.pause(id);\n queue.update(id, (m) => ({ ...m, speed: undefined }));\n },\n [queue],\n );\n\n const items = React.useMemo<UploadItem[]>(\n () =>\n jobs.map(({ id, status, error, meta }) => ({\n id,\n name: meta.file.name,\n size: meta.file.size,\n type: meta.file.type,\n progress: meta.progress,\n status: status === \"running\" ? \"uploading\" : status,\n error: error?.message,\n previewUrl: meta.previewUrl,\n speed: status === \"running\" ? meta.speed : undefined,\n })),\n [jobs],\n );\n return { items, add, pause, resume: queue.resume, retry: queue.retry, cancel: forget, remove: forget };\n}\n","// Server-safe: no hook, no context. role=\"progressbar\" with aria-valuenow / aria-valuemax (none when indeterminate),\n// named by `label`. A bar or a ring; the ring can hold content in its centre (an amount, a percentage).\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nexport const progress = tv({\n slots: {\n root: \"relative\",\n track: \"\",\n indicator: \"\",\n center: \"absolute inset-0 flex flex-col items-center justify-center text-center\",\n buffer: \"absolute inset-y-0 left-0 -z-10 rounded-full bg-accent/25 transition-[width] duration-500 motion-reduce:transition-none\",\n frame: \"flex w-full flex-col gap-1.5\",\n header: \"flex items-baseline gap-2 text-xs\",\n caption: \"min-w-0 flex-1 truncate font-medium text-fg\",\n figure: \"shrink-0 tabular-nums text-fg-secondary\",\n },\n variants: {\n variant: {\n bar: {\n root: \"relative isolate w-full overflow-hidden rounded-full bg-surface-3\",\n indicator:\n \"kz-progress h-full rounded-full bg-linear-to-r from-accent to-accent-2 transition-[width] duration-500 motion-reduce:transition-none \" +\n \"data-[state=indeterminate]:w-2/5\",\n },\n ring: {\n root: \"inline-flex shrink-0 items-center justify-center\",\n track: \"stroke-border-default\",\n indicator:\n \"stroke-accent transition-[stroke-dashoffset] duration-1000 motion-reduce:transition-none \" +\n \"data-[state=indeterminate]:origin-center data-[state=indeterminate]:animate-spin motion-reduce:data-[state=indeterminate]:animate-none\",\n },\n },\n size: { sm: {}, md: {}, lg: {} },\n status: {\n active: {},\n // Paused: the fill rests, striped — the task is waiting for someone, not broken.\n paused: { indicator: \"kz-progress-paused opacity-70\" },\n // Failed: the fill turns to the danger colour where it stopped.\n error: { indicator: \"from-danger to-danger stroke-danger\" },\n },\n },\n compoundVariants: [\n { variant: \"bar\", size: \"sm\", class: { root: \"h-1\" } },\n { variant: \"bar\", size: \"md\", class: { root: \"h-2\" } },\n { variant: \"bar\", size: \"lg\", class: { root: \"h-3\" } },\n { variant: \"ring\", size: \"sm\", class: { root: \"h-10 w-10\" } },\n { variant: \"ring\", size: \"md\", class: { root: \"h-20 w-20\" } },\n { variant: \"ring\", size: \"lg\", class: { root: \"h-36 w-36\" } },\n ],\n defaultVariants: { variant: \"bar\", size: \"md\", status: \"active\" },\n});\n\nexport type ProgressVariants = VariantProps<typeof progress>;\n\nexport type ProgressProps = Omit<React.ComponentProps<\"div\">, \"children\"> &\n ProgressVariants & {\n /** bar · ring (a ring holds its `children` in the centre). */\n variant?: ProgressVariants[\"variant\"];\n /** sm · md · lg: the bar's thickness, the ring's diameter. */\n size?: ProgressVariants[\"size\"];\n /** How far, between 0 and `max`. `null` or absent: indeterminate (a wait of unknown length). */\n value?: number | null;\n /** The value that means complete. */\n max?: number;\n /** The accessible name — passed translated (\"Upload\", \"Raised towards the goal\"). */\n label: string;\n /** The value in words for assistive technology (\"$420 of $1,000\") — default: the percentage. */\n valueText?: string;\n /** A ring's centre: an amount, a percentage, an icon. */\n children?: React.ReactNode;\n /** How far is already available beyond the value (a video buffered ahead), between 0 and `max` — bar only. */\n buffer?: number;\n /** Splits the bar or the ring into this many segments: the steps of a task, the parts of a goal. */\n segments?: number;\n /**\n * active · paused (the fill rests, striped: waiting for someone) · error (the fill turns to the danger colour where it stopped).\n * @default \"active\"\n */\n status?: ProgressVariants[\"status\"];\n /** Shows the label above the bar, with the percentage (or `valueText`) and `eta` — bar only. */\n showLabel?: boolean;\n /** The time left, in words (“About 2 min left”), shown with `showLabel` and read with the value — passed translated. */\n eta?: string;\n };\n\nconst STROKE = { sm: 12, md: 9, lg: 7 } as const;\n\n/** A progress bar or ring: `value` / `max`, indeterminate without a value. State: `data-state` (loading · complete · indeterminate). */\nexport function Progress({\n variant = \"bar\",\n size = \"md\",\n value,\n max = 100,\n label,\n valueText,\n className,\n children,\n buffer,\n segments,\n status = \"active\",\n showLabel,\n eta,\n ...props\n}: ProgressProps) {\n const s = progress({ variant, size, status });\n const maskId = `kz-seg-${React.useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n const known = typeof value === \"number\" && Number.isFinite(value);\n const ratio = known ? Math.min(1, Math.max(0, value / (max || 1))) : 0;\n const percent = Math.round(ratio * 100);\n const state = !known ? \"indeterminate\" : ratio >= 1 ? \"complete\" : \"loading\";\n const aria = {\n role: \"progressbar\" as const,\n \"aria-label\": label,\n \"aria-valuemin\": known ? 0 : undefined,\n \"aria-valuemax\": known ? max : undefined,\n \"aria-valuenow\": known ? Math.min(max, Math.max(0, value)) : undefined,\n \"aria-valuetext\": [known ? (valueText ?? `${percent}%`) : valueText, eta].filter(Boolean).join(\", \") || undefined,\n \"data-state\": state,\n \"data-status\": status === \"active\" ? undefined : status,\n \"data-value\": known ? value : undefined,\n \"data-variant\": variant,\n };\n if (variant === \"ring\") {\n const stroke = STROKE[size];\n const r = 50 - stroke / 2;\n return (\n <div {...aria} className={s.root({ className })} {...props}>\n <svg viewBox=\"0 0 100 100\" className=\"h-full w-full -rotate-90\" aria-hidden>\n {segments && segments > 1 && (\n <mask id={maskId}>\n <circle cx=\"50\" cy=\"50\" r={r} fill=\"none\" stroke=\"white\" strokeWidth={stroke + 2} pathLength={100} strokeDasharray={`${100 / segments - 2} 2`} />\n </mask>\n )}\n <g mask={segments && segments > 1 ? `url(#${maskId})` : undefined}>\n <circle cx=\"50\" cy=\"50\" r={r} fill=\"none\" strokeWidth={stroke} className={s.track()} />\n <circle\n cx=\"50\"\n cy=\"50\"\n r={r}\n fill=\"none\"\n strokeWidth={stroke}\n strokeLinecap=\"round\"\n pathLength={100}\n strokeDasharray={100}\n strokeDashoffset={known ? 100 - percent : 75}\n data-state={state}\n className={s.indicator()}\n />\n </g>\n </svg>\n {children !== undefined && <div className={s.center()}>{children}</div>}\n </div>\n );\n }\n const segmented = segments && segments > 1 ? ({ \"--kz-segments\": segments } as React.CSSProperties) : undefined;\n const bufferRatio = typeof buffer === \"number\" && Number.isFinite(buffer) ? Math.min(1, Math.max(0, buffer / (max || 1))) : null;\n const bar = (\n <div {...aria} className={s.root({ className: [segmented && \"kz-progress-segmented\", className].filter(Boolean).join(\" \") || undefined })} style={segmented} {...props}>\n <div data-state={state} className={s.indicator()} style={known ? { width: `${ratio * 100}%` } : undefined} />\n {bufferRatio !== null && <div aria-hidden className={s.buffer()} style={{ width: `${bufferRatio * 100}%` }} />}\n </div>\n );\n if (!showLabel) return bar;\n const figure = [known ? (valueText ?? `${percent} %`) : valueText, eta].filter(Boolean).join(\" · \");\n return (\n <div className={s.frame()}>\n <div aria-hidden className={s.header()}>\n <span className={s.caption()}>{label}</span>\n {figure && <span className={s.figure()}>{figure}</span>}\n </div>\n {bar}\n </div>\n );\n}\n"],"mappings":"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","names":["React","tv","jsx","jsxs","tv"]}
|
package/dist/filter-bar.js
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
filterBar,
|
|
6
6
|
readFilterParams,
|
|
7
7
|
writeFilterParams
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-FKBHWS4N.js";
|
|
9
|
+
import "./chunk-7UCPGXKN.js";
|
|
10
|
+
import "./chunk-D3T54RSL.js";
|
|
10
11
|
import "./chunk-J3NC6ER4.js";
|
|
11
12
|
import "./chunk-2FRWNSRO.js";
|
|
12
13
|
import "./chunk-CPA5CF35.js";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The Krizaka flock — the geometry of the mascots, drawn by the web `Mascot` (`./mascot.tsx`) and the native one
|
|
3
3
|
* (`../native/mascots.tsx`). Five birds that have carried the Krizaka story since the first site (the sentry falcon,
|
|
4
4
|
* the owl that reads, the developer duck, the flamingo that answers, the carrier pigeon), the lovebird that
|
|
5
|
-
* introduces (2026-10, with
|
|
5
|
+
* introduces (2026-10, with Yasakia: "l'inséparable présente") and the crane that listens (2026-10, with Yororis:
|
|
6
6
|
* "la grue écoute"), redrawn on one 120 × 120 grid so that any bird can wear any mood.
|
|
7
7
|
*
|
|
8
8
|
* Colours are roles, never fixed: `brand` (the default) paints a bird in the host's accent (`--kz-accent`,
|
package/dist/image-cropper.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -34,20 +34,22 @@ declare function OrazakaLogo({ size, animated, title, className }: MarkProps): R
|
|
|
34
34
|
*/
|
|
35
35
|
declare function OrochiaLogo({ size, animated, title, className }: MarkProps): React.JSX.Element;
|
|
36
36
|
|
|
37
|
-
type BrandId = "krizaka" | "orazaka" | "orochia" | "
|
|
37
|
+
type BrandId = "krizaka" | "orazaka" | "orochia" | "yasakia" | "yororis";
|
|
38
38
|
/** The mark of a Krizaka brand by id — for navigation, cards and lists driven by data. */
|
|
39
39
|
declare function ProductLogo({ id, ...props }: MarkProps & {
|
|
40
|
-
id: BrandId;
|
|
40
|
+
id: BrandId | "yazakia";
|
|
41
41
|
}): React.JSX.Element;
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* The
|
|
44
|
+
* The Yasakia mark: two voices — two solid speech bubbles that overlap, the left a shade deeper — in the Yasakia
|
|
45
45
|
* gradient (emerald → turquoise, the brand's mark gradient of @krizaka/tokens, the same in dark and light). Where they
|
|
46
46
|
* overlap, what the two share, is the bright core: a lens lit from above that breathes. The pair, two nodes on the
|
|
47
47
|
* dashed orbit, turns slowly around it — the family's grammar (orbit, ring, bold emblem, bright core). Below 48 px it
|
|
48
48
|
* crops to the emblem.
|
|
49
49
|
*/
|
|
50
|
-
declare function
|
|
50
|
+
declare function YasakiaLogo({ size, animated, title, className }: MarkProps): React.JSX.Element;
|
|
51
|
+
/** @deprecated Yazakia was renamed Yasakia (2026-10): use `YasakiaLogo`. Kept for one minor version. */
|
|
52
|
+
declare const YazakiaLogo: typeof YasakiaLogo;
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* The Yororis mark, "the hold": a "Y" whose arms are two filled leaves — open hands — rising from one stem and cupping
|
|
@@ -58,4 +60,4 @@ declare function YazakiaLogo({ size, animated, title, className }: MarkProps): R
|
|
|
58
60
|
*/
|
|
59
61
|
declare function YororisLogo({ size, animated, title, className }: MarkProps): React.JSX.Element;
|
|
60
62
|
|
|
61
|
-
export { type BrandId, KrizakaLogo, type MarkProps, OrazakaLogo, OrochiaLogo, ProductLogo, YazakiaLogo, YororisLogo, cx };
|
|
63
|
+
export { type BrandId, KrizakaLogo, type MarkProps, OrazakaLogo, OrochiaLogo, ProductLogo, YasakiaLogo, YazakiaLogo, YororisLogo, cx };
|
package/dist/index.js
CHANGED
|
@@ -212,10 +212,10 @@ function OrochiaLogo({ size = 36, animated = true, title, className }) {
|
|
|
212
212
|
] });
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
// src/marks/
|
|
215
|
+
// src/marks/yasakia-geometry.ts
|
|
216
216
|
import { marks as marks4 } from "@krizaka/tokens/native";
|
|
217
|
-
var turquoise = marks4.
|
|
218
|
-
var
|
|
217
|
+
var turquoise = marks4.yasakia[marks4.yasakia.length - 1][1];
|
|
218
|
+
var YASAKIA = {
|
|
219
219
|
viewBox: "0 0 400 400",
|
|
220
220
|
croppedViewBox: "80 80 240 240",
|
|
221
221
|
center: 200,
|
|
@@ -228,7 +228,7 @@ var YAZAKIA = {
|
|
|
228
228
|
shade: { color: "#000", opacity: 0.22 },
|
|
229
229
|
/** Where the bubbles overlap: the core, lit from above. */
|
|
230
230
|
lens: { d: "M200 112.6 A80 80 0 0 1 200 255.4 A80 80 0 0 1 200 112.6 Z", y1: 112.6, y2: 255.4 },
|
|
231
|
-
gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks4.
|
|
231
|
+
gradient: { x1: 110, y1: 300, x2: 290, y2: 100, stops: marks4.yasakia },
|
|
232
232
|
/** The light on the lens: bright at the top, fading down. */
|
|
233
233
|
highlight: { color: "#f0fdfa", from: 0.75, to: 0.2 },
|
|
234
234
|
/** The pair: two nodes on the orbit, opposite each other, that turn with it. */
|
|
@@ -240,51 +240,52 @@ var YAZAKIA = {
|
|
|
240
240
|
ring: { r: 134, width: 1 }
|
|
241
241
|
};
|
|
242
242
|
|
|
243
|
-
// src/marks/
|
|
243
|
+
// src/marks/YasakiaLogo.tsx
|
|
244
244
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
245
245
|
var CSS4 = `
|
|
246
246
|
.yzm-orbit { transform-origin: 200px 200px; animation: yzm-orbit 30s linear infinite; }
|
|
247
247
|
.yzm-core { transform-box: fill-box; transform-origin: center; animation: yzm-core 3.2s ease-in-out infinite; }
|
|
248
248
|
@keyframes yzm-orbit { to { transform: rotate(360deg); } }
|
|
249
249
|
@keyframes yzm-core {
|
|
250
|
-
0%, 100% { filter: drop-shadow(0 0 6px ${withAlpha(
|
|
251
|
-
50% { filter: drop-shadow(0 0 14px ${withAlpha(
|
|
250
|
+
0%, 100% { filter: drop-shadow(0 0 6px ${withAlpha(YASAKIA.pulse.color, YASAKIA.pulse.low)}); opacity: .9; }
|
|
251
|
+
50% { filter: drop-shadow(0 0 14px ${withAlpha(YASAKIA.pulse.color, YASAKIA.pulse.high)}); opacity: 1; }
|
|
252
252
|
}
|
|
253
253
|
@media (prefers-reduced-motion: reduce) { .yzm-orbit, .yzm-core { animation: none; } }
|
|
254
254
|
`;
|
|
255
|
-
function
|
|
255
|
+
function YasakiaLogo({ size = 32, animated = true, title, className }) {
|
|
256
256
|
const id = useIds("yzm", ["gradient", "light", "glow"]);
|
|
257
257
|
const fill = `url(#${id.gradient})`;
|
|
258
|
-
return /* @__PURE__ */ jsxs4("svg", { width: size, height: size, viewBox: size < 48 ?
|
|
258
|
+
return /* @__PURE__ */ jsxs4("svg", { width: size, height: size, viewBox: size < 48 ? YASAKIA.croppedViewBox : YASAKIA.viewBox, fill: "none", xmlns: "http://www.w3.org/2000/svg", className, ...a11y(title), children: [
|
|
259
259
|
/* @__PURE__ */ jsx4("style", { children: CSS4 }),
|
|
260
260
|
/* @__PURE__ */ jsxs4("defs", { children: [
|
|
261
|
-
/* @__PURE__ */ jsx4("linearGradient", { id: id.gradient, gradientUnits: "userSpaceOnUse", x1:
|
|
262
|
-
/* @__PURE__ */ jsxs4("linearGradient", { id: id.light, gradientUnits: "userSpaceOnUse", x1: "0", y1:
|
|
263
|
-
/* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor:
|
|
264
|
-
/* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor:
|
|
261
|
+
/* @__PURE__ */ jsx4("linearGradient", { id: id.gradient, gradientUnits: "userSpaceOnUse", x1: YASAKIA.gradient.x1, y1: YASAKIA.gradient.y1, x2: YASAKIA.gradient.x2, y2: YASAKIA.gradient.y2, children: YASAKIA.gradient.stops.map(([offset, color]) => /* @__PURE__ */ jsx4("stop", { offset, stopColor: color }, offset)) }),
|
|
262
|
+
/* @__PURE__ */ jsxs4("linearGradient", { id: id.light, gradientUnits: "userSpaceOnUse", x1: "0", y1: YASAKIA.lens.y1, x2: "0", y2: YASAKIA.lens.y2, children: [
|
|
263
|
+
/* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor: YASAKIA.highlight.color, stopOpacity: YASAKIA.highlight.from }),
|
|
264
|
+
/* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor: YASAKIA.highlight.color, stopOpacity: YASAKIA.highlight.to })
|
|
265
265
|
] }),
|
|
266
266
|
/* @__PURE__ */ jsxs4("radialGradient", { id: id.glow, cx: "50%", cy: "50%", r: "50%", children: [
|
|
267
|
-
/* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor:
|
|
268
|
-
/* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor:
|
|
267
|
+
/* @__PURE__ */ jsx4("stop", { offset: "0%", stopColor: YASAKIA.glow.color, stopOpacity: YASAKIA.glow.opacity }),
|
|
268
|
+
/* @__PURE__ */ jsx4("stop", { offset: "100%", stopColor: YASAKIA.glow.color, stopOpacity: "0" })
|
|
269
269
|
] })
|
|
270
270
|
] }),
|
|
271
|
-
size >= 48 ? /* @__PURE__ */ jsx4("circle", { cx:
|
|
272
|
-
/* @__PURE__ */ jsx4("circle", { cx:
|
|
271
|
+
size >= 48 ? /* @__PURE__ */ jsx4("circle", { cx: YASAKIA.center, cy: YASAKIA.center, r: "190", fill: `url(#${id.glow})` }) : null,
|
|
272
|
+
/* @__PURE__ */ jsx4("circle", { cx: YASAKIA.center, cy: YASAKIA.center, r: YASAKIA.ring.r, stroke: neutral("--kz-border-default", 30), strokeWidth: YASAKIA.ring.width, opacity: ".35" }),
|
|
273
273
|
/* @__PURE__ */ jsxs4("g", { className: animated ? "yzm-orbit" : void 0, children: [
|
|
274
|
-
/* @__PURE__ */ jsx4("circle", { cx:
|
|
275
|
-
|
|
274
|
+
/* @__PURE__ */ jsx4("circle", { cx: YASAKIA.center, cy: YASAKIA.center, r: YASAKIA.orbit.r, stroke: neutral("--kz-border-strong", 40), strokeWidth: YASAKIA.orbit.width, strokeDasharray: YASAKIA.orbit.dash, opacity: ".6" }),
|
|
275
|
+
YASAKIA.pair.at.map(([cx2, cy]) => /* @__PURE__ */ jsx4("circle", { cx: cx2, cy, r: YASAKIA.pair.r, fill }, cx2))
|
|
276
276
|
] }),
|
|
277
|
-
|
|
277
|
+
YASAKIA.voices.map((v, i) => /* @__PURE__ */ jsxs4("g", { children: [
|
|
278
278
|
/* @__PURE__ */ jsx4("path", { d: v.body, fill }),
|
|
279
279
|
/* @__PURE__ */ jsx4("path", { d: v.tail, fill }),
|
|
280
|
-
i === 0 ? /* @__PURE__ */ jsx4("path", { d: `${v.body} ${v.tail}`, fill:
|
|
280
|
+
i === 0 ? /* @__PURE__ */ jsx4("path", { d: `${v.body} ${v.tail}`, fill: YASAKIA.shade.color, opacity: YASAKIA.shade.opacity }) : null
|
|
281
281
|
] }, v.body)),
|
|
282
282
|
/* @__PURE__ */ jsxs4("g", { className: animated ? "yzm-core" : void 0, children: [
|
|
283
|
-
/* @__PURE__ */ jsx4("path", { d:
|
|
284
|
-
/* @__PURE__ */ jsx4("path", { d:
|
|
283
|
+
/* @__PURE__ */ jsx4("path", { d: YASAKIA.lens.d, fill }),
|
|
284
|
+
/* @__PURE__ */ jsx4("path", { d: YASAKIA.lens.d, fill: `url(#${id.light})` })
|
|
285
285
|
] })
|
|
286
286
|
] });
|
|
287
287
|
}
|
|
288
|
+
var YazakiaLogo = YasakiaLogo;
|
|
288
289
|
|
|
289
290
|
// src/marks/yororis-geometry.ts
|
|
290
291
|
import { marks as marks5 } from "@krizaka/tokens/native";
|
|
@@ -368,7 +369,7 @@ import { jsx as jsx6 } from "react/jsx-runtime";
|
|
|
368
369
|
function ProductLogo({ id, ...props }) {
|
|
369
370
|
if (id === "krizaka") return /* @__PURE__ */ jsx6(KrizakaLogo, { ...props });
|
|
370
371
|
if (id === "orazaka") return /* @__PURE__ */ jsx6(OrazakaLogo, { ...props });
|
|
371
|
-
if (id === "yazakia") return /* @__PURE__ */ jsx6(
|
|
372
|
+
if (id === "yasakia" || id === "yazakia") return /* @__PURE__ */ jsx6(YasakiaLogo, { ...props });
|
|
372
373
|
if (id === "yororis") return /* @__PURE__ */ jsx6(YororisLogo, { ...props });
|
|
373
374
|
return /* @__PURE__ */ jsx6(OrochiaLogo, { ...props });
|
|
374
375
|
}
|
|
@@ -379,6 +380,7 @@ export {
|
|
|
379
380
|
OrochiaLogo,
|
|
380
381
|
ProductLogo,
|
|
381
382
|
RotatingWord,
|
|
383
|
+
YasakiaLogo,
|
|
382
384
|
YazakiaLogo,
|
|
383
385
|
YororisLogo,
|
|
384
386
|
cx
|