@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,299 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { SideDock } from "./side-dock";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every test that wants the COLUMN branch passes `overlayBreakpoint={800}` —
|
|
8
|
+
* jsdom's `window.innerWidth` is 1024 and its stubbed `matchMedia` always
|
|
9
|
+
* reports `matches: false`, so the default `overlayBreakpoint` (1100) would
|
|
10
|
+
* render the OVERLAY branch in every test (see task-10-brief.md, Mechanism §2).
|
|
11
|
+
* The one test that wants the overlay branch passes `overlayBreakpoint={2000}`.
|
|
12
|
+
*/
|
|
13
|
+
const COLUMN_BREAKPOINT = 800;
|
|
14
|
+
|
|
15
|
+
describe("SideDock", () => {
|
|
16
|
+
// 1. Names the region.
|
|
17
|
+
it("names the complementary region from `title`", () => {
|
|
18
|
+
render(
|
|
19
|
+
<SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
|
|
20
|
+
Body
|
|
21
|
+
</SideDock>,
|
|
22
|
+
);
|
|
23
|
+
expect(screen.getByRole("complementary")).toHaveAccessibleName("Assistant");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 2. Names the handle and reports its value.
|
|
27
|
+
it("names the resize handle and reports its current value", () => {
|
|
28
|
+
render(
|
|
29
|
+
<SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
|
|
30
|
+
Body
|
|
31
|
+
</SideDock>,
|
|
32
|
+
);
|
|
33
|
+
const handle = screen.getByRole("separator");
|
|
34
|
+
expect(handle).toHaveAccessibleName("Resize Assistant");
|
|
35
|
+
expect(handle).toHaveAttribute("aria-orientation", "vertical");
|
|
36
|
+
expect(handle).toHaveAttribute("aria-valuenow", "400");
|
|
37
|
+
expect(handle).toHaveAttribute("aria-valuemin", "320");
|
|
38
|
+
expect(handle.getAttribute("aria-valuetext")).toContain("400 pixels");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 3. Keyboard widens (right-hand dock: ArrowLeft widens).
|
|
42
|
+
it("widens on ArrowLeft and commits once on keyup", () => {
|
|
43
|
+
const onWidthChange = vi.fn();
|
|
44
|
+
const onWidthCommit = vi.fn();
|
|
45
|
+
render(
|
|
46
|
+
<SideDock
|
|
47
|
+
title="Assistant"
|
|
48
|
+
open
|
|
49
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
50
|
+
onWidthChange={onWidthChange}
|
|
51
|
+
onWidthCommit={onWidthCommit}
|
|
52
|
+
>
|
|
53
|
+
Body
|
|
54
|
+
</SideDock>,
|
|
55
|
+
);
|
|
56
|
+
const handle = screen.getByRole("separator");
|
|
57
|
+
handle.focus();
|
|
58
|
+
fireEvent.keyDown(handle, { key: "ArrowLeft" });
|
|
59
|
+
expect(onWidthChange).toHaveBeenCalledTimes(1);
|
|
60
|
+
const widened = onWidthChange.mock.calls[0]![0] as number;
|
|
61
|
+
expect(widened).toBeGreaterThan(400);
|
|
62
|
+
expect(onWidthCommit).not.toHaveBeenCalled();
|
|
63
|
+
fireEvent.keyUp(handle, { key: "ArrowLeft" });
|
|
64
|
+
expect(onWidthCommit).toHaveBeenCalledTimes(1);
|
|
65
|
+
expect(onWidthCommit).toHaveBeenCalledWith(widened);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// 4. Keyboard narrows; Home/End jump to the effective bounds.
|
|
69
|
+
// CORRECTED 2026-09-05 (Ruling 29): under these defaults (minContentWidth
|
|
70
|
+
// 480 against jsdom's 1024px viewport) the effective maximum is 544, not
|
|
71
|
+
// maxWidth (640) — see clampWidth's viewport term.
|
|
72
|
+
it("narrows on ArrowRight and End jumps to the effective maximum (544, not maxWidth)", () => {
|
|
73
|
+
render(
|
|
74
|
+
<SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
|
|
75
|
+
Body
|
|
76
|
+
</SideDock>,
|
|
77
|
+
);
|
|
78
|
+
const handle = screen.getByRole("separator");
|
|
79
|
+
handle.focus();
|
|
80
|
+
fireEvent.keyDown(handle, { key: "ArrowRight" });
|
|
81
|
+
fireEvent.keyUp(handle, { key: "ArrowRight" });
|
|
82
|
+
expect(handle).toHaveAttribute("aria-valuenow", "384");
|
|
83
|
+
fireEvent.keyDown(handle, { key: "End" });
|
|
84
|
+
fireEvent.keyUp(handle, { key: "End" });
|
|
85
|
+
expect(handle).toHaveAttribute("aria-valuenow", "544");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// 5. A no-op is silent.
|
|
89
|
+
it("fires no callback and leaves the value unchanged when the narrowing key is a no-op at minWidth", () => {
|
|
90
|
+
const onWidthChange = vi.fn();
|
|
91
|
+
const onWidthCommit = vi.fn();
|
|
92
|
+
render(
|
|
93
|
+
<SideDock
|
|
94
|
+
title="Assistant"
|
|
95
|
+
open
|
|
96
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
97
|
+
width={320}
|
|
98
|
+
minWidth={320}
|
|
99
|
+
onWidthChange={onWidthChange}
|
|
100
|
+
onWidthCommit={onWidthCommit}
|
|
101
|
+
>
|
|
102
|
+
Body
|
|
103
|
+
</SideDock>,
|
|
104
|
+
);
|
|
105
|
+
const handle = screen.getByRole("separator");
|
|
106
|
+
handle.focus();
|
|
107
|
+
fireEvent.keyDown(handle, { key: "ArrowRight" });
|
|
108
|
+
fireEvent.keyUp(handle, { key: "ArrowRight" });
|
|
109
|
+
expect(onWidthChange).not.toHaveBeenCalled();
|
|
110
|
+
expect(onWidthCommit).not.toHaveBeenCalled();
|
|
111
|
+
expect(handle).toHaveAttribute("aria-valuenow", "320");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 6. A controlled out-of-range width converges (maxWidth as the sole
|
|
115
|
+
// upper bound — minContentWidth={0} zeroes the viewport bound, per
|
|
116
|
+
// Ruling 29; assertion 7 below is what covers the viewport bound).
|
|
117
|
+
it("converges a controlled width above maxWidth, emitting each callback once", () => {
|
|
118
|
+
const onWidthChange = vi.fn();
|
|
119
|
+
const onWidthCommit = vi.fn();
|
|
120
|
+
render(
|
|
121
|
+
<SideDock
|
|
122
|
+
title="Assistant"
|
|
123
|
+
open
|
|
124
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
125
|
+
width={9999}
|
|
126
|
+
maxWidth={640}
|
|
127
|
+
minContentWidth={0}
|
|
128
|
+
onWidthChange={onWidthChange}
|
|
129
|
+
onWidthCommit={onWidthCommit}
|
|
130
|
+
>
|
|
131
|
+
Body
|
|
132
|
+
</SideDock>,
|
|
133
|
+
);
|
|
134
|
+
expect(screen.getByRole("separator")).toHaveAttribute("aria-valuenow", "640");
|
|
135
|
+
expect(onWidthChange).toHaveBeenCalledTimes(1);
|
|
136
|
+
expect(onWidthChange).toHaveBeenCalledWith(640);
|
|
137
|
+
expect(onWidthCommit).toHaveBeenCalledTimes(1);
|
|
138
|
+
expect(onWidthCommit).toHaveBeenCalledWith(640);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// 7. minContentWidth binds against the live viewport.
|
|
142
|
+
it("binds minContentWidth against jsdom's 1024px viewport", () => {
|
|
143
|
+
render(
|
|
144
|
+
<SideDock
|
|
145
|
+
title="Assistant"
|
|
146
|
+
open
|
|
147
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
148
|
+
width={640}
|
|
149
|
+
minContentWidth={480}
|
|
150
|
+
>
|
|
151
|
+
Body
|
|
152
|
+
</SideDock>,
|
|
153
|
+
);
|
|
154
|
+
expect(screen.getByRole("separator")).toHaveAttribute("aria-valuenow", "544");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// 8. Uncontrolled default is closed.
|
|
158
|
+
// `data-state` lives on the GROUP element per task-10-brief.md Mechanism §1
|
|
159
|
+
// ("attrs ... goes on the group element that wraps both fragments" —
|
|
160
|
+
// mirrors Sidebar, which puts `data-state` on `data-slot="sidebar"`, never
|
|
161
|
+
// on `sidebar-container`); `inert` is the CONTAINER's own attribute (the
|
|
162
|
+
// fixed contract's "the collapsed container is inert").
|
|
163
|
+
it("is closed by default, inert, with its body content still mounted", () => {
|
|
164
|
+
render(
|
|
165
|
+
<SideDock title="Assistant" overlayBreakpoint={COLUMN_BREAKPOINT}>
|
|
166
|
+
Body content
|
|
167
|
+
</SideDock>,
|
|
168
|
+
);
|
|
169
|
+
const root = document.querySelector('[data-slot="side-dock"]');
|
|
170
|
+
const container = document.querySelector('[data-slot="side-dock-container"]');
|
|
171
|
+
expect(root).toHaveAttribute("data-state", "collapsed");
|
|
172
|
+
// jsdom does not implement the `.inert` IDL property (it reads back
|
|
173
|
+
// `undefined` even when the attribute is present) — assert the
|
|
174
|
+
// ATTRIBUTE, which is what React 19 actually renders for `inert={true}`.
|
|
175
|
+
expect(container).toHaveAttribute("inert");
|
|
176
|
+
expect(screen.getByText("Body content")).toBeInTheDocument();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 9. The close control closes it.
|
|
180
|
+
it("closes on clicking the close button", () => {
|
|
181
|
+
const onOpenChange = vi.fn();
|
|
182
|
+
render(
|
|
183
|
+
<SideDock
|
|
184
|
+
title="Assistant"
|
|
185
|
+
defaultOpen
|
|
186
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
187
|
+
onOpenChange={onOpenChange}
|
|
188
|
+
>
|
|
189
|
+
Body
|
|
190
|
+
</SideDock>,
|
|
191
|
+
);
|
|
192
|
+
fireEvent.click(screen.getByRole("button", { name: "Close" }));
|
|
193
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
194
|
+
const root = document.querySelector('[data-slot="side-dock"]');
|
|
195
|
+
expect(root).toHaveAttribute("data-state", "collapsed");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// 10. The overlay branch.
|
|
199
|
+
it("renders as an overlay Sheet below overlayBreakpoint, with no resize handle", () => {
|
|
200
|
+
render(
|
|
201
|
+
<SideDock title="Assistant" open overlayBreakpoint={2000} description="A helper panel.">
|
|
202
|
+
Body
|
|
203
|
+
</SideDock>,
|
|
204
|
+
);
|
|
205
|
+
const dialog = screen.getByRole("dialog");
|
|
206
|
+
expect(dialog).toHaveAccessibleName("Assistant");
|
|
207
|
+
expect(screen.queryByRole("separator")).not.toBeInTheDocument();
|
|
208
|
+
expect(screen.getByText("A helper panel.")).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// 11. side="left" inverts the keyboard direction.
|
|
212
|
+
it("inverts ArrowRight to widen on a left-hand dock", () => {
|
|
213
|
+
const onWidthChange = vi.fn();
|
|
214
|
+
render(
|
|
215
|
+
<SideDock
|
|
216
|
+
title="Assistant"
|
|
217
|
+
open
|
|
218
|
+
side="left"
|
|
219
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
220
|
+
onWidthChange={onWidthChange}
|
|
221
|
+
>
|
|
222
|
+
Body
|
|
223
|
+
</SideDock>,
|
|
224
|
+
);
|
|
225
|
+
const handle = screen.getByRole("separator");
|
|
226
|
+
handle.focus();
|
|
227
|
+
fireEvent.keyDown(handle, { key: "ArrowRight" });
|
|
228
|
+
expect(onWidthChange).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(onWidthChange.mock.calls[0]![0] as number).toBeGreaterThan(400);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// 12. Finding 2 (task-11f-brief.md): `inert={!panel.open}` on the column
|
|
233
|
+
// `<aside>` means, per the HTML focus-fixup rule, that a focused
|
|
234
|
+
// descendant (the close button) loses focus the instant the close click
|
|
235
|
+
// makes the container inert — the browser's default is to drop focus to
|
|
236
|
+
// `<body>`. This asserts the repair: closing via keyboard/click restores
|
|
237
|
+
// focus to whatever opened the dock, and explicitly never to `<body>`.
|
|
238
|
+
it("restores focus to the opener when the close button closes the dock", () => {
|
|
239
|
+
function Harness() {
|
|
240
|
+
const [open, setOpen] = useState(false);
|
|
241
|
+
return (
|
|
242
|
+
<>
|
|
243
|
+
<button type="button" onClick={() => setOpen(true)}>
|
|
244
|
+
Open dock
|
|
245
|
+
</button>
|
|
246
|
+
<SideDock
|
|
247
|
+
title="Assistant"
|
|
248
|
+
open={open}
|
|
249
|
+
onOpenChange={setOpen}
|
|
250
|
+
overlayBreakpoint={COLUMN_BREAKPOINT}
|
|
251
|
+
>
|
|
252
|
+
Body
|
|
253
|
+
</SideDock>
|
|
254
|
+
</>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
render(<Harness />);
|
|
258
|
+
|
|
259
|
+
const trigger = screen.getByRole("button", { name: "Open dock" });
|
|
260
|
+
trigger.focus();
|
|
261
|
+
expect(document.activeElement).toBe(trigger);
|
|
262
|
+
|
|
263
|
+
fireEvent.click(trigger);
|
|
264
|
+
const closeButton = screen.getByRole("button", { name: "Close" });
|
|
265
|
+
closeButton.focus();
|
|
266
|
+
expect(document.activeElement).toBe(closeButton);
|
|
267
|
+
|
|
268
|
+
fireEvent.click(closeButton);
|
|
269
|
+
|
|
270
|
+
expect(document.activeElement).not.toBe(document.body);
|
|
271
|
+
expect(document.activeElement).toBe(trigger);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("coalesces a burst of resize events into one rAF-scheduled update", () => {
|
|
275
|
+
const rafCallbacks: FrameRequestCallback[] = [];
|
|
276
|
+
const rafSpy = vi
|
|
277
|
+
.spyOn(window, "requestAnimationFrame")
|
|
278
|
+
.mockImplementation((cb: FrameRequestCallback) => {
|
|
279
|
+
rafCallbacks.push(cb);
|
|
280
|
+
return rafCallbacks.length;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
render(
|
|
284
|
+
<SideDock title="Assistant" open overlayBreakpoint={COLUMN_BREAKPOINT}>
|
|
285
|
+
Body
|
|
286
|
+
</SideDock>,
|
|
287
|
+
);
|
|
288
|
+
rafCallbacks.length = 0; // clear anything scheduled at mount
|
|
289
|
+
|
|
290
|
+
// A window drag fires `resize` many times per frame — every one of these
|
|
291
|
+
// must coalesce into a SINGLE scheduled frame, not one per event.
|
|
292
|
+
fireEvent(window, new Event("resize"));
|
|
293
|
+
fireEvent(window, new Event("resize"));
|
|
294
|
+
fireEvent(window, new Event("resize"));
|
|
295
|
+
expect(rafCallbacks).toHaveLength(1);
|
|
296
|
+
|
|
297
|
+
rafSpy.mockRestore();
|
|
298
|
+
});
|
|
299
|
+
});
|