@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
|
@@ -4,8 +4,18 @@ import { Upload as UploadIcon, X } from 'lucide-react'
|
|
|
4
4
|
import { cn } from '@/lib/utils'
|
|
5
5
|
import { Empty } from '@/design-system/components/Empty/empty'
|
|
6
6
|
import { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'
|
|
7
|
-
import { FileItem } from '@/design-system/components/FileItem/file-item'
|
|
7
|
+
import { FileItem, FILE_ITEM_RING_LAYER_CLASS, fileItemRingCutoutStyle } from '@/design-system/components/FileItem/file-item'
|
|
8
8
|
import { Button } from '@/design-system/components/Button/button'
|
|
9
|
+
// 「列上有小按鈕的一串」鍵盤路線的唯一判定與執行(與 Sidebar / TreeView / Command 共用;見下方檔案清單鍵盤段)
|
|
10
|
+
import {
|
|
11
|
+
ROVING_CONTROL_SELECTOR,
|
|
12
|
+
applyRovingAction,
|
|
13
|
+
isTextEntryElement,
|
|
14
|
+
listRovingControls,
|
|
15
|
+
pickRovingTabStop,
|
|
16
|
+
removeRovingControlsFromTabOrder,
|
|
17
|
+
resolveRovingKey,
|
|
18
|
+
} from '@/design-system/lib/roving-list-keyboard'
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* FileUpload — 拖放 / 點擊上傳區塊
|
|
@@ -105,7 +115,8 @@ export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
function focusAfterFileRemoval(current: HTMLButtonElement) {
|
|
108
|
-
|
|
118
|
+
// 清單容器兩種元素都可能(唯讀 ul / 可操作 grid div,見下方 fileListNode),用資料屬性找
|
|
119
|
+
const list = current.closest<HTMLElement>('[data-file-upload-list]')
|
|
109
120
|
const buttons = list ? Array.from(list.querySelectorAll<HTMLButtonElement>('[data-collection-remove]')) : []
|
|
110
121
|
const index = buttons.indexOf(current)
|
|
111
122
|
const next = buttons[index + 1] ?? buttons[index - 1]
|
|
@@ -113,6 +124,22 @@ function focusAfterFileRemoval(current: HTMLButtonElement) {
|
|
|
113
124
|
;(next ?? owner)?.focus()
|
|
114
125
|
}
|
|
115
126
|
|
|
127
|
+
// ── 鍵盤:內建檔案清單 = 一個 Tab 停靠點(2026-09-25 待辦總帳 B9「路線乙」)──
|
|
128
|
+
// 決策出處 = governance/planning/2026-09-25-interaction-and-hover-remediation.md B9,user 逐字(附條件同意,條件查證成立記在同列):
|
|
129
|
+
// 「確定建議符合我們一致的設計語言且不違背世界級的設計就照建議」。規則 SSOT = ds-canonical/references/keyboard-model-canonical.md
|
|
130
|
+
// 「列上有小按鈕的一串」;本元件的按鍵表只住 file-upload.spec.md「A11y 預設 › 檔案清單鍵盤」;判定與執行 = lib/roving-list-keyboard.ts
|
|
131
|
+
// (2026-09-26 與 Sidebar / TreeView / Command 四份合一,同檔〇節「按鍵規則合併」)。2026-09-25 前:每一列的「移除」鈕各佔一站(3 個檔案 = 3 站),沒有方向鍵。
|
|
132
|
+
//
|
|
133
|
+
// 實作取捨(AI 推導):
|
|
134
|
+
// - 列(role=row)的 0 / -1 由 React 管(列是本元件自己渲染的);列裡的按鈕一律 -1,但用 DOM 收 ——
|
|
135
|
+
// 列裡除了本元件的移除鈕,還可能有 consumer 經 `description`(ReactNode)放進來的連結,React 管不到。
|
|
136
|
+
// - 只在有 `onRemove`(= 列上有本元件自己的按鈕)時才變成一站 + 方向鍵;沒有 onRemove 的唯讀清單沒有可操作的東西,
|
|
137
|
+
// 列不該是焦點站(focus-canonical「問題一」:不可操作 → 不可聚焦),維持原本的純清單。
|
|
138
|
+
|
|
139
|
+
function getFileRows(list: HTMLElement): HTMLElement[] {
|
|
140
|
+
return Array.from(list.children).filter((el): el is HTMLElement => el instanceof HTMLElement && el.hasAttribute('data-file-upload-row'))
|
|
141
|
+
}
|
|
142
|
+
|
|
116
143
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
117
144
|
const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
|
|
118
145
|
(
|
|
@@ -183,52 +210,153 @@ const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
|
|
|
183
210
|
|
|
184
211
|
const hasFiles = Array.isArray(files) && files.length > 0
|
|
185
212
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
213
|
+
// ── 檔案清單鍵盤(B9 路線乙;實作取捨見上方「鍵盤:內建檔案清單 = 一個 Tab 停靠點」段)──
|
|
214
|
+
// 一站 + 方向鍵只在列上有本元件自己的按鈕(onRemove)時成立;唯讀清單維持純 ul / li。
|
|
215
|
+
const rowKeyboard = hasFiles && !!onRemove
|
|
216
|
+
const listRef = React.useRef<HTMLDivElement>(null)
|
|
217
|
+
// 從外面 Tab 回來落在上次停的那一列;沒停過(或那一列已被移除)→ 第一列
|
|
218
|
+
// (keyboard-model-canonical.md「列上有小按鈕的一串」引 W3C「the element that had focus the last time」)。
|
|
219
|
+
const [rememberedRowId, setRememberedRowId] = React.useState<string | null>(null)
|
|
220
|
+
// 停靠點判定與 Sidebar / TreeView 同一份(X1;lib/roving-list-keyboard.ts `pickRovingTabStop`):上次停的 → 第一列
|
|
221
|
+
const tabStopRowId = rowKeyboard
|
|
222
|
+
? (pickRovingTabStop(files!, { remembered: files!.find((f) => f.id === rememberedRowId) ?? null })?.id ?? null)
|
|
223
|
+
: null
|
|
224
|
+
|
|
225
|
+
// 列裡的按鈕一律不在 Tab 路上(-1;→ 才進得去)。每次 render 後同步一次:列增減、consumer 的 description 換內容都會 re-render。
|
|
226
|
+
React.useLayoutEffect(() => {
|
|
227
|
+
const list = listRef.current
|
|
228
|
+
if (!rowKeyboard || !list) return
|
|
229
|
+
for (const row of getFileRows(list)) removeRovingControlsFromTabOrder(row.querySelectorAll<HTMLElement>(ROVING_CONTROL_SELECTOR))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// 焦點用任何方式進到某一列(Tab / 方向鍵 / 點擊 / 移除後的焦點接力)→ 記住那一列
|
|
233
|
+
const handleFileListFocus = (event: React.FocusEvent<HTMLDivElement>) => {
|
|
234
|
+
const id = (event.target as HTMLElement).closest('[data-file-upload-row]')?.getAttribute('data-file-upload-row')
|
|
235
|
+
if (id && id !== rememberedRowId) setRememberedRowId(id)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 按鍵表 SSOT = file-upload.spec.md「A11y 預設 › 檔案清單鍵盤」(B9);判定與執行 = lib/roving-list-keyboard.ts。
|
|
239
|
+
// 用 capture:方向鍵整串歸清單,搶在列裡的按鈕 / 連結自己的處理之前(同 Sidebar;X6 全宿主一致:
|
|
240
|
+
// consumer 從 description 放進來的選單鈕按 ↓ 也是換列,開選單用 Enter / 空白鍵)。
|
|
241
|
+
const handleFileListKeyDownCapture = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
242
|
+
const list = event.currentTarget
|
|
243
|
+
const target = event.target as HTMLElement
|
|
244
|
+
const row = target.closest<HTMLElement>('[data-file-upload-row]')
|
|
245
|
+
if (!row || row.parentElement !== list) return
|
|
246
|
+
const onRow = target === row
|
|
247
|
+
const controls = listRovingControls(row)
|
|
248
|
+
// 焦點在列裡、但不是列上可走到的東西 → 不接管
|
|
249
|
+
if (!onRow && !controls.includes(target)) return
|
|
250
|
+
const action = resolveRovingKey({
|
|
251
|
+
key: event.key,
|
|
252
|
+
focus: onRow ? 'item' : 'control',
|
|
253
|
+
controlCount: controls.length,
|
|
254
|
+
controlIndex: onRow ? -1 : controls.indexOf(target),
|
|
255
|
+
controlIsTextEntry: !onRow && isTextEntryElement(target),
|
|
256
|
+
defaultPrevented: event.defaultPrevented,
|
|
257
|
+
metaKey: event.metaKey,
|
|
258
|
+
ctrlKey: event.ctrlKey,
|
|
259
|
+
altKey: event.altKey,
|
|
260
|
+
shiftKey: event.shiftKey,
|
|
261
|
+
})
|
|
262
|
+
applyRovingAction(action, { event, item: row, controls, items: getFileRows(list) })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 列的內容兩種清單共用(唯讀清單 ul/li、可操作清單 grid/row)—— FileItem 的 props 只寫一份
|
|
266
|
+
const renderFileRow = (f: FileUploadStatus) => (
|
|
267
|
+
<FileItem
|
|
268
|
+
// grid 的每一列只放一格,列的直接子元素是 gridcell(Fluent List 多動作列與 React Aria GridList 同構,出處見 spec)
|
|
269
|
+
role={rowKeyboard ? 'gridcell' : undefined}
|
|
270
|
+
mode={fileListMode}
|
|
271
|
+
name={f.name}
|
|
272
|
+
status={f.status}
|
|
273
|
+
progress={f.progress}
|
|
274
|
+
description={f.description ?? (f.size != null ? formatBytes(f.size) : undefined)}
|
|
275
|
+
thumbnailSrc={f.thumbnailSrc}
|
|
276
|
+
actions={
|
|
277
|
+
onRemove ? (
|
|
278
|
+
// Collection remove(per-file)— 不是 dismiss surface,故不套 `dismiss` prop。
|
|
279
|
+
// 視覺與 dismiss 一致(text variant + fg-muted dim)— 對齊 inline-action.spec.md
|
|
280
|
+
// 「Dismiss canonical — X close only」+「dismiss prop 觸發條件」段:onRemove callback 不觸發 dismiss prop。
|
|
281
|
+
<Button
|
|
282
|
+
iconOnly
|
|
283
|
+
variant="text"
|
|
284
|
+
size="xs"
|
|
285
|
+
startIcon={X}
|
|
286
|
+
aria-label={removeAriaLabel(f.name)}
|
|
287
|
+
data-collection-remove
|
|
288
|
+
onClick={(e) => {
|
|
289
|
+
e.stopPropagation()
|
|
290
|
+
focusAfterFileRemoval(e.currentTarget)
|
|
291
|
+
onRemove(f.id)
|
|
292
|
+
}}
|
|
293
|
+
className="text-fg-muted hover:text-fg-secondary"
|
|
294
|
+
/>
|
|
295
|
+
) : undefined
|
|
296
|
+
}
|
|
297
|
+
/>
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
const fileListClassName = cn(
|
|
301
|
+
'flex flex-col w-full',
|
|
302
|
+
// 2026-06-03 gap SSOT:列間 gap + control→list gap(mt)由「item 有無邊框」單一規則決定,
|
|
303
|
+
// 同值貫穿整個垂直堆疊 — rich(form surface = border card)→ 8px;compact(borderless/bg-pill)→ 4px。
|
|
304
|
+
// 消費 FileItem「List wrapper canonical」(file-item.spec.md),取代原硬寫 gap-2 / mt-3(不分 mode)。
|
|
305
|
+
fileListMode === 'rich' ? 'gap-2 mt-2' : 'gap-1 mt-1',
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
const fileListNode = !hasFiles ? null : rowKeyboard ? (
|
|
309
|
+
// 列上有按鈕的清單用 grid / row / gridcell 身分(file-upload.spec.md「A11y 預設 › 檔案清單鍵盤」;W3C:listbox 的選項裡不能互動,
|
|
310
|
+
// 「To present a list of interactive elements, see the Grid Pattern」)。用 div 不用 ul / li:HTML 的 ul / li
|
|
311
|
+
// 不允許改成 grid / row 角色(W3C ARIA in HTML 允許角色表;React Aria GridList 同樣渲染 div)。
|
|
312
|
+
// 宣告了 composite 角色就得真的有方向鍵 —— 處理程式 = 上方 handleFileListKeyDownCapture
|
|
313
|
+
// (keyboard-model-canonical.md「宣告了 composite 角色,就必須真的實作那套鍵盤」)。
|
|
314
|
+
<div
|
|
315
|
+
ref={listRef}
|
|
316
|
+
role="grid"
|
|
195
317
|
aria-label="已上傳的檔案"
|
|
318
|
+
data-file-upload-list=""
|
|
319
|
+
className={fileListClassName}
|
|
320
|
+
onKeyDownCapture={handleFileListKeyDownCapture}
|
|
321
|
+
onFocus={handleFileListFocus}
|
|
196
322
|
>
|
|
323
|
+
{files!.map((f) => {
|
|
324
|
+
// 列底貼著 FileItem 的進度條時(compact 且有 status),框改畫在列自己的框圖層 ::before,進度條那一段挖空
|
|
325
|
+
// (file-item.spec.md「焦點框 × 貼著列底的進度條」;挖的位置由 FileItem 給,與進度條同源)。其餘列照舊畫在列上。
|
|
326
|
+
const ringCutout = fileItemRingCutoutStyle({ mode: fileListMode, status: f.status })
|
|
327
|
+
return (
|
|
328
|
+
<div
|
|
329
|
+
key={f.id}
|
|
330
|
+
role="row"
|
|
331
|
+
data-file-upload-row={f.id}
|
|
332
|
+
// 整串只有一列是 0(上次停的那列 / 第一列),其餘 -1
|
|
333
|
+
tabIndex={f.id === tabStopRowId ? 0 : -1}
|
|
334
|
+
// 列拿到焦點時的框:內描邊(focus-canonical「框怎麼畫」:撐滿容器寬度的列往內畫;與 FileItem 自己的整列焦點框
|
|
335
|
+
// 同幾何)。圓角同 FileItem 的 rounded-md,框才貼著列的圓角走。滑鼠點列不畫(:focus-visible 啟發式)。
|
|
336
|
+
className={cn(
|
|
337
|
+
'rounded-md',
|
|
338
|
+
ringCutout
|
|
339
|
+
? ['relative', FILE_ITEM_RING_LAYER_CLASS, 'focus-visible:before:focus-ring-inset']
|
|
340
|
+
: 'focus-visible:focus-ring-inset',
|
|
341
|
+
// @focus-suppress C — 列自己這圈改畫在自己的 ::before 框圖層(上兩行),元素本身不再另畫全域外描邊;承擔者:file-upload.tsx:339
|
|
342
|
+
ringCutout && 'focus-visible:outline-none',
|
|
343
|
+
)}
|
|
344
|
+
style={ringCutout}
|
|
345
|
+
>
|
|
346
|
+
{renderFileRow(f)}
|
|
347
|
+
</div>
|
|
348
|
+
)
|
|
349
|
+
})}
|
|
350
|
+
</div>
|
|
351
|
+
) : (
|
|
352
|
+
<ul className={fileListClassName} aria-label="已上傳的檔案" data-file-upload-list="">
|
|
197
353
|
{files!.map((f) => (
|
|
198
354
|
<li key={f.id} className="list-none">
|
|
199
|
-
|
|
200
|
-
mode={fileListMode}
|
|
201
|
-
name={f.name}
|
|
202
|
-
status={f.status}
|
|
203
|
-
progress={f.progress}
|
|
204
|
-
description={f.description ?? (f.size != null ? formatBytes(f.size) : undefined)}
|
|
205
|
-
thumbnailSrc={f.thumbnailSrc}
|
|
206
|
-
actions={
|
|
207
|
-
onRemove ? (
|
|
208
|
-
// Collection remove(per-file)— 不是 dismiss surface,故不套 `dismiss` prop。
|
|
209
|
-
// 視覺與 dismiss 一致(text variant + fg-muted dim)— 對齊 inline-action.spec.md
|
|
210
|
-
// 「Dismiss canonical — X close only」+「dismiss prop 觸發條件」段:onRemove callback 不觸發 dismiss prop。
|
|
211
|
-
<Button
|
|
212
|
-
iconOnly
|
|
213
|
-
variant="text"
|
|
214
|
-
size="xs"
|
|
215
|
-
startIcon={X}
|
|
216
|
-
aria-label={removeAriaLabel(f.name)}
|
|
217
|
-
data-collection-remove
|
|
218
|
-
onClick={(e) => {
|
|
219
|
-
e.stopPropagation()
|
|
220
|
-
focusAfterFileRemoval(e.currentTarget)
|
|
221
|
-
onRemove(f.id)
|
|
222
|
-
}}
|
|
223
|
-
className="text-fg-muted hover:text-fg-secondary"
|
|
224
|
-
/>
|
|
225
|
-
) : undefined
|
|
226
|
-
}
|
|
227
|
-
/>
|
|
355
|
+
{renderFileRow(f)}
|
|
228
356
|
</li>
|
|
229
357
|
))}
|
|
230
358
|
</ul>
|
|
231
|
-
)
|
|
359
|
+
)
|
|
232
360
|
|
|
233
361
|
return (
|
|
234
362
|
<div ref={ref} className={cn('w-full', hasFiles && 'flex flex-col')}>
|
|
@@ -318,7 +446,9 @@ const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
|
|
|
318
446
|
// 對稱 padding p-[var(--layout-space-loose)]:四邊等距,density-aware(md=16px / lg=24px),對齊 DS chrome padding canonical。
|
|
319
447
|
// 不再硬寫 px-6 py-10(不對稱+非 token)。內容物(icon + title + description)垂直堆疊由 gap-2 控制
|
|
320
448
|
'rounded-md border-2 border-dashed p-[var(--layout-space-loose)]',
|
|
321
|
-
|
|
449
|
+
// 滑過 / 拖進來的邊框變色瞬間切換,不做過渡(2026-09-26 待辦總帳 L9「全部瞬間」延伸到外框,user:「確定這樣才是一致設計語言就做」;
|
|
450
|
+
// 同一次滑過底色瞬間、外框卻 0.15 秒 = 兩套手感;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)
|
|
451
|
+
'cursor-pointer',
|
|
322
452
|
// idle:--border(元件邊框,非 --divider 分隔線 — 2026-06-03 Q2 token 修正)+ surface 底
|
|
323
453
|
'border-border bg-surface',
|
|
324
454
|
// hover = drag-over 統一(2026-06-03 Q2-A 純 border-driven,對齊 Ant Dragger colorPrimaryHover):
|
|
@@ -415,7 +545,7 @@ export const fileUploadMeta = {
|
|
|
415
545
|
tokens: {
|
|
416
546
|
bg: ['bg-surface', 'bg-disabled'],
|
|
417
547
|
fg: [],
|
|
418
|
-
ring: ['ring
|
|
548
|
+
ring: ['--ring'],
|
|
419
549
|
},
|
|
420
550
|
} as const
|
|
421
551
|
|
|
@@ -636,7 +636,10 @@ const Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect,
|
|
|
636
636
|
data-thumb-index={i}
|
|
637
637
|
onClick={() => onSelect(i)}
|
|
638
638
|
className={cn(
|
|
639
|
-
|
|
639
|
+
// 底色不掛在按鈕上:`bg-muted` 是「靜態非互動 surface」(color.spec.md「能不能當可互動元素的靜止底」:
|
|
640
|
+
// `--muted` 不可以),縮圖鈕是可點的;它只當**佔位層**(沒有預覽圖時的檔案圖示 + 副檔名)的底,
|
|
641
|
+
// 有圖時圖填滿整格、看不到底(待辦總帳 A9③,2026-09-29;畫面不變)。
|
|
642
|
+
'shrink-0 rounded-md overflow-hidden',
|
|
640
643
|
// 2026-09-07 C14:選中與鍵盤焦點原本都是 `ring-2` + `--ring: var(--primary)`
|
|
641
644
|
// (semantic.css:337 是全 repo 唯一定義)→ 已選中的縮圖被聚焦時 **0 像素變化**,
|
|
642
645
|
// 違反 APG「selected 必須與 focus 指示器在視覺上可區分」。
|
|
@@ -649,7 +652,9 @@ const Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect,
|
|
|
649
652
|
// 不可寫 `outline-none`(它把 --tw-outline-style 設成 none,會讓下面三條靜默失效,
|
|
650
653
|
// 見 steps.tsx 同款事故)。
|
|
651
654
|
'focus-visible:focus-ring-inset',
|
|
652
|
-
|
|
655
|
+
// 不寫 transition-shadow:滑過外框(ring-border → ring-border-hover)一律瞬間(motion.spec.md「hover 回饋不做過渡」,
|
|
656
|
+
// 2026-09-26 L9 延伸,user:「確定這樣才是一致設計語言就做」)。同一條 ring 也畫選中框,所以選中切換跟著瞬間 ——
|
|
657
|
+
// 同 Chip / SegmentedControl 的選中色(motion.spec.md「同一組屬性若也被選中改變,一併瞬間」)。
|
|
653
658
|
active
|
|
654
659
|
? 'ring-2 ring-primary'
|
|
655
660
|
: 'ring-1 ring-border hover:ring-border-hover',
|
|
@@ -666,7 +671,7 @@ const Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect,
|
|
|
666
671
|
draggable={false}
|
|
667
672
|
/>
|
|
668
673
|
) : (
|
|
669
|
-
<div className="w-full h-full flex flex-col items-center justify-center gap-0.5">
|
|
674
|
+
<div className="w-full h-full flex flex-col items-center justify-center gap-0.5 bg-muted">
|
|
670
675
|
<FileText size={20} className="text-fg-muted" aria-hidden />
|
|
671
676
|
<span className="text-footnote text-fg-muted font-medium">{ext}</span>
|
|
672
677
|
</div>
|
|
@@ -1216,7 +1221,7 @@ export const fileViewerMeta = {
|
|
|
1216
1221
|
tokens: {
|
|
1217
1222
|
bg: ['bg-muted', 'bg-neutral-selected', 'bg-overlay', 'bg-surface-raised'],
|
|
1218
1223
|
fg: ['text-fg-muted', 'text-foreground'],
|
|
1219
|
-
ring: ['
|
|
1224
|
+
ring: ['focus-ring-inset'],
|
|
1220
1225
|
},
|
|
1221
1226
|
} as const
|
|
1222
1227
|
|
|
@@ -44,7 +44,7 @@ export const PersonProfileCard: Story = {
|
|
|
44
44
|
<span className="text-caption text-fg-muted">Reviewer:</span>
|
|
45
45
|
<HoverCard>
|
|
46
46
|
<HoverCardTrigger asChild>
|
|
47
|
-
<button type="button" aria-label="Ada Chen 個人資訊" className="cursor-
|
|
47
|
+
<button type="button" aria-label="Ada Chen 個人資訊" className="cursor-default rounded-full">
|
|
48
48
|
<Avatar src="https://i.pravatar.cc/56?u=ada-chen" alt="Ada Chen" color="indigo" size={28} />
|
|
49
49
|
</button>
|
|
50
50
|
</HoverCardTrigger>
|
|
@@ -249,7 +249,7 @@ export const TriggerShowcase: Story = {
|
|
|
249
249
|
<span className="text-footnote text-fg-muted font-mono">trigger: Avatar</span>
|
|
250
250
|
<HoverCard openDelay={MOTION_DELAY_RICH_MS} closeDelay={MOTION_DELAY_CLOSE_MS}>
|
|
251
251
|
<HoverCardTrigger asChild>
|
|
252
|
-
<button type="button" aria-label="Ada Chen 個人資訊" className="cursor-
|
|
252
|
+
<button type="button" aria-label="Ada Chen 個人資訊" className="cursor-default rounded-full">
|
|
253
253
|
<Avatar src="https://i.pravatar.cc/64?u=ada-chen" alt="Ada Chen" color="indigo" size={32} />
|
|
254
254
|
</button>
|
|
255
255
|
</HoverCardTrigger>
|
|
@@ -72,6 +72,8 @@ InlineEdit 本體只給:**對齊盒(-mx orientation-aware)+ 委派 view 統一 f
|
|
|
72
72
|
|
|
73
73
|
**「底色範圍 = 輸入框範圍」不變量**:view 與 edit 兩態共用同一對齊盒(同寬、同 1px 邊框盒),幾何同源(view×default = edit 幾何減 chrome),故 hover 底色與 edit 輸入框逐 pixel 對齊、態切換零位移。(舊版 read 外框 `w-full`+`-mx` 疊加 → 右側短一截、整塊左偏,已修;2026-07-16 -mx 由恆定改條件化;2026-07-17 進一步移除 fieldCtx gate → 純 orientation-aware,委派 view 統一 fieldViewGeometry field-px,standalone 也對齊。)
|
|
74
74
|
|
|
75
|
+
**點得到的範圍 = 底色範圍**(2026-09-26,待辦總帳 N53⑤):隱形 Pressable 用 `-inset-px` 蓋滿外框的 border box(含那圈 1px 透明邊框),滑過底色亮到哪、點下去就進 edit 到哪。先前是 `inset-0`,只蓋到 padding box —— 外圈 1px 亮著卻點不進 edit(實測一般螢幕左 / 右 / 下三邊、Retina 8 點中 5 點),正是 `../../ds-canonical/references/hit-area-canonical.md` 要防的「看到亮起來卻點不到」。修的是命中區、不是底色:底色與 edit 輸入框逐 pixel 對齊是本條不變量,不能縮。畫面不變,只有那 1px 的游標與點擊結果改變。世界級對照:Atlassian inline-edit 的讀取態把點擊與滑過底色掛在**同一個元素**上,兩者天生同一個盒([`read-view.js#L56`](https://cdn.jsdelivr.net/npm/@atlaskit/inline-edit@16.4.5/dist/es2019/internal/read-view.js) `onClick: onReadViewClick`、同一個 div 的 `className`(#L62)帶滑過樣式)。
|
|
76
|
+
|
|
75
77
|
**尺寸預設 = `sm`**(2026-07-09 user 拍板):view 態無邊框、視覺即純文字,尺寸過大會使版面鬆散;consumer 可傳 `size` 覆寫,標題場景另用 `readClassName` 疊大字級。
|
|
76
78
|
|
|
77
79
|
**在 `<Field>` 內也是 `sm`**(2026-07-28 user 拍板):Field 的預設 `md` 是「沒人決定過」的預設值,不得壓過本元件宣告的 `sm`。實作雙通道 —— 靜態 `InlineEdit.fieldPreferredSize = 'sm'` 供 Field 直接子元件同步偵測,加上掛載時的 `useRegisterFieldPreferredSize('sm')` 補位(包 wrapper / `React.memo` / HOC 會遮蔽靜態);控件自身尺寸則由 `useResolvedFieldSize` 依 `sizeExplicit` 同步解析,不等註冊。**唯有 consumer 顯式 `size`(在 Field 或 InlineEdit 上)才覆寫**。機制與失效語意 owner → `Field/field.spec.md`「Control 偏好尺寸 SSOT」。
|
|
@@ -400,15 +400,20 @@ function InlineEditImpl<T = string>(
|
|
|
400
400
|
>
|
|
401
401
|
{viewNode}
|
|
402
402
|
{/* 隱形 Pressable(僅 editable):提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。
|
|
403
|
-
hover 底色 + focus 藍框由外層 div 承載(Field focus 語言),故本 button 只需 outline-none 消瀏覽器預設外框。
|
|
403
|
+
hover 底色 + focus 藍框由外層 div 承載(Field focus 語言),故本 button 只需 outline-none 消瀏覽器預設外框。
|
|
404
|
+
`-inset-px`(不是 inset-0):外層 div 有 1px 透明邊框,滑過底色塗滿整個 border box(含那 1px),
|
|
405
|
+
而 absolute 的 inset-0 只蓋到 padding box —— 外圈那 1px 亮著、點了卻不進 edit(待辦總帳 N53⑤;
|
|
406
|
+
一般螢幕左 / 右 / 下三邊、Retina 8 點中 5 點點不到)。往外多蓋 1px = 恰好等於外框的 border box,
|
|
407
|
+
「亮的範圍 = 點得到的範圍」(inline-edit.spec.md「點得到的範圍 = 底色範圍」;hit-area-canonical.md「看到亮起來卻點不到」)。
|
|
408
|
+
不縮滑過底色:底色與 edit 輸入框逐 pixel 對齊是既有不變量,動的只能是命中區。 */}
|
|
404
409
|
{editable && (
|
|
405
410
|
<button
|
|
406
411
|
ref={readButtonRef}
|
|
407
412
|
type="button"
|
|
408
413
|
aria-label={label ? `編輯 ${label}` : '編輯'}
|
|
409
414
|
onClick={enterEdit}
|
|
410
|
-
// @focus-suppress C — C 隱形整列觸發器;承擔者:指示器在外層 div(見上方 :
|
|
411
|
-
className="absolute inset-
|
|
415
|
+
// @focus-suppress C — C 隱形整列觸發器;承擔者:指示器在外層 div(見上方 :397 的 `[&:has(button:focus-visible)]:border-primary`)
|
|
416
|
+
className="absolute -inset-px cursor-text rounded-md focus-visible:outline-none"
|
|
412
417
|
/>
|
|
413
418
|
)}
|
|
414
419
|
</div>
|
|
@@ -2,6 +2,7 @@ import type { Meta } from '@storybook/react'
|
|
|
2
2
|
import { useState, useEffect } from 'react'
|
|
3
3
|
import { Search, Mail, X, Eye, EyeOff } from 'lucide-react'
|
|
4
4
|
import { Input } from './input'
|
|
5
|
+
import { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'
|
|
5
6
|
|
|
6
7
|
const meta: Meta = {
|
|
7
8
|
title: 'Design System/Components/Input/設計規格',
|
|
@@ -115,18 +116,6 @@ const TokenAnnotation = ({ colors }: { colors: ColorSpec }) => (
|
|
|
115
116
|
</div>
|
|
116
117
|
)
|
|
117
118
|
|
|
118
|
-
const Tab = ({ active, onClick, disabled, children }: { active: boolean; onClick: () => void; disabled?: boolean; children: React.ReactNode }) => {
|
|
119
|
-
if (disabled) return <span className="px-2.5 py-1 text-[12px] font-mono rounded-md text-fg-disabled bg-neutral-hover cursor-not-allowed">{children}</span>
|
|
120
|
-
return (
|
|
121
|
-
<button type="button" onClick={onClick}
|
|
122
|
-
className={`px-2.5 py-1 text-[12px] font-mono rounded-md cursor-pointer transition-colors ${
|
|
123
|
-
active ? 'bg-primary text-white font-semibold' : 'bg-neutral-hover text-fg-secondary hover:bg-neutral-active'
|
|
124
|
-
}`}>
|
|
125
|
-
{children}
|
|
126
|
-
</button>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
119
|
const PropRow = ({ label, dot, children }: { label: string; dot?: string; children: React.ReactNode }) => (
|
|
131
120
|
<div className="flex items-start gap-3 py-2 border-b border-divider last:border-b-0">
|
|
132
121
|
<span className="text-[11px] text-fg-muted font-medium w-[72px] shrink-0 pt-0.5 flex items-center gap-1.5">
|
|
@@ -284,41 +273,44 @@ const InspectorInner = () => {
|
|
|
284
273
|
|
|
285
274
|
return (
|
|
286
275
|
<div className="flex flex-col gap-6">
|
|
287
|
-
{/* Controls
|
|
276
|
+
{/* Controls — 互斥切換消費 SegmentedControl(segmented-control.spec.md「何時用」2–5 個互斥選項;
|
|
277
|
+
取代手刻 Tab:它靜止借 neutral-hover、hover 借 neutral-active,是 color.spec.md 成對 token 的錯配) */}
|
|
288
278
|
<div className="flex flex-col gap-2.5">
|
|
289
279
|
<div className="flex items-center gap-2">
|
|
290
280
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">Mode</span>
|
|
291
|
-
<
|
|
292
|
-
{MODES.map((m) => <
|
|
293
|
-
</
|
|
281
|
+
<SegmentedControl size="sm" aria-label="Mode" value={mode} onValueChange={(v) => setMode(v as ModeKey)}>
|
|
282
|
+
{MODES.map((m) => <SegmentedControlItem key={m} value={m}>{m}</SegmentedControlItem>)}
|
|
283
|
+
</SegmentedControl>
|
|
294
284
|
</div>
|
|
295
285
|
<div className="flex items-center gap-2">
|
|
296
286
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">Size</span>
|
|
297
|
-
<
|
|
298
|
-
{SIZES.map((sz) => <
|
|
299
|
-
</
|
|
287
|
+
<SegmentedControl size="sm" aria-label="Size" value={size} onValueChange={(v) => setSize(v as SizeKey)}>
|
|
288
|
+
{SIZES.map((sz) => <SegmentedControlItem key={sz} value={sz}>{sz}</SegmentedControlItem>)}
|
|
289
|
+
</SegmentedControl>
|
|
300
290
|
</div>
|
|
301
291
|
<div className="flex items-center gap-2">
|
|
302
292
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">Error</span>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<
|
|
306
|
-
|
|
293
|
+
{/* 停用項用元件自身 disabled(segmented-control.spec.md「disabled」);停用項不得是當前值 → value 取實際生效值 */}
|
|
294
|
+
<SegmentedControl size="sm" aria-label="Error" value={error && isEdit ? 'on' : 'off'} onValueChange={(v) => setError(v === 'on')}>
|
|
295
|
+
<SegmentedControlItem value="off">off</SegmentedControlItem>
|
|
296
|
+
<SegmentedControlItem value="on" disabled={!isEdit}>on</SegmentedControlItem>
|
|
297
|
+
</SegmentedControl>
|
|
307
298
|
{!isEdit && <span className="text-[11px] text-fg-muted">僅 edit 模式</span>}
|
|
308
299
|
</div>
|
|
309
300
|
<div className="flex items-center gap-2">
|
|
310
301
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">startIcon</span>
|
|
311
|
-
<
|
|
312
|
-
<
|
|
313
|
-
<
|
|
314
|
-
</
|
|
302
|
+
<SegmentedControl size="sm" aria-label="startIcon" value={hasStartIcon ? 'on' : 'off'} onValueChange={(v) => setHasStartIcon(v === 'on')}>
|
|
303
|
+
<SegmentedControlItem value="off">off</SegmentedControlItem>
|
|
304
|
+
<SegmentedControlItem value="on">on</SegmentedControlItem>
|
|
305
|
+
</SegmentedControl>
|
|
315
306
|
</div>
|
|
316
307
|
<div className="flex items-center gap-2">
|
|
317
308
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">endAction</span>
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
<
|
|
321
|
-
|
|
309
|
+
{/* 同上:停用項不得是當前值 → value 取實際生效值(預覽也是 hasEndAction && isEdit) */}
|
|
310
|
+
<SegmentedControl size="sm" aria-label="endAction" value={hasEndAction && isEdit ? 'on' : 'off'} onValueChange={(v) => setHasEndAction(v === 'on')}>
|
|
311
|
+
<SegmentedControlItem value="off">off</SegmentedControlItem>
|
|
312
|
+
<SegmentedControlItem value="on" disabled={!isEdit}>on</SegmentedControlItem>
|
|
313
|
+
</SegmentedControl>
|
|
322
314
|
{!isEdit && <span className="text-[11px] text-fg-muted">僅 edit 模式</span>}
|
|
323
315
|
</div>
|
|
324
316
|
</div>
|
|
@@ -142,6 +142,17 @@ Input 有**一個公開** visual chrome variant `default`(+ 一個 `@internal`
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
|
+
## 點外框 = 點輸入處(2026-09-26,待辦總帳 N53③)
|
|
146
|
+
|
|
147
|
+
`default` 外框「明確邀請輸入」(上表),所以**外框裡任何不是按鈕 / 連結的地方,點下去都聚焦輸入處**:外框內距(N53 實測左 13px、上 5.5px,含 1px 邊框)、1px 邊框本身、前置 `startIcon`。`endAction` / `endSlot` 的按鈕照它自己的行為走,不搶焦點。外框用文字游標(`cursor-text`);停用時不掛(停用自有 `cursor-not-allowed`)。
|
|
148
|
+
|
|
149
|
+
- **為什麼是 bug 不是新規則**:規則早就寫在 `../Field/field-controls.spec.md`「點擊與游標原則」——「讓點擊穿透到底層的 input/select,確保使用者點擊 Field 內任何位置都能 focus/activate」、游標「input → `cursor-text`」。缺的是外框自己的內距與邊框:底下沒有 input 可以穿透,指到時外框變色(`hover:border-border-hover`),點下去卻不聚焦 —— 正是 `../../ds-canonical/references/hit-area-canonical.md` 要防的「看到亮起來卻點不到」。同家族 Select / Combobox / TimePicker / PeoplePicker / Textarea 的外框本身就是點擊目標,本來就沒有這個缺口。
|
|
150
|
+
- **實作只有一份**:`../Field/field-wrapper.tsx` 的 `focusFieldInputFromChrome`(外框 `onMouseDown`)+ `FIELD_TEXT_ENTRY_CURSOR`。所有「外框包著一個 `<input>`」的可打字控件(Input、NumberInput、LinkInput 編輯態)消費同一支,不各寫一份。用 mousedown 而不是 click:click 之前焦點會先離開輸入處(blur → focus 閃一下、`onBlur` 驗證誤觸)。插入點落在點的那一側:點在輸入處右邊放最後、左邊放最前。
|
|
151
|
+
- **畫面**:外框顏色、焦點框都不變(聚焦後的藍框與直接點輸入處相同);只有游標與點擊結果改變。
|
|
152
|
+
- **世界級對照**:[MUI InputBase `InputBase.js#L478-L481`](https://github.com/mui/material-ui/blob/v9.4.0/packages/mui-material/src/InputBase/InputBase.js#L478-L481)(點根元素 → `inputRef.current.focus()`)、根元素 [`cursor: 'text'`(#L105)](https://github.com/mui/material-ui/blob/v9.4.0/packages/mui-material/src/InputBase/InputBase.js#L105);[Primer `TextInput.tsx#L150-L154`](https://github.com/primer/react/blob/%40primer/react%4038.40.0/packages/react/src/TextInput/TextInput.tsx#L150-L154)(外框 `onClick={focusInput}`,[#L217](https://github.com/primer/react/blob/%40primer/react%4038.40.0/packages/react/src/TextInput/TextInput.tsx#L217));[rc-input(Ant Design)`BaseInput.tsx#L48-L51`](https://github.com/react-component/input/blob/v1.8.0/src/BaseInput.tsx#L48-L51)(容器內任何點擊 → `triggerFocus`)。
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
145
156
|
## Auto-width(AR46,2026-04-21)
|
|
146
157
|
|
|
147
158
|
`autoWidth` prop:Input 寬度自動等於「內容寬(value / placeholder)+ startIcon + endAction + padding」,基於 CSS `field-sizing: content`(Chrome 123+ / Safari 17.4+)。
|
|
@@ -4,7 +4,7 @@ import { type VariantProps } from 'class-variance-authority'
|
|
|
4
4
|
import type { LucideIcon } from 'lucide-react'
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
6
|
import type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'
|
|
7
|
-
import { fieldWrapperStyles, bareInputStyles } from '@/design-system/components/Field/field-wrapper'
|
|
7
|
+
import { fieldWrapperStyles, bareInputStyles, focusFieldInputFromChrome, FIELD_TEXT_ENTRY_CURSOR } from '@/design-system/components/Field/field-wrapper'
|
|
8
8
|
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'
|
|
9
9
|
import { useControllable } from '@/design-system/hooks/use-controllable'
|
|
10
10
|
import { ItemInlineAction, ItemPrefix, type InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
@@ -203,10 +203,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
203
203
|
<div
|
|
204
204
|
className={cn(
|
|
205
205
|
fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),
|
|
206
|
+
// 整個外框都是輸入處:文字游標 + 點內距 / 邊框 / 前置圖示就聚焦 input(待辦總帳 N53③;
|
|
207
|
+
// 規則 owner field-controls.spec.md「點擊與游標原則」;共用實作 field-wrapper.tsx focusFieldInputFromChrome)。
|
|
208
|
+
// 停用不掛:停用的外框自己是 cursor-not-allowed(field-wrapper disabled compound),而且不接受焦點。
|
|
209
|
+
resolvedMode !== 'disabled' && FIELD_TEXT_ENTRY_CURSOR,
|
|
206
210
|
// autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加
|
|
207
211
|
autoWidth && 'inline-flex w-auto',
|
|
208
212
|
className,
|
|
209
213
|
)}
|
|
214
|
+
onMouseDown={focusFieldInputFromChrome}
|
|
210
215
|
data-field-mode={resolvedMode}
|
|
211
216
|
data-error={isEditable && resolvedError ? '' : undefined}
|
|
212
217
|
aria-busy={loading || undefined}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Meta } from '@storybook/react'
|
|
7
7
|
import { useState, useEffect } from 'react'
|
|
8
8
|
import { LinkInput } from './link-input'
|
|
9
|
+
import { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'
|
|
9
10
|
|
|
10
11
|
const meta: Meta = {
|
|
11
12
|
title: 'Design System/Components/LinkInput/設計規格',
|
|
@@ -148,15 +149,6 @@ const TokenAnnotation = ({ colors }: { colors: ColorSpec }) => (
|
|
|
148
149
|
</div>
|
|
149
150
|
)
|
|
150
151
|
|
|
151
|
-
const Tab = ({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) => (
|
|
152
|
-
<button type="button" onClick={onClick}
|
|
153
|
-
className={`px-2.5 py-1 text-[12px] font-mono rounded-md cursor-pointer transition-colors ${
|
|
154
|
-
active ? 'bg-primary text-white font-semibold' : 'bg-neutral-hover text-fg-secondary hover:bg-neutral-active'
|
|
155
|
-
}`}>
|
|
156
|
-
{children}
|
|
157
|
-
</button>
|
|
158
|
-
)
|
|
159
|
-
|
|
160
152
|
const PropRow = ({ label, dot, children }: { label: string; dot?: string; children: React.ReactNode }) => (
|
|
161
153
|
<div className="flex items-start gap-3 py-2 border-b border-divider last:border-b-0">
|
|
162
154
|
<span className="text-[11px] text-fg-muted font-medium w-[72px] shrink-0 pt-0.5 flex items-center gap-1.5">
|
|
@@ -277,7 +269,7 @@ export const Overview = {
|
|
|
277
269
|
{/* Interaction flow */}
|
|
278
270
|
<div className="flex flex-col gap-3">
|
|
279
271
|
<H3>互動流程</H3>
|
|
280
|
-
<Desc>edit 模式下的狀態轉換。核心差異:點擊 value
|
|
272
|
+
<Desc>edit 模式下的狀態轉換。核心差異:點擊 value(網址文字)開啟連結,不是進入編輯——編輯由 Pencil 或點外框裡文字以外的空白處觸發。</Desc>
|
|
281
273
|
<div className="overflow-x-auto">
|
|
282
274
|
<table className="text-caption border-collapse">
|
|
283
275
|
<thead><tr><Th>觸發</Th><Th>效果</Th></tr></thead>
|
|
@@ -285,6 +277,8 @@ export const Overview = {
|
|
|
285
277
|
{[
|
|
286
278
|
['點擊 link text', '開啟連結(target="_blank")'],
|
|
287
279
|
['點擊 Pencil', '切換到 input 狀態,自動 focus'],
|
|
280
|
+
['點擊外框空白處(網址文字以外)', '同 Pencil:切換到 input 狀態,自動 focus'],
|
|
281
|
+
['從空白處拖過網址文字(選字)', '不進入編輯,保留選取'],
|
|
288
282
|
['blur(合法 URL)', '切回 link 狀態,觸發 onChange'],
|
|
289
283
|
['blur(不合法 URL)', '維持 input 狀態 + error 邊框'],
|
|
290
284
|
['blur(空值)', '清除值,顯示 placeholder'],
|
|
@@ -322,30 +316,33 @@ const InspectorInner = () => {
|
|
|
322
316
|
|
|
323
317
|
return (
|
|
324
318
|
<div className="flex flex-col gap-6">
|
|
325
|
-
{/* Controls
|
|
319
|
+
{/* Controls — 互斥切換消費 SegmentedControl(segmented-control.spec.md「何時用」2–5 個互斥選項;
|
|
320
|
+
取代手刻 Tab:它靜止借 neutral-hover、hover 借 neutral-active,是 color.spec.md 成對 token 的錯配) */}
|
|
326
321
|
<div className="flex flex-col gap-2.5">
|
|
327
322
|
<div className="flex items-center gap-2">
|
|
328
323
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">Mode</span>
|
|
329
|
-
<
|
|
330
|
-
{MODES.map((m) => <
|
|
331
|
-
</
|
|
324
|
+
<SegmentedControl size="sm" aria-label="Mode" value={mode} onValueChange={(v) => setMode(v as ModeKey)}>
|
|
325
|
+
{MODES.map((m) => <SegmentedControlItem key={m} value={m}>{m}</SegmentedControlItem>)}
|
|
326
|
+
</SegmentedControl>
|
|
332
327
|
</div>
|
|
333
328
|
<div className="flex items-center gap-2">
|
|
334
329
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">State</span>
|
|
335
|
-
|
|
330
|
+
{/* 非 edit 模式只有 link 狀態:原本是「點了沒反應」的靜默 no-op,改用元件自身的個別 item disabled
|
|
331
|
+
(segmented-control.spec.md「disabled」);當前值恆為 link,不會落在停用項上 */}
|
|
332
|
+
<SegmentedControl size="sm" aria-label="State" value={editState} onValueChange={(v) => setEditState(v as EditStateKey)}>
|
|
336
333
|
{EDIT_STATES.map((st) => (
|
|
337
|
-
<
|
|
334
|
+
<SegmentedControlItem key={st} value={st} disabled={mode !== 'edit' && st !== 'link'}>
|
|
338
335
|
{st}
|
|
339
|
-
</
|
|
336
|
+
</SegmentedControlItem>
|
|
340
337
|
))}
|
|
341
|
-
</
|
|
338
|
+
</SegmentedControl>
|
|
342
339
|
{mode !== 'edit' && <span className="text-[11px] text-fg-muted">readonly / disabled 只有 link 狀態</span>}
|
|
343
340
|
</div>
|
|
344
341
|
<div className="flex items-center gap-2">
|
|
345
342
|
<span className="text-[11px] text-fg-muted w-16 shrink-0">Size</span>
|
|
346
|
-
<
|
|
347
|
-
{SIZES.map((sz) => <
|
|
348
|
-
</
|
|
343
|
+
<SegmentedControl size="sm" aria-label="Size" value={size} onValueChange={(v) => setSize(v as SizeKey)}>
|
|
344
|
+
{SIZES.map((sz) => <SegmentedControlItem key={sz} value={sz}>{sz}</SegmentedControlItem>)}
|
|
345
|
+
</SegmentedControl>
|
|
349
346
|
</div>
|
|
350
347
|
</div>
|
|
351
348
|
|
|
@@ -69,7 +69,7 @@ export const UsageGuidance: Story = {
|
|
|
69
69
|
<div>
|
|
70
70
|
<Rule
|
|
71
71
|
title="LinkInput — 有合法 URL 時變藍色連結,點擊開啟新分頁"
|
|
72
|
-
note="個人資料的 website、文件參考連結、專案 repo URL 等場景。儲存後使用者可直接點擊連結開啟,不需要 copy + paste
|
|
72
|
+
note="個人資料的 website、文件參考連結、專案 repo URL 等場景。儲存後使用者可直接點擊連結開啟,不需要 copy + paste。點網址文字永遠是開連結;要改就按右側 Pencil,或點欄位裡文字以外的空白處"
|
|
73
73
|
>
|
|
74
74
|
<div>
|
|
75
75
|
<Label>✅ 個人資料:Website(儲存後可直接點擊開啟)</Label>
|
|
@@ -105,10 +105,10 @@ export const DisplayStateRule: Story = {
|
|
|
105
105
|
<div>
|
|
106
106
|
<Rule
|
|
107
107
|
title="有合法 URL 且未編輯 → 藍色連結 + Pencil"
|
|
108
|
-
note="主要顯示狀態。使用者一眼看到可點擊的連結,想改才按 Pencil。對照 Notion
|
|
108
|
+
note="主要顯示狀態。使用者一眼看到可點擊的連結,想改才按 Pencil(或點欄位空白處)。對照 Notion 的 URL 屬性——點網址開啟、不進入編輯,編輯另有按鈕(https://www.notion.com/releases/2021-09-08)"
|
|
109
109
|
>
|
|
110
110
|
<LinkInput value="https://react.dev/reference" onChange={() => {}} aria-label="參考連結" />
|
|
111
|
-
<Label>↑
|
|
111
|
+
<Label>↑ 點網址文字開啟;Pencil 或欄位裡文字以外的空白處進入編輯</Label>
|
|
112
112
|
</Rule>
|
|
113
113
|
|
|
114
114
|
<Rule
|
|
@@ -125,7 +125,7 @@ export const DisplayStateRule: Story = {
|
|
|
125
125
|
title="❌ 點 value 進入編輯(而不是開連結)"
|
|
126
126
|
note="LinkInput 跟 Input 的核心互動差異就在這——點 value 永遠是開連結。若使用者預期點 value 進入編輯,那該用 Input 不用 LinkInput"
|
|
127
127
|
>
|
|
128
|
-
<Label warn
|
|
128
|
+
<Label warn>(設計規則)點網址文字 = 開啟連結,編輯 = 按 Pencil 或點欄位空白處。兩者動作路徑分離</Label>
|
|
129
129
|
</Rule>
|
|
130
130
|
</div>
|
|
131
131
|
),
|