@qijenchen/design-system 0.1.0-beta.145 → 0.1.0-beta.147
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/dist/components/Accordion/accordion.d.ts +1 -1
- package/dist/components/Accordion/accordion.d.ts.map +1 -1
- package/dist/components/Accordion/accordion.js +8 -3
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
- package/dist/components/AccountMenu/account-menu.js.map +1 -1
- package/dist/components/AgentPanel/agent-panel-fab.d.ts +7 -1
- package/dist/components/AgentPanel/agent-panel-fab.d.ts.map +1 -1
- package/dist/components/AgentPanel/agent-panel-fab.js +36 -14
- package/dist/components/AgentPanel/agent-panel-fab.js.map +1 -1
- package/dist/components/AgentPanel/agent-panel-logo.d.ts +3 -3
- package/dist/components/AgentPanel/agent-panel-logo.js.map +1 -1
- package/dist/components/AgentPanel/agent-panel.d.ts +1 -1
- package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -1
- package/dist/components/AgentPanel/agent-panel.js +28 -22
- package/dist/components/AgentPanel/agent-panel.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.js +1 -1
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +95 -1
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +106 -15
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Avatar/index.js +14 -2
- package/dist/components/Breadcrumb/breadcrumb.d.ts +4 -2
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +2 -2
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +27 -2
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +2 -2
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +118 -22
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +34 -10
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +210 -36
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +3 -2
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts +1 -0
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +8 -0
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +10 -2
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +1 -1
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +4 -2
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +37 -11
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +106 -8
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +3 -2
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +24 -24
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +3 -4
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +20 -20
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +8 -5
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +197 -71
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +1 -1
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +358 -225
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +16 -1
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +55 -43
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts +86 -0
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.js +172 -0
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.js.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +6 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +50 -39
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +4 -0
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +1 -2
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +8 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -1
- package/dist/components/Field/field-edit-keys.js +3 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +18 -1
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +39 -10
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +3 -2
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +2 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts +26 -2
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +82 -25
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileItem/index.js +4 -2
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +132 -55
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +9 -4
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
- package/dist/components/InlineEdit/inline-edit.js +1 -1
- package/dist/components/InlineEdit/inline-edit.js.map +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +6 -1
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +20 -4
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Notice/notice.js +1 -1
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +6 -1
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/index.js +2 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +19 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +47 -27
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/pagination.d.ts +1 -1
- package/dist/components/Pagination/pagination.js +1 -1
- package/dist/components/Pagination/pagination.js.map +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +7 -6
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.js +6 -7
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.js +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +2 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +26 -28
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +10 -4
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.d.ts +28 -6
- package/dist/components/Rating/rating.d.ts.map +1 -1
- package/dist/components/Rating/rating.js +97 -80
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
- package/dist/components/ScrollArea/scroll-area.js +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +3 -2
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +15 -8
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +2 -1
- package/dist/components/SelectMenu/index.js.map +1 -1
- package/dist/components/SelectMenu/select-menu-keyboard.d.ts +59 -0
- package/dist/components/SelectMenu/select-menu-keyboard.d.ts.map +1 -0
- package/dist/components/SelectMenu/select-menu-keyboard.js +107 -0
- package/dist/components/SelectMenu/select-menu-keyboard.js.map +1 -0
- package/dist/components/SelectMenu/select-menu.d.ts +1 -22
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +18 -34
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
- package/dist/components/SelectionControl/selection-item.js +1 -0
- package/dist/components/SelectionControl/selection-item.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +7 -9
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +171 -50
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +9 -4
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +54 -26
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +1 -1
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +7 -2
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +39 -4
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +3 -2
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +8 -2
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
- package/dist/components/TimePicker/time-columns.js +8 -4
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +1 -1
- package/dist/components/TimePicker/time-picker.js +1 -1
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +9 -5
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +266 -214
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-input-modality.d.ts +12 -17
- package/dist/hooks/use-input-modality.d.ts.map +1 -1
- package/dist/hooks/use-input-modality.js +4 -34
- package/dist/hooks/use-input-modality.js.map +1 -1
- package/dist/hooks/use-overflow-items.d.ts.map +1 -1
- package/dist/hooks/use-overflow-items.js +2 -0
- package/dist/hooks/use-overflow-items.js.map +1 -1
- package/dist/hooks/use-truncated.d.ts.map +1 -1
- package/dist/hooks/use-truncated.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/drag-announcements.d.ts +32 -0
- package/dist/lib/drag-announcements.d.ts.map +1 -1
- package/dist/lib/drag-announcements.js +39 -1
- package/dist/lib/drag-announcements.js.map +1 -1
- package/dist/lib/focus-after-trigger.d.ts +45 -0
- package/dist/lib/focus-after-trigger.d.ts.map +1 -0
- package/dist/lib/focus-after-trigger.js +66 -0
- package/dist/lib/focus-after-trigger.js.map +1 -0
- package/dist/lib/overlay-coexistence.js +1 -1
- package/dist/lib/roving-list-keyboard.d.ts +208 -0
- package/dist/lib/roving-list-keyboard.d.ts.map +1 -0
- package/dist/lib/roving-list-keyboard.js +174 -0
- package/dist/lib/roving-list-keyboard.js.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +6 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +4 -4
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +14 -4
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -1
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js +25 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +6 -1
- package/dist/tokens/categorical-color.d.ts.map +1 -1
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/uiSize/icon-size.d.ts +1 -1
- package/dist/tokens/uiSize/stack-gap.d.ts +18 -0
- package/dist/tokens/uiSize/stack-gap.d.ts.map +1 -0
- package/dist/tokens/uiSize/stack-gap.js +5 -0
- package/dist/tokens/uiSize/stack-gap.js.map +1 -0
- package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/fork/codex/hooks.json +1 -1
- package/ds-canonical/fork/consumer/lock.json +17 -14
- package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +9 -0
- package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +47 -9
- package/ds-canonical/fork/consumer/managed-files/5a0d134a2645114c8caac20458070b00a7f0fd55b3e8805b7325cb4cf52fa15e.blob +110 -0
- package/ds-canonical/fork/consumer/managed-files/5af0a46760af338d51fd0f7950675f8e97f7d2f09386b8818fcd7a58e6654e75.blob +790 -0
- package/ds-canonical/fork/consumer/managed-files/68bfcddff9c4a16388c248cd34fb66163e9e64b3d809a48ef3984f6ae4ff6d12.blob +211 -0
- package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +45 -6
- package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +42 -2
- package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +27 -1
- package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +62 -14
- package/ds-canonical/fork/governance.lock +41 -17
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +16 -4
- package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +3 -1
- package/ds-canonical/fork/hooks/check_pattern_invariants.sh +13 -9
- package/ds-canonical/fork/launchers/settings-hooks.json +1 -1
- package/ds-canonical/fork/manifest.json +17 -11
- package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
- package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/fork/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +16 -4
- package/ds-canonical/hooks/check_consumer_code_quality.sh +3 -1
- package/ds-canonical/hooks/check_datatable_invariants.sh +4 -1
- package/ds-canonical/hooks/check_pattern_invariants.sh +13 -9
- package/ds-canonical/hooks/check_plugin_fork_health.sh +8 -1
- package/ds-canonical/hooks/check_propose_discipline.sh +8 -1
- package/ds-canonical/hooks/check_storybook_addon_packaging.sh +8 -1
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +64 -2
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +5 -2
- package/ds-canonical/hooks/registrations.json +12 -7
- package/ds-canonical/hooks/session_start_governance_check.sh +16 -0
- package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +25 -0
- package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +4 -0
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +14 -0
- package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +32 -0
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +59 -0
- package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +32 -1
- package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +21 -0
- package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +21 -0
- package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +97 -0
- package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +49 -0
- package/ds-canonical/references/README.md +12 -6
- package/ds-canonical/references/drag-canonical.md +3 -1
- package/ds-canonical/references/focus-canonical.md +26 -13
- package/ds-canonical/references/governance-audit-coverage.md +1 -1
- package/ds-canonical/references/hit-area-canonical.md +361 -0
- package/ds-canonical/references/item-anatomy-recipe.md +4 -2
- package/ds-canonical/references/keyboard-model-canonical.md +559 -0
- package/ds-canonical/rules/meta-patterns.md +4 -4
- package/ds-canonical/rules/self-verify.md +4 -4
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +96 -0
- package/ds-canonical/skills/knowledge-prune/SKILL.md +4 -1
- package/ds-canonical/skills/performance-audit/SKILL.md +1 -1
- package/ds-story-manifest.json +19 -7
- package/llms-full.txt +2 -2
- package/llms.txt +2 -2
- package/package.json +3 -1
- package/src/components/Accordion/accordion.anatomy.stories.tsx +3 -3
- package/src/components/Accordion/accordion.spec.md +1 -1
- package/src/components/Accordion/accordion.tsx +8 -3
- package/src/components/AccountMenu/account-menu.spec.md +2 -3
- package/src/components/AccountMenu/account-menu.tsx +0 -1
- package/src/components/AgentPanel/agent-decision-card.spec.md +88 -0
- package/src/components/AgentPanel/agent-panel-fab.spec.md +151 -0
- package/src/components/AgentPanel/agent-panel-fab.tsx +67 -24
- package/src/components/AgentPanel/agent-panel-logo.spec.md +87 -0
- package/src/components/AgentPanel/agent-panel-logo.tsx +3 -3
- package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +1 -1
- package/src/components/AgentPanel/agent-panel.principles.stories.tsx +1 -1
- package/src/components/AgentPanel/agent-panel.spec.md +30 -275
- package/src/components/AgentPanel/agent-panel.stories.tsx +113 -8
- package/src/components/AgentPanel/agent-panel.tsx +59 -22
- package/src/components/Alert/alert.anatomy.stories.tsx +1 -1
- package/src/components/AppShell/app-shell.tsx +1 -1
- package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +10 -15
- package/src/components/Avatar/avatar.anatomy.stories.tsx +23 -26
- package/src/components/Avatar/avatar.principles.stories.tsx +10 -10
- package/src/components/Avatar/avatar.spec.md +45 -6
- package/src/components/Avatar/avatar.stories.tsx +8 -8
- package/src/components/Avatar/avatar.tsx +227 -21
- package/src/components/Badge/badge.anatomy.stories.tsx +16 -21
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.spec.md +3 -3
- package/src/components/Breadcrumb/breadcrumb.tsx +5 -3
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +1 -0
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +57 -2
- package/src/components/BulkActionBar/bulk-action-bar.tsx +32 -2
- package/src/components/Button/button.anatomy.stories.tsx +174 -33
- package/src/components/Button/button.spec.md +23 -8
- package/src/components/Button/button.tsx +116 -21
- package/src/components/Calendar/calendar.anatomy.stories.tsx +34 -11
- package/src/components/Calendar/calendar.principles.stories.tsx +44 -1
- package/src/components/Calendar/calendar.spec.md +97 -20
- package/src/components/Calendar/calendar.stories.tsx +101 -4
- package/src/components/Calendar/calendar.tsx +379 -74
- package/src/components/Carousel/carousel.anatomy.stories.tsx +16 -17
- package/src/components/Carousel/carousel.spec.md +48 -2
- package/src/components/Carousel/carousel.tsx +9 -3
- package/src/components/Chart/chart.anatomy.stories.tsx +7 -5
- package/src/components/Chart/chart.spec.md +32 -7
- package/src/components/Chart/chart.tsx +9 -0
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +21 -28
- package/src/components/Checkbox/checkbox.spec.md +4 -2
- package/src/components/Checkbox/checkbox.tsx +10 -2
- package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +2 -1
- package/src/components/Chip/chip.tsx +4 -2
- package/src/components/Combobox/combobox.anatomy.stories.tsx +30 -37
- package/src/components/Combobox/combobox.spec.md +11 -3
- package/src/components/Combobox/combobox.tsx +43 -12
- package/src/components/Command/command.spec.md +11 -0
- package/src/components/Command/command.tsx +143 -10
- package/src/components/DataTable/cell-registry.tsx +5 -1
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +5 -6
- package/src/components/DataTable/data-table-interaction-layer.tsx +27 -14
- package/src/components/DataTable/data-table-known-defects.spec.md +72 -0
- package/src/components/DataTable/data-table-scroll-performance.spec.md +295 -0
- package/src/components/DataTable/data-table-sort-manager.tsx +5 -6
- package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
- package/src/components/DataTable/data-table.css +64 -0
- package/src/components/DataTable/data-table.spec.md +70 -346
- package/src/components/DataTable/data-table.stories.tsx +56 -9
- package/src/components/DataTable/data-table.tsx +392 -103
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
- package/src/components/DateGrid/date-grid.spec.md +163 -1
- package/src/components/DateGrid/date-grid.tsx +270 -7
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +18 -27
- package/src/components/DatePicker/date-picker.spec.md +6 -2
- package/src/components/DatePicker/date-picker.tsx +20 -1
- package/src/components/Dialog/dialog.spec.md +6 -2
- package/src/components/Dialog/dialog.stories.tsx +18 -9
- package/src/components/Dialog/dialog.tsx +83 -48
- package/src/components/DropdownMenu/dropdown-menu-keyboard.ts +255 -0
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +32 -33
- package/src/components/DropdownMenu/dropdown-menu.spec.md +18 -5
- package/src/components/DropdownMenu/dropdown-menu.stories.tsx +58 -1
- package/src/components/DropdownMenu/dropdown-menu.tsx +70 -33
- package/src/components/Empty/empty.spec.md +1 -2
- package/src/components/Field/field-context.ts +9 -4
- package/src/components/Field/field-controls.spec.md +5 -2
- package/src/components/Field/field-edit-keys.ts +12 -0
- package/src/components/Field/field-wrapper.tsx +76 -10
- package/src/components/Field/field.anatomy.stories.tsx +1 -1
- package/src/components/Field/field.spec.md +11 -6
- package/src/components/Field/field.stories.tsx +24 -1
- package/src/components/Field/field.tsx +10 -4
- package/src/components/FileItem/file-item.anatomy.stories.tsx +20 -10
- package/src/components/FileItem/file-item.principles.stories.tsx +14 -7
- package/src/components/FileItem/file-item.spec.md +50 -48
- package/src/components/FileItem/file-item.stories.tsx +41 -28
- package/src/components/FileItem/file-item.tsx +184 -50
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +11 -15
- package/src/components/FileUpload/file-upload.spec.md +27 -1
- package/src/components/FileUpload/file-upload.stories.tsx +33 -0
- package/src/components/FileUpload/file-upload.tsx +173 -43
- package/src/components/FileViewer/file-viewer.tsx +9 -4
- package/src/components/HoverCard/hover-card.stories.tsx +2 -2
- package/src/components/InlineEdit/inline-edit.spec.md +2 -0
- package/src/components/InlineEdit/inline-edit.tsx +8 -3
- package/src/components/Input/input.anatomy.stories.tsx +23 -31
- package/src/components/Input/input.spec.md +11 -0
- package/src/components/Input/input.tsx +6 -1
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +18 -21
- package/src/components/LinkInput/link-input.principles.stories.tsx +4 -4
- package/src/components/LinkInput/link-input.spec.md +12 -1
- package/src/components/LinkInput/link-input.tsx +37 -4
- package/src/components/Menu/menu-item.anatomy.stories.tsx +50 -56
- package/src/components/Menu/menu-item.spec.md +1 -1
- package/src/components/Menu/menu-item.stories.tsx +48 -42
- package/src/components/Notice/notice.anatomy.stories.tsx +8 -16
- package/src/components/Notice/notice.spec.md +1 -1
- package/src/components/Notice/notice.tsx +1 -1
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +21 -28
- package/src/components/NumberInput/number-input.spec.md +2 -0
- package/src/components/NumberInput/number-input.tsx +8 -1
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +28 -48
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +13 -1
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +12 -9
- package/src/components/OverflowIndicator/overflow-indicator.tsx +32 -12
- package/src/components/Pagination/pagination.spec.md +1 -0
- package/src/components/Pagination/pagination.tsx +1 -1
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +14 -13
- package/src/components/PeoplePicker/people-picker-helpers.ts +5 -2
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +4 -2
- package/src/components/PeoplePicker/people-picker.tsx +1 -4
- package/src/components/PeoplePicker/person-display.tsx +45 -28
- package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
- package/src/components/Popover/popover.principles.stories.tsx +3 -12
- package/src/components/Popover/popover.spec.md +7 -3
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.spec.md +1 -1
- package/src/components/ProfileCard/profile-card.tsx +0 -3
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +11 -21
- package/src/components/ProgressBar/progress-bar.spec.md +1 -1
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +17 -24
- package/src/components/RadioGroup/radio-group.spec.md +1 -1
- package/src/components/RadioGroup/radio-group.tsx +22 -4
- package/src/components/Rating/rating.anatomy.stories.tsx +131 -124
- package/src/components/Rating/rating.principles.stories.tsx +69 -43
- package/src/components/Rating/rating.spec.md +142 -63
- package/src/components/Rating/rating.stories.tsx +11 -15
- package/src/components/Rating/rating.tsx +131 -117
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +6 -3
- package/src/components/ScrollArea/scroll-area.spec.md +3 -1
- package/src/components/ScrollArea/scroll-area.stories.tsx +5 -2
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.spec.md +2 -2
- package/src/components/SegmentedControl/segmented-control.tsx +3 -2
- package/src/components/Select/select.anatomy.stories.tsx +26 -35
- package/src/components/Select/select.spec.md +4 -3
- package/src/components/Select/select.tsx +11 -4
- package/src/components/SelectMenu/select-menu-keyboard.ts +218 -0
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +6 -17
- package/src/components/SelectMenu/select-menu.spec.md +12 -4
- package/src/components/SelectMenu/select-menu.tsx +21 -56
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +9 -33
- package/src/components/SelectionControl/selection-item.spec.md +2 -2
- package/src/components/SelectionControl/selection-item.tsx +3 -1
- package/src/components/Separator/separator.stories.tsx +4 -4
- package/src/components/Sheet/sheet.principles.stories.tsx +31 -13
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +73 -6
- package/src/components/Sidebar/sidebar.stories.tsx +82 -4
- package/src/components/Sidebar/sidebar.tsx +281 -64
- package/src/components/Skeleton/skeleton.spec.md +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
- package/src/components/Slider/slider.spec.md +6 -5
- package/src/components/Slider/slider.tsx +13 -8
- package/src/components/Steps/steps-state-visuals.spec.md +157 -0
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -0
- package/src/components/Steps/steps.spec.md +59 -142
- package/src/components/Steps/steps.stories.tsx +1 -1
- package/src/components/Steps/steps.tsx +125 -33
- package/src/components/Switch/switch.spec.md +3 -3
- package/src/components/Switch/switch.tsx +6 -1
- package/src/components/Tabs/tabs.spec.md +5 -1
- package/src/components/Tabs/tabs.tsx +42 -3
- package/src/components/Tag/tag.anatomy.stories.tsx +24 -28
- package/src/components/Tag/tag.spec.md +10 -11
- package/src/components/Tag/tag.tsx +3 -3
- package/src/components/Textarea/textarea.tsx +7 -1
- package/src/components/TimePicker/time-columns.tsx +9 -4
- package/src/components/TimePicker/time-picker.spec.md +4 -1
- package/src/components/TimePicker/time-picker.tsx +1 -1
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +6 -15
- package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -16
- package/src/components/Tooltip/tooltip.spec.md +2 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +35 -13
- package/src/components/TreeView/tree-view.spec.md +67 -38
- package/src/components/TreeView/tree-view.stories.tsx +221 -12
- package/src/components/TreeView/tree-view.tsx +463 -356
- package/src/hooks/use-input-modality.ts +17 -46
- package/src/hooks/use-overflow-items.ts +4 -0
- package/src/hooks/use-truncated.ts +3 -2
- package/src/index.ts +17 -0
- package/src/lib/README.md +2 -0
- package/src/lib/drag-announcements.ts +78 -0
- package/src/lib/focus-after-trigger.ts +102 -0
- package/src/lib/roving-list-keyboard.ts +400 -0
- package/src/lib/utils.ts +5 -0
- package/src/patterns/action-bar/action-bar.spec.md +2 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +16 -12
- package/src/patterns/element-anatomy/item-anatomy.spec.md +98 -6
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +75 -70
- package/src/patterns/element-anatomy/item-anatomy.tsx +17 -6
- package/src/patterns/element-anatomy/truncated-text.spec.md +1 -0
- package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +2 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +36 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +28 -0
- package/src/patterns/overlay-surface/overlay-chrome-sizing.spec.md +180 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -162
- package/src/patterns/overlay-surface/overlay-surface.tsx +5 -3
- package/src/patterns/resize-handle/resize-handle.spec.md +3 -3
- package/src/styles/base.css +1 -1
- package/src/tokens/categorical-color.ts +6 -1
- package/src/tokens/color/color.spec.md +91 -24
- package/src/tokens/color/color.stories.tsx +50 -2
- package/src/tokens/color/primitives.css +41 -38
- package/src/tokens/color/semantic.css +105 -36
- package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -0
- package/src/tokens/motion/motion.spec.md +31 -8
- package/src/tokens/orphan-tokens.spec.md +1 -1
- package/src/tokens/uiSize/icon-size.ts +1 -1
- package/src/tokens/uiSize/stack-gap.ts +17 -0
- package/src/tokens/uiSize/uiSize.css +12 -0
- package/src/tokens/uiSize/uiSize.spec.md +17 -16
- package/src/tokens/uiSize/uiSize.stories.tsx +1 -1
- package/dist/node_modules/aria-hidden/dist/es2015/index.js +0 -147
- package/dist/node_modules/aria-hidden/dist/es2015/index.js.map +0 -1
|
@@ -48,11 +48,16 @@ const AccordionTrigger = React.forwardRef<
|
|
|
48
48
|
className={cn(
|
|
49
49
|
'flex flex-1 items-center justify-between gap-2',
|
|
50
50
|
'py-4 text-body font-medium text-foreground text-left',
|
|
51
|
-
|
|
51
|
+
// hover 字色瞬間切換,不寫 transition-colors(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色,待辦總帳 L9 / N4(3))。
|
|
52
|
+
// 下方箭頭的 transition-transform 是展開狀態的旋轉,不是 hover 觸發,保留。
|
|
53
|
+
'hover:text-fg-secondary',
|
|
52
54
|
// 內描邊:trigger 撐滿標題列高,往外 +2px 實測會壓到下方的內容區(2026-09-07 量測)
|
|
53
55
|
'focus-visible:focus-ring-inset',
|
|
54
56
|
// AccordionTrigger 單一 text-style 列 → semantic `text-fg-disabled`(非 opacity);Button canonical 對齊
|
|
55
|
-
|
|
57
|
+
// 停用:不再 pointer-events-none(原生 disabled 的 button 本來就不會發 click),留著接指標只為了掛禁止符號 ——
|
|
58
|
+
// pointer-events-none 讓 cursor 由外層決定、永遠是箭頭(hit-area-canonical.md 滑過原則一-3「停用的用禁止符號」;
|
|
59
|
+
// 2026-09-29 全站掃出:停用的手風琴標題游標是箭頭)。滑過的字色變化改由 disabled:hover 釘住。
|
|
60
|
+
'disabled:text-fg-disabled disabled:cursor-not-allowed disabled:hover:text-fg-disabled',
|
|
56
61
|
// 2026-05-31 M24:disabled 時 chevron(icon 載體)亦降 text-fg-disabled,不停留 text-fg-muted
|
|
57
62
|
//(muted=neutral-7 比 disabled=neutral-6 深 → 層級顛倒)。覆寫 chevron 自身 text-fg-muted。
|
|
58
63
|
'disabled:[&>svg]:text-fg-disabled',
|
|
@@ -106,7 +111,7 @@ export const accordionMeta = {
|
|
|
106
111
|
tokens: {
|
|
107
112
|
bg: [],
|
|
108
113
|
fg: ['text-fg-disabled', 'text-fg-muted', 'text-fg-secondary', 'text-foreground'],
|
|
109
|
-
ring: ['ring-
|
|
114
|
+
ring: ['focus-ring-inset'],
|
|
110
115
|
},
|
|
111
116
|
} as const
|
|
112
117
|
|
|
@@ -82,11 +82,11 @@ controlled `open` / `onOpenChange`,不靠查找 portal DOM。
|
|
|
82
82
|
|
|
83
83
|
- **「AccountMenu 該收 `mode` prop 自動決定放置」** — 錯;放置 SSOT 在 app-shell.spec.md,consumer 決定(元件收 mode = 第二份 SSOT + 元件被迫依賴 AppShell context 才能 render,Storybook / 非 AppShell 產品用不了)。
|
|
84
84
|
- **「demo 的 `AccountMenu` 是另一個元件」** — 不是;`_demo-helpers.tsx` 的 `AccountMenu` 是綁示範資料(Alan Chen)的薄 wrapper,消費本元件。
|
|
85
|
-
- **「要顯示 email 第二行」** —
|
|
85
|
+
- **「要顯示 email 第二行」** — **暫緩,不是定案不做**(user 2026-07-30 原話:「1.我覺得先不動」;本檔先前寫成「不做」並列入禁止事項 = 把「先不動」升格成定案,M36(a),2026-09-27 改回)。目前 Identity Label 的公開 contract 只含 `user.name`;要加第二行時另提 RFC 再擴充 `AccountMenuUser`,不在討論前自行擴充或另造展示資料。
|
|
86
86
|
|
|
87
87
|
## A11y 預設
|
|
88
88
|
|
|
89
|
-
- **Trigger**:`<button type="button" aria-label>`(icon-only 觸發必有 aria-label;預設「帳號與設定」,i18n 走 `triggerAriaLabel`)
|
|
89
|
+
- **Trigger**:`<button type="button" aria-label>`(icon-only 觸發必有 aria-label;預設「帳號與設定」,i18n 走 `triggerAriaLabel`);焦點框 = 全域 `:focus-visible` 外描邊(`outline: 2px solid var(--ring)`,往外 2px;trigger 不寫任何 focus class;互動感由焦點框提供,無 hover bg — chrome 輕量 entry,不放大 avatar 到 field height)。Radix modal menu 開啟時 trigger 所在 app subtree 會被 `aria-hidden`;由 `DropdownMenu` SSOT 同步把 trigger 設為 `tabIndex={-1}`,關閉時復原,AccountMenu 不另建 open-state 邏輯
|
|
90
90
|
- **Keyboard**:繼承 Radix DropdownMenu(Enter / Space / ArrowDown 開啟、Arrow 巡覽、Esc 關閉並 focus 還 trigger、typeahead)— owner = `dropdown-menu.spec.md` A11y 段,不重述
|
|
91
91
|
- **選單語義**:Label 為 presentation(不可 focus),item 為 menuitem(Radix 自管 role / aria)
|
|
92
92
|
|
|
@@ -105,7 +105,6 @@ controlled `open` / `onOpenChange`,不靠查找 portal DOM。
|
|
|
105
105
|
- ❌ **`primary-sidebar` 派桌面用 AccountMenu**(該 mode 帳號家 = SidebarFooter;app-shell.spec.md 放置 SSOT)
|
|
106
106
|
- ❌ **同畫面同時出現兩個帳號入口**(app-shell.spec.md「只能出現一次」rule)
|
|
107
107
|
- ❌ **用 ProfileCard 當帳號選單內容**(看別人 vs 自己語義錯置)
|
|
108
|
-
- ❌ **自行加入 email 第二行**(`user.name` 是 identity Label 唯一文字 contract)
|
|
109
108
|
- ❌ **繞過本元件手刻 avatar + DropdownMenu 重造帳號入口**(跨 consumer 漂移;本元件即 canonical 收斂)
|
|
110
109
|
|
|
111
110
|
## Benchmark(M8 / M22 cite)
|
|
@@ -117,7 +117,6 @@ export const AccountMenu = React.forwardRef<HTMLButtonElement, AccountMenuProps>
|
|
|
117
117
|
!onOpenSettings &&
|
|
118
118
|
!onSignOut
|
|
119
119
|
) {
|
|
120
|
-
// eslint-disable-next-line no-console
|
|
121
120
|
console.warn(
|
|
122
121
|
'[DS AccountMenu] default 選單項未接 onViewProfile/onOpenSettings/onSignOut——item 點擊無反應。請傳入 callback 或自組 children。'
|
|
123
122
|
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# AgentDecisionCard 設計原則(AgentPanel 家族;獨立 SSOT)
|
|
2
|
+
|
|
3
|
+
> **本 spec 是 AgentPanel 家族決策卡的獨立 SSOT**(2026-09-27 從 `agent-panel.spec.md`「元件規格」第 8 節整段抽出,避免單一 spec 過長(674 行 > 500 硬上限,待辦總帳 N19);內容零改動,只加本檔頭與檔尾指標)。家族定位 / Token / 何時用 / 動畫總表 / Esc 與關閉語意 / 禁止事項仍住 `./agent-panel.spec.md`;程式碼在 `agent-panel.tsx`(`AgentDecisionCard` 與產題守則機械層 `warnDecisionRules`)。
|
|
4
|
+
>
|
|
5
|
+
> **Layout Family**:同 `agent-panel.spec.md`(self-contained 容器家族;決策卡繼承 Popover surface 配方 + SurfaceFooter,見下)。本檔不另立 family。
|
|
6
|
+
|
|
7
|
+
### 8. AgentDecisionCard(決策卡)
|
|
8
|
+
|
|
9
|
+
- 僅代理被阻擋、需人決策時出現;完全覆蓋輸入區、貼面板底。
|
|
10
|
+
- 繼承 Popover surface(rounded-lg、border、elevation-200、compact header 45)+SurfaceFooter;
|
|
11
|
+
改寫:無下圓角;header 下、footer 上無分隔線;body 上下無內距、左右 `--layout-space-loose`。
|
|
12
|
+
- Header:`[小標「n / N」(僅 N>1)][題目 text-body font-medium][×=跳過]`,items-start。
|
|
13
|
+
- 選項卡(拍板樣張 2026-09-02):每個選項=灰底卡 `bg-secondary rounded-md px-3 py-2`,
|
|
14
|
+
**整卡可點**(滑過見下一條);卡片組合 `SelectionItem`(**py 0**:卡的 py 8 是唯一行距 owner,SelectionItem 自帶
|
|
15
|
+
(32−1lh)/2 歸零,避免 double padding——`../Checkbox/checkbox.spec.md`「零外部 gap」鐵律的反向)
|
|
16
|
+
+ RadioGroupItem md(複選=Checkbox md);radio↔label 8、label↔description 2;卡間距 8。
|
|
17
|
+
「其他」卡永遠最後、**常駐 Input**(md 32;label 行框↔Input 8;左縮排 24 = radio 16 + gap 8 對齊 label,
|
|
18
|
+
[Polaris ChoiceChildren 同款](https://github.com/Shopify/polaris/blob/main/polaris-react/src/components/Choice/Choice.module.css);
|
|
19
|
+
距卡右/下各 12);聚焦即選中「其他」;**滑鼠/觸控點整張「其他」卡 → 自動聚焦 Input**(明確指向意圖),
|
|
20
|
+
鍵盤方向鍵選中不搶焦點(APG radio roving,Tab 一步即到);radio `aria-controls` 指向 Input。
|
|
21
|
+
幾何:一般卡 8+21+2+21+8 = 60;「其他」卡 8+21+8+32+12 = 81。
|
|
22
|
+
- 選項卡滑過(2026-09-26 AI 建議,列在「其餘建議」、user 未另提 → AI 判讀照建議做;user 的問句見上;待辦總帳 B12):指標在整張卡的**任何位置**(內距、圓與字的空隙、說明文字、「其他」卡的輸入格),
|
|
23
|
+
卡內的圓(複選是方框)照它被自己的 label 滑過時的樣子變色 —— 顏色即 `../Checkbox/checkbox.spec.md`「狀態」Radio / Checkbox 表的 hover 列,
|
|
24
|
+
不新增顏色;**卡片灰底不換色**、字色不變;卡與卡之間的 8 不屬任何卡,滑過不變、點了也不選。
|
|
25
|
+
理由:DS 原本的單選/複選項目本身沒有滑過樣式,回應的只有控件(`../SelectionControl/selection-item.spec.md`「為何無 ColorMatrix / StateBehavior」),
|
|
26
|
+
指到字上圓也會變是 HTML label 的轉發;選項卡的命中區是整卡,同一套語言的回應範圍就是整卡。
|
|
27
|
+
user 原問句(逐字;問句,不是決定):「我覺得好像不用加上底色變化,若它是 radio 的話,那滑到整個 radio item 應該跟原本的radio item有一樣的設計語言?仔細研究查查原本hover radio item會長怎樣?全盤確認。」
|
|
28
|
+
「其他」卡指在輸入格上:圓照樣變,輸入格外框自己的滑過疊在上面 —— 卡片是宿主、輸入格是卡內控件,宿主保留自己的滑過
|
|
29
|
+
(`../../tokens/color/color.spec.md`「Hover 換色配對總則」巢狀滑過段;套到「宿主的滑過是控件變色」是 AI 推導);點輸入格即選中「其他」,圓亮起來的承諾成立。
|
|
30
|
+
作法:卡片掛具名群組 `group/agent-option`,控件把**自己的** `hover:` 配對原樣接到群組滑過(`agent-panel.tsx` `OPTION_RADIO_HOVER` /
|
|
31
|
+
`OPTION_CHECKBOX_HOVER`,逐條鏡射 `../RadioGroup/radio-group.tsx` / `../Checkbox/checkbox.tsx` 的 hover 行,owner 改色時同步改);
|
|
32
|
+
不把整卡改成 `<label>`:卡內已有 SelectionItem 的 label、`<div>` 與輸入格,違反 [WHATWG label 內容模型](https://html.spec.whatwg.org/multipage/forms.html#the-label-element)。
|
|
33
|
+
鍵盤不受影響(焦點框仍在控件本體)。世界級:卡片式單選 [Joy UI Radio `overlay`](https://github.com/mui/material-ui/blob/f2e0dab9d80271310843c57b9bd430e07e267b8d/docs/data/joy/components/radio-button/radio-button.md#L113-L119)
|
|
34
|
+
同形(整卡可點、滑過只變圓;Joy UI 已於 2026-03-11 自 MUI 主 repo 移除,只作曾經的做法);Carbon RadioTile 改換卡片底色
|
|
35
|
+
([_tile.scss#L62-L73](https://github.com/carbon-design-system/carbon/blob/7e8c8f7db6dd2ed98c4947b78b37614f43eda920/packages/styles/scss/components/tile/_tile.scss#L62-L73)),
|
|
36
|
+
本 DS 不採 —— 單選狀態表的底色欄一律不變(`../Checkbox/checkbox.spec.md`「狀態 › Radio」)。
|
|
37
|
+
- 關閉:header × 恆為跳過;第一題另有跳過鈕(第二題起左鈕換成上一題),兩者同一行為=跳過;無 Esc、無外點關閉(阻擋語意)。
|
|
38
|
+
- 步進:**一題一步**,footer 左鈕=第一題「跳過」(用預設繼續)、第二題起「上一題」(答案保留;
|
|
39
|
+
[Material Stepper Back](https://m1.material.io/components/steppers.html) / [GOV.UK Back link](https://design-system.service.gov.uk/components/back-link/) 同款,
|
|
40
|
+
2026-09-02 user 拍板);右鈕=「下一題」、末步「送出」;header × 恆為跳過。
|
|
41
|
+
「其他」選中而文字為空 → 下一題/送出 disabled。
|
|
42
|
+
- 選項=**RadioGroup md 包裝不改造**(Popover all-sm 律之顯式拍板豁免;footer 鈕維持 sm 守律);
|
|
43
|
+
預選項(`defaultValue`,省略=第一項)由元件在 label 後加「(建議)」。
|
|
44
|
+
- 進出=淡入+下滑 8、`--motion-duration-overlay`。
|
|
45
|
+
- A11y:`role="group"` + `aria-labelledby`;radiogroup / checkbox 原生鍵盤。
|
|
46
|
+
|
|
47
|
+
#### 產題守則(約束代理出題品質;標 ⚙ 者由元件 DEV 警告或渲染邏輯機械強制)
|
|
48
|
+
|
|
49
|
+
1. ⚙ **題數 1–3,每題必須改變代理下一步**;能一題就不問兩題;禁「計畫可以嗎?」類空問。
|
|
50
|
+
([Claude Agent SDK AskUserQuestion 1–4 題/次](https://code.claude.com/docs/en/agent-sdk/user-input),
|
|
51
|
+
本 DS 收緊為 ≤3;[GOV.UK one thing per page](https://design-system.service.gov.uk/patterns/question-pages/))
|
|
52
|
+
2. ⚙ **一題一步**:一次只顯示一題;N≥2 才顯示小標「n / N」;第二題起可「上一題」回頭改答(答案保留),不可跳題。
|
|
53
|
+
([GOV.UK 需要才加簡單「Question 3 of 9」](https://design-system.service.gov.uk/patterns/question-pages/);
|
|
54
|
+
[NN/g wizards 標出目前步、強制順序](https://www.nngroup.com/articles/wizards/))
|
|
55
|
+
3. ⚙ **題目=一句完整問句、以「?」結尾、句內點名決策對象**(「公告要用哪種語氣?」);禁「確定嗎?」「注意!」。
|
|
56
|
+
([Material 對話框標題=問句或陳述、禁 Are you sure?](https://m1.material.io/components/dialogs.html))
|
|
57
|
+
4. ⚙ **具名選項 2–4 個**(不含「其他」);>4 → 拆題。
|
|
58
|
+
([SDK 2–4 options](https://code.claude.com/docs/en/agent-sdk/user-input);
|
|
59
|
+
[NN/g ≤5 用 radio](https://www.nngroup.com/articles/listbox-dropdown/);`../RadioGroup/radio-group.spec.md`「2-5 且全部可見」)
|
|
60
|
+
5. ⚙ **選項標籤單行、≤10 字、無句尾標點、同題平行結構**;標籤說「選了會怎樣」,禁 A/B/C、「方案一」。
|
|
61
|
+
([Polaris ChoiceList:label based on what the option will do、無句尾標點](https://github.com/Shopify/polaris/blob/main/polaris.shopify.com/content/components/selection-and-input/choice-list.mdx))
|
|
62
|
+
6. ⚙ **每個具名選項附一行差異描述**:單句、無句號、不重複標籤、同題各選項比同一個維度(後果/取捨)。
|
|
63
|
+
([GOV.UK hint 單句無句號](https://design-system.service.gov.uk/components/radios/))
|
|
64
|
+
7. ⚙ **「其他」由元件附加、永遠最後**;代理不得自列「其他」;送出值=使用者文字(非「其他」二字);空字串不得前進。
|
|
65
|
+
([SDK:custom text as the answer value, not the word 'Other'](https://code.claude.com/docs/en/agent-sdk/user-input);
|
|
66
|
+
[GOV.UK none 選項放最後](https://design-system.service.gov.uk/components/checkboxes/))
|
|
67
|
+
8. ⚙ **單選題必預選推薦解、推薦解排第一、「(建議)」由元件標**(代理不寫該字樣);推薦必是代理有證據的最佳解。
|
|
68
|
+
例外:不可逆/安全/法律/身分稱謂類題 `noDefault` 不預選,把後果寫進描述。
|
|
69
|
+
([NN/g:pre-select the recommended when confident;例外 legal/presumptuous](https://www.nngroup.com/articles/radio-buttons-default-selection/);
|
|
70
|
+
反例 [GOV.UK do not pre-select](https://design-system.service.gov.uk/components/radios/) 的顧慮是漏答/交錯答,本卡一題一步且 Skip 明示用預設繼續,故取 NN/g 立場)
|
|
71
|
+
9. **排序**:推薦第一;其餘常見→少見或邏輯序(小→大、保守→激進),禁字母序;「其他」最後。
|
|
72
|
+
([GOV.UK most-to-least common](https://design-system.service.gov.uk/components/radios/))
|
|
73
|
+
10. ⚙ **單選為預設;答案本質可複選才 `multiSelect`**(「要包含哪些章節?」):複選改 CheckboxGroup、選項不得互斥、
|
|
74
|
+
**不預選**、仍附「其他」。([SDK multiSelect](https://code.claude.com/docs/en/agent-sdk/user-input);
|
|
75
|
+
[GOV.UK checkboxes 不預選](https://design-system.service.gov.uk/components/checkboxes/))
|
|
76
|
+
11. ⚙ **Skip=「全部用預設繼續」**(非取消、非關對話);header × 恆為 Skip;footer 只有兩鈕(第一題 跳過/下一題,之後 上一題/下一題|送出)。
|
|
77
|
+
([Material ≤2 actions、肯定右否定左](https://m1.material.io/components/dialogs.html);[NN/g wizards allow exit midway](https://www.nngroup.com/articles/wizards/))
|
|
78
|
+
12. **題與題互不依賴**:後題不因前題答案改變;需要分支 → 下一回合另開一張卡。
|
|
79
|
+
([NN/g wizards self-sufficient steps](https://www.nngroup.com/articles/wizards/))
|
|
80
|
+
13. **每步不需捲動即可讀完**(面板寬內 4 選項+描述皆單行);描述過長=精簡,不截斷
|
|
81
|
+
(`../Checkbox/checkbox.spec.md`「Clamp 政策」;ScrollArea 僅兜底)。
|
|
82
|
+
14. **內容真實**:題目與選項必是可辨識業務情境(檔名、頻道、客戶名);禁 Option A/B/C、Lorem(`AGENTS.md` mindset #4)。
|
|
83
|
+
|
|
84
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
85
|
+
|
|
86
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
87
|
+
|
|
88
|
+
- `agent-panel.spec.md`
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# AgentFab / AgentFabDock 設計原則(AgentPanel 家族附屬資產;獨立 SSOT)
|
|
2
|
+
|
|
3
|
+
> **本 spec 是 AgentPanel 家族入口鈕的獨立 SSOT**(2026-09-27 從 `agent-panel.spec.md`「AgentFab(浮動開關鈕;附屬資產)」節整段抽出,避免單一 spec 過長(674 行 > 500 硬上限,待辦總帳 N19);內容零改動,只加本檔頭、檔尾指標,與 N23 / N36 兩處改寫(見該處標註))。家族定位 / Token / 何時用 / 動畫總表 / 關閉語意 / 禁止事項仍住 `./agent-panel.spec.md`;程式碼對應 `agent-panel-fab.tsx`(`AgentFab` / `AgentFabDock`)與 `agent-panel.tsx`(`AgentPanelDock`)。
|
|
4
|
+
>
|
|
5
|
+
> **Layout Family**:同 `agent-panel.spec.md`(self-contained 容器家族);入口鈕是圓形 iconOnly 按鈕,本檔不另立 family。
|
|
6
|
+
|
|
7
|
+
## AgentFab(浮動開關鈕;附屬資產)
|
|
8
|
+
|
|
9
|
+
- 40 圓=`--field-height-lg` 於 lg 密度;圓形 iconOnly;面=`bg-surface-raised`+
|
|
10
|
+
`--elevation-200`(不寫死白色);內置 24 標誌(同一造型)。
|
|
11
|
+
- 外框=AI 觸發鈕特調:錐形(環向)漸層描邊 2px(整數寬+環向漸層=正圓對稱);
|
|
12
|
+
兩極=`AGENT_BRAND` 藍 258 / 紫 294(= `--color-blue-4` / `--color-purple-4`;品牌資產常數,agent-panel-logo.tsx 唯一數值來源;各落於兩緞帶
|
|
13
|
+
色相家族內;2026-09-02 藍→紫改色)。
|
|
14
|
+
- 動畫:待機=靜止(=標誌 still 態;2026-09-02 拍板全家族待機一律靜止);
|
|
15
|
+
有新訊=招喚態(標誌蓄勢;漣漪由邊框光圈代位:0–35% 貼邊聚亮至 .35(swell)→ 35% 呼氣起點
|
|
16
|
+
自邊框射出 r 21→27(settle)→ 90% 散盡 → 靜止空拍;寬 2.5;與標誌同 dur/keyTimes/曲線、同一
|
|
17
|
+
commit 掛載故同相;光圈漸層=環同兩極同方位);懸停=陰影升一級+微放大;點擊=開面板。
|
|
18
|
+
- 減動作:光圈屬位移 loop → 全停(標誌內部自回靜止)。
|
|
19
|
+
- **標誌狀態跟著面板裡的代理走**(prop `logoState`,與 `AgentPanelHeader.logoState` 同名 —— 產品端同一個變數餵兩邊;
|
|
20
|
+
2026-09-03 user 拍板「若開啟的 session 是思考中,FAB 的 logo 也應該是思考中」):入口鈕 = 那個對話**收起來的樣子**,
|
|
21
|
+
不是另一個獨立的東西 —— 代理在回覆時即使面板關著,入口鈕標誌照樣轉(`think`);有新訊 = 招喚(`attract`,標誌蓄勢
|
|
22
|
+
+ 邊框光圈);閒置 = 靜止(`still`)。兩種形態(40 圓 / 28 貼邊)都跟,尺寸變、狀態不變。光圈只給招喚態:思考態的
|
|
23
|
+
訊號是標誌自己在轉,再加光圈會變成兩個 loop 互相打架。對照:[Android Bubbles 收合後仍以圖示承載該對話的動態](https://developer.android.com/develop/ui/views/notifications/bubbles)。
|
|
24
|
+
- **放置與互斥**(2026-09-02 拍板;2026-09-03 抽成元件):FAB 為 opt-in 浮動入口,固定於舞台右下、內距
|
|
25
|
+
`--layout-space-loose`(16/24;Material FAB 最小邊距同值);**面板開 → FAB 隱藏、面板關 → FAB 回來**
|
|
26
|
+
(兩者互斥,開面板的入口與關面板的 × 不並存)。互斥、位置記憶與標誌狀態的**唯一住所 = `AgentPanelDock`**
|
|
27
|
+
(`<AgentPanelDock logoState={s}>{({ close, logoState }) => <AgentPanel>…</AgentPanel>}</AgentPanelDock>`,
|
|
28
|
+
外層容器需 `relative`):開時只渲染面板(× 接 `close`)、關時只渲染入口鈕(點一下開回來);產品端與所有 story
|
|
29
|
+
都用它,不各自寫一份 `open ? panel : fab`(「入口鈕三態」那個 story 例外:它展示的是獨立 `AgentFab` 的三種標誌狀態,沒有面板可互斥)。
|
|
30
|
+
**一個舞台一個 Dock**:家的座標只有一組(右下角離邊 loose),同一個 relative 容器內放兩個 `AgentPanelDock` 會像素級重疊;
|
|
31
|
+
要在同一頁擺兩個代理入口,請各自給不同的 relative 舞台(2026-09-03 稽核補上的不變式)。
|
|
32
|
+
- **遮擋與貼邊**(`AgentFabDock`;演進:2026-09-02 拖到邊 → hover 小鈕 → 拖曳自由座標 → 2026-09-03 user 拍板
|
|
33
|
+
「只有家與貼邊兩種位置」→ 帶的幾何(寬 36、下半部)→ 帶的樣式(drop-target 底 + 三邊虛線框)。**用語統一**:
|
|
34
|
+
位置叫「家 / 貼邊」、區域叫「帶」、動作文案叫「縮小按鈕 / 放大按鈕」,不再用「收到邊 / 收合 / 小鈕 / 藍框」):40 外徑 + loose 內距 = 佔右下 56×56(md)/ 64×64(lg),
|
|
35
|
+
與表格分頁列「操作右」必撞 → 主鈕可拖到右緣貼邊:
|
|
36
|
+
- **命中區 = 可視形狀本身**(2026-09-04 user 拍板原話:「按鈕的視覺 = 觸發事件的範圍 = 會觸發 tooltip 的範圍」;
|
|
37
|
+
「當我點擊按鈕的任何地方包括左側靠近邊邊的地方,只要還在按鈕範圍內就應該觸發事件」)。
|
|
38
|
+
三者**恆等**,沒有例外可以解釋:看得到的每一點都點得到、點得到的每一點都看得到、會出 tooltip 的範圍就是這一塊。
|
|
39
|
+
作法:語意 `<button>` 的尺寸與圓角**都等於可視形狀**(貼邊 28 + `rounded-l-full`、在家 40 + `rounded-full`);
|
|
40
|
+
漸層環是這一層自己的 2px padding,內層 span 只負責面色,所以按鈕的 border box 邊緣就是使用者看到的邊緣。
|
|
41
|
+
**DOM 盒 / 無障礙 target / 命中區 / Radix 錨點四者是同一個形狀。**
|
|
42
|
+
- **不外推**(2026-09-03 曾外推到 40×40,2026-09-04 撤回):外推會生出隱形帶,搶走底下內容的點擊,
|
|
43
|
+
並把 Radix 錨點推遠(tooltip 離可視形狀 20px 而不是 8px)。tooltip 與觸發點的距離恆為 8px(`OVERLAY_SIDE_OFFSET`),
|
|
44
|
+
兩者不重疊 —— 所以「點 tooltip 觸發按鈕」不是需求,也不該發生。
|
|
45
|
+
- **「會出 tooltip 的範圍」指的是觸發範圍,不是「tooltip 保持開著的範圍」**(2026-09-05 更正敘述):Radix Tooltip
|
|
46
|
+
未設 `disableHoverableContent` 時,游標離開鈕、穿過那 8px 空隙移向 tooltip 的期間 tooltip 不關
|
|
47
|
+
([Radix Tooltip `disableHoverableContent` / hoverable content](https://www.radix-ui.com/primitives/docs/components/tooltip)),
|
|
48
|
+
這是**刻意保留**的 —— [WCAG 1.4.13 Content on Hover or Focus](https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html)
|
|
49
|
+
要求懸停內容可被指標到達(hoverable)。空隙上的點擊落到底下內容是正確的,tooltip 開著不代表那裡屬於鈕。
|
|
50
|
+
定位殼改 `w-fit`(2026-09-04)拿掉的是殼比鈕寬 12px 的**結構鬆弛**(殼一直是 `pointer-events-none`,從未參與命中);
|
|
51
|
+
它對「空隙上 tooltip 仍開著」沒有、也不可能有影響 —— 先前把「殼的帶讓 tooltip 開著」記為此症狀的根因是誤判,撤回。
|
|
52
|
+
命中區 ≡ 可視形狀仍是這顆鈕的唯一契約(它沒有另外的懸停回饋,所以 `ds-canonical/references/hit-area-canonical.md`「一-4 細則」的「懸停回饋形狀 ≡ 命中區」在它身上退化成可視形狀;那條通則只管控件,不延伸成全 DS「點得到的都要看得到」—— 整列、整張卡照該檔滑過原則一-2)。
|
|
53
|
+
- **也不內縮**:先前寫成「按鈕保持矩形、圓角只畫內層,角落才點得到」,那是把**多**當成修正 ——
|
|
54
|
+
使用者要的是相等。(同時撤回一條誤判:2026-09-03 記錄「貼邊態 dy=±12 時最左 1–3px 點不到」並歸因於圓角命中;
|
|
55
|
+
複核幾何後,D 形在該高度的左緣本來就在 x≈6.8 而非 x=0,那幾點原本就在**視覺之外**,不是死區。)
|
|
56
|
+
- **貼邊鈕壓在別人的捲軸上時,鈕贏**(2026-09-04 user 拍板原話:「按鈕可能已經蓋到了表格的捲軸,
|
|
57
|
+
但點按鈕仍應該觸發按鈕的事件而不應該是捲動捲軸,因為按鈕是蓋在表格上,fab 通常也都是 z-index 最上面的東西」)。
|
|
58
|
+
入口鈕是浮在內容之上的全域入口,不是內容的一部分;它蓋到什麼,那一塊就歸它。
|
|
59
|
+
實測(真實滑鼠,非 `elementFromPoint`):貼邊鈕 x=1179–1207 與 DataTable 垂直捲軸 x≈1175–1190 重疊約 11px,
|
|
60
|
+
在重疊區點擊 → 面板開啟、`scrollTop` 不變。
|
|
61
|
+
**注意**:`document.elementFromPoint` **看不到原生捲軸**(它不是 DOM 元素),所以這一條只能用真實指標事件驗,
|
|
62
|
+
不能用命中圖 —— 先前的「每一點都命中」驗證對這個場景是盲的。
|
|
63
|
+
(本條取代 2026-09-03 的舊立場「捲軸露出的那幾 px 不屬於鈕,點在那裡捲動內容是正確行為」。)
|
|
64
|
+
- **懸停微放大掛在按鈕上**(不是內層):命中盒與可視形狀一起放大,兩者永遠同步;掛在內層的話
|
|
65
|
+
放大後可視會比命中盒大一圈。陰影升一級畫在內層(陰影屬於那個看得見的形狀)。值與獨立
|
|
66
|
+
`AgentFab` 同一組(`scale-[1.04]` + `--elevation-200-hover`),沒有另訂。
|
|
67
|
+
- **2026-09-04 對抗式稽核登記的兩條**:(a) **已修(2026-09-29,待辦總帳 N30)**:`AgentPanelDock` 開面板時整顆 `AgentFabDock` 被卸載,
|
|
68
|
+
關回來是全新掛載 → 首格 `size` 為 0,舊 `placementStyle` 在 `s.h === 0` 時放行未夾的 y、家的內距用 fallback 16
|
|
69
|
+
(改前實測:面板開著時視窗 800 → 400,關面板的第一次 commit 寫進 DOM 的 `top` 是未夾的 700、合法上界 300;
|
|
70
|
+
那一格沒被畫出來,靠的是 layout effect 內 setState 在 paint 前同步重繪)。現在靜止位置全由 CSS 對舞台錨定:
|
|
71
|
+
貼邊 `top: clamp(50% − 14px, y, max(…, 100% − 2·loose − 68px))`、家 `top: calc(100% − loose − 40px)` + `right: var(--layout-space-loose)`,
|
|
72
|
+
任何一格都不依賴量測;閘 `scripts/agent-panel-reopen-state.mjs` N30 段(殼重掛時 style 每一個被覆寫的舊值套到探針上量像素、
|
|
73
|
+
rAF 逐格、回家過渡中途取樣)+ `scripts/test-agent-fab-drag-zones.mjs`(表達式與 JS 公式同一組常數)。
|
|
74
|
+
(b) `resizable` 把手改用 `patterns/resize-handle` 後 pointerdown 多了 `preventDefault()`,
|
|
75
|
+
讓「拖欄寬時編輯中的 cell 自動結算」不再發生。**狀態的 owner 是 `../DataTable/data-table-known-defects.spec.md` 缺陷表 U 列**
|
|
76
|
+
(「不修,登記為既定行為」,隨 `a7b2be94`(#124)登記):編輯中拖欄寬不再自動結算退出編輯,cell 維持編輯態、
|
|
77
|
+
naked Field 隨欄寬即時變形,放開後仍在編輯。本段先前寫「未實測(本機沙箱起不了 Chromium),不寫成已知行為」,與 U 列
|
|
78
|
+
互相矛盾;2026-09-27(待辦總帳 N36)以缺陷表為 owner 對齊,這裡不再另記狀態 —— 是否曾實機驗證以 U 列的紀錄為準,本段不另作宣稱。
|
|
79
|
+
- **機械閘**:`scripts/agent-fab-hit-area-invariant.mjs` 掃 `elementFromPoint().closest('button')` 的
|
|
80
|
+
**真實命中測試**(不是 class 或 rect 的字面值),逐點分類「在可視形狀內/外」(圓角半徑直接讀
|
|
81
|
+
computed style,所以任何形態都適用),斷言 **H1 可視形狀內每一點都點得到(0 個死點)/
|
|
82
|
+
H4 可視形狀外不得點得到(只容 1.5px 次像素,再多就是隱形帶)/ H5 定位殼不得大於鈕 /
|
|
83
|
+
H2 不越舞台右下緣 / H3 舞台零溢出**;
|
|
84
|
+
**注意此閘驗不到「鈕壓在原生捲軸上」那一條** —— `elementFromPoint` 看不到捲軸(它不是 DOM 元素),
|
|
85
|
+
那條只能用真實指標事件驗;
|
|
86
|
+
併在 `npm run test:agent-panel-invariants`。**最小點擊尺寸不拿觸控裝置的準則來規範**(2026-09-27 依待辦總帳 N23 改寫;
|
|
87
|
+
user 2026-09-24 原話:「此外我們在做的是web component ,你他媽不要一直拿觸控裝置的設計原則來規範吧?滑鼠的指標是可以比手指頭精細很多的欸」;
|
|
88
|
+
owner `ds-canonical/references/hit-area-canonical.md`「本 DS 不採納觸控尺寸建議」):本 DS 以滑鼠指標的精度為前提。這裡先前列的
|
|
89
|
+
[Apple HIG 44pt](https://developer.apple.com/design/human-interface-guidelines/buttons) /
|
|
90
|
+
[WCAG 2.5.5 Target Size (Enhanced) 44 CSS px](https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html)
|
|
91
|
+
是**觸控尺寸建議、AI 自己找來的對照**,不是 user 要求,也從來不是「命中要大於視覺」的依據 —— 要把可點範圍做大,做大的是**可視形狀**,不是加隱形邊。
|
|
92
|
+
- **只有兩種合法位置**:「家」= 圓鈕 40,位置唯一在右下角(離右、下各 loose;[Android FAB 官方範例
|
|
93
|
+
`layout_margin` 16dp](https://developer.android.com/develop/ui/views/components/floating-action-button)、Teambition 16,
|
|
94
|
+
lg 密度 24)/「貼邊」= 收合鈕 `--field-height-sm` 28 貼右緣半圓(只留內側圓角、環只畫露出三邊、內置 16 標誌;
|
|
95
|
+
招喚態同款蓄勢、光圈省略),只有 y 可變、夾在右緣帶內。沒有第三種位置,使用者不可能把鈕拖到難用的地方。
|
|
96
|
+
**兩種形態點一下都直接開面板**(一段);< 8px 位移視為點擊。
|
|
97
|
+
**拖曳(≥ 8px)放開不得開面板,且不得依賴事件時序**(2026-09-16 user:「拖拉 agent panel 的 fab 很容易一不小心就開啟 panel,
|
|
98
|
+
但我明明就只是要移動它而已」):瀏覽器對同一顆鈕補發的 click 由旗標吞掉,旗標只在**下一次 pointerdown** 才清、不用計時器
|
|
99
|
+
(舊版 `setTimeout(0)` 在 click 晚一個 task 送達的環境 —— 遠端隔離 / 輸入代理 —— 會漏);鍵盤合成的 click(detail 0)不吞。
|
|
100
|
+
**拖曳的判準不是「中途收到幾個 pointermove」**:放開時若尚未判成拖曳,再以放開點走一次同一套位置計算(門檻 / 磁吸帶 / 落點),
|
|
101
|
+
pointermove 被代理丟掉 / 合併(只剩按下與放開)時仍判成拖曳、仍落到同一個磁吸位置(舊版只在 pointermove 裡設 moved,0 個 move 放開在 80px 外會被當成點擊);
|
|
102
|
+
已判成拖曳的手勢維持原判、落點取最後一次移動。
|
|
103
|
+
機械閘 `scripts/agent-fab-drag-click-invariant.mjs`(晚到 100ms 的 click 必被吞;0 個 move 放開在 80px 外不開面板;對照組先發 pointerdown 把旗標清掉必紅)。
|
|
104
|
+
- **右緣帶**(磁吸區;2026-09-03 user 留言拍板幾何):寬 36(= `--field-height-md`;游標離右緣 ≤ 36 即進帶,已在帶內時
|
|
105
|
+
再多 16px 才算離開,遲滯防抖);上緣 = 貼邊鈕圓心落在視窗中線(鈕頂 = 舞台高 ÷ 2 − 14);下緣 = 貼邊鈕底離家頂一個
|
|
106
|
+
loose(鈕頂 = 舞台高 − 2·loose − 68)。貼邊鈕只能從中線往下拖到家上方,永不與家重疊、不壓分頁列;矮視窗放不下時整帶
|
|
107
|
+
收成中線一點。帶(底色)= 這個矩形 = 貼邊鈕合法 y 範圍。判定用**指標**位置(意圖在指尖)。
|
|
108
|
+
- **拖 40 圓鈕(所見即所得)**:鈕全程跟著游標;整段拖曳期間右緣帶以**底色 + 三邊虛線框**標出(見下「帶的樣式」);
|
|
109
|
+
游標一進帶內,預覽當場變成貼邊鈕**貼在右緣、停在放開會落的高度**(帶內所見即所得,不預告在游標下);
|
|
110
|
+
放開在帶內 → 落定;放開在帶外 → 飛回家。
|
|
111
|
+
- **拖 28 貼邊鈕**:不顯示帶;帶內沿 y 移動維持貼邊鈕;一出帶外當場變回 40 圓鈕、放開飛回家。
|
|
112
|
+
- **帶的樣式**(2026-09-03 user 拍板;SSOT = `color.spec.md`「Drop target」段):消費 DS「可放下的區域」配對 ——
|
|
113
|
+
底 `bg-drop-target`(`--primary` @ 15% **兩模式都半透明**:覆蓋在頁面內容上就不能不透明,VS Code theme-color 鐵律;
|
|
114
|
+
15% = DS alpha 階梯上的既有階,且落在世界級區間 Material dragged 0.16 / VS Code 0.18 / Atlassian ≈0.20)+ **三邊 2px dashed**
|
|
115
|
+
`border-drop-target-border`(= `--primary-hover`;dashed = DS「可放下的暫時目標」語彙,與 FileUpload 拖入區同一組 token)。
|
|
116
|
+
**貼右緣那側不畫線、不留圓角**(`border-r-0 rounded-l-md`;Sheet / Sidebar / AppShell 側欄 / 貼邊鈕「環只畫露出三邊」同語言)。
|
|
117
|
+
與 FileUpload 常駐拖入區的分工:那邊靜止就看得見、進入合法區只換邊框不填色;這裡憑空出現、需要整區底色才讀得出範圍,
|
|
118
|
+
落點回饋由鈕自己的所見即所得預覽承擔(對照表在 color.spec.md)。
|
|
119
|
+
- **等價路徑**:鍵盤 家 → `→` 貼邊(停在帶底 = 家頂上方一個 loose);貼邊 `↑↓` 16px、`←` / Home 回家;右鍵 / Shift+F10
|
|
120
|
+
DropdownMenu 依狀態只給一項(家:「縮小按鈕」`ArrowRightToLine` / 貼邊:「放大按鈕」`ArrowLeftFromLine` —— 線 = 右緣、
|
|
121
|
+
箭頭方向 = 鍵盤 → / ← 等價路徑,[lucide 官方 tags collapse / expand 鏡像對](https://github.com/lucide-icons/lucide/blob/main/icons/arrow-right-to-line.json);
|
|
122
|
+
Maximize2 家族在 DS = 全螢幕、Panel 家族 = 面板本身,故不用;2026-09-03 user 拍板「縮小按鈕與放大按鈕
|
|
123
|
+
+ 前綴 icon」——「按鈕」點名對象,與 FileViewer 內容縮放的「放大 / 縮小」不混淆;DS 無 ContextMenu,以受控 DropdownMenu 代);
|
|
124
|
+
拖曳中 Esc 取消回原位。Tooltip 兩態不同:家「問我或推走我」(2026-09-03 user 文案,邀請拖曳)/ 貼邊
|
|
125
|
+
「開啟智慧代理」(2026-09-03 user 留言:小鈕只寫開啟;= aria-label);拖曳中不顯示。
|
|
126
|
+
- **動作**(依 motion.spec.md;2026-09-03 對照四家後定):形態切換 `--motion-duration-overlay` 150ms
|
|
127
|
+
([Carbon moderate-01 150ms「小型展開、短距離移動」](https://carbondesignsystem.com/elements/motion/overview/))/
|
|
128
|
+
飛回家與落點修正 `--motion-duration-surface` 250ms + enter 曲線([Atlassian transitions 150–400ms「較長時長幫助
|
|
129
|
+
追蹤空間變化」](https://atlassian.design/foundations/motion);拖曳中跟指標不過渡)/ 帶底色淡入淡出 150ms;
|
|
130
|
+
prefers-reduced-motion 三者全部直接落定([Atlassian「全部停用仍可用」](https://atlassian.design/foundations/motion)、
|
|
131
|
+
[Fluent「提供 no motion 設定」](https://fluent2.microsoft.design/motion)、WCAG 2.3.3)。
|
|
132
|
+
- **區域 → 落點表(可擴充)**:磁吸邏輯 = 依序判定指標所在區域,命中即決定「預覽 = 落點」;沒命中 = 圓鈕跟游標、
|
|
133
|
+
放開回家;要加磁吸點(鏡像左緣、四角)只在 `agent-panel-fab.tsx` `SNAP_ZONES` 加一列(區域矩形同時就是帶的底色範圍),流程不變。
|
|
134
|
+
判準:以指標判定、一區一落點一形狀、邊界 16px 遲滯、區域不重疊且邊帶優先於角落、無命中不做「最近磁吸點」
|
|
135
|
+
(會從放開處跳走)。上緣 / 下緣永不設區(標題列 / 分頁列)。**指標 x 夾在舞台內再判區(超出右緣讀作右緣;y 不夾)**:
|
|
136
|
+
拖曳用 pointer capture,`clientX` 可以越過視窗右緣,夾回舞台後仍落在右緣帶內 → 拖過頭一樣算貼邊;y 不夾,越過上下緣就是沒命中。
|
|
137
|
+
- 位置由 consumer 受控/非受控(`placement / defaultPlacement / onPlacementChange`,`{kind:'home'}` /
|
|
138
|
+
`{kind:'dock',y}`),DS 不寫 storage;要跨 session 記憶由 consumer 存。
|
|
139
|
+
- 對照:[Copilot DAB 可拖到內容區側邊變小圖示、拖回畫布即展開](https://support.microsoft.com/en-us/office/foundations-experiences/copilot-dab/the-copilot-dynamic-action-button-in-word-excel-and-powerpoint)、
|
|
140
|
+
[Windows Snap「拖到螢幕邊時 Snap 框當場顯示」= 拖曳中預告落點](https://support.microsoft.com/en-us/windows/snap-your-windows-885a9b1e-a983-a3b1-16cd-c531795e6241)、
|
|
141
|
+
[Android Bubbles 任意拖、拖到底部才出現關閉區](https://developer.android.com/develop/ui/views/notifications/bubbles)、
|
|
142
|
+
[Teambition 專案頁 hover「−」收到右緣、點圓弧先展開](https://www.teambition.com/)(2026-09-02 實測;本 DS 收合後
|
|
143
|
+
一段即開、且形態在拖曳中就切換,比它少一步、回饋更早);Material 明文 FAB 不移動
|
|
144
|
+
([M3 FAB](https://m3.material.io/components/floating-action-button/guidelines))→ 可拖在 DS 為 opt-in。
|
|
145
|
+
- A11y:`aria-label="開啟智慧代理"`。
|
|
146
|
+
|
|
147
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
148
|
+
|
|
149
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
150
|
+
|
|
151
|
+
- `agent-panel.spec.md`
|
|
@@ -262,24 +262,29 @@ const placementXY = (s: Stage, p: AgentFabPlacement): Point =>
|
|
|
262
262
|
: { x: s.w - s.inset - FAB_PX, y: s.h - s.inset - FAB_PX }
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
|
-
* **靜止時一律用 CSS
|
|
265
|
+
* **靜止時一律用 CSS 從舞台邊緣錨定,不用量出來的數字**(2026-09-03 user 回報
|
|
266
266
|
* 「小鈕左側點不到」「切 story 回來鈕不見了」的共同根因):
|
|
267
267
|
* 以 `left = 量到的舞台寬 − 40` 定位時,只要量到的寬度比當下版面舊一格(捲軸出現的那一刻),鈕就會凸出可視區,
|
|
268
268
|
* 凸出的那段 `elementFromPoint` 回 null = 點不到;而且鈕自己造成的水平溢出會生出捲軸 → 舞台變窄 → 位置更偏,
|
|
269
269
|
* 量測與版面互相追成迴圈,ResizeObserver 判定 loop 後停手,鈕就卡在出界(或整顆看不見)的狀態。
|
|
270
|
-
* 用 right
|
|
271
|
-
*
|
|
270
|
+
* x 用 `right` 錨定後永遠貼齊 padding box 邊緣、不可能溢出。
|
|
271
|
+
*
|
|
272
|
+
* **y 也一樣(2026-09-29,待辦總帳 N30)**:舊版貼邊 y 用 JS 夾在合法範圍,但夾的輸入是量到的舞台高 ——
|
|
273
|
+
* 面板開著時整顆 AgentFabDock 被卸載,關回來是全新掛載,第一次 commit 的舞台高是 0,`s.h > 0` 守衛只好放行未夾的 y、
|
|
274
|
+
* 家的內距也只能用 fallback 16;正確值要等 useLayoutEffect 量完再同步重繪一次才寫進去(實測第一格從沒被畫出來,
|
|
275
|
+
* 但那是靠 React「layout effect 內的 setState 在 paint 前同步沖」撐住的,規則本身寫在一個會過期的觀察量上,M37)。
|
|
276
|
+
* 現在合法範圍直接用 CSS 對舞台的百分比表達 —— `clamp(50% − 14px, y, max(50% − 14px, 100% − 2·loose − 68px))`
|
|
277
|
+
* 與下方 `dockMinY` / `dockMaxY` 同一組公式(百分比對 absolute 定位的包含塊 = 舞台的 padding box,與 `clientHeight` 同義)——
|
|
278
|
+
* 任何一格都不依賴量測,舞台變矮時瀏覽器自己夾,JS 的 `stage` 只剩拖曳 / 鍵盤 / 帶的幾何在用。
|
|
279
|
+
* 家與貼邊都寫成 `top`,兩態之間照樣過渡(CSS 會在 calc / clamp 之間內插)。
|
|
272
280
|
*/
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
? { left: 'auto', right: s.inset, top: s.h - s.inset - FAB_PX, bottom: 'auto' }
|
|
281
|
-
: { left: 'auto', right: s.inset, top: 'auto', bottom: s.inset }
|
|
282
|
-
}
|
|
281
|
+
const INSET_VAR = 'var(--layout-space-loose)'
|
|
282
|
+
const DOCK_MIN_Y_CSS = `calc(50% - ${DOCK_PX / 2}px)`
|
|
283
|
+
const DOCK_MAX_Y_CSS = `max(${DOCK_MIN_Y_CSS}, calc(100% - 2 * ${INSET_VAR} - ${FAB_PX + DOCK_PX}px))`
|
|
284
|
+
const placementStyle = (p: AgentFabPlacement): React.CSSProperties =>
|
|
285
|
+
p.kind === 'dock'
|
|
286
|
+
? { left: 'auto', right: 0, top: `clamp(${DOCK_MIN_Y_CSS}, ${Math.max(0, p.y)}px, ${DOCK_MAX_Y_CSS})`, bottom: 'auto' }
|
|
287
|
+
: { left: 'auto', right: INSET_VAR, top: `calc(100% - ${INSET_VAR} - ${FAB_PX}px)`, bottom: 'auto' }
|
|
283
288
|
const inRect = (p: Point, r: Rect, pad: number) =>
|
|
284
289
|
p.x >= r.x - pad && p.x <= r.x + r.w + pad && p.y >= r.y - pad && p.y <= r.y + r.h + pad
|
|
285
290
|
|
|
@@ -315,7 +320,7 @@ const dragPoint = (s: Stage, dx: number, dy: number): Point => ({ x: clamp(dx, 0
|
|
|
315
320
|
* @internal 拖曳磁吸的純函式與常數(無 DOM),只給 `scripts/test-agent-fab-drag-zones.mjs` 單測消費;
|
|
316
321
|
* 不是 consumer API(root barrel 排除,per-component subpath `export *` 仍看得到)。
|
|
317
322
|
*/
|
|
318
|
-
export const AGENT_FAB_DRAG_INTERNALS = { BAND_PX, DOCK_PX, HYSTERESIS, dockMinY, dockMaxY, dragPoint, findZone, SNAP_ZONES } as const
|
|
323
|
+
export const AGENT_FAB_DRAG_INTERNALS = { BAND_PX, DOCK_PX, FAB_PX, HYSTERESIS, dockMinY, dockMaxY, dragPoint, findZone, SNAP_ZONES, placementStyle } as const
|
|
319
324
|
|
|
320
325
|
function readPx(el: HTMLElement | null, variable: string, fallback: number) {
|
|
321
326
|
if (!el) return fallback
|
|
@@ -592,13 +597,24 @@ const AgentFabDock = React.forwardRef<HTMLDivElement, AgentFabDockProps>(
|
|
|
592
597
|
const stage: Stage = { w: size.w, h: size.h, inset }
|
|
593
598
|
const { drag, onPointerDown, onClickCapture } = useSnapDrag({ host, inset, placement, commit: setPlacement })
|
|
594
599
|
const [menuOpen, setMenuOpen] = React.useState(false)
|
|
600
|
+
// 右鍵選單關閉時焦點去哪(2026-09-25,待辦總帳 B11):只有按 Esc 或選了項目才回入口鈕;
|
|
601
|
+
// 點外面 → 留在點的地方、Tab → 由 DropdownMenu 帶到下一站。舊版無條件搶回入口鈕(R8 實測點外面也搶)。
|
|
602
|
+
const returnFocusOnCloseRef = React.useRef(false)
|
|
603
|
+
const openMenu = () => {
|
|
604
|
+
returnFocusOnCloseRef.current = false
|
|
605
|
+
setMenuOpen(true)
|
|
606
|
+
}
|
|
607
|
+
// 真正的開啟者是入口鈕(選單錨點是蓋在鈕上、aria-hidden 的透明 span):鈕以 aria-controls 宣告它開的是這個選單,
|
|
608
|
+
// DropdownMenu 據此從鈕算 Tab 的下一站(dropdown-menu.tsx menuOpener)。
|
|
609
|
+
const menuId = React.useId()
|
|
595
610
|
|
|
596
611
|
const shape: Shape = drag ? (drag.placement?.kind ?? 'home') : placement.kind
|
|
597
612
|
const spec = SHAPES[shape]
|
|
598
613
|
const isDock = shape === 'dock'
|
|
614
|
+
// 拖曳中跟指標(px);靜止位置不吃 stage(CSS 自己對舞台夾,見 placementStyle)
|
|
599
615
|
const posStyle: React.CSSProperties = drag
|
|
600
616
|
? { left: 'auto', right: Math.max(0, stage.w - drag.x - FAB_PX), top: drag.y, bottom: 'auto' }
|
|
601
|
-
: placementStyle(
|
|
617
|
+
: placementStyle(placement)
|
|
602
618
|
/** 帶只在拖 40 圓鈕時可見(拖小鈕不顯示;user 2026-09-03)。 */
|
|
603
619
|
const showZones = drag?.origin === 'home'
|
|
604
620
|
|
|
@@ -614,7 +630,7 @@ const AgentFabDock = React.forwardRef<HTMLDivElement, AgentFabDockProps>(
|
|
|
614
630
|
const onKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
615
631
|
if (e.key === 'F10' && e.shiftKey) {
|
|
616
632
|
e.preventDefault()
|
|
617
|
-
|
|
633
|
+
openMenu()
|
|
618
634
|
return
|
|
619
635
|
}
|
|
620
636
|
if (placement.kind === 'home') {
|
|
@@ -682,7 +698,7 @@ const AgentFabDock = React.forwardRef<HTMLDivElement, AgentFabDockProps>(
|
|
|
682
698
|
dragging ? 'cursor-grabbing select-none' : 'cursor-grab',
|
|
683
699
|
className,
|
|
684
700
|
)}
|
|
685
|
-
//
|
|
701
|
+
// 靜止位置全由 CSS 對舞台錨定(right / clamp),量到舞台尺寸之前就已正確;`ready` 只管要不要開過渡。
|
|
686
702
|
style={posStyle}
|
|
687
703
|
>
|
|
688
704
|
{/* 招喚態光圈:與獨立 AgentFab 同一顆元件;貼邊態省略(半圓貼邊,波會被切一半)。 */}
|
|
@@ -693,21 +709,29 @@ const AgentFabDock = React.forwardRef<HTMLDivElement, AgentFabDockProps>(
|
|
|
693
709
|
)}
|
|
694
710
|
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen} modal={false}>
|
|
695
711
|
{/* 選單只由右鍵 / Shift+F10 開;錨點 = 蓋住鈕的透明 span(pointer-events-none,不攔點擊)。
|
|
696
|
-
關閉後 Radix 會把焦點還給 trigger,但 trigger 是 aria-hidden 的錨點 →
|
|
712
|
+
關閉後 Radix 會把焦點還給 trigger,但 trigger 是 aria-hidden 的錨點 → 需要回鈕時顯式導回真正的按鈕。
|
|
713
|
+
錨點的 aria-controls 改指向實際的選單 id(預設指向 Radix 自產、已被本檔覆寫掉的 id,會是懸空參照)。 */}
|
|
697
714
|
<DropdownMenuTrigger asChild>
|
|
698
|
-
<span aria-hidden className="pointer-events-none absolute inset-0" />
|
|
715
|
+
<span aria-hidden aria-controls={menuOpen ? menuId : undefined} className="pointer-events-none absolute inset-0" />
|
|
699
716
|
</DropdownMenuTrigger>
|
|
700
717
|
<DropdownMenuContent
|
|
718
|
+
id={menuId}
|
|
701
719
|
align="end"
|
|
720
|
+
// B11:Esc = 退一步回開啟者(W3C menu「Escape … return focus to the element … from which the menu was opened」)
|
|
721
|
+
onEscapeKeyDown={() => {
|
|
722
|
+
returnFocusOnCloseRef.current = true
|
|
723
|
+
}}
|
|
702
724
|
onCloseAutoFocus={(e) => {
|
|
725
|
+
// 一律擋掉 Radix 還給透明錨點(聚焦不了 → 焦點會掉到 body);只有 Esc / 選了項目才回入口鈕(B11)
|
|
703
726
|
e.preventDefault()
|
|
704
|
-
buttonRef.current?.focus()
|
|
727
|
+
if (returnFocusOnCloseRef.current) buttonRef.current?.focus()
|
|
728
|
+
returnFocusOnCloseRef.current = false
|
|
705
729
|
}}
|
|
706
730
|
>
|
|
707
731
|
{placement.kind === 'home' ? (
|
|
708
|
-
<DropdownMenuItem startIcon={ArrowRightToLine} onSelect={() => setPlacement({ kind: 'dock', y: dockMaxY(stage) })}>{text.dock}</DropdownMenuItem>
|
|
732
|
+
<DropdownMenuItem startIcon={ArrowRightToLine} onSelect={() => { returnFocusOnCloseRef.current = true; setPlacement({ kind: 'dock', y: dockMaxY(stage) }) }}>{text.dock}</DropdownMenuItem>
|
|
709
733
|
) : (
|
|
710
|
-
<DropdownMenuItem startIcon={ArrowLeftFromLine} onSelect={() => setPlacement(AGENT_FAB_HOME)}>{text.home}</DropdownMenuItem>
|
|
734
|
+
<DropdownMenuItem startIcon={ArrowLeftFromLine} onSelect={() => { returnFocusOnCloseRef.current = true; setPlacement(AGENT_FAB_HOME) }}>{text.home}</DropdownMenuItem>
|
|
711
735
|
)}
|
|
712
736
|
</DropdownMenuContent>
|
|
713
737
|
</DropdownMenu>
|
|
@@ -768,6 +792,7 @@ const AgentFabDock = React.forwardRef<HTMLDivElement, AgentFabDockProps>(
|
|
|
768
792
|
aria-label={buttonLabel}
|
|
769
793
|
aria-haspopup="menu"
|
|
770
794
|
aria-expanded={menuOpen}
|
|
795
|
+
aria-controls={menuOpen ? menuId : undefined}
|
|
771
796
|
// 自有 handler 一律放在 spread **之後**:型別已擋(見 FabOwnedButtonProps),
|
|
772
797
|
// 這裡再擋一層執行期(JS consumer / as any 繞過型別)。
|
|
773
798
|
onPointerDown={onPointerDown}
|
|
@@ -776,7 +801,7 @@ const AgentFabDock = React.forwardRef<HTMLDivElement, AgentFabDockProps>(
|
|
|
776
801
|
onClick={onClick}
|
|
777
802
|
onContextMenu={(e) => {
|
|
778
803
|
e.preventDefault()
|
|
779
|
-
|
|
804
|
+
openMenu()
|
|
780
805
|
}}
|
|
781
806
|
>
|
|
782
807
|
{/* 可視形狀:漸層環 = 這一層自己的 2px padding,內層 span 只負責面色。
|
|
@@ -823,7 +848,11 @@ export interface AgentPanelDockProps
|
|
|
823
848
|
/** 入口鈕位置(受控);省略 = 由本元件保管,面板開關不會忘記(見下方註解)。 */
|
|
824
849
|
/** 面板是否開啟(受控);省略 = 非受控。 */
|
|
825
850
|
open?: boolean
|
|
826
|
-
/**
|
|
851
|
+
/**
|
|
852
|
+
* 非受控初始開關;預設**關**(v14 條 F:初次進入 / 重新整理 / 新開分頁都是關閉的新對話)。
|
|
853
|
+
* 瀏覽器從 back/forward cache 還原舊畫面(`pageshow` 且 `persisted`)時本元件也會關回去(受控時發 `onOpenChange(false)`);
|
|
854
|
+
* 對話本身要不要重設是消費端的事,消費端聽同一個 `pageshow` 事件處理。
|
|
855
|
+
*/
|
|
827
856
|
defaultOpen?: boolean
|
|
828
857
|
onOpenChange?: (open: boolean) => void
|
|
829
858
|
/** 代理狀態:面板標題列與入口鈕標誌吃同一個值(關著面板也照跑)。 */
|
|
@@ -880,6 +909,20 @@ const AgentPanelDock = React.forwardRef<HTMLDivElement, AgentPanelDockProps>(
|
|
|
880
909
|
[placementProp, onPlacementChange],
|
|
881
910
|
)
|
|
882
911
|
const close = React.useCallback(() => setOpen(false), [setOpen])
|
|
912
|
+
// v14 條 F 的最後半句「**以及原分頁離開宿主後返回**,均重新初始化為關閉的新對話」(2026-09-29,待辦總帳 OE2):
|
|
913
|
+
// 從網址列離開宿主再按上一頁回來,瀏覽器可能直接從 back/forward cache 還原整個舊畫面 —— JS 堆疊原封不動、
|
|
914
|
+
// 元件不會重新掛載,`defaultOpen = false` 那條路不會再走一次,面板就帶著離開前的狀態繼續開著。
|
|
915
|
+
// 唯一的訊號是 `pageshow` 且 `event.persisted === true`(一般載入的 pageshow 是 persisted:false,不動)。
|
|
916
|
+
// 「關閉」由本元件做(它是開關的 owner;受控時走 onOpenChange(false) 交給消費端);「新對話」那半句是宿主的
|
|
917
|
+
// (面板不知道對話),宿主聽同一個事件重設,與它自己的「重新整理」同一段(示範 agent-panel.stories.tsx UrlRegistryScene)。
|
|
918
|
+
// 閘:`scripts/agent-url-registry-demo-invariant.mjs` S14(合成 pageshow:persisted:true 必關、persisted:false 必不動)。
|
|
919
|
+
React.useEffect(() => {
|
|
920
|
+
const onPageShow = (event: PageTransitionEvent) => {
|
|
921
|
+
if (event.persisted) setOpen(false)
|
|
922
|
+
}
|
|
923
|
+
window.addEventListener('pageshow', onPageShow)
|
|
924
|
+
return () => window.removeEventListener('pageshow', onPageShow)
|
|
925
|
+
}, [setOpen])
|
|
883
926
|
// ── 2026-09-07 G2:關閉時**不再卸載面板**,改成藏起來 ──────────────────────
|
|
884
927
|
//
|
|
885
928
|
// 原本是 `if (open) return children`,關閉的瞬間整個面板連同它的 state 一起消失:
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# AgentLogo 設計原則(AgentPanel 家族附屬資產;獨立 SSOT)
|
|
2
|
+
|
|
3
|
+
> **本 spec 是 AgentPanel 家族標誌的獨立 SSOT**(2026-09-27 從 `agent-panel.spec.md`「AgentLogo(標誌;附屬資產)」節整段抽出,避免單一 spec 過長(674 行 > 500 硬上限,待辦總帳 N19);內容零改動,只加本檔頭與檔尾指標)。品牌色階常數 `AGENT_BRAND`、家族 Token(`--motion-easing-swell` / `--motion-easing-settle`)與動畫總表仍住 `./agent-panel.spec.md`;程式碼對應 `agent-panel-logo.tsx`,機械閘 `scripts/agent-logo-continuity-invariant.mjs` / `scripts/agent-logo-brand-scale-invariant.mjs`。
|
|
4
|
+
>
|
|
5
|
+
> **Layout Family**:同 `agent-panel.spec.md`(self-contained 容器家族);標誌是 SVG 資產,無 row / pill / field anatomy,本檔不另立 family。
|
|
6
|
+
|
|
7
|
+
## AgentLogo(標誌;附屬資產)
|
|
8
|
+
|
|
9
|
+
- 造型=user 提供黃金比例莫比烏斯 SVG 定稿(內橢圓長短軸比 φ、軸角 121.717°);
|
|
10
|
+
產品用尺寸 16-48(展示 story 放大到 72 以便逐格檢視動畫,不是新的產品級距);**所有尺寸同一造型**(4 層:面 + 底面陰影 + 面 + 提亮;2026-09-02 user 拍板:
|
|
11
|
+
形狀規則、不設簡化檔;原「≤24 自動簡化」與 `detail` prop 已移除)。
|
|
12
|
+
- 一息 3 秒家族(文字微光 2s 另計);緩動=swell(吸/起)/ settle(呼/收)/ exit(加速起步)token 值。
|
|
13
|
+
- **呼吸包絡(招喚與思考共用)**:0 靜 → **35% 吸頂**(脹 1.07、白疊層 14%)→ **85% 回落到底** →
|
|
14
|
+
**85–100% 靜止空拍**;呼出去的波 0–35% 貼邊聚亮 → 35% 離體 → **90% 散盡**(比本體多 0.15s 餘韻)。
|
|
15
|
+
依據:靜息呼吸 12–20 次/分 → 一息 3–5s([NCBI StatPearls](https://www.ncbi.nlm.nih.gov/books/NBK537306/));
|
|
16
|
+
靜息 I:E ≈ 1:2、呼氣為被動回彈且呼氣末有停頓([Deranged Physiology](https://derangedphysiology.com/main/cicm-primary-exam/respiratory-system/Chapter-539/inspiratory-pause-ie-ratio-and-inspiratory-rise-time));
|
|
17
|
+
脹=吸氣([Apple Watch Breathe](https://support.apple.com/guide/watch/start-a-reflect-or-breathe-session-apd371dfe3d7/watchos))。
|
|
18
|
+
**亮度包絡取代透明度包絡**:吸氣亮(白疊層)、呼氣暗;本體不透明度恆 1——白面上變淡=像停用,
|
|
19
|
+
且與 shrink/scale 疊加屬前庭誘因([MDN prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion));
|
|
20
|
+
2026-09-02 user 問「呼吸是否搭配透明度」→ 研究結論採亮度包絡 + 靜止空拍,不加透明度。
|
|
21
|
+
- **每次進入狀態從靜止起跑**:SMIL `begin="indefinite"` + 掛載當下 `beginElement()`(文件時間軸的
|
|
22
|
+
`begin="0s"` 會讓晚掛的動畫從半途起跑、加速段直接凍在終點);同一 commit 掛上的標誌本體與 FAB 光圈同相。
|
|
23
|
+
**起跑鍵必含 exit 段**:think → 減速段新掛的洞形變與亮度淡出同樣是 `begin="indefinite"`,鍵不含該段 = 7 個
|
|
24
|
+
animate 永不起跑(整段減速洞持圓、停定瞬間跳回橢圓;2026-09-03 逐格實測的斷層根因);已起跑的呼吸疊層
|
|
25
|
+
由 `data-begun` 守衛不重啟。機械驗證 = `scripts/agent-logo-continuity-invariant.mjs` C6。
|
|
26
|
+
同一支腳本的 **C7/C8 是純原始碼靜態檢查、不開瀏覽器**,所以任何環境都真的跑(不會被 SKIPPED-ENV 蓋掉):
|
|
27
|
+
C7 從定稿 path 反解外輪廓圓心並比對 `LOGO_CX/LOGO_CY`,同時掃 tsx 內是否還有把 627 當中心用的殘留
|
|
28
|
+
(扣掉註解後全掃,不列舉寫法);C8 擋轉速寫死 —— ω 必須由 `BREATH_S / SPIN_TURNS_PER_BREATH` 推出,
|
|
29
|
+
且一息必須切成整數圈。
|
|
30
|
+
- 狀態(prop `state`;「招喚/漣漪」為本家族新造狀態名,定義唯一住所=本節):
|
|
31
|
+
- **still 靜止(=待機)**:完全不動。2026-09-02 拍板:待機一律靜止,不另立呼吸態,狀態分類收斂為三態。
|
|
32
|
+
- **attract 招喚**(空狀態/FAB 有新訊):呼吸包絡上的脹 1.07+吸氣微亮(白疊層 0→14%→0→0)
|
|
33
|
+
+遮罩單波(雙色放射盤內藍 .5→靛 .44→紫 .34→邊緣 0;行程 560→830;0–35% 貼邊聚亮(swell)、
|
|
34
|
+
35% 離體擴散(settle)、90% 散盡;遮罩護負空間、無模糊)。`ripple={false}` 供 FAB 光圈代位。
|
|
35
|
+
- **think 思考**(=回覆中):靜止起步 → 加速 0.25s(=半圈,exit 曲線,位移 126° 使交接速度連續)
|
|
36
|
+
→ 等速 **720°/s(0.5s/圈 = 一息/6)**,linear,**持續到離開思考,一直思考就不停**;離開思考 →
|
|
37
|
+
**減速段**:從當下角度以 exit 曲線的時間鏡像 (0,0,0.7,1) 續轉最小 ≥252° 且落回正位 0° 的角度
|
|
38
|
+
(時長 Δ/(ω·0.7) ∈ 0.50–1.21s,交接速度連續、停定即正位),**停定即接靜止、不淡入**(見「轉場」)。
|
|
39
|
+
轉速依據(2026-09-04 修訂;取代 2026-09-02 版):比較對象一律取**出貨值**、逐檔讀第一手原始碼,
|
|
40
|
+
由快到慢 —— [Chakra v2 0.45s](https://github.com/chakra-ui/chakra-ui/blob/v2/packages/components/src/spinner/spinner.tsx#L68)、
|
|
41
|
+
[Carbon 690ms](https://github.com/carbon-design-system/carbon/blob/main/packages/styles/scss/components/loading/_animation.scss#L13)、
|
|
42
|
+
[Bootstrap .75s](https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss#L1694)、
|
|
43
|
+
[Radix Themes 800ms](https://github.com/radix-ui/themes/blob/main/packages/radix-ui-themes/src/components/spinner.css#L2)、
|
|
44
|
+
[Primer 1000ms](https://github.com/primer/react/blob/main/packages/react/src/Spinner/Spinner.tsx#L10)、
|
|
45
|
+
[Ant 1.2s](https://github.com/ant-design/ant-design/blob/master/components/spin/style/index.ts#L196)、
|
|
46
|
+
[Fluent 1.5s](https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-spinner/library/src/components/Spinner/useSpinnerStyles.styles.ts#L58)、
|
|
47
|
+
[VS Code 1.5s](https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/ui/codicons/codicon/codicon-modifiers.css#L23)、
|
|
48
|
+
[Material 3 ≈1568ms](https://github.com/material-components/material-web/blob/main/progress/internal/_circular-progress.scss#L47)
|
|
49
|
+
(單層等速旋轉的常數是 `$linear-rotate-duration: calc($arc-duration * 360 / 306)` = 1333 × 360 / 306 ≈ 1568ms;
|
|
50
|
+
`#L43` 的 `$arc-duration: 1333ms` 是弧長形變週期、不是轉速 —— 2026-09-05 更正,2026-09-02 版原本寫對,
|
|
51
|
+
Material 因此移到「由快到慢」最慢端)。
|
|
52
|
+
取 **0.5s/圈**:落在最快檔 0.45–0.69s 之內,不是自創的更快值;唯一更快的出貨值 0.45s 只多 11% 速度,
|
|
53
|
+
卻讓「一息 = N 圈」斷掉(3 ÷ 0.45 = 6.67),故不取。實心雙緞帶比細弧線更吃轉速,取最快檔而非中性帶。
|
|
54
|
+
**閃爍**:造型無 C2 對稱(洞心繞轉軸轉 180° 不落回自身)→ 整圈才重複一次 = 2.0 Hz,低於
|
|
55
|
+
[WCAG 2.3.1](https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html) 的 3 次/秒;
|
|
56
|
+
且 24px 全圖僅 576 px²,遠低於其面積門檻 341×256 = 87,296 px²。
|
|
57
|
+
(2026-09-02 版曾以「600ms = urgent/frantic」定上限,來源是一篇 Jetpack Compose 教學、無研究方法,
|
|
58
|
+
且被誤記為「實測」;2026-09-04 撤除該引用,改以出貨值定範圍。)
|
|
59
|
+
加速未完即離開 → 等加速段結束再減速;<1 影格直接切。+**負空間形變耦合轉速**(起步 0.25s 洞
|
|
60
|
+
橢圓→正圓、同 exit 曲線,轉到最快時洞正好圓;等速持圓;減速段正圓→橢圓、同 DECEL 曲線與時長,停定 0°
|
|
61
|
+
時洞正好回定稿形。2026-09-02 user 問「形變是否用在高速」→ 由 6s 呼吸圓化改為速度耦合:形狀說速度、
|
|
62
|
+
亮度說呼吸;對齊 squash-and-stretch「形變跟著速度」與 Dynamic Island「形隨動作」)+吸氣微亮(亮度
|
|
63
|
+
6s=2 息,峰值 14% 與招喚統一)+色流動(色場定錨畫布=漸層同構逆轉,減速段同步)。
|
|
64
|
+
- **轉心 = 外輪廓圓心 (634.671, 604.106),不是 viewBox 中心 (627, 627)**(2026-09-04 修訂):
|
|
65
|
+
墨色區 = 圓盤(r=505.300)減內橢圓;圓盤圓心由外弧端點反解(SVG 1.1 §F.6.5)得上值,與 viewBox 中心
|
|
66
|
+
差 24.145 單位 = 外半徑的 4.78%。旋轉/縮放/波源全部用它。繞 viewBox 中心會出兩個瑕疵:
|
|
67
|
+
(a) 思考態外緣每圈進出一次,24px 下**峰對峰 0.924px**,讀起來像動畫沒對正、不是轉速;
|
|
68
|
+
(b) 招喚態遮罩(r=512)原想留 6.7 單位等寬餘量,偏心後一側超出本體 17.4、對側多切 30.8,
|
|
69
|
+
光暈與本體邊緣之間出現不對稱死環。三個轉心選項覆算:外輪廓圓心對 viewBox 中心是 **Pareto 支配**
|
|
70
|
+
(外緣晃動 24.145→0,洞公轉半徑 65.051→43.487);移到洞心則外緣晃動反增到 43.487,更糟。
|
|
71
|
+
- **負空間不置中**(2026-09-04;回答「最快速時洞是否該回到標誌中心」):**不要**。最快速時洞已是正圓,
|
|
72
|
+
正圓與圓盤同心 = 完美圓環 = 旋轉不變,輪廓運動歸零 —— 恰好在最需要速度感的一刻把速度訊號關掉;
|
|
73
|
+
且洞心與圓盤心相距 43.487 單位是定稿造型的**剛體不變量**(任何轉心選擇都不改變),要歸零只能改寫
|
|
74
|
+
定稿路徑,等於在最被注視的一刻把黃金比例莫比烏斯換成泛用同心圓環。速度訊號維持由 2026-09-02 拍板的
|
|
75
|
+
「形狀說速度」(橢圓→正圓)承擔,不另加位移通道。
|
|
76
|
+
- 轉場:狀態切換=新狀態 0.15s 淡入(`--motion-duration-overlay`,只動 opacity),禁跳切;**例外:still ↔ think
|
|
77
|
+
不淡入**——兩邊在交接那一刻本來就長得一模一樣(定稿形、0°、無疊層),淡入會讓整顆先變透明再回來 = 斷層
|
|
78
|
+
(2026-09-03 user「最後沒有流暢地把負空間以及色彩回復」根因之一);淡入只留給形態真的不同的交接(招喚 ↔ 其他)。
|
|
79
|
+
減速段的色場基底亦設為當下角度,否則起跑前一影格會閃回 0°。**減動作**:
|
|
80
|
+
互動觸發必可停(WCAG 2.3.3);常駐 loop 全停(嚴於條文),一律回靜止、淡入亦停。
|
|
81
|
+
- 多實例安全:漸層/遮罩 id 以 useId 唯一化。
|
|
82
|
+
|
|
83
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
84
|
+
|
|
85
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
86
|
+
|
|
87
|
+
- `agent-panel.spec.md`
|