@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,187 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, use, useMemo, type ReactNode } from "react";
|
|
4
|
+
import { DirectionProvider } from "@radix-ui/react-direction";
|
|
5
|
+
import { DEFAULT_MESSAGES } from "./messages";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Public types
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/** A flat map of string keys to their translated values. */
|
|
12
|
+
export type Messages = Record<string, string>;
|
|
13
|
+
|
|
14
|
+
export interface LocaleContextValue {
|
|
15
|
+
/** BCP-47 locale tag, e.g. "en-US", "ar-EG", "de-DE". */
|
|
16
|
+
locale: string;
|
|
17
|
+
/** Layout direction for the locale region. */
|
|
18
|
+
dir: "ltr" | "rtl";
|
|
19
|
+
/**
|
|
20
|
+
* Translate a key.
|
|
21
|
+
* Falls back to: consumer messages → English default → the key itself.
|
|
22
|
+
* Supports simple `{name}`-style variable interpolation.
|
|
23
|
+
*/
|
|
24
|
+
t: (key: string, vars?: Record<string, string | number>) => string;
|
|
25
|
+
/** Format a number using the active locale. Memoized per locale+opts hash. */
|
|
26
|
+
formatNumber: (n: number, opts?: Intl.NumberFormatOptions) => string;
|
|
27
|
+
/** Format a date using the active locale. Memoized per locale+opts hash. */
|
|
28
|
+
formatDate: (d: Date | number, opts?: Intl.DateTimeFormatOptions) => string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Context
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
const LocaleContext = createContext<LocaleContextValue | null>(null);
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Default (no-provider) value — en-US / ltr
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function interpolate(template: string, vars?: Record<string, string | number>): string {
|
|
42
|
+
if (!vars) return template;
|
|
43
|
+
return template.replace(/\{(\w+)\}/g, (_, key: string) =>
|
|
44
|
+
key in vars ? String(vars[key]) : `{${key}}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeDefaultValue(): LocaleContextValue {
|
|
49
|
+
const locale = "en-US";
|
|
50
|
+
// Simple cache: one Intl object per serialized opts string
|
|
51
|
+
const numberCache = new Map<string, Intl.NumberFormat>();
|
|
52
|
+
const dateCache = new Map<string, Intl.DateTimeFormat>();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
locale,
|
|
56
|
+
dir: "ltr",
|
|
57
|
+
t(key, vars) {
|
|
58
|
+
const template = DEFAULT_MESSAGES[key] ?? key;
|
|
59
|
+
return interpolate(template, vars);
|
|
60
|
+
},
|
|
61
|
+
formatNumber(n, opts) {
|
|
62
|
+
const k = JSON.stringify(opts ?? null);
|
|
63
|
+
if (!numberCache.has(k)) numberCache.set(k, new Intl.NumberFormat(locale, opts));
|
|
64
|
+
return numberCache.get(k)!.format(n);
|
|
65
|
+
},
|
|
66
|
+
formatDate(d, opts) {
|
|
67
|
+
const k = JSON.stringify(opts ?? null);
|
|
68
|
+
if (!dateCache.has(k)) dateCache.set(k, new Intl.DateTimeFormat(locale, opts));
|
|
69
|
+
return dateCache.get(k)!.format(d);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Singleton default — stable across renders when no provider is mounted
|
|
75
|
+
const DEFAULT_LOCALE_VALUE = makeDefaultValue();
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Provider props
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
export interface LocaleProviderProps {
|
|
82
|
+
/**
|
|
83
|
+
* BCP-47 locale tag. Defaults to "en-US".
|
|
84
|
+
* Used by `t`, `formatNumber`, and `formatDate`.
|
|
85
|
+
*/
|
|
86
|
+
locale?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Layout direction for the subtree.
|
|
89
|
+
* Defaults to "ltr". Set "rtl" for Arabic, Hebrew, Farsi, etc.
|
|
90
|
+
*/
|
|
91
|
+
dir?: "ltr" | "rtl";
|
|
92
|
+
/**
|
|
93
|
+
* Additional/overriding message strings merged on top of the shipped English
|
|
94
|
+
* defaults. Only the keys you provide are overridden; the rest fall through
|
|
95
|
+
* to the English bundle.
|
|
96
|
+
*/
|
|
97
|
+
messages?: Messages;
|
|
98
|
+
children: ReactNode;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// LocaleProvider
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* `LocaleProvider` supplies locale, layout direction, component microcopy, and
|
|
107
|
+
* Intl formatting helpers to the component subtree.
|
|
108
|
+
*
|
|
109
|
+
* - Wrap your app (or a regional subtree) with it to localise brand-ui components.
|
|
110
|
+
* - Components call `useLocale()` unconditionally; without a provider they
|
|
111
|
+
* degrade gracefully to "en-US" / "ltr" / English defaults.
|
|
112
|
+
* - Portalled Radix content (Dialog, Sheet, Popover, DropdownMenu) still honors
|
|
113
|
+
* `dir` because the subtree is wrapped in Radix's `DirectionProvider`, which
|
|
114
|
+
* every Radix primitive reads via context regardless of DOM position (ADR-0014).
|
|
115
|
+
* The `<div dir={dir}>` continues to carry direction for non-Radix content.
|
|
116
|
+
*/
|
|
117
|
+
export function LocaleProvider({
|
|
118
|
+
locale = "en-US",
|
|
119
|
+
dir = "ltr",
|
|
120
|
+
messages,
|
|
121
|
+
children,
|
|
122
|
+
}: LocaleProviderProps) {
|
|
123
|
+
// Merge consumer messages over the shipped English defaults.
|
|
124
|
+
const merged: Messages = useMemo(
|
|
125
|
+
() => (messages ? { ...DEFAULT_MESSAGES, ...messages } : DEFAULT_MESSAGES),
|
|
126
|
+
// We intentionally deep-compare via JSON only when messages reference changes.
|
|
127
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
128
|
+
[messages],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Memoized Intl formatter caches — keyed by serialised opts
|
|
132
|
+
const numberCache = useMemo(() => new Map<string, Intl.NumberFormat>(), [locale]);
|
|
133
|
+
const dateCache = useMemo(() => new Map<string, Intl.DateTimeFormat>(), [locale]);
|
|
134
|
+
|
|
135
|
+
const value = useMemo<LocaleContextValue>(
|
|
136
|
+
() => ({
|
|
137
|
+
locale,
|
|
138
|
+
dir,
|
|
139
|
+
t(key, vars) {
|
|
140
|
+
const template = merged[key] ?? key;
|
|
141
|
+
return interpolate(template, vars);
|
|
142
|
+
},
|
|
143
|
+
formatNumber(n, opts) {
|
|
144
|
+
const k = JSON.stringify(opts ?? null);
|
|
145
|
+
if (!numberCache.has(k)) numberCache.set(k, new Intl.NumberFormat(locale, opts));
|
|
146
|
+
return numberCache.get(k)!.format(n);
|
|
147
|
+
},
|
|
148
|
+
formatDate(d, opts) {
|
|
149
|
+
const k = JSON.stringify(opts ?? null);
|
|
150
|
+
if (!dateCache.has(k)) dateCache.set(k, new Intl.DateTimeFormat(locale, opts));
|
|
151
|
+
return dateCache.get(k)!.format(d);
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
[locale, dir, merged, numberCache, dateCache],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<LocaleContext.Provider value={value}>
|
|
159
|
+
{/*
|
|
160
|
+
* `DirectionProvider` makes `dir` reach portalled Radix overlays (Dialog,
|
|
161
|
+
* Sheet, Popover, DropdownMenu, Select, Tooltip, …) which escape the DOM
|
|
162
|
+
* `dir` ancestor (ADR-0014). The `<div dir={dir}>` still carries direction
|
|
163
|
+
* for non-Radix content.
|
|
164
|
+
*/}
|
|
165
|
+
<DirectionProvider dir={dir}>
|
|
166
|
+
<div dir={dir}>{children}</div>
|
|
167
|
+
</DirectionProvider>
|
|
168
|
+
</LocaleContext.Provider>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// useLocale
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Read the active locale, direction, `t`, `formatNumber`, and `formatDate`.
|
|
178
|
+
*
|
|
179
|
+
* Provider-optional: without a `<LocaleProvider>` this returns the en-US
|
|
180
|
+
* defaults so components can call it unconditionally and remain usable in
|
|
181
|
+
* unwrapped contexts.
|
|
182
|
+
*/
|
|
183
|
+
export function useLocale(): LocaleContextValue {
|
|
184
|
+
// React 19 `use()` — can be called conditionally and composes with Suspense.
|
|
185
|
+
const ctx = use(LocaleContext);
|
|
186
|
+
return ctx ?? DEFAULT_LOCALE_VALUE;
|
|
187
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shipped English (en-US) default microcopy bundle.
|
|
5
|
+
*
|
|
6
|
+
* Keys are intentionally terse, semantic, and framework-agnostic so they
|
|
7
|
+
* translate cleanly into any target locale. Keep this list small and real —
|
|
8
|
+
* only add a key here when a component actually needs it.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MESSAGES: Record<string, string> = {
|
|
11
|
+
// ── Generic (shared across packages — reuse these before minting a new key) ──
|
|
12
|
+
close: "Close",
|
|
13
|
+
copy: "Copy",
|
|
14
|
+
previous: "Previous",
|
|
15
|
+
next: "Next",
|
|
16
|
+
previousSlide: "Previous slide",
|
|
17
|
+
nextSlide: "Next slide",
|
|
18
|
+
noResults: "No results.",
|
|
19
|
+
noRows: "No rows.",
|
|
20
|
+
loading: "Loading…",
|
|
21
|
+
more: "More",
|
|
22
|
+
selectAll: "Select all",
|
|
23
|
+
|
|
24
|
+
// ── @elabs-ai/components-ui ───────────────────────────────────────────────────────────────
|
|
25
|
+
"ui.metricCard.loading": "Loading metric…",
|
|
26
|
+
// CopyableValue. `hint` is APPENDED to the visible (compact) value, so the
|
|
27
|
+
// accessible name contains what the reader sees — WCAG 2.5.3, Label in Name.
|
|
28
|
+
"ui.copyableValue.hint": "Copy exact value",
|
|
29
|
+
"ui.copyableValue.copied": "Copied",
|
|
30
|
+
// ExpandDialog. Names the context pane beside the enlarged content, so a
|
|
31
|
+
// screen-reader user can tell the two scrollable regions apart.
|
|
32
|
+
"ui.expandDialog.detail": "Details",
|
|
33
|
+
// AttributionPanel. `required` labels a notice a licence obliges us to show —
|
|
34
|
+
// it is the WORD, not a colour, so the distinction survives greyscale (1.4.1).
|
|
35
|
+
"ui.attributionPanel.label": "Third-party attributions",
|
|
36
|
+
"ui.attributionPanel.required": "Required",
|
|
37
|
+
"ui.attributionPanel.empty": "No attributions match this filter.",
|
|
38
|
+
// ModelPicker. `refreshFailed` sits ABOVE a list that still works; `loadFailed`
|
|
39
|
+
// replaces the list. The distinction is load-bearing — see modelPickerBody.
|
|
40
|
+
"ui.modelPicker.label": "Choose a target",
|
|
41
|
+
"ui.modelPicker.searchPlaceholder": "Search…",
|
|
42
|
+
"ui.modelPicker.refreshFailed": "Couldn't refresh the list",
|
|
43
|
+
"ui.modelPicker.loadFailed": "Couldn't load the list",
|
|
44
|
+
"ui.modelPicker.nothingYet": "Nothing to show yet",
|
|
45
|
+
"ui.modelPicker.retry": "Retry",
|
|
46
|
+
// ViewToolbar (#331). `removeFilter` deliberately WRAPS the visible chip text
|
|
47
|
+
// so the accessible name contains it (WCAG 2.5.3 Label in Name).
|
|
48
|
+
"ui.viewToolbar.about": "About this view",
|
|
49
|
+
"ui.viewToolbar.activeFilters": "Active filters",
|
|
50
|
+
"ui.viewToolbar.clearAll": "Clear all",
|
|
51
|
+
"ui.viewToolbar.countOfTotal": "{count} of {total}",
|
|
52
|
+
"ui.viewToolbar.removeFilter": "Remove filter: {label}",
|
|
53
|
+
// Combobox `allowCustomValue` mode (#359) — `{value}` interpolates the
|
|
54
|
+
// currently typed, non-matching search text.
|
|
55
|
+
"ui.combobox.useCustomValue": 'Use "{value}"',
|
|
56
|
+
"ui.combobox.emptyAllowCustom": "No matches. Type to add a custom value.",
|
|
57
|
+
// Form kit (#370–#374).
|
|
58
|
+
"ui.boundedNumber.emptyLabel": "No limit",
|
|
59
|
+
"ui.keyValueEditor.addRow": "Add row",
|
|
60
|
+
"ui.keyValueEditor.keyLabel": "Key {n}",
|
|
61
|
+
"ui.keyValueEditor.valueLabel": "Value {n}",
|
|
62
|
+
"ui.keyValueEditor.removeRow": "Remove row {n}",
|
|
63
|
+
"ui.keyValueEditor.reveal": "Reveal value {n}",
|
|
64
|
+
"ui.keyValueEditor.hide": "Hide value {n}",
|
|
65
|
+
"ui.keyValueEditor.empty": "No entries yet.",
|
|
66
|
+
"ui.listEditor.addItem": "Add item",
|
|
67
|
+
"ui.listEditor.itemLabel": "Item {n}",
|
|
68
|
+
"ui.listEditor.removeItem": "Remove item {n}",
|
|
69
|
+
"ui.listEditor.moveUp": "Move item {n} up",
|
|
70
|
+
"ui.listEditor.moveDown": "Move item {n} down",
|
|
71
|
+
"ui.listEditor.empty": "No items yet.",
|
|
72
|
+
"ui.sliderNumber.reset": "Reset",
|
|
73
|
+
// MentionInput (#368). The listbox lives in a portal with no visible label of
|
|
74
|
+
// its own, so this IS its accessible name. The empty state deliberately
|
|
75
|
+
// reuses the generic `noResults` key rather than minting a near-duplicate.
|
|
76
|
+
"ui.mentionInput.listLabel": "Mention suggestions",
|
|
77
|
+
// ConfirmDialog's fallback action labels. A real confirmation replaces
|
|
78
|
+
// `confirmLabel` with the CONSEQUENCE ("Delete skill"); this generic default
|
|
79
|
+
// exists so the component is never unlabelled.
|
|
80
|
+
"ui.confirmDialog.confirm": "Confirm",
|
|
81
|
+
"ui.confirmDialog.cancel": "Cancel",
|
|
82
|
+
"ui.advancedGroup.title": "Advanced",
|
|
83
|
+
"ui.advancedGroup.changed": "{count} changed",
|
|
84
|
+
|
|
85
|
+
// ── @elabs-ai/components-data ─────────────────────────────────────────────────────────────
|
|
86
|
+
// The scroll region's accessible name is rendered ONLY when the table actually
|
|
87
|
+
// overflows its container, so it is never announced for a table that fits.
|
|
88
|
+
"data.table.scrollRegion": "Table contents, scrollable",
|
|
89
|
+
// Fallback name for a row's activation control when the row's first cell holds
|
|
90
|
+
// no primitive value to name it after (see `rowActionLabel`).
|
|
91
|
+
"data.table.rowAction": "Activate row",
|
|
92
|
+
|
|
93
|
+
// ── @elabs-ai/components-charts ───────────────────────────────────────────────────────────
|
|
94
|
+
// Shared caption for any bare chart surface's layout-shaped skeleton
|
|
95
|
+
// (ChartFrame, ChartCard, AutoChart all show the same "a chart is loading"
|
|
96
|
+
// concept — one key, reused, rather than three near-duplicate strings).
|
|
97
|
+
"charts.chart.loading": "Loading chart…",
|
|
98
|
+
"charts.metricGrid.loading": "Loading metrics…",
|
|
99
|
+
// Chart drill-down (#349). The chart SVG is aria-hidden, so these names are
|
|
100
|
+
// the ONLY thing AT reads for an interactive datapoint — there is no
|
|
101
|
+
// consumer-side workaround if they stay English.
|
|
102
|
+
"charts.datapointLayer.label": "Chart data points",
|
|
103
|
+
"charts.datapoint.label": "{series}, {category}: {value}",
|
|
104
|
+
"charts.datapoint.labelNoSeries": "{category}: {value}",
|
|
105
|
+
// Announced when `copyValueOnActivate` puts a datapoint's exact value on the
|
|
106
|
+
// clipboard — the recovery path for a compact axis label.
|
|
107
|
+
"charts.datapoint.copied": "Exact value copied",
|
|
108
|
+
|
|
109
|
+
// ── @elabs-ai/components-ai ───────────────────────────────────────────────────────────────
|
|
110
|
+
// Namespaced `ai.<area>.<key>` so package microcopy can't collide with the
|
|
111
|
+
// generic keys above, or with a future package's. See ADR 0017.
|
|
112
|
+
//
|
|
113
|
+
// The three attachment errors are the only @elabs-ai/components-ai strings a user reads as a
|
|
114
|
+
// SYSTEM MESSAGE rather than a control label — they are the reason this bundle
|
|
115
|
+
// grew beyond the generic set.
|
|
116
|
+
"ai.promptInput.errorAccept": "No files match the accepted types.",
|
|
117
|
+
"ai.promptInput.errorMaxFileSize": "All files exceed the maximum size.",
|
|
118
|
+
"ai.promptInput.errorMaxFiles": "Too many files. Some were not added.",
|
|
119
|
+
"ai.promptInput.placeholder": "What would you like to know?",
|
|
120
|
+
"ai.promptInput.uploadFiles": "Upload files",
|
|
121
|
+
"ai.promptInput.submit": "Submit",
|
|
122
|
+
"ai.promptInput.stop": "Stop",
|
|
123
|
+
"ai.codeBlock.generating": "Generating…",
|
|
124
|
+
"ai.composer.placeholder": "Ask me anything…",
|
|
125
|
+
"ai.context.usage": "Model context usage",
|
|
126
|
+
"ai.contextPanel.back": "Back to context",
|
|
127
|
+
"ai.contextPanel.toggle": "Toggle context panel",
|
|
128
|
+
"ai.environmentVariables.toggleVisibility": "Toggle value visibility",
|
|
129
|
+
"ai.gallery.label": "Image gallery",
|
|
130
|
+
"ai.gallery.expandImage": "Expand image",
|
|
131
|
+
"ai.gallery.downloadImage": "Download image",
|
|
132
|
+
"ai.message.actions": "Message actions",
|
|
133
|
+
"ai.message.editMessage": "Edit message",
|
|
134
|
+
"ai.message.feedback": "Message feedback",
|
|
135
|
+
"ai.message.previousBranch": "Previous branch",
|
|
136
|
+
"ai.message.nextBranch": "Next branch",
|
|
137
|
+
"ai.persona.idle": "Assistant idle",
|
|
138
|
+
"ai.persona.listening": "Assistant listening",
|
|
139
|
+
"ai.persona.thinking": "Assistant thinking…",
|
|
140
|
+
"ai.persona.speaking": "Assistant speaking",
|
|
141
|
+
"ai.persona.asleep": "Assistant asleep",
|
|
142
|
+
"ai.selectionToolbar.label": "Selection actions",
|
|
143
|
+
"ai.webPreview.urlPlaceholder": "Enter URL...",
|
|
144
|
+
"ai.micSelector.searchPlaceholder": "Search microphones...",
|
|
145
|
+
"ai.messageForm.selectPlaceholder": "Select…",
|
|
146
|
+
"ai.messageForm.label": "Form",
|
|
147
|
+
"ai.messageTable.label": "Data table",
|
|
148
|
+
"ai.voiceSelector.playPreview": "Play preview",
|
|
149
|
+
"ai.voiceSelector.pausePreview": "Pause preview",
|
|
150
|
+
|
|
151
|
+
// ── Streamdown chrome (third-party rendering surface) ──────────────────────
|
|
152
|
+
// `streamdown` renders its OWN controls inside every streamed-markdown block
|
|
153
|
+
// (code header, table menus, Mermaid toolbar, external-link interstitial) and
|
|
154
|
+
// exposes them through a `translations` prop. Those strings live in the
|
|
155
|
+
// dependency, not in our source, so `pnpm microcopy:check` structurally cannot
|
|
156
|
+
// see them — without this block a `<LocaleProvider>` stops at the boundary and
|
|
157
|
+
// the chrome stays English (#310).
|
|
158
|
+
//
|
|
159
|
+
// Values are BYTE-IDENTICAL to streamdown@2.5.0's `defaultTranslations`, so
|
|
160
|
+
// wiring them through `t()` is a no-op for anyone who overrides nothing
|
|
161
|
+
// (ADR 0017's defaults-unchanged property). Keep them that way on upgrade.
|
|
162
|
+
//
|
|
163
|
+
// Deliberately NOT aliased onto the generic `close` / `ai.gallery.downloadImage`
|
|
164
|
+
// keys: a locale may want different wording inside markdown chrome than in the
|
|
165
|
+
// surrounding app, and a translator needs the Streamdown surface addressable as
|
|
166
|
+
// one block.
|
|
167
|
+
"ai.streamdown.copyCode": "Copy Code",
|
|
168
|
+
"ai.streamdown.downloadFile": "Download file",
|
|
169
|
+
"ai.streamdown.downloadDiagram": "Download diagram",
|
|
170
|
+
"ai.streamdown.downloadDiagramAsSvg": "Download diagram as SVG",
|
|
171
|
+
"ai.streamdown.downloadDiagramAsPng": "Download diagram as PNG",
|
|
172
|
+
"ai.streamdown.downloadDiagramAsMmd": "Download diagram as MMD",
|
|
173
|
+
"ai.streamdown.viewFullscreen": "View fullscreen",
|
|
174
|
+
"ai.streamdown.exitFullscreen": "Exit fullscreen",
|
|
175
|
+
"ai.streamdown.mermaidFormatSvg": "SVG",
|
|
176
|
+
"ai.streamdown.mermaidFormatPng": "PNG",
|
|
177
|
+
"ai.streamdown.mermaidFormatMmd": "MMD",
|
|
178
|
+
"ai.streamdown.copyTable": "Copy table",
|
|
179
|
+
"ai.streamdown.copyTableAsMarkdown": "Copy table as Markdown",
|
|
180
|
+
"ai.streamdown.copyTableAsCsv": "Copy table as CSV",
|
|
181
|
+
"ai.streamdown.copyTableAsTsv": "Copy table as TSV",
|
|
182
|
+
"ai.streamdown.downloadTable": "Download table",
|
|
183
|
+
"ai.streamdown.downloadTableAsCsv": "Download table as CSV",
|
|
184
|
+
"ai.streamdown.downloadTableAsMarkdown": "Download table as Markdown",
|
|
185
|
+
"ai.streamdown.tableFormatMarkdown": "Markdown",
|
|
186
|
+
"ai.streamdown.tableFormatCsv": "CSV",
|
|
187
|
+
"ai.streamdown.tableFormatTsv": "TSV",
|
|
188
|
+
"ai.streamdown.imageNotAvailable": "Image not available",
|
|
189
|
+
"ai.streamdown.downloadImage": "Download image",
|
|
190
|
+
"ai.streamdown.openExternalLink": "Open external link?",
|
|
191
|
+
// Straight apostrophe on purpose — must stay byte-identical to streamdown's
|
|
192
|
+
// default, so the micro-typography curly-quote preference does not apply here.
|
|
193
|
+
"ai.streamdown.externalLinkWarning": "You're about to visit an external website.",
|
|
194
|
+
"ai.streamdown.close": "Close",
|
|
195
|
+
"ai.streamdown.copyLink": "Copy link",
|
|
196
|
+
"ai.streamdown.copied": "Copied",
|
|
197
|
+
"ai.streamdown.openLink": "Open link",
|
|
198
|
+
|
|
199
|
+
// ── @elabs-ai/components-viewer (ADR 0024) ─────────────────────────
|
|
200
|
+
// FileViewer chrome. Every control here is icon-only, so these ARE the
|
|
201
|
+
// accessible names — a non-English screen-reader user has no workaround.
|
|
202
|
+
"viewer.label": "File viewer",
|
|
203
|
+
// The scrolling content region's accessible name. It is a focusable tab stop
|
|
204
|
+
// (WCAG 2.1.1: a pane that scrolls but holds nothing focusable is unreachable
|
|
205
|
+
// from a keyboard), so it needs a name as well as a role.
|
|
206
|
+
"viewer.content": "File content",
|
|
207
|
+
"viewer.download": "Download {name}",
|
|
208
|
+
"viewer.raw": "Show source",
|
|
209
|
+
"viewer.rendered": "Show rendered",
|
|
210
|
+
// States. `loading` announces the region once (role="status"); the skeleton
|
|
211
|
+
// itself is aria-hidden, so this is the only thing AT hears.
|
|
212
|
+
"viewer.loading": "Loading {name}…",
|
|
213
|
+
"viewer.empty": "No file selected",
|
|
214
|
+
"viewer.emptyBody": "Choose a file to preview it here.",
|
|
215
|
+
// Failures. Each maps to one ViewerErrorCode, so the code is the contract and
|
|
216
|
+
// the wording can change per locale without touching component logic.
|
|
217
|
+
"viewer.error.unsupportedFormat": "Can't preview this file type",
|
|
218
|
+
"viewer.error.unsupportedFormatBody":
|
|
219
|
+
"{name} can be downloaded, but there's no preview for it here.",
|
|
220
|
+
"viewer.error.parserMissing": "Preview unavailable",
|
|
221
|
+
"viewer.error.parserMissingBody": "Previewing {name} needs {packages} to be installed.",
|
|
222
|
+
"viewer.error.readFailed": "Couldn't open this file",
|
|
223
|
+
"viewer.error.readFailedBody":
|
|
224
|
+
"{name} couldn't be read. It may have moved, or you may not have access.",
|
|
225
|
+
"viewer.error.parseFailed": "Couldn't read this file",
|
|
226
|
+
"viewer.error.parseFailedBody": "{name} isn't a valid {format} file, or it's damaged.",
|
|
227
|
+
"viewer.error.imageFailedTitle": "Couldn't show this image",
|
|
228
|
+
"viewer.error.imageFailed": "{name} could not be displayed.",
|
|
229
|
+
"viewer.retry": "Try again",
|
|
230
|
+
// Table view (CSV/TSV). The count is a summary for the whole grid.
|
|
231
|
+
"viewer.table.caption": "{rows} rows, {columns} columns",
|
|
232
|
+
"viewer.table.truncated": "Showing the first {count} rows.",
|
|
233
|
+
"viewer.table.emptyCell": "Empty",
|
|
234
|
+
// Text and JSON views.
|
|
235
|
+
"viewer.text.truncated": "Showing the first {shown} of {total} characters.",
|
|
236
|
+
"viewer.json.tree": "JSON structure",
|
|
237
|
+
// Page, scale and rotation chrome (ADR 0026). Format-agnostic: one pager
|
|
238
|
+
// serves PDF pages and PowerPoint slides, so the words are about "pages" and
|
|
239
|
+
// the slide-specific wording lives on the slide itself.
|
|
240
|
+
"viewer.pager.controls": "Pages",
|
|
241
|
+
"viewer.pager.previous": "Previous page",
|
|
242
|
+
"viewer.pager.next": "Next page",
|
|
243
|
+
// The field's own accessible name. The visible "of {total}" beside it is not
|
|
244
|
+
// a label — a screen reader would read the two as one run without this.
|
|
245
|
+
"viewer.pager.pageNumber": "Page number",
|
|
246
|
+
"viewer.pager.of": "of {total}",
|
|
247
|
+
// Live region: a repainted canvas and a changed input value both announce
|
|
248
|
+
// nothing, so this sentence is the only confirmation a page turned.
|
|
249
|
+
"viewer.pager.status": "Page {page} of {total}",
|
|
250
|
+
"viewer.zoom.controls": "Zoom",
|
|
251
|
+
"viewer.zoom.in": "Zoom in",
|
|
252
|
+
"viewer.zoom.out": "Zoom out",
|
|
253
|
+
"viewer.zoom.level": "Zoom level",
|
|
254
|
+
"viewer.zoom.fitWidth": "Fit width",
|
|
255
|
+
"viewer.zoom.fitPage": "Fit page",
|
|
256
|
+
"viewer.zoom.status": "Zoom {level}",
|
|
257
|
+
"viewer.rotate": "Rotate clockwise",
|
|
258
|
+
// PDF view.
|
|
259
|
+
"viewer.pdf.page": "Page {page}",
|
|
260
|
+
"viewer.pdf.pages": "Document pages",
|
|
261
|
+
"viewer.pdf.pageFailed": "Page {page} couldn't be drawn.",
|
|
262
|
+
// Media view (video / audio).
|
|
263
|
+
"viewer.media.label": "{name} player",
|
|
264
|
+
"viewer.media.unsupportedTitle": "Can't play this file",
|
|
265
|
+
"viewer.media.unsupported": "This browser can't play {name}.",
|
|
266
|
+
// Office views. A preview shows a document's STRUCTURE, not Word's or
|
|
267
|
+
// PowerPoint's page layout — the copy never promises a faithful reproduction.
|
|
268
|
+
"viewer.docx.empty": "This document has no text",
|
|
269
|
+
"viewer.sheet.tabs": "Sheets",
|
|
270
|
+
"viewer.pptx.slide": "Slide {slide}",
|
|
271
|
+
"viewer.pptx.untitled": "Untitled slide",
|
|
272
|
+
"viewer.pptx.notes": "Speaker notes",
|
|
273
|
+
"viewer.pptx.empty": "This slide has no text",
|
|
274
|
+
// Code and markdown views. `file.empty` is shared: "the file opened and there
|
|
275
|
+
// is nothing in it" is one message whatever the format.
|
|
276
|
+
"viewer.file.empty": "This file is empty",
|
|
277
|
+
// Find-in-document (ADR 0025). The count is a live region read on every step,
|
|
278
|
+
// so it stays short; `findNone` replaces it rather than sitting beside it, so
|
|
279
|
+
// a fruitless search never reads as "0 of 0".
|
|
280
|
+
"viewer.find.open": "Find in document",
|
|
281
|
+
"viewer.find.label": "Find in document",
|
|
282
|
+
"viewer.find.placeholder": "Find…",
|
|
283
|
+
"viewer.find.close": "Close find",
|
|
284
|
+
"viewer.find.previous": "Previous match",
|
|
285
|
+
"viewer.find.next": "Next match",
|
|
286
|
+
"viewer.find.caseSensitive": "Match case",
|
|
287
|
+
"viewer.find.count": "{index} of {total}",
|
|
288
|
+
"viewer.find.none": "No matches",
|
|
289
|
+
"viewer.find.capped": "Showing the first {limit} matches of {total}.",
|
|
290
|
+
// Citations / passages the app points the viewer at. A miss is a STATE, not a
|
|
291
|
+
// silent no-op — the reader is told the passage could not be located, and
|
|
292
|
+
// "past the part we previewed" is different news from "not in this document".
|
|
293
|
+
"viewer.highlight.notFound": "Couldn't find that passage in this document.",
|
|
294
|
+
"viewer.highlight.notFoundTruncated":
|
|
295
|
+
"That passage may be beyond the part of this document we could preview.",
|
|
296
|
+
"viewer.highlight.unsupported": "This build can't point at part of a {format} file.",
|
|
297
|
+
"viewer.highlight.previous": "Previous passage",
|
|
298
|
+
"viewer.highlight.next": "Next passage",
|
|
299
|
+
"viewer.highlight.count": "Passage {index} of {total}",
|
|
300
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Button } from "../button";
|
|
4
|
+
import { MatchHighlight, queryToRanges } from "./match-highlight";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Typography/MatchHighlight",
|
|
8
|
+
component: MatchHighlight,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
parameters: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
"Wraps query matches inside a string in real `<mark>` elements, styled with the semantic `--highlight` token pair (AA-legible ink in every theme). Presentation-only — pass `query` (case-insensitive by default) or precomputed `ranges` from your own fuzzy matcher.",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
argTypes: {
|
|
19
|
+
text: { control: "text" },
|
|
20
|
+
query: { control: "text" },
|
|
21
|
+
caseSensitive: { control: "boolean" },
|
|
22
|
+
activeIndex: { control: "number" },
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof MatchHighlight>;
|
|
25
|
+
export default meta;
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: { text: "My Notes note", query: "note" },
|
|
30
|
+
render: (args) => (
|
|
31
|
+
<p className="max-w-md text-body text-foreground">
|
|
32
|
+
<MatchHighlight {...args} />
|
|
33
|
+
</p>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** A search-result title inside a paragraph — the canonical omnisearch use. */
|
|
38
|
+
export const InParagraph: Story = {
|
|
39
|
+
render: () => (
|
|
40
|
+
<p className="max-w-md text-body text-foreground">
|
|
41
|
+
The <MatchHighlight text="quarterly revenue report" query="revenue" /> was updated after the{" "}
|
|
42
|
+
<MatchHighlight text="board meeting" query="board" /> concluded.
|
|
43
|
+
</p>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Multiple needles (e.g. every token of a multi-word query). */
|
|
48
|
+
export const MultipleNeedles: Story = {
|
|
49
|
+
args: { text: "red green blue amber", query: ["red", "blue"] },
|
|
50
|
+
render: (args) => (
|
|
51
|
+
<p className="text-body text-foreground">
|
|
52
|
+
<MatchHighlight {...args} />
|
|
53
|
+
</p>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Precomputed ranges from a fuzzy matcher (overlapping/adjacent are merged). */
|
|
58
|
+
export const PrecomputedRanges: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<p className="text-body text-foreground">
|
|
61
|
+
<MatchHighlight
|
|
62
|
+
text="src/features/document/backlinks-panel.tsx"
|
|
63
|
+
ranges={[
|
|
64
|
+
[4, 12],
|
|
65
|
+
[13, 21],
|
|
66
|
+
]}
|
|
67
|
+
/>
|
|
68
|
+
</p>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `activeIndex` marks ONE of the matches as the current one — the "3 of 12" a
|
|
74
|
+
* find bar or a document citation is pointing at. It is never colour-only: the
|
|
75
|
+
* current mark also carries an outline and `aria-current="true"`, and it can be
|
|
76
|
+
* found for scrolling via `[data-slot="match-highlight-mark"][data-active]`.
|
|
77
|
+
*/
|
|
78
|
+
export const CurrentMatch: Story = {
|
|
79
|
+
args: {
|
|
80
|
+
text: "Revenue rose in Q1. Revenue fell in Q2. Revenue held flat in Q3.",
|
|
81
|
+
query: "revenue",
|
|
82
|
+
activeIndex: 1,
|
|
83
|
+
},
|
|
84
|
+
render: (args) => (
|
|
85
|
+
<p className="max-w-md text-body text-foreground">
|
|
86
|
+
<MatchHighlight {...args} />
|
|
87
|
+
</p>
|
|
88
|
+
),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Stepping through matches — what a find bar drives. */
|
|
92
|
+
export const SteppingThroughMatches: Story = {
|
|
93
|
+
args: { text: "", query: "" },
|
|
94
|
+
render: function SteppingThroughMatches() {
|
|
95
|
+
const text =
|
|
96
|
+
"The report notes a delay, the delay was escalated, and the delay is now resolved.";
|
|
97
|
+
const total = queryToRanges(text, "delay").length;
|
|
98
|
+
const [index, setIndex] = useState(0);
|
|
99
|
+
const step = (by: number) => setIndex((i) => (i + by + total) % total);
|
|
100
|
+
return (
|
|
101
|
+
<div className="max-w-md space-y-3">
|
|
102
|
+
<p className="text-body text-foreground">
|
|
103
|
+
<MatchHighlight text={text} query="delay" activeIndex={index} />
|
|
104
|
+
</p>
|
|
105
|
+
<div className="flex items-center gap-2">
|
|
106
|
+
<Button size="sm" variant="outline" onClick={() => step(-1)}>
|
|
107
|
+
Previous
|
|
108
|
+
</Button>
|
|
109
|
+
<Button size="sm" variant="outline" onClick={() => step(1)}>
|
|
110
|
+
Next
|
|
111
|
+
</Button>
|
|
112
|
+
<span aria-live="polite" className="text-meta text-muted-foreground">
|
|
113
|
+
{index + 1} of {total}
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** No match → the string renders normally (never broken markup). */
|
|
122
|
+
export const NoMatch: Story = {
|
|
123
|
+
args: { text: "nothing to highlight here", query: "zzz" },
|
|
124
|
+
render: (args) => (
|
|
125
|
+
<p className="text-body text-foreground">
|
|
126
|
+
<MatchHighlight {...args} />
|
|
127
|
+
</p>
|
|
128
|
+
),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Long content truncates cleanly. The flex child needs `min-w-0` for `truncate`
|
|
133
|
+
* to engage (the interaction-guidelines "silent culprit").
|
|
134
|
+
*/
|
|
135
|
+
export const LongStringTruncation: Story = {
|
|
136
|
+
render: () => (
|
|
137
|
+
<div className="flex w-64 items-center gap-2 rounded-md border border-border bg-card p-2">
|
|
138
|
+
<span className="min-w-0 flex-1 truncate text-body text-foreground">
|
|
139
|
+
<MatchHighlight
|
|
140
|
+
text="A very long document title that will definitely overflow its container and must ellipsis"
|
|
141
|
+
query="document"
|
|
142
|
+
/>
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
),
|
|
146
|
+
};
|