@elabs-ai/components-ui 4.0.0 → 4.2.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/README.md +10 -8
- package/dist/chunk-FCH4ZN6G.js +57 -0
- package/dist/chunk-FCH4ZN6G.js.map +1 -0
- package/dist/form.d.ts +28 -0
- package/dist/form.js +123 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +2597 -188
- package/dist/index.js +12855 -3578
- package/dist/index.js.map +1 -1
- package/dist/lib/cn.js +10 -1
- package/dist/lib/cn.js.map +1 -1
- package/package.json +21 -7
- package/src/__contract__/attribution-panel.contract.test.tsx +49 -0
- package/src/__contract__/badge.contract.test.tsx +49 -0
- package/src/__contract__/bounded-number.contract.test.tsx +49 -0
- package/src/__contract__/button.contract.test.tsx +49 -0
- package/src/__contract__/color-picker.contract.test.tsx +49 -0
- package/src/__contract__/combobox.contract.test.tsx +49 -0
- package/src/__contract__/command-trigger.contract.test.tsx +49 -0
- package/src/__contract__/copyable-value.contract.test.tsx +49 -0
- package/src/__contract__/date-picker.contract.test.tsx +49 -0
- package/src/__contract__/date-range-picker.contract.test.tsx +49 -0
- package/src/__contract__/empty-state.contract.test.tsx +49 -0
- package/src/__contract__/error-state.contract.test.tsx +49 -0
- package/src/__contract__/icon-button.contract.test.tsx +49 -0
- package/src/__contract__/input.contract.test.tsx +49 -0
- package/src/__contract__/key-value-editor.contract.test.tsx +49 -0
- package/src/__contract__/list-editor.contract.test.tsx +49 -0
- package/src/__contract__/loading-state.contract.test.tsx +49 -0
- package/src/__contract__/metric-card.contract.test.tsx +49 -0
- package/src/__contract__/model-picker.contract.test.tsx +49 -0
- package/src/__contract__/nav-notifications.contract.test.tsx +49 -0
- package/src/__contract__/number-input.contract.test.tsx +49 -0
- package/src/__contract__/rating.contract.test.tsx +49 -0
- package/src/__contract__/schema-form.contract.test.tsx +49 -0
- package/src/__contract__/section-header.contract.test.tsx +49 -0
- package/src/__contract__/segmented-field.contract.test.tsx +49 -0
- package/src/__contract__/slider-number.contract.test.tsx +49 -0
- package/src/__contract__/status-badge.contract.test.tsx +49 -0
- package/src/__contract__/tag-input.contract.test.tsx +49 -0
- package/src/__contract__/text.contract.test.tsx +49 -0
- package/src/__contract__/textarea.contract.test.tsx +49 -0
- package/src/__contract__/timeline.contract.test.tsx +49 -0
- package/src/__contract__/toggle.contract.test.tsx +49 -0
- package/src/__contract__/transfer.contract.test.tsx +49 -0
- package/src/__contract__/tree-select.contract.test.tsx +49 -0
- package/src/__contract__/tree.contract.test.tsx +49 -0
- package/src/__contract__/virtual-select.contract.test.tsx +49 -0
- package/src/__contract__/workspace-picker.contract.test.tsx +49 -0
- package/src/_peer-deps.test.ts +59 -0
- package/src/blocks-comparison-table.stories.tsx +2 -2
- package/src/components/accordion/accordion.tsx +13 -4
- package/src/components/advanced-group/advanced-group.tsx +1 -1
- package/src/components/alert/alert.tsx +15 -4
- package/src/components/alert-dialog/alert-dialog.test.tsx +32 -0
- package/src/components/alert-dialog/alert-dialog.tsx +28 -6
- package/src/components/app-shell/app-shell.stories.tsx +44 -2
- package/src/components/app-shell/app-shell.test.tsx +99 -0
- package/src/components/app-shell/app-shell.tsx +56 -4
- package/src/components/app-sidebar/app-sidebar.stories.tsx +9 -1
- package/src/components/attribution-panel/attribution-panel.tsx +3 -1
- package/src/components/attribution-panel/attributions.generated.ts +83 -79
- package/src/components/avatar/avatar.tsx +4 -1
- package/src/components/badge/badge.stories.tsx +8 -0
- package/src/components/badge/badge.test.tsx +8 -0
- package/src/components/badge/badge.tsx +15 -5
- package/src/components/bento-grid/bento-grid.test.tsx +24 -3
- package/src/components/bento-grid/bento-grid.tsx +11 -1
- package/src/components/bounded-number/bounded-number.test.tsx +1 -1
- package/src/components/bounded-number/bounded-number.tsx +78 -75
- package/src/components/breadcrumb/breadcrumb.stories.tsx +38 -0
- package/src/components/breadcrumb/breadcrumb.test.tsx +24 -1
- package/src/components/breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/button/button.stories.tsx +116 -8
- package/src/components/button/button.tsx +18 -12
- package/src/components/button-group/button-group.tsx +1 -1
- package/src/components/calendar/calendar.tsx +3 -2
- package/src/components/card/card.stories.tsx +11 -6
- package/src/components/card/card.test.tsx +24 -2
- package/src/components/card/card.tsx +48 -7
- package/src/components/carousel/carousel.test.tsx +169 -0
- package/src/components/carousel/carousel.tsx +93 -32
- package/src/components/change-review/change-review.stories.tsx +145 -1
- package/src/components/change-review/change-review.test.tsx +143 -0
- package/src/components/change-review/change-review.tsx +214 -24
- package/src/components/checkbox/checkbox.tsx +2 -1
- package/src/components/collapsible-panel/use-collapsible-panel.ts +0 -3
- package/src/components/color-picker/color-picker.tsx +22 -15
- package/src/components/combobox/combobox.stories.tsx +10 -0
- package/src/components/combobox/combobox.test.tsx +54 -0
- package/src/components/combobox/combobox.tsx +25 -11
- package/src/components/command/command.stories.tsx +16 -6
- package/src/components/command/command.test.tsx +88 -0
- package/src/components/command/command.tsx +38 -13
- package/src/components/command/index.ts +1 -0
- package/src/components/command-trigger/command-trigger.stories.tsx +39 -0
- package/src/components/command-trigger/command-trigger.test.tsx +18 -0
- package/src/components/command-trigger/command-trigger.tsx +70 -0
- package/src/components/command-trigger/index.ts +1 -0
- package/src/components/context-menu/context-menu.tsx +16 -7
- package/src/components/context-rail/context-rail.stories.tsx +285 -0
- package/src/components/context-rail/context-rail.test.tsx +263 -0
- package/src/components/context-rail/context-rail.tsx +678 -0
- package/src/components/context-rail/index.ts +1 -0
- package/src/components/copyable-value/copyable-value.stories.tsx +10 -0
- package/src/components/copyable-value/copyable-value.test.tsx +28 -1
- package/src/components/copyable-value/copyable-value.tsx +13 -2
- package/src/components/date-picker/date-picker.stories.tsx +9 -0
- package/src/components/date-picker/date-picker.test.tsx +52 -0
- package/src/components/date-picker/date-picker.tsx +13 -15
- package/src/components/date-range-picker/date-range-picker.stories.tsx +9 -0
- package/src/components/date-range-picker/date-range-picker.test.tsx +76 -1
- package/src/components/date-range-picker/date-range-picker.tsx +39 -28
- package/src/components/descriptions/descriptions.tsx +1 -1
- package/src/components/dialog/dialog.test.tsx +8 -3
- package/src/components/dialog/dialog.tsx +14 -6
- package/src/components/drawer/drawer.tsx +23 -6
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +4 -4
- package/src/components/empty-state/empty-state.test.tsx +26 -0
- package/src/components/empty-state/empty-state.tsx +6 -1
- package/src/components/expand-dialog/expand-dialog.tsx +1 -2
- package/src/components/field/field-context.ts +59 -0
- package/src/components/field/field.stories.tsx +142 -0
- package/src/components/field/field.test.tsx +415 -0
- package/src/components/field/field.tsx +342 -0
- package/src/components/field/index.ts +9 -0
- package/src/components/field-row/field-row.stories.tsx +85 -1
- package/src/components/field-row/field-row.test.tsx +145 -0
- package/src/components/field-row/field-row.tsx +50 -6
- package/src/components/file-upload/file-upload.stories.tsx +16 -0
- package/src/components/file-upload/file-upload.test.tsx +90 -12
- package/src/components/file-upload/file-upload.tsx +164 -31
- package/src/components/form/form.tsx +8 -3
- package/src/components/hover-card/hover-card.tsx +1 -0
- package/src/components/icon-button/icon-button.test.tsx +1 -1
- package/src/components/icon-button/icon-button.tsx +3 -3
- package/src/components/input/input.stories.tsx +121 -0
- package/src/components/input/input.test.tsx +13 -0
- package/src/components/input/input.tsx +8 -4
- package/src/components/input-group/input-group.stories.tsx +74 -0
- package/src/components/input-group/input-group.tsx +18 -9
- package/src/components/input-otp/input-otp.tsx +8 -2
- package/src/components/keyboard-shortcuts/index.ts +6 -0
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.stories.tsx +177 -0
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.test.tsx +73 -0
- package/src/components/keyboard-shortcuts/keyboard-shortcuts.tsx +203 -0
- package/src/components/label/label.tsx +1 -1
- package/src/components/link-preview/link-preview.tsx +1 -1
- package/src/components/list-editor/list-editor.test.tsx +24 -0
- package/src/components/list-editor/list-editor.tsx +126 -52
- package/src/components/locale-provider/index.ts +6 -1
- package/src/components/locale-provider/locale-keys.test.ts +134 -0
- package/src/components/locale-provider/locale-provider.stories.tsx +89 -1
- package/src/components/locale-provider/locale-provider.test.tsx +257 -1
- package/src/components/locale-provider/locale-provider.tsx +63 -8
- package/src/components/locale-provider/messages.ts +822 -2
- package/src/components/match-highlight/match-highlight.stories.tsx +1 -1
- package/src/components/mention-input/mention-input-mirror.stories.tsx +9 -1
- package/src/components/mention-input/mention-input.stories.tsx +3 -3
- package/src/components/mention-input/mention-input.test.tsx +51 -1
- package/src/components/mention-input/mention-input.tsx +66 -46
- package/src/components/mention-input/mention-value.ts +8 -2
- package/src/components/menubar/menubar.tsx +16 -6
- package/src/components/metric-card/metric-card.stories.tsx +5 -3
- package/src/components/metric-card/metric-card.tsx +21 -2
- package/src/components/model-picker/model-picker.stories.tsx +63 -3
- package/src/components/model-picker/model-picker.test.tsx +63 -0
- package/src/components/model-picker/model-picker.tsx +62 -36
- package/src/components/nav-main/nav-main.tsx +8 -4
- package/src/components/nav-notifications/nav-notifications.stories.tsx +110 -0
- package/src/components/nav-notifications/nav-notifications.test.tsx +11 -0
- package/src/components/nav-notifications/nav-notifications.tsx +67 -9
- package/src/components/nav-user/nav-user.tsx +5 -5
- package/src/components/navigation-menu/navigation-menu.stories.tsx +22 -0
- package/src/components/navigation-menu/navigation-menu.test.tsx +174 -0
- package/src/components/navigation-menu/navigation-menu.tsx +204 -15
- package/src/components/number-input/number-input.stories.tsx +7 -3
- package/src/components/number-input/number-input.test.tsx +47 -0
- package/src/components/number-input/number-input.tsx +65 -24
- package/src/components/page-shell/page-shell.stories.tsx +163 -0
- package/src/components/page-shell/page-shell.test.tsx +229 -0
- package/src/components/page-shell/page-shell.tsx +108 -25
- package/src/components/pagination/pagination.test.tsx +109 -0
- package/src/components/pagination/pagination.tsx +27 -19
- package/src/components/popover/popover.tsx +1 -0
- package/src/components/progress/progress.tsx +2 -0
- package/src/components/radio-group/radio-group.tsx +1 -1
- package/src/components/rating/rating.tsx +3 -1
- package/src/components/resizable/resizable.stories.tsx +114 -2
- package/src/components/resizable/resizable.test.tsx +76 -0
- package/src/components/resizable/resizable.tsx +32 -6
- package/src/components/revision-timeline/revision-timeline.stories.tsx +8 -1
- package/src/components/revision-timeline/revision-timeline.test.tsx +26 -0
- package/src/components/revision-timeline/revision-timeline.tsx +53 -5
- package/src/components/schema-form/from-json-schema.test.ts +390 -0
- package/src/components/schema-form/from-json-schema.ts +375 -0
- package/src/components/schema-form/index.ts +81 -0
- package/src/components/schema-form/schema-form-spec.ts +698 -0
- package/src/components/schema-form/schema-form-store.ts +506 -0
- package/src/components/schema-form/schema-form.stories.tsx +566 -0
- package/src/components/schema-form/schema-form.test.tsx +1021 -0
- package/src/components/schema-form/schema-form.tsx +1381 -0
- package/src/components/section-header/section-header.stories.tsx +37 -0
- package/src/components/section-header/section-header.tsx +14 -3
- package/src/components/select/select.tsx +13 -7
- package/src/components/separator/separator.tsx +1 -0
- package/src/components/sheet/sheet.test.tsx +39 -0
- package/src/components/sheet/sheet.tsx +40 -9
- package/src/components/side-dock/index.ts +1 -0
- package/src/components/side-dock/side-dock.stories.tsx +137 -0
- package/src/components/side-dock/side-dock.test.tsx +299 -0
- package/src/components/side-dock/side-dock.tsx +515 -0
- package/src/components/sidebar/index.ts +1 -0
- package/src/components/sidebar/sidebar-frame.test.tsx +162 -0
- package/src/components/sidebar/sidebar.stories.tsx +421 -1
- package/src/components/sidebar/sidebar.test.tsx +71 -2
- package/src/components/sidebar/sidebar.tsx +264 -38
- package/src/components/skeleton/skeleton.tsx +1 -0
- package/src/components/skip-link/index.ts +1 -0
- package/src/components/skip-link/skip-link.stories.tsx +106 -0
- package/src/components/skip-link/skip-link.test.tsx +20 -0
- package/src/components/skip-link/skip-link.tsx +34 -0
- package/src/components/slider/slider.tsx +1 -1
- package/src/components/slider-number/slider-number.stories.tsx +4 -1
- package/src/components/slider-number/slider-number.test.tsx +1 -1
- package/src/components/sonner/sonner.tsx +2 -0
- package/src/components/spinner/spinner.test.tsx +18 -0
- package/src/components/spinner/spinner.tsx +14 -4
- package/src/components/split-panel/index.ts +6 -1
- package/src/components/split-panel/split-panel.stories.tsx +18 -2
- package/src/components/split-panel/split-panel.test.tsx +20 -0
- package/src/components/split-panel/split-panel.tsx +28 -8
- package/src/components/state-panel/state-panel.stories.tsx +52 -0
- package/src/components/state-panel/state-panel.test.tsx +125 -0
- package/src/components/state-panel/state-panel.tsx +110 -24
- package/src/components/status-badge/status-badge.stories.tsx +8 -0
- package/src/components/switch/switch.tsx +3 -1
- package/src/components/table/table.stories.tsx +64 -0
- package/src/components/table/table.test.tsx +149 -1
- package/src/components/table/table.tsx +71 -2
- package/src/components/tabs/index.ts +10 -1
- package/src/components/tabs/tabs.stories.tsx +48 -0
- package/src/components/tabs/tabs.test.tsx +67 -0
- package/src/components/tabs/tabs.tsx +176 -72
- package/src/components/tag-input/tag-input.tsx +6 -3
- package/src/components/team-switcher/team-switcher.tsx +35 -6
- package/src/components/textarea/textarea.tsx +2 -2
- package/src/components/theme-switcher/theme-switcher.stories.tsx +76 -0
- package/src/components/theme-switcher/theme-switcher.test.tsx +182 -0
- package/src/components/theme-switcher/theme-switcher.tsx +140 -12
- package/src/components/theme-switcher/use-theme-transition.ts +2 -2
- package/src/components/timeline/timeline.stories.tsx +27 -0
- package/src/components/timeline/timeline.tsx +10 -3
- package/src/components/toggle/toggle.tsx +7 -2
- package/src/components/toggle-group/toggle-group.tsx +2 -0
- package/src/components/toolbar/toolbar.stories.tsx +10 -2
- package/src/components/tooltip/tooltip.tsx +2 -1
- package/src/components/top-nav/top-nav.tsx +1 -1
- package/src/components/transfer/transfer.tsx +35 -30
- package/src/components/tree/tree.stories.tsx +2 -2
- package/src/components/tree/tree.test.tsx +41 -0
- package/src/components/tree/tree.tsx +91 -31
- package/src/components/tree/use-tree-keyboard.ts +37 -14
- package/src/components/tree-select/tree-select.stories.tsx +9 -0
- package/src/components/tree-select/tree-select.test.tsx +5 -0
- package/src/components/tree-select/tree-select.tsx +11 -14
- package/src/components/typography/prose.test.tsx +3 -2
- package/src/components/typography/prose.tsx +3 -3
- package/src/components/typography/typography.stories.tsx +228 -0
- package/src/components/view-toolbar/view-toolbar.stories.tsx +41 -1
- package/src/components/view-toolbar/view-toolbar.test.tsx +33 -0
- package/src/components/view-toolbar/view-toolbar.tsx +37 -4
- package/src/components/virtual-select/virtual-select.stories.tsx +9 -0
- package/src/components/virtual-select/virtual-select.test.tsx +29 -3
- package/src/components/virtual-select/virtual-select.tsx +56 -18
- package/src/components/wizard/wizard.test.tsx +9 -0
- package/src/components/wizard/wizard.tsx +13 -5
- package/src/components/workspace-picker/index.ts +3 -0
- package/src/components/workspace-picker/workspace-picker-state.ts +50 -0
- package/src/components/workspace-picker/workspace-picker.stories.tsx +126 -0
- package/src/components/workspace-picker/workspace-picker.test.tsx +207 -0
- package/src/components/workspace-picker/workspace-picker.tsx +172 -0
- package/src/illustrations/empty-list-illustration.tsx +26 -0
- package/src/illustrations/error-illustration.tsx +60 -0
- package/src/illustrations/first-run-illustration.tsx +27 -0
- package/src/illustrations/illustration-base.tsx +108 -0
- package/src/illustrations/illustrations.stories.tsx +94 -0
- package/src/illustrations/illustrations.test.tsx +98 -0
- package/src/illustrations/index.ts +16 -0
- package/src/illustrations/no-access-illustration.tsx +45 -0
- package/src/illustrations/no-results-illustration.tsx +46 -0
- package/src/illustrations/offline-illustration.tsx +37 -0
- package/src/illustrations/success-illustration.tsx +40 -0
- package/src/index.ts +80 -2
- package/src/lib/agent-event-model.test.ts +12 -0
- package/src/lib/agent-event-model.ts +42 -0
- package/src/lib/approval-option.test.ts +17 -0
- package/src/lib/approval-option.ts +53 -0
- package/src/lib/check-result.ts +33 -0
- package/src/lib/cn.ts +9 -0
- package/src/lib/csv.ts +48 -0
- package/src/lib/diff-line.test.ts +36 -0
- package/src/lib/diff-line.ts +63 -0
- package/src/lib/diff-rows.test.ts +76 -0
- package/src/lib/diff-rows.ts +94 -0
- package/src/lib/format-duration.test.ts +22 -0
- package/src/lib/format-duration.ts +30 -0
- package/src/lib/has-renderable-content.test.ts +49 -0
- package/src/lib/has-renderable-content.ts +32 -0
- package/src/lib/merge-refs.ts +2 -0
- package/src/lib/operating-mode.test.ts +36 -0
- package/src/lib/operating-mode.ts +81 -0
- package/src/lib/optional-peer.ts +59 -0
- package/src/lib/session-launch.ts +37 -0
- package/src/lib/slash-command.test.ts +44 -0
- package/src/lib/slash-command.ts +45 -0
- package/src/lib/trigger-query.test.ts +94 -0
- package/src/lib/trigger-query.ts +88 -0
- package/src/lib/use-controllable-state.ts +47 -0
- package/src/lib/use-mobile.test.tsx +47 -0
- package/src/lib/use-mobile.ts +34 -12
- package/src/motion.stories.tsx +6 -0
- package/src/templates-object-detail-hub.stories.tsx +2 -2
- package/src/templates-screen-states.stories.tsx +5 -5
- package/src/templates-settings.stories.tsx +1 -1
- package/src/blocks/sidebar-02/app-sidebar.tsx +0 -160
- package/src/blocks/sidebar-02/logo.tsx +0 -18
- package/src/blocks/sidebar-02/nav-main.tsx +0 -13
- package/src/blocks/sidebar-02/nav-notifications.tsx +0 -7
- package/src/blocks/sidebar-02/sidebar-02.stories.tsx +0 -23
- package/src/blocks/sidebar-02/team-switcher.tsx +0 -7
- package/src/blocks/sidebar-04/app-sidebar.tsx +0 -236
- package/src/blocks/sidebar-04/mail-context.tsx +0 -29
- package/src/blocks/sidebar-04/nav-user.tsx +0 -6
- package/src/blocks/sidebar-04/sidebar-04.stories.tsx +0 -150
- package/src/blocks/sidebar-05/app-sidebar.tsx +0 -375
- package/src/blocks/sidebar-05/sidebar-05.stories.tsx +0 -18
- package/src/blocks/sidebar-05/team-switcher.tsx +0 -6
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { findTriggerQuery, replaceTriggerRun } from "./trigger-query";
|
|
3
|
+
|
|
4
|
+
describe("findTriggerQuery — word boundary (default)", () => {
|
|
5
|
+
it("finds a trigger + query run under the caret", () => {
|
|
6
|
+
const text = "Hi @ada";
|
|
7
|
+
expect(findTriggerQuery(text, text.length, "@")).toEqual({ start: 3, query: "ada" });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("returns null when there is no trigger before the caret", () => {
|
|
11
|
+
expect(findTriggerQuery("hello", 5, "@")).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns null when the run contains whitespace (the query already closed)", () => {
|
|
15
|
+
const text = "Hi @ada lovelace";
|
|
16
|
+
expect(findTriggerQuery(text, text.length, "@")).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns null when the trigger is not at a word boundary (inside a token)", () => {
|
|
20
|
+
const text = "ping ada@example.com";
|
|
21
|
+
const at = text.indexOf("@");
|
|
22
|
+
expect(findTriggerQuery(text, at + 1, "@")).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("allows the trigger at index 0", () => {
|
|
26
|
+
expect(findTriggerQuery("@ada", 4, "@")).toEqual({ start: 0, query: "ada" });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("allows the trigger right after '(' or '['", () => {
|
|
30
|
+
expect(findTriggerQuery("see (@ada", 9, "@")).toEqual({ start: 5, query: "ada" });
|
|
31
|
+
expect(findTriggerQuery("see [@ada", 9, "@")).toEqual({ start: 5, query: "ada" });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("supports a multi-character trigger", () => {
|
|
35
|
+
const text = "run ::deploy";
|
|
36
|
+
expect(findTriggerQuery(text, text.length, "::")).toEqual({ start: 4, query: "deploy" });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("findTriggerQuery — line-start boundary", () => {
|
|
41
|
+
it("opens a query at the start of a line", () => {
|
|
42
|
+
const text = "/help";
|
|
43
|
+
expect(findTriggerQuery(text, text.length, "/", { boundary: "line-start" })).toEqual({
|
|
44
|
+
start: 0,
|
|
45
|
+
query: "help",
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("opens a query right after a newline", () => {
|
|
50
|
+
const text = "first line\n/help";
|
|
51
|
+
expect(findTriggerQuery(text, text.length, "/", { boundary: "line-start" })).toEqual({
|
|
52
|
+
start: 11,
|
|
53
|
+
query: "help",
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("does not open mid-line, even at a word boundary the 'word' boundary would accept", () => {
|
|
58
|
+
const text = "cd /usr";
|
|
59
|
+
expect(findTriggerQuery(text, text.length, "/", { boundary: "line-start" })).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("findTriggerQuery — isTriggerConsumed veto", () => {
|
|
64
|
+
it("returns null when the candidate trigger has already been consumed", () => {
|
|
65
|
+
const text = "Hi @ada";
|
|
66
|
+
const result = findTriggerQuery(text, text.length, "@", {
|
|
67
|
+
isTriggerConsumed: (start) => start === 3,
|
|
68
|
+
});
|
|
69
|
+
expect(result).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns the candidate when isTriggerConsumed reports false", () => {
|
|
73
|
+
const text = "Hi @ada";
|
|
74
|
+
const result = findTriggerQuery(text, text.length, "@", {
|
|
75
|
+
isTriggerConsumed: () => false,
|
|
76
|
+
});
|
|
77
|
+
expect(result).toEqual({ start: 3, query: "ada" });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("replaceTriggerRun", () => {
|
|
82
|
+
it("splices the insert text over the trigger + query run", () => {
|
|
83
|
+
const text = "Hi @ada how are you";
|
|
84
|
+
const result = replaceTriggerRun(text, 3, 7, "@Ada Lovelace ");
|
|
85
|
+
expect(result.text).toBe("Hi @Ada Lovelace how are you");
|
|
86
|
+
expect(result.caret).toBe(3 + "@Ada Lovelace ".length);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("places the caret immediately after the inserted text", () => {
|
|
90
|
+
const result = replaceTriggerRun("/help", 0, 5, "/deploy ");
|
|
91
|
+
expect(result.text).toBe("/deploy ");
|
|
92
|
+
expect(result.caret).toBe("/deploy ".length);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generic, component-agnostic half of a trigger-driven inline palette —
|
|
3
|
+
* "is the caret sitting inside a `trigger + query` run, and how do I splice a
|
|
4
|
+
* chosen result back in". Zero React, zero DOM, zero knowledge of mentions.
|
|
5
|
+
*
|
|
6
|
+
* Lifted out of `MentionInput`'s private `findQuery`/`isWordBoundary`
|
|
7
|
+
* (docs/decisions/2026-09-01-brainless-adoption-architecture.md § 5) so a second
|
|
8
|
+
* trigger-driven surface (a slash-command palette) reuses the algorithm instead
|
|
9
|
+
* of re-implementing it. `MentionInput` consumes this module unchanged — see
|
|
10
|
+
* `mention-input.tsx`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Where a trigger character is allowed to START a query.
|
|
15
|
+
*
|
|
16
|
+
* `"word"` — index 0, or preceded by whitespace / "(" / "[". (`MentionInput`'s
|
|
17
|
+
* behaviour, unchanged — an "@" inside an email address must not open a popup.)
|
|
18
|
+
* `"line-start"` — index 0, or preceded by "\n". (A slash palette: "/help" opens, "cd /usr" does not.)
|
|
19
|
+
*/
|
|
20
|
+
export type TriggerBoundary = "word" | "line-start";
|
|
21
|
+
|
|
22
|
+
/** An in-progress `trigger + query` run under the caret. */
|
|
23
|
+
export interface TriggerQuery {
|
|
24
|
+
/** Offset of the trigger character in `text`. */
|
|
25
|
+
start: number;
|
|
26
|
+
/** The query WITHOUT the trigger character. */
|
|
27
|
+
query: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface FindTriggerQueryOptions {
|
|
31
|
+
/** @default "word" */
|
|
32
|
+
boundary?: TriggerBoundary;
|
|
33
|
+
/**
|
|
34
|
+
* Veto a candidate whose trigger character has already been consumed by a
|
|
35
|
+
* committed token — `MentionInput` passes `mentionAt(...)` here so an
|
|
36
|
+
* inserted "@Ada Lovelace" cannot re-open the popup. Omit when there are no
|
|
37
|
+
* committed tokens (a slash palette has none).
|
|
38
|
+
*/
|
|
39
|
+
isTriggerConsumed?: (start: number) => boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isBoundary(text: string, index: number, boundary: TriggerBoundary): boolean {
|
|
43
|
+
if (index === 0) return true;
|
|
44
|
+
const previous = text[index - 1] ?? "";
|
|
45
|
+
if (boundary === "line-start") return previous === "\n";
|
|
46
|
+
return /\s/.test(previous) || previous === "(" || previous === "[";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Is the caret sitting inside a `trigger + query` run?
|
|
51
|
+
*
|
|
52
|
+
* DERIVED from committed text + caret, never intercepted at keydown — a
|
|
53
|
+
* keydown-driven trigger must `preventDefault()` every printable character and
|
|
54
|
+
* re-insert it by hand, which destroys IME composition, native undo, spellcheck
|
|
55
|
+
* and paste. Deriving means the popup opens identically whether the trigger
|
|
56
|
+
* arrived by typing, pasting, an IME commit, or a click that moved the caret
|
|
57
|
+
* back into a half-typed query.
|
|
58
|
+
*/
|
|
59
|
+
export function findTriggerQuery(
|
|
60
|
+
text: string,
|
|
61
|
+
caret: number,
|
|
62
|
+
trigger: string,
|
|
63
|
+
options?: FindTriggerQueryOptions,
|
|
64
|
+
): TriggerQuery | null {
|
|
65
|
+
const boundary = options?.boundary ?? "word";
|
|
66
|
+
|
|
67
|
+
if (caret < trigger.length) return null;
|
|
68
|
+
const start = text.lastIndexOf(trigger, caret - trigger.length);
|
|
69
|
+
if (start < 0) return null;
|
|
70
|
+
|
|
71
|
+
const query = text.slice(start + trigger.length, caret);
|
|
72
|
+
if (/\s/.test(query)) return null;
|
|
73
|
+
if (!isBoundary(text, start, boundary)) return null;
|
|
74
|
+
if (options?.isTriggerConsumed?.(start)) return null;
|
|
75
|
+
|
|
76
|
+
return { start, query };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The plain-text half of an insert: splice `insertText` over the run, and say where the caret lands. */
|
|
80
|
+
export function replaceTriggerRun(
|
|
81
|
+
text: string,
|
|
82
|
+
queryStart: number,
|
|
83
|
+
caret: number,
|
|
84
|
+
insertText: string,
|
|
85
|
+
): { text: string; caret: number } {
|
|
86
|
+
const nextText = text.slice(0, queryStart) + insertText + text.slice(caret);
|
|
87
|
+
return { text: nextText, caret: queryStart + insertText.length };
|
|
88
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared controlled/uncontrolled state primitive.
|
|
7
|
+
*
|
|
8
|
+
* `isControlled` is fixed by whether `controlledValue` is `undefined` on the
|
|
9
|
+
* FIRST render (the usual React contract — don't flip modes across renders).
|
|
10
|
+
* The setter always does BOTH of its jobs, unconditionally:
|
|
11
|
+
* - uncontrolled: updates the internal state (so the next render reflects it)
|
|
12
|
+
* - always: calls `onChange`, so a listener attached to an otherwise
|
|
13
|
+
* uncontrolled component still observes every change.
|
|
14
|
+
*
|
|
15
|
+
* A hand-rolled `value ?? internal` + `onChange ? onChange(v) : setInternal(v)`
|
|
16
|
+
* ternary gets this wrong in two ways: an uncontrolled component with an
|
|
17
|
+
* `onChange` handler stops updating its own displayed value (the ternary
|
|
18
|
+
* only ever takes one branch), and clearing a controlled value to `undefined`
|
|
19
|
+
* falls through to a stale `internal` instead of showing "no value" — see
|
|
20
|
+
* Combobox/DatePicker (#reviewed 1.7).
|
|
21
|
+
*/
|
|
22
|
+
export function useControllableState<T>(
|
|
23
|
+
controlledValue: T | undefined,
|
|
24
|
+
defaultValue: T,
|
|
25
|
+
onChange?: (value: T) => void,
|
|
26
|
+
): [T, (next: T) => void] {
|
|
27
|
+
// Locked on mount, not recomputed every render: once a caller passes a
|
|
28
|
+
// defined `value`, the component stays controlled even if a later render
|
|
29
|
+
// clears it to `undefined` (e.g. "no date selected"). Recomputing
|
|
30
|
+
// `value !== undefined` on every render would flip a cleared controlled
|
|
31
|
+
// value to the (stale) uncontrolled `internal` state instead of showing
|
|
32
|
+
// "empty" — see DateRangePicker (#reviewed 1.7).
|
|
33
|
+
const isControlledRef = useRef(controlledValue !== undefined);
|
|
34
|
+
const isControlled = isControlledRef.current;
|
|
35
|
+
const [internal, setInternal] = useState<T>(defaultValue);
|
|
36
|
+
const value = isControlled ? (controlledValue as T) : internal;
|
|
37
|
+
|
|
38
|
+
const setValue = useCallback(
|
|
39
|
+
(next: T) => {
|
|
40
|
+
if (!isControlled) setInternal(next);
|
|
41
|
+
onChange?.(next);
|
|
42
|
+
},
|
|
43
|
+
[isControlled, onChange],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return [value, setValue];
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { useIsMobile } from "./use-mobile";
|
|
4
|
+
|
|
5
|
+
function setViewportWidth(width: number) {
|
|
6
|
+
Object.defineProperty(window, "innerWidth", {
|
|
7
|
+
writable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
value: width,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("useIsMobile", () => {
|
|
14
|
+
const originalInnerWidth = window.innerWidth;
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
setViewportWidth(originalInnerWidth);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("reports the correct value on the very first render — no desktop flash", () => {
|
|
21
|
+
setViewportWidth(500);
|
|
22
|
+
const renderedValues: boolean[] = [];
|
|
23
|
+
function Probe() {
|
|
24
|
+
const isMobile = useIsMobile();
|
|
25
|
+
renderedValues.push(isMobile);
|
|
26
|
+
return <span>{String(isMobile)}</span>;
|
|
27
|
+
}
|
|
28
|
+
render(<Probe />);
|
|
29
|
+
|
|
30
|
+
// A stale `false` in the recorded renders means the mobile viewport
|
|
31
|
+
// still rendered the desktop layout for at least one paint before the
|
|
32
|
+
// effect corrected it — i.e. a flash.
|
|
33
|
+
expect(renderedValues.length).toBeGreaterThan(0);
|
|
34
|
+
expect(renderedValues.every((value) => value === true)).toBe(true);
|
|
35
|
+
expect(screen.getByText("true")).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("reports false on a desktop-width viewport", () => {
|
|
39
|
+
setViewportWidth(1280);
|
|
40
|
+
function Probe() {
|
|
41
|
+
const isMobile = useIsMobile();
|
|
42
|
+
return <span>{String(isMobile)}</span>;
|
|
43
|
+
}
|
|
44
|
+
render(<Probe />);
|
|
45
|
+
expect(screen.getByText("false")).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
});
|
package/src/lib/use-mobile.ts
CHANGED
|
@@ -1,16 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
2
4
|
|
|
3
5
|
const MOBILE_BREAKPOINT = 768;
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
function subscribe(breakpoint: number, onStoreChange: () => void) {
|
|
8
|
+
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
9
|
+
mql.addEventListener("change", onStoreChange);
|
|
10
|
+
return () => mql.removeEventListener("change", onStoreChange);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getSnapshot(breakpoint: number) {
|
|
14
|
+
return window.innerWidth < breakpoint;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// SSR / first-paint snapshot: no viewport to measure yet, so report "not
|
|
18
|
+
// mobile" rather than guessing — this must match what the server rendered to
|
|
19
|
+
// avoid a hydration mismatch, and useSyncExternalStore re-syncs to the real
|
|
20
|
+
// value on the client before paint (no desktop-layout flash).
|
|
21
|
+
function getServerSnapshot() {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns true when the viewport is below `breakpoint` (default 768px, the
|
|
27
|
+
* mobile breakpoint). Additive optional argument (ADR 0035 §5) — every
|
|
28
|
+
* existing zero-arg call site is unchanged. A caller with its own threshold
|
|
29
|
+
* (e.g. `ContextRail`'s `overlayBreakpoint`) passes it directly instead of a
|
|
30
|
+
* second hook.
|
|
31
|
+
*/
|
|
32
|
+
export function useIsMobile(breakpoint: number = MOBILE_BREAKPOINT): boolean {
|
|
33
|
+
return useSyncExternalStore(
|
|
34
|
+
(onStoreChange) => subscribe(breakpoint, onStoreChange),
|
|
35
|
+
() => getSnapshot(breakpoint),
|
|
36
|
+
getServerSnapshot,
|
|
37
|
+
);
|
|
16
38
|
}
|
package/src/motion.stories.tsx
CHANGED
|
@@ -235,6 +235,12 @@ export const GateFloorNeverZero: Story = {
|
|
|
235
235
|
* additions, these resolve to defaults/none and this fails.
|
|
236
236
|
*/
|
|
237
237
|
export const UtilitiesGenerated: Story = {
|
|
238
|
+
// The suite runs under `prefers-reduced-motion: reduce`
|
|
239
|
+
// (`apps/docs/vitest.config.ts`, #125), which collapses the CSS gate to its
|
|
240
|
+
// floor. This assertion is ABOUT the full-motion scale, so the story opts
|
|
241
|
+
// back in explicitly — `data-motion-pref="full"` is the documented
|
|
242
|
+
// informed-consent override that beats the OS rule.
|
|
243
|
+
globals: { motionPref: "full" },
|
|
238
244
|
render: () => (
|
|
239
245
|
<div>
|
|
240
246
|
<span data-testid="util-fast" className="transition-opacity duration-fast ease-standard" />
|
|
@@ -209,7 +209,7 @@ function PipelineList({
|
|
|
209
209
|
onClick={() => onSelect(p.id)}
|
|
210
210
|
className={[
|
|
211
211
|
"flex w-full items-center gap-2 rounded-md px-2.5 py-2 text-start transition-colors",
|
|
212
|
-
"focus-
|
|
212
|
+
"focus-ring",
|
|
213
213
|
selected ? "bg-primary/10 text-foreground" : "hover:bg-surface-muted",
|
|
214
214
|
].join(" ")}
|
|
215
215
|
>
|
|
@@ -449,7 +449,7 @@ const meta = {
|
|
|
449
449
|
docs: {
|
|
450
450
|
description: {
|
|
451
451
|
component:
|
|
452
|
-
"The canonical master-detail enterprise screen: a searchable object list (SplitPanel `start`) drives a detail HUB (`end`) with a sticky SectionHeader identity + status, a ButtonGroup toolbar, Tabs for the object's sections (Overview / Activity / Settings), Descriptions for attributes, a related-runs list, and a high-value Run task promoted to its own Dialog. Selecting nothing shows a real StatePanel empty state. Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in
|
|
452
|
+
"The canonical master-detail enterprise screen: a searchable object list (SplitPanel `start`) drives a detail HUB (`end`) with a sticky SectionHeader identity + status, a ButtonGroup toolbar, Tabs for the object's sections (Overview / Activity / Settings), Descriptions for attributes, a related-runs list, and a high-value Run task promoted to its own Dialog. Selecting nothing shows a real StatePanel empty state. Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in every theme.",
|
|
453
453
|
},
|
|
454
454
|
},
|
|
455
455
|
},
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* • Error — StatePanel kind="error" with a retry action (role="alert").
|
|
15
15
|
* • FirstRun — onboarding empty: illustration slot + one sentence + one CTA.
|
|
16
16
|
*
|
|
17
|
-
* Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in
|
|
18
|
-
*
|
|
17
|
+
* Compose-only from @elabs-ai/components-* primitives; semantic tokens only; reads in
|
|
18
|
+
* every theme. Verify with globals=theme:<slug>.
|
|
19
19
|
*/
|
|
20
20
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
21
21
|
import { Inbox, Plus, RotateCcw, Search, UploadCloud, UserPlus } from "lucide-react";
|
|
@@ -210,8 +210,8 @@ function CustomersScreen({ state = "ready" }: { state?: ScreenState }) {
|
|
|
210
210
|
{state === "error" && (
|
|
211
211
|
<StatePanel
|
|
212
212
|
kind="error"
|
|
213
|
-
title="Couldn
|
|
214
|
-
description="The billing service didn
|
|
213
|
+
title="Couldn’t load customers"
|
|
214
|
+
description="The billing service didn’t respond. Your data is safe — this is a display error."
|
|
215
215
|
actions={
|
|
216
216
|
<Button variant="outline" size="sm" onClick={() => toast.success("Retrying…")}>
|
|
217
217
|
<RotateCcw className="size-4" aria-hidden="true" />
|
|
@@ -273,7 +273,7 @@ const meta = {
|
|
|
273
273
|
docs: {
|
|
274
274
|
description: {
|
|
275
275
|
component:
|
|
276
|
-
'One representative data screen (Customers) rendered across the FULL state grid — Ready, Loading, Empty, Error, FirstRun — with the same chrome and the real state primitives (Table, Skeleton, StatePanel kind="empty"|"error"). This is the design-first state grid that the Patterns/Templates examples omit. Semantic tokens only; reads in
|
|
276
|
+
'One representative data screen (Customers) rendered across the FULL state grid — Ready, Loading, Empty, Error, FirstRun — with the same chrome and the real state primitives (Table, Skeleton, StatePanel kind="empty"|"error"). This is the design-first state grid that the Patterns/Templates examples omit. Semantic tokens only; reads in every theme.',
|
|
277
277
|
},
|
|
278
278
|
},
|
|
279
279
|
},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (app-shell + Descriptions + Wizard). This story is the single source of
|
|
4
4
|
* truth: `pnpm gen:templates` derives the consumer template source
|
|
5
5
|
* (`docs/playbooks/templates/settings.tsx`) from it.
|
|
6
|
-
* Verify across
|
|
6
|
+
* Verify across every theme with globals=theme:<slug>.
|
|
7
7
|
*/
|
|
8
8
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
9
9
|
import { useState } from "react";
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/** sidebar-02 — dashboard sidebar (copy-owned block). Adapted from blocks.so
|
|
2
|
-
* (ephraim duncan). framer-motion removed (plain fade); next/link -> <a>. */
|
|
3
|
-
"use client";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
Activity,
|
|
7
|
-
DollarSign,
|
|
8
|
-
Home,
|
|
9
|
-
Infinity as InfinityIcon,
|
|
10
|
-
Link as LinkIcon,
|
|
11
|
-
Package2,
|
|
12
|
-
Percent,
|
|
13
|
-
PieChart,
|
|
14
|
-
Settings,
|
|
15
|
-
ShoppingBag,
|
|
16
|
-
Sparkles,
|
|
17
|
-
Store,
|
|
18
|
-
TrendingUp,
|
|
19
|
-
Users,
|
|
20
|
-
} from "lucide-react";
|
|
21
|
-
import { AppSidebar, SidebarTrigger, cn, useSidebar } from "@elabs-ai/components-ui";
|
|
22
|
-
import { Logo } from "./logo";
|
|
23
|
-
import DashboardNavigation, { type Route } from "./nav-main";
|
|
24
|
-
import { NotificationsPopover } from "./nav-notifications";
|
|
25
|
-
import { TeamSwitcher } from "./team-switcher";
|
|
26
|
-
|
|
27
|
-
const sampleNotifications = [
|
|
28
|
-
{
|
|
29
|
-
id: "1",
|
|
30
|
-
fallback: "OM",
|
|
31
|
-
text: "New order received.",
|
|
32
|
-
time: "10m ago",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: "2",
|
|
36
|
-
fallback: "JL",
|
|
37
|
-
text: "Server upgrade completed.",
|
|
38
|
-
time: "1h ago",
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
id: "3",
|
|
42
|
-
fallback: "HH",
|
|
43
|
-
text: "New user signed up.",
|
|
44
|
-
time: "2h ago",
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const dashboardRoutes: Route[] = [
|
|
49
|
-
{ id: "home", title: "Home", icon: <Home className="size-4" />, link: "#" },
|
|
50
|
-
{
|
|
51
|
-
id: "products",
|
|
52
|
-
title: "Products",
|
|
53
|
-
icon: <Package2 className="size-4" />,
|
|
54
|
-
link: "#",
|
|
55
|
-
subs: [
|
|
56
|
-
{ title: "Catalogue", link: "#" },
|
|
57
|
-
{ title: "Checkout Links", link: "#" },
|
|
58
|
-
{ title: "Discounts", link: "#" },
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: "usage-billing",
|
|
63
|
-
title: "Usage Billing",
|
|
64
|
-
icon: <PieChart className="size-4" />,
|
|
65
|
-
link: "#",
|
|
66
|
-
subs: [
|
|
67
|
-
{ title: "Meters", link: "#" },
|
|
68
|
-
{ title: "Events", link: "#" },
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
{ id: "benefits", title: "Benefits", icon: <Sparkles className="size-4" />, link: "#" },
|
|
72
|
-
{ id: "customers", title: "Customers", icon: <Users className="size-4" />, link: "#" },
|
|
73
|
-
{
|
|
74
|
-
id: "sales",
|
|
75
|
-
title: "Sales",
|
|
76
|
-
icon: <ShoppingBag className="size-4" />,
|
|
77
|
-
link: "#",
|
|
78
|
-
subs: [
|
|
79
|
-
{ title: "Orders", link: "#" },
|
|
80
|
-
{ title: "Subscriptions", link: "#" },
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
{ id: "storefront", title: "Storefront", icon: <Store className="size-4" />, link: "#" },
|
|
84
|
-
{ id: "analytics", title: "Analytics", icon: <TrendingUp className="size-4" />, link: "#" },
|
|
85
|
-
{
|
|
86
|
-
id: "finance",
|
|
87
|
-
title: "Finance",
|
|
88
|
-
icon: <DollarSign className="size-4" />,
|
|
89
|
-
link: "#",
|
|
90
|
-
subs: [
|
|
91
|
-
{ title: "Incoming", link: "#" },
|
|
92
|
-
{ title: "Outgoing", link: "#" },
|
|
93
|
-
{ title: "Payout Account", link: "#" },
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "settings",
|
|
98
|
-
title: "Settings",
|
|
99
|
-
icon: <Settings className="size-4" />,
|
|
100
|
-
link: "#",
|
|
101
|
-
subs: [
|
|
102
|
-
{ title: "General", link: "#" },
|
|
103
|
-
{ title: "Webhooks", link: "#" },
|
|
104
|
-
{ title: "Custom Fields", link: "#" },
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
// Reference icons retained for parity with the source data shape.
|
|
110
|
-
void Activity;
|
|
111
|
-
void InfinityIcon;
|
|
112
|
-
void LinkIcon;
|
|
113
|
-
void Percent;
|
|
114
|
-
|
|
115
|
-
const teams = [
|
|
116
|
-
{ name: "Alpha Inc.", logo: Logo, plan: "Free" },
|
|
117
|
-
{ name: "Beta Corp.", logo: Logo, plan: "Free" },
|
|
118
|
-
{ name: "Gamma Tech", logo: Logo, plan: "Free" },
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
export function DashboardSidebar() {
|
|
122
|
-
const { state } = useSidebar();
|
|
123
|
-
const isCollapsed = state === "collapsed";
|
|
124
|
-
|
|
125
|
-
const header = (
|
|
126
|
-
<div
|
|
127
|
-
className={cn(
|
|
128
|
-
"flex md:pt-3.5",
|
|
129
|
-
isCollapsed
|
|
130
|
-
? "flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start"
|
|
131
|
-
: "flex-row items-center justify-between",
|
|
132
|
-
)}
|
|
133
|
-
>
|
|
134
|
-
<a href="#" className="flex items-center gap-2">
|
|
135
|
-
<Logo className="h-8 w-8" />
|
|
136
|
-
{!isCollapsed && <span className="font-semibold text-foreground">Acme</span>}
|
|
137
|
-
</a>
|
|
138
|
-
<div
|
|
139
|
-
className={cn(
|
|
140
|
-
"flex items-center gap-2 animate-in fade-in",
|
|
141
|
-
isCollapsed ? "flex-row md:flex-col-reverse" : "flex-row",
|
|
142
|
-
)}
|
|
143
|
-
>
|
|
144
|
-
<NotificationsPopover notifications={sampleNotifications} />
|
|
145
|
-
<SidebarTrigger />
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<AppSidebar
|
|
152
|
-
variant="inset"
|
|
153
|
-
collapsible="icon"
|
|
154
|
-
header={header}
|
|
155
|
-
footer={<TeamSwitcher teams={teams} />}
|
|
156
|
-
>
|
|
157
|
-
<DashboardNavigation routes={dashboardRoutes} className="gap-4 px-2 py-4" />
|
|
158
|
-
</AppSidebar>
|
|
159
|
-
);
|
|
160
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/** sidebar-02 — Acme logo mark (copy-owned block). */
|
|
2
|
-
import type { SVGProps } from "react";
|
|
3
|
-
|
|
4
|
-
export const Logo = (props: SVGProps<SVGSVGElement>) => (
|
|
5
|
-
<svg fill="currentColor" height="48" viewBox="0 0 40 48" width="40" {...props}>
|
|
6
|
-
<clipPath id="acme-a">
|
|
7
|
-
<path d="m0 0h40v48h-40z" />
|
|
8
|
-
</clipPath>
|
|
9
|
-
<g clipPath="url(#acme-a)">
|
|
10
|
-
<path d="m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z" />
|
|
11
|
-
<path d="m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z" />
|
|
12
|
-
<path d="m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z" />
|
|
13
|
-
<path d="m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z" />
|
|
14
|
-
<path d="m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z" />
|
|
15
|
-
<path d="m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z" />
|
|
16
|
-
</g>
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sidebar-02 — primary navigation.
|
|
3
|
-
* Re-exports the shared `NavMain` primitive from `@elabs-ai/components-ui` so this block
|
|
4
|
-
* no longer maintains its own copy (issue #99).
|
|
5
|
-
*
|
|
6
|
-
* The block-local alias `DashboardNavigation` is preserved for back-compat
|
|
7
|
-
* so `app-sidebar.tsx` does not need to change its import name.
|
|
8
|
-
*/
|
|
9
|
-
export { NavMain as default, NavMain as DashboardNavigation } from "@elabs-ai/components-ui";
|
|
10
|
-
export type {
|
|
11
|
-
NavMainProps as DashboardNavigationProps,
|
|
12
|
-
NavMainRoute as Route,
|
|
13
|
-
} from "@elabs-ai/components-ui";
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sidebar-02 — notifications popover.
|
|
3
|
-
* Re-exports the shared `NavNotifications` primitive from `@elabs-ai/components-ui` so this
|
|
4
|
-
* block no longer maintains its own copy (issue #99).
|
|
5
|
-
*/
|
|
6
|
-
export { NavNotifications as NotificationsPopover } from "@elabs-ai/components-ui";
|
|
7
|
-
export type { NavNotificationsProps, NavNotification } from "@elabs-ai/components-ui";
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { SidebarInset, SidebarProvider } from "@elabs-ai/components-ui";
|
|
3
|
-
import { DashboardSidebar } from "./app-sidebar";
|
|
4
|
-
|
|
5
|
-
const meta = {
|
|
6
|
-
title: "Layout/App Shell/Dashboard",
|
|
7
|
-
parameters: { layout: "fullscreen" },
|
|
8
|
-
} satisfies Meta;
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof meta>;
|
|
11
|
-
|
|
12
|
-
export const Default: Story = {
|
|
13
|
-
render: () => (
|
|
14
|
-
<SidebarProvider>
|
|
15
|
-
<div className="relative flex h-dvh w-full">
|
|
16
|
-
<DashboardSidebar />
|
|
17
|
-
<SidebarInset className="flex flex-col p-6 text-body text-muted-foreground">
|
|
18
|
-
Dashboard content area (collapsible icon sidebar — press ⌘/Ctrl+B).
|
|
19
|
-
</SidebarInset>
|
|
20
|
-
</div>
|
|
21
|
-
</SidebarProvider>
|
|
22
|
-
),
|
|
23
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sidebar-02 — team switcher.
|
|
3
|
-
* Re-exports the shared `TeamSwitcher` primitive from `@elabs-ai/components-ui` so this block
|
|
4
|
-
* no longer maintains its own copy (issue #99).
|
|
5
|
-
*/
|
|
6
|
-
export { TeamSwitcher } from "@elabs-ai/components-ui";
|
|
7
|
-
export type { TeamSwitcherProps, TeamSwitcherTeam } from "@elabs-ai/components-ui";
|