@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
|
@@ -12,7 +12,7 @@ const meta: Meta<typeof Rating> = {
|
|
|
12
12
|
docs: {
|
|
13
13
|
description: {
|
|
14
14
|
component:
|
|
15
|
-
'
|
|
15
|
+
'讓使用者點一顆整星給分;已提交的個人或平均評分一律用唯讀精簡版「★ 4.7 (12,843)」顯示。連續數值改用 Slider,二元喜好改用 Switch 或 icon button,完成進度改用 ProgressBar / CircularProgress。',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
},
|
|
@@ -21,7 +21,7 @@ const meta: Meta<typeof Rating> = {
|
|
|
21
21
|
export default meta
|
|
22
22
|
type Story = StoryObj<typeof Rating>
|
|
23
23
|
|
|
24
|
-
/* ── 商品列表的平均分(Amazon / Shopify
|
|
24
|
+
/* ── 商品列表的平均分(Amazon / Shopify 風格)——唯讀精簡版,評論數走 count ── */
|
|
25
25
|
export const ReadOnlyProductRating: Story = {
|
|
26
26
|
name: '商品列表平均分',
|
|
27
27
|
render: () => (
|
|
@@ -35,17 +35,14 @@ export const ReadOnlyProductRating: Story = {
|
|
|
35
35
|
<div key={p.name} className="flex items-center gap-3 p-3 border border-border rounded-md bg-surface">
|
|
36
36
|
<div className="flex-1 min-w-0">
|
|
37
37
|
<div className="text-body font-medium truncate">{p.name}</div>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<span className="text-caption text-fg-secondary">{p.rating}</span>
|
|
47
|
-
<span className="text-caption text-fg-muted">({p.count.toLocaleString()})</span>
|
|
48
|
-
</div>
|
|
38
|
+
<Rating
|
|
39
|
+
value={p.rating}
|
|
40
|
+
count={p.count}
|
|
41
|
+
readOnly
|
|
42
|
+
size="sm"
|
|
43
|
+
className="mt-1"
|
|
44
|
+
aria-label={`平均評分 ${p.rating} 星,共 5 星,${p.count.toLocaleString()} 則評論`}
|
|
45
|
+
/>
|
|
49
46
|
</div>
|
|
50
47
|
</div>
|
|
51
48
|
))}
|
|
@@ -53,7 +50,7 @@ export const ReadOnlyProductRating: Story = {
|
|
|
53
50
|
),
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/* ── 送出評分 flow(Yelp / Google Reviews
|
|
53
|
+
/* ── 送出評分 flow(Yelp / Google Reviews 風格)——只有整顆 ── */
|
|
57
54
|
export const InteractiveReview: Story = {
|
|
58
55
|
name: '送出評分流程',
|
|
59
56
|
render: () => {
|
|
@@ -71,7 +68,6 @@ export const InteractiveReview: Story = {
|
|
|
71
68
|
value={rating}
|
|
72
69
|
onChange={setRating}
|
|
73
70
|
size="lg"
|
|
74
|
-
precision="full"
|
|
75
71
|
aria-label="為這次服務給 1 到 5 星"
|
|
76
72
|
/>
|
|
77
73
|
<span className="text-body text-fg-secondary min-w-[48px]">
|
|
@@ -3,6 +3,8 @@ import * as React from 'react'
|
|
|
3
3
|
import { Star, type LucideIcon } from 'lucide-react'
|
|
4
4
|
import { cn } from '@/lib/utils'
|
|
5
5
|
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode } from '@/design-system/components/Field/field-context'
|
|
6
|
+
import { fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
|
|
7
|
+
import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Rating — 星星評分元件
|
|
@@ -20,12 +22,10 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
|
|
|
20
22
|
* 空色用 `var(--divider)`(neutral-4 借 divider semantic alias;灰色;與 disabled/empty 同級)。
|
|
21
23
|
*
|
|
22
24
|
* ── 互動 ──
|
|
23
|
-
* interactive(預設)
|
|
24
|
-
* readOnly
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* precision="full"(預設) — 整星(1, 2, 3, 4, 5)
|
|
28
|
-
* precision="half" — 半星(0.5, 1, 1.5, 2, 2.5, ..., 5)
|
|
25
|
+
* interactive(預設):只有整顆星 —— hover 預覽、click 設值、keyboard ±1
|
|
26
|
+
* readOnly:一律精簡版「★ 4.7 (12,843)」—— 一顆實心星 + 數值 + 選填評論數,不畫五顆星
|
|
27
|
+
* (user 2026-09-26:「唯讀直接一律給精簡版就好吧?搞得這麼麻煩幹嘛?其他照你建議」;
|
|
28
|
+
* rating.spec.md「Interactive vs ReadOnly」)
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
// ── Icon size canonical(2026-04-21 AR48 修正)──
|
|
@@ -34,25 +34,18 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
|
|
|
34
34
|
// 理由:
|
|
35
35
|
// - 星星是 filled shape(解析整個 icon 是重量感的一部分),不像純 outline icon 靠 stroke
|
|
36
36
|
// - Field 內 Rating 跟 Avatar / Tag 並排時視覺份量要對齊,否則 row height 一致但 icon 看起來比重量不對
|
|
37
|
-
// - 世界級對照:Ant Rate in Form = 20px、Material MUI Rating fontSize=inherit 預設約 24、Airbnb 評分星 24px
|
|
38
37
|
//
|
|
39
|
-
//
|
|
38
|
+
// 因此可以點的星對齊 **item-anatomy inline Avatar sizes**:sm=20 / md=24 / lg=24。
|
|
40
39
|
// 非 icon tier(16/16/20)——star 不是次要 affordance icon,它是主要資料視覺。
|
|
41
40
|
//
|
|
42
|
-
//
|
|
43
|
-
// field-height
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
const SIZE_PX = {
|
|
50
|
-
const CONTAINER_HEIGHT: Record<'xs' | 'sm' | 'md' | 'lg', string> = {
|
|
51
|
-
xs: 'h-field-xs',
|
|
52
|
-
sm: 'h-field-sm',
|
|
53
|
-
md: 'h-field-md',
|
|
54
|
-
lg: 'h-field-lg',
|
|
55
|
-
}
|
|
41
|
+
// ── 高度模型(2026-09-27;user:「我反而覺得其跟 switch 和 checkbox 十分接近的邏輯」)──
|
|
42
|
+
// 元件只有內容高、不吃 `--field-height-*` 容器:可以點的評分根高 = 星高;唯讀精簡版 = 星與文字行高的較大者。
|
|
43
|
+
// 列高由所在的列供給 —— Field 控件槽 `min-h-field-* + items-center`(field.tsx)、表格格子、列的 items-center。
|
|
44
|
+
// 世界級六家(MUI / Ant / Fluent / Mantine / Chakra / PrimeReact)評分根容器都 hug 星、表單對齊交給表單列;
|
|
45
|
+
// 之前自己吃容器會把 Field 外的列撐高(商品卡 24 容器實佔 26px),對命中零幫助。出處與實量:rating.spec.md「Size」。
|
|
46
|
+
// xs 於同日退役(可以點的 xs 與 sm 只差容器;唯讀 xs 是 AI 自訂、無 user 同意、產品零處)。
|
|
47
|
+
type RatingSize = 'sm' | 'md' | 'lg'
|
|
48
|
+
const SIZE_PX: Record<RatingSize, number> = { sm: 20, md: 24, lg: 24 }
|
|
56
49
|
|
|
57
50
|
export interface RatingProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
58
51
|
/** 當前評分(0 ~ max) */
|
|
@@ -63,12 +56,12 @@ export interface RatingProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
63
56
|
onChange?: (value: number) => void
|
|
64
57
|
/** 滿分(預設 5) */
|
|
65
58
|
max?: number
|
|
66
|
-
/**
|
|
67
|
-
size?:
|
|
68
|
-
/**
|
|
69
|
-
precision?: 'full' | 'half'
|
|
70
|
-
/** 唯讀(無 hover / click 響應) */
|
|
59
|
+
/** 尺寸。預設 md、元件只有內容高(同 Switch / Checkbox);解析序同 Input 類:明傳 > Field 明確指定 > Field 預設 md > 表格 surface size > md。沒有 xs(2026-09-27 退役) */
|
|
60
|
+
size?: RatingSize
|
|
61
|
+
/** 唯讀:顯示精簡版「★ 數值 (評論數)」,不響應 hover / click / 鍵盤 */
|
|
71
62
|
readOnly?: boolean
|
|
63
|
+
/** 唯讀時顯示在數值後的評論數(千分位);interactive 時不顯示 */
|
|
64
|
+
count?: number
|
|
72
65
|
/** 完全停用 */
|
|
73
66
|
disabled?: boolean
|
|
74
67
|
/**
|
|
@@ -97,8 +90,8 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
97
90
|
onChange,
|
|
98
91
|
max = 5,
|
|
99
92
|
size: sizeProp,
|
|
100
|
-
precision = 'full',
|
|
101
93
|
readOnly: readOnlyProp = false,
|
|
94
|
+
count,
|
|
102
95
|
disabled: disabledProp,
|
|
103
96
|
loading = false,
|
|
104
97
|
icon: Icon = Star,
|
|
@@ -107,11 +100,6 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
107
100
|
},
|
|
108
101
|
ref,
|
|
109
102
|
) => {
|
|
110
|
-
// Context-aware default size(AR31 canonical):
|
|
111
|
-
// - Field 內(有 FieldContext.size) → 跟 Field size 對齊(sm / md / lg)
|
|
112
|
-
// - Standalone(無 Field context) → default `xs`(24px,對齊 Avatar / Tag sm / iOS HIG standalone)
|
|
113
|
-
// consumer 可傳 size 顯式 override。世界級對照:Material Rating standalone 24dp、
|
|
114
|
-
// Ant Rate in Form 跟 Form.itemSize,standalone 24px。
|
|
115
103
|
// 2026-07-18 決策4:max dev-warn(spec canonical ≤7;維持 number 型別不收窄,對齊 MUI Rating.max)。
|
|
116
104
|
if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production' && (max > 7 || max < 1 || Math.round(max) !== max)) {
|
|
117
105
|
console.warn(`[DS] Rating max ${max} 超出 canonical 範圍 1-7(或非整數)。建議 ≤7 顆星(對齊世界級 review-stars)。`)
|
|
@@ -119,19 +107,71 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
119
107
|
const fieldCtx = useFieldContext() // 保留:aria-labelledby 用 fieldCtx.labelId
|
|
120
108
|
// 2026-06-08 SSOT:<Field disabled> cascade(原 isInteractive 只看 local disabled prop)
|
|
121
109
|
const disabled = useResolvedFieldDisabled(disabledProp)
|
|
122
|
-
// <Field mode="readonly"> cascade(2026-06-12 補):Rating 的 readonly 呈現 =
|
|
123
|
-
// (
|
|
110
|
+
// <Field mode="readonly"> cascade(2026-06-12 補):Rating 的 readonly 呈現 = 唯讀精簡版「★ 數值」
|
|
111
|
+
// (2026-09-26 起;role=img)——不包灰框,只鎖互動。
|
|
124
112
|
const resolvedMode = useResolvedFieldMode({ mode: undefined, disabled, readOnly: readOnlyProp })
|
|
125
113
|
const readOnly = readOnlyProp || resolvedMode === 'readonly'
|
|
126
|
-
|
|
114
|
+
// 尺寸解析(field-context useResolvedFieldSize):prop > Field 明確指定的 size > Field 預設 md > 表格 surface size > md。
|
|
115
|
+
// 不傳 fallback:傳了會排在 Field 預設與 surface size 前面(那是 Switch / Checkbox 的 'md' 寫法,表格格子就接不到 surface size);
|
|
116
|
+
// Rating 在表格格子裡要跟表格(rating.spec.md「Size」:使用時機 = 跟所在容器)。
|
|
117
|
+
// 執行期傳 'xs'(型別已拒,JS consumer / 舊 d.ts 仍可能傳)→ 映到 sm 並 dev-warn(靜默映射 = 零訊號);閘 scripts/rating-field-size-invariant.mjs(meta test scripts/test-rating-field-size-invariant.mjs)。
|
|
118
|
+
const legacyXs = (sizeProp as string | undefined) === 'xs'
|
|
119
|
+
if (legacyXs && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
120
|
+
console.warn('[DS] Rating size="xs" 已於 2026-09-27 退役(改為 sm;元件只有內容高,列高由所在的列供給)。')
|
|
121
|
+
}
|
|
122
|
+
const size = useResolvedFieldSize<RatingSize>(legacyXs ? 'sm' : sizeProp)
|
|
127
123
|
const [internalValue, setInternalValue] = React.useState(defaultValue)
|
|
128
124
|
const [hoverValue, setHoverValue] = React.useState<number | null>(null)
|
|
125
|
+
const valueTextId = React.useId()
|
|
129
126
|
const isControlled = value !== undefined
|
|
130
127
|
const currentValue = isControlled ? value : internalValue
|
|
131
|
-
|
|
128
|
+
// 可以點的評分只有整顆:外部給小數時四捨五入成整顆,畫面、鍵盤起點、讀屏念的值三者同一個數
|
|
129
|
+
// (否則 4.7 按 ← 會變 3.7、讀屏念 3.7 卻畫 4 顆)
|
|
130
|
+
const wholeValue = Math.max(0, Math.min(max, Math.round(currentValue)))
|
|
131
|
+
const displayValue = hoverValue ?? wholeValue
|
|
132
132
|
const iconPx = SIZE_PX[size]
|
|
133
133
|
const isInteractive = !readOnly && !disabled && !loading
|
|
134
134
|
|
|
135
|
+
// ── 唯讀 = 精簡版「★ 4.7 (12,843)」────────────────────────────────────────
|
|
136
|
+
// 世界級:Fluent 2 把唯讀拆成 RatingDisplay,`compact`「Renders a single filled star, with the value
|
|
137
|
+
// written next to it」、`count`「formatted with a thousands separator … displayed next to the value」
|
|
138
|
+
// (github.com/microsoft/fluentui react-rating `RatingDisplay.types.ts`,2026-09-26 讀 master)。
|
|
139
|
+
// 本 DS 不另拆元件:唯讀一律走這條,互動才畫五顆星(user 2026-09-26 選的是「唯讀一律精簡版」)。
|
|
140
|
+
// 星走 icon tier(16 / 16 / 20);星 ↔ 數值的縫三個尺寸都是 gap-1 4px —— Rating 內只有一種縫(可以點的星與星之間也是 4px),
|
|
141
|
+
// user 2026-09-27 拍板(原話「我覺得星號與文字gap應該跟這個一樣都是4px吧?」→「照你建議」);Fluent 2 RatingDisplay 16px 星 + 12px 字
|
|
142
|
+
// 亦是 4px、Tag 資料指示器圖示 ↔ 文字 4px。出處、實量與世界級對照見 rating.spec.md「唯讀精簡版」。
|
|
143
|
+
if (readOnly) {
|
|
144
|
+
const shown = Math.max(0, Math.min(max, currentValue))
|
|
145
|
+
return (
|
|
146
|
+
<div
|
|
147
|
+
ref={ref}
|
|
148
|
+
role="img"
|
|
149
|
+
data-size={size}
|
|
150
|
+
// Field 內:名稱 = 欄位標籤 + 數值(role=img 的子內容讀屏不念,只指標籤會只聽到「滿意度」聽不到分數);
|
|
151
|
+
// standalone 沒有 labelId → 走 consumer 的 aria-label(規格必填)
|
|
152
|
+
aria-labelledby={fieldCtx?.labelId ? `${fieldCtx.labelId} ${valueTextId}` : undefined}
|
|
153
|
+
aria-disabled={disabled || undefined}
|
|
154
|
+
aria-busy={loading || undefined}
|
|
155
|
+
className={cn(
|
|
156
|
+
// 不換行:星、數值、評論數是同一個值,窄容器裡折行會把整列撐高一倍(lg 實測 48px)。
|
|
157
|
+
// 高度 = 星與文字行高的較大者(sm/md 21、lg 24),沒有容器;星 ↔ 數值 gap-1(Rating 內只有一種縫)
|
|
158
|
+
'inline-flex items-center whitespace-nowrap gap-1',
|
|
159
|
+
fieldDisplayTextClass(size),
|
|
160
|
+
(disabled || loading) && 'opacity-disabled pointer-events-none',
|
|
161
|
+
className,
|
|
162
|
+
)}
|
|
163
|
+
{...props}
|
|
164
|
+
>
|
|
165
|
+
<Icon size={size === 'lg' ? ICON_SIZE.lg : ICON_SIZE.sm} fill={FILL_FILLED} stroke="none" aria-hidden
|
|
166
|
+
className="shrink-0" style={{ color: FILL_FILLED }} />
|
|
167
|
+
<span id={valueTextId} aria-hidden className="text-foreground tabular-nums">
|
|
168
|
+
{formatRatingValue(shown)}
|
|
169
|
+
{count !== undefined && <span className="text-fg-secondary">{` (${count.toLocaleString()})`}</span>}
|
|
170
|
+
</span>
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
135
175
|
const setValue = (v: number) => {
|
|
136
176
|
if (!isControlled) setInternalValue(v)
|
|
137
177
|
onChange?.(v)
|
|
@@ -139,14 +179,14 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
139
179
|
|
|
140
180
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
141
181
|
if (!isInteractive) return
|
|
142
|
-
const step =
|
|
182
|
+
const step = 1 // 只有整顆(rating.spec.md「Interactive vs ReadOnly」)
|
|
143
183
|
// Full ARIA slider pattern(WAI-ARIA):Arrow / Home / End 支援 — D4 UX audit 2026-04-22
|
|
144
184
|
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
|
|
145
185
|
e.preventDefault()
|
|
146
|
-
setValue(Math.min(max,
|
|
186
|
+
setValue(Math.min(max, wholeValue + step))
|
|
147
187
|
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
|
|
148
188
|
e.preventDefault()
|
|
149
|
-
setValue(Math.max(0,
|
|
189
|
+
setValue(Math.max(0, wholeValue - step))
|
|
150
190
|
} else if (e.key === 'Home') {
|
|
151
191
|
e.preventDefault()
|
|
152
192
|
setValue(0)
|
|
@@ -160,16 +200,18 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
160
200
|
<div
|
|
161
201
|
ref={ref}
|
|
162
202
|
role={isInteractive ? 'slider' : 'img'}
|
|
203
|
+
data-size={size}
|
|
163
204
|
// a11y(#30):role=slider 必有 accessible name(WAI-ARIA APG slider pattern)。
|
|
164
205
|
// Field 內 → 自動 aria-labelledby 指向 FieldLabel 的 id(fieldCtx.labelId,免填);
|
|
165
206
|
// Standalone → 仍需 consumer 傳 aria-label。對齊 TimePicker / DatePicker 同 canonical
|
|
166
207
|
// (time-picker.tsx:313 / date-picker.tsx:514:aria-labelledby={fieldCtx?.labelId})。
|
|
167
208
|
// 置於 {...props} 前,consumer 顯式傳的 aria-labelledby 仍可覆寫。
|
|
168
209
|
aria-labelledby={fieldCtx?.labelId} // 2026-06-12 修:readonly/disabled(role=img)也需 accessible name,labelledby 對 img 合法
|
|
169
|
-
aria-valuenow={isInteractive ?
|
|
210
|
+
aria-valuenow={isInteractive ? wholeValue : undefined}
|
|
170
211
|
aria-valuemin={isInteractive ? 0 : undefined}
|
|
171
212
|
aria-valuemax={isInteractive ? max : undefined}
|
|
172
|
-
|
|
213
|
+
// 讀屏文字中文(全庫 SR label 中文,2026-07-04;同 spec 唯讀範例「平均評分 4.7 星,共 5 星」的說法)
|
|
214
|
+
aria-valuetext={isInteractive ? `${wholeValue} 星,共 ${max} 星` : undefined}
|
|
173
215
|
aria-disabled={disabled || undefined}
|
|
174
216
|
// a11y: 刻意不設 aria-readonly — readOnly 時 role=img(axe aria-allowed-attr 禁 img 用 aria-readonly,2026-04-25);
|
|
175
217
|
// interactive 時 role=slider 但必非 readOnly(isInteractive = !readOnly)。兩 state 皆不該有此屬性,故省略。
|
|
@@ -177,40 +219,39 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
177
219
|
tabIndex={isInteractive ? 0 : undefined}
|
|
178
220
|
onKeyDown={handleKeyDown}
|
|
179
221
|
onMouseLeave={() => setHoverValue(null)}
|
|
222
|
+
// 星與星之間的縫(`gap-1`;元件只有內容高,沒有上下留白):指標從某顆星移進來時,那顆星的預覽照舊亮著
|
|
223
|
+
// (只有離開整個元件才收);亮著時在這裡點下去 = 確認正在預覽的值(AI 建議,2026-09-26 列在
|
|
224
|
+
// 「其餘建議」裡、user 未另提 → 照建議做,AI 判讀;hit-area-canonical.md 滑過原則一-6)。
|
|
225
|
+
// 點在星上由星自己處理(e.target 是星,不是容器),不會重複送出。
|
|
226
|
+
onClick={isInteractive ? (e) => {
|
|
227
|
+
if (e.target === e.currentTarget && hoverValue !== null) setValue(hoverValue)
|
|
228
|
+
} : undefined}
|
|
180
229
|
className={cn(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
'
|
|
185
|
-
// disabled 跟 loading 視覺相同(composite uniform dim),semantic 由 aria-disabled / aria-busy
|
|
186
|
-
(
|
|
230
|
+
// 元件只有內容高(= 星高);列高由所在的列供給(見檔頭「高度模型」)。焦點框跟著這個內容盒(rounded-md)
|
|
231
|
+
'inline-flex items-center gap-1 rounded-md',
|
|
232
|
+
// 預覽亮著時縫裡點得到 → 縫裡也是手形(與星同一個游標)
|
|
233
|
+
isInteractive && hoverValue !== null && 'cursor-pointer',
|
|
234
|
+
// disabled 跟 loading 視覺相同(composite uniform dim),semantic 由 aria-disabled / aria-busy 區分。
|
|
235
|
+
// 事件仍全部擋在星上(子元素 pointer-events-none;容器此時沒有任何 handler),容器自己留著接指標
|
|
236
|
+
// 只為了掛禁止符號 —— 容器也 pointer-events-none 的話游標由外層決定,永遠是箭頭
|
|
237
|
+
//(hit-area-canonical.md 滑過原則一-3「停用的用禁止符號」;field-controls.spec.md「游標指引」)。
|
|
238
|
+
(disabled || loading) && 'opacity-disabled cursor-not-allowed [&>*]:pointer-events-none',
|
|
187
239
|
className,
|
|
188
240
|
)}
|
|
189
241
|
{...props}
|
|
190
242
|
>
|
|
191
243
|
{Array.from({ length: max }, (_, i) => {
|
|
192
244
|
const starValue = i + 1
|
|
193
|
-
const fillRatio = Math.max(0, Math.min(1, displayValue - i)) // 0..1
|
|
194
|
-
const isHalf = precision === 'half' && fillRatio > 0 && fillRatio < 1
|
|
195
|
-
|
|
196
245
|
return (
|
|
197
246
|
<StarIcon
|
|
198
247
|
key={i}
|
|
199
248
|
Icon={Icon}
|
|
200
249
|
sizePx={iconPx}
|
|
201
|
-
|
|
202
|
-
|
|
250
|
+
// 只有整顆:displayValue 已是整數(wholeValue 四捨五入,同 Zag rating-group 沒開半顆時 `Math.round`)
|
|
251
|
+
filled={displayValue >= starValue}
|
|
203
252
|
interactive={isInteractive}
|
|
204
|
-
onHover={(
|
|
205
|
-
|
|
206
|
-
const v = precision === 'half' && halfFirst ? starValue - 0.5 : starValue
|
|
207
|
-
setHoverValue(v)
|
|
208
|
-
}}
|
|
209
|
-
onClick={(halfFirst) => {
|
|
210
|
-
if (!isInteractive) return
|
|
211
|
-
const v = precision === 'half' && halfFirst ? starValue - 0.5 : starValue
|
|
212
|
-
setValue(v)
|
|
213
|
-
}}
|
|
253
|
+
onHover={() => { if (isInteractive) setHoverValue(starValue) }}
|
|
254
|
+
onClick={() => { if (isInteractive) setValue(starValue) }}
|
|
214
255
|
/>
|
|
215
256
|
)
|
|
216
257
|
})}
|
|
@@ -220,74 +261,47 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
|
|
|
220
261
|
)
|
|
221
262
|
Rating.displayName = 'Rating'
|
|
222
263
|
|
|
223
|
-
// ── StarIcon:
|
|
264
|
+
// ── StarIcon: 單顆整星(可以點的評分用;唯讀精簡版直接畫一顆實心星)─────────────
|
|
224
265
|
|
|
225
266
|
interface StarIconProps {
|
|
226
267
|
Icon: LucideIcon
|
|
227
268
|
sizePx: number
|
|
228
|
-
|
|
229
|
-
isHalf: boolean
|
|
269
|
+
filled: boolean
|
|
230
270
|
interactive: boolean
|
|
231
|
-
onHover: (
|
|
232
|
-
onClick: (
|
|
271
|
+
onHover: () => void
|
|
272
|
+
onClick: () => void
|
|
233
273
|
}
|
|
234
274
|
|
|
235
275
|
const FILL_FILLED = 'var(--warning)' // yellow-6 — 黃星 convention
|
|
236
276
|
const FILL_EMPTY = 'var(--divider)' // 灰色空星(neutral-4 借 divider semantic alias,user 2026-05-09 拍板;對齊 Material rgba(0,0,0,0.26) muted-fill canonical)
|
|
237
277
|
|
|
238
|
-
|
|
278
|
+
/** 唯讀數值:整數照原樣,小數取一位(4 → "4"、4.7 → "4.7"、4.25 → "4.3");不走 locale,避免小數點變逗號 */
|
|
279
|
+
function formatRatingValue(value: number): string {
|
|
280
|
+
return Number.isInteger(value) ? String(value) : (Math.round(value * 10) / 10).toString()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function StarIcon({ Icon, sizePx, filled, interactive, onHover, onClick }: StarIconProps) {
|
|
239
284
|
// a11y(2026-04-25 axe nested-interactive fix):inner 點擊目標改 <span>(非 interactive
|
|
240
285
|
// element),不會跟外層 role='slider' 形成 nested-interactive 違規。鍵盤控制統一在外層
|
|
241
286
|
// slider 的 arrow keys,inner 只處理 mouse click 定位。Ant Rate / Material MUI 同模式。
|
|
242
|
-
|
|
243
|
-
// Full: 一整顆 fill(filled 或 empty)
|
|
244
|
-
const fill = fillRatio >= 1 ? FILL_FILLED : FILL_EMPTY
|
|
245
|
-
return (
|
|
246
|
-
<span
|
|
247
|
-
role="presentation"
|
|
248
|
-
onMouseEnter={interactive ? () => onHover(false) : undefined}
|
|
249
|
-
onClick={interactive ? () => onClick(false) : undefined}
|
|
250
|
-
className={cn(
|
|
251
|
-
'inline-flex',
|
|
252
|
-
interactive ? 'cursor-pointer' : 'cursor-default',
|
|
253
|
-
)}
|
|
254
|
-
style={{ color: fill }}
|
|
255
|
-
aria-hidden
|
|
256
|
-
>
|
|
257
|
-
{/* stroke="none" 移除 Lucide Star 預設的 outline stroke(lucide defaultAttributes
|
|
258
|
-
strokeWidth=2 + stroke=currentColor 會畫輪廓),讓星星是純 fill-only 的 shape——
|
|
259
|
-
fill 與 outline 同色視覺上仍有亮度差。
|
|
260
|
-
世界級對照:Ant Rate / Material MUI Rating 皆純 fill,無 outline stroke。*/}
|
|
261
|
-
<Icon size={sizePx} fill={fill} stroke="none" className="shrink-0" />
|
|
262
|
-
</span>
|
|
263
|
-
)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Half: 兩個重疊 icon,左半 filled / 右半 empty + 兩個 hover zone 切半星
|
|
287
|
+
const fill = filled ? FILL_FILLED : FILL_EMPTY
|
|
267
288
|
return (
|
|
268
|
-
<span
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<span
|
|
276
|
-
role="presentation"
|
|
277
|
-
onMouseEnter={() => onHover(true)}
|
|
278
|
-
onClick={() => onClick(true)}
|
|
279
|
-
className="absolute inset-y-0 left-0 w-1/2 cursor-pointer"
|
|
280
|
-
aria-hidden
|
|
281
|
-
/>
|
|
282
|
-
<span
|
|
283
|
-
role="presentation"
|
|
284
|
-
onMouseEnter={() => onHover(false)}
|
|
285
|
-
onClick={() => onClick(false)}
|
|
286
|
-
className="absolute inset-y-0 right-0 w-1/2 cursor-pointer"
|
|
287
|
-
aria-hidden
|
|
288
|
-
/>
|
|
289
|
-
</>
|
|
289
|
+
<span
|
|
290
|
+
role="presentation"
|
|
291
|
+
onMouseEnter={interactive ? onHover : undefined}
|
|
292
|
+
onClick={interactive ? onClick : undefined}
|
|
293
|
+
className={cn(
|
|
294
|
+
'inline-flex',
|
|
295
|
+
interactive ? 'cursor-pointer' : 'cursor-default',
|
|
290
296
|
)}
|
|
297
|
+
style={{ color: fill }}
|
|
298
|
+
aria-hidden
|
|
299
|
+
>
|
|
300
|
+
{/* stroke="none" 移除 Lucide Star 預設的 outline stroke(lucide defaultAttributes
|
|
301
|
+
strokeWidth=2 + stroke=currentColor 會畫輪廓),讓星星是純 fill-only 的 shape——
|
|
302
|
+
fill 與 outline 同色視覺上仍有亮度差。
|
|
303
|
+
世界級對照:Ant Rate / Material MUI Rating 皆純 fill,無 outline stroke。*/}
|
|
304
|
+
<Icon size={sizePx} fill={fill} stroke="none" className="shrink-0" />
|
|
291
305
|
</span>
|
|
292
306
|
)
|
|
293
307
|
}
|
|
@@ -307,7 +321,7 @@ export const ratingMeta = {
|
|
|
307
321
|
tokens: {
|
|
308
322
|
bg: ['bg-transparent'],
|
|
309
323
|
fg: [],
|
|
310
|
-
ring: ['ring
|
|
324
|
+
ring: ['--ring'],
|
|
311
325
|
},
|
|
312
326
|
} as const
|
|
313
327
|
|
|
@@ -445,7 +445,7 @@ export const Accessibility = {
|
|
|
445
445
|
render: () => (
|
|
446
446
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
447
447
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
448
|
-
<p className="whitespace-pre-line">{"詳 `scroll-area.spec.md` 「A11y 預設」段。摘要(Radix primitive + 本 DS 橋接):\n\n- 鍵盤捲動 :本 DS 在 Viewport 加 tabIndex={0} 使其可被鍵盤聚焦(Radix 不自動標 focusable,Safari 尤其需要),聚焦後支援 ArrowUp/Down/Left/Right / PageUp/Down / Home/End \n- Focus 可見 :聚焦的 Viewport 顯示 DS
|
|
448
|
+
<p className="whitespace-pre-line">{"詳 `scroll-area.spec.md` 「A11y 預設」段。摘要(Radix primitive + 本 DS 橋接):\n\n- 鍵盤捲動 :本 DS 在 Viewport 加 tabIndex={0} 使其可被鍵盤聚焦(Radix 不自動標 focusable,Safari 尤其需要),聚焦後支援 ArrowUp/Down/Left/Right / PageUp/Down / Home/End \n- Focus 可見 :聚焦的 Viewport 顯示 DS 焦點框(focus-visible:focus-ring-inset,往內 2px)\n- Scrollbar 非 tab stop :scrollbar thumb 不搶焦點,使用鍵盤的使用者透過 viewport 捲動(Radix 內建)\n- Pointer 支援 :thumb 可拖曳,track 可 click-to-jump(Radix 內建)\n\n本元件已內建 tabIndex 與 focus ring(滿足 axe scrollable-region-focusable);但 Viewport 預設無 role / accessible name——scroll 區域有具體語意(如「留言列表」「程式碼區塊」)時,consumer 應提供 aria-label,否則 SR 只報「可捲動區域」無內容描述;純視覺裝飾容器可省略。"}</p>
|
|
449
449
|
</div>
|
|
450
450
|
),
|
|
451
451
|
}
|
|
@@ -130,8 +130,9 @@ export const UsageGuidance: Story = {
|
|
|
130
130
|
<div className="w-[320px]">
|
|
131
131
|
<ScrollArea className="h-[180px] border border-border rounded-md bg-surface">
|
|
132
132
|
<div className="p-2">
|
|
133
|
+
{/* 靜態示意列(無 onClick、不能聚焦)不給 hover 底色(item-anatomy.spec.md「選中 × 互動疊加」:hover 要誠實回答「再點會發生什麼」) */}
|
|
133
134
|
{NOTION_PAGES.map((p) => (
|
|
134
|
-
<div key={p} className="px-3 py-1.5 text-body
|
|
135
|
+
<div key={p} className="px-3 py-1.5 text-body">{p}</div>
|
|
135
136
|
))}
|
|
136
137
|
</div>
|
|
137
138
|
</ScrollArea>
|
|
@@ -169,8 +170,9 @@ export const UsageGuidance: Story = {
|
|
|
169
170
|
<ScrollArea className="h-[220px] border border-success/40 rounded-md bg-surface">
|
|
170
171
|
<div className="p-3">
|
|
171
172
|
<div className="text-body font-medium mb-2">單層 ScrollArea</div>
|
|
173
|
+
{/* 靜態示意列不給 hover 底色(同上) */}
|
|
172
174
|
{NOTION_PAGES.map((p) => (
|
|
173
|
-
<div key={p} className="px-2 py-1.5 text-body
|
|
175
|
+
<div key={p} className="px-2 py-1.5 text-body">{p}</div>
|
|
174
176
|
))}
|
|
175
177
|
</div>
|
|
176
178
|
</ScrollArea>
|
|
@@ -193,8 +195,9 @@ export const OrientationChoice: Story = {
|
|
|
193
195
|
<div className="w-[280px]">
|
|
194
196
|
<ScrollArea className="h-[180px] border border-border rounded-md bg-surface">
|
|
195
197
|
<div className="p-2">
|
|
198
|
+
{/* 靜態示意列(無 onClick、不能聚焦)不給 hover 底色(item-anatomy.spec.md「選中 × 互動疊加」:hover 要誠實回答「再點會發生什麼」) */}
|
|
196
199
|
{NOTION_PAGES.map((p) => (
|
|
197
|
-
<div key={p} className="px-3 py-1.5 text-body
|
|
200
|
+
<div key={p} className="px-3 py-1.5 text-body">{p}</div>
|
|
198
201
|
))}
|
|
199
202
|
</div>
|
|
200
203
|
</ScrollArea>
|
|
@@ -101,7 +101,7 @@ ScrollArea 用 Radix 自訂 overlay 捲軸 → **跨 OS 一致、不吃寬度、
|
|
|
101
101
|
Radix primitive + 本 DS a11y 橋接:
|
|
102
102
|
|
|
103
103
|
- **鍵盤捲動**:本 DS 在 Viewport 加 `tabIndex={0}`(預設)使其可被鍵盤聚焦(Radix 不自動把 scroll container 標 focusable,Safari 尤其需要;此即 axe `scrollable-region-focusable` fix)。聚焦後 `ArrowUp/Down/Left/Right` / `PageUp/Down` / `Home/End` 捲動為瀏覽器原生行為,非 Radix 提供。**`viewportTabIndex={-1}` opt-out(2026-07-14)**:內容已有自帶 focusable + 鍵盤處理的元素(如 TimePicker `role="listbox"` `tabIndex={0}`)時傳 `-1`,消除「Viewport+內容雙 tab stop、焦點落 Viewport 時方向鍵觸發原生捲動非元件行為」;axe 由內部 focusable 內容滿足。**`viewportTabIndex={null}` composite opt-out(2026-07-31)**:ScrollArea 被 `role="menu"` 等 ARIA composite 當純結構 wrapper、鍵盤與 focus 由其 item family own 時,完全省略 viewport `tabindex`;`-1` 仍會留下 `div[tabindex]`,不能滿足 composite 的 required-children 結構
|
|
104
|
-
- **Focus 可見**:聚焦的 Viewport 顯示 DS
|
|
104
|
+
- **Focus 可見**:聚焦的 Viewport 顯示 DS 焦點框(`focus-visible:focus-ring-inset`,往內 2px),非瀏覽器原生 focus ring(2026-09-24 訂正,原 `focus-visible:outline-ring` 是已退役的手寫 outline 寫法,`scroll-area.tsx:69` 實為 `focus-ring-inset`)
|
|
105
105
|
- **Scrollbar 非 tab stop**:scrollbar thumb 不搶焦點,使用鍵盤的使用者透過 viewport 捲動(Radix 內建)
|
|
106
106
|
- **Pointer 支援**:thumb 可拖曳,track 可 click-to-jump(Radix 內建)
|
|
107
107
|
|
|
@@ -151,6 +151,8 @@ Radix Viewport 內建 wrapper `min-width:100%; display:table`——這是**水
|
|
|
151
151
|
|
|
152
152
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
153
153
|
|
|
154
|
+
- `data-table-known-defects.spec.md`
|
|
154
155
|
- `dialog.spec.md`
|
|
155
156
|
- `file-viewer.spec.md`
|
|
156
157
|
- `overlay-surface.spec.md`
|
|
158
|
+
- `sidebar.spec.md`
|
|
@@ -165,9 +165,11 @@ export const VerticalIssueList: Story = {
|
|
|
165
165
|
長 issue 清單(12 筆),容器固定 320px 高。macOS / Windows 呈現一致,不吃寬度。
|
|
166
166
|
</p>
|
|
167
167
|
<ScrollArea className="h-[320px] border border-border rounded-lg">
|
|
168
|
+
{/* 列是靜態 div(無 onClick、不能聚焦)→ 不給 hover 底色與 cursor-pointer:hover 回饋要誠實回答「再點會發生什麼」
|
|
169
|
+
(item-anatomy.spec.md「選中 × 互動疊加」表)。本則示範的是 ScrollArea 捲動,不是可點的議題列 */}
|
|
168
170
|
<div className="p-2">
|
|
169
171
|
{LINEAR_ISSUES.map((issue) => (
|
|
170
|
-
<div key={issue.id} className="flex items-center gap-3 px-3 py-2
|
|
172
|
+
<div key={issue.id} className="flex items-center gap-3 px-3 py-2">
|
|
171
173
|
<span className="text-caption font-mono text-fg-muted shrink-0 w-20">{issue.id}</span>
|
|
172
174
|
<span className="text-body flex-1 truncate">{issue.title}</span>
|
|
173
175
|
<span className="inline-flex items-center gap-1.5 text-footnote shrink-0">
|
|
@@ -194,8 +196,9 @@ export const SidebarNav: Story = {
|
|
|
194
196
|
{NOTION_NAV.map((group) => (
|
|
195
197
|
<div key={group.section} className="mb-4">
|
|
196
198
|
<div className="px-2 py-1 text-footnote text-fg-muted font-medium">{group.section}</div>
|
|
199
|
+
{/* 同上:靜態示意列不給 hover / cursor-pointer。真的側欄導覽請用 Sidebar(SidebarMenuButton;SidebarContent 自帶 ScrollArea) */}
|
|
197
200
|
{group.items.map((item) => (
|
|
198
|
-
<div key={item} className="px-2 py-1.5
|
|
201
|
+
<div key={item} className="px-2 py-1.5 text-body truncate">
|
|
199
202
|
{item}
|
|
200
203
|
</div>
|
|
201
204
|
))}
|
|
@@ -289,7 +289,7 @@ export const Accessibility = {
|
|
|
289
289
|
render: () => (
|
|
290
290
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
291
291
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
292
|
-
<p className="whitespace-pre-line">{" ARIA / Pattern :繼承 Radix toggle-group primitive 的無障礙預設——整組 role=\"group\",每個 item role=\"radio\"(type=\"single\" 互斥選一),鍵盤導覽由 primitive 處理。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/toggle-group#accessibility)。\n\n Keyboard 行為 :\n\n- Tab — 進入整組(只有一個 tab 停留點,落在選中項;沒選就落在上次 focus 過的 item,初次進入才是第一項)\n- ←/→ — 在 item 之間移動 roving focus(只移焦點,不切換選取)。WAI-ARIA APG 對水平 segmented/radio group 的主要導覽鍵\n- ↑/↓ — 同 ←/→,也會在 item 間移動 roving focus。Radix toggle-group 未鎖 orientation(沿用 default 雙軸 roving),APG 接受 group 同時支援兩軸方向鍵\n- Enter / Space — 選取目前 focus 的 item\n\n Focus :採 roving tabindex——整組共用一個 tab 停留點,方向鍵在 item 間移動焦點(選取仍需 Enter/Space/click);不是 Dialog 那種把焦點關在裡面的 focus trap。Focus ring 對齊 Button:focus-visible:
|
|
292
|
+
<p className="whitespace-pre-line">{" ARIA / Pattern :繼承 Radix toggle-group primitive 的無障礙預設——整組 role=\"group\",每個 item role=\"radio\"(type=\"single\" 互斥選一),鍵盤導覽由 primitive 處理。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/toggle-group#accessibility)。\n\n Keyboard 行為 :\n\n- Tab — 進入整組(只有一個 tab 停留點,落在選中項;沒選就落在上次 focus 過的 item,初次進入才是第一項)\n- ←/→ — 在 item 之間移動 roving focus(只移焦點,不切換選取)。WAI-ARIA APG 對水平 segmented/radio group 的主要導覽鍵\n- ↑/↓ — 同 ←/→,也會在 item 間移動 roving focus。Radix toggle-group 未鎖 orientation(沿用 default 雙軸 roving),APG 接受 group 同時支援兩軸方向鍵\n- Enter / Space — 選取目前 focus 的 item\n\n Focus :採 roving tabindex——整組共用一個 tab 停留點,方向鍵在 item 間移動焦點(選取仍需 Enter/Space/click);不是 Dialog 那種把焦點關在裡面的 focus trap。Focus ring 對齊 Button:全域 :focus-visible 外描邊(outline: 2px solid var(--ring),往外 2px;元件不寫任何焦點框 class,只加 focus-visible:z-20 讓往外長的框壓在相鄰 item 之上)。\n\n 驗證 :Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。"}</p>
|
|
293
293
|
</div>
|
|
294
294
|
),
|
|
295
295
|
}
|
|
@@ -218,7 +218,7 @@ Items 之間 `-ml-px`(除了第一個)讓相鄰 border 重疊、視覺上只
|
|
|
218
218
|
|
|
219
219
|
### focus-visible
|
|
220
220
|
|
|
221
|
-
由底層 ToggleGroup 處理——左右箭頭在 items 間移動 focus,空白/Enter
|
|
221
|
+
由底層 ToggleGroup 處理——左右箭頭在 items 間移動 focus,空白/Enter 選取。焦點框對齊 Button:全域 `:focus-visible` 外描邊(`styles/base.css` 的 `outline: 2px solid var(--ring)`,往外 `outline-offset: 2px`;item 不寫任何 ring / outline class,只加 `focus-visible:z-20` 把聚焦 item 提到相鄰 item 之上,讓外描邊浮在 `-ml-px` 重疊的鄰居 border 之上——與選中態 `z-10` 同一種手法;幾何 SSOT `ds-canonical/references/focus-canonical.md`「框怎麼畫」)。
|
|
222
222
|
|
|
223
223
|
---
|
|
224
224
|
|
|
@@ -276,7 +276,7 @@ Items 之間 `-ml-px`(除了第一個)讓相鄰 border 重疊、視覺上只
|
|
|
276
276
|
- ↑/↓ — 同 ←/→,也會在 item 間移動 roving focus。Radix `toggle-group` 未鎖 `orientation`(沿用 default 雙軸 roving),APG 接受 group 同時支援兩軸方向鍵
|
|
277
277
|
- Enter / Space — 選取目前 focus 的 item
|
|
278
278
|
|
|
279
|
-
**Focus**:Radix primitive 採 roving tabindex(整組共用單一 tab 停留點,方向鍵在 item 間移動焦點,不切換選取),非 Dialog 式 focus trap / restoration
|
|
279
|
+
**Focus**:Radix primitive 採 roving tabindex(整組共用單一 tab 停留點,方向鍵在 item 間移動焦點,不切換選取),非 Dialog 式 focus trap / restoration。焦點框對齊 Button focus-visible canonical(全域 `:focus-visible` 外描邊 `outline: 2px solid var(--ring)`,往外 2px——CSS outline,非 box-shadow ring;item 不寫任何 ring class)。
|
|
280
280
|
|
|
281
281
|
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
282
282
|
|
|
@@ -167,7 +167,8 @@ const itemVariants = cva(
|
|
|
167
167
|
'relative inline-flex items-center justify-center',
|
|
168
168
|
'whitespace-nowrap font-medium',
|
|
169
169
|
'border border-border bg-surface text-fg-secondary',
|
|
170
|
-
|
|
170
|
+
// 不寫 transition-colors:hover 的外框與字色一律瞬間(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色與外框,待辦總帳 L9 / N4(3))。
|
|
171
|
+
// 選中的外框與字色跟 hover 是同一組 CSS 屬性,滑鼠點選時指標必在項目上 → 選中變色一併瞬間(與 Chip 同一處理)。
|
|
171
172
|
'cursor-pointer select-none',
|
|
172
173
|
'focus-visible:z-20',
|
|
173
174
|
// disabled:cursor-not-allowed + 鎖住 hover 色(不用 pointer-events-none,否則 cursor 無法變)
|
|
@@ -315,7 +316,7 @@ export const segmentedControlMeta = {
|
|
|
315
316
|
tokens: {
|
|
316
317
|
bg: ['bg-surface'],
|
|
317
318
|
fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'], // selected 態 cva data-[state=on]:text-primary 實際消費(2026-07-06 選中改 base)
|
|
318
|
-
ring: ['ring
|
|
319
|
+
ring: ['--ring'],
|
|
319
320
|
},
|
|
320
321
|
defaultSize: 'md',
|
|
321
322
|
} as const
|