@krizaka/ui 2.2.0 → 2.4.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 +33 -8
- package/dist/audio-player.d.ts +113 -0
- package/dist/audio-player.js +207 -0
- package/dist/audio-player.js.map +1 -0
- package/dist/{button-Cgfkg_3y.d.ts → button-BMAO7dKF.d.ts} +29 -2
- package/dist/button.d.ts +32 -5
- package/dist/button.js +62 -9
- package/dist/button.js.map +1 -1
- package/dist/chip-link.d.ts +53 -0
- package/dist/chip-link.js +32 -0
- package/dist/chip-link.js.map +1 -0
- package/dist/chunk-2USC2YT6.js +17 -0
- package/dist/chunk-2USC2YT6.js.map +1 -0
- package/dist/chunk-43PTCDTL.js +31 -0
- package/dist/chunk-43PTCDTL.js.map +1 -0
- package/dist/chunk-6ISW63JI.js +11 -0
- package/dist/chunk-6ISW63JI.js.map +1 -0
- package/dist/chunk-EUPTXKH7.js +95 -0
- package/dist/chunk-EUPTXKH7.js.map +1 -0
- package/dist/chunk-HAQWKORZ.js +117 -0
- package/dist/chunk-HAQWKORZ.js.map +1 -0
- package/dist/{chunk-FHYZSO2S.js → chunk-R6SDS7JM.js} +5 -3
- package/dist/chunk-R6SDS7JM.js.map +1 -0
- package/dist/chunk-RUPNPMP3.js +103 -0
- package/dist/chunk-RUPNPMP3.js.map +1 -0
- package/dist/chunk-RUPRYJ45.js +82 -0
- package/dist/chunk-RUPRYJ45.js.map +1 -0
- package/dist/chunk-VDR2EP6K.js +49 -0
- package/dist/chunk-VDR2EP6K.js.map +1 -0
- package/dist/chunk-WEVYKZIF.js +33 -0
- package/dist/chunk-WEVYKZIF.js.map +1 -0
- package/dist/chunk-XUMJA5ZI.js +283 -0
- package/dist/chunk-XUMJA5ZI.js.map +1 -0
- package/dist/chunk-YH4LMWRU.js +92 -0
- package/dist/chunk-YH4LMWRU.js.map +1 -0
- package/dist/chunk-YX6CNVJA.js +94 -0
- package/dist/chunk-YX6CNVJA.js.map +1 -0
- package/dist/chunk-ZIXB33X7.js +95 -0
- package/dist/chunk-ZIXB33X7.js.map +1 -0
- package/dist/combobox.d.ts +188 -0
- package/dist/combobox.js +16 -0
- package/dist/combobox.js.map +1 -0
- package/dist/command.d.ts +7 -7
- package/dist/command.js +3 -2
- package/dist/command.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/data-table.d.ts +246 -0
- package/dist/data-table.js +290 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +288 -0
- package/dist/date-picker.js +364 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/{dialog-GQ4Nvuyf.d.ts → dialog-D42QwYb_.d.ts} +11 -1
- package/dist/dialog.d.ts +1 -1
- package/dist/dialog.js +3 -2
- package/dist/dialog.js.map +1 -1
- package/dist/file-upload.d.ts +238 -0
- package/dist/file-upload.js +437 -0
- package/dist/file-upload.js.map +1 -0
- package/dist/filter-bar.d.ts +139 -0
- package/dist/filter-bar.js +155 -0
- package/dist/filter-bar.js.map +1 -0
- package/dist/glow-card.d.ts +20 -0
- package/dist/glow-card.js +30 -0
- package/dist/glow-card.js.map +1 -0
- package/dist/image-cropper.d.ts +115 -0
- package/dist/image-cropper.js +166 -0
- package/dist/image-cropper.js.map +1 -0
- package/dist/index.d.ts +2 -20
- package/dist/index.js +7 -64
- package/dist/index.js.map +1 -1
- package/dist/iso.d.ts +400 -0
- package/dist/iso.js +363 -0
- package/dist/iso.js.map +1 -0
- package/dist/loader.d.ts +206 -0
- package/dist/loader.js +138 -0
- package/dist/loader.js.map +1 -0
- package/dist/marquee.d.ts +33 -0
- package/dist/marquee.js +52 -0
- package/dist/marquee.js.map +1 -0
- package/dist/media-capture.d.ts +155 -0
- package/dist/media-capture.js +242 -0
- package/dist/media-capture.js.map +1 -0
- package/dist/meter.d.ts +146 -0
- package/dist/meter.js +107 -0
- package/dist/meter.js.map +1 -0
- package/dist/motion.css +394 -3
- package/dist/motion.d.ts +46 -0
- package/dist/motion.js +10 -0
- package/dist/motion.js.map +1 -0
- package/dist/native/media-capture.d.ts +49 -0
- package/dist/native/media-capture.js +113 -0
- package/dist/native/media-capture.js.map +1 -0
- package/dist/native/video-player.d.ts +50 -0
- package/dist/native/video-player.js +69 -0
- package/dist/native/video-player.js.map +1 -0
- package/dist/native.d.ts +66 -2
- package/dist/native.js +166 -68
- package/dist/native.js.map +1 -1
- package/dist/nav-bar.d.ts +178 -0
- package/dist/nav-bar.js +157 -0
- package/dist/nav-bar.js.map +1 -0
- package/dist/number-ticker.d.ts +32 -0
- package/dist/number-ticker.js +61 -0
- package/dist/number-ticker.js.map +1 -0
- package/dist/pagination.d.ts +141 -0
- package/dist/pagination.js +165 -0
- package/dist/pagination.js.map +1 -0
- package/dist/progress.d.ts +56 -1
- package/dist/progress.js +72 -25
- package/dist/progress.js.map +1 -1
- package/dist/search-field.d.ts +100 -0
- package/dist/search-field.js +167 -0
- package/dist/search-field.js.map +1 -0
- package/dist/skeleton.d.ts +18 -3
- package/dist/skeleton.js +9 -1
- package/dist/skeleton.js.map +1 -1
- package/dist/spinner.d.ts +21 -5
- package/dist/spinner.js +13 -12
- package/dist/spinner.js.map +1 -1
- package/dist/stepper.d.ts +112 -0
- package/dist/stepper.js +67 -0
- package/dist/stepper.js.map +1 -0
- package/dist/tabs.d.ts +1 -1
- package/dist/tabs.js +14 -8
- package/dist/tabs.js.map +1 -1
- package/dist/tag-input.d.ts +44 -0
- package/dist/tag-input.js +67 -0
- package/dist/tag-input.js.map +1 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +2 -1
- package/dist/theme.js.map +1 -1
- package/dist/toast.d.ts +39 -4
- package/dist/toast.js +81 -7
- package/dist/toast.js.map +1 -1
- package/dist/video-player.d.ts +204 -0
- package/dist/video-player.js +371 -0
- package/dist/video-player.js.map +1 -0
- package/dist/video-trimmer.d.ts +142 -0
- package/dist/video-trimmer.js +193 -0
- package/dist/video-trimmer.js.map +1 -0
- package/package.json +375 -7
- package/registry/alert.json +1 -1
- package/registry/audio-player.json +282 -0
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/button.json +232 -26
- package/registry/card.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +158 -0
- package/registry/chip.json +1 -1
- package/registry/combobox.json +475 -0
- package/registry/command.json +1 -1
- package/registry/confirm-button.json +91 -7
- package/registry/countdown.json +1 -1
- package/registry/data-table.json +500 -0
- package/registry/date-picker.json +978 -0
- package/registry/dialog.json +15 -7
- package/registry/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/examples/audio-player/podcast.js +22 -0
- package/registry/examples/audio-player/podcast.tsx +19 -0
- package/registry/examples/audio-player/voice-note.d.ts +4 -0
- package/registry/examples/audio-player/voice-note.js +12 -0
- package/registry/examples/audio-player/voice-note.tsx +12 -0
- package/registry/examples/button/error.d.ts +4 -0
- package/registry/examples/button/error.js +11 -0
- package/registry/examples/button/error.tsx +10 -0
- package/registry/examples/button/loading.tsx +1 -1
- package/registry/examples/button/save-flow.d.ts +4 -0
- package/registry/examples/button/save-flow.js +28 -0
- package/registry/examples/button/save-flow.tsx +24 -0
- package/registry/examples/button/signature.d.ts +4 -0
- package/registry/examples/button/signature.js +17 -0
- package/registry/examples/button/signature.tsx +19 -0
- package/registry/examples/button/upload-progress.d.ts +4 -0
- package/registry/examples/button/upload-progress.js +23 -0
- package/registry/examples/button/upload-progress.tsx +26 -0
- package/registry/examples/chip-link/filters.d.ts +4 -0
- package/registry/examples/chip-link/filters.js +12 -0
- package/registry/examples/chip-link/filters.tsx +17 -0
- package/registry/examples/combobox/async.d.ts +4 -0
- package/registry/examples/combobox/async.js +23 -0
- package/registry/examples/combobox/async.tsx +22 -0
- package/registry/examples/combobox/creatable.d.ts +4 -0
- package/registry/examples/combobox/creatable.js +32 -0
- package/registry/examples/combobox/creatable.tsx +28 -0
- package/registry/examples/combobox/invalid.d.ts +4 -0
- package/registry/examples/combobox/invalid.js +23 -0
- package/registry/examples/combobox/invalid.tsx +17 -0
- package/registry/examples/combobox/multiple.d.ts +4 -0
- package/registry/examples/combobox/multiple.js +29 -0
- package/registry/examples/combobox/multiple.tsx +26 -0
- package/registry/examples/data-table/cards.d.ts +4 -0
- package/registry/examples/data-table/cards.js +21 -0
- package/registry/examples/data-table/cards.tsx +20 -0
- package/registry/examples/data-table/empty.d.ts +4 -0
- package/registry/examples/data-table/empty.js +26 -0
- package/registry/examples/data-table/empty.tsx +21 -0
- package/registry/examples/data-table/error.d.ts +4 -0
- package/registry/examples/data-table/error.js +28 -0
- package/registry/examples/data-table/error.tsx +29 -0
- package/registry/examples/data-table/jobs.d.ts +4 -0
- package/registry/examples/data-table/jobs.js +37 -0
- package/registry/examples/data-table/jobs.tsx +36 -0
- package/registry/examples/data-table/loading.d.ts +4 -0
- package/registry/examples/data-table/loading.js +16 -0
- package/registry/examples/data-table/loading.tsx +12 -0
- package/registry/examples/data-table/virtualized.d.ts +4 -0
- package/registry/examples/data-table/virtualized.js +21 -0
- package/registry/examples/data-table/virtualized.tsx +20 -0
- package/registry/examples/date-picker/calendar.d.ts +4 -0
- package/registry/examples/date-picker/calendar.js +14 -0
- package/registry/examples/date-picker/calendar.tsx +16 -0
- package/registry/examples/date-picker/french.d.ts +4 -0
- package/registry/examples/date-picker/french.js +20 -0
- package/registry/examples/date-picker/french.tsx +14 -0
- package/registry/examples/date-picker/open.d.ts +4 -0
- package/registry/examples/date-picker/open.js +11 -0
- package/registry/examples/date-picker/open.tsx +8 -0
- package/registry/examples/date-picker/range.d.ts +4 -0
- package/registry/examples/date-picker/range.js +29 -0
- package/registry/examples/date-picker/range.tsx +25 -0
- package/registry/examples/date-picker/schedule.d.ts +4 -0
- package/registry/examples/date-picker/schedule.js +26 -0
- package/registry/examples/date-picker/schedule.tsx +22 -0
- package/registry/examples/dialog/left.d.ts +4 -0
- package/registry/examples/dialog/left.js +22 -0
- package/registry/examples/dialog/left.tsx +32 -0
- package/registry/examples/file-upload/images.d.ts +4 -0
- package/registry/examples/file-upload/images.js +32 -0
- package/registry/examples/file-upload/images.tsx +25 -0
- package/registry/examples/file-upload/states.d.ts +4 -0
- package/registry/examples/file-upload/states.js +28 -0
- package/registry/examples/file-upload/states.tsx +23 -0
- package/registry/examples/file-upload/tus.d.ts +4 -0
- package/registry/examples/file-upload/tus.js +23 -0
- package/registry/examples/file-upload/tus.tsx +26 -0
- package/registry/examples/file-upload/video.d.ts +4 -0
- package/registry/examples/file-upload/video.js +39 -0
- package/registry/examples/file-upload/video.tsx +36 -0
- package/registry/examples/filter-bar/address.d.ts +4 -0
- package/registry/examples/filter-bar/address.js +29 -0
- package/registry/examples/filter-bar/address.tsx +27 -0
- package/registry/examples/filter-bar/jobs.d.ts +4 -0
- package/registry/examples/filter-bar/jobs.js +37 -0
- package/registry/examples/filter-bar/jobs.tsx +37 -0
- package/registry/examples/filter-bar/menu.d.ts +4 -0
- package/registry/examples/filter-bar/menu.js +27 -0
- package/registry/examples/filter-bar/menu.tsx +23 -0
- package/registry/examples/glow-card/link-card.d.ts +4 -0
- package/registry/examples/glow-card/link-card.js +14 -0
- package/registry/examples/glow-card/link-card.tsx +13 -0
- package/registry/examples/glow-card/plans.d.ts +4 -0
- package/registry/examples/glow-card/plans.js +25 -0
- package/registry/examples/glow-card/plans.tsx +26 -0
- package/registry/examples/glow-card/spotlight.d.ts +4 -0
- package/registry/examples/glow-card/spotlight.js +14 -0
- package/registry/examples/glow-card/spotlight.tsx +15 -0
- package/registry/examples/image-cropper/avatar.d.ts +4 -0
- package/registry/examples/image-cropper/avatar.js +13 -0
- package/registry/examples/image-cropper/avatar.tsx +14 -0
- package/registry/examples/image-cropper/banner.d.ts +4 -0
- package/registry/examples/image-cropper/banner.js +11 -0
- package/registry/examples/image-cropper/banner.tsx +10 -0
- package/registry/examples/image-cropper/vertical-video.d.ts +4 -0
- package/registry/examples/image-cropper/vertical-video.js +12 -0
- package/registry/examples/image-cropper/vertical-video.tsx +16 -0
- package/registry/examples/iso/compose.d.ts +4 -0
- package/registry/examples/iso/compose.js +22 -0
- package/registry/examples/iso/compose.tsx +19 -0
- package/registry/examples/iso/orazaka-hero.d.ts +4 -0
- package/registry/examples/iso/orazaka-hero.js +26 -0
- package/registry/examples/iso/orazaka-hero.tsx +21 -0
- package/registry/examples/iso/orochia-hero.d.ts +4 -0
- package/registry/examples/iso/orochia-hero.js +29 -0
- package/registry/examples/iso/orochia-hero.tsx +23 -0
- package/registry/examples/iso/still.d.ts +4 -0
- package/registry/examples/iso/still.js +21 -0
- package/registry/examples/iso/still.tsx +15 -0
- package/registry/examples/loader/endings.d.ts +4 -0
- package/registry/examples/loader/endings.js +14 -0
- package/registry/examples/loader/endings.tsx +11 -0
- package/registry/examples/loader/fetch-flow.d.ts +4 -0
- package/registry/examples/loader/fetch-flow.js +24 -0
- package/registry/examples/loader/fetch-flow.tsx +24 -0
- package/registry/examples/loader/minimal.d.ts +4 -0
- package/registry/examples/loader/minimal.js +11 -0
- package/registry/examples/loader/minimal.tsx +6 -0
- package/registry/examples/loader/overlay.d.ts +4 -0
- package/registry/examples/loader/overlay.js +16 -0
- package/registry/examples/loader/overlay.tsx +13 -0
- package/registry/examples/loader/region-handover.d.ts +4 -0
- package/registry/examples/loader/region-handover.js +42 -0
- package/registry/examples/loader/region-handover.tsx +29 -0
- package/registry/examples/loader/upload-eta.d.ts +4 -0
- package/registry/examples/loader/upload-eta.js +11 -0
- package/registry/examples/loader/upload-eta.tsx +10 -0
- package/registry/examples/marquee/cards.d.ts +4 -0
- package/registry/examples/marquee/cards.js +23 -0
- package/registry/examples/marquee/cards.tsx +21 -0
- package/registry/examples/marquee/logos.d.ts +4 -0
- package/registry/examples/marquee/logos.js +12 -0
- package/registry/examples/marquee/logos.tsx +17 -0
- package/registry/examples/media-capture/clip.d.ts +4 -0
- package/registry/examples/media-capture/clip.js +20 -0
- package/registry/examples/media-capture/clip.tsx +18 -0
- package/registry/examples/media-capture/native/photo.d.ts +4 -0
- package/registry/examples/media-capture/native/photo.dark.png +0 -0
- package/registry/examples/media-capture/native/photo.js +27 -0
- package/registry/examples/media-capture/native/photo.light.png +0 -0
- package/registry/examples/media-capture/native/photo.tsx +20 -0
- package/registry/examples/media-capture/profile-photo.d.ts +4 -0
- package/registry/examples/media-capture/profile-photo.js +24 -0
- package/registry/examples/media-capture/profile-photo.tsx +20 -0
- package/registry/examples/meter/balance.d.ts +4 -0
- package/registry/examples/meter/balance.js +14 -0
- package/registry/examples/meter/balance.tsx +15 -0
- package/registry/examples/meter/quota.d.ts +4 -0
- package/registry/examples/meter/quota.js +15 -0
- package/registry/examples/meter/quota.tsx +12 -0
- package/registry/examples/meter/split.d.ts +4 -0
- package/registry/examples/meter/split.js +18 -0
- package/registry/examples/meter/split.tsx +18 -0
- package/registry/examples/nav-bar/app-bar.d.ts +4 -0
- package/registry/examples/nav-bar/app-bar.js +22 -0
- package/registry/examples/nav-bar/app-bar.tsx +21 -0
- package/registry/examples/nav-bar/menu-open.d.ts +4 -0
- package/registry/examples/nav-bar/menu-open.js +26 -0
- package/registry/examples/nav-bar/menu-open.tsx +26 -0
- package/registry/examples/nav-bar/site.d.ts +4 -0
- package/registry/examples/nav-bar/site.js +31 -0
- package/registry/examples/nav-bar/site.tsx +36 -0
- package/registry/examples/number-ticker/gradient.d.ts +4 -0
- package/registry/examples/number-ticker/gradient.js +11 -0
- package/registry/examples/number-ticker/gradient.tsx +10 -0
- package/registry/examples/number-ticker/live-total.d.ts +4 -0
- package/registry/examples/number-ticker/live-total.js +17 -0
- package/registry/examples/number-ticker/live-total.tsx +21 -0
- package/registry/examples/number-ticker/stats.d.ts +4 -0
- package/registry/examples/number-ticker/stats.js +19 -0
- package/registry/examples/number-ticker/stats.tsx +23 -0
- package/registry/examples/pagination/cursor.d.ts +4 -0
- package/registry/examples/pagination/cursor.js +24 -0
- package/registry/examples/pagination/cursor.tsx +20 -0
- package/registry/examples/pagination/links.d.ts +4 -0
- package/registry/examples/pagination/links.js +11 -0
- package/registry/examples/pagination/links.tsx +6 -0
- package/registry/examples/pagination/native/cursor.d.ts +4 -0
- package/registry/examples/pagination/native/cursor.dark.png +0 -0
- package/registry/examples/pagination/native/cursor.js +19 -0
- package/registry/examples/pagination/native/cursor.light.png +0 -0
- package/registry/examples/pagination/native/cursor.tsx +13 -0
- package/registry/examples/pagination/native/pages.d.ts +4 -0
- package/registry/examples/pagination/native/pages.dark.png +0 -0
- package/registry/examples/pagination/native/pages.js +21 -0
- package/registry/examples/pagination/native/pages.light.png +0 -0
- package/registry/examples/pagination/native/pages.tsx +14 -0
- package/registry/examples/pagination/results.d.ts +4 -0
- package/registry/examples/pagination/results.js +23 -0
- package/registry/examples/pagination/results.tsx +19 -0
- package/registry/examples/pagination/table-footer.d.ts +4 -0
- package/registry/examples/pagination/table-footer.js +31 -0
- package/registry/examples/pagination/table-footer.tsx +27 -0
- package/registry/examples/progress/buffer.d.ts +4 -0
- package/registry/examples/progress/buffer.js +11 -0
- package/registry/examples/progress/buffer.tsx +10 -0
- package/registry/examples/progress/statuses.d.ts +4 -0
- package/registry/examples/progress/statuses.js +14 -0
- package/registry/examples/progress/statuses.tsx +11 -0
- package/registry/examples/progress/steps.d.ts +4 -0
- package/registry/examples/progress/steps.js +14 -0
- package/registry/examples/progress/steps.tsx +13 -0
- package/registry/examples/progress/upload.d.ts +4 -0
- package/registry/examples/progress/upload.js +11 -0
- package/registry/examples/progress/upload.tsx +10 -0
- package/registry/examples/search-field/global.d.ts +4 -0
- package/registry/examples/search-field/global.js +19 -0
- package/registry/examples/search-field/global.tsx +15 -0
- package/registry/examples/search-field/native/default.d.ts +4 -0
- package/registry/examples/search-field/native/default.dark.png +0 -0
- package/registry/examples/search-field/native/default.js +13 -0
- package/registry/examples/search-field/native/default.light.png +0 -0
- package/registry/examples/search-field/native/default.tsx +7 -0
- package/registry/examples/search-field/suggestions.d.ts +4 -0
- package/registry/examples/search-field/suggestions.js +30 -0
- package/registry/examples/search-field/suggestions.tsx +27 -0
- package/registry/examples/search-field/toolbar.d.ts +4 -0
- package/registry/examples/search-field/toolbar.js +17 -0
- package/registry/examples/search-field/toolbar.tsx +21 -0
- package/registry/examples/skeleton/paragraph.d.ts +4 -0
- package/registry/examples/skeleton/paragraph.js +14 -0
- package/registry/examples/skeleton/paragraph.tsx +12 -0
- package/registry/examples/skeleton/shape-of-content.d.ts +4 -0
- package/registry/examples/skeleton/shape-of-content.js +23 -0
- package/registry/examples/skeleton/shape-of-content.tsx +26 -0
- package/registry/examples/spinner/default.js +5 -2
- package/registry/examples/spinner/default.tsx +7 -1
- package/registry/examples/spinner/sizes.js +2 -1
- package/registry/examples/spinner/sizes.tsx +2 -0
- package/registry/examples/spinner/value.d.ts +4 -0
- package/registry/examples/spinner/value.js +11 -0
- package/registry/examples/spinner/value.tsx +12 -0
- package/registry/examples/stepper/checkout.d.ts +4 -0
- package/registry/examples/stepper/checkout.js +22 -0
- package/registry/examples/stepper/checkout.tsx +17 -0
- package/registry/examples/stepper/failed.d.ts +4 -0
- package/registry/examples/stepper/failed.js +26 -0
- package/registry/examples/stepper/failed.tsx +21 -0
- package/registry/examples/stepper/job.d.ts +4 -0
- package/registry/examples/stepper/job.js +32 -0
- package/registry/examples/stepper/job.tsx +28 -0
- package/registry/examples/tag-input/recipients.d.ts +4 -0
- package/registry/examples/tag-input/recipients.js +23 -0
- package/registry/examples/tag-input/recipients.tsx +20 -0
- package/registry/examples/tag-input/video-tags.d.ts +4 -0
- package/registry/examples/tag-input/video-tags.js +26 -0
- package/registry/examples/tag-input/video-tags.tsx +22 -0
- package/registry/examples/toast/grouped.d.ts +4 -0
- package/registry/examples/toast/grouped.js +21 -0
- package/registry/examples/toast/grouped.tsx +23 -0
- package/registry/examples/toast/undo.d.ts +4 -0
- package/registry/examples/toast/undo.js +29 -0
- package/registry/examples/toast/undo.tsx +30 -0
- package/registry/examples/toast/upload-progress.d.ts +4 -0
- package/registry/examples/toast/upload-progress.js +29 -0
- package/registry/examples/toast/upload-progress.tsx +31 -0
- package/registry/examples/video-player/default.d.ts +4 -0
- package/registry/examples/video-player/default.js +12 -0
- package/registry/examples/video-player/default.tsx +8 -0
- package/registry/examples/video-player/gated.d.ts +4 -0
- package/registry/examples/video-player/gated.js +26 -0
- package/registry/examples/video-player/gated.tsx +23 -0
- package/registry/examples/video-player/native/default.d.ts +4 -0
- package/registry/examples/video-player/native/default.dark.png +0 -0
- package/registry/examples/video-player/native/default.js +18 -0
- package/registry/examples/video-player/native/default.light.png +0 -0
- package/registry/examples/video-player/native/default.tsx +12 -0
- package/registry/examples/video-player/signed-source.d.ts +4 -0
- package/registry/examples/video-player/signed-source.js +16 -0
- package/registry/examples/video-player/signed-source.tsx +15 -0
- package/registry/examples/video-player/vertical.d.ts +4 -0
- package/registry/examples/video-player/vertical.js +12 -0
- package/registry/examples/video-player/vertical.tsx +12 -0
- package/registry/examples/video-trimmer/custom.d.ts +4 -0
- package/registry/examples/video-trimmer/custom.js +24 -0
- package/registry/examples/video-trimmer/custom.tsx +26 -0
- package/registry/examples/video-trimmer/filmstrip.d.ts +4 -0
- package/registry/examples/video-trimmer/filmstrip.js +15 -0
- package/registry/examples/video-trimmer/filmstrip.tsx +13 -0
- package/registry/examples/video-trimmer/playhead.d.ts +4 -0
- package/registry/examples/video-trimmer/playhead.js +15 -0
- package/registry/examples/video-trimmer/playhead.tsx +13 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +471 -0
- package/registry/filter-bar.json +347 -0
- package/registry/glow-card.json +162 -0
- package/registry/image-cropper.json +313 -0
- package/registry/index.json +445 -20
- package/registry/iso.json +1355 -0
- package/registry/kbd.json +1 -1
- package/registry/loader.json +399 -0
- package/registry/marquee.json +225 -0
- package/registry/mascots.json +3 -3
- package/registry/media-capture.json +347 -0
- package/registry/meter.json +324 -0
- package/registry/nav-bar.json +480 -0
- package/registry/number-ticker.json +219 -0
- package/registry/page-header.json +1 -1
- package/registry/pagination.json +475 -0
- package/registry/popover.json +1 -1
- package/registry/progress.json +114 -9
- package/registry/radio-group.json +1 -1
- package/registry/search-field.json +436 -0
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/skeleton.json +69 -13
- package/registry/slider.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/spinner.json +91 -27
- package/registry/stat.json +1 -1
- package/registry/stepper.json +174 -0
- package/registry/switch.json +1 -1
- package/registry/tabs.json +11 -7
- package/registry/tag-input.json +363 -0
- package/registry/theme.json +3 -3
- package/registry/toast.json +41 -13
- package/registry/tooltip.json +1 -1
- package/registry/video-player.json +598 -0
- package/registry/video-trimmer.json +385 -0
- package/dist/chunk-FHYZSO2S.js.map +0 -1
- package/dist/chunk-PESA2R4U.js +0 -51
- package/dist/chunk-PESA2R4U.js.map +0 -1
- package/registry/examples/spinner/muted.js +0 -11
- package/registry/examples/spinner/muted.tsx +0 -6
- /package/registry/examples/{spinner/muted.d.ts → audio-player/podcast.d.ts} +0 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "file-upload",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"title": "File upload",
|
|
5
|
+
"summary": "A drop zone that checks type, size and count, a list of uploads with preview, progress, speed and the actions each state allows, and a queue that pauses and resumes — Tus-ready without depending on it.",
|
|
6
|
+
"description": "A drop zone that checks type, size and count, a list of uploads with preview, progress, speed and the actions each state allows, and a queue that pauses and resumes — Tus-ready without depending on it.",
|
|
7
|
+
"status": "beta",
|
|
8
|
+
"category": "media",
|
|
9
|
+
"platforms": "web",
|
|
10
|
+
"whenToUse": [
|
|
11
|
+
"To send files: a video to publish, documents to index, pictures for a gallery.",
|
|
12
|
+
"With `useUploadQueue` for large files that must survive a pause, a failure or a slow network (Tus, multipart)."
|
|
13
|
+
],
|
|
14
|
+
"whenNotToUse": [
|
|
15
|
+
{
|
|
16
|
+
"when": "To take a picture or a clip with the camera in the page: media capture.",
|
|
17
|
+
"use": "media-capture"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"when": "To choose a picture's frame before sending it: the cropper, then the upload.",
|
|
21
|
+
"use": "image-cropper"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"bestPractices": [
|
|
25
|
+
"Say what is accepted in `hint` (“MP4 or MOV, up to 4 GB”) and enforce it with `accept` and `maxSize`.",
|
|
26
|
+
"Explain each refusal with `refusalLabel` (“clip.avi is not a supported format”).",
|
|
27
|
+
"Wrap your upload client in `upload(file, { signal, onProgress, resumed })`: abort on `signal`, resume from the offset when `resumed`.",
|
|
28
|
+
"Revoke previews you create yourself; `useUploadQueue` revokes its own."
|
|
29
|
+
],
|
|
30
|
+
"accessibility": {
|
|
31
|
+
"keyboard": [
|
|
32
|
+
{
|
|
33
|
+
"keys": "Enter / Space",
|
|
34
|
+
"action": "On the button: open the file picker (dropping is never the only way)."
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"notes": [
|
|
38
|
+
"The zone's button is described by the title and the hint; the file input itself stays out of the tab order.",
|
|
39
|
+
"Each upload's progress is a `progressbar` named by the file; a failure is announced (`role=\"alert\"`).",
|
|
40
|
+
"Every action is a button named with the file (“Pause clip.mp4”)."
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"related": [
|
|
44
|
+
"progress",
|
|
45
|
+
"media-capture",
|
|
46
|
+
"image-cropper",
|
|
47
|
+
"video-trimmer"
|
|
48
|
+
],
|
|
49
|
+
"web": {
|
|
50
|
+
"entry": "@krizaka/ui/file-upload",
|
|
51
|
+
"import": "import { Dropzone, UploadList, useUploadQueue, acceptsFile, formatBytes } from \"@krizaka/ui/file-upload\";",
|
|
52
|
+
"props": [
|
|
53
|
+
{
|
|
54
|
+
"component": "Dropzone",
|
|
55
|
+
"description": "A zone to drop files on, with a button that opens the picker: checks type, size and count.",
|
|
56
|
+
"props": [
|
|
57
|
+
{
|
|
58
|
+
"name": "browseLabel",
|
|
59
|
+
"type": "string",
|
|
60
|
+
"default": null,
|
|
61
|
+
"description": "The button that opens the file picker (\"Choose a file\"), passed translated.",
|
|
62
|
+
"required": true
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "onFiles",
|
|
66
|
+
"type": "(files: File[]) => void",
|
|
67
|
+
"default": null,
|
|
68
|
+
"description": "Called with the files accepted (never empty).",
|
|
69
|
+
"required": true
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "title",
|
|
73
|
+
"type": "string",
|
|
74
|
+
"default": null,
|
|
75
|
+
"description": "The title in the zone (\"Drop your video here\"), passed translated.",
|
|
76
|
+
"required": true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "accept",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"default": null,
|
|
82
|
+
"description": "The types accepted, as the `accept` attribute (`\"video/*\"`, `\"image/png,.pdf\"`).",
|
|
83
|
+
"required": false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "capture",
|
|
87
|
+
"type": "\"user\" | \"environment\"",
|
|
88
|
+
"default": null,
|
|
89
|
+
"description": "On a phone, opens the camera directly (`\"environment\"` the back one, `\"user\"` the front one).",
|
|
90
|
+
"required": false
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "className",
|
|
94
|
+
"type": "string",
|
|
95
|
+
"default": null,
|
|
96
|
+
"description": "Classes merged on the zone.",
|
|
97
|
+
"required": false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "disabled",
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"default": null,
|
|
103
|
+
"description": "Not available (an upload is running).",
|
|
104
|
+
"required": false
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "hint",
|
|
108
|
+
"type": "string",
|
|
109
|
+
"default": null,
|
|
110
|
+
"description": "What is accepted, in words (\"MP4 or MOV, up to 4 GB\").",
|
|
111
|
+
"required": false
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "icon",
|
|
115
|
+
"type": "ReactNode",
|
|
116
|
+
"default": null,
|
|
117
|
+
"description": "An icon instead of the upload glyph.",
|
|
118
|
+
"required": false
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "maxFiles",
|
|
122
|
+
"type": "number",
|
|
123
|
+
"default": null,
|
|
124
|
+
"description": "The most files at once (1 unless `multiple`).",
|
|
125
|
+
"required": false
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "maxSize",
|
|
129
|
+
"type": "number",
|
|
130
|
+
"default": null,
|
|
131
|
+
"description": "The largest file, in bytes.",
|
|
132
|
+
"required": false
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "multiple",
|
|
136
|
+
"type": "boolean",
|
|
137
|
+
"default": null,
|
|
138
|
+
"description": "Several files at once.",
|
|
139
|
+
"required": false
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "onReject",
|
|
143
|
+
"type": "((rejections: FileRejection[]) => void)",
|
|
144
|
+
"default": null,
|
|
145
|
+
"description": "Called with the files refused and why (show `refusalLabel`'s words, or your own).",
|
|
146
|
+
"required": false
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "refusalLabel",
|
|
150
|
+
"type": "((rejection: FileRejection) => string)",
|
|
151
|
+
"default": null,
|
|
152
|
+
"description": "The words of a refusal, shown in the zone: `(r) => r.file.name + \" is too large\"`. Without it, nothing is shown.",
|
|
153
|
+
"required": false
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"component": "UploadList",
|
|
159
|
+
"description": "The files being sent: preview, size, progress, speed and time left, and the actions each state allows.",
|
|
160
|
+
"props": [
|
|
161
|
+
{
|
|
162
|
+
"name": "items",
|
|
163
|
+
"type": "readonly UploadItem[]",
|
|
164
|
+
"default": null,
|
|
165
|
+
"description": "The uploads, as `useUploadQueue` gives them (or your own state).",
|
|
166
|
+
"required": true
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "className",
|
|
170
|
+
"type": "string",
|
|
171
|
+
"default": null,
|
|
172
|
+
"description": "Classes merged on the list.",
|
|
173
|
+
"required": false
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"name": "labels",
|
|
177
|
+
"type": "Partial<UploadListLabels>",
|
|
178
|
+
"default": null,
|
|
179
|
+
"description": "The words, passed translated; English by default.",
|
|
180
|
+
"required": false
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "locale",
|
|
184
|
+
"type": "string",
|
|
185
|
+
"default": "en",
|
|
186
|
+
"description": "The locale of sizes and speeds. Default `\"en\"`.",
|
|
187
|
+
"required": false
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"name": "onCancel",
|
|
191
|
+
"type": "((id: string) => void)",
|
|
192
|
+
"default": null,
|
|
193
|
+
"description": "Stops an upload under way and drops it.",
|
|
194
|
+
"required": false
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "onPause",
|
|
198
|
+
"type": "((id: string) => void)",
|
|
199
|
+
"default": null,
|
|
200
|
+
"description": "Pauses an upload (shown while it uploads, when given).",
|
|
201
|
+
"required": false
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "onRemove",
|
|
205
|
+
"type": "((id: string) => void)",
|
|
206
|
+
"default": null,
|
|
207
|
+
"description": "Drops a finished or failed one from the list.",
|
|
208
|
+
"required": false
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "onResume",
|
|
212
|
+
"type": "((id: string) => void)",
|
|
213
|
+
"default": null,
|
|
214
|
+
"description": "Resumes a paused one.",
|
|
215
|
+
"required": false
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "onRetry",
|
|
219
|
+
"type": "((id: string) => void)",
|
|
220
|
+
"default": null,
|
|
221
|
+
"description": "Tries a failed one again.",
|
|
222
|
+
"required": false
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
],
|
|
227
|
+
"examples": [
|
|
228
|
+
{
|
|
229
|
+
"name": "video",
|
|
230
|
+
"title": "Publish a video",
|
|
231
|
+
"description": "The zone and the queue together: drop, watch the progress, pause and resume.",
|
|
232
|
+
"path": "examples/file-upload/video.tsx",
|
|
233
|
+
"module": "@krizaka/ui/registry/examples/file-upload/video",
|
|
234
|
+
"code": "\"use client\";\n\nimport { Dropzone, UploadList, useUploadQueue } from \"@krizaka/ui/file-upload\";\n\n// A stand-in for your client (Tus, multipart…): it reports progress and stops when the signal aborts.\nfunction fakeUpload(file: File, { signal, onProgress }: { signal: AbortSignal; onProgress: (sent: number) => void }) {\n return new Promise<void>((resolve) => {\n let sent = 0;\n const timer = setInterval(() => {\n sent = Math.min(file.size, sent + file.size / 40);\n onProgress(sent);\n if (sent < file.size) return;\n clearInterval(timer);\n resolve();\n }, 150);\n signal.addEventListener(\"abort\", () => clearInterval(timer));\n });\n}\n\nexport default function FileUploadVideo() {\n const queue = useUploadQueue({ upload: fakeUpload });\n return (\n <div className=\"flex w-full max-w-lg flex-col gap-3\">\n <Dropzone\n title=\"Drop your video here\"\n browseLabel=\"Choose a video\"\n hint=\"MP4, MOV or WebM, up to 4 GB\"\n accept=\"video/*\"\n maxSize={4 * 1024 ** 3}\n onFiles={queue.add}\n refusalLabel={(r) => `${r.file.name}: ${r.reason === \"size\" ? \"larger than 4 GB\" : \"not a video\"}`}\n />\n {queue.items.length > 0 && <UploadList items={queue.items} onPause={queue.pause} onResume={queue.resume} onCancel={queue.cancel} onRemove={queue.remove} onRetry={queue.retry} />}\n </div>\n );\n}\n"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"name": "states",
|
|
238
|
+
"title": "Every state",
|
|
239
|
+
"description": "`UploadList` with an upload under way, a paused one, a failed one and a finished one.",
|
|
240
|
+
"path": "examples/file-upload/states.tsx",
|
|
241
|
+
"module": "@krizaka/ui/registry/examples/file-upload/states",
|
|
242
|
+
"code": "import { UploadList } from \"@krizaka/ui/file-upload\";\n\nconst noop = () => undefined;\n\n// Each state with the actions it allows: pause while it sends, resume, retry, remove.\nexport default function FileUploadStates() {\n return (\n <UploadList\n className=\"max-w-lg\"\n items={[\n { id: \"a\", name: \"sunset-timelapse.mp4\", size: 1_840_000_000, type: \"video/mp4\", progress: 0.42, status: \"uploading\", speed: 12_400_000 },\n { id: \"b\", name: \"behind-the-scenes.mov\", size: 620_000_000, type: \"video/quicktime\", progress: 0.71, status: \"paused\" },\n { id: \"c\", name: \"interview.mp4\", size: 240_000_000, type: \"video/mp4\", progress: 0.1, status: \"error\", error: \"The connection was lost. Your upload resumes where it stopped.\" },\n { id: \"d\", name: \"cover.jpg\", size: 820_000, type: \"image/jpeg\", progress: 1, status: \"done\" },\n ]}\n onPause={noop}\n onResume={noop}\n onRetry={noop}\n onCancel={noop}\n onRemove={noop}\n />\n );\n}\n"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "images",
|
|
246
|
+
"title": "Several pictures",
|
|
247
|
+
"description": "Up to six images of 10 MB; the refused ones are listed with their reason.",
|
|
248
|
+
"path": "examples/file-upload/images.tsx",
|
|
249
|
+
"module": "@krizaka/ui/registry/examples/file-upload/images",
|
|
250
|
+
"code": "\"use client\";\n\nimport { Dropzone } from \"@krizaka/ui/file-upload\";\nimport * as React from \"react\";\n\n// Up to six pictures of 10 MB; each refusal says why.\nexport default function FileUploadImages() {\n const [names, setNames] = React.useState<string[]>([]);\n return (\n <div className=\"flex w-full max-w-lg flex-col gap-2\">\n <Dropzone\n title=\"Add pictures to the gallery\"\n browseLabel=\"Choose pictures\"\n hint=\"JPEG, PNG or WebP — up to 6 pictures of 10 MB\"\n accept=\"image/jpeg,image/png,image/webp\"\n multiple\n maxFiles={6}\n maxSize={10 * 1024 ** 2}\n onFiles={(files) => setNames(files.map((f) => f.name))}\n refusalLabel={(r) => `${r.file.name}: ${{ type: \"not a JPEG, PNG or WebP\", size: \"larger than 10 MB\", count: \"more than 6 pictures\" }[r.reason]}`}\n />\n {names.length > 0 && <p className=\"text-sm text-fg-secondary\">Ready: {names.join(\", \")}</p>}\n </div>\n );\n}\n"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"name": "tus",
|
|
254
|
+
"title": "Tus, resumable",
|
|
255
|
+
"description": "`useUploadQueue` around a resumable client: abort on pause, continue from the offset on resume.",
|
|
256
|
+
"path": "examples/file-upload/tus.tsx",
|
|
257
|
+
"module": "@krizaka/ui/registry/examples/file-upload/tus",
|
|
258
|
+
"code": "\"use client\";\n\nimport { Dropzone, UploadList, useUploadQueue } from \"@krizaka/ui/file-upload\";\n\n// The shape of a Tus client (tus-js-client: new Upload(file, options), start(), abort()), kept out of the example.\ntype Resumable = { start(): void; abort(): void };\ndeclare function createTusUpload(file: File, events: { onProgress: (sent: number) => void; onSuccess: () => void; onError: (e: Error) => void }): Resumable;\n\nexport default function FileUploadTus() {\n const queue = useUploadQueue({\n concurrency: 1,\n // `resumed`: Tus finds the offset by itself (its fingerprint); a multipart client would skip the parts sent.\n upload: (file, { signal, onProgress }) =>\n new Promise((resolve, reject) => {\n const upload = createTusUpload(file, { onProgress, onSuccess: resolve, onError: reject });\n signal.addEventListener(\"abort\", () => upload.abort());\n upload.start();\n }),\n });\n return (\n <div className=\"flex w-full max-w-lg flex-col gap-3\">\n <Dropzone title=\"Drop documents to index\" browseLabel=\"Choose documents\" accept=\".pdf,.docx,.md\" multiple onFiles={queue.add} />\n <UploadList items={queue.items} onPause={queue.pause} onResume={queue.resume} onRetry={queue.retry} onCancel={queue.cancel} onRemove={queue.remove} />\n </div>\n );\n}\n"
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
"native": null,
|
|
263
|
+
"files": [
|
|
264
|
+
{
|
|
265
|
+
"path": "file-upload/file-upload.tsx",
|
|
266
|
+
"content": "// 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"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"path": "file-upload/index.ts",
|
|
270
|
+
"content": "/** @krizaka/ui/file-upload — `Dropzone`, `UploadList`, `useUploadQueue` (pause / resume through an AbortSignal: Tus-ready without depending on it), `acceptsFile`, `formatBytes`, `fileUpload` (the slots). Client (\"use client\"). */\nexport {\n acceptsFile,\n Dropzone,\n type DropzoneProps,\n type FileRejection,\n fileUpload,\n formatBytes,\n type UploadContext,\n type UploadItem,\n UploadList,\n type UploadListLabels,\n uploadListLabels,\n type UploadListProps,\n type UploadQueueOptions,\n type UploadStatus,\n useUploadQueue,\n} from \"./file-upload\";\n"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"path": "file-upload/meta.ts",
|
|
274
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"File upload\",\n summary: \"A drop zone that checks type, size and count, a list of uploads with preview, progress, speed and the actions each state allows, and a queue that pauses and resumes — Tus-ready without depending on it.\",\n status: \"beta\",\n category: \"media\",\n platforms: \"web\",\n whenToUse: [\n \"To send files: a video to publish, documents to index, pictures for a gallery.\",\n \"With `useUploadQueue` for large files that must survive a pause, a failure or a slow network (Tus, multipart).\",\n ],\n whenNotToUse: [\n { when: \"To take a picture or a clip with the camera in the page: media capture.\", use: \"media-capture\" },\n { when: \"To choose a picture's frame before sending it: the cropper, then the upload.\", use: \"image-cropper\" },\n ],\n bestPractices: [\n \"Say what is accepted in `hint` (“MP4 or MOV, up to 4 GB”) and enforce it with `accept` and `maxSize`.\",\n \"Explain each refusal with `refusalLabel` (“clip.avi is not a supported format”).\",\n \"Wrap your upload client in `upload(file, { signal, onProgress, resumed })`: abort on `signal`, resume from the offset when `resumed`.\",\n \"Revoke previews you create yourself; `useUploadQueue` revokes its own.\",\n ],\n accessibility: {\n keyboard: [{ keys: \"Enter / Space\", action: \"On the button: open the file picker (dropping is never the only way).\" }],\n notes: [\n \"The zone's button is described by the title and the hint; the file input itself stays out of the tab order.\",\n \"Each upload's progress is a `progressbar` named by the file; a failure is announced (`role=\\\"alert\\\"`).\",\n \"Every action is a button named with the file (“Pause clip.mp4”).\",\n ],\n },\n related: [\"progress\", \"media-capture\", \"image-cropper\", \"video-trimmer\"],\n web: {\n imports: [\"Dropzone\", \"UploadList\", \"useUploadQueue\", \"acceptsFile\", \"formatBytes\"],\n examples: [\n { name: \"video\", title: \"Publish a video\", description: \"The zone and the queue together: drop, watch the progress, pause and resume.\" },\n { name: \"states\", title: \"Every state\", description: \"`UploadList` with an upload under way, a paused one, a failed one and a finished one.\" },\n { name: \"images\", title: \"Several pictures\", description: \"Up to six images of 10 MB; the refused ones are listed with their reason.\" },\n { name: \"tus\", title: \"Tus, resumable\", description: \"`useUploadQueue` around a resumable client: abort on pause, continue from the offset on resume.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"path": "glyphs.tsx",
|
|
278
|
+
"content": "// The small line glyphs the data and media primitives draw (play, volume, search, chevrons…): decorative, 24-unit\n// box, `currentColor`, sized by the caller's class. Shared by the entries that need them; never exported on its own —\n// a product draws its own icons with @krizaka/icons.\nimport type * as React from \"react\";\n\ntype GlyphProps = { className?: string };\n\nfunction Glyph({ className = \"h-4 w-4\", fill, children }: GlyphProps & { fill?: boolean; children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n className={className}\n fill={fill ? \"currentColor\" : \"none\"}\n stroke={fill ? \"none\" : \"currentColor\"}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n focusable=\"false\"\n >\n {children}\n </svg>\n );\n}\n\nexport const PlayGlyph = (p: GlyphProps) => (\n <Glyph {...p} fill>\n <path d=\"M8 5.5v13a1 1 0 0 0 1.5.86l10.4-6.5a1 1 0 0 0 0-1.72L9.5 4.64A1 1 0 0 0 8 5.5Z\" />\n </Glyph>\n);\nexport const PauseGlyph = (p: GlyphProps) => (\n <Glyph {...p} fill>\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </Glyph>\n);\nexport const VolumeGlyph = ({ muted, ...p }: GlyphProps & { muted?: boolean }) => (\n <Glyph {...p}>\n <path d=\"M11 5 6 9H3v6h3l5 4V5Z\" />\n {muted ? <path d=\"m22 9-6 6m0-6 6 6\" /> : <path d=\"M15.5 8.5a5 5 0 0 1 0 7M18.5 5.5a9 9 0 0 1 0 13\" />}\n </Glyph>\n);\nexport const FullscreenGlyph = ({ exit, ...p }: GlyphProps & { exit?: boolean }) => (\n <Glyph {...p}>\n {exit ? <path d=\"M9 4v5H4m11-5v5h5M9 20v-5H4m11 5v-5h5\" /> : <path d=\"M4 9V4h5m6 0h5v5M4 15v5h5m11-5v5h-5\" />}\n </Glyph>\n);\nexport const CaptionsGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10.5a2 2 0 1 0 0 3M17 10.5a2 2 0 1 0 0 3\" />\n </Glyph>\n);\nexport const PipGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <rect x=\"12\" y=\"12\" width=\"6\" height=\"4\" rx=\"1\" />\n </Glyph>\n);\nexport const SearchGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n </Glyph>\n);\nexport const CloseGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </Glyph>\n);\nexport const ChevronGlyph = ({ direction = \"down\", ...p }: GlyphProps & { direction?: \"left\" | \"right\" | \"up\" | \"down\" }) => (\n <Glyph {...p}>\n <path d={{ left: \"m15 6-6 6 6 6\", right: \"m9 6 6 6-6 6\", up: \"m6 15 6-6 6 6\", down: \"m6 9 6 6 6-6\" }[direction]} />\n </Glyph>\n);\nexport const CheckGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M20 6 9 17l-5-5\" />\n </Glyph>\n);\nexport const PlusGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 5v14M5 12h14\" />\n </Glyph>\n);\nexport const CalendarGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"16\" rx=\"2\" />\n <path d=\"M3 10h18M8 3v4M16 3v4\" />\n </Glyph>\n);\nexport const UploadGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M12 16V4m0 0L7 9m5-5 5 5\" />\n <path d=\"M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\" />\n </Glyph>\n);\nexport const FileGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z\" />\n <path d=\"M14 3v5h5\" />\n </Glyph>\n);\nexport const CameraGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 8h3l2-3h6l2 3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1Z\" />\n <circle cx=\"12\" cy=\"13.5\" r=\"3.5\" />\n </Glyph>\n);\nexport const FlipGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 12a8 8 0 0 1 14-5.3M20 12a8 8 0 0 1-14 5.3\" />\n <path d=\"M18 3v4h-4M6 21v-4h4\" />\n </Glyph>\n);\nexport const RetryGlyph = (p: GlyphProps) => (\n <Glyph {...p}>\n <path d=\"M4 12a8 8 0 1 0 2.3-5.7L4 8.5\" />\n <path d=\"M4 4v4.5h4.5\" />\n </Glyph>\n);\nexport const SortGlyph = ({ direction, ...p }: GlyphProps & { direction?: \"ascending\" | \"descending\" }) => (\n <Glyph {...p}>\n {direction === \"ascending\" ? <path d=\"m7 14 5-5 5 5\" /> : direction === \"descending\" ? <path d=\"m7 10 5 5 5-5\" /> : <path d=\"m8 9 4-4 4 4M8 15l4 4 4-4\" />}\n </Glyph>\n);\n"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"path": "meta.ts",
|
|
282
|
+
"content": "// The documentation of a component, carried by its code: each primitive has a `meta.ts` beside its source\n// (`src/<name>/meta.ts`; a component that exists only in React Native: `src/native/meta/<name>.ts`). Never built into\n// the package: `scripts/build-registry.mjs` reads them into `registry/<name>.json`, which krizaka.com renders as\n// /docs/ui/<name>, and `scripts/registry.test.mjs` fails when one is missing or incomplete.\n//\n// Why a typed module and not JSDoc tags: the fields are lists and links (`related`, `whenNotToUse[].use`, examples),\n// which a type checks and a comment does not; the file is plain data (erasable TypeScript), so Node reads it without a\n// compiler, and an editor completes it.\n\n/** Every documented component: the web primitives (one entry of the package each) and the native-only ones. */\nexport type ComponentName =\n | \"alert\"\n | \"audio-player\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"card\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"cn\"\n | \"combobox\"\n | \"command\"\n | \"confirm-button\"\n | \"countdown\"\n | \"data-table\"\n | \"date-picker\"\n | \"dialog\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"glow-card\"\n | \"image-cropper\"\n | \"iso\"\n | \"kbd\"\n | \"loader\"\n | \"marquee\"\n | \"mascots\"\n | \"media-capture\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"page-header\"\n | \"pagination\"\n | \"popover\"\n | \"progress\"\n | \"radio-group\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"skeleton\"\n | \"slider\"\n | \"slot\"\n | \"spinner\"\n | \"stat\"\n | \"stepper\"\n | \"switch\"\n | \"tabs\"\n | \"tag-input\"\n | \"theme\"\n | \"toast\"\n | \"tooltip\"\n | \"txt\"\n | \"video-player\"\n | \"video-trimmer\";\n\n/** Where a component exists: `@krizaka/ui/<name>` (web), `@krizaka/ui/native` (React Native), or both. */\nexport type Platform = \"web\" | \"native\" | \"both\";\n\n/** `stable`: its API only changes in a major. `beta`: it may still change in a minor. */\nexport type Status = \"stable\" | \"beta\";\n\n/** The group of the catalogue. `media`: video, sound, camera, files, cropping. */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"layout\" | \"foundations\";\n\n/**\n * A named example: the file `registry/examples/<component>/<name>.tsx` (web) or\n * `registry/examples/<component>/native/<name>.tsx` (React Native) — at most 40 lines, imported by the stories and\n * shown (live, with its code) by the documentation.\n */\nexport interface Example {\n /** The file name, kebab-case (`primary`, `icon-button`). */\n readonly name: string;\n /** A short title (`Primary`, `Icon button`). */\n readonly title: string;\n /** One or two sentences: when this variant is the right one. */\n readonly description: string;\n}\n\n/** One platform's side of a component. */\nexport interface PlatformDocs {\n /** The names to import (`[\"Button\", \"IconButton\"]`): from `@krizaka/ui/<name>` on the web, `@krizaka/ui/native` in React Native. */\n readonly imports: readonly string[];\n /** The examples, in reading order; the first one is the component's preview. */\n readonly examples: readonly Example[];\n}\n\n/** The React Native side: the same concept, and what differs from the web API. */\nexport interface NativeDocs extends PlatformDocs {\n /**\n * The entry to import from when it is not `@krizaka/ui/native`: a component that needs an Expo module (an optional\n * peer) has its own, `@krizaka/ui/native/<name>`, so an app that does not use it never installs the module.\n */\n readonly entry?: string;\n /** What differs from the web API (`label` instead of children…); empty for a native-only component. */\n readonly differences: readonly string[];\n}\n\nexport interface KeyboardInteraction {\n /** The keys, as written on a keyboard: `Space`, `Enter`, `Arrow keys`, `Esc`, `Tab`. */\n readonly keys: string;\n /** What they do. */\n readonly action: string;\n}\n\nexport interface ComponentMeta {\n /** The display name: `Button`, `Dropdown menu`. */\n readonly title: string;\n /** One sentence: what it is. */\n readonly summary: string;\n readonly status: Status;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** When to reach for it: concrete situations, one per item. */\n readonly whenToUse: readonly string[];\n /** When not to — and what to use instead (`use`), when another component is the answer. */\n readonly whenNotToUse: readonly { readonly when: string; readonly use?: ComponentName }[];\n /** How to use it well: wording, placement, composition. */\n readonly bestPractices: readonly string[];\n readonly accessibility: {\n /** The keyboard, when it does more than Tab and Enter. */\n readonly keyboard: readonly KeyboardInteraction[];\n /** Roles, names and announcements: what the component does, what the app must provide. */\n readonly notes: readonly string[];\n };\n /** The components a reader of this page should know about. */\n readonly related: readonly ComponentName[];\n readonly web?: PlatformDocs;\n readonly native?: NativeDocs;\n}\n"
|
|
283
|
+
}
|
|
284
|
+
],
|
|
285
|
+
"dependencies": [
|
|
286
|
+
"@krizaka/intl@^0.1.0",
|
|
287
|
+
"tailwind-variants@^3.3.1"
|
|
288
|
+
],
|
|
289
|
+
"registryDependencies": [
|
|
290
|
+
"progress"
|
|
291
|
+
],
|
|
292
|
+
"demo": {
|
|
293
|
+
"path": "examples/file-upload/video.tsx",
|
|
294
|
+
"content": "\"use client\";\n\nimport { Dropzone, UploadList, useUploadQueue } from \"@krizaka/ui/file-upload\";\n\n// A stand-in for your client (Tus, multipart…): it reports progress and stops when the signal aborts.\nfunction fakeUpload(file: File, { signal, onProgress }: { signal: AbortSignal; onProgress: (sent: number) => void }) {\n return new Promise<void>((resolve) => {\n let sent = 0;\n const timer = setInterval(() => {\n sent = Math.min(file.size, sent + file.size / 40);\n onProgress(sent);\n if (sent < file.size) return;\n clearInterval(timer);\n resolve();\n }, 150);\n signal.addEventListener(\"abort\", () => clearInterval(timer));\n });\n}\n\nexport default function FileUploadVideo() {\n const queue = useUploadQueue({ upload: fakeUpload });\n return (\n <div className=\"flex w-full max-w-lg flex-col gap-3\">\n <Dropzone\n title=\"Drop your video here\"\n browseLabel=\"Choose a video\"\n hint=\"MP4, MOV or WebM, up to 4 GB\"\n accept=\"video/*\"\n maxSize={4 * 1024 ** 3}\n onFiles={queue.add}\n refusalLabel={(r) => `${r.file.name}: ${r.reason === \"size\" ? \"larger than 4 GB\" : \"not a video\"}`}\n />\n {queue.items.length > 0 && <UploadList items={queue.items} onPause={queue.pause} onResume={queue.resume} onCancel={queue.cancel} onRemove={queue.remove} onRetry={queue.retry} />}\n </div>\n );\n}\n"
|
|
295
|
+
},
|
|
296
|
+
"props": [
|
|
297
|
+
{
|
|
298
|
+
"component": "Dropzone",
|
|
299
|
+
"description": "A zone to drop files on, with a button that opens the picker: checks type, size and count.",
|
|
300
|
+
"props": [
|
|
301
|
+
{
|
|
302
|
+
"name": "browseLabel",
|
|
303
|
+
"type": "string",
|
|
304
|
+
"default": null,
|
|
305
|
+
"description": "The button that opens the file picker (\"Choose a file\"), passed translated.",
|
|
306
|
+
"required": true
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"name": "onFiles",
|
|
310
|
+
"type": "(files: File[]) => void",
|
|
311
|
+
"default": null,
|
|
312
|
+
"description": "Called with the files accepted (never empty).",
|
|
313
|
+
"required": true
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"name": "title",
|
|
317
|
+
"type": "string",
|
|
318
|
+
"default": null,
|
|
319
|
+
"description": "The title in the zone (\"Drop your video here\"), passed translated.",
|
|
320
|
+
"required": true
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"name": "accept",
|
|
324
|
+
"type": "string",
|
|
325
|
+
"default": null,
|
|
326
|
+
"description": "The types accepted, as the `accept` attribute (`\"video/*\"`, `\"image/png,.pdf\"`).",
|
|
327
|
+
"required": false
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"name": "capture",
|
|
331
|
+
"type": "\"user\" | \"environment\"",
|
|
332
|
+
"default": null,
|
|
333
|
+
"description": "On a phone, opens the camera directly (`\"environment\"` the back one, `\"user\"` the front one).",
|
|
334
|
+
"required": false
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "className",
|
|
338
|
+
"type": "string",
|
|
339
|
+
"default": null,
|
|
340
|
+
"description": "Classes merged on the zone.",
|
|
341
|
+
"required": false
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"name": "disabled",
|
|
345
|
+
"type": "boolean",
|
|
346
|
+
"default": null,
|
|
347
|
+
"description": "Not available (an upload is running).",
|
|
348
|
+
"required": false
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"name": "hint",
|
|
352
|
+
"type": "string",
|
|
353
|
+
"default": null,
|
|
354
|
+
"description": "What is accepted, in words (\"MP4 or MOV, up to 4 GB\").",
|
|
355
|
+
"required": false
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"name": "icon",
|
|
359
|
+
"type": "ReactNode",
|
|
360
|
+
"default": null,
|
|
361
|
+
"description": "An icon instead of the upload glyph.",
|
|
362
|
+
"required": false
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"name": "maxFiles",
|
|
366
|
+
"type": "number",
|
|
367
|
+
"default": null,
|
|
368
|
+
"description": "The most files at once (1 unless `multiple`).",
|
|
369
|
+
"required": false
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"name": "maxSize",
|
|
373
|
+
"type": "number",
|
|
374
|
+
"default": null,
|
|
375
|
+
"description": "The largest file, in bytes.",
|
|
376
|
+
"required": false
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"name": "multiple",
|
|
380
|
+
"type": "boolean",
|
|
381
|
+
"default": null,
|
|
382
|
+
"description": "Several files at once.",
|
|
383
|
+
"required": false
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"name": "onReject",
|
|
387
|
+
"type": "((rejections: FileRejection[]) => void)",
|
|
388
|
+
"default": null,
|
|
389
|
+
"description": "Called with the files refused and why (show `refusalLabel`'s words, or your own).",
|
|
390
|
+
"required": false
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"name": "refusalLabel",
|
|
394
|
+
"type": "((rejection: FileRejection) => string)",
|
|
395
|
+
"default": null,
|
|
396
|
+
"description": "The words of a refusal, shown in the zone: `(r) => r.file.name + \" is too large\"`. Without it, nothing is shown.",
|
|
397
|
+
"required": false
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"component": "UploadList",
|
|
403
|
+
"description": "The files being sent: preview, size, progress, speed and time left, and the actions each state allows.",
|
|
404
|
+
"props": [
|
|
405
|
+
{
|
|
406
|
+
"name": "items",
|
|
407
|
+
"type": "readonly UploadItem[]",
|
|
408
|
+
"default": null,
|
|
409
|
+
"description": "The uploads, as `useUploadQueue` gives them (or your own state).",
|
|
410
|
+
"required": true
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"name": "className",
|
|
414
|
+
"type": "string",
|
|
415
|
+
"default": null,
|
|
416
|
+
"description": "Classes merged on the list.",
|
|
417
|
+
"required": false
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"name": "labels",
|
|
421
|
+
"type": "Partial<UploadListLabels>",
|
|
422
|
+
"default": null,
|
|
423
|
+
"description": "The words, passed translated; English by default.",
|
|
424
|
+
"required": false
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"name": "locale",
|
|
428
|
+
"type": "string",
|
|
429
|
+
"default": "en",
|
|
430
|
+
"description": "The locale of sizes and speeds. Default `\"en\"`.",
|
|
431
|
+
"required": false
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"name": "onCancel",
|
|
435
|
+
"type": "((id: string) => void)",
|
|
436
|
+
"default": null,
|
|
437
|
+
"description": "Stops an upload under way and drops it.",
|
|
438
|
+
"required": false
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"name": "onPause",
|
|
442
|
+
"type": "((id: string) => void)",
|
|
443
|
+
"default": null,
|
|
444
|
+
"description": "Pauses an upload (shown while it uploads, when given).",
|
|
445
|
+
"required": false
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"name": "onRemove",
|
|
449
|
+
"type": "((id: string) => void)",
|
|
450
|
+
"default": null,
|
|
451
|
+
"description": "Drops a finished or failed one from the list.",
|
|
452
|
+
"required": false
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"name": "onResume",
|
|
456
|
+
"type": "((id: string) => void)",
|
|
457
|
+
"default": null,
|
|
458
|
+
"description": "Resumes a paused one.",
|
|
459
|
+
"required": false
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"name": "onRetry",
|
|
463
|
+
"type": "((id: string) => void)",
|
|
464
|
+
"default": null,
|
|
465
|
+
"description": "Tries a failed one again.",
|
|
466
|
+
"required": false
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
}
|
|
470
|
+
]
|
|
471
|
+
}
|