@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,354 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
// a2ui.exposed: no
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
forwardRef,
|
|
7
|
+
use,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useId,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
type HTMLAttributes,
|
|
15
|
+
type ReactNode,
|
|
16
|
+
} from "react";
|
|
17
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
18
|
+
import { Check } from "lucide-react";
|
|
19
|
+
import { cn } from "../../lib/cn";
|
|
20
|
+
import { Button } from "../button";
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Types & context
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export interface WizardStepMeta {
|
|
27
|
+
/** Stable id for the step. */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Title shown in the step indicator. */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Optional supporting text shown under the title. */
|
|
32
|
+
description?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A step validator — return false (or a rejected/`false` promise) to block advance. */
|
|
36
|
+
export type WizardValidator = () => boolean | Promise<boolean>;
|
|
37
|
+
|
|
38
|
+
interface WizardContextValue {
|
|
39
|
+
baseId: string;
|
|
40
|
+
step: number;
|
|
41
|
+
steps: WizardStepMeta[];
|
|
42
|
+
count: number;
|
|
43
|
+
isFirst: boolean;
|
|
44
|
+
isLast: boolean;
|
|
45
|
+
validating: boolean;
|
|
46
|
+
goTo: (index: number) => void;
|
|
47
|
+
next: () => void;
|
|
48
|
+
prev: () => void;
|
|
49
|
+
setActiveValidator: (validator: WizardValidator | undefined) => void;
|
|
50
|
+
orientation: "horizontal" | "vertical";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const WizardContext = createContext<WizardContextValue | null>(null);
|
|
54
|
+
|
|
55
|
+
function useWizard(): WizardContextValue {
|
|
56
|
+
const ctx = use(WizardContext);
|
|
57
|
+
if (!ctx) throw new Error("Wizard subcomponents must be used inside <Wizard>.");
|
|
58
|
+
return ctx;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const wizardVariants = cva("flex gap-6", {
|
|
62
|
+
variants: {
|
|
63
|
+
orientation: {
|
|
64
|
+
horizontal: "flex-col",
|
|
65
|
+
vertical: "flex-row",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
defaultVariants: {
|
|
69
|
+
orientation: "horizontal",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Wizard (Provider / root)
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export interface WizardProps
|
|
78
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">, VariantProps<typeof wizardVariants> {
|
|
79
|
+
/** The ordered step metadata used to render the indicator. */
|
|
80
|
+
steps: WizardStepMeta[];
|
|
81
|
+
/** Controlled active step index. */
|
|
82
|
+
step?: number;
|
|
83
|
+
/** Uncontrolled initial step index. Default 0. */
|
|
84
|
+
defaultStep?: number;
|
|
85
|
+
/** Called whenever the active step changes. */
|
|
86
|
+
onStepChange?: (step: number) => void;
|
|
87
|
+
children?: ReactNode;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Wizard — a multi-step flow with a step indicator, per-step validation gating,
|
|
92
|
+
* and Back/Next/Finish navigation.
|
|
93
|
+
*
|
|
94
|
+
* Controlled via `step`/`onStepChange`; uncontrolled via `defaultStep`. State is
|
|
95
|
+
* lifted into the provider and exposed to `WizardSteps`/`WizardStep`/`WizardNav`
|
|
96
|
+
* via context. `Next` runs the active step's `validate` (sync or async) before
|
|
97
|
+
* advancing and stays enabled until validation runs.
|
|
98
|
+
*/
|
|
99
|
+
export const Wizard = forwardRef<HTMLDivElement, WizardProps>(function Wizard(
|
|
100
|
+
{
|
|
101
|
+
steps,
|
|
102
|
+
step: stepProp,
|
|
103
|
+
defaultStep = 0,
|
|
104
|
+
onStepChange,
|
|
105
|
+
orientation = "horizontal",
|
|
106
|
+
className,
|
|
107
|
+
children,
|
|
108
|
+
...props
|
|
109
|
+
},
|
|
110
|
+
ref,
|
|
111
|
+
) {
|
|
112
|
+
const baseId = useId();
|
|
113
|
+
const isControlled = stepProp !== undefined;
|
|
114
|
+
const [internalStep, setInternalStep] = useState(defaultStep);
|
|
115
|
+
const [validating, setValidating] = useState(false);
|
|
116
|
+
const activeValidator = useRef<WizardValidator | undefined>(undefined);
|
|
117
|
+
|
|
118
|
+
const count = steps.length;
|
|
119
|
+
const step = Math.min(isControlled ? (stepProp ?? 0) : internalStep, Math.max(0, count - 1));
|
|
120
|
+
|
|
121
|
+
const commit = useCallback(
|
|
122
|
+
(next: number) => {
|
|
123
|
+
const clamped = Math.min(Math.max(0, next), Math.max(0, count - 1));
|
|
124
|
+
if (!isControlled) setInternalStep(clamped);
|
|
125
|
+
onStepChange?.(clamped);
|
|
126
|
+
},
|
|
127
|
+
[isControlled, onStepChange, count],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const goTo = useCallback(
|
|
131
|
+
(index: number) => {
|
|
132
|
+
// Backward / same navigation never requires validation.
|
|
133
|
+
if (index <= step) {
|
|
134
|
+
commit(index);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
[step, commit],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const next = useCallback(() => {
|
|
141
|
+
const validator = activeValidator.current;
|
|
142
|
+
const advance = () => commit(step + 1);
|
|
143
|
+
if (!validator) {
|
|
144
|
+
advance();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const result = validator();
|
|
148
|
+
if (typeof result === "boolean") {
|
|
149
|
+
if (result) advance();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setValidating(true);
|
|
153
|
+
result
|
|
154
|
+
.then((ok) => {
|
|
155
|
+
if (ok) advance();
|
|
156
|
+
})
|
|
157
|
+
.finally(() => setValidating(false));
|
|
158
|
+
}, [step, commit]);
|
|
159
|
+
|
|
160
|
+
const prev = useCallback(() => commit(step - 1), [step, commit]);
|
|
161
|
+
|
|
162
|
+
const setActiveValidator = useCallback((validator: WizardValidator | undefined) => {
|
|
163
|
+
activeValidator.current = validator;
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
const ctx = useMemo<WizardContextValue>(
|
|
167
|
+
() => ({
|
|
168
|
+
baseId,
|
|
169
|
+
step,
|
|
170
|
+
steps,
|
|
171
|
+
count,
|
|
172
|
+
isFirst: step === 0,
|
|
173
|
+
isLast: step === count - 1,
|
|
174
|
+
validating,
|
|
175
|
+
goTo,
|
|
176
|
+
next,
|
|
177
|
+
prev,
|
|
178
|
+
setActiveValidator,
|
|
179
|
+
orientation: orientation ?? "horizontal",
|
|
180
|
+
}),
|
|
181
|
+
[baseId, step, steps, count, validating, goTo, next, prev, setActiveValidator, orientation],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<WizardContext value={ctx}>
|
|
186
|
+
<div
|
|
187
|
+
ref={ref}
|
|
188
|
+
data-slot="wizard"
|
|
189
|
+
className={cn(wizardVariants({ orientation }), className)}
|
|
190
|
+
{...props}
|
|
191
|
+
>
|
|
192
|
+
{children}
|
|
193
|
+
</div>
|
|
194
|
+
</WizardContext>
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// WizardSteps (the indicator)
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
export type WizardStepsProps = HTMLAttributes<HTMLOListElement>;
|
|
203
|
+
|
|
204
|
+
/** WizardSteps — the ordered step indicator. Reads steps from context. */
|
|
205
|
+
export const WizardSteps = forwardRef<HTMLOListElement, WizardStepsProps>(function WizardSteps(
|
|
206
|
+
{ className, ...props },
|
|
207
|
+
ref,
|
|
208
|
+
) {
|
|
209
|
+
const { baseId, step, steps, orientation, goTo } = useWizard();
|
|
210
|
+
return (
|
|
211
|
+
<ol
|
|
212
|
+
ref={ref}
|
|
213
|
+
className={cn(
|
|
214
|
+
"flex gap-2",
|
|
215
|
+
orientation === "vertical" ? "flex-col" : "flex-row flex-wrap items-center",
|
|
216
|
+
className,
|
|
217
|
+
)}
|
|
218
|
+
{...props}
|
|
219
|
+
>
|
|
220
|
+
{steps.map((meta, index) => {
|
|
221
|
+
const isActive = index === step;
|
|
222
|
+
const isComplete = index < step;
|
|
223
|
+
const isClickable = index <= step;
|
|
224
|
+
return (
|
|
225
|
+
<li
|
|
226
|
+
key={meta.id}
|
|
227
|
+
id={`${baseId}-step-${index}`}
|
|
228
|
+
aria-current={isActive ? "step" : undefined}
|
|
229
|
+
className={cn("flex items-center gap-2", orientation === "horizontal" && "min-w-0")}
|
|
230
|
+
>
|
|
231
|
+
<button
|
|
232
|
+
type="button"
|
|
233
|
+
disabled={!isClickable}
|
|
234
|
+
onClick={() => goTo(index)}
|
|
235
|
+
className={cn(
|
|
236
|
+
"flex size-7 shrink-0 items-center justify-center rounded-full border text-xs font-medium tabular-nums",
|
|
237
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
238
|
+
isActive && "border-primary bg-primary text-primary-foreground",
|
|
239
|
+
// #399 — the step NUMBER is text on a `/10` wash, not plate ink
|
|
240
|
+
// and not a mark: `-text` rung. The border keeps the fill rung.
|
|
241
|
+
isComplete && "border-primary bg-primary/10 text-primary-text",
|
|
242
|
+
!isActive && !isComplete && "border-border-strong text-muted-foreground",
|
|
243
|
+
isClickable ? "cursor-pointer" : "cursor-default",
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
{isComplete ? <Check className="size-4" aria-hidden="true" /> : index + 1}
|
|
247
|
+
</button>
|
|
248
|
+
<span className="flex min-w-0 flex-col">
|
|
249
|
+
<span
|
|
250
|
+
className={cn(
|
|
251
|
+
"truncate text-sm font-medium",
|
|
252
|
+
isActive ? "text-foreground" : "text-muted-foreground",
|
|
253
|
+
)}
|
|
254
|
+
>
|
|
255
|
+
{meta.title}
|
|
256
|
+
</span>
|
|
257
|
+
{meta.description && (
|
|
258
|
+
<span className="truncate text-xs text-muted-foreground">{meta.description}</span>
|
|
259
|
+
)}
|
|
260
|
+
</span>
|
|
261
|
+
</li>
|
|
262
|
+
);
|
|
263
|
+
})}
|
|
264
|
+
</ol>
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// WizardStep (per-step content + validator)
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
export interface WizardStepProps extends HTMLAttributes<HTMLDivElement> {
|
|
273
|
+
/** This step's index (0-based) — must match its position in `steps`. */
|
|
274
|
+
step: number;
|
|
275
|
+
/** Run before advancing past this step. Return false to block. */
|
|
276
|
+
validate?: WizardValidator;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* WizardStep — renders its children only when it is the active step, and
|
|
281
|
+
* registers its `validate` with the wizard while active. Receives focus on
|
|
282
|
+
* activation for keyboard users.
|
|
283
|
+
*/
|
|
284
|
+
export const WizardStep = forwardRef<HTMLDivElement, WizardStepProps>(function WizardStep(
|
|
285
|
+
{ step, validate, className, children, ...props },
|
|
286
|
+
ref,
|
|
287
|
+
) {
|
|
288
|
+
const { step: activeStep, baseId, setActiveValidator } = useWizard();
|
|
289
|
+
const isActive = step === activeStep;
|
|
290
|
+
const regionRef = useRef<HTMLDivElement>(null);
|
|
291
|
+
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
if (!isActive) return;
|
|
294
|
+
setActiveValidator(validate);
|
|
295
|
+
// Move focus to the newly-revealed step for keyboard users.
|
|
296
|
+
regionRef.current?.focus();
|
|
297
|
+
return () => setActiveValidator(undefined);
|
|
298
|
+
}, [isActive, validate, setActiveValidator]);
|
|
299
|
+
|
|
300
|
+
if (!isActive) return null;
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div
|
|
304
|
+
ref={(node) => {
|
|
305
|
+
regionRef.current = node;
|
|
306
|
+
if (typeof ref === "function") ref(node);
|
|
307
|
+
else if (ref) ref.current = node;
|
|
308
|
+
}}
|
|
309
|
+
role="group"
|
|
310
|
+
aria-labelledby={`${baseId}-step-${step}`}
|
|
311
|
+
tabIndex={-1}
|
|
312
|
+
className={cn("min-w-0 flex-1 focus-visible:outline-none", className)}
|
|
313
|
+
{...props}
|
|
314
|
+
>
|
|
315
|
+
{children}
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// WizardNav (Back / Next / Finish)
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
export interface WizardNavProps extends HTMLAttributes<HTMLDivElement> {
|
|
325
|
+
backLabel?: string;
|
|
326
|
+
nextLabel?: string;
|
|
327
|
+
finishLabel?: string;
|
|
328
|
+
/** Called when Finish is pressed on the last step. */
|
|
329
|
+
onFinish?: () => void;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** WizardNav — Back / Next (or Finish on the last step) controls. */
|
|
333
|
+
export const WizardNav = forwardRef<HTMLDivElement, WizardNavProps>(function WizardNav(
|
|
334
|
+
{ backLabel = "Back", nextLabel = "Next", finishLabel = "Finish", onFinish, className, ...props },
|
|
335
|
+
ref,
|
|
336
|
+
) {
|
|
337
|
+
const { isFirst, isLast, validating, next, prev } = useWizard();
|
|
338
|
+
return (
|
|
339
|
+
<div ref={ref} className={cn("flex items-center justify-between gap-2", className)} {...props}>
|
|
340
|
+
<Button type="button" variant="outline" onClick={prev} disabled={isFirst}>
|
|
341
|
+
{backLabel}
|
|
342
|
+
</Button>
|
|
343
|
+
{isLast ? (
|
|
344
|
+
<Button type="button" onClick={onFinish}>
|
|
345
|
+
{finishLabel}
|
|
346
|
+
</Button>
|
|
347
|
+
) : (
|
|
348
|
+
<Button type="button" onClick={next} disabled={validating}>
|
|
349
|
+
{validating ? "Checking…" : nextLabel}
|
|
350
|
+
</Button>
|
|
351
|
+
)}
|
|
352
|
+
</div>
|
|
353
|
+
);
|
|
354
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect } from "storybook/test";
|
|
3
|
+
import { DecorationProvider, type DecorationLevel } from "@elabs-ai/components-tokens";
|
|
4
|
+
import { Button } from "./components/button";
|
|
5
|
+
import { Card, CardContent, CardHeader, CardTitle } from "./components/card";
|
|
6
|
+
import { Badge } from "./components/badge";
|
|
7
|
+
import { Input } from "./components/input";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Foundation/Decoration — the decoration DIAL (`--decoration`, 0–10), orthogonal
|
|
11
|
+
* to color. The SAME real `@elabs-ai/components-ui` components are rendered across the ramp.
|
|
12
|
+
*
|
|
13
|
+
* WHAT THE DIAL TOUCHES: backgrounds, and nothing else. It fades a drafting
|
|
14
|
+
* sheet in behind the page (faded, never flat — see `Fade` below) and, at 8–10,
|
|
15
|
+
* squares the large radii and goes shadowless. Charts additionally swap flat
|
|
16
|
+
* series fills for patterns at 8–10.
|
|
17
|
+
*
|
|
18
|
+
* WHAT IT NEVER TOUCHES: the inside of a control. The buttons, inputs and badges
|
|
19
|
+
* below render identically at decoration 10 and at 0 — no hatch, no
|
|
20
|
+
* drawn-not-filled plates. Texture inside an input is noise where someone is
|
|
21
|
+
* reading their own typing, and re-inking the six role fills collapsed them to
|
|
22
|
+
* one appearance, which used to force a whole compensating non-colour channel.
|
|
23
|
+
*
|
|
24
|
+
* It is hue-INDEPENDENT — flip the **theme** toolbar to see the dial on green,
|
|
25
|
+
* navy, light paper, etc. Any story can be swept via the **Decoration** toolbar
|
|
26
|
+
* global (or `globals=decoration:<0..10>`).
|
|
27
|
+
* Set it in code via `<DecorationProvider level={n}>`, a `data-decoration="n"`
|
|
28
|
+
* attribute, or `ThemeProvider`/`useDecoration` (document-level).
|
|
29
|
+
*/
|
|
30
|
+
const meta = {
|
|
31
|
+
title: "Foundations/Decoration",
|
|
32
|
+
tags: ["autodocs"],
|
|
33
|
+
parameters: { layout: "padded" },
|
|
34
|
+
} satisfies Meta;
|
|
35
|
+
export default meta;
|
|
36
|
+
|
|
37
|
+
type Story = StoryObj<typeof meta>;
|
|
38
|
+
|
|
39
|
+
function Sample() {
|
|
40
|
+
return (
|
|
41
|
+
<Card className="w-64">
|
|
42
|
+
<CardHeader>
|
|
43
|
+
<CardTitle>Revenue</CardTitle>
|
|
44
|
+
</CardHeader>
|
|
45
|
+
<CardContent className="space-y-3">
|
|
46
|
+
<p className="text-display font-semibold tracking-tight text-foreground">$48.2M</p>
|
|
47
|
+
<div className="flex gap-2">
|
|
48
|
+
<Badge>healthy</Badge>
|
|
49
|
+
<Badge variant="warning">degraded</Badge>
|
|
50
|
+
</div>
|
|
51
|
+
<Input placeholder="Filter services…" />
|
|
52
|
+
<div className="flex gap-2">
|
|
53
|
+
<Button>Save</Button>
|
|
54
|
+
<Button variant="outline">Cancel</Button>
|
|
55
|
+
</div>
|
|
56
|
+
</CardContent>
|
|
57
|
+
</Card>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const LEVELS: DecorationLevel[] = [0, 4, 8, 10];
|
|
62
|
+
|
|
63
|
+
export const Dial: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<div className="flex flex-wrap items-start gap-8">
|
|
66
|
+
{LEVELS.map((level) => (
|
|
67
|
+
<div key={level} className="space-y-2">
|
|
68
|
+
<p className="font-mono text-meta uppercase tracking-wider text-muted-foreground">
|
|
69
|
+
decoration {level}
|
|
70
|
+
</p>
|
|
71
|
+
<DecorationProvider level={level}>
|
|
72
|
+
<Sample />
|
|
73
|
+
</DecorationProvider>
|
|
74
|
+
</div>
|
|
75
|
+
))}
|
|
76
|
+
</div>
|
|
77
|
+
),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const FADES = ["top", "bottom", "edges", "center"] as const;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The GROUND FADE (`data-decoration-fade`) — an opt-in region gesture that fades
|
|
84
|
+
* the ambient graph paper out instead of ruling a region edge to edge. It paints
|
|
85
|
+
* the SAME `--deco-grid` on a decorative `::before` layer and masks THAT, so the ink
|
|
86
|
+
* still rides the dial (inert at decoration 0) and the region's own content is
|
|
87
|
+
* never masked — the text in each panel stays at full opacity.
|
|
88
|
+
*
|
|
89
|
+
* The fade OWNS its region's ground. Opaque surfaces are never ruled themselves —
|
|
90
|
+
* the ambient sheet is painted once, behind the page — so a nested surface can't
|
|
91
|
+
* punch a crisp, full-strength rectangle into the field that was just faded out
|
|
92
|
+
* (the third panel below nests a `bg-card`).
|
|
93
|
+
*
|
|
94
|
+
* Budget: it spends the region's one focal drafting gesture. For a one-off fade on
|
|
95
|
+
* a single element, use Tailwind's own `mask-t-from-*` / `mask-radial-*` utilities.
|
|
96
|
+
*
|
|
97
|
+
* Shipped in anger on `Patterns/Templates/Marketing`, whose hero band carries
|
|
98
|
+
* `data-decoration-fade="top"` so the sheet fades in behind the headline instead of
|
|
99
|
+
* starting on a hard ruled edge — that template, not this matrix, is the surface to
|
|
100
|
+
* judge the gesture on.
|
|
101
|
+
*
|
|
102
|
+
* Sweep it with the **Decoration** toolbar (this story pins the dial to 10 so the
|
|
103
|
+
* gesture is visible on every theme; at 0 the mask has nothing to reveal).
|
|
104
|
+
*/
|
|
105
|
+
export const Fade: Story = {
|
|
106
|
+
globals: { decoration: "10" },
|
|
107
|
+
render: () => (
|
|
108
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
109
|
+
{FADES.map((fade) => (
|
|
110
|
+
<div
|
|
111
|
+
key={fade}
|
|
112
|
+
data-decoration-fade={fade}
|
|
113
|
+
data-testid={`fade-${fade}`}
|
|
114
|
+
className="min-h-40 rounded-lg bg-card p-4 text-card-foreground"
|
|
115
|
+
>
|
|
116
|
+
<p className="text-subtitle font-medium">{fade}</p>
|
|
117
|
+
<p className="text-caption text-muted-foreground">
|
|
118
|
+
Content stays fully opaque — only the ground fades.
|
|
119
|
+
</p>
|
|
120
|
+
{fade === "edges" ? (
|
|
121
|
+
<div
|
|
122
|
+
data-testid="fade-nested-surface"
|
|
123
|
+
className="mt-3 rounded-md bg-card p-2 text-caption text-muted-foreground"
|
|
124
|
+
>
|
|
125
|
+
A nested surface does not re-rule the faded ground.
|
|
126
|
+
</div>
|
|
127
|
+
) : null}
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
),
|
|
132
|
+
/**
|
|
133
|
+
* Locks the #257 contract that a static reading of the CSS cannot: the mask
|
|
134
|
+
* belongs to a decorative `::before` layer, so the host's CHILDREN are never
|
|
135
|
+
* faded and the layer never swallows a click — and the faded region is ruled
|
|
136
|
+
* exactly once, host and descendants alike.
|
|
137
|
+
*/
|
|
138
|
+
play: async ({ canvas }) => {
|
|
139
|
+
const host = canvas.getByTestId("fade-edges");
|
|
140
|
+
|
|
141
|
+
// The host itself is unmasked — otherwise its text would fade with the grid.
|
|
142
|
+
await expect(getComputedStyle(host).maskImage).toBe("none");
|
|
143
|
+
|
|
144
|
+
// The decorative layer exists, paints the dial-driven grid, and is inert.
|
|
145
|
+
const layer = getComputedStyle(host, "::before");
|
|
146
|
+
await expect(layer.content).not.toBe("none");
|
|
147
|
+
await expect(layer.pointerEvents).toBe("none");
|
|
148
|
+
await expect(layer.backgroundImage).toContain("gradient");
|
|
149
|
+
|
|
150
|
+
// Every child renders at full opacity.
|
|
151
|
+
for (const child of Array.from(host.children)) {
|
|
152
|
+
await expect(getComputedStyle(child).opacity).toBe("1");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The faded host is not ALSO ruled crisply…
|
|
156
|
+
await expect(getComputedStyle(host).backgroundImage).toBe("none");
|
|
157
|
+
// …and neither is a surface nested inside it (no crisp patch in a faded field).
|
|
158
|
+
const nested = canvas.getByTestId("fade-nested-surface");
|
|
159
|
+
await expect(getComputedStyle(nested).backgroundImage).toBe("none");
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The AMBIENT GROUND, as an executable lock. The sheet is painted exactly once —
|
|
165
|
+
* on a fixed, masked layer behind the page — so this asserts the three properties
|
|
166
|
+
* a static reading of the CSS cannot: the layer exists and carries the dial-driven
|
|
167
|
+
* grid, it is MASKED (never flat), and no opaque surface re-rules it on top.
|
|
168
|
+
*
|
|
169
|
+
* The dial is set from the toolbar global, which writes `data-decoration` on the
|
|
170
|
+
* preview root exactly as `ThemeProvider` does — a `DecorationProvider` wrapper
|
|
171
|
+
* would NOT reach `body`, which is why `Dial` above cannot stand in for this.
|
|
172
|
+
*/
|
|
173
|
+
export const AmbientGround: Story = {
|
|
174
|
+
globals: { decoration: "10" },
|
|
175
|
+
render: () => (
|
|
176
|
+
<Card className="w-64" data-testid="ground-card">
|
|
177
|
+
<CardHeader>
|
|
178
|
+
<CardTitle>On the sheet</CardTitle>
|
|
179
|
+
</CardHeader>
|
|
180
|
+
<CardContent>
|
|
181
|
+
<p className="text-caption text-muted-foreground">
|
|
182
|
+
An opaque panel covers the ground; it never re-rules it.
|
|
183
|
+
</p>
|
|
184
|
+
</CardContent>
|
|
185
|
+
</Card>
|
|
186
|
+
),
|
|
187
|
+
play: async () => {
|
|
188
|
+
const ground = getComputedStyle(document.body, "::before");
|
|
189
|
+
|
|
190
|
+
// The sheet exists and carries the dial-driven grid…
|
|
191
|
+
await expect(ground.content).not.toBe("none");
|
|
192
|
+
await expect(ground.backgroundImage).toContain("gradient");
|
|
193
|
+
await expect(ground.position).toBe("fixed");
|
|
194
|
+
await expect(ground.pointerEvents).toBe("none");
|
|
195
|
+
|
|
196
|
+
// …and it is never flat: the layer itself is masked.
|
|
197
|
+
await expect(ground.maskImage).not.toBe("none");
|
|
198
|
+
|
|
199
|
+
// The host is unmasked, so the page's own content is never faded with it.
|
|
200
|
+
await expect(getComputedStyle(document.body).maskImage).toBe("none");
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The control invariant, as an executable lock: at FULL decoration the dial must
|
|
206
|
+
* not have painted anything into a control. A `Button` keeps its solid role fill
|
|
207
|
+
* (no `background-image`), an `Input` keeps a clean ground, and a `Badge` keeps
|
|
208
|
+
* its own tone rather than collapsing to a drawn outline.
|
|
209
|
+
*
|
|
210
|
+
* This is the story to run when someone reintroduces a "drawn-not-filled" rule:
|
|
211
|
+
* it fails on the hatch, not on a screenshot review three weeks later.
|
|
212
|
+
*/
|
|
213
|
+
export const ControlsAreUntouched: Story = {
|
|
214
|
+
globals: { decoration: "10" },
|
|
215
|
+
render: () => (
|
|
216
|
+
<div data-testid="controls" className="flex flex-wrap items-center gap-3">
|
|
217
|
+
<Button data-testid="deco-button">Save</Button>
|
|
218
|
+
<Button variant="outline">Cancel</Button>
|
|
219
|
+
<Badge variant="warning">degraded</Badge>
|
|
220
|
+
<Input data-testid="deco-input" placeholder="Filter services…" className="w-48" />
|
|
221
|
+
</div>
|
|
222
|
+
),
|
|
223
|
+
play: async ({ canvas }) => {
|
|
224
|
+
const button = canvas.getByTestId("deco-button");
|
|
225
|
+
const input = canvas.getByTestId("deco-input");
|
|
226
|
+
|
|
227
|
+
// No hatch, no texture: the dial paints backgrounds, never controls.
|
|
228
|
+
await expect(getComputedStyle(button).backgroundImage).toBe("none");
|
|
229
|
+
await expect(getComputedStyle(input).backgroundImage).toBe("none");
|
|
230
|
+
|
|
231
|
+
// And the role fill is still a real, opaque plate — not the old
|
|
232
|
+
// "transparent ground + hairline" drawn control.
|
|
233
|
+
await expect(getComputedStyle(button).backgroundColor).not.toBe("rgba(0, 0, 0, 0)");
|
|
234
|
+
await expect(getComputedStyle(button).backgroundColor).not.toBe("transparent");
|
|
235
|
+
},
|
|
236
|
+
};
|