@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,47 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import {
|
|
3
|
+
Breadcrumb,
|
|
4
|
+
BreadcrumbItem,
|
|
5
|
+
BreadcrumbLink,
|
|
6
|
+
BreadcrumbList,
|
|
7
|
+
BreadcrumbPage,
|
|
8
|
+
BreadcrumbSeparator,
|
|
9
|
+
} from "./breadcrumb";
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Navigation/Breadcrumb",
|
|
12
|
+
component: Breadcrumb,
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
argTypes: {
|
|
15
|
+
children: {
|
|
16
|
+
description: "Breadcrumb content — compose with BreadcrumbList, BreadcrumbItem, etc.",
|
|
17
|
+
control: false,
|
|
18
|
+
table: { category: "Content" },
|
|
19
|
+
},
|
|
20
|
+
className: {
|
|
21
|
+
description: "Additional CSS classes applied to the nav element.",
|
|
22
|
+
control: "text",
|
|
23
|
+
table: { category: "Styling" },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<typeof Breadcrumb>;
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<typeof meta>;
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
render: () => (
|
|
31
|
+
<Breadcrumb>
|
|
32
|
+
<BreadcrumbList>
|
|
33
|
+
<BreadcrumbItem>
|
|
34
|
+
<BreadcrumbLink href="#">Home</BreadcrumbLink>
|
|
35
|
+
</BreadcrumbItem>
|
|
36
|
+
<BreadcrumbSeparator />
|
|
37
|
+
<BreadcrumbItem>
|
|
38
|
+
<BreadcrumbLink href="#">Projects</BreadcrumbLink>
|
|
39
|
+
</BreadcrumbItem>
|
|
40
|
+
<BreadcrumbSeparator />
|
|
41
|
+
<BreadcrumbItem>
|
|
42
|
+
<BreadcrumbPage>brand-ui</BreadcrumbPage>
|
|
43
|
+
</BreadcrumbItem>
|
|
44
|
+
</BreadcrumbList>
|
|
45
|
+
</Breadcrumb>
|
|
46
|
+
),
|
|
47
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "./breadcrumb";
|
|
4
|
+
describe("Breadcrumb", () => {
|
|
5
|
+
it("renders the current page", () => {
|
|
6
|
+
render(
|
|
7
|
+
<Breadcrumb>
|
|
8
|
+
<BreadcrumbList>
|
|
9
|
+
<BreadcrumbItem>
|
|
10
|
+
<BreadcrumbPage>Here</BreadcrumbPage>
|
|
11
|
+
</BreadcrumbItem>
|
|
12
|
+
</BreadcrumbList>
|
|
13
|
+
</Breadcrumb>,
|
|
14
|
+
);
|
|
15
|
+
expect(screen.getByText("Here")).toHaveAttribute("aria-current", "page");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { forwardRef, type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
|
|
6
|
+
export const Breadcrumb = forwardRef<HTMLElement, ComponentProps<"nav">>(
|
|
7
|
+
function Breadcrumb(props, ref) {
|
|
8
|
+
return <nav ref={ref} aria-label="breadcrumb" {...props} />;
|
|
9
|
+
},
|
|
10
|
+
);
|
|
11
|
+
export const BreadcrumbList = forwardRef<HTMLOListElement, ComponentProps<"ol">>(
|
|
12
|
+
function BreadcrumbList({ className, ...props }, ref) {
|
|
13
|
+
return (
|
|
14
|
+
<ol
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
export const BreadcrumbItem = forwardRef<HTMLLIElement, ComponentProps<"li">>(
|
|
26
|
+
function BreadcrumbItem({ className, ...props }, ref) {
|
|
27
|
+
return (
|
|
28
|
+
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
export const BreadcrumbLink = forwardRef<
|
|
33
|
+
HTMLAnchorElement,
|
|
34
|
+
ComponentProps<"a"> & { asChild?: boolean }
|
|
35
|
+
>(function BreadcrumbLink({ className, asChild, ...props }, ref) {
|
|
36
|
+
const Comp = asChild ? Slot : "a";
|
|
37
|
+
return (
|
|
38
|
+
<Comp
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn("transition-colors duration-fast hover:text-foreground", className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
export const BreadcrumbPage = forwardRef<HTMLSpanElement, ComponentProps<"span">>(
|
|
46
|
+
function BreadcrumbPage({ className, ...props }, ref) {
|
|
47
|
+
return (
|
|
48
|
+
<span
|
|
49
|
+
ref={ref}
|
|
50
|
+
role="link"
|
|
51
|
+
aria-disabled="true"
|
|
52
|
+
aria-current="page"
|
|
53
|
+
className={cn("font-medium text-foreground", className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
export function BreadcrumbSeparator({ children, className, ...props }: ComponentProps<"li">) {
|
|
60
|
+
return (
|
|
61
|
+
<li
|
|
62
|
+
role="presentation"
|
|
63
|
+
aria-hidden="true"
|
|
64
|
+
className={cn("[&>svg]:size-3.5", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{children ?? <ChevronRight />}
|
|
68
|
+
</li>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
export function BreadcrumbEllipsis({ className, ...props }: ComponentProps<"span">) {
|
|
72
|
+
return (
|
|
73
|
+
<span
|
|
74
|
+
role="presentation"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
className={cn("flex size-9 items-center justify-center", className)}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<MoreHorizontal className="size-4" />
|
|
80
|
+
<span className="sr-only">More</span>
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
export function BreadcrumbItems({ children }: { children: ReactNode }) {
|
|
85
|
+
return <>{children}</>;
|
|
86
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect } from "storybook/test";
|
|
3
|
+
import { Button } from "./button";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Core/Button",
|
|
7
|
+
component: Button,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: { children: "Button" },
|
|
10
|
+
argTypes: {
|
|
11
|
+
variant: {
|
|
12
|
+
control: "select",
|
|
13
|
+
options: [
|
|
14
|
+
"default",
|
|
15
|
+
"secondary",
|
|
16
|
+
"destructive",
|
|
17
|
+
"outline",
|
|
18
|
+
"outline-subtle",
|
|
19
|
+
"ghost",
|
|
20
|
+
"link",
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
size: { control: "select", options: ["sm", "default", "lg", "icon"] },
|
|
24
|
+
},
|
|
25
|
+
} satisfies Meta<typeof Button>;
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {};
|
|
30
|
+
export const Secondary: Story = { args: { variant: "secondary" } };
|
|
31
|
+
export const Destructive: Story = { args: { variant: "destructive" } };
|
|
32
|
+
export const Outline: Story = { args: { variant: "outline" } };
|
|
33
|
+
// The quiet hairline rung (#194): `border-border` instead of the strong
|
|
34
|
+
// form-field `border-input` — for "outlined but calm" non-field controls.
|
|
35
|
+
export const OutlineSubtle: Story = { args: { variant: "outline-subtle" } };
|
|
36
|
+
export const Ghost: Story = { args: { variant: "ghost" } };
|
|
37
|
+
|
|
38
|
+
// The single project-wide CssCheck. Proves the shared preview actually loaded
|
|
39
|
+
// the brand token stylesheet + Tailwind: the default Button variant is
|
|
40
|
+
// `bg-primary`, which resolves to the applied theme's `--primary` token. If
|
|
41
|
+
// themes.css/Tailwind failed to load, this would be the transparent default
|
|
42
|
+
// `rgba(0, 0, 0, 0)` and fail — where a plain `toBeVisible()` would still pass
|
|
43
|
+
// on the unstyled element.
|
|
44
|
+
//
|
|
45
|
+
// The harness applies the shipped DEFAULT theme (`light`) to
|
|
46
|
+
// `document.documentElement` (see `apps/docs/.storybook/preview.tsx`'s
|
|
47
|
+
// `withTheme` decorator, #402), so `--primary` here is light's own
|
|
48
|
+
// value (`oklch(0.553 0.143 153)` in themes.css) — not the unbranded `:root`
|
|
49
|
+
// fallback this test asserted against before #402 fixed the harness to
|
|
50
|
+
// actually apply a theme.
|
|
51
|
+
export const CssCheck: Story = {
|
|
52
|
+
play: async ({ canvas }) => {
|
|
53
|
+
const btn = canvas.getByRole("button", { name: "Button" });
|
|
54
|
+
await expect(getComputedStyle(btn).backgroundColor).toBe("oklch(0.553 0.143 153)");
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const AllVariants: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<div className="flex flex-wrap gap-2">
|
|
61
|
+
<Button>Default</Button>
|
|
62
|
+
<Button variant="secondary">Secondary</Button>
|
|
63
|
+
<Button variant="destructive">Destructive</Button>
|
|
64
|
+
<Button variant="outline">Outline</Button>
|
|
65
|
+
<Button variant="outline-subtle">Outline subtle</Button>
|
|
66
|
+
<Button variant="ghost">Ghost</Button>
|
|
67
|
+
<Button variant="link">Link</Button>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
|
|
6
|
+
describe("Button", () => {
|
|
7
|
+
it("renders its children", () => {
|
|
8
|
+
render(<Button>Click me</Button>);
|
|
9
|
+
expect(screen.getByRole("button", { name: "Click me" })).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("calls onClick when pressed", async () => {
|
|
13
|
+
const onClick = vi.fn();
|
|
14
|
+
render(<Button onClick={onClick}>Go</Button>);
|
|
15
|
+
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
|
16
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("does not fire when disabled", async () => {
|
|
20
|
+
const onClick = vi.fn();
|
|
21
|
+
render(
|
|
22
|
+
<Button disabled onClick={onClick}>
|
|
23
|
+
Nope
|
|
24
|
+
</Button>,
|
|
25
|
+
);
|
|
26
|
+
await userEvent.click(screen.getByRole("button", { name: "Nope" }));
|
|
27
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// #194: `outline-subtle` targets `border-border`, `outline` targets the form-field
|
|
31
|
+
// `border-input`. Since the ADR 0010 Amendment (2026-06-20) returned `--input` to the
|
|
32
|
+
// subtle rung these two render identically by default, but the class STRINGS stay
|
|
33
|
+
// distinct (a semantic seam a brand could re-separate). This locks the class target,
|
|
34
|
+
// not the resting contrast.
|
|
35
|
+
it("outline-subtle uses border-border, not the form-field border-input", () => {
|
|
36
|
+
render(<Button variant="outline-subtle">Quiet</Button>);
|
|
37
|
+
const btn = screen.getByRole("button", { name: "Quiet" });
|
|
38
|
+
expect(btn.className.split(" ")).toContain("border-border");
|
|
39
|
+
expect(btn.className.split(" ")).not.toContain("border-input");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("renders as a child element with asChild", () => {
|
|
43
|
+
render(
|
|
44
|
+
<Button asChild>
|
|
45
|
+
<a href="/home">Home</a>
|
|
46
|
+
</Button>,
|
|
47
|
+
);
|
|
48
|
+
expect(screen.getByRole("link", { name: "Home" })).toHaveAttribute("href", "/home");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
|
|
6
|
+
export const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap touch-manipulation rounded-md text-body font-medium transition-[color,background-color,border-color,box-shadow,scale] duration-fast ease-standard active:scale-[0.98] motion-reduce:active:scale-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default:
|
|
12
|
+
"bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
|
|
13
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
|
|
16
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
17
|
+
// The calm "outlined but quiet" rung (#194, research 02 §3a). `outline` uses
|
|
18
|
+
// the form-field `border-input` token, `outline-subtle` uses `border-border`.
|
|
19
|
+
// Since the ADR 0010 Amendment (2026-06-20) returned `--input` to the subtle
|
|
20
|
+
// rung, these two are now VISUALLY IDENTICAL by default — both variant names
|
|
21
|
+
// are kept as a SEMANTIC SEAM (a future brand could re-separate `--input` from
|
|
22
|
+
// `--border`) and to avoid churning `outline-subtle` callers (e.g.
|
|
23
|
+
// change-review.tsx). `outline` stays for genuinely form-adjacent controls.
|
|
24
|
+
"outline-subtle":
|
|
25
|
+
"border border-border bg-background hover:bg-accent hover:text-accent-foreground",
|
|
26
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
27
|
+
// #399 — a link button is TEXT on the page, so it takes the on-surface
|
|
28
|
+
// `-text` rung; `bg-primary` above keeps the fill rung.
|
|
29
|
+
link: "text-primary-text underline-offset-4 hover:underline",
|
|
30
|
+
},
|
|
31
|
+
size: {
|
|
32
|
+
sm: "h-8 rounded-md px-3 text-meta",
|
|
33
|
+
default: "h-9 px-4 py-2",
|
|
34
|
+
lg: "h-10 rounded-md px-6",
|
|
35
|
+
icon: "size-9",
|
|
36
|
+
"icon-sm": "size-8",
|
|
37
|
+
"icon-lg": "size-10",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultVariants: { variant: "default", size: "default" },
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export interface ButtonProps
|
|
45
|
+
extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
46
|
+
/** Render the child element as the button (Radix Slot) instead of a <button>. */
|
|
47
|
+
asChild?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
51
|
+
{ className, variant, size, asChild = false, type, ...props },
|
|
52
|
+
ref,
|
|
53
|
+
) {
|
|
54
|
+
const Comp = asChild ? Slot : "button";
|
|
55
|
+
return (
|
|
56
|
+
<Comp
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
59
|
+
type={asChild ? undefined : (type ?? "button")}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Button, buttonVariants, type ButtonProps } from "./button";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { ButtonGroup, ButtonGroupText } from "./button-group";
|
|
3
|
+
import { Button } from "../button";
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Forms/ButtonGroup",
|
|
6
|
+
component: ButtonGroup,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
argTypes: {
|
|
9
|
+
orientation: {
|
|
10
|
+
description: "Layout direction of the group.",
|
|
11
|
+
control: { type: "radio" },
|
|
12
|
+
options: ["horizontal", "vertical"],
|
|
13
|
+
table: { category: "Appearance" },
|
|
14
|
+
},
|
|
15
|
+
className: {
|
|
16
|
+
description: "Additional CSS classes applied to the root element.",
|
|
17
|
+
control: "text",
|
|
18
|
+
table: { category: "Appearance" },
|
|
19
|
+
},
|
|
20
|
+
children: {
|
|
21
|
+
description: "Button or ButtonGroupText elements to group together.",
|
|
22
|
+
control: false,
|
|
23
|
+
table: { category: "Content" },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<typeof ButtonGroup>;
|
|
27
|
+
export default meta;
|
|
28
|
+
type Story = StoryObj<typeof meta>;
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
render: () => (
|
|
31
|
+
<ButtonGroup>
|
|
32
|
+
<Button variant="outline">Day</Button>
|
|
33
|
+
<Button variant="outline">Week</Button>
|
|
34
|
+
<Button variant="outline">Month</Button>
|
|
35
|
+
</ButtonGroup>
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
export const WithText: Story = {
|
|
39
|
+
render: () => (
|
|
40
|
+
<ButtonGroup>
|
|
41
|
+
<ButtonGroupText>https://</ButtonGroupText>
|
|
42
|
+
<Button variant="outline">brand-ui.dev</Button>
|
|
43
|
+
</ButtonGroup>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { forwardRef, type ComponentProps } from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../lib/cn";
|
|
5
|
+
import { Separator } from "../separator";
|
|
6
|
+
|
|
7
|
+
export const buttonGroupVariants = cva(
|
|
8
|
+
"flex w-fit items-stretch [&>*]:relative [&>*]:focus-within:z-10 has-[>[data-slot=button-group]]:gap-2",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
orientation: {
|
|
12
|
+
horizontal:
|
|
13
|
+
"[&>*:not(:first-child)]:rounded-s-none [&>*:not(:first-child)]:border-s-0 [&>*:not(:last-child)]:rounded-e-none",
|
|
14
|
+
vertical:
|
|
15
|
+
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: { orientation: "horizontal" },
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const ButtonGroup = forwardRef<
|
|
23
|
+
HTMLDivElement,
|
|
24
|
+
ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>
|
|
25
|
+
>(function ButtonGroup({ className, orientation = "horizontal", ...props }, ref) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
ref={ref}
|
|
29
|
+
role="group"
|
|
30
|
+
data-slot="button-group"
|
|
31
|
+
data-orientation={orientation}
|
|
32
|
+
className={cn(buttonGroupVariants({ orientation }), className)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const ButtonGroupText = forwardRef<
|
|
39
|
+
HTMLDivElement,
|
|
40
|
+
ComponentProps<"div"> & { asChild?: boolean }
|
|
41
|
+
>(function ButtonGroupText({ className, asChild = false, ...props }, ref) {
|
|
42
|
+
const Comp = asChild ? Slot : "div";
|
|
43
|
+
return (
|
|
44
|
+
<Comp
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={cn(
|
|
47
|
+
"flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const ButtonGroupSeparator = forwardRef<HTMLDivElement, ComponentProps<typeof Separator>>(
|
|
56
|
+
function ButtonGroupSeparator({ className, orientation = "vertical", ...props }, ref) {
|
|
57
|
+
return (
|
|
58
|
+
<Separator
|
|
59
|
+
ref={ref}
|
|
60
|
+
data-slot="button-group-separator"
|
|
61
|
+
orientation={orientation}
|
|
62
|
+
className={cn(
|
|
63
|
+
"relative !m-0 self-stretch bg-input data-[orientation=vertical]:h-auto",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { expect } from "storybook/test";
|
|
4
|
+
import { Calendar } from "./calendar";
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Forms/Calendar",
|
|
7
|
+
component: Calendar,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
argTypes: {
|
|
10
|
+
mode: {
|
|
11
|
+
description: "Selection mode: single date, multiple dates, or a range.",
|
|
12
|
+
control: { type: "select" },
|
|
13
|
+
options: ["single", "multiple", "range"],
|
|
14
|
+
table: { category: "Behavior" },
|
|
15
|
+
},
|
|
16
|
+
showOutsideDays: {
|
|
17
|
+
description: "Show days from adjacent months in the current month grid.",
|
|
18
|
+
control: "boolean",
|
|
19
|
+
table: { category: "Appearance" },
|
|
20
|
+
},
|
|
21
|
+
numberOfMonths: {
|
|
22
|
+
description: "Number of months to display side by side.",
|
|
23
|
+
control: "number",
|
|
24
|
+
table: { category: "Appearance" },
|
|
25
|
+
},
|
|
26
|
+
disabled: {
|
|
27
|
+
description: "Disables specific dates (Matcher or boolean).",
|
|
28
|
+
control: false,
|
|
29
|
+
table: { category: "State" },
|
|
30
|
+
},
|
|
31
|
+
selected: {
|
|
32
|
+
description: "Controlled selected date(s).",
|
|
33
|
+
control: false,
|
|
34
|
+
table: { category: "State" },
|
|
35
|
+
},
|
|
36
|
+
onSelect: {
|
|
37
|
+
description: "Called when the user picks a date.",
|
|
38
|
+
control: false,
|
|
39
|
+
table: { category: "Behavior" },
|
|
40
|
+
},
|
|
41
|
+
className: {
|
|
42
|
+
description: "Additional CSS classes applied to the root element.",
|
|
43
|
+
control: "text",
|
|
44
|
+
table: { category: "Appearance" },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
} satisfies Meta<typeof Calendar>;
|
|
48
|
+
export default meta;
|
|
49
|
+
type Story = StoryObj<typeof meta>;
|
|
50
|
+
// Pinned rather than `new Date()` (#430): the selected day's cell paints
|
|
51
|
+
// `--primary-foreground` on `--primary` (light), a real, accepted AA
|
|
52
|
+
// miss (4.31:1 vs a 4.5:1 floor — #180, `INK_EXEMPT` in
|
|
53
|
+
// `packages/tokens/src/themes-contrast.test.ts`). axe-core's `color-contrast`
|
|
54
|
+
// rule reports single-CHARACTER text as `incomplete`/`shortTextContent`
|
|
55
|
+
// instead of scoring it, so with `new Date()` this story was red on days
|
|
56
|
+
// 10-31 of the month and silently green on days 1-9 — a verification result
|
|
57
|
+
// keyed to the wall clock, not to the code. The pinned day MUST stay in the
|
|
58
|
+
// 10-31 range: a single-digit pin would make the suite green forever, but
|
|
59
|
+
// only by routing the cell into that same unscoreable-text path, which hides
|
|
60
|
+
// the miss rather than fixing or honestly exempting it (see `Test to add` in
|
|
61
|
+
// #430 and `scripts/a11y-baseline.json`, which now carries this story's
|
|
62
|
+
// entry). `defaultMonth` is pinned alongside it so the displayed month is
|
|
63
|
+
// never a function of "now" either.
|
|
64
|
+
//
|
|
65
|
+
// The `today` cell (react-day-picker's `today` modifier) still tracks the
|
|
66
|
+
// real wall clock and is NOT pinned — only the `selected` cell is. That is
|
|
67
|
+
// safe for this lock: the `today` plate (`bg-accent`/`text-accent-foreground`)
|
|
68
|
+
// is a different, contrast-clean token pair (confirmed by probing it directly
|
|
69
|
+
// with axe, `runOnly: ["color-contrast"]`, on a two-digit "today" — zero
|
|
70
|
+
// violations), so it cannot flip this story's axe outcome on any date.
|
|
71
|
+
const FIXED_MONTH = new Date(2026, 7, 1); // 2026-08-01
|
|
72
|
+
const FIXED_SELECTED = new Date(2026, 7, 17); // 2026-08-17 — two digits, on purpose
|
|
73
|
+
|
|
74
|
+
export const Default: Story = {
|
|
75
|
+
render: () => {
|
|
76
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
77
|
+
const [date, setDate] = useState<Date | undefined>(FIXED_SELECTED);
|
|
78
|
+
return (
|
|
79
|
+
<Calendar
|
|
80
|
+
mode="single"
|
|
81
|
+
defaultMonth={FIXED_MONTH}
|
|
82
|
+
selected={date}
|
|
83
|
+
onSelect={setDate}
|
|
84
|
+
className="rounded-md border"
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
// Confirms the calendar grid renders and a day cell is present.
|
|
89
|
+
play: async ({ canvas }) => {
|
|
90
|
+
const grid = canvas.getByRole("grid");
|
|
91
|
+
await expect(grid).toBeInTheDocument();
|
|
92
|
+
const dayCells = canvas.getAllByRole("gridcell");
|
|
93
|
+
await expect(dayCells.length).toBeGreaterThan(0);
|
|
94
|
+
},
|
|
95
|
+
};
|