@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,79 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { ColorPicker, COLOR_TOKENS } from "./color-picker";
|
|
5
|
+
|
|
6
|
+
describe("ColorPicker", () => {
|
|
7
|
+
it("renders a trigger button with default aria-label", () => {
|
|
8
|
+
render(<ColorPicker defaultValue="chart-1" />);
|
|
9
|
+
expect(screen.getByRole("button", { name: "Pick color" })).toBeInTheDocument();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("accepts a custom aria-label on the trigger", () => {
|
|
13
|
+
render(<ColorPicker defaultValue="primary" aria-label="Chart color" />);
|
|
14
|
+
expect(screen.getByRole("button", { name: "Chart color" })).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("opens popover on trigger click and shows swatches", async () => {
|
|
18
|
+
render(<ColorPicker defaultValue="chart-1" />);
|
|
19
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
20
|
+
expect(screen.getByRole("radiogroup", { name: "Color swatches" })).toBeInTheDocument();
|
|
21
|
+
// Each default token should render as a radio
|
|
22
|
+
expect(screen.getAllByRole("radio").length).toBe(COLOR_TOKENS.length);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("marks the current value as checked", async () => {
|
|
26
|
+
render(<ColorPicker defaultValue="primary" />);
|
|
27
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
28
|
+
expect(screen.getByRole("radio", { name: "primary" })).toHaveAttribute("aria-checked", "true");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("calls onValueChange and closes popover when a swatch is selected", async () => {
|
|
32
|
+
const onValueChange = vi.fn();
|
|
33
|
+
render(<ColorPicker defaultValue="chart-1" onValueChange={onValueChange} />);
|
|
34
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
35
|
+
await userEvent.click(screen.getByRole("radio", { name: "chart-2" }));
|
|
36
|
+
expect(onValueChange).toHaveBeenCalledWith("chart-2");
|
|
37
|
+
// Popover should close — radiogroup no longer visible
|
|
38
|
+
expect(screen.queryByRole("radiogroup")).not.toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("does not render custom hex section by default", async () => {
|
|
42
|
+
render(<ColorPicker defaultValue="chart-1" />);
|
|
43
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
44
|
+
expect(screen.queryByLabelText("Custom hex color")).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders custom hex input when allowCustom=true", async () => {
|
|
48
|
+
render(<ColorPicker defaultValue="chart-1" allowCustom />);
|
|
49
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
50
|
+
expect(screen.getByLabelText("Custom hex color")).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("applies a valid hex value from the custom input", async () => {
|
|
54
|
+
const onValueChange = vi.fn();
|
|
55
|
+
render(<ColorPicker defaultValue="chart-1" allowCustom onValueChange={onValueChange} />);
|
|
56
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
57
|
+
const hexInput = screen.getByLabelText("Custom hex color");
|
|
58
|
+
await userEvent.type(hexInput, "#ff0000");
|
|
59
|
+
await userEvent.click(screen.getByRole("button", { name: "Apply" }));
|
|
60
|
+
expect(onValueChange).toHaveBeenCalledWith("#ff0000");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("respects a custom swatches subset", async () => {
|
|
64
|
+
render(<ColorPicker defaultValue="success" swatches={["success", "warning"]} />);
|
|
65
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
66
|
+
expect(screen.getAllByRole("radio").length).toBe(2);
|
|
67
|
+
expect(screen.getByRole("radio", { name: "success" })).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByRole("radio", { name: "warning" })).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("uses only bg-* token classes for swatches (no raw hex in class)", async () => {
|
|
72
|
+
render(<ColorPicker defaultValue="chart-1" />);
|
|
73
|
+
await userEvent.click(screen.getByRole("button", { name: "Pick color" }));
|
|
74
|
+
const radios = screen.getAllByRole("radio");
|
|
75
|
+
for (const radio of radios) {
|
|
76
|
+
expect(radio.className).not.toMatch(/#[0-9a-fA-F]/);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// a2ui.exposed: yes
|
|
2
|
+
import {
|
|
3
|
+
forwardRef,
|
|
4
|
+
useCallback,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type ButtonHTMLAttributes,
|
|
8
|
+
type HTMLAttributes,
|
|
9
|
+
type KeyboardEvent,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { cn } from "../../lib/cn";
|
|
12
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Token vocabulary
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The semantic color token names that ColorPicker understands as "safe" values.
|
|
20
|
+
* These map 1-to-1 with Tailwind utilities backed by CSS variables so they
|
|
21
|
+
* remain theme-aware at runtime.
|
|
22
|
+
*/
|
|
23
|
+
export type ColorToken =
|
|
24
|
+
| "primary"
|
|
25
|
+
| "secondary"
|
|
26
|
+
| "destructive"
|
|
27
|
+
| "success"
|
|
28
|
+
| "warning"
|
|
29
|
+
| "info"
|
|
30
|
+
| "chart-1"
|
|
31
|
+
| "chart-2"
|
|
32
|
+
| "chart-3"
|
|
33
|
+
| "chart-4"
|
|
34
|
+
| "chart-5"
|
|
35
|
+
| "chart-6"
|
|
36
|
+
| "chart-7"
|
|
37
|
+
| "chart-8"
|
|
38
|
+
| "chart-9"
|
|
39
|
+
| "chart-10"
|
|
40
|
+
| "chart-11"
|
|
41
|
+
| "chart-12";
|
|
42
|
+
|
|
43
|
+
/** All recognised token names in display order. */
|
|
44
|
+
export const COLOR_TOKENS: ColorToken[] = [
|
|
45
|
+
"chart-1",
|
|
46
|
+
"chart-2",
|
|
47
|
+
"chart-3",
|
|
48
|
+
"chart-4",
|
|
49
|
+
"chart-5",
|
|
50
|
+
"chart-6",
|
|
51
|
+
"chart-7",
|
|
52
|
+
"chart-8",
|
|
53
|
+
"chart-9",
|
|
54
|
+
"chart-10",
|
|
55
|
+
"chart-11",
|
|
56
|
+
"chart-12",
|
|
57
|
+
"primary",
|
|
58
|
+
"secondary",
|
|
59
|
+
"destructive",
|
|
60
|
+
"success",
|
|
61
|
+
"warning",
|
|
62
|
+
"info",
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Map a ColorToken → the Tailwind bg-* utility class that renders it.
|
|
67
|
+
* These classes must be present as complete strings so Tailwind's JIT scanner
|
|
68
|
+
* can detect them statically.
|
|
69
|
+
*/
|
|
70
|
+
const TOKEN_BG: Record<ColorToken, string> = {
|
|
71
|
+
"chart-1": "bg-chart-1",
|
|
72
|
+
"chart-2": "bg-chart-2",
|
|
73
|
+
"chart-3": "bg-chart-3",
|
|
74
|
+
"chart-4": "bg-chart-4",
|
|
75
|
+
"chart-5": "bg-chart-5",
|
|
76
|
+
"chart-6": "bg-chart-6",
|
|
77
|
+
"chart-7": "bg-chart-7",
|
|
78
|
+
"chart-8": "bg-chart-8",
|
|
79
|
+
"chart-9": "bg-chart-9",
|
|
80
|
+
"chart-10": "bg-chart-10",
|
|
81
|
+
"chart-11": "bg-chart-11",
|
|
82
|
+
"chart-12": "bg-chart-12",
|
|
83
|
+
primary: "bg-primary",
|
|
84
|
+
secondary: "bg-secondary",
|
|
85
|
+
destructive: "bg-destructive",
|
|
86
|
+
success: "bg-success",
|
|
87
|
+
warning: "bg-warning",
|
|
88
|
+
info: "bg-info",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function isColorToken(v: string): v is ColorToken {
|
|
92
|
+
return Object.prototype.hasOwnProperty.call(TOKEN_BG, v);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Sub-component: individual swatch button
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
interface SwatchProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onSelect"> {
|
|
100
|
+
token: ColorToken;
|
|
101
|
+
selected: boolean;
|
|
102
|
+
onSelect: (token: ColorToken) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const Swatch = forwardRef<HTMLButtonElement, SwatchProps>(function Swatch(
|
|
106
|
+
{ token, selected, onSelect, className, ...props },
|
|
107
|
+
ref,
|
|
108
|
+
) {
|
|
109
|
+
return (
|
|
110
|
+
<button
|
|
111
|
+
ref={ref}
|
|
112
|
+
type="button"
|
|
113
|
+
role="radio"
|
|
114
|
+
aria-checked={selected}
|
|
115
|
+
aria-label={token}
|
|
116
|
+
onClick={() => onSelect(token)}
|
|
117
|
+
className={cn(
|
|
118
|
+
"size-7 rounded-md border-2 transition-[border-color,box-shadow] duration-fast",
|
|
119
|
+
TOKEN_BG[token],
|
|
120
|
+
selected
|
|
121
|
+
? "border-foreground ring-2 ring-ring ring-offset-1 ring-offset-background"
|
|
122
|
+
: "border-transparent hover:border-foreground/40",
|
|
123
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
124
|
+
className,
|
|
125
|
+
)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Trigger swatch preview
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
interface TriggerSwatchProps {
|
|
136
|
+
value: string;
|
|
137
|
+
className?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function TriggerSwatch({ value, className }: TriggerSwatchProps) {
|
|
141
|
+
const bgClass = isColorToken(value) ? TOKEN_BG[value] : undefined;
|
|
142
|
+
return (
|
|
143
|
+
<span
|
|
144
|
+
className={cn("inline-block size-4 rounded-sm border border-border", bgClass, className)}
|
|
145
|
+
// When a raw hex value is supplied, use inline style — the only safe
|
|
146
|
+
// place for a non-token color (isolated to this preview span).
|
|
147
|
+
style={!bgClass ? { backgroundColor: value } : undefined}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Main component
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
export interface ColorPickerProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
157
|
+
/**
|
|
158
|
+
* Controlled value — a `ColorToken` name (preferred, theme-aware) or a raw
|
|
159
|
+
* hex string (only when `allowCustom` is true).
|
|
160
|
+
*/
|
|
161
|
+
value?: ColorToken | string;
|
|
162
|
+
/** Uncontrolled default value. */
|
|
163
|
+
defaultValue?: ColorToken | string;
|
|
164
|
+
/** Called when the selected color changes. */
|
|
165
|
+
onValueChange?: (value: ColorToken | string) => void;
|
|
166
|
+
/**
|
|
167
|
+
* Swatches to display. Defaults to all chart + status tokens.
|
|
168
|
+
* Accepts any subset of `ColorToken`.
|
|
169
|
+
*/
|
|
170
|
+
swatches?: ColorToken[];
|
|
171
|
+
/**
|
|
172
|
+
* When true, a free-form hex input is shown below the swatches.
|
|
173
|
+
* NOTE: custom hex values are NOT theme-aware — they will not adapt when
|
|
174
|
+
* the active theme changes. Prefer token values for all theme-safe UI.
|
|
175
|
+
*/
|
|
176
|
+
allowCustom?: boolean;
|
|
177
|
+
/** aria-label for the trigger button. Defaults to "Pick color". */
|
|
178
|
+
"aria-label"?: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* ColorPicker — a token-safe swatch picker backed by a Popover.
|
|
183
|
+
*
|
|
184
|
+
* The default value type is a `ColorToken` (a semantic CSS variable name),
|
|
185
|
+
* not a hex string. This ensures the chosen color adapts to the active theme.
|
|
186
|
+
* Raw hex is available opt-in via `allowCustom` — see the prop docs.
|
|
187
|
+
*
|
|
188
|
+
* Swatch group uses `role="radiogroup"` with arrow-key navigation.
|
|
189
|
+
*/
|
|
190
|
+
export const ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>(function ColorPicker(
|
|
191
|
+
{
|
|
192
|
+
value: valueProp,
|
|
193
|
+
defaultValue,
|
|
194
|
+
onValueChange,
|
|
195
|
+
swatches = COLOR_TOKENS,
|
|
196
|
+
allowCustom = false,
|
|
197
|
+
"aria-label": ariaLabel = "Pick color",
|
|
198
|
+
className,
|
|
199
|
+
...props
|
|
200
|
+
},
|
|
201
|
+
ref,
|
|
202
|
+
) {
|
|
203
|
+
const isControlled = valueProp !== undefined;
|
|
204
|
+
const [internalValue, setInternalValue] = useState<ColorToken | string>(
|
|
205
|
+
defaultValue ?? "chart-1",
|
|
206
|
+
);
|
|
207
|
+
const [customHex, setCustomHex] = useState("");
|
|
208
|
+
const [open, setOpen] = useState(false);
|
|
209
|
+
|
|
210
|
+
const current = isControlled ? (valueProp ?? "") : internalValue;
|
|
211
|
+
|
|
212
|
+
const commit = useCallback(
|
|
213
|
+
(next: ColorToken | string) => {
|
|
214
|
+
if (!isControlled) setInternalValue(next);
|
|
215
|
+
onValueChange?.(next);
|
|
216
|
+
},
|
|
217
|
+
[isControlled, onValueChange],
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const handleSwatchSelect = useCallback(
|
|
221
|
+
(token: ColorToken) => {
|
|
222
|
+
commit(token);
|
|
223
|
+
setOpen(false);
|
|
224
|
+
},
|
|
225
|
+
[commit],
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const handleCustomSubmit = useCallback(() => {
|
|
229
|
+
const hex = customHex.trim();
|
|
230
|
+
if (/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)) {
|
|
231
|
+
commit(hex);
|
|
232
|
+
setOpen(false);
|
|
233
|
+
}
|
|
234
|
+
}, [customHex, commit]);
|
|
235
|
+
|
|
236
|
+
// Arrow-key navigation within the radiogroup
|
|
237
|
+
const groupRef = useRef<HTMLDivElement>(null);
|
|
238
|
+
const handleGroupKeyDown = useCallback((e: KeyboardEvent<HTMLDivElement>) => {
|
|
239
|
+
const buttons = groupRef.current
|
|
240
|
+
? Array.from(groupRef.current.querySelectorAll<HTMLButtonElement>("[role=radio]"))
|
|
241
|
+
: [];
|
|
242
|
+
if (!buttons.length) return;
|
|
243
|
+
const focused = document.activeElement as HTMLButtonElement;
|
|
244
|
+
const idx = buttons.indexOf(focused);
|
|
245
|
+
if (idx === -1) return;
|
|
246
|
+
|
|
247
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
buttons[(idx + 1) % buttons.length]?.focus();
|
|
250
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
buttons[(idx - 1 + buttons.length) % buttons.length]?.focus();
|
|
253
|
+
}
|
|
254
|
+
}, []);
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<div ref={ref} className={cn("inline-flex", className)} {...props}>
|
|
258
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
259
|
+
<PopoverTrigger asChild>
|
|
260
|
+
<button
|
|
261
|
+
type="button"
|
|
262
|
+
aria-label={ariaLabel}
|
|
263
|
+
aria-haspopup="dialog"
|
|
264
|
+
aria-expanded={open}
|
|
265
|
+
className={cn(
|
|
266
|
+
"inline-flex items-center gap-2 rounded-md border border-input bg-background px-3 py-1.5 text-sm shadow-sm",
|
|
267
|
+
"transition-[color,box-shadow] duration-fast",
|
|
268
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
269
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
270
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
271
|
+
)}
|
|
272
|
+
>
|
|
273
|
+
<TriggerSwatch value={current} />
|
|
274
|
+
<span className="text-muted-foreground">{current || "None"}</span>
|
|
275
|
+
</button>
|
|
276
|
+
</PopoverTrigger>
|
|
277
|
+
|
|
278
|
+
<PopoverContent className="w-auto p-3" align="start">
|
|
279
|
+
<div
|
|
280
|
+
ref={groupRef}
|
|
281
|
+
role="radiogroup"
|
|
282
|
+
aria-label="Color swatches"
|
|
283
|
+
className="flex flex-wrap gap-1.5"
|
|
284
|
+
onKeyDown={handleGroupKeyDown}
|
|
285
|
+
>
|
|
286
|
+
{swatches.map((token) => (
|
|
287
|
+
<Swatch
|
|
288
|
+
key={token}
|
|
289
|
+
token={token}
|
|
290
|
+
selected={current === token}
|
|
291
|
+
onSelect={handleSwatchSelect}
|
|
292
|
+
/>
|
|
293
|
+
))}
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
{allowCustom && (
|
|
297
|
+
<div className="mt-3 border-t border-border pt-3">
|
|
298
|
+
{/* Custom hex is NOT theme-aware — it will not adapt to theme changes. */}
|
|
299
|
+
<p className="mb-1.5 text-xs text-muted-foreground">
|
|
300
|
+
Custom hex <span className="text-warning">(not theme-aware)</span>
|
|
301
|
+
</p>
|
|
302
|
+
<div className="flex gap-2">
|
|
303
|
+
<input
|
|
304
|
+
type="text"
|
|
305
|
+
value={customHex}
|
|
306
|
+
placeholder="#3b82f6"
|
|
307
|
+
maxLength={7}
|
|
308
|
+
spellCheck={false}
|
|
309
|
+
aria-label="Custom hex color"
|
|
310
|
+
onChange={(e) => setCustomHex(e.target.value)}
|
|
311
|
+
onKeyDown={(e) => {
|
|
312
|
+
if (e.key === "Enter") {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
handleCustomSubmit();
|
|
315
|
+
}
|
|
316
|
+
}}
|
|
317
|
+
className={cn(
|
|
318
|
+
"h-8 w-28 rounded-md border border-input bg-background px-2 text-sm",
|
|
319
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
320
|
+
"placeholder:text-muted-foreground",
|
|
321
|
+
)}
|
|
322
|
+
/>
|
|
323
|
+
<button
|
|
324
|
+
type="button"
|
|
325
|
+
onClick={handleCustomSubmit}
|
|
326
|
+
className={cn(
|
|
327
|
+
"h-8 rounded-md border border-input bg-background px-2 text-sm",
|
|
328
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
329
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
|
|
330
|
+
)}
|
|
331
|
+
>
|
|
332
|
+
Apply
|
|
333
|
+
</button>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
</PopoverContent>
|
|
338
|
+
</Popover>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ColorPicker, COLOR_TOKENS, type ColorPickerProps, type ColorToken } from "./color-picker";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { expect, within } from "storybook/test";
|
|
3
|
+
import { Combobox } from "./combobox";
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Forms/Combobox",
|
|
6
|
+
component: Combobox,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
argTypes: {
|
|
9
|
+
options: {
|
|
10
|
+
description: "Array of `{ label, value }` options.",
|
|
11
|
+
control: false,
|
|
12
|
+
table: { category: "Content" },
|
|
13
|
+
},
|
|
14
|
+
value: {
|
|
15
|
+
description: "Controlled selected value.",
|
|
16
|
+
control: "text",
|
|
17
|
+
table: { category: "State" },
|
|
18
|
+
},
|
|
19
|
+
placeholder: {
|
|
20
|
+
description: "Text shown in the trigger when no option is selected.",
|
|
21
|
+
control: "text",
|
|
22
|
+
table: { category: "Content" },
|
|
23
|
+
},
|
|
24
|
+
searchPlaceholder: {
|
|
25
|
+
description: "Placeholder inside the search input.",
|
|
26
|
+
control: "text",
|
|
27
|
+
table: { category: "Content" },
|
|
28
|
+
},
|
|
29
|
+
emptyText: {
|
|
30
|
+
description: "Message shown when the search yields no results.",
|
|
31
|
+
control: "text",
|
|
32
|
+
table: { category: "Content" },
|
|
33
|
+
},
|
|
34
|
+
onValueChange: {
|
|
35
|
+
description: "Called when the selected value changes.",
|
|
36
|
+
control: false,
|
|
37
|
+
table: { category: "Behavior" },
|
|
38
|
+
},
|
|
39
|
+
className: {
|
|
40
|
+
description: "Additional CSS classes applied to the trigger button.",
|
|
41
|
+
control: "text",
|
|
42
|
+
table: { category: "Appearance" },
|
|
43
|
+
},
|
|
44
|
+
disabled: {
|
|
45
|
+
description: "Disables the trigger and prevents the popover from opening.",
|
|
46
|
+
control: "boolean",
|
|
47
|
+
table: { category: "State" },
|
|
48
|
+
},
|
|
49
|
+
allowCustomValue: {
|
|
50
|
+
description:
|
|
51
|
+
"When true, typed text not present in options becomes a submittable value (#359).",
|
|
52
|
+
control: "boolean",
|
|
53
|
+
table: { category: "Behavior" },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
} satisfies Meta<typeof Combobox>;
|
|
57
|
+
export default meta;
|
|
58
|
+
type Story = StoryObj<typeof meta>;
|
|
59
|
+
export const Default: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
options: [
|
|
62
|
+
{ label: "Production", value: "prod" },
|
|
63
|
+
{ label: "Staging", value: "staging" },
|
|
64
|
+
{ label: "Development", value: "dev" },
|
|
65
|
+
],
|
|
66
|
+
placeholder: "Select environment…",
|
|
67
|
+
},
|
|
68
|
+
// Opens the popover, confirms options appear, then picks "Staging".
|
|
69
|
+
play: async ({ canvas, canvasElement, userEvent }) => {
|
|
70
|
+
const trigger = canvas.getByRole("combobox");
|
|
71
|
+
await userEvent.click(trigger);
|
|
72
|
+
const body = within(canvasElement.ownerDocument.body);
|
|
73
|
+
const option = await body.findByRole("option", { name: "Staging" });
|
|
74
|
+
await expect(option).toBeInTheDocument();
|
|
75
|
+
await userEvent.click(option);
|
|
76
|
+
await expect(trigger).toHaveTextContent("Staging");
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ENVIRONMENTS = [
|
|
81
|
+
{ label: "Production", value: "prod" },
|
|
82
|
+
{ label: "Staging", value: "staging" },
|
|
83
|
+
{ label: "Development", value: "dev" },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
/** Enabled vs disabled, side by side (#343). */
|
|
87
|
+
export const DisabledState: Story = {
|
|
88
|
+
name: "Disabled",
|
|
89
|
+
render: () => (
|
|
90
|
+
<div className="flex items-center gap-4">
|
|
91
|
+
<Combobox
|
|
92
|
+
aria-label="Environment (enabled)"
|
|
93
|
+
options={ENVIRONMENTS}
|
|
94
|
+
placeholder="Select environment…"
|
|
95
|
+
/>
|
|
96
|
+
<Combobox
|
|
97
|
+
aria-label="Environment (disabled)"
|
|
98
|
+
options={ENVIRONMENTS}
|
|
99
|
+
placeholder="Select environment…"
|
|
100
|
+
disabled
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
),
|
|
104
|
+
// The disabled trigger is a real native `disabled` button — a genuine
|
|
105
|
+
// pointer click can't even land on it (`pointer-events: none`, asserted by
|
|
106
|
+
// the browser itself), so there's nothing to open.
|
|
107
|
+
play: async ({ canvas }) => {
|
|
108
|
+
const [, disabledTrigger] = canvas.getAllByRole("combobox");
|
|
109
|
+
await expect(disabledTrigger).toBeDisabled();
|
|
110
|
+
await expect(canvas.queryByPlaceholderText("Search…")).not.toBeInTheDocument();
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* `aria-label` names the control's PURPOSE ("Session switcher"), independent
|
|
116
|
+
* of the selected value's own text (#347).
|
|
117
|
+
*/
|
|
118
|
+
export const AccessibleName: Story = {
|
|
119
|
+
args: {
|
|
120
|
+
options: ENVIRONMENTS,
|
|
121
|
+
value: "prod",
|
|
122
|
+
"aria-label": "Environment switcher",
|
|
123
|
+
},
|
|
124
|
+
play: async ({ canvas }) => {
|
|
125
|
+
const trigger = canvas.getByRole("combobox", { name: "Environment switcher" });
|
|
126
|
+
await expect(trigger).toHaveTextContent("Production");
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `allowCustomValue` accepts free text alongside the suggested options — a
|
|
132
|
+
* value not in `options` is offered via a "Use…" suggestion, still filtered
|
|
133
|
+
* live as the user types (#359).
|
|
134
|
+
*/
|
|
135
|
+
export const AllowCustomValue: Story = {
|
|
136
|
+
name: "Allow custom value",
|
|
137
|
+
args: {
|
|
138
|
+
options: ENVIRONMENTS,
|
|
139
|
+
allowCustomValue: true,
|
|
140
|
+
placeholder: "Select or type an environment…",
|
|
141
|
+
"aria-label": "Environment (custom value allowed)",
|
|
142
|
+
},
|
|
143
|
+
play: async ({ canvas, canvasElement, userEvent }) => {
|
|
144
|
+
const trigger = canvas.getByRole("combobox");
|
|
145
|
+
await userEvent.click(trigger);
|
|
146
|
+
const body = within(canvasElement.ownerDocument.body);
|
|
147
|
+
const input = await body.findByPlaceholderText("Search…");
|
|
148
|
+
await userEvent.type(input, "Canary");
|
|
149
|
+
const customItem = await body.findByRole("option", { name: /use.*canary/i });
|
|
150
|
+
await expect(customItem).toBeInTheDocument();
|
|
151
|
+
await userEvent.click(customItem);
|
|
152
|
+
await expect(trigger).toHaveTextContent("Canary");
|
|
153
|
+
},
|
|
154
|
+
};
|