@krizaka/ui 3.54.0 → 3.55.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 +1 -0
- package/dist/account-menu.d.ts +6 -2
- package/dist/account-menu.js +8 -1
- package/dist/account-menu.js.map +1 -1
- package/dist/account-settings.js +28 -88
- package/dist/account-settings.js.map +1 -1
- package/dist/anatomy-plate.d.ts +4 -1
- package/dist/anatomy-plate.js +4 -0
- package/dist/anatomy-plate.js.map +1 -1
- package/dist/app-header/search.d.ts +63 -0
- package/dist/app-header/search.js +70 -0
- package/dist/app-header/search.js.map +1 -0
- package/dist/app-header.d.ts +88 -4
- package/dist/app-header.js +63 -5
- package/dist/app-header.js.map +1 -1
- package/dist/app-shell.js +31 -40
- package/dist/app-shell.js.map +1 -1
- package/dist/argument-card.js +3 -3
- package/dist/bird-plate.d.ts +3 -1
- package/dist/bird-plate.js +2 -2
- package/dist/bird-plate.js.map +1 -1
- package/dist/blocks.css +162 -27
- package/dist/chat.js +2 -2
- package/dist/chat.js.map +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/chip.js +5 -98
- package/dist/chip.js.map +1 -1
- package/dist/choice-grid.js +28 -88
- package/dist/choice-grid.js.map +1 -1
- package/dist/{chunk-YGIN2YIY.js → chunk-3X3OF4TI.js} +2 -2
- package/dist/chunk-3X3OF4TI.js.map +1 -0
- package/dist/chunk-5VEVYDVC.js +11 -0
- package/dist/chunk-5VEVYDVC.js.map +1 -0
- package/dist/chunk-7U5TXA5Q.js +105 -0
- package/dist/chunk-7U5TXA5Q.js.map +1 -0
- package/dist/{chunk-KBQD6HE3.js → chunk-AEP3K4YD.js} +3 -3
- package/dist/{chunk-KBQD6HE3.js.map → chunk-AEP3K4YD.js.map} +1 -1
- package/dist/chunk-BDIU5LZB.js +18 -0
- package/dist/chunk-BDIU5LZB.js.map +1 -0
- package/dist/chunk-CSRKYZ53.js +39 -0
- package/dist/chunk-CSRKYZ53.js.map +1 -0
- package/dist/chunk-HEKDKM5Y.js +128 -0
- package/dist/chunk-HEKDKM5Y.js.map +1 -0
- package/dist/chunk-KGLRZMCY.js +16 -0
- package/dist/chunk-KGLRZMCY.js.map +1 -0
- package/dist/chunk-KID32IRJ.js +18 -0
- package/dist/chunk-KID32IRJ.js.map +1 -0
- package/dist/{chunk-ZQW7LF4Y.js → chunk-OIMXKRXF.js} +2 -2
- package/dist/{chunk-YHKTYFOQ.js → chunk-QWKCNSPF.js} +271 -47
- package/dist/chunk-QWKCNSPF.js.map +1 -0
- package/dist/{chunk-4LYKBSOG.js → chunk-USPOIQV5.js} +2 -2
- package/dist/chunk-USPOIQV5.js.map +1 -0
- package/dist/{chunk-7NRSPHAF.js → chunk-VB7MWE2N.js} +2 -2
- package/dist/{chunk-7NRSPHAF.js.map → chunk-VB7MWE2N.js.map} +1 -1
- package/dist/chunk-VIPSZXO4.js +103 -0
- package/dist/chunk-VIPSZXO4.js.map +1 -0
- package/dist/chunk-YCZ7N6Q6.js +385 -0
- package/dist/chunk-YCZ7N6Q6.js.map +1 -0
- package/dist/code-block.js +81 -98
- package/dist/code-block.js.map +1 -1
- package/dist/combobox.js +2 -2
- package/dist/component-page.d.ts +4 -1
- package/dist/component-page.js +5 -0
- package/dist/component-page.js.map +1 -1
- package/dist/continue-prompt.d.ts +4 -1
- package/dist/continue-prompt.js +4 -0
- package/dist/continue-prompt.js.map +1 -1
- package/dist/data-table.d.ts +60 -4
- package/dist/data-table.js +19 -7
- package/dist/date-picker.js +2 -2
- package/dist/date-picker.js.map +1 -1
- package/dist/detail-layout.d.ts +28 -17
- package/dist/detail-layout.js +73 -16
- package/dist/detail-layout.js.map +1 -1
- package/dist/doodle.d.ts +21 -0
- package/dist/doodle.js +39 -0
- package/dist/doodle.js.map +1 -0
- package/dist/drop-overlay.d.ts +12 -4
- package/dist/drop-overlay.js +16 -6
- package/dist/drop-overlay.js.map +1 -1
- package/dist/elephant.d.ts +2 -0
- package/dist/elephant.js +4 -0
- package/dist/elephant.js.map +1 -1
- package/dist/field.js +1 -1
- package/dist/field.js.map +1 -1
- package/dist/filter-bar.js +2 -2
- package/dist/force-update-screen.d.ts +5 -1
- package/dist/force-update-screen.js +4 -0
- package/dist/force-update-screen.js.map +1 -1
- package/dist/gallery-page.d.ts +4 -1
- package/dist/gallery-page.js +4 -0
- package/dist/gallery-page.js.map +1 -1
- package/dist/genui.js +5 -5
- package/dist/image-cropper.d.ts +10 -2
- package/dist/image-cropper.js +4 -0
- package/dist/image-cropper.js.map +1 -1
- package/dist/kanban.d.ts +3 -0
- package/dist/kanban.js +4 -0
- package/dist/kanban.js.map +1 -1
- package/dist/landscape.d.ts +4 -1
- package/dist/landscape.js +4 -0
- package/dist/landscape.js.map +1 -1
- package/dist/list.js +1 -1
- package/dist/maintenance-screen.d.ts +5 -1
- package/dist/maintenance-screen.js +4 -0
- package/dist/maintenance-screen.js.map +1 -1
- package/dist/map.js +6 -0
- package/dist/map.js.map +1 -1
- package/dist/markdown.d.ts +5 -1
- package/dist/markdown.js +12 -5
- package/dist/markdown.js.map +1 -1
- package/dist/match-dialog.d.ts +4 -1
- package/dist/match-dialog.js +4 -0
- package/dist/match-dialog.js.map +1 -1
- package/dist/media-capture.d.ts +6 -1
- package/dist/media-capture.js +4 -0
- package/dist/media-capture.js.map +1 -1
- package/dist/meter.js +5 -121
- package/dist/meter.js.map +1 -1
- package/dist/native/map.js +6 -0
- package/dist/native/map.js.map +1 -1
- package/dist/native/markdown.d.ts +9 -2
- package/dist/native/markdown.js +46 -7
- package/dist/native/markdown.js.map +1 -1
- package/dist/native/media-capture.d.ts +1 -0
- package/dist/native/media-capture.js +5 -0
- package/dist/native/media-capture.js.map +1 -1
- package/dist/native/vertical-video.d.ts +8 -2
- package/dist/native/vertical-video.js +6 -0
- package/dist/native/vertical-video.js.map +1 -1
- package/dist/native.d.ts +94 -4
- package/dist/native.js +1236 -1110
- package/dist/native.js.map +1 -1
- package/dist/nav-bar/me.d.ts +32 -0
- package/dist/nav-bar/me.js +23 -0
- package/dist/nav-bar/me.js.map +1 -0
- package/dist/nav-bar.d.ts +60 -2
- package/dist/nav-bar.js +92 -48
- package/dist/nav-bar.js.map +1 -1
- package/dist/notification-preferences.d.ts +151 -0
- package/dist/notification-preferences.js +77 -0
- package/dist/notification-preferences.js.map +1 -0
- package/dist/page-header.js +1 -1
- package/dist/page-header.js.map +1 -1
- package/dist/pointer-light.js +5 -0
- package/dist/pointer-light.js.map +1 -1
- package/dist/product-page.d.ts +4 -1
- package/dist/product-page.js +4 -0
- package/dist/product-page.js.map +1 -1
- package/dist/profile-card.d.ts +119 -1
- package/dist/profile-card.js +68 -21
- package/dist/profile-card.js.map +1 -1
- package/dist/promise-card.d.ts +4 -1
- package/dist/promise-card.js +4 -0
- package/dist/promise-card.js.map +1 -1
- package/dist/quota-meter.d.ts +113 -0
- package/dist/quota-meter.js +58 -0
- package/dist/quota-meter.js.map +1 -0
- package/dist/radio-group.js +6 -8
- package/dist/radio-group.js.map +1 -1
- package/dist/rail-nav.d.ts +18 -2
- package/dist/rail-nav.js +25 -2
- package/dist/rail-nav.js.map +1 -1
- package/dist/retro-tv.d.ts +30 -0
- package/dist/retro-tv.js +124 -0
- package/dist/retro-tv.js.map +1 -0
- package/dist/router.d.ts +12 -1
- package/dist/router.js +85 -8
- package/dist/router.js.map +1 -1
- package/dist/scroll-stage.d.ts +5 -1
- package/dist/scroll-stage.js +5 -0
- package/dist/scroll-stage.js.map +1 -1
- package/dist/search-field.js +1 -1
- package/dist/showcase.d.ts +9 -2
- package/dist/showcase.js +5 -0
- package/dist/showcase.js.map +1 -1
- package/dist/signature.d.ts +19 -0
- package/dist/signature.js +39 -0
- package/dist/signature.js.map +1 -0
- package/dist/site-footer.js +1 -1
- package/dist/sites.js +1 -1
- package/dist/sites.js.map +1 -1
- package/dist/slot.d.ts +1 -1
- package/dist/stat.js +1 -1
- package/dist/stat.js.map +1 -1
- package/dist/status-page.d.ts +4 -1
- package/dist/status-page.js +5 -0
- package/dist/status-page.js.map +1 -1
- package/dist/stuck.d.ts +27 -0
- package/dist/stuck.js +26 -0
- package/dist/stuck.js.map +1 -0
- package/dist/swipe-deck.d.ts +9 -2
- package/dist/swipe-deck.js +5 -0
- package/dist/swipe-deck.js.map +1 -1
- package/dist/switch.js +4 -32
- package/dist/switch.js.map +1 -1
- package/dist/tabs.d.ts +47 -8
- package/dist/tabs.js +62 -12
- package/dist/tabs.js.map +1 -1
- package/dist/tag-input.js +2 -2
- package/dist/tag-selector.js +1 -1
- package/dist/terminal-replay.d.ts +4 -1
- package/dist/terminal-replay.js +4 -0
- package/dist/terminal-replay.js.map +1 -1
- package/dist/tree-view.js +4 -0
- package/dist/tree-view.js.map +1 -1
- package/dist/update-prompt.d.ts +5 -1
- package/dist/update-prompt.js +4 -0
- package/dist/update-prompt.js.map +1 -1
- package/dist/video-player.js +7 -376
- package/dist/video-player.js.map +1 -1
- package/dist/video-trimmer.d.ts +6 -1
- package/dist/video-trimmer.js +4 -0
- package/dist/video-trimmer.js.map +1 -1
- package/package.json +141 -29
- package/registry/account-menu.json +9 -8
- package/registry/account-settings.json +2 -2
- package/registry/achievement.json +3 -4
- package/registry/action-bar.json +3 -3
- package/registry/action-sheet.json +1 -0
- package/registry/activity-feed.json +2 -1
- package/registry/agenda.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/anatomy-plate.json +13 -5
- package/registry/app-header.json +470 -23
- package/registry/app-shell.json +2 -2
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +3 -2
- package/registry/badge.json +2 -1
- package/registry/bird-plate.json +16 -2
- package/registry/button.json +7 -6
- package/registry/captcha.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +7 -6
- package/registry/catalogue-page.json +3 -4
- package/registry/changelog-page.json +3 -5
- package/registry/chapter.json +1 -1
- package/registry/chat.json +3 -2
- package/registry/checkbox.json +4 -3
- package/registry/chip-link.json +2 -2
- package/registry/chip-row.json +2 -2
- package/registry/chip.json +3 -2
- package/registry/choice-grid.json +3 -4
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +3 -2
- package/registry/command.json +2 -2
- package/registry/comparison-table.json +1 -1
- package/registry/component-page.json +14 -5
- package/registry/confirm-button.json +4 -4
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +13 -5
- package/registry/countdown.json +4 -3
- package/registry/cover-hero.json +3 -4
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +63 -7
- package/registry/date-picker.json +5 -4
- package/registry/decision-list.json +1 -1
- package/registry/description-list.json +2 -1
- package/registry/detail-layout.json +30 -6
- package/registry/dialog.json +7 -6
- package/registry/docs-article-page.json +3 -4
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/docs-nav.json +1 -1
- package/registry/document-viewer.json +3 -5
- package/registry/doodle.json +209 -0
- package/registry/drop-overlay.json +30 -16
- package/registry/dropdown-menu.json +2 -2
- package/registry/elephant.json +15 -7
- package/registry/empty-state.json +2 -1
- package/registry/engine-flow.json +1 -1
- package/registry/error-page.json +3 -5
- package/registry/examples/app-header/family.d.ts +4 -0
- package/registry/examples/app-header/family.js +41 -0
- package/registry/examples/app-header/family.tsx +37 -0
- package/registry/examples/app-header/native/family.d.ts +4 -0
- package/registry/examples/app-header/native/family.dark.png +0 -0
- package/registry/examples/app-header/native/family.js +25 -0
- package/registry/examples/app-header/native/family.light.png +0 -0
- package/registry/examples/app-header/native/family.tsx +33 -0
- package/registry/examples/data-table/applicant-files.d.ts +4 -0
- package/registry/examples/data-table/applicant-files.js +24 -0
- package/registry/examples/data-table/applicant-files.tsx +23 -0
- package/registry/examples/data-table/checks.d.ts +4 -0
- package/registry/examples/data-table/checks.js +25 -0
- package/registry/examples/data-table/checks.tsx +24 -0
- package/registry/examples/detail-layout/profile.d.ts +4 -0
- package/registry/examples/detail-layout/profile.js +26 -0
- package/registry/examples/detail-layout/profile.tsx +21 -0
- package/registry/examples/doodle/kinds.d.ts +4 -0
- package/registry/examples/doodle/kinds.js +17 -0
- package/registry/examples/doodle/kinds.tsx +14 -0
- package/registry/examples/doodle/margin.d.ts +4 -0
- package/registry/examples/doodle/margin.js +17 -0
- package/registry/examples/doodle/margin.tsx +17 -0
- package/registry/examples/markdown/native/basic.dark.png +0 -0
- package/registry/examples/markdown/native/basic.light.png +0 -0
- package/registry/examples/markdown/native/wide-table.d.ts +4 -0
- package/registry/examples/markdown/native/wide-table.dark.png +0 -0
- package/registry/examples/markdown/native/wide-table.js +23 -0
- package/registry/examples/markdown/native/wide-table.light.png +0 -0
- package/registry/examples/markdown/native/wide-table.tsx +22 -0
- package/registry/examples/markdown/wide-table.d.ts +4 -0
- package/registry/examples/markdown/wide-table.js +28 -0
- package/registry/examples/markdown/wide-table.tsx +23 -0
- package/registry/examples/nav-bar/family-product.js +2 -4
- package/registry/examples/nav-bar/family-product.tsx +3 -5
- package/registry/examples/notification-preferences/default.d.ts +4 -0
- package/registry/examples/notification-preferences/default.js +29 -0
- package/registry/examples/notification-preferences/default.tsx +28 -0
- package/registry/examples/profile-card/banner.d.ts +4 -0
- package/registry/examples/profile-card/banner.js +28 -0
- package/registry/examples/profile-card/banner.tsx +24 -0
- package/registry/examples/profile-card/native/banner.d.ts +4 -0
- package/registry/examples/profile-card/native/banner.dark.png +0 -0
- package/registry/examples/profile-card/native/banner.js +22 -0
- package/registry/examples/profile-card/native/banner.light.png +0 -0
- package/registry/examples/profile-card/native/banner.tsx +18 -0
- package/registry/examples/quota-meter/default.d.ts +4 -0
- package/registry/examples/quota-meter/default.js +26 -0
- package/registry/examples/quota-meter/default.tsx +20 -0
- package/registry/examples/quota-meter/full.d.ts +4 -0
- package/registry/examples/quota-meter/full.js +25 -0
- package/registry/examples/quota-meter/full.tsx +18 -0
- package/registry/examples/rail-nav/shortcuts.d.ts +4 -0
- package/registry/examples/rail-nav/shortcuts.js +49 -0
- package/registry/examples/rail-nav/shortcuts.tsx +40 -0
- package/registry/examples/retro-tv/cabinet.d.ts +4 -0
- package/registry/examples/retro-tv/cabinet.js +12 -0
- package/registry/examples/retro-tv/cabinet.tsx +7 -0
- package/registry/examples/retro-tv/portable.d.ts +4 -0
- package/registry/examples/retro-tv/portable.js +12 -0
- package/registry/examples/retro-tv/portable.tsx +7 -0
- package/registry/examples/signature/both.d.ts +4 -0
- package/registry/examples/signature/both.js +11 -0
- package/registry/examples/signature/both.tsx +6 -0
- package/registry/examples/signature/variants.d.ts +4 -0
- package/registry/examples/signature/variants.js +14 -0
- package/registry/examples/signature/variants.tsx +11 -0
- package/registry/examples/stuck/card.d.ts +4 -0
- package/registry/examples/stuck/card.js +13 -0
- package/registry/examples/stuck/card.tsx +14 -0
- package/registry/examples/stuck/holds.d.ts +4 -0
- package/registry/examples/stuck/holds.js +12 -0
- package/registry/examples/stuck/holds.tsx +16 -0
- package/registry/examples/stuck/tag.d.ts +4 -0
- package/registry/examples/stuck/tag.js +12 -0
- package/registry/examples/stuck/tag.tsx +13 -0
- package/registry/examples/tabs/binder.d.ts +4 -0
- package/registry/examples/tabs/binder.js +22 -0
- package/registry/examples/tabs/binder.tsx +20 -0
- package/registry/examples/tabs/native/binder.d.ts +4 -0
- package/registry/examples/tabs/native/binder.dark.png +0 -0
- package/registry/examples/tabs/native/binder.js +26 -0
- package/registry/examples/tabs/native/binder.light.png +0 -0
- package/registry/examples/tabs/native/binder.tsx +22 -0
- package/registry/fab.json +1 -0
- package/registry/field.json +3 -2
- package/registry/file-upload.json +6 -11
- package/registry/filter-bar.json +2 -2
- package/registry/flock.json +3 -4
- package/registry/footnote.json +2 -3
- package/registry/force-update-screen.json +14 -5
- package/registry/form.json +5 -4
- package/registry/gallery-page.json +14 -5
- package/registry/genui.json +2 -2
- package/registry/glossary-term.json +1 -1
- package/registry/glossary.json +1 -1
- package/registry/glow-card.json +2 -2
- package/registry/goal-gauge.json +2 -1
- package/registry/header.json +1 -0
- package/registry/how-it-works-page.json +3 -5
- package/registry/image-cropper.json +13 -5
- package/registry/image-viewer.json +3 -4
- package/registry/index.json +301 -87
- package/registry/iso.json +1 -1
- package/registry/kanban.json +13 -5
- package/registry/kbd.json +1 -1
- package/registry/kpi-explained.json +2 -2
- package/registry/landing-page.json +3 -5
- package/registry/landscape.json +13 -5
- package/registry/language-switch.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/list.json +2 -1
- package/registry/loader.json +2 -1
- package/registry/longform-page.json +1 -1
- package/registry/maintenance-screen.json +14 -5
- package/registry/map.json +3 -2
- package/registry/markdown.json +86 -9
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +12 -11
- package/registry/mascots.json +2 -1
- package/registry/match-dialog.json +14 -5
- package/registry/media-capture.json +14 -5
- package/registry/media-gallery.json +3 -5
- package/registry/media-grid.json +1 -1
- package/registry/media-picker.json +2 -3
- package/registry/meta-row.json +2 -1
- package/registry/meter.json +4 -3
- package/registry/nav-bar.json +100 -9
- package/registry/next-actions.json +1 -1
- package/registry/notification-preferences.json +250 -0
- package/registry/number-ticker.json +2 -1
- package/registry/onboarding.json +1 -0
- package/registry/opening-hours.json +1 -1
- package/registry/otp-input.json +1 -0
- package/registry/page-header.json +2 -2
- package/registry/page-hero.json +1 -1
- package/registry/page-index.json +1 -1
- package/registry/page.json +3 -3
- package/registry/pagination.json +2 -1
- package/registry/permission-prompt.json +2 -2
- package/registry/photo-grid.json +4 -4
- package/registry/pointer-light.json +6 -2
- package/registry/popover.json +2 -2
- package/registry/pricing-page.json +4 -6
- package/registry/product-page.json +14 -5
- package/registry/profile-card.json +232 -8
- package/registry/progress.json +4 -3
- package/registry/promise-card.json +13 -5
- package/registry/pull-to-refresh.json +1 -0
- package/registry/quota-meter.json +308 -0
- package/registry/radio-group.json +4 -3
- package/registry/rail-nav.json +48 -10
- package/registry/recipes.json +1 -1
- package/registry/retro-tv.json +478 -0
- package/registry/router.json +3 -3
- package/registry/screen.json +1 -0
- package/registry/scroll-stage.json +14 -5
- package/registry/search-field.json +3 -2
- package/registry/section-backdrop.json +1 -1
- package/registry/segmented.json +1 -0
- package/registry/separator.json +2 -2
- package/registry/service-card.json +1 -1
- package/registry/settings-layout.json +2 -1
- package/registry/showcase.json +14 -5
- package/registry/signature.json +185 -0
- package/registry/site-footer.json +3 -3
- package/registry/sites.json +2 -2
- package/registry/skeleton.json +2 -1
- package/registry/skip-link.json +1 -1
- package/registry/slider.json +5 -4
- package/registry/slot-picker.json +1 -1
- package/registry/slot.json +2 -2
- package/registry/specimen-tag.json +1 -1
- package/registry/spinner.json +4 -3
- package/registry/splash-handoff.json +1 -0
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +2 -3
- package/registry/stat.json +2 -2
- package/registry/status-page.json +13 -5
- package/registry/step-progress.json +2 -1
- package/registry/step-showcase.json +3 -5
- package/registry/stepper.json +2 -1
- package/registry/sticky-action.json +1 -1
- package/registry/stuck.json +219 -0
- package/registry/swipe-deck.json +14 -5
- package/registry/swipeable-row.json +1 -0
- package/registry/switch.json +3 -2
- package/registry/tab-bar.json +1 -0
- package/registry/tabs.json +154 -22
- package/registry/tag-input.json +1 -1
- package/registry/tag-selector.json +2 -1
- package/registry/taped-photo.json +1 -1
- package/registry/terminal-replay.json +14 -5
- package/registry/text-style.json +5 -5
- package/registry/theme.json +2 -1
- package/registry/toast.json +2 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +2 -2
- package/registry/tree-view.json +6 -2
- package/registry/txt.json +1 -0
- package/registry/update-prompt.json +14 -5
- package/registry/upload-queue.json +1 -1
- package/registry/verified-badge.json +6 -6
- package/registry/vertical-video.json +8 -3
- package/registry/video-player.json +5 -6
- package/registry/video-trimmer.json +13 -5
- package/registry/virtual.json +2 -2
- package/registry/waveform.json +4 -3
- package/dist/chunk-4LYKBSOG.js.map +0 -1
- package/dist/chunk-HHFWK2HH.js +0 -163
- package/dist/chunk-HHFWK2HH.js.map +0 -1
- package/dist/chunk-R3LTSMS5.js +0 -75
- package/dist/chunk-R3LTSMS5.js.map +0 -1
- package/dist/chunk-YGIN2YIY.js.map +0 -1
- package/dist/chunk-YHKTYFOQ.js.map +0 -1
- /package/dist/{chunk-ZQW7LF4Y.js.map → chunk-OIMXKRXF.js.map} +0 -0
package/registry/tabs.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"name": "tabs",
|
|
3
3
|
"type": "primitive",
|
|
4
4
|
"title": "Tabs",
|
|
5
|
-
"summary": "Views of the same page, one at a time: underlined sections, a segmented switch or
|
|
6
|
-
"description": "Views of the same page, one at a time: underlined sections, a segmented switch or
|
|
5
|
+
"summary": "Views of the same page, one at a time: underlined sections, a segmented switch, pills or binder dividers that organise collections — one light glides from the active tab to the next.",
|
|
6
|
+
"description": "Views of the same page, one at a time: underlined sections, a segmented switch, pills or binder dividers that organise collections — one light glides from the active tab to the next.",
|
|
7
7
|
"status": "beta",
|
|
8
8
|
"deprecated": null,
|
|
9
9
|
"category": "navigation",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"whenToUse": [
|
|
23
23
|
"To split a page into sections the user switches between without leaving it: videos, stories, about.",
|
|
24
|
-
"`variant=\"segmented\"` for a view switch (grid, list); `pills` for a feed's sections."
|
|
24
|
+
"`variant=\"segmented\"` for a view switch (grid, list); `pills` for a feed's sections.",
|
|
25
|
+
"`variant=\"binder\"` to organise a workspace's collections, tags or statuses as binder dividers (intercalaires): a coloured edge (`hue`), the words and a count; best as routes (`Tabs.Nav`), so the address is the state."
|
|
25
26
|
],
|
|
26
27
|
"whenNotToUse": [
|
|
27
28
|
{
|
|
@@ -38,6 +39,8 @@
|
|
|
38
39
|
],
|
|
39
40
|
"bestPractices": [
|
|
40
41
|
"Short labels of one or two words; the active one should be clear without colour.",
|
|
42
|
+
"Binder: the colour organises, it never says on its own — the words and the count do. `hue` takes a role of the theme (accent · success · warning · danger · neutral), each 3:1 or more on both themes; give a status divider its status' role. No paper texture: it is the products' register.",
|
|
43
|
+
"Binder: « + » (`add`) sits after the dividers, outside the tab list, for creating a collection.",
|
|
41
44
|
"Keep the URL in sync with the active tab when a tab is worth sharing: better, use `Tabs.Nav` (tabs as routes), where the route is the state and the active tab is read from it."
|
|
42
45
|
],
|
|
43
46
|
"accessibility": {
|
|
@@ -53,7 +56,9 @@
|
|
|
53
56
|
],
|
|
54
57
|
"notes": [
|
|
55
58
|
"Radix Tabs: `tablist`, `tab` with `aria-selected` and `aria-controls`, `tabpanel`; name the list with `aria-label`.",
|
|
56
|
-
"A disabled tab is skipped."
|
|
59
|
+
"A disabled tab is skipped.",
|
|
60
|
+
"A count is part of the tab's name (« Regulars 21 »); a binder divider's colour is decoration (the words say it).",
|
|
61
|
+
"The binder's « + » is a button after the list (a tablist holds only tabs), reached by Tab after the tabs."
|
|
57
62
|
]
|
|
58
63
|
},
|
|
59
64
|
"related": [
|
|
@@ -85,9 +90,9 @@
|
|
|
85
90
|
},
|
|
86
91
|
{
|
|
87
92
|
"name": "variant",
|
|
88
|
-
"type": "\"segmented\" | \"underline\" | \"pills\"",
|
|
93
|
+
"type": "\"segmented\" | \"underline\" | \"pills\" | \"binder\"",
|
|
89
94
|
"default": "underline",
|
|
90
|
-
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections).",
|
|
95
|
+
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections) · binder (dividers that organise collections, tags, statuses).",
|
|
91
96
|
"required": false
|
|
92
97
|
}
|
|
93
98
|
]
|
|
@@ -95,7 +100,35 @@
|
|
|
95
100
|
{
|
|
96
101
|
"component": "TabsList",
|
|
97
102
|
"description": "The tabs. Name it (`aria-label`) when no heading says what it switches. One light glides to the active tab.",
|
|
98
|
-
"props": [
|
|
103
|
+
"props": [
|
|
104
|
+
{
|
|
105
|
+
"name": "add",
|
|
106
|
+
"type": "TabsAdd",
|
|
107
|
+
"default": null,
|
|
108
|
+
"description": "A « + » button after the tabs (`variant=\"binder\"`: « + Intercalaire »): its words and what it does.",
|
|
109
|
+
"required": false
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"component": "TabsTrigger",
|
|
115
|
+
"description": "",
|
|
116
|
+
"props": [
|
|
117
|
+
{
|
|
118
|
+
"name": "count",
|
|
119
|
+
"type": "number",
|
|
120
|
+
"default": null,
|
|
121
|
+
"description": "A count after the words (clients in a collection, unread): part of the tab's name.",
|
|
122
|
+
"required": false
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "hue",
|
|
126
|
+
"type": "TabsHue",
|
|
127
|
+
"default": null,
|
|
128
|
+
"description": "`variant=\"binder\"`: the divider's colour (the accent by default).",
|
|
129
|
+
"required": false
|
|
130
|
+
}
|
|
131
|
+
]
|
|
99
132
|
},
|
|
100
133
|
{
|
|
101
134
|
"component": "TabsNav",
|
|
@@ -122,11 +155,18 @@
|
|
|
122
155
|
"description": "The navigation's accessible name (« Sections »).",
|
|
123
156
|
"required": true
|
|
124
157
|
},
|
|
158
|
+
{
|
|
159
|
+
"name": "add",
|
|
160
|
+
"type": "TabsAdd",
|
|
161
|
+
"default": null,
|
|
162
|
+
"description": "A « + » button after the tabs (`variant=\"binder\"`: « + Intercalaire »).",
|
|
163
|
+
"required": false
|
|
164
|
+
},
|
|
125
165
|
{
|
|
126
166
|
"name": "variant",
|
|
127
|
-
"type": "\"segmented\" | \"underline\" | \"pills\"",
|
|
167
|
+
"type": "\"segmented\" | \"underline\" | \"pills\" | \"binder\"",
|
|
128
168
|
"default": "underline",
|
|
129
|
-
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections).",
|
|
169
|
+
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections) · binder (dividers).",
|
|
130
170
|
"required": false
|
|
131
171
|
}
|
|
132
172
|
]
|
|
@@ -142,12 +182,26 @@
|
|
|
142
182
|
"description": "Renders your router's `<Link>` (pass it as the only child; the `href` goes on this part and on the link).",
|
|
143
183
|
"required": false
|
|
144
184
|
},
|
|
185
|
+
{
|
|
186
|
+
"name": "count",
|
|
187
|
+
"type": "number",
|
|
188
|
+
"default": null,
|
|
189
|
+
"description": "A count after the words (not with `asChild`: put it in your link).",
|
|
190
|
+
"required": false
|
|
191
|
+
},
|
|
145
192
|
{
|
|
146
193
|
"name": "exact",
|
|
147
194
|
"type": "boolean",
|
|
148
195
|
"default": null,
|
|
149
196
|
"description": "Match this path only, not the pages below it.",
|
|
150
197
|
"required": false
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "hue",
|
|
201
|
+
"type": "TabsHue",
|
|
202
|
+
"default": null,
|
|
203
|
+
"description": "`variant=\"binder\"`: the divider's colour (the accent by default).",
|
|
204
|
+
"required": false
|
|
151
205
|
}
|
|
152
206
|
]
|
|
153
207
|
}
|
|
@@ -185,6 +239,14 @@
|
|
|
185
239
|
"module": "@krizaka/ui/registry/examples/tabs/vertical",
|
|
186
240
|
"code": "import { Tabs } from \"@krizaka/ui/tabs\";\n\n// `orientation=\"vertical\"`: up and down arrows, the list on the side.\nexport default function TabsVertical() {\n return (\n <Tabs.Root orientation=\"vertical\" defaultValue=\"videos\">\n <Tabs.List aria-label=\"Profile\">\n <Tabs.Trigger value=\"videos\">Videos</Tabs.Trigger>\n <Tabs.Trigger value=\"stories\">Stories</Tabs.Trigger>\n <Tabs.Trigger value=\"collections\">Collections</Tabs.Trigger>\n </Tabs.List>\n <Tabs.Content value=\"videos\" className=\"text-sm text-fg-secondary\">\n Twelve videos, newest first.\n </Tabs.Content>\n <Tabs.Content value=\"stories\" className=\"text-sm text-fg-secondary\">\n Stories of the last 24 hours.\n </Tabs.Content>\n <Tabs.Content value=\"collections\" className=\"text-sm text-fg-secondary\">\n Three public collections.\n </Tabs.Content>\n </Tabs.Root>\n );\n}\n"
|
|
187
241
|
},
|
|
242
|
+
{
|
|
243
|
+
"name": "binder",
|
|
244
|
+
"title": "Binder dividers",
|
|
245
|
+
"description": "`variant=\"binder\"` as routes: a client's collections as dividers with their colour and count, « + Divider » after them; they scroll sideways with fading edges when they do not fit.",
|
|
246
|
+
"path": "examples/tabs/binder.tsx",
|
|
247
|
+
"module": "@krizaka/ui/registry/examples/tabs/binder",
|
|
248
|
+
"code": "import { Tabs } from \"@krizaka/ui/tabs\";\n\n// Binder dividers as routes: each collection is a divider with its colour and count; the address is the state.\n// `current` comes from your router; « + Divider » opens the host's form.\nconst current = \"/clients?view=regulars\";\n\nexport default function TabsBinder() {\n return (\n <div className=\"max-w-xl\">\n <Tabs.Nav label=\"Client collections\" current={current} variant=\"binder\" add={{ label: \"Divider\", onAdd: () => {} }}>\n <Tabs.NavLink href=\"/clients?view=all\" count={48}>All</Tabs.NavLink>\n <Tabs.NavLink href=\"/clients?view=regulars\" hue=\"success\" count={21}>Regulars</Tabs.NavLink>\n <Tabs.NavLink href=\"/clients?view=new\" hue=\"accent\" count={9}>New</Tabs.NavLink>\n <Tabs.NavLink href=\"/clients?view=follow-up\" hue=\"warning\" count={5}>Follow up</Tabs.NavLink>\n <Tabs.NavLink href=\"/clients?view=archived\" hue=\"neutral\" count={13}>Archived</Tabs.NavLink>\n </Tabs.Nav>\n <div className=\"rounded-b-lg border border-t-0 border-border-default bg-surface-1 p-4 text-sm text-fg-secondary\">21 regular clients.</div>\n </div>\n );\n}\n"
|
|
249
|
+
},
|
|
188
250
|
{
|
|
189
251
|
"name": "routes",
|
|
190
252
|
"title": "Tabs as routes",
|
|
@@ -200,8 +262,10 @@
|
|
|
200
262
|
"import": "import { Tabs } from \"@krizaka/ui/native\";",
|
|
201
263
|
"differences": [
|
|
202
264
|
"`items` (value, label, count, content) instead of the web's parts.",
|
|
203
|
-
"The underlined tab list scrolls sideways when it does not fit; for the app's main sections use `TabBar`."
|
|
265
|
+
"The underlined tab list scrolls sideways when it does not fit; for the app's main sections use `TabBar`.",
|
|
266
|
+
"`hue` is on each item; `add` is a prop of `Tabs` (no routes on a phone: the selection is the screen's state)."
|
|
204
267
|
],
|
|
268
|
+
"deprecated": null,
|
|
205
269
|
"props": [
|
|
206
270
|
{
|
|
207
271
|
"component": "Tabs",
|
|
@@ -214,6 +278,13 @@
|
|
|
214
278
|
"description": "The tabs, in order.",
|
|
215
279
|
"required": true
|
|
216
280
|
},
|
|
281
|
+
{
|
|
282
|
+
"name": "add",
|
|
283
|
+
"type": "{ label: string; onAdd: () => void; }",
|
|
284
|
+
"default": null,
|
|
285
|
+
"description": "`variant=\"binder\"`: a « + » button after the dividers (« Intercalaire »): its words and what it does.",
|
|
286
|
+
"required": false
|
|
287
|
+
},
|
|
217
288
|
{
|
|
218
289
|
"name": "aria-label",
|
|
219
290
|
"type": "string",
|
|
@@ -251,9 +322,9 @@
|
|
|
251
322
|
},
|
|
252
323
|
{
|
|
253
324
|
"name": "variant",
|
|
254
|
-
"type": "\"segmented\" | \"underline\"",
|
|
325
|
+
"type": "\"segmented\" | \"underline\" | \"binder\"",
|
|
255
326
|
"default": "underline",
|
|
256
|
-
"description": "`underline` (default: sections of a screen, scrolls sideways when they do not fit)
|
|
327
|
+
"description": "`underline` (default: sections of a screen, scrolls sideways when they do not fit), `segmented` (two to four views) or `binder` (dividers that organise collections, tags, statuses; they scroll sideways).",
|
|
257
328
|
"required": false
|
|
258
329
|
}
|
|
259
330
|
]
|
|
@@ -283,13 +354,25 @@
|
|
|
283
354
|
"dark": "examples/tabs/native/segmented.dark.png",
|
|
284
355
|
"light": "examples/tabs/native/segmented.light.png"
|
|
285
356
|
}
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"name": "binder",
|
|
360
|
+
"title": "Binder dividers",
|
|
361
|
+
"description": "`variant=\"binder\"`: collections as dividers with a coloured edge and a count, « + Divider » after them.",
|
|
362
|
+
"path": "examples/tabs/native/binder.tsx",
|
|
363
|
+
"module": "@krizaka/ui/registry/examples/tabs/native/binder",
|
|
364
|
+
"code": "import { Tabs, Txt } from \"@krizaka/ui/native\";\nimport { View } from \"react-native\";\n\n// Binder dividers on a phone: a coloured edge and a count per collection; they scroll sideways when they do not fit.\nexport default function NativeTabsBinder() {\n return (\n <View style={{ width: 340 }}>\n <Tabs\n aria-label=\"Client collections\"\n variant=\"binder\"\n defaultValue=\"regulars\"\n add={{ label: \"Divider\", onAdd: () => undefined }}\n items={[\n { value: \"all\", label: \"All\", count: 48, content: <Txt tone=\"secondary\">48 clients.</Txt> },\n { value: \"regulars\", label: \"Regulars\", hue: \"success\", count: 21, content: <Txt tone=\"secondary\">21 regular clients.</Txt> },\n { value: \"new\", label: \"New\", hue: \"accent\", count: 9, content: <Txt tone=\"secondary\">9 new clients.</Txt> },\n { value: \"follow-up\", label: \"Follow up\", hue: \"warning\", count: 5, content: <Txt tone=\"secondary\">5 to call back.</Txt> },\n ]}\n />\n </View>\n );\n}\n",
|
|
365
|
+
"screenshots": {
|
|
366
|
+
"dark": "examples/tabs/native/binder.dark.png",
|
|
367
|
+
"light": "examples/tabs/native/binder.light.png"
|
|
368
|
+
}
|
|
286
369
|
}
|
|
287
370
|
]
|
|
288
371
|
},
|
|
289
372
|
"files": [
|
|
290
373
|
{
|
|
291
374
|
"path": "meta.ts",
|
|
292
|
-
"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 ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<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 | \"agenda\"\n | \"account-settings\"\n | \"drop-overlay\"\n | \"upload-queue\"\n | \"achievement\"\n | \"anatomy-plate\"\n | \"bird-plate\"\n | \"taped-photo\"\n | \"text-style\"\n | \"flock\"\n | \"elephant\"\n | \"footnote\"\n | \"page-index\"\n | \"chapter\"\n | \"specimen-tag\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glossary\"\n | \"glossary-term\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"kpi-explained\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"router\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\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/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\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 */\ninterface ExampleBase {\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/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: 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 /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\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"
|
|
375
|
+
"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 ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<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 | \"notification-preferences\"\n | \"quota-meter\"\n | \"agenda\"\n | \"account-settings\"\n | \"drop-overlay\"\n | \"upload-queue\"\n | \"achievement\"\n | \"anatomy-plate\"\n | \"bird-plate\"\n | \"taped-photo\"\n | \"stuck\"\n | \"doodle\"\n | \"signature\"\n | \"text-style\"\n | \"flock\"\n | \"elephant\"\n | \"footnote\"\n | \"page-index\"\n | \"chapter\"\n | \"specimen-tag\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glossary\"\n | \"glossary-term\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"kpi-explained\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"retro-tv\"\n | \"radio-group\"\n | \"router\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\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/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\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 */\ninterface ExampleBase {\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/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: 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 /** The React Native entry alone is deprecated while the web one stays (ADR-006 §7); a deprecated component says it once, above. */\n readonly deprecated?: Deprecation;\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 /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\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"
|
|
293
376
|
},
|
|
294
377
|
{
|
|
295
378
|
"path": "sliding-indicator.ts",
|
|
@@ -297,19 +380,19 @@
|
|
|
297
380
|
},
|
|
298
381
|
{
|
|
299
382
|
"path": "tabs/index.ts",
|
|
300
|
-
"content": "/** @krizaka/ui/tabs — `Tabs.Root/List/Trigger/Content` (variant underline · segmented · pills), `Tabs.Nav/NavLink` (the same tabs as routes: links, the URL is the state), `tabs` (the slots). Client (\"use client\"). */\nexport {
|
|
383
|
+
"content": "/** @krizaka/ui/tabs — `Tabs.Root/List/Trigger/Content` (variant underline · segmented · pills · binder), `Tabs.Nav/NavLink` (the same tabs as routes: links, the URL is the state), `tabs` (the slots). Client (\"use client\"). */\nexport {\n isTabActive,\n Tabs,\n tabs,\n type TabsAdd,\n TabsContent,\n type TabsHue,\n TabsList,\n type TabsListProps,\n TabsNav,\n TabsNavLink,\n type TabsNavLinkProps,\n type TabsNavProps,\n TabsRoot,\n type TabsRootProps,\n TabsTrigger,\n type TabsTriggerProps,\n type TabsVariants,\n} from \"./tabs\";\n"
|
|
301
384
|
},
|
|
302
385
|
{
|
|
303
386
|
"path": "tabs/meta.ts",
|
|
304
|
-
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Tabs\",\n summary: \"Views of the same page, one at a time: underlined sections, a segmented switch or
|
|
387
|
+
"content": "import type { ComponentMeta } from \"../meta\";\n\nexport const meta = {\n title: \"Tabs\",\n summary: \"Views of the same page, one at a time: underlined sections, a segmented switch, pills or binder dividers that organise collections — one light glides from the active tab to the next.\",\n status: \"beta\",\n category: \"navigation\",\n platforms: \"both\",\n parity: { desktop: \"web\" },\n whenToUse: [\n \"To split a page into sections the user switches between without leaving it: videos, stories, about.\",\n \"`variant=\\\"segmented\\\"` for a view switch (grid, list); `pills` for a feed's sections.\",\n \"`variant=\\\"binder\\\"` to organise a workspace's collections, tags or statuses as binder dividers (intercalaires): a coloured edge (`hue`), the words and a count; best as routes (`Tabs.Nav`), so the address is the state.\",\n ],\n whenNotToUse: [\n { when: \"For a filter that narrows a list.\", use: \"chip\" },\n { when: \"To go to other pages from a tablist: use `Tabs.Nav` (links), not `Tabs.Trigger`.\" },\n { when: \"For a choice in a form.\", use: \"radio-group\" },\n ],\n bestPractices: [\n \"Short labels of one or two words; the active one should be clear without colour.\",\n \"Binder: the colour organises, it never says on its own — the words and the count do. `hue` takes a role of the theme (accent · success · warning · danger · neutral), each 3:1 or more on both themes; give a status divider its status' role. No paper texture: it is the products' register.\",\n \"Binder: « + » (`add`) sits after the dividers, outside the tab list, for creating a collection.\",\n \"Keep the URL in sync with the active tab when a tab is worth sharing: better, use `Tabs.Nav` (tabs as routes), where the route is the state and the active tab is read from it.\",\n ],\n accessibility: {\n keyboard: [\n { keys: \"Tab\", action: \"Moves to the active tab, then into its panel.\" },\n { keys: \"Arrow keys / Home / End\", action: \"Move between tabs and activate them.\" },\n ],\n notes: [\n \"Radix Tabs: `tablist`, `tab` with `aria-selected` and `aria-controls`, `tabpanel`; name the list with `aria-label`.\",\n \"A disabled tab is skipped.\",\n \"A count is part of the tab's name (« Regulars 21 »); a binder divider's colour is decoration (the words say it).\",\n \"The binder's « + » is a button after the list (a tablist holds only tabs), reached by Tab after the tabs.\",\n ],\n },\n related: [\"chip\", \"segmented\", \"radio-group\"],\n web: {\n imports: [\"Tabs\"],\n examples: [\n { name: \"underline\", title: \"Underline\", description: \"The sections of a page; a disabled tab is skipped.\" },\n { name: \"segmented\", title: \"Segmented\", description: \"A view switch: equal segments in a track.\" },\n { name: \"pills\", title: \"Pills\", description: \"A feed's sections, scrolling sideways on a phone.\" },\n { name: \"vertical\", title: \"Vertical\", description: \"`orientation=\\\"vertical\\\"`: the list on the side.\" },\n { name: \"binder\", title: \"Binder dividers\", description: \"`variant=\\\"binder\\\"` as routes: a client's collections as dividers with their colour and count, « + Divider » after them; they scroll sideways with fading edges when they do not fit.\" },\n { name: \"routes\", title: \"Tabs as routes\", description: \"`Tabs.Nav` + `Tabs.NavLink`: each tab is a link, the active one comes from the current route (`aria-current=\\\"page\\\"`), the URL is the state.\" },\n ],\n },\n native: {\n imports: [\"Tabs\"],\n differences: [\n \"`items` (value, label, count, content) instead of the web's parts.\",\n \"The underlined tab list scrolls sideways when it does not fit; for the app's main sections use `TabBar`.\",\n \"`hue` is on each item; `add` is a prop of `Tabs` (no routes on a phone: the selection is the screen's state).\",\n ],\n examples: [\n { name: \"sections\", title: \"Sections\", description: \"A creator's page: underlined tabs with counts.\" },\n { name: \"segmented\", title: \"Segmented\", description: \"Two views of the same content.\" },\n { name: \"binder\", title: \"Binder dividers\", description: \"`variant=\\\"binder\\\"`: collections as dividers with a coloured edge and a count, « + Divider » after them.\" },\n ],\n },\n} satisfies ComponentMeta;\n"
|
|
305
388
|
},
|
|
306
389
|
{
|
|
307
390
|
"path": "tabs/tabs.tsx",
|
|
308
|
-
"content": "// Client (a context). Radix Tabs does the roles (tablist, tab, tabpanel), the aria-selected / aria-controls wiring,\n// the roving focus (arrows, Home, End) and automatic activation: this file only holds the three looks.\nimport { Slot, Tabs as TabsPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { useRouteBinding } from \"../router\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const tabs = tv({\n slots: {\n root: \"flex gap-4 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:flex-row\",\n // One light glides from the active tab to the next (`before:`, placed by useSlidingIndicator); until it is placed\n // (server render), each trigger carries its own active style.\n list:\n \"group/tabs relative isolate flex shrink-0 data-[orientation=vertical]:flex-col \" +\n \"before:pointer-events-none before:absolute before:-z-10 before:opacity-0 before:transition-[translate,width,height,opacity] before:duration-300 \" +\n \"motion-reduce:before:transition-none data-[indicator=on]:before:opacity-100\",\n trigger:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40\",\n content: \"kz-tabs-panel min-w-0 flex-1 rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[state=inactive]:hidden\",\n },\n variants: {\n variant: {\n /* The section of a page: a line under the list, the active tab underlined in the accent. */\n underline: {\n list:\n \"gap-1 border-border-default data-[orientation=horizontal]:border-b data-[orientation=vertical]:border-r before:rounded-sm before:bg-accent \" +\n \"data-[orientation=horizontal]:before:-bottom-px data-[orientation=horizontal]:before:left-0 data-[orientation=horizontal]:before:h-0.5 \" +\n \"data-[orientation=horizontal]:before:w-(--kz-ind-w) data-[orientation=horizontal]:before:translate-x-(--kz-ind-x) \" +\n \"data-[orientation=vertical]:before:-right-px data-[orientation=vertical]:before:top-0 data-[orientation=vertical]:before:w-0.5 \" +\n \"data-[orientation=vertical]:before:h-(--kz-ind-h) data-[orientation=vertical]:before:translate-y-(--kz-ind-y)\",\n trigger:\n \"relative rounded-md px-3 py-2.5 text-sm text-fg-secondary hover:text-fg data-[state=active]:text-fg \" +\n \"after:absolute after:rounded-sm after:bg-transparent data-[state=active]:after:bg-accent \" +\n \"data-[orientation=horizontal]:after:inset-x-2 data-[orientation=horizontal]:after:-bottom-px data-[orientation=horizontal]:after:h-0.5 \" +\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:after:inset-y-1.5 data-[orientation=vertical]:after:-right-px data-[orientation=vertical]:after:w-0.5 \" +\n \"group-data-[indicator=on]/tabs:data-[state=active]:after:bg-transparent\",\n },\n /* A view switch: equal segments in a track, the active one filled. */\n segmented: {\n list:\n \"inline-grid auto-cols-fr grid-flow-col gap-1 self-start rounded-xl border border-border-default bg-surface-1 p-1 data-[orientation=vertical]:grid-flow-row \" +\n \"before:left-0 before:top-0 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) before:rounded-lg before:bg-accent before:shadow-sm\",\n trigger:\n \"min-h-8 rounded-lg px-3 py-1.5 text-xs text-fg-secondary hover:bg-surface-2 hover:text-fg \" +\n \"data-[state=active]:bg-accent data-[state=active]:text-on-accent data-[state=active]:shadow-sm data-[state=active]:hover:bg-accent \" +\n \"transition-colors group-data-[indicator=on]/tabs:data-[state=active]:bg-transparent group-data-[indicator=on]/tabs:data-[state=active]:shadow-none\",\n },\n /* Pills that scroll sideways on a phone: a feed's sections. */\n pills: {\n list:\n \"gap-1.5 overflow-x-auto overflow-y-hidden overscroll-x-contain pt-1 -mt-1 pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden \" +\n \"before:left-0 before:top-0 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-sm before:border before:border-accent before:bg-accent-soft\",\n trigger:\n \"h-9 rounded-sm border border-border-default bg-surface-1 px-4 text-xs text-fg-secondary hover:border-border-strong hover:text-fg \" +\n \"data-[state=active]:border-accent data-[state=active]:bg-accent-soft data-[state=active]:text-fg \" +\n \"group-data-[indicator=on]/tabs:data-[state=active]:border-transparent group-data-[indicator=on]/tabs:data-[state=active]:bg-transparent\",\n },\n },\n },\n defaultVariants: { variant: \"underline\" },\n});\n\nexport type TabsVariants = VariantProps<typeof tabs>;\ntype Variant = NonNullable<TabsVariants[\"variant\"]>;\n\nconst VariantContext = React.createContext<Variant>(\"underline\");\n\nexport type TabsRootProps = React.ComponentProps<typeof TabsPrimitive.Root> & {\n /** underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections). */\n variant?: TabsVariants[\"variant\"];\n /** Binds the active tab to this search parameter of the URL (`?tab=…`): the URL is the state. Without it the state is local — only for an ephemeral choice. */\n routeKey?: string;\n /** The values the parameter may hold (the tabs' `value`s); the first is the default unless `defaultValue` says otherwise. */\n routeValues?: readonly string[];\n};\n\n/** `value`/`defaultValue` + `onValueChange`, `orientation` (horizontal by default), `variant` for the list. */\nexport function TabsRoot({ variant = \"underline\", className, routeKey, routeValues, ...rest }: TabsRootProps) {\n const props = useRouteBinding(routeKey, routeValues, rest);\n return (\n <VariantContext.Provider value={variant}>\n <TabsPrimitive.Root data-variant={variant} className={tabs({ variant }).root({ className })} {...props} />\n </VariantContext.Provider>\n );\n}\n\n/** The tabs. Name it (`aria-label`) when no heading says what it switches. One light glides to the active tab. */\nexport function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>) {\n const variant = React.useContext(VariantContext);\n const { ref } = useSlidingIndicator<HTMLDivElement>({ item: \"[role=tab]\", active: \"[role=tab][data-state=active]\" });\n return <TabsPrimitive.List ref={ref} data-variant={variant} className={tabs({ variant }).list({ className })} {...props} />;\n}\n\nexport function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {\n const variant = React.useContext(VariantContext);\n return <TabsPrimitive.Trigger className={tabs({ variant }).trigger({ className })} {...props} />;\n}\n\nexport function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {\n return <TabsPrimitive.Content className={tabs().content({ className })} {...props} />;\n}\n\n// ── Route mode: tabs that are links; the URL is the state (Oussama, 2026-10-22) ──────────────────────────────────────\n\n/**\n * Is the link `href` the tab of the route `current` (a path, with its search: `/docs/api?tab=x`)? The path must equal the\n * link's (a trailing slash, a locale-free comparison and the hash do not count); with `exact` false (the default) a deeper\n * path of the same section counts too (`/docs/api/x` is in `/docs/api`); every search parameter of the link must be in\n * `current`. The hub link (`/`) is only ever active on `/` itself.\n */\nexport function isTabActive(current: string, href: string, exact = false): boolean {\n const split = (value: string) => {\n const url = new URL(value.split(\"#\")[0]!, \"https://tabs.invalid\");\n return { path: url.pathname.replace(/\\/+$/, \"\") || \"/\", search: url.searchParams };\n };\n const here = split(current);\n const target = split(href);\n const samePath = here.path === target.path;\n const deeper = !exact && target.path !== \"/\" && here.path.startsWith(`${target.path}/`);\n if (!samePath && !deeper) return false;\n for (const [key, value] of target.search) if (here.search.get(key) !== value) return false;\n return true;\n}\n\ninterface NavState {\n current: string;\n variant: Variant;\n}\nconst NavContext = React.createContext<NavState | null>(null);\n\nexport type TabsNavProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n /** The navigation's accessible name (« Sections »). */\n label: string;\n /** The current route, passed in: the path with its search (`usePathname()` + `useSearchParams()`, `/docs?tab=api`). The active tab comes from it; no local state. */\n current: string;\n /** underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections). */\n variant?: TabsVariants[\"variant\"];\n /** The tabs: `Tabs.NavLink`. */\n children: React.ReactNode;\n};\n\n/**\n * Tabs as routes: a `nav` of links, the URL is the state. Per WAI-ARIA these are links, not a tablist: each tab is an\n * anchor, the active one carries `aria-current=\"page\"`, the keyboard is the page's own (Tab walks the links, Enter\n * follows one) and there is no panel — the page is the panel. The same looks and the same gliding light as `Tabs.List`.\n */\nexport function TabsNav({ label, current, variant = \"underline\", className, children, ...props }: TabsNavProps) {\n const { ref } = useSlidingIndicator<HTMLElement>({ item: \"[data-tabs-link]\", active: \"[data-tabs-link][data-state=active]\" });\n const value = React.useMemo(() => ({ current, variant }), [current, variant]);\n return (\n <NavContext.Provider value={value}>\n <nav ref={ref} aria-label={label} data-variant={variant} data-orientation=\"horizontal\" className={tabs({ variant }).list({ className })} {...props}>\n {children}\n </nav>\n </NavContext.Provider>\n );\n}\n\nexport type TabsNavLinkProps = React.ComponentProps<\"a\"> & {\n /** Where the tab goes (`/docs/api`, `/docs?tab=api`). */\n href: string;\n /** Match this path only, not the pages below it. */\n exact?: boolean;\n /** Renders your router's `<Link>` (pass it as the only child; the `href` goes on this part and on the link). */\n asChild?: boolean;\n};\n\n/** One tab of `Tabs.Nav`: an anchor that is active when the current route is its `href`. */\nexport function TabsNavLink({ href, exact, asChild, className, children, ...props }: TabsNavLinkProps) {\n const ctx = React.useContext(NavContext);\n if (!ctx) throw new Error(\"Tabs.NavLink must be inside <Tabs.Nav>\");\n const active = isTabActive(ctx.current, href, exact);\n const Comp = asChild ? Slot.Root : \"a\";\n return (\n <Comp\n data-tabs-link=\"\"\n data-state={active ? \"active\" : \"inactive\"}\n data-orientation=\"horizontal\"\n aria-current={active ? \"page\" : undefined}\n {...(asChild ? {} : { href })}\n className={cn(tabs({ variant: ctx.variant }).trigger(), \"no-underline\", className)}\n {...props}\n >\n {children}\n </Comp>\n );\n}\n\n/** `Tabs.Root/List/Trigger/Content` — `variant` underline · segmented · pills on the root. `Tabs.Nav` + `Tabs.NavLink`: the same tabs as routes. */\nexport const Tabs = { Root: TabsRoot, List: TabsList, Trigger: TabsTrigger, Content: TabsContent, Nav: TabsNav, NavLink: TabsNavLink };\n"
|
|
391
|
+
"content": "// Client (a context). Radix Tabs does the roles (tablist, tab, tabpanel), the aria-selected / aria-controls wiring,\n// the roving focus (arrows, Home, End) and automatic activation: this file only holds the three looks.\nimport { Slot, Tabs as TabsPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { tv, type VariantProps } from \"tailwind-variants\";\n\nimport { cn } from \"../cn\";\nimport { useRouteBinding } from \"../router\";\nimport { useSlidingIndicator } from \"../sliding-indicator\";\n\nexport const tabs = tv({\n slots: {\n root: \"flex gap-4 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:flex-row\",\n // One light glides from the active tab to the next (`before:`, placed by useSlidingIndicator); until it is placed\n // (server render), each trigger carries its own active style.\n list:\n \"group/tabs relative isolate flex shrink-0 data-[orientation=vertical]:flex-col \" +\n \"before:pointer-events-none before:absolute before:-z-10 before:opacity-0 before:transition-[translate,width,height,opacity] before:duration-300 \" +\n \"motion-reduce:before:transition-none data-[indicator=on]:before:opacity-100\",\n trigger:\n \"inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap font-semibold transition-colors \" +\n \"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40\",\n content: \"kz-tabs-panel min-w-0 flex-1 rounded-lg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring data-[state=inactive]:hidden\",\n },\n variants: {\n variant: {\n /* The section of a page: a line under the list, the active tab underlined in the accent. */\n underline: {\n list:\n \"gap-1 border-border-default data-[orientation=horizontal]:border-b data-[orientation=vertical]:border-r before:rounded-sm before:bg-accent \" +\n \"data-[orientation=horizontal]:before:-bottom-px data-[orientation=horizontal]:before:left-0 data-[orientation=horizontal]:before:h-0.5 \" +\n \"data-[orientation=horizontal]:before:w-(--kz-ind-w) data-[orientation=horizontal]:before:translate-x-(--kz-ind-x) \" +\n \"data-[orientation=vertical]:before:-right-px data-[orientation=vertical]:before:top-0 data-[orientation=vertical]:before:w-0.5 \" +\n \"data-[orientation=vertical]:before:h-(--kz-ind-h) data-[orientation=vertical]:before:translate-y-(--kz-ind-y)\",\n trigger:\n \"relative rounded-md px-3 py-2.5 text-sm text-fg-secondary hover:text-fg data-[state=active]:text-fg \" +\n \"after:absolute after:rounded-sm after:bg-transparent data-[state=active]:after:bg-accent \" +\n \"data-[orientation=horizontal]:after:inset-x-2 data-[orientation=horizontal]:after:-bottom-px data-[orientation=horizontal]:after:h-0.5 \" +\n \"data-[orientation=vertical]:justify-start data-[orientation=vertical]:after:inset-y-1.5 data-[orientation=vertical]:after:-right-px data-[orientation=vertical]:after:w-0.5 \" +\n \"group-data-[indicator=on]/tabs:data-[state=active]:after:bg-transparent\",\n },\n /* A view switch: equal segments in a track, the active one filled. */\n segmented: {\n list:\n \"inline-grid auto-cols-fr grid-flow-col gap-1 self-start rounded-xl border border-border-default bg-surface-1 p-1 data-[orientation=vertical]:grid-flow-row \" +\n \"before:left-0 before:top-0 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) before:rounded-lg before:bg-accent before:shadow-sm\",\n trigger:\n \"min-h-8 rounded-lg px-3 py-1.5 text-xs text-fg-secondary hover:bg-surface-2 hover:text-fg \" +\n \"data-[state=active]:bg-accent data-[state=active]:text-on-accent data-[state=active]:shadow-sm data-[state=active]:hover:bg-accent \" +\n \"transition-colors group-data-[indicator=on]/tabs:data-[state=active]:bg-transparent group-data-[indicator=on]/tabs:data-[state=active]:shadow-none\",\n },\n /* Pills that scroll sideways on a phone: a feed's sections. */\n pills: {\n list:\n \"gap-1.5 overflow-x-auto overflow-y-hidden overscroll-x-contain pt-1 -mt-1 pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden \" +\n \"before:left-0 before:top-0 before:h-(--kz-ind-h) before:w-(--kz-ind-w) before:translate-x-(--kz-ind-x) before:translate-y-(--kz-ind-y) \" +\n \"before:rounded-sm before:border before:border-accent before:bg-accent-soft\",\n trigger:\n \"h-9 rounded-sm border border-border-default bg-surface-1 px-4 text-xs text-fg-secondary hover:border-border-strong hover:text-fg \" +\n \"data-[state=active]:border-accent data-[state=active]:bg-accent-soft data-[state=active]:text-fg \" +\n \"group-data-[indicator=on]/tabs:data-[state=active]:border-transparent group-data-[indicator=on]/tabs:data-[state=active]:bg-transparent\",\n },\n /*\n * Binder dividers (intercalaires, #648): organise collections, tags and statuses. Each tab is a divider with a\n * coloured top edge (`hue`), its words and a count; the active one is the page's colour and joins the content\n * under the list's line. Too many dividers scroll sideways, the edges fading. No paper texture: organisation only.\n */\n binder: {\n list:\n \"items-end gap-1 overflow-x-auto overflow-y-hidden overscroll-x-contain border-b border-border-default px-3 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden \" +\n \"[mask-image:linear-gradient(to_right,transparent,black_12px,black_calc(100%-12px),transparent)]\",\n trigger:\n \"relative -mb-px min-h-10 rounded-t-lg border border-b-0 border-border-default bg-surface-2 px-3.5 pb-2 pt-2.5 text-sm text-fg-secondary \" +\n \"before:absolute before:inset-x-0 before:-top-px before:h-1 before:rounded-t-lg before:bg-(--kz-hue,var(--kz-accent)) \" +\n \"hover:bg-surface-3 hover:text-fg data-[state=active]:z-[1] data-[state=active]:bg-surface-1 data-[state=active]:pb-2.5 data-[state=active]:text-fg \" +\n \"data-[state=active]:shadow-[0_1px_0_0_var(--kz-surface-1)] motion-reduce:transition-none\",\n },\n },\n },\n defaultVariants: { variant: \"underline\" },\n});\n\n/**\n * A divider's colour (`variant=\"binder\"`): the brand's accent by default, or a role of the theme — every one reads at 3:1 or\n * more on both themes. The colour is never the only signal: the words and the count say it.\n */\nexport type TabsHue = \"accent\" | \"success\" | \"warning\" | \"danger\" | \"neutral\";\n// The text roles where the fill role is too light on a light page (success, warning: 2:1): every edge reaches 3:1, tested.\nconst HUE: Record<TabsHue, string> = {\n accent: \"var(--kz-accent)\",\n success: \"var(--kz-success-text)\",\n warning: \"var(--kz-warning-text)\",\n danger: \"var(--kz-danger-text)\",\n neutral: \"var(--kz-text-muted)\",\n};\n/** The style that paints a divider's edge (`--kz-hue`), merged with the host's. */\nconst hueStyle = (hue: TabsHue | undefined, style: React.CSSProperties | undefined) =>\n hue ? { ...style, [\"--kz-hue\" as string]: HUE[hue] } : style;\n\n/** The count after a tab's words: part of its name (« Clients 12 »), muted, tabular. */\nfunction TabCount({ count }: { count: number | undefined }) {\n if (count === undefined) return null;\n return <span data-tab-count=\"\" className=\"font-mono text-xs font-medium tabular-nums text-fg-secondary\">{count}</span>;\n}\n\n/** « + Intercalaire »: a button after the dividers (outside the tab list: a tablist holds only tabs). */\nfunction AddButton({ add }: { add: TabsAdd }) {\n return (\n <button\n type=\"button\"\n onClick={add.onAdd}\n className=\"mb-1 ml-1 inline-flex min-h-9 shrink-0 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-fg-secondary hover:bg-surface-2 hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden focusable=\"false\">\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n {add.label}\n </button>\n );\n}\n\nexport type TabsAdd = {\n /** Its words (« Intercalaire », « New tag ») — passed translated. */\n label: string;\n /** Adds a divider (opens the host's form). */\n onAdd: () => void;\n};\n\nexport type TabsVariants = VariantProps<typeof tabs>;\ntype Variant = NonNullable<TabsVariants[\"variant\"]>;\n\nconst VariantContext = React.createContext<Variant>(\"underline\");\n\nexport type TabsRootProps = React.ComponentProps<typeof TabsPrimitive.Root> & {\n /** underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections) · binder (dividers that organise collections, tags, statuses). */\n variant?: TabsVariants[\"variant\"];\n /** Binds the active tab to this search parameter of the URL (`?tab=…`): the URL is the state. Without it the state is local — only for an ephemeral choice. */\n routeKey?: string;\n /** The values the parameter may hold (the tabs' `value`s); the first is the default unless `defaultValue` says otherwise. */\n routeValues?: readonly string[];\n};\n\n/** `value`/`defaultValue` + `onValueChange`, `orientation` (horizontal by default), `variant` for the list. */\nexport function TabsRoot({ variant = \"underline\", className, routeKey, routeValues, ...rest }: TabsRootProps) {\n const props = useRouteBinding(routeKey, routeValues, rest);\n return (\n <VariantContext.Provider value={variant}>\n <TabsPrimitive.Root data-variant={variant} className={tabs({ variant }).root({ className })} {...props} />\n </VariantContext.Provider>\n );\n}\n\nexport type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List> & {\n /** A « + » button after the tabs (`variant=\"binder\"`: « + Intercalaire »): its words and what it does. */\n add?: TabsAdd;\n};\n\n/** The tabs. Name it (`aria-label`) when no heading says what it switches. One light glides to the active tab. */\nexport function TabsList({ className, add, ...props }: TabsListProps) {\n const variant = React.useContext(VariantContext);\n const { ref } = useSlidingIndicator<HTMLDivElement>({ item: \"[role=tab]\", active: \"[role=tab][data-state=active]\" });\n const list = <TabsPrimitive.List ref={ref} data-variant={variant} className={tabs({ variant }).list({ className: cn(add && \"min-w-0 flex-1 border-b-0\", className) })} {...props} />;\n if (!add) return list;\n return (\n <div data-tabs-row=\"\" className={cn(\"flex min-w-0 items-end\", variant === \"binder\" && \"border-b border-border-default\")}>\n {list}\n <AddButton add={add} />\n </div>\n );\n}\n\nexport type TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Trigger> & {\n /** A count after the words (clients in a collection, unread): part of the tab's name. */\n count?: number;\n /** `variant=\"binder\"`: the divider's colour (the accent by default). */\n hue?: TabsHue;\n};\n\nexport function TabsTrigger({ className, count, hue, style, children, ...props }: TabsTriggerProps) {\n const variant = React.useContext(VariantContext);\n return (\n <TabsPrimitive.Trigger className={tabs({ variant }).trigger({ className })} style={hueStyle(hue, style)} data-hue={hue} {...props}>\n {children}\n <TabCount count={count} />\n </TabsPrimitive.Trigger>\n );\n}\n\nexport function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {\n return <TabsPrimitive.Content className={tabs().content({ className })} {...props} />;\n}\n\n// ── Route mode: tabs that are links; the URL is the state (Oussama, 2026-10-22) ──────────────────────────────────────\n\n/**\n * Is the link `href` the tab of the route `current` (a path, with its search: `/docs/api?tab=x`)? The path must equal the\n * link's (a trailing slash, a locale-free comparison and the hash do not count); with `exact` false (the default) a deeper\n * path of the same section counts too (`/docs/api/x` is in `/docs/api`); every search parameter of the link must be in\n * `current`. The hub link (`/`) is only ever active on `/` itself.\n */\nexport function isTabActive(current: string, href: string, exact = false): boolean {\n const split = (value: string) => {\n const url = new URL(value.split(\"#\")[0]!, \"https://tabs.invalid\");\n return { path: url.pathname.replace(/\\/+$/, \"\") || \"/\", search: url.searchParams };\n };\n const here = split(current);\n const target = split(href);\n const samePath = here.path === target.path;\n const deeper = !exact && target.path !== \"/\" && here.path.startsWith(`${target.path}/`);\n if (!samePath && !deeper) return false;\n for (const [key, value] of target.search) if (here.search.get(key) !== value) return false;\n return true;\n}\n\ninterface NavState {\n current: string;\n variant: Variant;\n}\nconst NavContext = React.createContext<NavState | null>(null);\n\nexport type TabsNavProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n /** A « + » button after the tabs (`variant=\"binder\"`: « + Intercalaire »). */\n add?: TabsAdd;\n /** The navigation's accessible name (« Sections »). */\n label: string;\n /** The current route, passed in: the path with its search (`usePathname()` + `useSearchParams()`, `/docs?tab=api`). The active tab comes from it; no local state. */\n current: string;\n /** underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections) · binder (dividers). */\n variant?: TabsVariants[\"variant\"];\n /** The tabs: `Tabs.NavLink`. */\n children: React.ReactNode;\n};\n\n/**\n * Tabs as routes: a `nav` of links, the URL is the state. Per WAI-ARIA these are links, not a tablist: each tab is an\n * anchor, the active one carries `aria-current=\"page\"`, the keyboard is the page's own (Tab walks the links, Enter\n * follows one) and there is no panel — the page is the panel. The same looks and the same gliding light as `Tabs.List`.\n */\nexport function TabsNav({ label, current, variant = \"underline\", add, className, children, ...props }: TabsNavProps) {\n const { ref } = useSlidingIndicator<HTMLElement>({ item: \"[data-tabs-link]\", active: \"[data-tabs-link][data-state=active]\" });\n const value = React.useMemo(() => ({ current, variant }), [current, variant]);\n const nav = (\n <nav ref={ref} aria-label={label} data-variant={variant} data-orientation=\"horizontal\" className={tabs({ variant }).list({ className: cn(add && \"min-w-0 flex-1 border-b-0\", className) })} {...props}>\n {children}\n </nav>\n );\n return (\n <NavContext.Provider value={value}>\n {add ? (\n <div data-tabs-row=\"\" className={cn(\"flex min-w-0 items-end\", variant === \"binder\" && \"border-b border-border-default\")}>\n {nav}\n <AddButton add={add} />\n </div>\n ) : (\n nav\n )}\n </NavContext.Provider>\n );\n}\n\nexport type TabsNavLinkProps = React.ComponentProps<\"a\"> & {\n /** Where the tab goes (`/docs/api`, `/docs?tab=api`). */\n href: string;\n /** Match this path only, not the pages below it. */\n exact?: boolean;\n /** Renders your router's `<Link>` (pass it as the only child; the `href` goes on this part and on the link). */\n asChild?: boolean;\n /** A count after the words (not with `asChild`: put it in your link). */\n count?: number;\n /** `variant=\"binder\"`: the divider's colour (the accent by default). */\n hue?: TabsHue;\n};\n\n/** One tab of `Tabs.Nav`: an anchor that is active when the current route is its `href`. */\nexport function TabsNavLink({ href, exact, asChild, count, hue, style, className, children, ...props }: TabsNavLinkProps) {\n const ctx = React.useContext(NavContext);\n if (!ctx) throw new Error(\"Tabs.NavLink must be inside <Tabs.Nav>\");\n const active = isTabActive(ctx.current, href, exact);\n const Comp = asChild ? Slot.Root : \"a\";\n return (\n <Comp\n data-tabs-link=\"\"\n data-state={active ? \"active\" : \"inactive\"}\n data-orientation=\"horizontal\"\n aria-current={active ? \"page\" : undefined}\n {...(asChild ? {} : { href })}\n data-hue={hue}\n style={hueStyle(hue, style)}\n className={cn(tabs({ variant: ctx.variant }).trigger(), \"no-underline\", className)}\n {...props}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n <TabCount count={count} />\n </>\n )}\n </Comp>\n );\n}\n\n/** `Tabs.Root/List/Trigger/Content` — `variant` underline · segmented · pills · binder on the root. `Tabs.Nav` + `Tabs.NavLink`: the same tabs as routes. */\nexport const Tabs = { Root: TabsRoot, List: TabsList, Trigger: TabsTrigger, Content: TabsContent, Nav: TabsNav, NavLink: TabsNavLink };\n"
|
|
309
392
|
}
|
|
310
393
|
],
|
|
311
394
|
"dependencies": [
|
|
312
|
-
"radix-ui@^1.
|
|
395
|
+
"radix-ui@^1.8.1",
|
|
313
396
|
"tailwind-variants@^3.3.1"
|
|
314
397
|
],
|
|
315
398
|
"registryDependencies": [
|
|
@@ -341,9 +424,9 @@
|
|
|
341
424
|
},
|
|
342
425
|
{
|
|
343
426
|
"name": "variant",
|
|
344
|
-
"type": "\"segmented\" | \"underline\" | \"pills\"",
|
|
427
|
+
"type": "\"segmented\" | \"underline\" | \"pills\" | \"binder\"",
|
|
345
428
|
"default": "underline",
|
|
346
|
-
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections).",
|
|
429
|
+
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections) · binder (dividers that organise collections, tags, statuses).",
|
|
347
430
|
"required": false
|
|
348
431
|
}
|
|
349
432
|
]
|
|
@@ -351,7 +434,35 @@
|
|
|
351
434
|
{
|
|
352
435
|
"component": "TabsList",
|
|
353
436
|
"description": "The tabs. Name it (`aria-label`) when no heading says what it switches. One light glides to the active tab.",
|
|
354
|
-
"props": [
|
|
437
|
+
"props": [
|
|
438
|
+
{
|
|
439
|
+
"name": "add",
|
|
440
|
+
"type": "TabsAdd",
|
|
441
|
+
"default": null,
|
|
442
|
+
"description": "A « + » button after the tabs (`variant=\"binder\"`: « + Intercalaire »): its words and what it does.",
|
|
443
|
+
"required": false
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"component": "TabsTrigger",
|
|
449
|
+
"description": "",
|
|
450
|
+
"props": [
|
|
451
|
+
{
|
|
452
|
+
"name": "count",
|
|
453
|
+
"type": "number",
|
|
454
|
+
"default": null,
|
|
455
|
+
"description": "A count after the words (clients in a collection, unread): part of the tab's name.",
|
|
456
|
+
"required": false
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"name": "hue",
|
|
460
|
+
"type": "TabsHue",
|
|
461
|
+
"default": null,
|
|
462
|
+
"description": "`variant=\"binder\"`: the divider's colour (the accent by default).",
|
|
463
|
+
"required": false
|
|
464
|
+
}
|
|
465
|
+
]
|
|
355
466
|
},
|
|
356
467
|
{
|
|
357
468
|
"component": "TabsNav",
|
|
@@ -378,11 +489,18 @@
|
|
|
378
489
|
"description": "The navigation's accessible name (« Sections »).",
|
|
379
490
|
"required": true
|
|
380
491
|
},
|
|
492
|
+
{
|
|
493
|
+
"name": "add",
|
|
494
|
+
"type": "TabsAdd",
|
|
495
|
+
"default": null,
|
|
496
|
+
"description": "A « + » button after the tabs (`variant=\"binder\"`: « + Intercalaire »).",
|
|
497
|
+
"required": false
|
|
498
|
+
},
|
|
381
499
|
{
|
|
382
500
|
"name": "variant",
|
|
383
|
-
"type": "\"segmented\" | \"underline\" | \"pills\"",
|
|
501
|
+
"type": "\"segmented\" | \"underline\" | \"pills\" | \"binder\"",
|
|
384
502
|
"default": "underline",
|
|
385
|
-
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections).",
|
|
503
|
+
"description": "underline (the sections of a page) · segmented (a view switch) · pills (a feed's sections) · binder (dividers).",
|
|
386
504
|
"required": false
|
|
387
505
|
}
|
|
388
506
|
]
|
|
@@ -398,12 +516,26 @@
|
|
|
398
516
|
"description": "Renders your router's `<Link>` (pass it as the only child; the `href` goes on this part and on the link).",
|
|
399
517
|
"required": false
|
|
400
518
|
},
|
|
519
|
+
{
|
|
520
|
+
"name": "count",
|
|
521
|
+
"type": "number",
|
|
522
|
+
"default": null,
|
|
523
|
+
"description": "A count after the words (not with `asChild`: put it in your link).",
|
|
524
|
+
"required": false
|
|
525
|
+
},
|
|
401
526
|
{
|
|
402
527
|
"name": "exact",
|
|
403
528
|
"type": "boolean",
|
|
404
529
|
"default": null,
|
|
405
530
|
"description": "Match this path only, not the pages below it.",
|
|
406
531
|
"required": false
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"name": "hue",
|
|
535
|
+
"type": "TabsHue",
|
|
536
|
+
"default": null,
|
|
537
|
+
"description": "`variant=\"binder\"`: the divider's colour (the accent by default).",
|
|
538
|
+
"required": false
|
|
407
539
|
}
|
|
408
540
|
]
|
|
409
541
|
}
|
package/registry/tag-input.json
CHANGED
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
226
|
"path": "meta.ts",
|
|
227
|
-
"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 ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<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 | \"agenda\"\n | \"account-settings\"\n | \"drop-overlay\"\n | \"upload-queue\"\n | \"achievement\"\n | \"anatomy-plate\"\n | \"bird-plate\"\n | \"taped-photo\"\n | \"text-style\"\n | \"flock\"\n | \"elephant\"\n | \"footnote\"\n | \"page-index\"\n | \"chapter\"\n | \"specimen-tag\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glossary\"\n | \"glossary-term\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"kpi-explained\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"radio-group\"\n | \"router\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\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/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\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 */\ninterface ExampleBase {\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/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: 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 /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\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"
|
|
227
|
+
"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 ui.krizaka.com (apps/site) renders as\n// ui.krizaka.com/components/<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 | \"notification-preferences\"\n | \"quota-meter\"\n | \"agenda\"\n | \"account-settings\"\n | \"drop-overlay\"\n | \"upload-queue\"\n | \"achievement\"\n | \"anatomy-plate\"\n | \"bird-plate\"\n | \"taped-photo\"\n | \"stuck\"\n | \"doodle\"\n | \"signature\"\n | \"text-style\"\n | \"flock\"\n | \"elephant\"\n | \"footnote\"\n | \"page-index\"\n | \"chapter\"\n | \"specimen-tag\"\n | \"action-bar\"\n | \"action-sheet\"\n | \"activity-feed\"\n | \"alert\"\n | \"app-header\"\n | \"app-shell\"\n | \"argument-card\"\n | \"audio-player\"\n | \"auth-layout\"\n | \"avatar\"\n | \"badge\"\n | \"button\"\n | \"captcha\"\n | \"card\"\n | \"card-grid\"\n | \"catalogue-page\"\n | \"changelog-page\"\n | \"chat\"\n | \"checkbox\"\n | \"chip\"\n | \"chip-link\"\n | \"chip-row\"\n | \"choice-grid\"\n | \"cn\"\n | \"code-block\"\n | \"combobox\"\n | \"comparison-table\"\n | \"command\"\n | \"component-page\"\n | \"confirm-button\"\n | \"contact-page\"\n | \"continue-prompt\"\n | \"countdown\"\n | \"cover-hero\"\n | \"cta-panel\"\n | \"dashboard-layout\"\n | \"data-table\"\n | \"date-picker\"\n | \"decision-list\"\n | \"description-list\"\n | \"detail-layout\"\n | \"dialog\"\n | \"docs-article-page\"\n | \"docs-hub-page\"\n | \"docs-layout\"\n | \"docs-nav\"\n | \"document-viewer\"\n | \"dropdown-menu\"\n | \"empty-state\"\n | \"engine-flow\"\n | \"error-page\"\n | \"fab\"\n | \"field\"\n | \"file-upload\"\n | \"filter-bar\"\n | \"force-update-screen\"\n | \"form\"\n | \"gallery-page\"\n | \"genui\"\n | \"glossary\"\n | \"glossary-term\"\n | \"glow-card\"\n | \"goal-gauge\"\n | \"header\"\n | \"how-it-works-page\"\n | \"image-cropper\"\n | \"image-viewer\"\n | \"iso\"\n | \"kanban\"\n | \"kbd\"\n | \"kpi-explained\"\n | \"landing-page\"\n | \"landscape\"\n | \"legal-page\"\n | \"link-card\"\n | \"list\"\n | \"loader\"\n | \"longform-page\"\n | \"maintenance-screen\"\n | \"map\"\n | \"markdown\"\n | \"meta-row\"\n | \"waveform\"\n | \"marketing-layout\"\n | \"marquee\"\n | \"mascot-quote\"\n | \"mascot-state\"\n | \"mascots\"\n | \"match-dialog\"\n | \"media-capture\"\n | \"media-grid\"\n | \"language-switch\"\n | \"media-gallery\"\n | \"media-picker\"\n | \"meter\"\n | \"nav-bar\"\n | \"number-ticker\"\n | \"onboarding\"\n | \"opening-hours\"\n | \"otp-input\"\n | \"page\"\n | \"page-header\"\n | \"page-hero\"\n | \"pagination\"\n | \"permission-prompt\"\n | \"photo-grid\"\n | \"pointer-light\"\n | \"popover\"\n | \"pricing-page\"\n | \"product-page\"\n | \"profile-card\"\n | \"progress\"\n | \"promise-card\"\n | \"pull-to-refresh\"\n | \"retro-tv\"\n | \"radio-group\"\n | \"router\"\n | \"rail-nav\"\n | \"recipes\"\n | \"screen\"\n | \"scroll-stage\"\n | \"search-field\"\n | \"section-backdrop\"\n | \"segmented\"\n | \"separator\"\n | \"service-card\"\n | \"settings-layout\"\n | \"account-menu\"\n | \"showcase\"\n | \"step-showcase\"\n | \"site-footer\"\n | \"sites\"\n | \"skeleton\"\n | \"skip-link\"\n | \"slider\"\n | \"slot-picker\"\n | \"slot\"\n | \"spinner\"\n | \"sticky-action\"\n | \"splash-handoff\"\n | \"split-view\"\n | \"stage-banner\"\n | \"stat\"\n | \"status-page\"\n | \"step-progress\"\n | \"stepper\"\n | \"swipe-deck\"\n | \"swipeable-row\"\n | \"switch\"\n | \"tab-bar\"\n | \"tabs\"\n | \"tag-input\"\n | \"tag-selector\"\n | \"terminal-replay\"\n | \"theme\"\n | \"toast\"\n | \"toc\"\n | \"tooltip\"\n | \"tree-view\"\n | \"txt\"\n | \"update-prompt\"\n | \"verified-badge\"\n | \"vertical-video\"\n | \"video-player\"\n | \"video-trimmer\"\n | \"virtual\";\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/**\n * Why a component is not on a platform — a decision, never a silence (the parity matrix of ui.krizaka.com/platforms):\n * - `gap` (✗): it belongs there and is not built yet; `issue` is the issue that tracks it (`krizaka/krizaka-ui#275`);\n * - `n/a`: it is not meant for that platform (a site's page block on a phone, a swipe on a desktop); `use` names the\n * component that answers there, when one does.\n */\nexport type Absence =\n | { readonly status: \"gap\"; readonly issue: string; readonly reason: string; readonly use?: undefined }\n | { readonly status: \"n/a\"; readonly reason: string; readonly use?: ComponentName; readonly issue?: undefined };\n\n/**\n * Desktop is the Orazaka Mac app (Tauri 2: the system WebView renders the web components — ADR-002). `web`: the web\n * component runs there as it is; `desktop`: it also has a desktop form (a window's title bar, a denser layout…), said in\n * its documentation; otherwise the absence and why.\n */\nexport type DesktopSupport = \"web\" | \"desktop\" | Absence;\n\n/**\n * The three platforms of Krizaka UI — web · native (React Native, Expo) · desktop — for one component. The side a\n * component has is `platforms`; the side it lacks says why here: `native` is required for (and only for) a web-only\n * component, `web` for a native-only one; `desktop` always. `scripts/registry.test.mjs` fails on a missing decision.\n */\nexport interface Parity {\n readonly web?: Absence;\n readonly native?: Absence;\n readonly desktop: DesktopSupport;\n}\n\n/**\n * `stable`: its API only changes in a major. `beta`: it may still change in a minor. `deprecated`: no product uses it and\n * no work plans to — it still works, says what replaces it, and goes in the major named by `deprecated.removedIn`.\n */\nexport type Status = \"stable\" | \"beta\" | \"deprecated\";\n\n/** What a deprecated component says (required when `status` is `deprecated`, absent otherwise). */\nexport interface Deprecation {\n /** The version that deprecated it (`3.6.0`). */\n readonly since: string;\n /** The major that removes it (`4.0.0`). */\n readonly removedIn: string;\n /** What replaces it, when a component does. */\n readonly use?: ComponentName;\n /** Why it goes: no product uses it, no planned work needs it. */\n readonly reason: string;\n}\n\n/**\n * The group of the catalogue. `media`: video, sound, camera, files, cropping. `blocks`: the page blocks — the frame,\n * the hero, sections, cards that lead or argue, decisions, code, calls to act — that every page of a site is made of.\n * `layouts`: the shells of whole screens — an application, a dashboard, settings, sign-in, an object, a list and its\n * item, a site's page, a documentation page — each with its screens. `templates`: the page templates — whole pages of a\n * site (a landing, a product, a docs hub, a long read, a changelog, pricing, status, an error…), each a distinct\n * composition of the blocks, words and content as props.\n */\nexport type Category = \"actions\" | \"forms\" | \"navigation\" | \"overlays\" | \"feedback\" | \"data-display\" | \"media\" | \"blocks\" | \"layouts\" | \"templates\" | \"layout\" | \"foundations\";\n\n/** Why a reader needs a template: the seven intents the templates are grouped by (sidebar sub-headings, catalogue filter). */\nexport type TemplatePurpose = \"convince\" | \"explain\" | \"show\" | \"act\" | \"operate\" | \"converse\" | \"recover\";\n\n/** The groups of templates, in reading order: the title, and the one line that says when to come to that group. */\nexport const templatePurposes = {\n convince: { title: \"Convince\", useWhen: \"You come here when a visitor must decide: a landing, a product, a price, a comparison, a proof.\" },\n explain: { title: \"Explain\", useWhen: \"You come here when a reader must understand: how it works, a documentation hub or article, a changelog, a FAQ, a policy.\" },\n show: { title: \"Show\", useWhen: \"You come here when the work itself is the argument: a gallery, a story, a case study, an object and its details.\" },\n act: { title: \"Act\", useWhen: \"You come here when a person must do something once: reach the team, sign in or up, set up, pay.\" },\n operate: { title: \"Operate\", useWhen: \"You come here when a person works every day: a dashboard, a console with a table, settings, an app shell.\" },\n converse: { title: \"Converse\", useWhen: \"You come here when people talk: an inbox, a chat, comments, a notifications centre.\" },\n recover: { title: \"Recover\", useWhen: \"You come here when the way is blocked: an empty list, an error, a missing page, maintenance, a product still being built.\" },\n} as const satisfies Record<TemplatePurpose, { readonly title: string; readonly useWhen: string }>;\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 */\ninterface ExampleBase {\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/** A plain example, or a template: a whole screen with its intent (`purpose`) and the user goal its layout serves (`why`). */\nexport type Example =\n | (ExampleBase & { readonly template?: false; readonly purpose?: undefined; readonly why?: undefined })\n | (ExampleBase & {\n /**\n * A template: a whole screen of a product composed from the platform (a layout and its blocks, real words, its\n * phone version thought out), copied as a starting point — at most 160 lines, listed on ui.krizaka.com/templates.\n */\n readonly template: true;\n /** The intent that groups it: why you need this block. */\n readonly purpose: TemplatePurpose;\n /** Why this layout: the user goal it serves, in one sentence. */\n readonly why: 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 /** The React Native entry alone is deprecated while the web one stays (ADR-006 §7); a deprecated component says it once, above. */\n readonly deprecated?: Deprecation;\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 /** Required for, and only for, a `deprecated` component. */\n readonly deprecated?: Deprecation;\n readonly category: Category;\n /** Must match the sides below: `both` ⇔ `web` and `native`. */\n readonly platforms: Platform;\n /** The platforms it is not on, and why; and its desktop form (see `Parity`). */\n readonly parity: Parity;\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"
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
"path": "tag-input/index.ts",
|