@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,618 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, waitFor, within, userEvent } from "storybook/test";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Folder, File, FileText, Database, Server } from "lucide-react";
|
|
5
|
+
import { Badge } from "../badge";
|
|
6
|
+
import { Tree, type TreeNode } from "./tree";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Shared sample data
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const fileTree: TreeNode[] = [
|
|
13
|
+
{
|
|
14
|
+
id: "src",
|
|
15
|
+
label: "src",
|
|
16
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
id: "components",
|
|
20
|
+
label: "components",
|
|
21
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
22
|
+
children: [
|
|
23
|
+
{
|
|
24
|
+
id: "button",
|
|
25
|
+
label: "button.tsx",
|
|
26
|
+
icon: <FileText className="size-4 text-muted-foreground" />,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "tree-tsx",
|
|
30
|
+
label: "tree.tsx",
|
|
31
|
+
icon: <FileText className="size-4 text-muted-foreground" />,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "lib",
|
|
37
|
+
label: "lib",
|
|
38
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
39
|
+
children: [
|
|
40
|
+
{
|
|
41
|
+
id: "cn",
|
|
42
|
+
label: "cn.ts",
|
|
43
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "index",
|
|
49
|
+
label: "index.ts",
|
|
50
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "public",
|
|
56
|
+
label: "public",
|
|
57
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
58
|
+
children: [
|
|
59
|
+
{
|
|
60
|
+
id: "favicon",
|
|
61
|
+
label: "favicon.ico",
|
|
62
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "package-json",
|
|
68
|
+
label: "package.json",
|
|
69
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const orgTree: TreeNode[] = [
|
|
74
|
+
{
|
|
75
|
+
id: "engineering",
|
|
76
|
+
label: "Engineering",
|
|
77
|
+
icon: <Server className="size-4 text-muted-foreground" />,
|
|
78
|
+
children: [
|
|
79
|
+
{
|
|
80
|
+
id: "frontend",
|
|
81
|
+
label: "Frontend",
|
|
82
|
+
children: [
|
|
83
|
+
{ id: "alice", label: "Alice" },
|
|
84
|
+
{ id: "bob", label: "Bob" },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "backend",
|
|
89
|
+
label: "Backend",
|
|
90
|
+
children: [
|
|
91
|
+
{ id: "charlie", label: "Charlie" },
|
|
92
|
+
{ id: "dana", label: "Dana", disabled: true },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "data",
|
|
99
|
+
label: "Data",
|
|
100
|
+
icon: <Database className="size-4 text-muted-foreground" />,
|
|
101
|
+
children: [
|
|
102
|
+
{ id: "eve", label: "Eve" },
|
|
103
|
+
{ id: "frank", label: "Frank" },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
// The same file tree, with a trailing size badge on each file leaf (#369) —
|
|
109
|
+
// `TreeNode.accessory` renders it as a SIBLING of the label, so it never joins
|
|
110
|
+
// the row's accessible name and never triggers select/expand.
|
|
111
|
+
const fileTreeWithSizes: TreeNode[] = [
|
|
112
|
+
{
|
|
113
|
+
id: "src",
|
|
114
|
+
label: "src",
|
|
115
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
116
|
+
children: [
|
|
117
|
+
{
|
|
118
|
+
id: "components",
|
|
119
|
+
label: "components",
|
|
120
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
121
|
+
children: [
|
|
122
|
+
{
|
|
123
|
+
id: "button",
|
|
124
|
+
label: "button.tsx",
|
|
125
|
+
icon: <FileText className="size-4 text-muted-foreground" />,
|
|
126
|
+
accessory: <Badge variant="outline">2.1 KB</Badge>,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "tree-tsx",
|
|
130
|
+
label: "tree.tsx",
|
|
131
|
+
icon: <FileText className="size-4 text-muted-foreground" />,
|
|
132
|
+
accessory: <Badge variant="outline">18.4 KB</Badge>,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "index",
|
|
138
|
+
label: "index.ts",
|
|
139
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
140
|
+
accessory: <Badge variant="outline">0.4 KB</Badge>,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "package-json",
|
|
146
|
+
label: "package.json",
|
|
147
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
148
|
+
accessory: <Badge variant="outline">1.2 KB</Badge>,
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// 5 000-node flat tree for the virtualization story
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
function buildLargeTree(count: number): TreeNode[] {
|
|
157
|
+
const nodes: TreeNode[] = [];
|
|
158
|
+
for (let i = 0; i < count; i++) {
|
|
159
|
+
nodes.push({ id: `node-${i}`, label: `Node ${i + 1}` });
|
|
160
|
+
}
|
|
161
|
+
return nodes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const LARGE_NODES = buildLargeTree(5000);
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Lazy-loading demo data
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
function makeLazyRoot(): TreeNode[] {
|
|
171
|
+
return [
|
|
172
|
+
{
|
|
173
|
+
id: "root-a",
|
|
174
|
+
label: "Category A",
|
|
175
|
+
hasChildren: true,
|
|
176
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "root-b",
|
|
180
|
+
label: "Category B",
|
|
181
|
+
hasChildren: true,
|
|
182
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "root-c",
|
|
186
|
+
label: "Category C (fails to load)",
|
|
187
|
+
hasChildren: true,
|
|
188
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "root-d",
|
|
192
|
+
label: "Static leaf",
|
|
193
|
+
icon: <File className="size-4 text-muted-foreground" />,
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function fakeFetch(nodeId: string): Promise<TreeNode[]> {
|
|
199
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
200
|
+
if (nodeId === "root-c") throw new Error("Network error");
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
id: `${nodeId}-child-1`,
|
|
204
|
+
label: `${nodeId} / child 1`,
|
|
205
|
+
hasChildren: true,
|
|
206
|
+
icon: <Folder className="size-4 text-muted-foreground" />,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: `${nodeId}-child-2`,
|
|
210
|
+
label: `${nodeId} / child 2`,
|
|
211
|
+
icon: <FileText className="size-4 text-muted-foreground" />,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: `${nodeId}-child-3`,
|
|
215
|
+
label: `${nodeId} / child 3`,
|
|
216
|
+
icon: <FileText className="size-4 text-muted-foreground" />,
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Meta
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
const meta = {
|
|
226
|
+
title: "Navigation/Tree",
|
|
227
|
+
component: Tree,
|
|
228
|
+
tags: ["autodocs"],
|
|
229
|
+
args: {
|
|
230
|
+
nodes: fileTree,
|
|
231
|
+
defaultExpandedIds: ["src", "components"],
|
|
232
|
+
},
|
|
233
|
+
parameters: {
|
|
234
|
+
layout: "padded",
|
|
235
|
+
},
|
|
236
|
+
argTypes: {
|
|
237
|
+
nodes: {
|
|
238
|
+
description: "Array of TreeNode objects describing the tree structure.",
|
|
239
|
+
control: false,
|
|
240
|
+
table: { category: "Data" },
|
|
241
|
+
},
|
|
242
|
+
selectionMode: {
|
|
243
|
+
description: "Whether items can be selected — single, multiple, or none.",
|
|
244
|
+
control: { type: "radio" },
|
|
245
|
+
options: ["single", "multiple", "none"],
|
|
246
|
+
table: { category: "Behaviour" },
|
|
247
|
+
},
|
|
248
|
+
checkboxes: {
|
|
249
|
+
description: "Show checkboxes alongside nodes in multi-select mode.",
|
|
250
|
+
control: "boolean",
|
|
251
|
+
table: { category: "Behaviour" },
|
|
252
|
+
},
|
|
253
|
+
virtualize: {
|
|
254
|
+
description: "Enable windowed rendering for large trees (>50 visible rows).",
|
|
255
|
+
control: "boolean",
|
|
256
|
+
table: { category: "Behaviour" },
|
|
257
|
+
},
|
|
258
|
+
estimateRowHeight: {
|
|
259
|
+
description: "Estimated row height in px used by the virtualizer. Default 32.",
|
|
260
|
+
control: "number",
|
|
261
|
+
table: { category: "Behaviour" },
|
|
262
|
+
},
|
|
263
|
+
overscan: {
|
|
264
|
+
description: "Extra rows rendered beyond the visible window by the virtualizer. Default 8.",
|
|
265
|
+
control: "number",
|
|
266
|
+
table: { category: "Behaviour" },
|
|
267
|
+
},
|
|
268
|
+
maxHeight: {
|
|
269
|
+
description: "CSS max-height for the scroll viewport when virtualize is true.",
|
|
270
|
+
control: "text",
|
|
271
|
+
table: { category: "Behaviour" },
|
|
272
|
+
},
|
|
273
|
+
defaultExpandedIds: {
|
|
274
|
+
description: "Uncontrolled initial set of expanded node ids.",
|
|
275
|
+
control: false,
|
|
276
|
+
table: { category: "State" },
|
|
277
|
+
},
|
|
278
|
+
expandedIds: {
|
|
279
|
+
description: "Controlled set of expanded node ids.",
|
|
280
|
+
control: false,
|
|
281
|
+
table: { category: "State" },
|
|
282
|
+
},
|
|
283
|
+
onExpandedChange: {
|
|
284
|
+
description: "Callback when the expanded set changes.",
|
|
285
|
+
control: false,
|
|
286
|
+
table: { category: "Events" },
|
|
287
|
+
},
|
|
288
|
+
defaultSelectedIds: {
|
|
289
|
+
description: "Uncontrolled initial set of selected node ids.",
|
|
290
|
+
control: false,
|
|
291
|
+
table: { category: "State" },
|
|
292
|
+
},
|
|
293
|
+
selectedIds: {
|
|
294
|
+
description: "Controlled set of selected node ids.",
|
|
295
|
+
control: false,
|
|
296
|
+
table: { category: "State" },
|
|
297
|
+
},
|
|
298
|
+
onSelectionChange: {
|
|
299
|
+
description: "Callback when the selection set changes.",
|
|
300
|
+
control: false,
|
|
301
|
+
table: { category: "Events" },
|
|
302
|
+
},
|
|
303
|
+
loadChildren: {
|
|
304
|
+
description: "Async function to load children for a node with hasChildren: true.",
|
|
305
|
+
control: false,
|
|
306
|
+
table: { category: "Behaviour" },
|
|
307
|
+
},
|
|
308
|
+
className: {
|
|
309
|
+
description: "Additional CSS classes applied to the tree root.",
|
|
310
|
+
control: "text",
|
|
311
|
+
table: { category: "Styling" },
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
} satisfies Meta<typeof Tree>;
|
|
315
|
+
|
|
316
|
+
export default meta;
|
|
317
|
+
type Story = StoryObj<typeof meta>;
|
|
318
|
+
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Existing stories (unchanged)
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
/** Default single-selection file tree with icons. */
|
|
324
|
+
export const Default: Story = {
|
|
325
|
+
// Confirms the pre-expanded nodes are visible and clicking a node selects it.
|
|
326
|
+
play: async ({ canvasElement, userEvent }) => {
|
|
327
|
+
const canvas = within(canvasElement);
|
|
328
|
+
// "src" and "components" are pre-expanded via defaultExpandedIds
|
|
329
|
+
const srcItem = canvas.getByRole("treeitem", { name: /^src$/i });
|
|
330
|
+
await waitFor(() => expect(srcItem).toBeVisible());
|
|
331
|
+
const componentsItem = canvas.getByRole("treeitem", { name: /^components$/i });
|
|
332
|
+
await waitFor(() => expect(componentsItem).toBeVisible());
|
|
333
|
+
// Click a leaf node to select it
|
|
334
|
+
const buttonTsx = canvas.getByRole("treeitem", { name: /button\.tsx/i });
|
|
335
|
+
await userEvent.click(buttonTsx);
|
|
336
|
+
await expect(buttonTsx).toHaveAttribute("aria-selected", "true");
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/** Multiple selection with checkboxes. */
|
|
341
|
+
export const Multiple: Story = {
|
|
342
|
+
args: {
|
|
343
|
+
nodes: orgTree,
|
|
344
|
+
defaultExpandedIds: ["engineering", "frontend", "backend", "data"],
|
|
345
|
+
selectionMode: "multiple",
|
|
346
|
+
checkboxes: true,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/** Fully expanded at mount via defaultExpandedIds. */
|
|
351
|
+
export const Expanded: Story = {
|
|
352
|
+
args: {
|
|
353
|
+
defaultExpandedIds: ["src", "components", "lib", "public"],
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/** Collapsed — no nodes expanded at start. */
|
|
358
|
+
export const Collapsed: Story = {
|
|
359
|
+
args: {
|
|
360
|
+
defaultExpandedIds: [],
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/** Nodes with disabled items. */
|
|
365
|
+
export const DisabledNodes: Story = {
|
|
366
|
+
args: {
|
|
367
|
+
nodes: orgTree,
|
|
368
|
+
defaultExpandedIds: ["engineering", "frontend", "backend", "data"],
|
|
369
|
+
selectionMode: "single",
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/** Deep nesting — 4 levels. */
|
|
374
|
+
export const DeepNesting: Story = {
|
|
375
|
+
args: {
|
|
376
|
+
nodes: [
|
|
377
|
+
{
|
|
378
|
+
id: "l1",
|
|
379
|
+
label: "Level 1",
|
|
380
|
+
children: [
|
|
381
|
+
{
|
|
382
|
+
id: "l2",
|
|
383
|
+
label: "Level 2",
|
|
384
|
+
children: [
|
|
385
|
+
{
|
|
386
|
+
id: "l3",
|
|
387
|
+
label: "Level 3",
|
|
388
|
+
children: [
|
|
389
|
+
{ id: "l4a", label: "Level 4 — A" },
|
|
390
|
+
{ id: "l4b", label: "Level 4 — B" },
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
defaultExpandedIds: ["l1", "l2", "l3"],
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
/** Controlled expand + select with external state display. */
|
|
403
|
+
export const Controlled: Story = {
|
|
404
|
+
render: () => {
|
|
405
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
406
|
+
const [expanded, setExpanded] = useState<string[]>(["src"]);
|
|
407
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
408
|
+
const [selected, setSelected] = useState<string[]>([]);
|
|
409
|
+
return (
|
|
410
|
+
<div className="flex gap-8">
|
|
411
|
+
<Tree
|
|
412
|
+
nodes={fileTree}
|
|
413
|
+
expandedIds={expanded}
|
|
414
|
+
onExpandedChange={setExpanded}
|
|
415
|
+
selectedIds={selected}
|
|
416
|
+
onSelectionChange={setSelected}
|
|
417
|
+
className="w-64"
|
|
418
|
+
/>
|
|
419
|
+
<div className="text-body text-muted-foreground space-y-1">
|
|
420
|
+
<p>
|
|
421
|
+
<span className="font-medium">Expanded:</span> {expanded.join(", ") || "none"}
|
|
422
|
+
</p>
|
|
423
|
+
<p>
|
|
424
|
+
<span className="font-medium">Selected:</span> {selected.join(", ") || "none"}
|
|
425
|
+
</p>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
/** No selection mode — purely navigational. */
|
|
433
|
+
export const NoSelection: Story = {
|
|
434
|
+
args: {
|
|
435
|
+
selectionMode: "none",
|
|
436
|
+
defaultExpandedIds: ["src", "components"],
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
// New story: 5 000-node virtualized tree
|
|
442
|
+
// ---------------------------------------------------------------------------
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 5 000 flat nodes rendered with `virtualize`. Only the visible window (~15–25
|
|
446
|
+
* rows) is mounted in the DOM at any time; keyboard navigation and End/Home
|
|
447
|
+
* work across all 5 000 items.
|
|
448
|
+
*/
|
|
449
|
+
export const Virtualized: Story = {
|
|
450
|
+
args: {
|
|
451
|
+
nodes: LARGE_NODES,
|
|
452
|
+
virtualize: true,
|
|
453
|
+
estimateRowHeight: 32,
|
|
454
|
+
overscan: 8,
|
|
455
|
+
maxHeight: "400px",
|
|
456
|
+
selectionMode: "single",
|
|
457
|
+
defaultExpandedIds: [],
|
|
458
|
+
},
|
|
459
|
+
parameters: {
|
|
460
|
+
docs: {
|
|
461
|
+
description: {
|
|
462
|
+
story:
|
|
463
|
+
"5 000 flat nodes with `virtualize={true}`. Only the visible window is in the DOM. Use keyboard ArrowDown/Up/Home/End to navigate; the virtualizer scrolls automatically.",
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* `scrollToId` reveals a node the virtualizer has windowed out of the DOM. Setting
|
|
471
|
+
* `selectedIds` to a deep node alone would select it invisibly (structure shown,
|
|
472
|
+
* but not "where I am"); passing the same id as `scrollToId` centers it. Click a
|
|
473
|
+
* button — the matching row scrolls into view and shows as selected. (Use
|
|
474
|
+
* `scrollSelectionIntoView` to reveal on every selection change instead.)
|
|
475
|
+
*/
|
|
476
|
+
function RevealSelectedNodeDemo() {
|
|
477
|
+
const [target, setTarget] = useState<string | undefined>(undefined);
|
|
478
|
+
return (
|
|
479
|
+
<div className="space-y-3">
|
|
480
|
+
<div className="flex flex-wrap gap-2">
|
|
481
|
+
{["node-50", "node-1200", "node-4300"].map((id) => (
|
|
482
|
+
<button
|
|
483
|
+
key={id}
|
|
484
|
+
type="button"
|
|
485
|
+
onClick={() => setTarget(id)}
|
|
486
|
+
className="rounded-md border border-border-strong bg-background px-3 py-1.5 text-body font-medium text-foreground hover:bg-surface-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
487
|
+
>
|
|
488
|
+
Navigate to Node {Number(id.split("-")[1]) + 1}
|
|
489
|
+
</button>
|
|
490
|
+
))}
|
|
491
|
+
</div>
|
|
492
|
+
<Tree
|
|
493
|
+
nodes={LARGE_NODES}
|
|
494
|
+
virtualize
|
|
495
|
+
estimateRowHeight={32}
|
|
496
|
+
overscan={8}
|
|
497
|
+
maxHeight="400px"
|
|
498
|
+
selectionMode="single"
|
|
499
|
+
selectedIds={target ? [target] : []}
|
|
500
|
+
scrollToId={target}
|
|
501
|
+
/>
|
|
502
|
+
</div>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export const RevealSelectedNode: Story = {
|
|
507
|
+
render: () => <RevealSelectedNodeDemo />,
|
|
508
|
+
parameters: {
|
|
509
|
+
docs: {
|
|
510
|
+
description: {
|
|
511
|
+
story:
|
|
512
|
+
"Reveal a programmatically-selected node in a 5 000-node virtualized tree. `scrollToId` scrolls the windowed-out row into view (and expands its lazy ancestors when needed); keyboard navigation is unaffected.",
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
play: async ({ canvasElement }) => {
|
|
517
|
+
const canvas = within(canvasElement);
|
|
518
|
+
// Node 4301 (id node-4300) is far below the fold — not mounted initially.
|
|
519
|
+
expect(canvas.queryByText("Node 4301")).toBeNull();
|
|
520
|
+
await userEvent.click(canvas.getByRole("button", { name: "Navigate to Node 4301" }));
|
|
521
|
+
// scrollToId scrolls the virtualizer to it, so its row mounts and centers.
|
|
522
|
+
await waitFor(() => expect(canvas.getByText("Node 4301")).toBeInTheDocument());
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// New story: lazy-loading children on demand
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* `surface="sidebar"` renders the selected row with the sidebar's own active-item
|
|
532
|
+
* treatment (`bg-sidebar-accent` + `text-sidebar-accent-foreground` + `font-medium`),
|
|
533
|
+
* matching `SidebarMenuButton`'s active state — because the default `bg-accent`
|
|
534
|
+
* fill is near-invisible against `--sidebar`. No fill is ≥3:1 on a sidebar, so
|
|
535
|
+
* (like the Sidebar itself) perceivability comes from the foreground/weight shift
|
|
536
|
+
* plus `aria-selected`, not fill contrast alone.
|
|
537
|
+
*
|
|
538
|
+
* NOTE: cross-theme visual correctness (the three shipped themes — light,
|
|
539
|
+
* dark) requires a real render and is owed to a human reviewer;
|
|
540
|
+
* the tokens are semantic so they adapt, but pixel-level confirmation cannot be
|
|
541
|
+
* automated in this environment.
|
|
542
|
+
*/
|
|
543
|
+
export const SidebarSurface: Story = {
|
|
544
|
+
args: {
|
|
545
|
+
nodes: fileTree,
|
|
546
|
+
surface: "sidebar",
|
|
547
|
+
defaultExpandedIds: ["src", "components"],
|
|
548
|
+
defaultSelectedIds: ["button"],
|
|
549
|
+
selectionMode: "single",
|
|
550
|
+
},
|
|
551
|
+
render: (args) => (
|
|
552
|
+
<div className="w-64 rounded-md bg-sidebar p-2">
|
|
553
|
+
<Tree {...args} />
|
|
554
|
+
</div>
|
|
555
|
+
),
|
|
556
|
+
play: async ({ canvasElement }) => {
|
|
557
|
+
const canvas = within(canvasElement);
|
|
558
|
+
const selected = canvas.getByRole("treeitem", { name: /button\.tsx/i });
|
|
559
|
+
await waitFor(() => expect(selected).toBeVisible());
|
|
560
|
+
await expect(selected).toHaveAttribute("aria-selected", "true");
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Nodes marked `hasChildren: true` with no loaded `children` show the expand
|
|
566
|
+
* chevron. On first expand, `loadChildren` is called; a skeleton loading row
|
|
567
|
+
* appears under `aria-busy` while the request is in flight. Resolved children
|
|
568
|
+
* are cached — re-expanding does not re-fetch. "Category C" simulates a
|
|
569
|
+
* network error and shows the inline retry affordance.
|
|
570
|
+
*/
|
|
571
|
+
export const LazyLoading: Story = {
|
|
572
|
+
args: {
|
|
573
|
+
nodes: makeLazyRoot(),
|
|
574
|
+
loadChildren: (node) => fakeFetch(node.id),
|
|
575
|
+
selectionMode: "single",
|
|
576
|
+
defaultExpandedIds: [],
|
|
577
|
+
},
|
|
578
|
+
parameters: {
|
|
579
|
+
docs: {
|
|
580
|
+
description: {
|
|
581
|
+
story:
|
|
582
|
+
"Children are fetched on first expand via `loadChildren`. The spinner appears in the expand chevron while loading. A failed load shows a retry button.",
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// ---------------------------------------------------------------------------
|
|
589
|
+
// New story: per-node trailing accessory (#369)
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Per-node trailing content — e.g. a file-size badge — via `TreeNode.accessory`
|
|
594
|
+
* (#369). It renders as a SIBLING of the label, outside the row's interactive
|
|
595
|
+
* label region: it never joins the row's accessible name and clicking it never
|
|
596
|
+
* selects or expands the row.
|
|
597
|
+
*/
|
|
598
|
+
export const TrailingAccessory: Story = {
|
|
599
|
+
args: {
|
|
600
|
+
nodes: fileTreeWithSizes,
|
|
601
|
+
defaultExpandedIds: ["src", "components"],
|
|
602
|
+
},
|
|
603
|
+
play: async ({ canvasElement, userEvent }) => {
|
|
604
|
+
const canvas = within(canvasElement);
|
|
605
|
+
const buttonRow = canvas.getByRole("treeitem", { name: "button.tsx" });
|
|
606
|
+
await waitFor(() => expect(buttonRow).toBeVisible());
|
|
607
|
+
// The accessible name is exactly the file name — the badge text never leaks in.
|
|
608
|
+
expect(canvas.queryByRole("treeitem", { name: /button\.tsx.*2\.1 KB/ })).toBeNull();
|
|
609
|
+
|
|
610
|
+
// Clicking the badge does not select the row.
|
|
611
|
+
await userEvent.click(canvas.getByText("2.1 KB"));
|
|
612
|
+
await expect(buttonRow).not.toHaveAttribute("aria-selected", "true");
|
|
613
|
+
|
|
614
|
+
// Clicking the row itself still selects it.
|
|
615
|
+
await userEvent.click(buttonRow);
|
|
616
|
+
await expect(buttonRow).toHaveAttribute("aria-selected", "true");
|
|
617
|
+
},
|
|
618
|
+
};
|