@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,263 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { fireEvent, render, screen, within } from "@testing-library/react";
|
|
4
|
+
import { FileText, Quote } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
import { ContextRail, type ContextRailSection } from "./context-rail";
|
|
7
|
+
|
|
8
|
+
const sections: ContextRailSection[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "sources",
|
|
11
|
+
label: "Sources",
|
|
12
|
+
icon: <FileText />,
|
|
13
|
+
count: 3,
|
|
14
|
+
content: <p>Sources body</p>,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "quotes",
|
|
18
|
+
label: "Quotes",
|
|
19
|
+
icon: <Quote />,
|
|
20
|
+
content: <p>Quotes body</p>,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const ORIGINAL_INNER_WIDTH = window.innerWidth;
|
|
25
|
+
|
|
26
|
+
function setViewportWidth(width: number) {
|
|
27
|
+
Object.defineProperty(window, "innerWidth", {
|
|
28
|
+
writable: true,
|
|
29
|
+
configurable: true,
|
|
30
|
+
value: width,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("ContextRail", () => {
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
setViewportWidth(ORIGINAL_INNER_WIDTH);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("1. is reachable by exact accessible name when collapsed", () => {
|
|
40
|
+
render(<ContextRail sections={sections} open={false} />);
|
|
41
|
+
expect(screen.getByRole("button", { name: "Sources 3 items" })).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByRole("button", { name: "Quotes" })).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("2. mounts only the active section's content", () => {
|
|
46
|
+
render(<ContextRail sections={sections} activeSectionId="sources" />);
|
|
47
|
+
expect(screen.getByText("Sources body")).toBeInTheDocument();
|
|
48
|
+
expect(screen.queryByText("Quotes body")).not.toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("3. calls onActiveSectionChange with the clicked section's id", () => {
|
|
52
|
+
const onActiveSectionChange = vi.fn();
|
|
53
|
+
render(
|
|
54
|
+
<ContextRail
|
|
55
|
+
sections={sections}
|
|
56
|
+
activeSectionId="sources"
|
|
57
|
+
onActiveSectionChange={onActiveSectionChange}
|
|
58
|
+
/>,
|
|
59
|
+
);
|
|
60
|
+
fireEvent.click(screen.getByRole("button", { name: "Quotes" }));
|
|
61
|
+
expect(onActiveSectionChange).toHaveBeenCalledWith("quotes");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("4. defaults to sections[0] uncontrolled, honours defaultActiveSectionId, and updates on click with no controlled prop", () => {
|
|
65
|
+
const { unmount } = render(<ContextRail sections={sections} />);
|
|
66
|
+
expect(screen.getByText("Sources body")).toBeInTheDocument();
|
|
67
|
+
unmount();
|
|
68
|
+
|
|
69
|
+
render(<ContextRail sections={sections} defaultActiveSectionId="quotes" />);
|
|
70
|
+
expect(screen.getByText("Quotes body")).toBeInTheDocument();
|
|
71
|
+
fireEvent.click(screen.getByRole("button", { name: "Sources 3 items" }));
|
|
72
|
+
expect(screen.getByText("Sources body")).toBeInTheDocument();
|
|
73
|
+
expect(screen.queryByText("Quotes body")).not.toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("5. gives the active entry aria-current=true", () => {
|
|
77
|
+
render(<ContextRail sections={sections} activeSectionId="quotes" />);
|
|
78
|
+
expect(screen.getByRole("button", { name: "Quotes" })).toHaveAttribute("aria-current", "true");
|
|
79
|
+
expect(screen.getByRole("button", { name: "Sources 3 items" })).not.toHaveAttribute(
|
|
80
|
+
"aria-current",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Finding 1 (task-11f-brief.md, BLOCKER): `isActive` was computed but never
|
|
85
|
+
// passed to `SidebarMenuButton`, so `data-active` defaulted to `false` on
|
|
86
|
+
// every entry and the ENTIRE active-state stylesheet (fill, label weight,
|
|
87
|
+
// icon colour, accent bar — all gated on `data-[active=true]:…` in
|
|
88
|
+
// `sidebarMenuButtonVariants`) never applied. Assert BOTH halves: the
|
|
89
|
+
// active entry carries `data-active="true"` AND an inactive one does not —
|
|
90
|
+
// asserting only the first half would pass against code that marks every
|
|
91
|
+
// entry active.
|
|
92
|
+
it("5b. gives the active entry data-active=true and an inactive one data-active=false", () => {
|
|
93
|
+
render(<ContextRail sections={sections} activeSectionId="quotes" />);
|
|
94
|
+
expect(screen.getByRole("button", { name: "Quotes" })).toHaveAttribute("data-active", "true");
|
|
95
|
+
expect(screen.getByRole("button", { name: "Sources 3 items" })).toHaveAttribute(
|
|
96
|
+
"data-active",
|
|
97
|
+
"false",
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("6. clicking the active entry toggles open; clicking an inactive entry opens and reports the new section", () => {
|
|
102
|
+
const onOpenChange = vi.fn();
|
|
103
|
+
const { rerender } = render(
|
|
104
|
+
<ContextRail
|
|
105
|
+
sections={sections}
|
|
106
|
+
activeSectionId="sources"
|
|
107
|
+
open={true}
|
|
108
|
+
onOpenChange={onOpenChange}
|
|
109
|
+
/>,
|
|
110
|
+
);
|
|
111
|
+
fireEvent.click(screen.getByRole("button", { name: "Sources 3 items" }));
|
|
112
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
113
|
+
|
|
114
|
+
onOpenChange.mockClear();
|
|
115
|
+
const onActiveSectionChange = vi.fn();
|
|
116
|
+
rerender(
|
|
117
|
+
<ContextRail
|
|
118
|
+
sections={sections}
|
|
119
|
+
activeSectionId="sources"
|
|
120
|
+
open={false}
|
|
121
|
+
onOpenChange={onOpenChange}
|
|
122
|
+
onActiveSectionChange={onActiveSectionChange}
|
|
123
|
+
/>,
|
|
124
|
+
);
|
|
125
|
+
fireEvent.click(screen.getByRole("button", { name: "Quotes" }));
|
|
126
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
127
|
+
expect(onActiveSectionChange).toHaveBeenCalledWith("quotes");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("7. disabled sections render a disabled button and do not fire onActiveSectionChange", () => {
|
|
131
|
+
const onActiveSectionChange = vi.fn();
|
|
132
|
+
const disabledSections: ContextRailSection[] = [
|
|
133
|
+
sections[0]!,
|
|
134
|
+
{ ...sections[1]!, disabled: true },
|
|
135
|
+
];
|
|
136
|
+
render(
|
|
137
|
+
<ContextRail
|
|
138
|
+
sections={disabledSections}
|
|
139
|
+
activeSectionId="sources"
|
|
140
|
+
onActiveSectionChange={onActiveSectionChange}
|
|
141
|
+
/>,
|
|
142
|
+
);
|
|
143
|
+
const quotesButton = screen.getByRole("button", { name: "Quotes" });
|
|
144
|
+
expect(quotesButton).toBeDisabled();
|
|
145
|
+
fireEvent.click(quotesButton);
|
|
146
|
+
expect(onActiveSectionChange).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("8. renders the empty slot for an empty sections array, localized by default, overridable via `empty`", () => {
|
|
150
|
+
const { container, rerender } = render(<ContextRail sections={[]} />);
|
|
151
|
+
const emptySlot = container.querySelector('[data-slot="context-rail-empty"]');
|
|
152
|
+
expect(emptySlot).not.toBeNull();
|
|
153
|
+
expect(within(emptySlot as HTMLElement).getByText("No sections")).toBeInTheDocument();
|
|
154
|
+
|
|
155
|
+
const custom: ReactNode = <p>Nothing to show here</p>;
|
|
156
|
+
rerender(<ContextRail sections={[]} empty={custom} />);
|
|
157
|
+
const customEmptySlot = container.querySelector('[data-slot="context-rail-empty"]');
|
|
158
|
+
expect(customEmptySlot).not.toBeNull();
|
|
159
|
+
expect(screen.getByText("Nothing to show here")).toBeInTheDocument();
|
|
160
|
+
expect(screen.queryByText("No sections")).not.toBeInTheDocument();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("9. publishes `width` as --sidebar-width on the provider element, defaulting to 20rem", () => {
|
|
164
|
+
const { container, rerender } = render(<ContextRail sections={sections} />);
|
|
165
|
+
const provider = container.firstElementChild as HTMLElement;
|
|
166
|
+
expect(provider.getAttribute("style") ?? "").toContain("--sidebar-width: 20rem");
|
|
167
|
+
|
|
168
|
+
rerender(<ContextRail sections={sections} width="24rem" />);
|
|
169
|
+
const providerAfter = container.firstElementChild as HTMLElement;
|
|
170
|
+
expect(providerAfter.getAttribute("style") ?? "").toContain("--sidebar-width: 24rem");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("10. warns exactly once when overlayBreakpoint < 768, and not when >= 768", () => {
|
|
174
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
175
|
+
const { unmount } = render(<ContextRail sections={sections} overlayBreakpoint={500} />);
|
|
176
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
177
|
+
unmount();
|
|
178
|
+
warnSpy.mockClear();
|
|
179
|
+
|
|
180
|
+
render(<ContextRail sections={sections} overlayBreakpoint={768} />);
|
|
181
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
182
|
+
warnSpy.mockRestore();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("11. below overlayBreakpoint renders the narrow strip (no [data-slot=sidebar]) with the same switcher names", () => {
|
|
186
|
+
setViewportWidth(500);
|
|
187
|
+
const { container } = render(<ContextRail sections={sections} />);
|
|
188
|
+
expect(container.querySelector('[data-slot="sidebar"]')).toBeNull();
|
|
189
|
+
expect(container.querySelector('[data-slot="context-rail"]')).not.toBeNull();
|
|
190
|
+
expect(screen.getByRole("button", { name: "Sources 3 items" })).toBeInTheDocument();
|
|
191
|
+
expect(screen.getByRole("button", { name: "Quotes" })).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("12. renders all seven data-slot values on an open, non-empty rail", () => {
|
|
195
|
+
const { container } = render(
|
|
196
|
+
<ContextRail sections={sections} open={true} activeSectionId="sources" />,
|
|
197
|
+
);
|
|
198
|
+
for (const slot of [
|
|
199
|
+
"context-rail",
|
|
200
|
+
"context-rail-switcher",
|
|
201
|
+
"context-rail-switcher-item",
|
|
202
|
+
"context-rail-count",
|
|
203
|
+
"context-rail-heading",
|
|
204
|
+
"context-rail-body",
|
|
205
|
+
]) {
|
|
206
|
+
expect(
|
|
207
|
+
container.querySelector(`[data-slot="${slot}"]`),
|
|
208
|
+
`expected [data-slot="${slot}"] to be present`,
|
|
209
|
+
).not.toBeNull();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// #382 — `variant` used to be a derived (`Omit<ComponentProps<typeof
|
|
214
|
+
// Sidebar>, …>`) member of `ContextRailProps`, honoured only in the wide
|
|
215
|
+
// (`Sidebar`) branch and silently inert below `overlayBreakpoint` (the
|
|
216
|
+
// narrow branch has no `Sidebar` to forward it to). Fixed by declaring the
|
|
217
|
+
// props surface instead of deriving it — `variant` is no longer a public
|
|
218
|
+
// prop at all. This is a compile-time lock: it fails the moment someone
|
|
219
|
+
// re-derives `ContextRailProps` from `Sidebar`, which is the only way the
|
|
220
|
+
// prop returns.
|
|
221
|
+
it("13. (type-level) does not accept a `variant` prop", () => {
|
|
222
|
+
function typeOnly() {
|
|
223
|
+
// @ts-expect-error — `variant` was removed from `ContextRailProps`; a
|
|
224
|
+
// consumer can no longer pass it, and it can no longer be silently
|
|
225
|
+
// inert in the narrow branch.
|
|
226
|
+
return <ContextRail sections={[]} variant="floating" />;
|
|
227
|
+
}
|
|
228
|
+
expect(typeof typeOnly).toBe("function");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Companion to #13: the type was re-derived from `ComponentProps<"div">`
|
|
232
|
+
// and stopped omitting `children` alongside `onSelect`, unintentionally
|
|
233
|
+
// making `children` a public prop again even though both `ContextRailWide`
|
|
234
|
+
// and `ContextRailNarrow` supply their own JSX children after spreading
|
|
235
|
+
// `props` — a caller's `children` type-checks and is then silently
|
|
236
|
+
// discarded at render, the same advertised-but-inert failure #382 fixed
|
|
237
|
+
// for `variant`. Compile-time lock: fails the moment `children` is
|
|
238
|
+
// dropped from the `Omit` again.
|
|
239
|
+
it("15. (type-level) does not accept a `children` prop", () => {
|
|
240
|
+
function typeOnly() {
|
|
241
|
+
// @ts-expect-error — `children` is omitted from `ContextRailProps`;
|
|
242
|
+
// both render branches discard it after spreading `props`, so it
|
|
243
|
+
// must not be publicly accepted.
|
|
244
|
+
return <ContextRail sections={[]}>discarded</ContextRail>;
|
|
245
|
+
}
|
|
246
|
+
expect(typeof typeOnly).toBe("function");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("14. the wide branch renders data-variant=sidebar on the sidebar root; the narrow branch renders no data-variant at all", () => {
|
|
250
|
+
const wide = render(<ContextRail sections={sections} open={true} activeSectionId="sources" />);
|
|
251
|
+
const sidebarRoot = wide.container.querySelector('[data-slot="sidebar"]');
|
|
252
|
+
expect(sidebarRoot).not.toBeNull();
|
|
253
|
+
expect(sidebarRoot).toHaveAttribute("data-variant", "sidebar");
|
|
254
|
+
wide.unmount();
|
|
255
|
+
|
|
256
|
+
setViewportWidth(500);
|
|
257
|
+
const narrow = render(
|
|
258
|
+
<ContextRail sections={sections} open={true} activeSectionId="sources" />,
|
|
259
|
+
);
|
|
260
|
+
expect(narrow.container.querySelector("[data-variant]")).toBeNull();
|
|
261
|
+
narrow.unmount();
|
|
262
|
+
});
|
|
263
|
+
});
|