@elabs-ai/components-ui 4.0.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/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/index.d.ts +4497 -0
- package/dist/index.js +15192 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.js +20 -0
- package/dist/lib/cn.js.map +1 -0
- package/package.json +106 -0
- package/src/blocks/sidebar-02/app-sidebar.tsx +160 -0
- package/src/blocks/sidebar-02/logo.tsx +18 -0
- package/src/blocks/sidebar-02/nav-main.tsx +13 -0
- package/src/blocks/sidebar-02/nav-notifications.tsx +7 -0
- package/src/blocks/sidebar-02/sidebar-02.stories.tsx +23 -0
- package/src/blocks/sidebar-02/team-switcher.tsx +7 -0
- package/src/blocks/sidebar-04/app-sidebar.tsx +236 -0
- package/src/blocks/sidebar-04/mail-context.tsx +29 -0
- package/src/blocks/sidebar-04/nav-user.tsx +6 -0
- package/src/blocks/sidebar-04/sidebar-04.stories.tsx +150 -0
- package/src/blocks/sidebar-05/app-sidebar.tsx +375 -0
- package/src/blocks/sidebar-05/sidebar-05.stories.tsx +18 -0
- package/src/blocks/sidebar-05/team-switcher.tsx +6 -0
- package/src/blocks-comparison-table.stories.tsx +165 -0
- package/src/components/accordion/accordion.stories.tsx +71 -0
- package/src/components/accordion/accordion.test.tsx +18 -0
- package/src/components/accordion/accordion.tsx +49 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/advanced-group/advanced-group.stories.tsx +143 -0
- package/src/components/advanced-group/advanced-group.test.tsx +81 -0
- package/src/components/advanced-group/advanced-group.tsx +111 -0
- package/src/components/advanced-group/index.ts +2 -0
- package/src/components/alert/alert.stories.tsx +156 -0
- package/src/components/alert/alert.test.tsx +57 -0
- package/src/components/alert/alert.tsx +91 -0
- package/src/components/alert/index.ts +8 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +73 -0
- package/src/components/alert-dialog/alert-dialog.tsx +104 -0
- package/src/components/alert-dialog/index.ts +12 -0
- package/src/components/app-shell/app-shell.stories.tsx +61 -0
- package/src/components/app-shell/app-shell.tsx +31 -0
- package/src/components/app-shell/index.ts +1 -0
- package/src/components/app-sidebar/app-sidebar.stories.tsx +121 -0
- package/src/components/app-sidebar/app-sidebar.test.tsx +55 -0
- package/src/components/app-sidebar/app-sidebar.tsx +53 -0
- package/src/components/app-sidebar/index.ts +1 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +46 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +3 -0
- package/src/components/aspect-ratio/index.ts +1 -0
- package/src/components/attribution-panel/attribution-panel.stories.tsx +83 -0
- package/src/components/attribution-panel/attribution-panel.test.tsx +123 -0
- package/src/components/attribution-panel/attribution-panel.tsx +191 -0
- package/src/components/attribution-panel/attribution-types.ts +44 -0
- package/src/components/attribution-panel/attributions.generated.ts +1422 -0
- package/src/components/attribution-panel/index.ts +3 -0
- package/src/components/avatar/avatar.stories.tsx +45 -0
- package/src/components/avatar/avatar.test.tsx +13 -0
- package/src/components/avatar/avatar.tsx +46 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/badge/badge.stories.tsx +44 -0
- package/src/components/badge/badge.test.tsx +10 -0
- package/src/components/badge/badge.tsx +28 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/bento-grid/bento-grid.stories.tsx +165 -0
- package/src/components/bento-grid/bento-grid.test.tsx +119 -0
- package/src/components/bento-grid/bento-grid.tsx +339 -0
- package/src/components/bento-grid/index.ts +9 -0
- package/src/components/bounded-number/bounded-number.stories.tsx +118 -0
- package/src/components/bounded-number/bounded-number.test.tsx +73 -0
- package/src/components/bounded-number/bounded-number.tsx +110 -0
- package/src/components/bounded-number/index.ts +1 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +47 -0
- package/src/components/breadcrumb/breadcrumb.test.tsx +17 -0
- package/src/components/breadcrumb/breadcrumb.tsx +86 -0
- package/src/components/breadcrumb/index.ts +9 -0
- package/src/components/button/button.stories.tsx +70 -0
- package/src/components/button/button.test.tsx +50 -0
- package/src/components/button/button.tsx +63 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/button-group/button-group.stories.tsx +45 -0
- package/src/components/button-group/button-group.tsx +70 -0
- package/src/components/button-group/index.ts +6 -0
- package/src/components/calendar/calendar.stories.tsx +95 -0
- package/src/components/calendar/calendar.test.tsx +140 -0
- package/src/components/calendar/calendar.tsx +70 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/card/card.stories.tsx +438 -0
- package/src/components/card/card.test.tsx +153 -0
- package/src/components/card/card.tsx +332 -0
- package/src/components/card/index.ts +13 -0
- package/src/components/carousel/carousel.stories.tsx +102 -0
- package/src/components/carousel/carousel.tsx +225 -0
- package/src/components/carousel/index.ts +8 -0
- package/src/components/change-review/change-review.stories.tsx +288 -0
- package/src/components/change-review/change-review.test.tsx +261 -0
- package/src/components/change-review/change-review.tsx +637 -0
- package/src/components/change-review/index.ts +23 -0
- package/src/components/checkbox/checkbox.stories.tsx +119 -0
- package/src/components/checkbox/checkbox.test.tsx +40 -0
- package/src/components/checkbox/checkbox.tsx +34 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/collapsible/collapsible.stories.tsx +62 -0
- package/src/components/collapsible/collapsible.tsx +22 -0
- package/src/components/collapsible/index.ts +1 -0
- package/src/components/collapsible-panel/index.ts +8 -0
- package/src/components/collapsible-panel/use-collapsible-panel.test.tsx +93 -0
- package/src/components/collapsible-panel/use-collapsible-panel.ts +159 -0
- package/src/components/color-picker/color-picker.stories.tsx +93 -0
- package/src/components/color-picker/color-picker.test.tsx +79 -0
- package/src/components/color-picker/color-picker.tsx +341 -0
- package/src/components/color-picker/index.ts +1 -0
- package/src/components/combobox/combobox.stories.tsx +154 -0
- package/src/components/combobox/combobox.test.tsx +246 -0
- package/src/components/combobox/combobox.tsx +210 -0
- package/src/components/combobox/index.ts +1 -0
- package/src/components/command/command.stories.tsx +371 -0
- package/src/components/command/command.test.tsx +280 -0
- package/src/components/command/command.tsx +301 -0
- package/src/components/command/index.ts +13 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +183 -0
- package/src/components/confirm-dialog/confirm-dialog.test.tsx +147 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +146 -0
- package/src/components/confirm-dialog/index.ts +2 -0
- package/src/components/context-menu/context-menu.stories.tsx +70 -0
- package/src/components/context-menu/context-menu.tsx +172 -0
- package/src/components/context-menu/index.ts +16 -0
- package/src/components/copyable-value/copyable-value.stories.tsx +80 -0
- package/src/components/copyable-value/copyable-value.test.tsx +128 -0
- package/src/components/copyable-value/copyable-value.tsx +101 -0
- package/src/components/copyable-value/index.ts +1 -0
- package/src/components/date-picker/date-picker.stories.tsx +42 -0
- package/src/components/date-picker/date-picker.tsx +49 -0
- package/src/components/date-picker/index.ts +1 -0
- package/src/components/date-range-picker/date-range-picker.stories.tsx +151 -0
- package/src/components/date-range-picker/date-range-picker.test.tsx +62 -0
- package/src/components/date-range-picker/date-range-picker.tsx +126 -0
- package/src/components/date-range-picker/index.ts +6 -0
- package/src/components/descriptions/descriptions.stories.tsx +43 -0
- package/src/components/descriptions/descriptions.test.tsx +41 -0
- package/src/components/descriptions/descriptions.tsx +95 -0
- package/src/components/descriptions/index.ts +6 -0
- package/src/components/dialog/dialog.stories.tsx +728 -0
- package/src/components/dialog/dialog.test.tsx +285 -0
- package/src/components/dialog/dialog.tsx +340 -0
- package/src/components/dialog/index.ts +18 -0
- package/src/components/dialog/use-dialog-dismiss-guard.test.tsx +80 -0
- package/src/components/dialog/use-dialog-dismiss-guard.ts +109 -0
- package/src/components/drawer/drawer.stories.tsx +76 -0
- package/src/components/drawer/drawer.tsx +85 -0
- package/src/components/drawer/index.ts +11 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +176 -0
- package/src/components/dropdown-menu/dropdown-menu.test.tsx +109 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +179 -0
- package/src/components/dropdown-menu/index.ts +17 -0
- package/src/components/empty-state/empty-state.stories.tsx +54 -0
- package/src/components/empty-state/empty-state.test.tsx +11 -0
- package/src/components/empty-state/empty-state.tsx +30 -0
- package/src/components/empty-state/index.ts +1 -0
- package/src/components/error-state/error-state.stories.tsx +51 -0
- package/src/components/error-state/error-state.tsx +27 -0
- package/src/components/error-state/index.ts +1 -0
- package/src/components/expand-dialog/expand-dialog.stories.tsx +246 -0
- package/src/components/expand-dialog/expand-dialog.test.tsx +185 -0
- package/src/components/expand-dialog/expand-dialog.tsx +387 -0
- package/src/components/expand-dialog/index.ts +11 -0
- package/src/components/field-row/field-row.stories.tsx +114 -0
- package/src/components/field-row/field-row.test.tsx +188 -0
- package/src/components/field-row/field-row.tsx +115 -0
- package/src/components/field-row/index.ts +1 -0
- package/src/components/file-upload/file-upload.stories.tsx +183 -0
- package/src/components/file-upload/file-upload.test.tsx +169 -0
- package/src/components/file-upload/file-upload.tsx +459 -0
- package/src/components/file-upload/index.ts +13 -0
- package/src/components/form/form.stories.tsx +71 -0
- package/src/components/form/form.tsx +141 -0
- package/src/components/form/index.ts +10 -0
- package/src/components/hover-card/hover-card.stories.tsx +57 -0
- package/src/components/hover-card/hover-card.tsx +25 -0
- package/src/components/hover-card/index.ts +1 -0
- package/src/components/icon-button/icon-button.stories.tsx +108 -0
- package/src/components/icon-button/icon-button.test.tsx +93 -0
- package/src/components/icon-button/icon-button.tsx +139 -0
- package/src/components/icon-button/index.ts +1 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.stories.tsx +44 -0
- package/src/components/input/input.test.tsx +19 -0
- package/src/components/input/input.tsx +26 -0
- package/src/components/input-group/index.ts +11 -0
- package/src/components/input-group/input-group.stories.tsx +87 -0
- package/src/components/input-group/input-group.test.tsx +48 -0
- package/src/components/input-group/input-group.tsx +170 -0
- package/src/components/input-otp/index.ts +1 -0
- package/src/components/input-otp/input-otp.stories.tsx +66 -0
- package/src/components/input-otp/input-otp.tsx +68 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.stories.tsx +45 -0
- package/src/components/kbd/kbd.tsx +20 -0
- package/src/components/key-value-editor/index.ts +1 -0
- package/src/components/key-value-editor/key-value-editor.stories.tsx +130 -0
- package/src/components/key-value-editor/key-value-editor.test.tsx +231 -0
- package/src/components/key-value-editor/key-value-editor.tsx +263 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.stories.tsx +35 -0
- package/src/components/label/label.tsx +19 -0
- package/src/components/link-preview/index.ts +2 -0
- package/src/components/link-preview/link-preview.stories.tsx +178 -0
- package/src/components/link-preview/link-preview.test.tsx +123 -0
- package/src/components/link-preview/link-preview.tsx +194 -0
- package/src/components/list-editor/index.ts +1 -0
- package/src/components/list-editor/list-editor.stories.tsx +119 -0
- package/src/components/list-editor/list-editor.test.tsx +96 -0
- package/src/components/list-editor/list-editor.tsx +192 -0
- package/src/components/loading-state/index.ts +1 -0
- package/src/components/loading-state/loading-state.stories.tsx +38 -0
- package/src/components/loading-state/loading-state.tsx +26 -0
- package/src/components/locale-provider/index.ts +8 -0
- package/src/components/locale-provider/locale-provider.stories.tsx +83 -0
- package/src/components/locale-provider/locale-provider.test.tsx +238 -0
- package/src/components/locale-provider/locale-provider.tsx +187 -0
- package/src/components/locale-provider/messages.ts +300 -0
- package/src/components/match-highlight/index.ts +7 -0
- package/src/components/match-highlight/match-highlight.stories.tsx +146 -0
- package/src/components/match-highlight/match-highlight.test.tsx +202 -0
- package/src/components/match-highlight/match-highlight.tsx +145 -0
- package/src/components/mention-input/index.ts +28 -0
- package/src/components/mention-input/mention-input-mirror.stories.tsx +171 -0
- package/src/components/mention-input/mention-input.stories.tsx +463 -0
- package/src/components/mention-input/mention-input.test.tsx +617 -0
- package/src/components/mention-input/mention-input.tsx +1102 -0
- package/src/components/mention-input/mention-value.test.ts +253 -0
- package/src/components/mention-input/mention-value.ts +193 -0
- package/src/components/menubar/index.ts +17 -0
- package/src/components/menubar/menubar.stories.tsx +67 -0
- package/src/components/menubar/menubar.tsx +198 -0
- package/src/components/metric-card/index.ts +1 -0
- package/src/components/metric-card/metric-card.stories.tsx +224 -0
- package/src/components/metric-card/metric-card.test.tsx +198 -0
- package/src/components/metric-card/metric-card.tsx +229 -0
- package/src/components/model-picker/index.ts +11 -0
- package/src/components/model-picker/model-picker-state.ts +85 -0
- package/src/components/model-picker/model-picker.stories.tsx +128 -0
- package/src/components/model-picker/model-picker.test.tsx +241 -0
- package/src/components/model-picker/model-picker.tsx +270 -0
- package/src/components/nav-main/index.ts +1 -0
- package/src/components/nav-main/nav-main.tsx +147 -0
- package/src/components/nav-notifications/index.ts +5 -0
- package/src/components/nav-notifications/nav-notifications.test.tsx +15 -0
- package/src/components/nav-notifications/nav-notifications.tsx +70 -0
- package/src/components/nav-user/index.ts +1 -0
- package/src/components/nav-user/nav-user.test.tsx +29 -0
- package/src/components/nav-user/nav-user.tsx +114 -0
- package/src/components/navigation-menu/index.ts +10 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +92 -0
- package/src/components/navigation-menu/navigation-menu.tsx +99 -0
- package/src/components/number-input/index.ts +1 -0
- package/src/components/number-input/number-input.stories.tsx +120 -0
- package/src/components/number-input/number-input.test.tsx +96 -0
- package/src/components/number-input/number-input.tsx +219 -0
- package/src/components/page-shell/index.ts +1 -0
- package/src/components/page-shell/page-shell.stories.tsx +105 -0
- package/src/components/page-shell/page-shell.test.tsx +57 -0
- package/src/components/page-shell/page-shell.tsx +62 -0
- package/src/components/pagination/index.ts +9 -0
- package/src/components/pagination/pagination.stories.tsx +57 -0
- package/src/components/pagination/pagination.tsx +77 -0
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.stories.tsx +28 -0
- package/src/components/popover/popover.test.tsx +16 -0
- package/src/components/popover/popover.tsx +29 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/progress/progress.stories.tsx +80 -0
- package/src/components/progress/progress.test.tsx +35 -0
- package/src/components/progress/progress.tsx +50 -0
- package/src/components/radio-group/index.ts +1 -0
- package/src/components/radio-group/radio-group.stories.tsx +75 -0
- package/src/components/radio-group/radio-group.test.tsx +16 -0
- package/src/components/radio-group/radio-group.tsx +33 -0
- package/src/components/rating/index.ts +1 -0
- package/src/components/rating/rating.stories.tsx +111 -0
- package/src/components/rating/rating.test.tsx +41 -0
- package/src/components/rating/rating.tsx +226 -0
- package/src/components/resizable/index.ts +1 -0
- package/src/components/resizable/resizable.stories.tsx +25 -0
- package/src/components/resizable/resizable.tsx +40 -0
- package/src/components/reveal/index.ts +7 -0
- package/src/components/reveal/reveal.stories.tsx +90 -0
- package/src/components/reveal/reveal.test.tsx +43 -0
- package/src/components/reveal/reveal.tsx +95 -0
- package/src/components/revision-timeline/index.ts +10 -0
- package/src/components/revision-timeline/revision-timeline.stories.tsx +785 -0
- package/src/components/revision-timeline/revision-timeline.test.tsx +509 -0
- package/src/components/revision-timeline/revision-timeline.tsx +1107 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +45 -0
- package/src/components/scroll-area/scroll-area.tsx +43 -0
- package/src/components/section-header/index.ts +1 -0
- package/src/components/section-header/section-header.stories.tsx +20 -0
- package/src/components/section-header/section-header.tsx +36 -0
- package/src/components/segmented-field/index.ts +5 -0
- package/src/components/segmented-field/segmented-field.stories.tsx +121 -0
- package/src/components/segmented-field/segmented-field.test.tsx +163 -0
- package/src/components/segmented-field/segmented-field.tsx +235 -0
- package/src/components/select/index.ts +10 -0
- package/src/components/select/select.stories.tsx +106 -0
- package/src/components/select/select.test.tsx +98 -0
- package/src/components/select/select.tsx +230 -0
- package/src/components/separator/index.ts +2 -0
- package/src/components/separator/separator.stories.tsx +87 -0
- package/src/components/separator/separator.tsx +51 -0
- package/src/components/sheet/index.ts +12 -0
- package/src/components/sheet/sheet.stories.tsx +65 -0
- package/src/components/sheet/sheet.tsx +109 -0
- package/src/components/sidebar/index.ts +27 -0
- package/src/components/sidebar/sidebar.stories.tsx +69 -0
- package/src/components/sidebar/sidebar.test.tsx +43 -0
- package/src/components/sidebar/sidebar.tsx +688 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.stories.tsx +28 -0
- package/src/components/skeleton/skeleton.tsx +15 -0
- package/src/components/slider/index.ts +1 -0
- package/src/components/slider/slider.stories.tsx +211 -0
- package/src/components/slider/slider.test.tsx +168 -0
- package/src/components/slider/slider.tsx +136 -0
- package/src/components/slider-number/index.ts +1 -0
- package/src/components/slider-number/slider-number.stories.tsx +123 -0
- package/src/components/slider-number/slider-number.test.tsx +165 -0
- package/src/components/slider-number/slider-number.tsx +191 -0
- package/src/components/sonner/index.ts +1 -0
- package/src/components/sonner/sonner.stories.tsx +124 -0
- package/src/components/sonner/sonner.test.tsx +76 -0
- package/src/components/sonner/sonner.tsx +101 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/spinner/spinner.stories.tsx +27 -0
- package/src/components/spinner/spinner.tsx +19 -0
- package/src/components/split-panel/index.ts +1 -0
- package/src/components/split-panel/split-panel.stories.tsx +60 -0
- package/src/components/split-panel/split-panel.test.tsx +38 -0
- package/src/components/split-panel/split-panel.tsx +95 -0
- package/src/components/state-panel/index.ts +1 -0
- package/src/components/state-panel/state-panel.stories.tsx +80 -0
- package/src/components/state-panel/state-panel.test.tsx +64 -0
- package/src/components/state-panel/state-panel.tsx +167 -0
- package/src/components/status-badge/index.ts +18 -0
- package/src/components/status-badge/status-badge.stories.tsx +132 -0
- package/src/components/status-badge/status-badge.test.tsx +189 -0
- package/src/components/status-badge/status-badge.tsx +330 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.stories.tsx +58 -0
- package/src/components/switch/switch.test.tsx +12 -0
- package/src/components/switch/switch.tsx +29 -0
- package/src/components/table/index.ts +10 -0
- package/src/components/table/table.stories.tsx +53 -0
- package/src/components/table/table.test.tsx +17 -0
- package/src/components/table/table.tsx +106 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +267 -0
- package/src/components/tabs/tabs.test.tsx +94 -0
- package/src/components/tabs/tabs.tsx +170 -0
- package/src/components/tag-input/index.ts +1 -0
- package/src/components/tag-input/tag-input.stories.tsx +120 -0
- package/src/components/tag-input/tag-input.test.tsx +97 -0
- package/src/components/tag-input/tag-input.tsx +253 -0
- package/src/components/team-switcher/index.ts +1 -0
- package/src/components/team-switcher/team-switcher.test.tsx +39 -0
- package/src/components/team-switcher/team-switcher.tsx +100 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.stories.tsx +42 -0
- package/src/components/textarea/textarea.tsx +24 -0
- package/src/components/theme-switcher/index.ts +2 -0
- package/src/components/theme-switcher/theme-switcher.stories.tsx +146 -0
- package/src/components/theme-switcher/theme-switcher.test.tsx +253 -0
- package/src/components/theme-switcher/theme-switcher.tsx +280 -0
- package/src/components/theme-switcher/use-theme-transition.ts +62 -0
- package/src/components/timeline/index.ts +13 -0
- package/src/components/timeline/timeline.stories.tsx +99 -0
- package/src/components/timeline/timeline.test.tsx +196 -0
- package/src/components/timeline/timeline.tsx +188 -0
- package/src/components/toggle/index.ts +1 -0
- package/src/components/toggle/toggle.stories.tsx +116 -0
- package/src/components/toggle/toggle.test.tsx +119 -0
- package/src/components/toggle/toggle.tsx +64 -0
- package/src/components/toggle-group/index.ts +1 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +110 -0
- package/src/components/toggle-group/toggle-group.tsx +59 -0
- package/src/components/toolbar/index.ts +14 -0
- package/src/components/toolbar/toolbar.stories.tsx +126 -0
- package/src/components/toolbar/toolbar.test.tsx +73 -0
- package/src/components/toolbar/toolbar.tsx +177 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.stories.tsx +67 -0
- package/src/components/tooltip/tooltip.tsx +27 -0
- package/src/components/top-nav/index.ts +1 -0
- package/src/components/top-nav/top-nav.stories.tsx +18 -0
- package/src/components/top-nav/top-nav.tsx +34 -0
- package/src/components/transfer/index.ts +2 -0
- package/src/components/transfer/transfer.stories.tsx +162 -0
- package/src/components/transfer/transfer.test.tsx +177 -0
- package/src/components/transfer/transfer.tsx +481 -0
- package/src/components/tree/index.ts +6 -0
- package/src/components/tree/tree.stories.tsx +618 -0
- package/src/components/tree/tree.test.tsx +699 -0
- package/src/components/tree/tree.tsx +1149 -0
- package/src/components/tree/use-tree-keyboard.ts +314 -0
- package/src/components/tree-select/index.ts +1 -0
- package/src/components/tree-select/tree-select.stories.tsx +127 -0
- package/src/components/tree-select/tree-select.test.tsx +40 -0
- package/src/components/tree-select/tree-select.tsx +137 -0
- package/src/components/typography/index.ts +31 -0
- package/src/components/typography/prose.test.tsx +48 -0
- package/src/components/typography/prose.tsx +194 -0
- package/src/components/typography/typography.stories.tsx +244 -0
- package/src/components/typography/typography.test.tsx +140 -0
- package/src/components/typography/typography.tsx +126 -0
- package/src/components/view-toolbar/index.ts +10 -0
- package/src/components/view-toolbar/view-toolbar.stories.tsx +342 -0
- package/src/components/view-toolbar/view-toolbar.test.tsx +262 -0
- package/src/components/view-toolbar/view-toolbar.tsx +320 -0
- package/src/components/virtual-select/index.ts +2 -0
- package/src/components/virtual-select/use-virtual-listbox.ts +132 -0
- package/src/components/virtual-select/virtual-select.stories.tsx +203 -0
- package/src/components/virtual-select/virtual-select.test.tsx +106 -0
- package/src/components/virtual-select/virtual-select.tsx +361 -0
- package/src/components/wizard/index.ts +12 -0
- package/src/components/wizard/wizard.stories.tsx +148 -0
- package/src/components/wizard/wizard.test.tsx +67 -0
- package/src/components/wizard/wizard.tsx +354 -0
- package/src/decoration.stories.tsx +236 -0
- package/src/index.ts +174 -0
- package/src/lib/cn.test.ts +39 -0
- package/src/lib/cn.ts +31 -0
- package/src/lib/compact-number.ts +61 -0
- package/src/lib/document-address.test.ts +75 -0
- package/src/lib/document-address.ts +265 -0
- package/src/lib/download.ts +93 -0
- package/src/lib/file-icon.test.ts +45 -0
- package/src/lib/file-icon.ts +58 -0
- package/src/lib/file-kind.test.ts +98 -0
- package/src/lib/file-kind.ts +303 -0
- package/src/lib/file-source.test.ts +217 -0
- package/src/lib/file-source.ts +269 -0
- package/src/lib/merge-refs.test.ts +37 -0
- package/src/lib/merge-refs.ts +17 -0
- package/src/lib/streamdown-translations.ts +99 -0
- package/src/lib/use-copy-to-clipboard.ts +75 -0
- package/src/lib/use-mobile.ts +16 -0
- package/src/motion.stories.tsx +266 -0
- package/src/taste-profile.test.tsx +252 -0
- package/src/templates-object-detail-hub.stories.tsx +466 -0
- package/src/templates-screen-states.stories.tsx +306 -0
- package/src/templates-settings.stories.tsx +245 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Toolbar,
|
|
3
|
+
ToolbarButton,
|
|
4
|
+
ToolbarSeparator,
|
|
5
|
+
ToolbarSlot,
|
|
6
|
+
ToolbarToggleGroup,
|
|
7
|
+
ToolbarToggleItem,
|
|
8
|
+
toolbarVariants,
|
|
9
|
+
type ToolbarButtonProps,
|
|
10
|
+
type ToolbarProps,
|
|
11
|
+
type ToolbarSeparatorProps,
|
|
12
|
+
type ToolbarToggleGroupProps,
|
|
13
|
+
type ToolbarToggleItemProps,
|
|
14
|
+
} from "./toolbar";
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
3
|
+
import {
|
|
4
|
+
AlignCenterIcon,
|
|
5
|
+
AlignLeftIcon,
|
|
6
|
+
AlignRightIcon,
|
|
7
|
+
BoldIcon,
|
|
8
|
+
ItalicIcon,
|
|
9
|
+
Redo2Icon,
|
|
10
|
+
Undo2Icon,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Toolbar,
|
|
15
|
+
ToolbarButton,
|
|
16
|
+
ToolbarSeparator,
|
|
17
|
+
ToolbarToggleGroup,
|
|
18
|
+
ToolbarToggleItem,
|
|
19
|
+
} from "./toolbar";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A dense row of controls that acts on the content beside it — and the only row
|
|
23
|
+
* in the system that claims `role="toolbar"`, because it is the only one that
|
|
24
|
+
* keeps the role's promise: **one tab stop, arrow keys move between controls**.
|
|
25
|
+
*
|
|
26
|
+
* For the ordinary control row above a list or table, reach for `ViewToolbar`
|
|
27
|
+
* instead — there every control is its own tab stop, which is what readers
|
|
28
|
+
* expect there.
|
|
29
|
+
*/
|
|
30
|
+
const meta = {
|
|
31
|
+
title: "Foundation/Toolbar",
|
|
32
|
+
component: Toolbar,
|
|
33
|
+
tags: ["autodocs"],
|
|
34
|
+
parameters: { layout: "padded" },
|
|
35
|
+
args: { "aria-label": "Formatting" },
|
|
36
|
+
} satisfies Meta<typeof Toolbar>;
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof meta>;
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {
|
|
42
|
+
render: (args) => (
|
|
43
|
+
<Toolbar {...args}>
|
|
44
|
+
<ToolbarButton aria-label="Undo" size="icon-sm">
|
|
45
|
+
<Undo2Icon aria-hidden="true" />
|
|
46
|
+
</ToolbarButton>
|
|
47
|
+
<ToolbarButton aria-label="Redo" size="icon-sm">
|
|
48
|
+
<Redo2Icon aria-hidden="true" />
|
|
49
|
+
</ToolbarButton>
|
|
50
|
+
<ToolbarSeparator />
|
|
51
|
+
<ToolbarToggleGroup type="multiple" aria-label="Text style">
|
|
52
|
+
<ToolbarToggleItem value="bold" aria-label="Bold">
|
|
53
|
+
<BoldIcon aria-hidden="true" />
|
|
54
|
+
</ToolbarToggleItem>
|
|
55
|
+
<ToolbarToggleItem value="italic" aria-label="Italic">
|
|
56
|
+
<ItalicIcon aria-hidden="true" />
|
|
57
|
+
</ToolbarToggleItem>
|
|
58
|
+
</ToolbarToggleGroup>
|
|
59
|
+
<ToolbarSeparator />
|
|
60
|
+
<ToolbarToggleGroup type="single" defaultValue="left" aria-label="Alignment">
|
|
61
|
+
<ToolbarToggleItem value="left" aria-label="Align left">
|
|
62
|
+
<AlignLeftIcon aria-hidden="true" />
|
|
63
|
+
</ToolbarToggleItem>
|
|
64
|
+
<ToolbarToggleItem value="center" aria-label="Align centre">
|
|
65
|
+
<AlignCenterIcon aria-hidden="true" />
|
|
66
|
+
</ToolbarToggleItem>
|
|
67
|
+
<ToolbarToggleItem value="right" aria-label="Align right">
|
|
68
|
+
<AlignRightIcon aria-hidden="true" />
|
|
69
|
+
</ToolbarToggleItem>
|
|
70
|
+
</ToolbarToggleGroup>
|
|
71
|
+
<span className="flex-1" />
|
|
72
|
+
<ToolbarButton variant="outline">Publish</ToolbarButton>
|
|
73
|
+
</Toolbar>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The behaviour the role promises, exercised: Tab reaches the toolbar ONCE, and
|
|
79
|
+
* the arrow keys walk it. A row that claims `role="toolbar"` without this tells
|
|
80
|
+
* a screen-reader user to press keys that do nothing.
|
|
81
|
+
*/
|
|
82
|
+
export const KeyboardNavigation: Story = {
|
|
83
|
+
...Default,
|
|
84
|
+
play: async ({ canvasElement }) => {
|
|
85
|
+
const canvas = within(canvasElement);
|
|
86
|
+
const toolbar = canvas.getByRole("toolbar", { name: "Formatting" });
|
|
87
|
+
|
|
88
|
+
await userEvent.tab();
|
|
89
|
+
const undo = canvas.getByRole("button", { name: "Undo" });
|
|
90
|
+
await expect(undo).toHaveFocus();
|
|
91
|
+
|
|
92
|
+
await userEvent.keyboard("{ArrowRight}");
|
|
93
|
+
await expect(canvas.getByRole("button", { name: "Redo" })).toHaveFocus();
|
|
94
|
+
|
|
95
|
+
// Out of the toolbar in one Tab — the whole row is a single stop.
|
|
96
|
+
await userEvent.tab();
|
|
97
|
+
await expect(toolbar.contains(document.activeElement)).toBe(false);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** A rail beside the content it acts on. Arrow keys follow the orientation. */
|
|
102
|
+
export const Vertical: Story = {
|
|
103
|
+
args: { "aria-label": "Document", orientation: "vertical" },
|
|
104
|
+
render: (args) => (
|
|
105
|
+
<Toolbar {...args} className="w-fit">
|
|
106
|
+
<ToolbarButton aria-label="Undo" size="icon-sm">
|
|
107
|
+
<Undo2Icon aria-hidden="true" />
|
|
108
|
+
</ToolbarButton>
|
|
109
|
+
<ToolbarButton aria-label="Redo" size="icon-sm">
|
|
110
|
+
<Redo2Icon aria-hidden="true" />
|
|
111
|
+
</ToolbarButton>
|
|
112
|
+
<ToolbarSeparator />
|
|
113
|
+
<ToolbarToggleGroup type="multiple" aria-label="Text style">
|
|
114
|
+
<ToolbarToggleItem value="bold" aria-label="Bold">
|
|
115
|
+
<BoldIcon aria-hidden="true" />
|
|
116
|
+
</ToolbarToggleItem>
|
|
117
|
+
</ToolbarToggleGroup>
|
|
118
|
+
</Toolbar>
|
|
119
|
+
),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Explicitly horizontal — the default, spelled out. */
|
|
123
|
+
export const Horizontal: Story = {
|
|
124
|
+
...Default,
|
|
125
|
+
args: { "aria-label": "Formatting", orientation: "horizontal" },
|
|
126
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { Toolbar, ToolbarButton, ToolbarSeparator } from "./toolbar";
|
|
6
|
+
|
|
7
|
+
function Fixture() {
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<button type="button">before</button>
|
|
11
|
+
<Toolbar aria-label="Document">
|
|
12
|
+
<ToolbarButton>First</ToolbarButton>
|
|
13
|
+
<ToolbarSeparator />
|
|
14
|
+
<ToolbarButton>Second</ToolbarButton>
|
|
15
|
+
<ToolbarButton>Third</ToolbarButton>
|
|
16
|
+
</Toolbar>
|
|
17
|
+
<button type="button">after</button>
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("Toolbar", () => {
|
|
23
|
+
it("is a named toolbar", () => {
|
|
24
|
+
render(<Fixture />);
|
|
25
|
+
expect(screen.getByRole("toolbar", { name: "Document" })).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("is ONE tab stop — the promise the role makes", async () => {
|
|
29
|
+
render(<Fixture />);
|
|
30
|
+
|
|
31
|
+
await userEvent.tab();
|
|
32
|
+
expect(screen.getByRole("button", { name: "before" })).toHaveFocus();
|
|
33
|
+
|
|
34
|
+
await userEvent.tab();
|
|
35
|
+
expect(screen.getByRole("button", { name: "First" })).toHaveFocus();
|
|
36
|
+
|
|
37
|
+
// Not "Second": the whole row is a single stop, so Tab leaves it.
|
|
38
|
+
await userEvent.tab();
|
|
39
|
+
expect(screen.getByRole("button", { name: "after" })).toHaveFocus();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("moves between controls with the arrow keys", async () => {
|
|
43
|
+
render(<Fixture />);
|
|
44
|
+
|
|
45
|
+
await userEvent.tab();
|
|
46
|
+
await userEvent.tab();
|
|
47
|
+
await userEvent.keyboard("{ArrowRight}");
|
|
48
|
+
expect(screen.getByRole("button", { name: "Second" })).toHaveFocus();
|
|
49
|
+
|
|
50
|
+
await userEvent.keyboard("{ArrowLeft}");
|
|
51
|
+
expect(screen.getByRole("button", { name: "First" })).toHaveFocus();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns focus to the control the user left, not to the first one", async () => {
|
|
55
|
+
render(<Fixture />);
|
|
56
|
+
|
|
57
|
+
await userEvent.tab();
|
|
58
|
+
await userEvent.tab();
|
|
59
|
+
await userEvent.keyboard("{ArrowRight}{ArrowRight}");
|
|
60
|
+
expect(screen.getByRole("button", { name: "Third" })).toHaveFocus();
|
|
61
|
+
|
|
62
|
+
await userEvent.tab();
|
|
63
|
+
await userEvent.tab({ shift: true });
|
|
64
|
+
expect(screen.getByRole("button", { name: "Third" })).toHaveFocus();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("keeps the separator out of the accessibility tree's control list", () => {
|
|
68
|
+
render(<Fixture />);
|
|
69
|
+
// Radix renders it as a real separator, not a focusable child.
|
|
70
|
+
expect(screen.getByRole("separator")).toBeInTheDocument();
|
|
71
|
+
expect(screen.getAllByRole("button")).toHaveLength(5);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Toolbar — the ONE row that genuinely claims `role="toolbar"`.
|
|
5
|
+
*
|
|
6
|
+
* The role is a promise: a toolbar is a SINGLE tab stop, and the arrow keys move
|
|
7
|
+
* between its controls (WAI-ARIA APG). Four rows in this repo claim the role
|
|
8
|
+
* without implementing that, which is worse than not claiming it — a screen
|
|
9
|
+
* reader tells the user to press the arrow keys and nothing happens. This
|
|
10
|
+
* component delegates the roving tabindex to `@radix-ui/react-toolbar` so the
|
|
11
|
+
* promise is kept, and exists so no one has to hand-roll it again.
|
|
12
|
+
*
|
|
13
|
+
* When NOT to use it. A row of ordinary controls above a list or table is a
|
|
14
|
+
* `ViewToolbar` — that component deliberately does not claim the role, because
|
|
15
|
+
* every control there is an ordinary tab stop and readers expect Tab, not
|
|
16
|
+
* arrows. Reach for `Toolbar` when the row is DENSE and SECONDARY to the content
|
|
17
|
+
* it acts on (a document's page/zoom controls, a formatting bar): collapsing a
|
|
18
|
+
* dozen controls into one tab stop is what makes the content reachable again.
|
|
19
|
+
*
|
|
20
|
+
* Anatomy — a shell plus three composable parts. There is no `actions` prop and
|
|
21
|
+
* no items array: a toolbar's layout is the caller's, so groups are arranged
|
|
22
|
+
* with ordinary flex utilities and `ToolbarSeparator` between them.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
|
|
26
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
27
|
+
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
|
|
28
|
+
|
|
29
|
+
import { cn } from "../../lib/cn";
|
|
30
|
+
import { buttonVariants } from "../button";
|
|
31
|
+
import { toggleVariants } from "../toggle";
|
|
32
|
+
|
|
33
|
+
/* -------------------------------------------------------------------------- */
|
|
34
|
+
/* Root */
|
|
35
|
+
/* -------------------------------------------------------------------------- */
|
|
36
|
+
|
|
37
|
+
export const toolbarVariants = cva("flex items-center gap-1", {
|
|
38
|
+
variants: {
|
|
39
|
+
orientation: {
|
|
40
|
+
horizontal: "flex-row",
|
|
41
|
+
// Radix already writes `aria-orientation`; this only lays it out.
|
|
42
|
+
vertical: "h-full flex-col",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: { orientation: "horizontal" },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export interface ToolbarProps
|
|
49
|
+
extends
|
|
50
|
+
Omit<ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root>, "orientation">,
|
|
51
|
+
VariantProps<typeof toolbarVariants> {
|
|
52
|
+
/**
|
|
53
|
+
* What this toolbar acts on ("Document", "Formatting"). A toolbar is one tab
|
|
54
|
+
* stop with several controls inside it, so without a name a screen-reader
|
|
55
|
+
* user lands on an anonymous group — hence required, not optional.
|
|
56
|
+
*/
|
|
57
|
+
"aria-label": string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The row shell. Renders no surface of its own: a toolbar sits ON something
|
|
62
|
+
* (a card header, a document frame) and inherits that surface, so painting a
|
|
63
|
+
* background here would double the fill wherever it is placed.
|
|
64
|
+
*/
|
|
65
|
+
export const Toolbar = forwardRef<ElementRef<typeof ToolbarPrimitive.Root>, ToolbarProps>(
|
|
66
|
+
function Toolbar({ className, orientation, ...props }, ref) {
|
|
67
|
+
return (
|
|
68
|
+
<ToolbarPrimitive.Root
|
|
69
|
+
ref={ref}
|
|
70
|
+
data-slot="toolbar"
|
|
71
|
+
orientation={orientation ?? "horizontal"}
|
|
72
|
+
className={cn(toolbarVariants({ orientation }), className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
/* -------------------------------------------------------------------------- */
|
|
80
|
+
/* Parts */
|
|
81
|
+
/* -------------------------------------------------------------------------- */
|
|
82
|
+
|
|
83
|
+
export interface ToolbarButtonProps
|
|
84
|
+
extends
|
|
85
|
+
ComponentPropsWithoutRef<typeof ToolbarPrimitive.Button>,
|
|
86
|
+
Pick<VariantProps<typeof buttonVariants>, "variant" | "size"> {}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A control inside the toolbar. Styled with `buttonVariants` — the same ink,
|
|
90
|
+
* heights and focus ring as every other button in the system, not a parallel
|
|
91
|
+
* set. Icon-only controls take `size="icon-sm"` and MUST carry an `aria-label`;
|
|
92
|
+
* wrap in a `Tooltip` when the glyph needs a name on screen too.
|
|
93
|
+
*/
|
|
94
|
+
export const ToolbarButton = forwardRef<
|
|
95
|
+
ElementRef<typeof ToolbarPrimitive.Button>,
|
|
96
|
+
ToolbarButtonProps
|
|
97
|
+
>(function ToolbarButton({ className, variant = "ghost", size = "sm", ...props }, ref) {
|
|
98
|
+
return (
|
|
99
|
+
<ToolbarPrimitive.Button
|
|
100
|
+
ref={ref}
|
|
101
|
+
data-slot="toolbar-button"
|
|
102
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type ToolbarSeparatorProps = ComponentPropsWithoutRef<typeof ToolbarPrimitive.Separator>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The divider between two groups of controls. It is the ONLY cue between two
|
|
112
|
+
* same-fill regions of the row, so it takes the strong rung (WCAG 1.4.11,
|
|
113
|
+
* ADR 0010) — `Separator`'s subtle default would be a decorative hairline here.
|
|
114
|
+
*/
|
|
115
|
+
export const ToolbarSeparator = forwardRef<
|
|
116
|
+
ElementRef<typeof ToolbarPrimitive.Separator>,
|
|
117
|
+
ToolbarSeparatorProps
|
|
118
|
+
>(function ToolbarSeparator({ className, ...props }, ref) {
|
|
119
|
+
return (
|
|
120
|
+
<ToolbarPrimitive.Separator
|
|
121
|
+
ref={ref}
|
|
122
|
+
data-slot="toolbar-separator"
|
|
123
|
+
className={cn(
|
|
124
|
+
"bg-border-strong mx-1 shrink-0",
|
|
125
|
+
"data-[orientation=horizontal]:h-5 data-[orientation=horizontal]:w-px",
|
|
126
|
+
"data-[orientation=vertical]:h-px data-[orientation=vertical]:w-5",
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export type ToolbarToggleGroupProps = ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleGroup>;
|
|
135
|
+
|
|
136
|
+
/** A set of mutually-exclusive or multi-select toggles (a view switcher, bold/italic). */
|
|
137
|
+
export const ToolbarToggleGroup = forwardRef<
|
|
138
|
+
ElementRef<typeof ToolbarPrimitive.ToggleGroup>,
|
|
139
|
+
ToolbarToggleGroupProps
|
|
140
|
+
>(function ToolbarToggleGroup({ className, ...props }, ref) {
|
|
141
|
+
return (
|
|
142
|
+
<ToolbarPrimitive.ToggleGroup
|
|
143
|
+
ref={ref}
|
|
144
|
+
data-slot="toolbar-toggle-group"
|
|
145
|
+
className={cn("flex items-center gap-1", className)}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export interface ToolbarToggleItemProps
|
|
152
|
+
extends
|
|
153
|
+
ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleItem>,
|
|
154
|
+
Pick<VariantProps<typeof toggleVariants>, "size"> {}
|
|
155
|
+
|
|
156
|
+
/** One toggle. Shares `Toggle`'s pressed styling, so state reads the same everywhere. */
|
|
157
|
+
export const ToolbarToggleItem = forwardRef<
|
|
158
|
+
ElementRef<typeof ToolbarPrimitive.ToggleItem>,
|
|
159
|
+
ToolbarToggleItemProps
|
|
160
|
+
>(function ToolbarToggleItem({ className, size = "sm", ...props }, ref) {
|
|
161
|
+
return (
|
|
162
|
+
<ToolbarPrimitive.ToggleItem
|
|
163
|
+
ref={ref}
|
|
164
|
+
data-slot="toolbar-toggle-item"
|
|
165
|
+
className={cn(toggleVariants({ size }), className)}
|
|
166
|
+
{...props}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* An escape hatch for anything the parts above do not cover — a `Select`, an
|
|
173
|
+
* input, a `DropdownMenu` trigger. `asChild` hands the roving-tabindex wiring to
|
|
174
|
+
* whatever you render, so the control stays part of the arrow-key sequence
|
|
175
|
+
* instead of becoming a second tab stop inside a single-tab-stop row.
|
|
176
|
+
*/
|
|
177
|
+
export const ToolbarSlot = ToolbarPrimitive.Button;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from "./tooltip";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, waitFor, within } from "storybook/test";
|
|
3
|
+
import { Button } from "../button";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Overlays/Tooltip",
|
|
8
|
+
component: Tooltip,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
open: {
|
|
12
|
+
description: "Controlled open state of the tooltip.",
|
|
13
|
+
control: "boolean",
|
|
14
|
+
table: { category: "State" },
|
|
15
|
+
},
|
|
16
|
+
defaultOpen: {
|
|
17
|
+
description: "Uncontrolled initial open state.",
|
|
18
|
+
control: "boolean",
|
|
19
|
+
table: { category: "State" },
|
|
20
|
+
},
|
|
21
|
+
onOpenChange: {
|
|
22
|
+
description: "Callback when the open state changes.",
|
|
23
|
+
control: false,
|
|
24
|
+
table: { category: "Events" },
|
|
25
|
+
},
|
|
26
|
+
delayDuration: {
|
|
27
|
+
description: "Milliseconds to wait before opening. Default 700.",
|
|
28
|
+
control: "number",
|
|
29
|
+
table: { category: "Behaviour" },
|
|
30
|
+
},
|
|
31
|
+
disableHoverableContent: {
|
|
32
|
+
description: "Prevent the tooltip content from being hoverable.",
|
|
33
|
+
control: "boolean",
|
|
34
|
+
table: { category: "Behaviour" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
} satisfies Meta<typeof Tooltip>;
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof meta>;
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {
|
|
42
|
+
render: () => (
|
|
43
|
+
<TooltipProvider delayDuration={0}>
|
|
44
|
+
<Tooltip>
|
|
45
|
+
<TooltipTrigger asChild>
|
|
46
|
+
<Button variant="outline">Hover me</Button>
|
|
47
|
+
</TooltipTrigger>
|
|
48
|
+
<TooltipContent>Helpful context</TooltipContent>
|
|
49
|
+
</Tooltip>
|
|
50
|
+
</TooltipProvider>
|
|
51
|
+
),
|
|
52
|
+
// Hovers over the trigger (delay zeroed for determinism) to open the portalled
|
|
53
|
+
// tooltip and confirms the content is visible. NOTE: Radix renders the visible
|
|
54
|
+
// tooltip content as a node separate from its clipped, sr-only role="tooltip"
|
|
55
|
+
// announcement span — so we assert on the visible CONTENT text, not getByRole.
|
|
56
|
+
play: async ({ canvas, canvasElement, userEvent }) => {
|
|
57
|
+
const trigger = canvas.getByRole("button", { name: /hover me/i });
|
|
58
|
+
await userEvent.hover(trigger);
|
|
59
|
+
const body = within(canvasElement.ownerDocument.body);
|
|
60
|
+
// Radix nests an sr-only role="tooltip" copy of the text INSIDE the visible
|
|
61
|
+
// popper content, so the text matches twice. Target the popper content node
|
|
62
|
+
// (the only one carrying data-side) and assert it is visible.
|
|
63
|
+
const content = await body.findByText("Helpful context", { selector: "[data-side]" });
|
|
64
|
+
// wait out the fade-in entrance animation before asserting visibility
|
|
65
|
+
await waitFor(() => expect(content).toBeVisible());
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
|
|
2
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
|
|
5
|
+
export const TooltipProvider = TooltipPrimitive.Provider;
|
|
6
|
+
export const Tooltip = TooltipPrimitive.Root;
|
|
7
|
+
export const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
8
|
+
|
|
9
|
+
export const TooltipContent = forwardRef<
|
|
10
|
+
ElementRef<typeof TooltipPrimitive.Content>,
|
|
11
|
+
ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
12
|
+
>(function TooltipContent({ className, sideOffset = 6, ...props }, ref) {
|
|
13
|
+
return (
|
|
14
|
+
<TooltipPrimitive.Portal>
|
|
15
|
+
<TooltipPrimitive.Content
|
|
16
|
+
ref={ref}
|
|
17
|
+
sideOffset={sideOffset}
|
|
18
|
+
className={cn(
|
|
19
|
+
"z-50 overflow-hidden rounded-md bg-foreground px-2.5 py-1.5 text-xs text-background shadow-md",
|
|
20
|
+
"data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
</TooltipPrimitive.Portal>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TopNav, type TopNavProps } from "./top-nav";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Button } from "../button";
|
|
3
|
+
import { TopNav } from "./top-nav";
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Layout/TopNav",
|
|
6
|
+
component: TopNav,
|
|
7
|
+
parameters: { layout: "fullscreen" },
|
|
8
|
+
} satisfies Meta<typeof TopNav>;
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof meta>;
|
|
11
|
+
export const Default: Story = {
|
|
12
|
+
render: () => (
|
|
13
|
+
<TopNav
|
|
14
|
+
start={<span className="font-semibold">Dashboard</span>}
|
|
15
|
+
end={<Button size="sm">Invite</Button>}
|
|
16
|
+
/>
|
|
17
|
+
),
|
|
18
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { cn } from "../../lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface TopNavProps extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** Left slot (brand on mobile, breadcrumbs, title). */
|
|
6
|
+
start?: ReactNode;
|
|
7
|
+
/** Right slot (search, actions, account menu). */
|
|
8
|
+
end?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Application top bar. Sticky, single row, brand-neutral. */
|
|
12
|
+
export const TopNav = forwardRef<HTMLElement, TopNavProps>(function TopNav(
|
|
13
|
+
{ className, start, end, children, ...props },
|
|
14
|
+
ref,
|
|
15
|
+
) {
|
|
16
|
+
return (
|
|
17
|
+
<header
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn(
|
|
20
|
+
"sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-surface-elevated/80 px-4 backdrop-blur",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{start ? <div className="flex items-center gap-2">{start}</div> : null}
|
|
26
|
+
{children ? (
|
|
27
|
+
<div className="flex flex-1 items-center">{children}</div>
|
|
28
|
+
) : (
|
|
29
|
+
<div className="flex-1" />
|
|
30
|
+
)}
|
|
31
|
+
{end ? <div className="flex items-center gap-2">{end}</div> : null}
|
|
32
|
+
</header>
|
|
33
|
+
);
|
|
34
|
+
});
|