@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,598 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "video-player",
|
|
3
|
+
"type": "primitive",
|
|
4
|
+
"title": "Video player",
|
|
5
|
+
"summary": "A video with accessible controls — HLS through an hls.js loaded only when needed, signed sources resolved at the last moment, subtitles, speed, full screen, a pause when it leaves the screen.",
|
|
6
|
+
"description": "A video with accessible controls — HLS through an hls.js loaded only when needed, signed sources resolved at the last moment, subtitles, speed, full screen, a pause when it leaves the screen.",
|
|
7
|
+
"status": "beta",
|
|
8
|
+
"category": "media",
|
|
9
|
+
"platforms": "both",
|
|
10
|
+
"whenToUse": [
|
|
11
|
+
"To play a video on a page: a watch page, a lesson, a product tour, a creator's clip.",
|
|
12
|
+
"With `src` as a function when the address is signed and short-lived: it is fetched when needed, and again after an error.",
|
|
13
|
+
"With `overlay` to draw a gate over the video (sign in, unlock, age check) without forking the player."
|
|
14
|
+
],
|
|
15
|
+
"whenNotToUse": [
|
|
16
|
+
{
|
|
17
|
+
"when": "For a short decorative loop with no sound: `controls={false}`, `autoPlay`, `loop`, `muted` — or a still image under reduced motion."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"when": "For a sound without a picture: the audio player.",
|
|
21
|
+
"use": "audio-player"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"bestPractices": [
|
|
25
|
+
"Pass hls.js as `loadHls={() => import(\"hls.js\")}`: the module is downloaded only by browsers without native HLS (Chrome, Firefox), never by Safari or iOS.",
|
|
26
|
+
"Name the player by its content (`title=\"Video: Sunset over Montréal\"`) and pass every control's words in `labels`.",
|
|
27
|
+
"Use `preload=\"none\"` in a list of many videos, `metadata` (default) on a watch page.",
|
|
28
|
+
"Give subtitles (`tracks`, WebVTT): the C key and a button cycle through them.",
|
|
29
|
+
"In React Native, import it from `@krizaka/ui/native/video-player` (expo-video, an optional peer)."
|
|
30
|
+
],
|
|
31
|
+
"accessibility": {
|
|
32
|
+
"keyboard": [
|
|
33
|
+
{
|
|
34
|
+
"keys": "Space / K",
|
|
35
|
+
"action": "Play or pause (the player has the focus)."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"keys": "Arrow Left / Arrow Right",
|
|
39
|
+
"action": "Back or forward 5 seconds."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"keys": "J / L",
|
|
43
|
+
"action": "Back or forward 10 seconds."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"keys": "Arrow Up / Arrow Down",
|
|
47
|
+
"action": "Volume up or down."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"keys": "M · F · C",
|
|
51
|
+
"action": "Mute, full screen, subtitles."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"keys": "Home / End · 0–9",
|
|
55
|
+
"action": "Start, end, or a tenth of the video."
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"notes": [
|
|
59
|
+
"A `region` named by `title`, focusable; every button is named by a prop and says its state (“Pause”, “Unmute”).",
|
|
60
|
+
"Seek and volume are sliders (`@krizaka/ui/slider`) whose value is read as a time (“1:05 of 3:20”) or a percentage.",
|
|
61
|
+
"A failure is announced (`role=\"alert\"`) with a retry; waiting for data is a status (“Loading”).",
|
|
62
|
+
"Under reduced motion autoplay waits for a gesture and the controls appear without fading."
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"related": [
|
|
66
|
+
"audio-player",
|
|
67
|
+
"video-trimmer",
|
|
68
|
+
"slider",
|
|
69
|
+
"media-capture"
|
|
70
|
+
],
|
|
71
|
+
"web": {
|
|
72
|
+
"entry": "@krizaka/ui/video-player",
|
|
73
|
+
"import": "import { VideoPlayer } from \"@krizaka/ui/video-player\";",
|
|
74
|
+
"props": [
|
|
75
|
+
{
|
|
76
|
+
"component": "VideoPlayer",
|
|
77
|
+
"description": "A video player with accessible controls, HLS on demand, signed sources, subtitles, speed, full screen.",
|
|
78
|
+
"props": [
|
|
79
|
+
{
|
|
80
|
+
"name": "src",
|
|
81
|
+
"type": "string | (() => Promise<string>)",
|
|
82
|
+
"default": null,
|
|
83
|
+
"description": "The address of the video (MP4, WebM, or an HLS `.m3u8`), or a function that resolves a signed one when needed.",
|
|
84
|
+
"required": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "title",
|
|
88
|
+
"type": "string",
|
|
89
|
+
"default": null,
|
|
90
|
+
"description": "The accessible name of the player (\"Video: Sunset over Montréal\"), passed translated.",
|
|
91
|
+
"required": true
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "aspectRatio",
|
|
95
|
+
"type": "string",
|
|
96
|
+
"default": "16 / 9",
|
|
97
|
+
"description": "The width / height ratio of the frame. Default `\"16 / 9\"`; `\"9 / 16\"` for a vertical video.",
|
|
98
|
+
"required": false
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "autoPlay",
|
|
102
|
+
"type": "boolean",
|
|
103
|
+
"default": null,
|
|
104
|
+
"description": "Starts playing, muted (browsers refuse sound without a gesture) — not under reduced motion.",
|
|
105
|
+
"required": false
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "className",
|
|
109
|
+
"type": "string",
|
|
110
|
+
"default": null,
|
|
111
|
+
"description": "Classes merged on the root.",
|
|
112
|
+
"required": false
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "controls",
|
|
116
|
+
"type": "boolean",
|
|
117
|
+
"default": "true",
|
|
118
|
+
"description": "Shows the controls (default). `false`: a bare video (a background loop) — give it no sound.",
|
|
119
|
+
"required": false
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "labels",
|
|
123
|
+
"type": "Partial<VideoPlayerLabels>",
|
|
124
|
+
"default": null,
|
|
125
|
+
"description": "The words, passed translated; English by default.",
|
|
126
|
+
"required": false
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "loadHls",
|
|
130
|
+
"type": "(() => Promise<HlsModule>)",
|
|
131
|
+
"default": null,
|
|
132
|
+
"description": "Loads hls.js when the browser cannot play HLS itself: `() => import(\"hls.js\")`. Without it, HLS needs Safari.",
|
|
133
|
+
"required": false
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "loop",
|
|
137
|
+
"type": "boolean",
|
|
138
|
+
"default": null,
|
|
139
|
+
"description": "Plays again from the start at the end.",
|
|
140
|
+
"required": false
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "muted",
|
|
144
|
+
"type": "boolean",
|
|
145
|
+
"default": "false",
|
|
146
|
+
"description": "Starts muted.",
|
|
147
|
+
"required": false
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "onEnded",
|
|
151
|
+
"type": "(() => void)",
|
|
152
|
+
"default": null,
|
|
153
|
+
"description": "Called at the end.",
|
|
154
|
+
"required": false
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "onError",
|
|
158
|
+
"type": "((error: unknown) => void)",
|
|
159
|
+
"default": null,
|
|
160
|
+
"description": "Called when the video cannot play (a network failure, an expired address).",
|
|
161
|
+
"required": false
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "onPause",
|
|
165
|
+
"type": "(() => void)",
|
|
166
|
+
"default": null,
|
|
167
|
+
"description": "Called when playback pauses.",
|
|
168
|
+
"required": false
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "onPlay",
|
|
172
|
+
"type": "(() => void)",
|
|
173
|
+
"default": null,
|
|
174
|
+
"description": "Called when playback starts.",
|
|
175
|
+
"required": false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "onTimeUpdate",
|
|
179
|
+
"type": "((time: number) => void)",
|
|
180
|
+
"default": null,
|
|
181
|
+
"description": "Called as the time moves, in seconds (a few times a second).",
|
|
182
|
+
"required": false
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "overlay",
|
|
186
|
+
"type": "ReactNode",
|
|
187
|
+
"default": null,
|
|
188
|
+
"description": "Covers the video and hides the controls: a gate (sign in, unlock, age check) drawn by the app.",
|
|
189
|
+
"required": false
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "pauseWhenHidden",
|
|
193
|
+
"type": "boolean",
|
|
194
|
+
"default": "true",
|
|
195
|
+
"description": "Pauses when less than a quarter of the player is on screen. Default `true`.",
|
|
196
|
+
"required": false
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"name": "playbackRates",
|
|
200
|
+
"type": "readonly number[]",
|
|
201
|
+
"default": "[0.5, 1, 1.25, 1.5, 2] as const",
|
|
202
|
+
"description": "The speeds offered by the speed button. Default `[0.5, 1, 1.25, 1.5, 2]`.",
|
|
203
|
+
"required": false
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"name": "poster",
|
|
207
|
+
"type": "string",
|
|
208
|
+
"default": null,
|
|
209
|
+
"description": "The picture shown before playback.",
|
|
210
|
+
"required": false
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "preload",
|
|
214
|
+
"type": "\"metadata\" | \"none\" | \"auto\"",
|
|
215
|
+
"default": "metadata",
|
|
216
|
+
"description": "How much to fetch before playback: `none` (a list of many videos), `metadata` (default), `auto`.",
|
|
217
|
+
"required": false
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "tracks",
|
|
221
|
+
"type": "readonly VideoTrack[]",
|
|
222
|
+
"default": "[]",
|
|
223
|
+
"description": "Subtitles or captions (WebVTT): the C key and a button cycle through them.",
|
|
224
|
+
"required": false
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "type",
|
|
228
|
+
"type": "\"auto\" | \"file\" | \"hls\"",
|
|
229
|
+
"default": "auto",
|
|
230
|
+
"description": "`auto` (default): HLS when the address ends in `.m3u8`.",
|
|
231
|
+
"required": false
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"examples": [
|
|
237
|
+
{
|
|
238
|
+
"name": "default",
|
|
239
|
+
"title": "Default",
|
|
240
|
+
"description": "An MP4 with its poster and the full set of controls.",
|
|
241
|
+
"path": "examples/video-player/default.tsx",
|
|
242
|
+
"module": "@krizaka/ui/registry/examples/video-player/default",
|
|
243
|
+
"code": "import { VideoPlayer } from \"@krizaka/ui/video-player\";\n\nconst TOUR = \"https://www.krizaka.com/assets/orochia/tour\";\n\n// For HLS, add `loadHls={() => import(\"hls.js\")}`: only browsers without native HLS download it.\nexport default function VideoPlayerDefault() {\n return <VideoPlayer title=\"Video: an auction on Orochia\" src={`${TOUR}/alex-bid.mp4`} poster={`${TOUR}/alex-bid.jpg`} />;\n}\n"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"name": "signed-source",
|
|
247
|
+
"title": "Signed source",
|
|
248
|
+
"description": "`src` as a function: a short-lived address asked from the server on the first play.",
|
|
249
|
+
"path": "examples/video-player/signed-source.tsx",
|
|
250
|
+
"module": "@krizaka/ui/registry/examples/video-player/signed-source",
|
|
251
|
+
"code": "\"use client\";\n\nimport { VideoPlayer } from \"@krizaka/ui/video-player\";\n\nconst TOUR = \"https://www.krizaka.com/assets/orochia/tour\";\n\n// Your server signs a short-lived address for this viewer; it is asked on the first play, and again after an error.\nasync function signedAddress() {\n await new Promise((resolve) => setTimeout(resolve, 200));\n return `${TOUR}/elena-publish.mp4?token=demo&expires=300`;\n}\n\nexport default function VideoPlayerSignedSource() {\n return <VideoPlayer title=\"Video: the creator studio\" src={signedAddress} preload=\"none\" poster={`${TOUR}/elena-publish.jpg`} />;\n}\n"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"name": "vertical",
|
|
255
|
+
"title": "Vertical",
|
|
256
|
+
"description": "A 9:16 clip, muted, looping — autoplay waits under reduced motion.",
|
|
257
|
+
"path": "examples/video-player/vertical.tsx",
|
|
258
|
+
"module": "@krizaka/ui/registry/examples/video-player/vertical",
|
|
259
|
+
"code": "import { VideoPlayer } from \"@krizaka/ui/video-player\";\n\nconst TOUR = \"https://www.krizaka.com/assets/orochia/tour\";\n\n// A 9:16 clip: muted, looping; autoplay waits for a gesture under reduced motion.\nexport default function VideoPlayerVertical() {\n return (\n <div className=\"w-64\">\n <VideoPlayer title=\"Story: behind the scenes\" src={`${TOUR}/alex-story.mp4`} poster={`${TOUR}/alex-story.jpg`} aspectRatio=\"9 / 16\" autoPlay loop muted playbackRates={[]} />\n </div>\n );\n}\n"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"name": "gated",
|
|
263
|
+
"title": "With a gate",
|
|
264
|
+
"description": "`overlay`: the app draws its own gate over the video, the controls step aside.",
|
|
265
|
+
"path": "examples/video-player/gated.tsx",
|
|
266
|
+
"module": "@krizaka/ui/registry/examples/video-player/gated",
|
|
267
|
+
"code": "import { Button } from \"@krizaka/ui/button\";\nimport { VideoPlayer } from \"@krizaka/ui/video-player\";\n\nconst TOUR = \"https://www.krizaka.com/assets/orochia/tour\";\n\n// The app draws its gate over the video; the controls step aside until it opens.\nexport default function VideoPlayerGated() {\n return (\n <VideoPlayer\n title=\"Video: unlocked for supporters\"\n src={`${TOUR}/alex-unlock.mp4`}\n poster={`${TOUR}/alex-unlock.jpg`}\n preload=\"none\"\n overlay={\n <div className=\"flex h-full flex-col items-center justify-center gap-3 bg-scrim-strong p-6 text-center backdrop-blur-md\">\n <p className=\"text-base font-semibold text-fg-on-media\">Supporters only</p>\n <p className=\"max-w-xs text-sm text-fg-on-media\">Unlock this video with a tip of $5 or more.</p>\n <Button variant=\"primary\">Unlock for $5</Button>\n </div>\n }\n />\n );\n}\n"
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"native": {
|
|
272
|
+
"entry": "@krizaka/ui/native/video-player",
|
|
273
|
+
"import": "import { VideoPlayer } from \"@krizaka/ui/native/video-player\";",
|
|
274
|
+
"differences": [
|
|
275
|
+
"Its own entry, `@krizaka/ui/native/video-player`, on expo-video (an optional peer): an app that plays no video installs nothing.",
|
|
276
|
+
"The platform's controls (`nativeControls`, default `true`): accessible, full screen and picture in picture by the system.",
|
|
277
|
+
"`source` is an object (`uri`, `contentType`, `headers`); `paused` stops it off screen (a FlatList's viewability)."
|
|
278
|
+
],
|
|
279
|
+
"props": [
|
|
280
|
+
{
|
|
281
|
+
"component": "VideoPlayer",
|
|
282
|
+
"description": "A video on expo-video: the platform's own player and controls (accessible, full screen, picture in picture), the\nposter until the first frame, autoplay that waits under reduced motion, and `paused` to stop it off screen. The\nweb `VideoPlayer` draws its own controls; here the system's are the right ones.",
|
|
283
|
+
"props": [
|
|
284
|
+
{
|
|
285
|
+
"name": "source",
|
|
286
|
+
"type": "VideoPlayerSource",
|
|
287
|
+
"default": null,
|
|
288
|
+
"description": "The video. A new source replaces the current one.",
|
|
289
|
+
"required": true
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"name": "title",
|
|
293
|
+
"type": "string",
|
|
294
|
+
"default": null,
|
|
295
|
+
"description": "The accessible name of the player (\"Video: Sunset over Montréal\"), passed translated.",
|
|
296
|
+
"required": true
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"name": "aspectRatio",
|
|
300
|
+
"type": "number",
|
|
301
|
+
"default": "16 / 9",
|
|
302
|
+
"description": "The width / height ratio of the frame. Default 16 / 9.",
|
|
303
|
+
"required": false
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"name": "autoPlay",
|
|
307
|
+
"type": "boolean",
|
|
308
|
+
"default": "false",
|
|
309
|
+
"description": "Starts playing (muted unless `muted={false}`) — not when the system asks to reduce motion.",
|
|
310
|
+
"required": false
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"name": "contentFit",
|
|
314
|
+
"type": "VideoContentFit",
|
|
315
|
+
"default": "contain",
|
|
316
|
+
"description": "How the picture fills the frame: `contain` (default), `cover`, `fill`.",
|
|
317
|
+
"required": false
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"name": "fullscreen",
|
|
321
|
+
"type": "boolean",
|
|
322
|
+
"default": "true",
|
|
323
|
+
"description": "Allows full screen from the controls. Default `true`.",
|
|
324
|
+
"required": false
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"name": "loop",
|
|
328
|
+
"type": "boolean",
|
|
329
|
+
"default": "false",
|
|
330
|
+
"description": "Plays again from the start at the end.",
|
|
331
|
+
"required": false
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"name": "muted",
|
|
335
|
+
"type": "boolean",
|
|
336
|
+
"default": "false",
|
|
337
|
+
"description": "Starts muted. Default: `autoPlay`.",
|
|
338
|
+
"required": false
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"name": "nativeControls",
|
|
342
|
+
"type": "boolean",
|
|
343
|
+
"default": "true",
|
|
344
|
+
"description": "The platform's controls (default `true`: accessible, with full screen). `false` for a story or a background.",
|
|
345
|
+
"required": false
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"name": "onPlayingChange",
|
|
349
|
+
"type": "((playing: boolean) => void)",
|
|
350
|
+
"default": null,
|
|
351
|
+
"description": "Called when it starts or stops playing.",
|
|
352
|
+
"required": false
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"name": "paused",
|
|
356
|
+
"type": "boolean",
|
|
357
|
+
"default": "false",
|
|
358
|
+
"description": "Pauses while `true`: the app passes it when the video leaves the screen (a FlatList's viewability).",
|
|
359
|
+
"required": false
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"name": "pictureInPicture",
|
|
363
|
+
"type": "boolean",
|
|
364
|
+
"default": "false",
|
|
365
|
+
"description": "Allows picture in picture. Default `false`.",
|
|
366
|
+
"required": false
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"name": "poster",
|
|
370
|
+
"type": "ImageSourcePropType",
|
|
371
|
+
"default": null,
|
|
372
|
+
"description": "The picture shown until the first frame.",
|
|
373
|
+
"required": false
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"name": "style",
|
|
377
|
+
"type": "StyleProp<ViewStyle>",
|
|
378
|
+
"default": null,
|
|
379
|
+
"description": "Styles merged last, on the frame.",
|
|
380
|
+
"required": false
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
],
|
|
385
|
+
"examples": [
|
|
386
|
+
{
|
|
387
|
+
"name": "default",
|
|
388
|
+
"title": "Default",
|
|
389
|
+
"description": "An HLS source with its poster until the first frame.",
|
|
390
|
+
"path": "examples/video-player/native/default.tsx",
|
|
391
|
+
"module": "@krizaka/ui/registry/examples/video-player/native/default",
|
|
392
|
+
"code": "import { VideoPlayer } from \"@krizaka/ui/native/video-player\";\n\n// expo-video plays HLS natively on iOS and Android; the poster stays until the first frame.\nexport default function NativeVideoPlayerDefault() {\n return (\n <VideoPlayer\n title=\"Video: an auction on Orochia\"\n source={{ uri: \"https://www.krizaka.com/assets/orochia/tour/alex-bid.mp4\" }}\n poster={{ uri: \"https://www.krizaka.com/assets/orochia/tour/alex-bid.jpg\" }}\n />\n );\n}\n",
|
|
393
|
+
"screenshots": {
|
|
394
|
+
"dark": "examples/video-player/native/default.dark.png",
|
|
395
|
+
"light": "examples/video-player/native/default.light.png"
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
"files": [
|
|
401
|
+
{
|
|
402
|
+
"path": "glyphs.tsx",
|
|
403
|
+
"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"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"path": "media.ts",
|
|
407
|
+
"content": "// Shared by the data and media primitives (client): the clock format, the reduced-motion preference, the\n// in-view observer, a debounced callback. Never an entry of its own.\nimport * as React from \"react\";\n\n/** Seconds as a clock: `75` → `1:15`, `3725` → `1:02:05`. Negative or unknown values read `0:00`. */\nexport function formatClock(seconds: number): string {\n const s = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const rest = String(s % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${rest}` : `${m}:${rest}`;\n}\n\n/** Clamps `value` between `min` and `max`. */\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst REDUCE = \"(prefers-reduced-motion: reduce)\";\nconst subscribeReduce = (notify: () => void) => {\n if (typeof window === \"undefined\" || !window.matchMedia) return () => undefined;\n const query = window.matchMedia(REDUCE);\n query.addEventListener?.(\"change\", notify);\n return () => query.removeEventListener?.(\"change\", notify);\n};\n\n/** `true` when the person asked for less motion (`prefers-reduced-motion: reduce`); follows a change of the setting. */\nexport function usePrefersReducedMotion(): boolean {\n return React.useSyncExternalStore(\n subscribeReduce,\n () => (typeof window !== \"undefined\" && window.matchMedia ? window.matchMedia(REDUCE).matches : false),\n () => false,\n );\n}\n\n/** The latest value of a callback, for an effect or an event that must not re-subscribe when it changes. */\nexport function useLatest<T>(value: T) {\n const ref = React.useRef(value);\n React.useLayoutEffect(() => {\n ref.current = value;\n });\n return ref;\n}\n\n/** Calls `onChange(visible)` when at least `threshold` of the element enters or leaves the viewport. */\nexport function useInView(ref: React.RefObject<Element | null>, onChange: (visible: boolean) => void, enabled = true, threshold = 0.25) {\n const latest = useLatest(onChange);\n React.useEffect(() => {\n const element = ref.current;\n if (!enabled || !element || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(([entry]) => entry && latest.current(entry.isIntersecting), { threshold });\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, enabled, threshold, latest]);\n}\n\n/** A stable function that runs `callback` once `delay` ms have passed without a new call; `cancel()` drops the pending one. */\nexport function useDebouncedCallback<A extends unknown[]>(callback: (...args: A) => void, delay: number) {\n const latest = useLatest(callback);\n const [debounced] = React.useState(() => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let wait = delay;\n const run = (...args: A) => {\n clearTimeout(timer);\n if (wait <= 0) latest.current(...args);\n else timer = setTimeout(() => latest.current(...args), wait);\n };\n return Object.assign(run, {\n cancel: () => clearTimeout(timer),\n setDelay: (next: number) => {\n wait = next;\n },\n });\n });\n React.useEffect(() => debounced.setDelay(delay), [debounced, delay]);\n React.useEffect(() => () => debounced.cancel(), [debounced]);\n return debounced;\n}\n\n/** A value controlled by a prop when it is given, held inside otherwise; `set` calls `onChange` either way. */\nexport function useControllable<T>(value: T | undefined, defaultValue: T, onChange?: (next: T) => void) {\n const [inner, setInner] = React.useState(defaultValue);\n const controlled = value !== undefined;\n const current = controlled ? value : inner;\n const set = React.useCallback(\n (next: T) => {\n if (!controlled) setInner(next);\n onChange?.(next);\n },\n [controlled, onChange],\n );\n return [current, set] as const;\n}\n\n/** Starts a media element without an unhandled rejection (autoplay refused, the source replaced meanwhile). */\nexport function playSafely(media: HTMLMediaElement | null | undefined) {\n try {\n const started = media?.play() as Promise<void> | undefined;\n started?.catch?.(() => undefined);\n } catch {\n // Not playable here (no media support): the controls stay as they are.\n }\n}\n"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"path": "meta.ts",
|
|
411
|
+
"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"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"path": "video-player/index.ts",
|
|
415
|
+
"content": "/** @krizaka/ui/video-player — `VideoPlayer` (accessible controls, HLS through an optional hls.js loaded on demand, signed sources, subtitles, speed, full screen, pause off screen), `videoPlayer` (the slots). Client (\"use client\"). */\nexport { type HlsModule, VideoPlayer, videoPlayer, type VideoPlayerLabels, videoPlayerLabels, type VideoPlayerProps, type VideoTrack } from \"./video-player\";\n"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"path": "video-player/meta.ts",
|
|
419
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Video player\",\n summary: \"A video with accessible controls — HLS through an hls.js loaded only when needed, signed sources resolved at the last moment, subtitles, speed, full screen, a pause when it leaves the screen.\",\n status: \"beta\",\n category: \"media\",\n platforms: \"both\",\n whenToUse: [\n \"To play a video on a page: a watch page, a lesson, a product tour, a creator's clip.\",\n \"With `src` as a function when the address is signed and short-lived: it is fetched when needed, and again after an error.\",\n \"With `overlay` to draw a gate over the video (sign in, unlock, age check) without forking the player.\",\n ],\n whenNotToUse: [\n { when: \"For a short decorative loop with no sound: `controls={false}`, `autoPlay`, `loop`, `muted` — or a still image under reduced motion.\" },\n { when: \"For a sound without a picture: the audio player.\", use: \"audio-player\" },\n ],\n bestPractices: [\n \"Pass hls.js as `loadHls={() => import(\\\"hls.js\\\")}`: the module is downloaded only by browsers without native HLS (Chrome, Firefox), never by Safari or iOS.\",\n \"Name the player by its content (`title=\\\"Video: Sunset over Montréal\\\"`) and pass every control's words in `labels`.\",\n \"Use `preload=\\\"none\\\"` in a list of many videos, `metadata` (default) on a watch page.\",\n \"Give subtitles (`tracks`, WebVTT): the C key and a button cycle through them.\",\n \"In React Native, import it from `@krizaka/ui/native/video-player` (expo-video, an optional peer).\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Space / K\", action: \"Play or pause (the player has the focus).\" },\n { keys: \"Arrow Left / Arrow Right\", action: \"Back or forward 5 seconds.\" },\n { keys: \"J / L\", action: \"Back or forward 10 seconds.\" },\n { keys: \"Arrow Up / Arrow Down\", action: \"Volume up or down.\" },\n { keys: \"M · F · C\", action: \"Mute, full screen, subtitles.\" },\n { keys: \"Home / End · 0–9\", action: \"Start, end, or a tenth of the video.\" },\n ],\n notes: [\n \"A `region` named by `title`, focusable; every button is named by a prop and says its state (“Pause”, “Unmute”).\",\n \"Seek and volume are sliders (`@krizaka/ui/slider`) whose value is read as a time (“1:05 of 3:20”) or a percentage.\",\n \"A failure is announced (`role=\\\"alert\\\"`) with a retry; waiting for data is a status (“Loading”).\",\n \"Under reduced motion autoplay waits for a gesture and the controls appear without fading.\",\n ],\n },\n related: [\"audio-player\", \"video-trimmer\", \"slider\", \"media-capture\"],\n web: {\n imports: [\"VideoPlayer\"],\n examples: [\n { name: \"default\", title: \"Default\", description: \"An MP4 with its poster and the full set of controls.\" },\n { name: \"signed-source\", title: \"Signed source\", description: \"`src` as a function: a short-lived address asked from the server on the first play.\" },\n { name: \"vertical\", title: \"Vertical\", description: \"A 9:16 clip, muted, looping — autoplay waits under reduced motion.\" },\n { name: \"gated\", title: \"With a gate\", description: \"`overlay`: the app draws its own gate over the video, the controls step aside.\" },\n ],\n },\n native: {\n entry: \"@krizaka/ui/native/video-player\",\n imports: [\"VideoPlayer\"],\n differences: [\n \"Its own entry, `@krizaka/ui/native/video-player`, on expo-video (an optional peer): an app that plays no video installs nothing.\",\n \"The platform's controls (`nativeControls`, default `true`): accessible, full screen and picture in picture by the system.\",\n \"`source` is an object (`uri`, `contentType`, `headers`); `paused` stops it off screen (a FlatList's viewability).\",\n ],\n examples: [{ name: \"default\", title: \"Default\", description: \"An HLS source with its poster until the first frame.\" }],\n },\n} satisfies ComponentMeta;\n"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"path": "video-player/video-player.tsx",
|
|
423
|
+
"content": "// Client. A `<video>` with its own controls, accessible from end to end: a named region the keyboard reaches (Space /\n// K, arrows, J / L, M, F, C, Home / End, 0–9), every button named by a prop, the seek and volume sliders of\n// `@krizaka/ui/slider` with the time read aloud. HLS plays natively where the browser can (Safari, iOS) and through\n// hls.js elsewhere — loaded only then, by the app's `loadHls` (`() => import(\"hls.js\")`): an optional peer, never in\n// the bundle of a page that does not play HLS. A signed address can be resolved at the last moment (`src` as a\n// function), again after an error; the video pauses when it leaves the screen; autoplay waits under reduced motion.\nimport * as React from \"react\";\nimport { tv } from \"tailwind-variants\";\n\nimport { CaptionsGlyph, FullscreenGlyph, PauseGlyph, PipGlyph, PlayGlyph, RetryGlyph, VolumeGlyph } from \"../glyphs\";\nimport { clamp, formatClock, playSafely, useInView, usePrefersReducedMotion } from \"../media\";\nimport { Slider } from \"../slider\";\n\nexport const videoPlayer = tv({\n slots: {\n root:\n \"theme-dark group/player relative isolate w-full overflow-hidden rounded-xl bg-media text-fg-on-media \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[idle]:cursor-none\",\n video: \"absolute inset-0 h-full w-full object-contain\",\n poster: \"pointer-events-none absolute inset-0 h-full w-full object-contain\",\n big:\n \"absolute left-1/2 top-1/2 z-10 inline-flex h-16 w-16 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full \" +\n \"bg-scrim-strong text-fg-on-media shadow-lg backdrop-blur-sm transition-transform hover:scale-105 motion-reduce:transition-none motion-reduce:hover:scale-100 \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\",\n bar:\n \"absolute inset-x-0 bottom-0 z-20 flex flex-col gap-1 bg-linear-to-t from-scrim-strong via-scrim to-transparent px-3 pb-2 pt-10 \" +\n \"transition-opacity duration-300 motion-reduce:transition-none group-data-[idle]/player:opacity-0 group-focus-within/player:opacity-100\",\n row: \"flex items-center gap-1\",\n button:\n \"inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1 rounded-md px-1.5 text-xs font-semibold text-fg-on-media \" +\n \"transition-colors hover:bg-fg-on-media/15 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring aria-pressed:bg-fg-on-media/20\",\n time: \"px-1.5 font-mono text-[11px] tabular-nums text-fg-on-media\",\n volume: \"hidden w-20 items-center sm:flex\",\n status: \"absolute inset-0 z-30 flex flex-col items-center justify-center gap-3 bg-scrim-strong p-6 text-center text-sm\",\n spinner: \"h-10 w-10 animate-spin rounded-full border-2 border-fg-on-media/30 border-t-fg-on-media motion-reduce:animate-none\",\n overlay: \"absolute inset-0 z-30\",\n },\n});\n\n/** What `loadHls` resolves to: the module of hls.js (only the members the player uses are typed). */\nexport type HlsModule = {\n default: {\n new (config?: Record<string, unknown>): {\n loadSource(url: string): void;\n attachMedia(media: HTMLMediaElement): void;\n destroy(): void;\n startLoad(): void;\n recoverMediaError(): void;\n on(event: string, listener: (event: string, data: { fatal?: boolean; type?: string }) => void): void;\n };\n isSupported(): boolean;\n Events: { ERROR: string };\n ErrorTypes: { NETWORK_ERROR: string; MEDIA_ERROR: string };\n };\n};\n\nexport type VideoTrack = {\n /** The WebVTT file. */\n src: string;\n /** Its language, BCP 47 (`\"fr\"`). */\n srcLang: string;\n /** Its name in the menu (\"Français\"), passed translated. */\n label: string;\n /** `subtitles` (default) or `captions` (with the sounds, for deaf viewers). */\n kind?: \"subtitles\" | \"captions\";\n /** Shown from the start. */\n default?: boolean;\n};\n\nexport type VideoPlayerLabels = {\n play: string;\n pause: string;\n mute: string;\n unmute: string;\n volume: string;\n seek: string;\n /** The speed button: `(rate) => \"Speed \" + rate + \"×\"`. */\n speed: (rate: number) => string;\n /** The captions button: `(track) => \"Subtitles: \" + (track ?? \"off\")`, the track's label or `null` when off. */\n captions: (track: string | null) => string;\n fullscreen: string;\n exitFullscreen: string;\n pictureInPicture: string;\n /** Read while the video waits for data. */\n loading: string;\n /** The video cannot play. */\n error: string;\n /** Tries again (resolves a signed `src` again). */\n retry: string;\n /** The seek slider's value: `(time, duration) => \"1:05 of 3:20\"`. */\n time: (time: string, duration: string) => string;\n};\n\nexport const videoPlayerLabels: VideoPlayerLabels = {\n play: \"Play\",\n pause: \"Pause\",\n mute: \"Mute\",\n unmute: \"Unmute\",\n volume: \"Volume\",\n seek: \"Seek\",\n speed: (rate) => `Speed ${rate}×`,\n captions: (track) => `Subtitles: ${track ?? \"off\"}`,\n fullscreen: \"Full screen\",\n exitFullscreen: \"Exit full screen\",\n pictureInPicture: \"Picture in picture\",\n loading: \"Loading\",\n error: \"This video cannot be played.\",\n retry: \"Try again\",\n time: (time, duration) => `${time} of ${duration}`,\n};\n\nexport type VideoPlayerProps = {\n /** The address of the video (MP4, WebM, or an HLS `.m3u8`), or a function that resolves a signed one when needed. */\n src: string | (() => Promise<string>);\n /** `auto` (default): HLS when the address ends in `.m3u8`. */\n type?: \"auto\" | \"hls\" | \"file\";\n /** Loads hls.js when the browser cannot play HLS itself: `() => import(\"hls.js\")`. Without it, HLS needs Safari. */\n loadHls?: () => Promise<HlsModule>;\n /** The accessible name of the player (\"Video: Sunset over Montréal\"), passed translated. */\n title: string;\n /** The picture shown before playback. */\n poster?: string;\n /** Starts playing, muted (browsers refuse sound without a gesture) — not under reduced motion. */\n autoPlay?: boolean;\n /** Starts muted. */\n muted?: boolean;\n /** Plays again from the start at the end. */\n loop?: boolean;\n /** How much to fetch before playback: `none` (a list of many videos), `metadata` (default), `auto`. */\n preload?: \"none\" | \"metadata\" | \"auto\";\n /** Subtitles or captions (WebVTT): the C key and a button cycle through them. */\n tracks?: readonly VideoTrack[];\n /** The speeds offered by the speed button. Default `[0.5, 1, 1.25, 1.5, 2]`. */\n playbackRates?: readonly number[];\n /** Pauses when less than a quarter of the player is on screen. Default `true`. */\n pauseWhenHidden?: boolean;\n /** Shows the controls (default). `false`: a bare video (a background loop) — give it no sound. */\n controls?: boolean;\n /** Covers the video and hides the controls: a gate (sign in, unlock, age check) drawn by the app. */\n overlay?: React.ReactNode;\n /** The width / height ratio of the frame. Default `\"16 / 9\"`; `\"9 / 16\"` for a vertical video. */\n aspectRatio?: string;\n /** Called when playback starts. */\n onPlay?: () => void;\n /** Called when playback pauses. */\n onPause?: () => void;\n /** Called at the end. */\n onEnded?: () => void;\n /** Called as the time moves, in seconds (a few times a second). */\n onTimeUpdate?: (time: number) => void;\n /** Called when the video cannot play (a network failure, an expired address). */\n onError?: (error: unknown) => void;\n /** The words, passed translated; English by default. */\n labels?: Partial<VideoPlayerLabels>;\n /** Classes merged on the root. */\n className?: string;\n};\n\nconst RATES = [0.5, 1, 1.25, 1.5, 2] as const;\nconst isHls = (url: string, type: VideoPlayerProps[\"type\"]) => type === \"hls\" || (type !== \"file\" && /\\.m3u8(\\?|#|$)/i.test(url));\n\n/** Shows the text track `index` of a video (and hides the others); -1 hides them all. */\nfunction showTrack(element: HTMLVideoElement, index: number) {\n const list = element.textTracks;\n for (let i = 0; i < list.length; i += 1) list[i]!.mode = i === index ? \"showing\" : \"disabled\";\n}\n\n/** A video player with accessible controls, HLS on demand, signed sources, subtitles, speed, full screen. */\nexport function VideoPlayer({\n src,\n type = \"auto\",\n loadHls,\n title,\n poster,\n autoPlay,\n muted: initiallyMuted = false,\n loop,\n preload = \"metadata\",\n tracks = [],\n playbackRates = RATES,\n pauseWhenHidden = true,\n controls = true,\n overlay,\n aspectRatio = \"16 / 9\",\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n onError,\n labels: given,\n className,\n}: VideoPlayerProps) {\n const labels = { ...videoPlayerLabels, ...given };\n const s = videoPlayer();\n const root = React.useRef<HTMLDivElement>(null);\n const video = React.useRef<HTMLVideoElement>(null);\n const reduce = usePrefersReducedMotion();\n const [resolved, setResolved] = React.useState<{ attempt: number; url: string } | null>(null);\n const [attempt, setAttempt] = React.useState(0);\n const [wanted, setWanted] = React.useState(false);\n const [playing, setPlaying] = React.useState(false);\n const [time, setTime] = React.useState(0);\n const [duration, setDuration] = React.useState(0);\n const [volume, setVolume] = React.useState(1);\n const [muted, setMuted] = React.useState(initiallyMuted || Boolean(autoPlay));\n const [rate, setRate] = React.useState(1);\n const [caption, setCaption] = React.useState(() => tracks.findIndex((t) => t.default));\n const [fullscreen, setFullscreen] = React.useState(false);\n const [waiting, setWaiting] = React.useState(false);\n const [failed, setFailed] = React.useState(false);\n const [idle, setIdle] = React.useState(false);\n // The poster is an image over the video until the first play: it loads like any image (lazy, decoded once), the\n // same in every browser — the `poster` attribute is painted late and differently.\n const [started, setStarted] = React.useState(false);\n const idleTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const url = typeof src === \"string\" ? src : resolved?.attempt === attempt ? resolved.url : null;\n const needSource = typeof src === \"function\" && (preload !== \"none\" || wanted || (autoPlay && !reduce));\n\n // A signed source is resolved when it is needed, and again on \"Try again\".\n React.useEffect(() => {\n if (typeof src !== \"function\" || !needSource) return;\n let live = true;\n src().then(\n (next) => live && setResolved({ attempt, url: next }),\n (error: unknown) => {\n if (!live) return;\n setFailed(true);\n onError?.(error);\n },\n );\n return () => {\n live = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `onError` is a callback, not a reason to resolve again\n }, [src, needSource, attempt]);\n\n // Attaches the source: natively, or through hls.js when the browser needs it.\n React.useEffect(() => {\n const element = video.current;\n if (!element || !url) return;\n let hls: InstanceType<HlsModule[\"default\"]> | null = null;\n let live = true;\n const fail = (error: unknown) => {\n if (!live) return;\n setFailed(true);\n onError?.(error);\n };\n if (isHls(url, type) && !element.canPlayType(\"application/vnd.apple.mpegurl\")) {\n if (!loadHls) fail(new Error(\"VideoPlayer: HLS needs `loadHls` in this browser.\"));\n else\n loadHls().then(({ default: Hls }) => {\n if (!live) return;\n if (!Hls.isSupported()) return fail(new Error(\"VideoPlayer: HLS is not supported here.\"));\n hls = new Hls({ enableWorker: true, backBufferLength: 60 });\n hls.on(Hls.Events.ERROR, (_event, data) => {\n if (!data.fatal) return;\n if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls?.startLoad();\n else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls?.recoverMediaError();\n else fail(data);\n });\n hls.loadSource(url);\n hls.attachMedia(element);\n }, fail);\n } else element.src = url;\n return () => {\n live = false;\n hls?.destroy();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- `onError` and `loadHls` are callbacks\n }, [url, type]);\n\n // Plays once the source is there, when it was asked for (a click before a signed source resolved, an autoplay).\n React.useEffect(() => {\n const element = video.current;\n if (!element || !url || !(wanted || (autoPlay && !reduce))) return;\n playSafely(element);\n }, [url, wanted, autoPlay, reduce]);\n\n React.useEffect(() => {\n if (video.current) showTrack(video.current, caption);\n }, [caption, tracks.length]);\n\n React.useEffect(() => {\n const change = () => setFullscreen(document.fullscreenElement === root.current);\n document.addEventListener(\"fullscreenchange\", change);\n return () => document.removeEventListener(\"fullscreenchange\", change);\n }, []);\n React.useEffect(() => () => clearTimeout(idleTimer.current), []);\n\n useInView(root, (visible) => !visible && video.current && !video.current.paused && video.current.pause(), pauseWhenHidden);\n\n const play = () => {\n setWanted(true);\n setFailed(false);\n playSafely(video.current);\n };\n const toggle = () => (playing ? video.current?.pause() : play());\n const seek = (to: number) => {\n if (!video.current) return;\n video.current.currentTime = clamp(to, 0, duration || video.current.duration || 0);\n setTime(video.current.currentTime);\n };\n const changeVolume = (to: number) => {\n if (!video.current) return;\n const next = clamp(to, 0, 1);\n video.current.volume = next;\n video.current.muted = next === 0;\n setVolume(next);\n setMuted(next === 0);\n };\n const toggleMute = () => {\n if (!video.current) return;\n if (muted && volume === 0) changeVolume(0.5);\n video.current.muted = !muted;\n setMuted(!muted);\n };\n const cycleRate = () => {\n const next = playbackRates[(playbackRates.indexOf(rate) + 1) % playbackRates.length] ?? 1;\n if (video.current) video.current.playbackRate = next;\n setRate(next);\n };\n const cycleCaption = () => tracks.length > 0 && setCaption((c) => (c + 2) % (tracks.length + 1) - 1);\n const toggleFullscreen = () => {\n const element = root.current;\n if (!element) return;\n if (document.fullscreenElement) void document.exitFullscreen();\n else if (element.requestFullscreen) void element.requestFullscreen();\n else (video.current as HTMLVideoElement & { webkitEnterFullscreen?: () => void })?.webkitEnterFullscreen?.();\n };\n const pip = () => {\n if (document.pictureInPictureElement) void document.exitPictureInPicture();\n else void video.current?.requestPictureInPicture?.();\n };\n const wake = () => {\n setIdle(false);\n clearTimeout(idleTimer.current);\n if (playing) idleTimer.current = setTimeout(() => setIdle(true), 2500);\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n const target = event.target as HTMLElement;\n const inControl = target.getAttribute(\"role\") === \"slider\";\n const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;\n const now = video.current?.currentTime ?? 0;\n const actions: Record<string, (() => void) | undefined> = {\n \" \": target.tagName === \"BUTTON\" ? undefined : toggle,\n k: toggle,\n j: () => seek(now - 10),\n l: () => seek(now + 10),\n m: toggleMute,\n f: toggleFullscreen,\n c: tracks.length ? cycleCaption : undefined,\n ArrowLeft: inControl ? undefined : () => seek(now - 5),\n ArrowRight: inControl ? undefined : () => seek(now + 5),\n ArrowUp: inControl ? undefined : () => changeVolume(volume + 0.1),\n ArrowDown: inControl ? undefined : () => changeVolume(volume - 0.1),\n Home: inControl ? undefined : () => seek(0),\n End: inControl ? undefined : () => seek(duration),\n };\n const action = /^[0-9]$/.test(key) && !inControl ? () => seek((Number(key) / 10) * duration) : actions[key];\n if (!action || event.metaKey || event.ctrlKey || event.altKey) return;\n event.preventDefault();\n action();\n wake();\n };\n\n const showControls = controls && !overlay;\n const pipSupported = typeof document !== \"undefined\" && \"pictureInPictureEnabled\" in document && document.pictureInPictureEnabled;\n\n return (\n <div\n ref={root}\n role=\"region\"\n aria-label={title}\n tabIndex={showControls ? 0 : undefined}\n data-idle={idle && playing ? \"\" : undefined}\n data-state={playing ? \"playing\" : \"paused\"}\n className={s.root({ className })}\n style={{ aspectRatio }}\n onKeyDown={showControls ? onKeyDown : undefined}\n onPointerMove={showControls ? wake : undefined}\n onPointerLeave={() => playing && setIdle(true)}\n >\n <video\n ref={video}\n className={s.video()}\n preload={preload}\n loop={loop}\n muted={muted}\n playsInline\n crossOrigin={tracks.length ? \"anonymous\" : undefined}\n onClick={showControls ? toggle : undefined}\n onPlay={() => {\n setStarted(true);\n setPlaying(true);\n wake();\n onPlay?.();\n }}\n onPause={() => {\n setPlaying(false);\n setIdle(false);\n onPause?.();\n }}\n onEnded={() => onEnded?.()}\n onTimeUpdate={(event) => {\n setTime(event.currentTarget.currentTime);\n onTimeUpdate?.(event.currentTarget.currentTime);\n }}\n onDurationChange={(event) => setDuration(Number.isFinite(event.currentTarget.duration) ? event.currentTarget.duration : 0)}\n onWaiting={() => setWaiting(true)}\n onPlaying={() => setWaiting(false)}\n onCanPlay={() => setWaiting(false)}\n onError={(event) => {\n setFailed(true);\n onError?.(event.currentTarget.error);\n }}\n >\n {tracks.map((track) => (\n <track key={track.src} src={track.src} srcLang={track.srcLang} label={track.label} kind={track.kind ?? \"subtitles\"} default={track.default} />\n ))}\n </video>\n\n {poster && !started ? <img src={poster} alt=\"\" aria-hidden decoding=\"async\" className={s.poster()} /> : null}\n\n {showControls && !playing && !failed && !waiting && (\n <button type=\"button\" className={s.big()} aria-label={labels.play} onClick={play}>\n <PlayGlyph className=\"ml-1 h-7 w-7\" />\n </button>\n )}\n\n {waiting && !failed && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center\">\n <span className={s.spinner()} aria-hidden />\n <span role=\"status\" className=\"sr-only\">\n {labels.loading}\n </span>\n </div>\n )}\n\n {failed && (\n <div role=\"alert\" className={s.status()}>\n <p>{labels.error}</p>\n <button\n type=\"button\"\n className={s.button({ className: \"border border-fg-on-media/40 px-3\" })}\n onClick={() => {\n setFailed(false);\n setAttempt((a) => a + 1);\n video.current?.load();\n }}\n >\n <RetryGlyph className=\"h-4 w-4\" />\n {labels.retry}\n </button>\n </div>\n )}\n\n {overlay ? <div className={s.overlay()}>{overlay}</div> : null}\n\n {showControls && (\n <div className={s.bar()}>\n <Slider\n label={labels.seek}\n min={0}\n max={duration || 1}\n step={0.1}\n value={Math.min(time, duration || 1)}\n onValueChange={seek}\n formatValue={(v) => labels.time(formatClock(v), formatClock(duration))}\n disabled={!duration}\n className=\"[&_[role=slider]]:h-4 [&_[role=slider]]:w-4\"\n />\n <div className={s.row()}>\n <button type=\"button\" className={s.button()} aria-label={playing ? labels.pause : labels.play} onClick={toggle}>\n {playing ? <PauseGlyph className=\"h-5 w-5\" /> : <PlayGlyph className=\"h-5 w-5\" />}\n </button>\n <button type=\"button\" className={s.button()} aria-label={muted ? labels.unmute : labels.mute} onClick={toggleMute}>\n <VolumeGlyph muted={muted} className=\"h-5 w-5\" />\n </button>\n <div className={s.volume()}>\n <Slider label={labels.volume} min={0} max={1} step={0.05} value={muted ? 0 : volume} onValueChange={changeVolume} formatValue={(v) => `${Math.round(v * 100)} %`} className=\"w-full\" />\n </div>\n <span className={s.time()} aria-hidden>\n {formatClock(time)} / {formatClock(duration)}\n </span>\n <span className=\"flex-1\" />\n {playbackRates.length > 1 && (\n <button type=\"button\" className={s.button()} aria-label={labels.speed(rate)} onClick={cycleRate}>\n {rate}×\n </button>\n )}\n {tracks.length > 0 && (\n <button type=\"button\" className={s.button()} aria-pressed={caption >= 0} aria-label={labels.captions(caption >= 0 ? (tracks[caption]?.label ?? null) : null)} onClick={cycleCaption}>\n <CaptionsGlyph className=\"h-5 w-5\" />\n </button>\n )}\n {pipSupported && (\n <button type=\"button\" className={s.button()} aria-label={labels.pictureInPicture} onClick={pip}>\n <PipGlyph className=\"h-5 w-5\" />\n </button>\n )}\n <button type=\"button\" className={s.button()} aria-label={fullscreen ? labels.exitFullscreen : labels.fullscreen} onClick={toggleFullscreen}>\n <FullscreenGlyph exit={fullscreen} className=\"h-5 w-5\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n"
|
|
424
|
+
}
|
|
425
|
+
],
|
|
426
|
+
"dependencies": [
|
|
427
|
+
"tailwind-variants@^3.3.1"
|
|
428
|
+
],
|
|
429
|
+
"registryDependencies": [
|
|
430
|
+
"slider"
|
|
431
|
+
],
|
|
432
|
+
"demo": {
|
|
433
|
+
"path": "examples/video-player/default.tsx",
|
|
434
|
+
"content": "import { VideoPlayer } from \"@krizaka/ui/video-player\";\n\nconst TOUR = \"https://www.krizaka.com/assets/orochia/tour\";\n\n// For HLS, add `loadHls={() => import(\"hls.js\")}`: only browsers without native HLS download it.\nexport default function VideoPlayerDefault() {\n return <VideoPlayer title=\"Video: an auction on Orochia\" src={`${TOUR}/alex-bid.mp4`} poster={`${TOUR}/alex-bid.jpg`} />;\n}\n"
|
|
435
|
+
},
|
|
436
|
+
"props": [
|
|
437
|
+
{
|
|
438
|
+
"component": "VideoPlayer",
|
|
439
|
+
"description": "A video player with accessible controls, HLS on demand, signed sources, subtitles, speed, full screen.",
|
|
440
|
+
"props": [
|
|
441
|
+
{
|
|
442
|
+
"name": "src",
|
|
443
|
+
"type": "string | (() => Promise<string>)",
|
|
444
|
+
"default": null,
|
|
445
|
+
"description": "The address of the video (MP4, WebM, or an HLS `.m3u8`), or a function that resolves a signed one when needed.",
|
|
446
|
+
"required": true
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"name": "title",
|
|
450
|
+
"type": "string",
|
|
451
|
+
"default": null,
|
|
452
|
+
"description": "The accessible name of the player (\"Video: Sunset over Montréal\"), passed translated.",
|
|
453
|
+
"required": true
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"name": "aspectRatio",
|
|
457
|
+
"type": "string",
|
|
458
|
+
"default": "16 / 9",
|
|
459
|
+
"description": "The width / height ratio of the frame. Default `\"16 / 9\"`; `\"9 / 16\"` for a vertical video.",
|
|
460
|
+
"required": false
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"name": "autoPlay",
|
|
464
|
+
"type": "boolean",
|
|
465
|
+
"default": null,
|
|
466
|
+
"description": "Starts playing, muted (browsers refuse sound without a gesture) — not under reduced motion.",
|
|
467
|
+
"required": false
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"name": "className",
|
|
471
|
+
"type": "string",
|
|
472
|
+
"default": null,
|
|
473
|
+
"description": "Classes merged on the root.",
|
|
474
|
+
"required": false
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
"name": "controls",
|
|
478
|
+
"type": "boolean",
|
|
479
|
+
"default": "true",
|
|
480
|
+
"description": "Shows the controls (default). `false`: a bare video (a background loop) — give it no sound.",
|
|
481
|
+
"required": false
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
"name": "labels",
|
|
485
|
+
"type": "Partial<VideoPlayerLabels>",
|
|
486
|
+
"default": null,
|
|
487
|
+
"description": "The words, passed translated; English by default.",
|
|
488
|
+
"required": false
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"name": "loadHls",
|
|
492
|
+
"type": "(() => Promise<HlsModule>)",
|
|
493
|
+
"default": null,
|
|
494
|
+
"description": "Loads hls.js when the browser cannot play HLS itself: `() => import(\"hls.js\")`. Without it, HLS needs Safari.",
|
|
495
|
+
"required": false
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"name": "loop",
|
|
499
|
+
"type": "boolean",
|
|
500
|
+
"default": null,
|
|
501
|
+
"description": "Plays again from the start at the end.",
|
|
502
|
+
"required": false
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"name": "muted",
|
|
506
|
+
"type": "boolean",
|
|
507
|
+
"default": "false",
|
|
508
|
+
"description": "Starts muted.",
|
|
509
|
+
"required": false
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"name": "onEnded",
|
|
513
|
+
"type": "(() => void)",
|
|
514
|
+
"default": null,
|
|
515
|
+
"description": "Called at the end.",
|
|
516
|
+
"required": false
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"name": "onError",
|
|
520
|
+
"type": "((error: unknown) => void)",
|
|
521
|
+
"default": null,
|
|
522
|
+
"description": "Called when the video cannot play (a network failure, an expired address).",
|
|
523
|
+
"required": false
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
"name": "onPause",
|
|
527
|
+
"type": "(() => void)",
|
|
528
|
+
"default": null,
|
|
529
|
+
"description": "Called when playback pauses.",
|
|
530
|
+
"required": false
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
"name": "onPlay",
|
|
534
|
+
"type": "(() => void)",
|
|
535
|
+
"default": null,
|
|
536
|
+
"description": "Called when playback starts.",
|
|
537
|
+
"required": false
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
"name": "onTimeUpdate",
|
|
541
|
+
"type": "((time: number) => void)",
|
|
542
|
+
"default": null,
|
|
543
|
+
"description": "Called as the time moves, in seconds (a few times a second).",
|
|
544
|
+
"required": false
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"name": "overlay",
|
|
548
|
+
"type": "ReactNode",
|
|
549
|
+
"default": null,
|
|
550
|
+
"description": "Covers the video and hides the controls: a gate (sign in, unlock, age check) drawn by the app.",
|
|
551
|
+
"required": false
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"name": "pauseWhenHidden",
|
|
555
|
+
"type": "boolean",
|
|
556
|
+
"default": "true",
|
|
557
|
+
"description": "Pauses when less than a quarter of the player is on screen. Default `true`.",
|
|
558
|
+
"required": false
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
"name": "playbackRates",
|
|
562
|
+
"type": "readonly number[]",
|
|
563
|
+
"default": "[0.5, 1, 1.25, 1.5, 2] as const",
|
|
564
|
+
"description": "The speeds offered by the speed button. Default `[0.5, 1, 1.25, 1.5, 2]`.",
|
|
565
|
+
"required": false
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
"name": "poster",
|
|
569
|
+
"type": "string",
|
|
570
|
+
"default": null,
|
|
571
|
+
"description": "The picture shown before playback.",
|
|
572
|
+
"required": false
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"name": "preload",
|
|
576
|
+
"type": "\"metadata\" | \"none\" | \"auto\"",
|
|
577
|
+
"default": "metadata",
|
|
578
|
+
"description": "How much to fetch before playback: `none` (a list of many videos), `metadata` (default), `auto`.",
|
|
579
|
+
"required": false
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
"name": "tracks",
|
|
583
|
+
"type": "readonly VideoTrack[]",
|
|
584
|
+
"default": "[]",
|
|
585
|
+
"description": "Subtitles or captions (WebVTT): the C key and a button cycle through them.",
|
|
586
|
+
"required": false
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
"name": "type",
|
|
590
|
+
"type": "\"auto\" | \"file\" | \"hls\"",
|
|
591
|
+
"default": "auto",
|
|
592
|
+
"description": "`auto` (default): HLS when the address ends in `.m3u8`.",
|
|
593
|
+
"required": false
|
|
594
|
+
}
|
|
595
|
+
]
|
|
596
|
+
}
|
|
597
|
+
]
|
|
598
|
+
}
|