@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,156 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
|
3
|
+
import { expect } from "storybook/test";
|
|
4
|
+
import { Alert, AlertDescription, AlertTitle } from "./alert";
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "States/Alert",
|
|
7
|
+
component: Alert,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
argTypes: {
|
|
10
|
+
variant: {
|
|
11
|
+
description: "Visual tone of the alert banner.",
|
|
12
|
+
control: { type: "select" },
|
|
13
|
+
options: ["default", "info", "success", "warning", "destructive"],
|
|
14
|
+
table: { category: "Appearance" },
|
|
15
|
+
},
|
|
16
|
+
role: {
|
|
17
|
+
description:
|
|
18
|
+
'ARIA role. Use "alert" for assertive announcements or "group" for panels with focusable controls.',
|
|
19
|
+
control: { type: "select" },
|
|
20
|
+
options: ["alert", "group", "status"],
|
|
21
|
+
table: { category: "Accessibility" },
|
|
22
|
+
},
|
|
23
|
+
className: {
|
|
24
|
+
description: "Additional CSS classes applied to the alert container.",
|
|
25
|
+
control: "text",
|
|
26
|
+
table: { category: "Styling" },
|
|
27
|
+
},
|
|
28
|
+
children: {
|
|
29
|
+
description: "Compose AlertTitle and AlertDescription inside.",
|
|
30
|
+
control: false,
|
|
31
|
+
table: { category: "Content" },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} satisfies Meta<typeof Alert>;
|
|
35
|
+
export default meta;
|
|
36
|
+
type Story = StoryObj<typeof meta>;
|
|
37
|
+
export const Info_: Story = {
|
|
38
|
+
name: "Info",
|
|
39
|
+
render: () => (
|
|
40
|
+
<Alert variant="info" className="max-w-md">
|
|
41
|
+
<Info />
|
|
42
|
+
<AlertTitle>Heads up</AlertTitle>
|
|
43
|
+
<AlertDescription>This deploy includes a schema migration.</AlertDescription>
|
|
44
|
+
</Alert>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
export const Destructive: Story = {
|
|
48
|
+
render: () => (
|
|
49
|
+
<Alert variant="destructive" className="max-w-md">
|
|
50
|
+
<AlertTriangle />
|
|
51
|
+
<AlertTitle>Error</AlertTitle>
|
|
52
|
+
<AlertDescription>Your session has expired.</AlertDescription>
|
|
53
|
+
</Alert>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `default` and `success` were selectable in the controls panel
|
|
59
|
+
* (`argTypes.variant.options`) but never actually rendered by any story
|
|
60
|
+
* (#388) — so neither one ever reached the blocking interaction + axe job.
|
|
61
|
+
* This renders them alongside `info`/`warning` to close that gap.
|
|
62
|
+
*
|
|
63
|
+
* `destructive` is intentionally NOT repeated here: it already has its own
|
|
64
|
+
* dedicated `Destructive` story above, with a known, separately-tracked
|
|
65
|
+
* contrast exemption (`states-alert--destructive` in
|
|
66
|
+
* scripts/a11y-baseline.json, #381) — folding it into this new story id would
|
|
67
|
+
* launder that pre-existing violation onto a brand-new, unbaselined surface.
|
|
68
|
+
*/
|
|
69
|
+
export const AllVariants: Story = {
|
|
70
|
+
render: () => (
|
|
71
|
+
<div className="flex max-w-md flex-col gap-3">
|
|
72
|
+
<Alert variant="default">
|
|
73
|
+
<AlertTitle>Default</AlertTitle>
|
|
74
|
+
<AlertDescription>Neutral tone — no semantic status implied.</AlertDescription>
|
|
75
|
+
</Alert>
|
|
76
|
+
<Alert variant="info">
|
|
77
|
+
<Info />
|
|
78
|
+
<AlertTitle>Info</AlertTitle>
|
|
79
|
+
<AlertDescription>This deploy includes a schema migration.</AlertDescription>
|
|
80
|
+
</Alert>
|
|
81
|
+
<Alert variant="success">
|
|
82
|
+
<CheckCircle2 />
|
|
83
|
+
<AlertTitle>Success</AlertTitle>
|
|
84
|
+
<AlertDescription>Your changes were saved.</AlertDescription>
|
|
85
|
+
</Alert>
|
|
86
|
+
<Alert variant="warning">
|
|
87
|
+
<AlertTriangle />
|
|
88
|
+
<AlertTitle>Warning</AlertTitle>
|
|
89
|
+
<AlertDescription>Double-check before continuing.</AlertDescription>
|
|
90
|
+
</Alert>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* `AlertTitle` defaults to `<h5>` (unchanged) but accepts `as` to render at a
|
|
97
|
+
* different heading level, avoiding a skipped-level jump when the alert sits
|
|
98
|
+
* inline in a form under an `<h2>`/`<h3>` (#329).
|
|
99
|
+
*
|
|
100
|
+
* Uses `variant="warning"`, not `"destructive"` — the destructive variant's
|
|
101
|
+
* text-on-tint contrast is a pre-existing, separately tracked issue
|
|
102
|
+
* (`states-alert--destructive` in scripts/a11y-baseline.json) unrelated to
|
|
103
|
+
* this fix; reusing it here would launder a second, avoidable a11y violation
|
|
104
|
+
* into a brand-new story.
|
|
105
|
+
*/
|
|
106
|
+
export const InlineFormError: Story = {
|
|
107
|
+
render: () => (
|
|
108
|
+
<div className="flex max-w-md flex-col gap-3">
|
|
109
|
+
<h2 className="text-title font-semibold text-foreground">Payment details</h2>
|
|
110
|
+
<Alert variant="warning">
|
|
111
|
+
<AlertTriangle />
|
|
112
|
+
<AlertTitle as="h3">Card declined</AlertTitle>
|
|
113
|
+
<AlertDescription>Your card was declined — try another payment method.</AlertDescription>
|
|
114
|
+
</Alert>
|
|
115
|
+
</div>
|
|
116
|
+
),
|
|
117
|
+
play: async ({ canvas }) => {
|
|
118
|
+
const heading = canvas.getByRole("heading", { level: 3, name: "Card declined" });
|
|
119
|
+
await expect(heading.tagName).toBe("H3");
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `AlertDescription` gains an opt-in `measure` prop that caps genuine prose to
|
|
125
|
+
* a readable line length (`max-w-prose`, ~65ch) instead of running edge to
|
|
126
|
+
* edge in a wide alert (#339). Off by default.
|
|
127
|
+
*/
|
|
128
|
+
export const DescriptionMeasure: Story = {
|
|
129
|
+
render: () => (
|
|
130
|
+
<div className="flex w-[900px] flex-col gap-4">
|
|
131
|
+
<Alert variant="info" className="w-full">
|
|
132
|
+
<Info />
|
|
133
|
+
<AlertTitle>Uncapped (default)</AlertTitle>
|
|
134
|
+
<AlertDescription>
|
|
135
|
+
This migration note runs the full width of a very wide alert banner, which makes it
|
|
136
|
+
materially harder to scan once the line gets long enough for the eye to lose its place
|
|
137
|
+
between lines.
|
|
138
|
+
</AlertDescription>
|
|
139
|
+
</Alert>
|
|
140
|
+
<Alert variant="info" className="w-full">
|
|
141
|
+
<Info />
|
|
142
|
+
<AlertTitle>Capped via `measure`</AlertTitle>
|
|
143
|
+
<AlertDescription measure>
|
|
144
|
+
This migration note is capped to a readable prose measure even inside a very wide banner,
|
|
145
|
+
so each line stays short enough to read comfortably.
|
|
146
|
+
</AlertDescription>
|
|
147
|
+
</Alert>
|
|
148
|
+
</div>
|
|
149
|
+
),
|
|
150
|
+
play: async ({ canvas }) => {
|
|
151
|
+
const capped = canvas.getByText(/capped to a readable prose measure/);
|
|
152
|
+
await expect(capped).toHaveClass("max-w-prose");
|
|
153
|
+
const uncapped = canvas.getByText(/runs the full width/);
|
|
154
|
+
await expect(uncapped).not.toHaveClass("max-w-prose");
|
|
155
|
+
},
|
|
156
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { Alert, AlertDescription, AlertTitle } from "./alert";
|
|
4
|
+
describe("Alert", () => {
|
|
5
|
+
it("renders with role=alert and a title", () => {
|
|
6
|
+
render(
|
|
7
|
+
<Alert>
|
|
8
|
+
<AlertTitle>Heads up</AlertTitle>
|
|
9
|
+
</Alert>,
|
|
10
|
+
);
|
|
11
|
+
expect(screen.getByRole("alert")).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByText("Heads up")).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// --- AlertTitle `as` seam + ref-type fix (#329) ---
|
|
16
|
+
it("AlertTitle defaults to <h5> (byte-identical to pre-#329 output)", () => {
|
|
17
|
+
render(<AlertTitle>Default level</AlertTitle>);
|
|
18
|
+
const heading = screen.getByRole("heading", { level: 5, name: "Default level" });
|
|
19
|
+
expect(heading.tagName).toBe("H5");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("AlertTitle renders the requested heading level via `as` (#329)", () => {
|
|
23
|
+
render(<AlertTitle as="h3">Inline error</AlertTitle>);
|
|
24
|
+
const heading = screen.getByRole("heading", { level: 3, name: "Inline error" });
|
|
25
|
+
expect(heading.tagName).toBe("H3");
|
|
26
|
+
expect(heading).toHaveClass("mb-1", "font-medium", "leading-none", "tracking-tight");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// --- AlertDescription `measure` cap (#339) ---
|
|
30
|
+
it("AlertDescription has no max-w cap by default (#339)", () => {
|
|
31
|
+
render(<AlertDescription>Short note</AlertDescription>);
|
|
32
|
+
expect(screen.getByText("Short note")).not.toHaveClass("max-w-prose");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("AlertDescription caps to a readable measure when `measure` is set (#339)", () => {
|
|
36
|
+
render(
|
|
37
|
+
<AlertDescription measure>
|
|
38
|
+
Genuine prose that should not run edge to edge in a wide alert.
|
|
39
|
+
</AlertDescription>,
|
|
40
|
+
);
|
|
41
|
+
const el = screen.getByText(/Genuine prose/);
|
|
42
|
+
expect(el).toHaveClass("max-w-prose");
|
|
43
|
+
expect(el).toHaveClass("text-muted-foreground");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// role override seam (#191, research 11 §B.3): a pending decision with
|
|
47
|
+
// focusable controls must be a labelled region, not an assertive live region.
|
|
48
|
+
it("accepts a role override (default stays alert)", () => {
|
|
49
|
+
render(
|
|
50
|
+
<Alert aria-label="Pending decision" role="group">
|
|
51
|
+
<AlertTitle>Approve this?</AlertTitle>
|
|
52
|
+
</Alert>,
|
|
53
|
+
);
|
|
54
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
55
|
+
expect(screen.getByRole("group", { name: "Pending decision" })).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { forwardRef, type HTMLAttributes } from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../../lib/cn";
|
|
4
|
+
|
|
5
|
+
export const alertVariants = cva(
|
|
6
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg]:absolute [&>svg]:start-4 [&>svg]:top-4 [&>svg]:size-4 [&>svg]:text-current [&>svg~*]:ps-7",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-card text-card-foreground",
|
|
11
|
+
info: "border-info/40 bg-info/10 text-foreground",
|
|
12
|
+
success: "border-success/40 bg-success/10 text-foreground",
|
|
13
|
+
warning: "border-warning/50 bg-warning/10 text-foreground",
|
|
14
|
+
destructive:
|
|
15
|
+
"border-destructive/40 bg-destructive/10 text-destructive [&>svg]:text-destructive",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: { variant: "default" },
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const Alert = forwardRef<
|
|
23
|
+
HTMLDivElement,
|
|
24
|
+
HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
25
|
+
>(function Alert(
|
|
26
|
+
// `role` is an explicit override seam (default "alert"): a PENDING decision
|
|
27
|
+
// containing focusable controls (e.g. @elabs-ai/components-ai ApprovalCard) must be a
|
|
28
|
+
// labelled region (`role="group"` + aria-labelledby), not an assertive live
|
|
29
|
+
// region (research/structural-design/11 §B.3).
|
|
30
|
+
{ className, variant, role = "alert", ...props },
|
|
31
|
+
ref,
|
|
32
|
+
) {
|
|
33
|
+
return (
|
|
34
|
+
<div ref={ref} role={role} className={cn(alertVariants({ variant }), className)} {...props} />
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export interface AlertTitleProps extends HTMLAttributes<HTMLHeadingElement> {
|
|
39
|
+
/**
|
|
40
|
+
* Render at a different heading level to fit the surrounding document
|
|
41
|
+
* outline (#329). The visual (`mb-1 font-medium leading-none
|
|
42
|
+
* tracking-tight`) is identical at every level. @default "h5"
|
|
43
|
+
*/
|
|
44
|
+
as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Ref type matches the rendered element (`HTMLHeadingElement` for every `as`
|
|
48
|
+
// value) — previously mismatched to `HTMLParagraphElement` (#329).
|
|
49
|
+
export const AlertTitle = forwardRef<HTMLHeadingElement, AlertTitleProps>(function AlertTitle(
|
|
50
|
+
{ className, as: Tag = "h5", ...props },
|
|
51
|
+
ref,
|
|
52
|
+
) {
|
|
53
|
+
return (
|
|
54
|
+
<Tag
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export interface AlertDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {
|
|
63
|
+
/**
|
|
64
|
+
* Cap the line length to a readable prose measure (`max-w-prose`, ~65ch)
|
|
65
|
+
* for genuine paragraph content (#339). Off by default — short,
|
|
66
|
+
* label-style descriptions should stay full width.
|
|
67
|
+
*/
|
|
68
|
+
measure?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// NOTE (#329 AC): checked for the same ref-type mismatch as `AlertTitle` —
|
|
72
|
+
// `AlertDescription` renders a `<div>`, not a heading, so it needs no `as`
|
|
73
|
+
// heading seam. Its own generic (`HTMLParagraphElement` on a rendered `<div>`)
|
|
74
|
+
// is a pre-existing, unrelated mismatch; left as-is (out of scope for #329,
|
|
75
|
+
// which is scoped to heading semantics, and not requested by #339 either) to
|
|
76
|
+
// avoid a ref-type change unrelated to either issue's acceptance criteria.
|
|
77
|
+
export const AlertDescription = forwardRef<HTMLParagraphElement, AlertDescriptionProps>(
|
|
78
|
+
function AlertDescription({ className, measure, ...props }, ref) {
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
ref={ref}
|
|
82
|
+
className={cn(
|
|
83
|
+
"text-sm [&_p]:leading-relaxed text-muted-foreground",
|
|
84
|
+
measure && "max-w-prose",
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, waitFor, within } from "storybook/test";
|
|
3
|
+
import { Button } from "../button";
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogAction,
|
|
7
|
+
AlertDialogCancel,
|
|
8
|
+
AlertDialogContent,
|
|
9
|
+
AlertDialogDescription,
|
|
10
|
+
AlertDialogFooter,
|
|
11
|
+
AlertDialogHeader,
|
|
12
|
+
AlertDialogTitle,
|
|
13
|
+
AlertDialogTrigger,
|
|
14
|
+
} from "./alert-dialog";
|
|
15
|
+
const meta = {
|
|
16
|
+
title: "Overlays/AlertDialog",
|
|
17
|
+
component: AlertDialog,
|
|
18
|
+
tags: ["autodocs"],
|
|
19
|
+
argTypes: {
|
|
20
|
+
open: {
|
|
21
|
+
description: "Controlled open state of the dialog.",
|
|
22
|
+
control: "boolean",
|
|
23
|
+
table: { category: "State" },
|
|
24
|
+
},
|
|
25
|
+
defaultOpen: {
|
|
26
|
+
description: "Uncontrolled initial open state.",
|
|
27
|
+
control: "boolean",
|
|
28
|
+
table: { category: "State" },
|
|
29
|
+
},
|
|
30
|
+
onOpenChange: {
|
|
31
|
+
description: "Callback when the open state changes.",
|
|
32
|
+
control: false,
|
|
33
|
+
table: { category: "Events" },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
} satisfies Meta<typeof AlertDialog>;
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
export const Default: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<AlertDialog>
|
|
42
|
+
<AlertDialogTrigger asChild>
|
|
43
|
+
<Button variant="destructive">Delete</Button>
|
|
44
|
+
</AlertDialogTrigger>
|
|
45
|
+
<AlertDialogContent>
|
|
46
|
+
<AlertDialogHeader>
|
|
47
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
48
|
+
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
|
49
|
+
</AlertDialogHeader>
|
|
50
|
+
<AlertDialogFooter>
|
|
51
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
52
|
+
<AlertDialogAction>Delete</AlertDialogAction>
|
|
53
|
+
</AlertDialogFooter>
|
|
54
|
+
</AlertDialogContent>
|
|
55
|
+
</AlertDialog>
|
|
56
|
+
),
|
|
57
|
+
// Opens the alert dialog (portalled to body), asserts the title and both action
|
|
58
|
+
// buttons are visible, then cancels to close it.
|
|
59
|
+
play: async ({ canvas, canvasElement, userEvent }) => {
|
|
60
|
+
await userEvent.click(canvas.getByRole("button", { name: /delete/i }));
|
|
61
|
+
const body = within(canvasElement.ownerDocument.body);
|
|
62
|
+
const dialog = await body.findByRole("alertdialog");
|
|
63
|
+
await waitFor(() => expect(within(dialog).getByText("Are you sure?")).toBeVisible());
|
|
64
|
+
await waitFor(() =>
|
|
65
|
+
expect(within(dialog).getByRole("button", { name: /cancel/i })).toBeVisible(),
|
|
66
|
+
);
|
|
67
|
+
await waitFor(() =>
|
|
68
|
+
expect(within(dialog).getByRole("button", { name: /delete/i })).toBeVisible(),
|
|
69
|
+
);
|
|
70
|
+
// Cancel dismisses the dialog
|
|
71
|
+
await userEvent.click(within(dialog).getByRole("button", { name: /cancel/i }));
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
type ComponentPropsWithoutRef,
|
|
4
|
+
type ElementRef,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
} from "react";
|
|
7
|
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
|
|
8
|
+
import { cn } from "../../lib/cn";
|
|
9
|
+
import { buttonVariants } from "../button";
|
|
10
|
+
|
|
11
|
+
export const AlertDialog = AlertDialogPrimitive.Root;
|
|
12
|
+
export const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
|
13
|
+
export const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
14
|
+
|
|
15
|
+
const AlertDialogOverlay = forwardRef<
|
|
16
|
+
ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
17
|
+
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
18
|
+
>(function AlertDialogOverlay({ className, ...props }, ref) {
|
|
19
|
+
return (
|
|
20
|
+
<AlertDialogPrimitive.Overlay
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
"fixed inset-0 z-50 bg-foreground/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const AlertDialogContent = forwardRef<
|
|
32
|
+
ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
33
|
+
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
|
34
|
+
>(function AlertDialogContent({ className, ...props }, ref) {
|
|
35
|
+
return (
|
|
36
|
+
<AlertDialogPortal>
|
|
37
|
+
<AlertDialogOverlay />
|
|
38
|
+
<AlertDialogPrimitive.Content
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn(
|
|
41
|
+
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-card p-6 text-card-foreground shadow-ring-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
</AlertDialogPortal>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export function AlertDialogHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
51
|
+
return <div className={cn("flex flex-col gap-1.5 text-start", className)} {...props} />;
|
|
52
|
+
}
|
|
53
|
+
export function AlertDialogFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
export const AlertDialogTitle = forwardRef<
|
|
62
|
+
ElementRef<typeof AlertDialogPrimitive.Title>,
|
|
63
|
+
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
|
64
|
+
>(function AlertDialogTitle({ className, ...props }, ref) {
|
|
65
|
+
return (
|
|
66
|
+
<AlertDialogPrimitive.Title
|
|
67
|
+
ref={ref}
|
|
68
|
+
className={cn("text-lg font-semibold", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
export const AlertDialogDescription = forwardRef<
|
|
74
|
+
ElementRef<typeof AlertDialogPrimitive.Description>,
|
|
75
|
+
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
|
76
|
+
>(function AlertDialogDescription({ className, ...props }, ref) {
|
|
77
|
+
return (
|
|
78
|
+
<AlertDialogPrimitive.Description
|
|
79
|
+
ref={ref}
|
|
80
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
export const AlertDialogAction = forwardRef<
|
|
86
|
+
ElementRef<typeof AlertDialogPrimitive.Action>,
|
|
87
|
+
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
|
88
|
+
>(function AlertDialogAction({ className, ...props }, ref) {
|
|
89
|
+
return (
|
|
90
|
+
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
export const AlertDialogCancel = forwardRef<
|
|
94
|
+
ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
|
95
|
+
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
|
96
|
+
>(function AlertDialogCancel({ className, ...props }, ref) {
|
|
97
|
+
return (
|
|
98
|
+
<AlertDialogPrimitive.Cancel
|
|
99
|
+
ref={ref}
|
|
100
|
+
className={cn(buttonVariants({ variant: "outline" }), className)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { AppShell } from "./app-shell";
|
|
3
|
+
import { TopNav } from "../top-nav";
|
|
4
|
+
import { PageShell } from "../page-shell";
|
|
5
|
+
import { SectionHeader } from "../section-header";
|
|
6
|
+
import { Button } from "../button";
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Layout/App Shell/Basic",
|
|
10
|
+
component: AppShell,
|
|
11
|
+
parameters: { layout: "fullscreen" },
|
|
12
|
+
} satisfies Meta<typeof AppShell>;
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
const nav = ["Dashboard", "Projects", "Reports", "Settings"];
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<div className="h-[600px]">
|
|
21
|
+
<AppShell
|
|
22
|
+
sidebar={
|
|
23
|
+
<nav className="flex h-full w-60 flex-col gap-1 border-e border-sidebar-border bg-sidebar p-3 text-sidebar-foreground">
|
|
24
|
+
<div className="px-2 pb-3 font-semibold">Brand UI</div>
|
|
25
|
+
{nav.map((n, i) => (
|
|
26
|
+
<button
|
|
27
|
+
key={n}
|
|
28
|
+
className={
|
|
29
|
+
"rounded-md px-3 py-2 text-start text-body font-medium " +
|
|
30
|
+
(i === 0
|
|
31
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
32
|
+
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60")
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
{n}
|
|
36
|
+
</button>
|
|
37
|
+
))}
|
|
38
|
+
</nav>
|
|
39
|
+
}
|
|
40
|
+
topNav={
|
|
41
|
+
<TopNav end={<Button size="sm">Invite</Button>}>
|
|
42
|
+
<span className="text-body font-medium">Dashboard</span>
|
|
43
|
+
</TopNav>
|
|
44
|
+
}
|
|
45
|
+
>
|
|
46
|
+
<PageShell
|
|
47
|
+
header={
|
|
48
|
+
<SectionHeader
|
|
49
|
+
title="Overview"
|
|
50
|
+
description="A lightweight generic shell. For full sidebar behavior use the Sidebar primitive."
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
<div className="rounded-xl border bg-card p-6 text-body text-muted-foreground">
|
|
55
|
+
Main content area.
|
|
56
|
+
</div>
|
|
57
|
+
</PageShell>
|
|
58
|
+
</AppShell>
|
|
59
|
+
</div>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { cn } from "../../lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface AppShellProps {
|
|
5
|
+
/** Persistent left navigation (often a <Sidebar />). */
|
|
6
|
+
sidebar?: ReactNode;
|
|
7
|
+
/** Top bar (often a <TopNav />). */
|
|
8
|
+
topNav?: ReactNode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
/** Class for the scrolling main content region. */
|
|
12
|
+
mainClassName?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* App-first layout: fixed-height viewport with an optional sidebar, a top bar,
|
|
17
|
+
* and a scrollable main region. Pure layout — bring your own nav components.
|
|
18
|
+
*/
|
|
19
|
+
export function AppShell({ sidebar, topNav, children, className, mainClassName }: AppShellProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn("flex h-dvh w-full overflow-hidden bg-background text-foreground", className)}
|
|
23
|
+
>
|
|
24
|
+
{sidebar ? <div className="hidden md:flex">{sidebar}</div> : null}
|
|
25
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
26
|
+
{topNav}
|
|
27
|
+
<main className={cn("min-h-0 flex-1 overflow-y-auto", mainClassName)}>{children}</main>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AppShell, type AppShellProps } from "./app-shell";
|