@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,6 +4,7 @@ import useEmblaCarousel from "embla-carousel-react";
|
|
|
4
4
|
import { ChevronRight, ChevronLeft } from "lucide-react";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
6
|
import { Button } from "../Button/button.js";
|
|
7
|
+
import { isTextEntryElement } from "../../lib/roving-list-keyboard.js";
|
|
7
8
|
const CarouselContext = React.createContext(null);
|
|
8
9
|
function useCarousel() {
|
|
9
10
|
const ctx = React.useContext(CarouselContext);
|
|
@@ -58,6 +59,7 @@ const Carousel = React.forwardRef(
|
|
|
58
59
|
};
|
|
59
60
|
}, [api, onSelect, onReInit]);
|
|
60
61
|
const handleKeyDown = (e) => {
|
|
62
|
+
if (isTextEntryElement(e.target)) return;
|
|
61
63
|
const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
|
|
62
64
|
const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
|
|
63
65
|
if (e.key === prevKey) {
|
|
@@ -157,7 +159,6 @@ const CarouselItem = React.forwardRef(
|
|
|
157
159
|
CarouselItem.displayName = "CarouselItem";
|
|
158
160
|
const arrowWrapperClass = cn(
|
|
159
161
|
"absolute z-10",
|
|
160
|
-
"transition-opacity duration-200 motion-reduce:duration-0",
|
|
161
162
|
"opacity-0 group-hover/carousel:opacity-100",
|
|
162
163
|
"focus-within:opacity-100",
|
|
163
164
|
"[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none"
|
|
@@ -302,7 +303,7 @@ const carouselMeta = {
|
|
|
302
303
|
tokens: {
|
|
303
304
|
bg: [],
|
|
304
305
|
fg: [],
|
|
305
|
-
ring: ["ring
|
|
306
|
+
ring: ["--ring"]
|
|
306
307
|
}
|
|
307
308
|
};
|
|
308
309
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"carousel.js","sources":["../../../src/components/Carousel/carousel.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * Carousel — 圖片 / 內容水平(或垂直)輪播\n *\n * 實作基礎:shadcn `Carousel` 結構 + `embla-carousel-react` v8 engine + 本 DS token。\n *\n * ── 世界級對照 ──\n * shadcn Carousel(本元件主要參考)/ Ant Carousel / Polaris 無 /\n * Swiper(獨立 lib,功能更多但不在 DS 範疇)\n *\n * ── 視覺慣例(user 指示) ──\n * 預設「hover 上去」左右兩邊才出現 prev/next 按鈕,不打擾主視覺;\n * 指示器(dots)在底部中央,clickable。\n *\n * ── API(shadcn parity) ──\n * <Carousel opts plugins orientation>\n * <CarouselContent>\n * <CarouselItem>...</CarouselItem>\n * <CarouselItem>...</CarouselItem>\n * </CarouselContent>\n * <CarouselPrevious /> ← 左箭頭\n * <CarouselNext /> ← 右箭頭\n * <CarouselDots /> ← 本 DS 擴充(shadcn 無,Ant/Swiper 慣例)\n * </Carousel>\n */\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ninterface CarouselProps {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: 'horizontal' | 'vertical'\n setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n scrollTo: (i: number) => void\n canScrollPrev: boolean\n canScrollNext: boolean\n selectedIndex: number\n scrollSnaps: number[]\n orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const ctx = React.useContext(CarouselContext)\n if (!ctx) throw new Error('useCarousel 必須在 <Carousel> 內使用')\n return ctx\n}\n\n// ── Root ────────────────────────────────────────────────────────────────────\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = 'horizontal',\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },\n plugins,\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([])\n\n const onSelect = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setCanScrollPrev(a.canScrollPrev())\n setCanScrollNext(a.canScrollNext())\n setSelectedIndex(a.selectedScrollSnap())\n }, [])\n\n // reInit(動態 slide 增減 / options 變更)必刷新 scrollSnaps —— onSelect 只讀 select\n // 狀態不含 scrollSnapList;若不在此補,dots 數量 stale(舊 dots 殘留 / 縮減不更新)。\n const onReInit = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setScrollSnaps(a.scrollSnapList())\n onSelect(a)\n }, [onSelect])\n\n const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api])\n const scrollNext = React.useCallback(() => api?.scrollNext(), [api])\n const scrollTo = React.useCallback((i: number) => api?.scrollTo(i), [api])\n\n React.useEffect(() => {\n if (!api || !setApi) return\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) return\n setScrollSnaps(api.scrollSnapList())\n onSelect(api)\n api.on('reInit', onReInit)\n api.on('select', onSelect)\n return () => {\n api?.off('select', onSelect)\n api?.off('reInit', onReInit) // D3 fix: previously leaked — stale closure on remount\n }\n }, [api, onSelect, onReInit])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // 鍵盤方向對齊內容捲動方向(APG 建議):horizontal → ←/→;vertical → ↑/↓\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'\n if (e.key === prevKey) { e.preventDefault(); scrollPrev() }\n else if (e.key === nextKey) { e.preventDefault(); scrollNext() }\n }\n\n const contextValue = React.useMemo(\n () => ({\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n }),\n [\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n ]\n )\n\n return (\n <CarouselContext.Provider value={contextValue}>\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn('group/carousel relative', className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"輪播\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n },\n)\nCarousel.displayName = 'Carousel'\n\n// ── Content / Item ──────────────────────────────────────────────────────────\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n // 2026-07-18 決策5:注入 slide index/total 給 CarouselItem 自動產生「第 N 張,共 M 張」a11y 位置名\n // (WAI-ARIA APG carousel「N of M」https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)。count 取\n // React.Children.count(可靠,不依賴 embla init 後的 scrollSnaps)。\n const total = React.Children.count(children)\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n className,\n )}\n {...props}\n >\n {React.Children.map(children, (child, index) =>\n React.isValidElement(child)\n ? React.cloneElement(child as React.ReactElement<CarouselItemInternalProps>, { _slideIndex: index, _slideTotal: total })\n : child,\n )}\n </div>\n </div>\n )\n})\nCarouselContent.displayName = 'CarouselContent'\n\ninterface CarouselItemInternalProps extends React.HTMLAttributes<HTMLDivElement> {\n /** @internal CarouselContent 注入 —— 用於自動 slide 位置名。 */\n _slideIndex?: number\n _slideTotal?: number\n}\n\nconst CarouselItem = React.forwardRef<HTMLDivElement, CarouselItemInternalProps>(\n ({ className, _slideIndex, _slideTotal, 'aria-label': ariaLabel, ...props }, ref) => {\n const { orientation } = useCarousel()\n // 無 consumer aria-label 時自動位置名(第 N 張,共 M 張);consumer 傳 aria-label 優先(可自訂內容名)。\n const autoLabel =\n ariaLabel ?? (_slideIndex != null && _slideTotal != null ? `第 ${_slideIndex + 1} 張,共 ${_slideTotal} 張` : undefined)\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={autoLabel}\n className={cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n className,\n )}\n {...props}\n />\n )\n },\n)\nCarouselItem.displayName = 'CarouselItem'\n\n// ── Arrow buttons(hover 才顯示)────────────────────────────────────────────\n// 使用 DS Button (tertiary + iconOnly size md);hover-only 顯示由 wrapper 的\n// opacity transition 控制(Button 本身不負責)。此 wrapper 存在僅為絕對定位 +\n// hover/focus 可見性,不再覆寫 Button 的視覺 token。\n\n// 2026-07-18 決策14(user 授權 B 案:開放完整 Button props + wrapperClassName):\n// 對齊 shadcn/Radix Carousel —— `className` 指**箭頭按鈕本身**(非定位 wrapper),\n// consumer 可換 variant/size、綁 analytics onClick、傳 data-* 等完整 Button props。\n// 定位層(絕對定位 + hover-only 顯示)的 className 走新 `wrapperClassName`。\n// **BREAKING**:`className` 語義由「定位 wrapper」改為「按鈕」(遷移:定位覆寫改傳 wrapperClassName)。\n// onClick/disabled 內部與 carousel 導覽 compose(scrollPrev/Next + 邊界 disable 保留,consumer 疊加)。\ntype ArrowProps = Omit<React.ComponentPropsWithoutRef<typeof Button>, 'iconOnly'> & {\n /** 定位層 wrapper 的 className(絕對定位 + hover-only 顯示層);箭頭按鈕樣式走 `className`。 */\n wrapperClassName?: string\n}\n\nconst arrowWrapperClass = cn(\n 'absolute z-10',\n 'transition-opacity duration-200 motion-reduce:duration-0',\n 'opacity-0 group-hover/carousel:opacity-100',\n 'focus-within:opacity-100',\n '[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none',\n)\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(\n (\n {\n className,\n wrapperClassName,\n onClick,\n disabled,\n variant = 'tertiary',\n size = 'md',\n 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */,\n ...props\n },\n ref,\n ) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'left-3 top-1/2 -translate-y-1/2'\n : 'top-3 left-1/2 -translate-x-1/2 rotate-90',\n wrapperClassName,\n )}\n >\n <Button\n ref={ref}\n variant={variant}\n size={size}\n startIcon={ChevronLeft}\n aria-label={ariaLabel}\n {...props}\n iconOnly\n disabled={!canScrollPrev || disabled}\n onClick={(e) => { scrollPrev(); onClick?.(e) }}\n // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,\n // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos\n // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。\n // consumer className 疊加在後可覆寫(決策14)。\n className={cn('rounded-full', className)}\n />\n </div>\n )\n },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\n// code-quality-allow: long-function — 與 CarouselPrevious 結構對稱的 JSX-heavy arrow(方向/位置/邊界三態 className 組合),拆 helper 會破壞兩支對讀性\nconst CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(\n (\n {\n className,\n wrapperClassName,\n onClick,\n disabled,\n variant = 'tertiary',\n size = 'md',\n 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */,\n ...props\n },\n ref,\n ) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'right-3 top-1/2 -translate-y-1/2'\n : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n wrapperClassName,\n )}\n >\n <Button\n ref={ref}\n variant={variant}\n size={size}\n startIcon={ChevronRight}\n aria-label={ariaLabel}\n {...props}\n iconOnly\n disabled={!canScrollNext || disabled}\n onClick={(e) => { scrollNext(); onClick?.(e) }}\n // documented exception:同 Previous,媒體導向 carousel 箭頭圓形;consumer className 疊加可覆寫。\n className={cn('rounded-full', className)}\n />\n </div>\n )\n },\n)\nCarouselNext.displayName = 'CarouselNext'\n\n// ── Dots indicator(底部中央)───────────────────────────────────────────────\n\nconst CarouselDots = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { scrollSnaps, selectedIndex, scrollTo } = useCarousel()\n if (scrollSnaps.length <= 1) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'absolute bottom-3 left-1/2 -translate-x-1/2 z-10',\n 'flex items-center gap-1.5',\n className,\n )}\n // 2026-06-12 a11y(deep-audit R2 verdict b):APG grouped carousel 模型,非 tabs 模型。\n // APG:「picker buttons 收在 group 容器;現張的 picker button aria-disabled=true」\n // (https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)+ Embla 官方 Accessibility plugin\n // 同採 plain button + label、無 tab roles(https://www.embla-carousel.com/docs/plugins/accessibility)。\n // 為何不用完整 tabs 模型(slide=tabpanel + dot aria-controls):\n // (1) Embla snap ≠ slide — basis-1/3 多張並排時 1 dot 控多張,tab↔tabpanel 1:1 不成立\n // (2) tabs 鍵盤契約(方向鍵移 tablist 內 focus)與根容器方向鍵=切張(本檔 handleKeyDown)衝突\n // (3) compound API 下 CarouselItem 不知 index,aria-controls id 接線需額外 registration\n // 舊實作 role=\"tablist\"/\"tab\" + aria-selected 是半套 tabs(無 aria-controls、slide 非 tabpanel)= 非法混血。\n // 現張標記用 aria-current(非 APG 原文的 aria-disabled)— M23 DS 既有 canonical 優先:\n // FileViewer filmstrip(file-viewer.tsx aria-current={active ? 'true' : undefined})同類\n // 「視覺項導航 + 現張標記」已 codify group + button + aria-current;Bootstrap 5 indicators 同 idiom。\n role=\"group\"\n aria-label=\"輪播指示器\" /* i18n-allow: DS default; 對齊同檔其他 aria-label 語言 */\n {...props}\n >\n {scrollSnaps.map((_, i) => (\n <button\n key={i}\n type=\"button\"\n // 現張 dot aria-current(告知 SR「你在這張」;仍可 focus / 點擊為 no-op)\n aria-current={i === selectedIndex ? 'true' : undefined}\n aria-label={`跳至第 ${i + 1} 張`}\n onClick={() => scrollTo(i)}\n className={cn(\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」);只留選中指示點的寬度變化\n 'h-1.5 rounded-full transition-[width] motion-reduce:duration-0',\n // 2026-06-11 a11y(R2;Phase B codex 修重疊):hit-area 垂直擴至 24px、水平 ±3px(dot 6px+gap 6px → 中心距 12px,±3 恰相切零重疊;再寬必互搶點擊)\n // — 視覺 dot 維持 6×6 不變,僅點擊目標擴大(anatomy story 原宣稱的 hit-area 機制補真)\n 'relative before:absolute before:-inset-y-[9px] before:-inset-x-[3px] before:content-[\"\"]',\n // Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義\n // 跟其他「於飽和色底上的淺色前景」一致\n 'bg-on-emphasis/60 hover:bg-on-emphasis/80',\n // 2026-08-11 修偏移(item-anatomy「選中 × 互動疊加」同原則):現張 dot 再點 = no-op,\n // 但 base hover /80(0,2,0)蓋過 plain bg(0,1,0)→ hover 反而變淡 = 意外。釘住不變。\n i === selectedIndex ? 'w-6 bg-on-emphasis hover:bg-on-emphasis' : 'w-1.5',\n )}\n />\n ))}\n </div>\n )\n})\nCarouselDots.displayName = 'CarouselDots'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const carouselMeta = {\n component: 'Carousel',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — 全檔無按壓視覺(prev/next 按壓屬內嵌 Button meta,同 Notice/Alert 排除慣例;\n // 2026-07-07 詞彙統一 DS-wide 交叉掃補修)。dot hover 為自有 hover 態(bg-on-emphasis alpha 變體)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n CarouselDots,\n type CarouselApi,\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"carousel.js","sources":["../../../src/components/Carousel/carousel.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { isTextEntryElement } from '@/design-system/lib/roving-list-keyboard'\n\n/**\n * Carousel — 圖片 / 內容水平(或垂直)輪播\n *\n * 實作基礎:shadcn `Carousel` 結構 + `embla-carousel-react` v8 engine + 本 DS token。\n *\n * ── 世界級對照 ──\n * shadcn Carousel(本元件主要參考)/ Ant Carousel / Polaris 無 /\n * Swiper(獨立 lib,功能更多但不在 DS 範疇)\n *\n * ── 視覺慣例(user 指示) ──\n * 預設「hover 上去」左右兩邊才出現 prev/next 按鈕,不打擾主視覺;\n * 指示器(dots)在底部中央,clickable。\n *\n * ── API(shadcn parity) ──\n * <Carousel opts plugins orientation>\n * <CarouselContent>\n * <CarouselItem>...</CarouselItem>\n * <CarouselItem>...</CarouselItem>\n * </CarouselContent>\n * <CarouselPrevious /> ← 左箭頭\n * <CarouselNext /> ← 右箭頭\n * <CarouselDots /> ← 本 DS 擴充(shadcn 無,Ant/Swiper 慣例)\n * </Carousel>\n */\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ninterface CarouselProps {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: 'horizontal' | 'vertical'\n setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n scrollTo: (i: number) => void\n canScrollPrev: boolean\n canScrollNext: boolean\n selectedIndex: number\n scrollSnaps: number[]\n orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const ctx = React.useContext(CarouselContext)\n if (!ctx) throw new Error('useCarousel 必須在 <Carousel> 內使用')\n return ctx\n}\n\n// ── Root ────────────────────────────────────────────────────────────────────\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = 'horizontal',\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },\n plugins,\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([])\n\n const onSelect = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setCanScrollPrev(a.canScrollPrev())\n setCanScrollNext(a.canScrollNext())\n setSelectedIndex(a.selectedScrollSnap())\n }, [])\n\n // reInit(動態 slide 增減 / options 變更)必刷新 scrollSnaps —— onSelect 只讀 select\n // 狀態不含 scrollSnapList;若不在此補,dots 數量 stale(舊 dots 殘留 / 縮減不更新)。\n const onReInit = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setScrollSnaps(a.scrollSnapList())\n onSelect(a)\n }, [onSelect])\n\n const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api])\n const scrollNext = React.useCallback(() => api?.scrollNext(), [api])\n const scrollTo = React.useCallback((i: number) => api?.scrollTo(i), [api])\n\n React.useEffect(() => {\n if (!api || !setApi) return\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) return\n setScrollSnaps(api.scrollSnapList())\n onSelect(api)\n api.on('reInit', onReInit)\n api.on('select', onSelect)\n return () => {\n api?.off('select', onSelect)\n api?.off('reInit', onReInit) // D3 fix: previously leaked — stale closure on remount\n }\n }, [api, onSelect, onReInit])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // 投影片裡的文字輸入(輸入框 / 多行框 / 下拉 / 可編輯區)保留自己的方向鍵:它們的 ←→ / ↑↓ 是移插入點、換選項,\n // 根容器在捕獲階段搶走等於讓表單打不了字(carousel.spec.md「A11y 預設」鍵盤條;判斷式 = 全 DS 唯一那份\n // isTextEntryElement,與「列上有小按鈕的一串」的「項目裡的輸入框保留自己的方向鍵」同一條)。\n if (isTextEntryElement(e.target as Element)) return\n // 鍵盤方向對齊內容捲動方向(APG 建議):horizontal → ←/→;vertical → ↑/↓\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'\n if (e.key === prevKey) { e.preventDefault(); scrollPrev() }\n else if (e.key === nextKey) { e.preventDefault(); scrollNext() }\n }\n\n const contextValue = React.useMemo(\n () => ({\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n }),\n [\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n ]\n )\n\n return (\n <CarouselContext.Provider value={contextValue}>\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn('group/carousel relative', className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"輪播\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n },\n)\nCarousel.displayName = 'Carousel'\n\n// ── Content / Item ──────────────────────────────────────────────────────────\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n // 2026-07-18 決策5:注入 slide index/total 給 CarouselItem 自動產生「第 N 張,共 M 張」a11y 位置名\n // (WAI-ARIA APG carousel「N of M」https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)。count 取\n // React.Children.count(可靠,不依賴 embla init 後的 scrollSnaps)。\n const total = React.Children.count(children)\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n className,\n )}\n {...props}\n >\n {React.Children.map(children, (child, index) =>\n React.isValidElement(child)\n ? React.cloneElement(child as React.ReactElement<CarouselItemInternalProps>, { _slideIndex: index, _slideTotal: total })\n : child,\n )}\n </div>\n </div>\n )\n})\nCarouselContent.displayName = 'CarouselContent'\n\ninterface CarouselItemInternalProps extends React.HTMLAttributes<HTMLDivElement> {\n /** @internal CarouselContent 注入 —— 用於自動 slide 位置名。 */\n _slideIndex?: number\n _slideTotal?: number\n}\n\nconst CarouselItem = React.forwardRef<HTMLDivElement, CarouselItemInternalProps>(\n ({ className, _slideIndex, _slideTotal, 'aria-label': ariaLabel, ...props }, ref) => {\n const { orientation } = useCarousel()\n // 無 consumer aria-label 時自動位置名(第 N 張,共 M 張);consumer 傳 aria-label 優先(可自訂內容名)。\n const autoLabel =\n ariaLabel ?? (_slideIndex != null && _slideTotal != null ? `第 ${_slideIndex + 1} 張,共 ${_slideTotal} 張` : undefined)\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={autoLabel}\n className={cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n className,\n )}\n {...props}\n />\n )\n },\n)\nCarouselItem.displayName = 'CarouselItem'\n\n// ── Arrow buttons(hover 才顯示)────────────────────────────────────────────\n// 使用 DS Button (tertiary + iconOnly size md);hover-only 顯示由 wrapper 的\n// opacity 控制(Button 本身不負責),**瞬間出現、不淡入**(滑過造成的變化一律不做過渡,\n// tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 待辦總帳 L9「全部瞬間」延伸,\n// user:「確定這樣才是一致設計語言就做」)。此 wrapper 存在僅為絕對定位 +\n// hover/focus 可見性,不再覆寫 Button 的視覺 token。\n\n// 2026-07-18 決策14(user 授權 B 案:開放完整 Button props + wrapperClassName):\n// 對齊 shadcn/Radix Carousel —— `className` 指**箭頭按鈕本身**(非定位 wrapper),\n// consumer 可換 variant/size、綁 analytics onClick、傳 data-* 等完整 Button props。\n// 定位層(絕對定位 + hover-only 顯示)的 className 走新 `wrapperClassName`。\n// **BREAKING**:`className` 語義由「定位 wrapper」改為「按鈕」(遷移:定位覆寫改傳 wrapperClassName)。\n// onClick/disabled 內部與 carousel 導覽 compose(scrollPrev/Next + 邊界 disable 保留,consumer 疊加)。\ntype ArrowProps = Omit<React.ComponentPropsWithoutRef<typeof Button>, 'iconOnly'> & {\n /** 定位層 wrapper 的 className(絕對定位 + hover-only 顯示層);箭頭按鈕樣式走 `className`。 */\n wrapperClassName?: string\n}\n\nconst arrowWrapperClass = cn(\n 'absolute z-10',\n 'opacity-0 group-hover/carousel:opacity-100',\n 'focus-within:opacity-100',\n '[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none',\n)\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(\n (\n {\n className,\n wrapperClassName,\n onClick,\n disabled,\n variant = 'tertiary',\n size = 'md',\n 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */,\n ...props\n },\n ref,\n ) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'left-3 top-1/2 -translate-y-1/2'\n : 'top-3 left-1/2 -translate-x-1/2 rotate-90',\n wrapperClassName,\n )}\n >\n <Button\n ref={ref}\n variant={variant}\n size={size}\n startIcon={ChevronLeft}\n aria-label={ariaLabel}\n {...props}\n iconOnly\n disabled={!canScrollPrev || disabled}\n onClick={(e) => { scrollPrev(); onClick?.(e) }}\n // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,\n // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos\n // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。\n // consumer className 疊加在後可覆寫(決策14)。\n className={cn('rounded-full', className)}\n />\n </div>\n )\n },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\n// code-quality-allow: long-function — 與 CarouselPrevious 結構對稱的 JSX-heavy arrow(方向/位置/邊界三態 className 組合),拆 helper 會破壞兩支對讀性\nconst CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(\n (\n {\n className,\n wrapperClassName,\n onClick,\n disabled,\n variant = 'tertiary',\n size = 'md',\n 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */,\n ...props\n },\n ref,\n ) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'right-3 top-1/2 -translate-y-1/2'\n : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n wrapperClassName,\n )}\n >\n <Button\n ref={ref}\n variant={variant}\n size={size}\n startIcon={ChevronRight}\n aria-label={ariaLabel}\n {...props}\n iconOnly\n disabled={!canScrollNext || disabled}\n onClick={(e) => { scrollNext(); onClick?.(e) }}\n // documented exception:同 Previous,媒體導向 carousel 箭頭圓形;consumer className 疊加可覆寫。\n className={cn('rounded-full', className)}\n />\n </div>\n )\n },\n)\nCarouselNext.displayName = 'CarouselNext'\n\n// ── Dots indicator(底部中央)───────────────────────────────────────────────\n\nconst CarouselDots = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { scrollSnaps, selectedIndex, scrollTo } = useCarousel()\n if (scrollSnaps.length <= 1) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'absolute bottom-3 left-1/2 -translate-x-1/2 z-10',\n 'flex items-center gap-1.5',\n className,\n )}\n // 2026-06-12 a11y(deep-audit R2 verdict b):APG grouped carousel 模型,非 tabs 模型。\n // APG:「picker buttons 收在 group 容器;現張的 picker button aria-disabled=true」\n // (https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)+ Embla 官方 Accessibility plugin\n // 同採 plain button + label、無 tab roles(https://www.embla-carousel.com/docs/plugins/accessibility)。\n // 為何不用完整 tabs 模型(slide=tabpanel + dot aria-controls):\n // (1) Embla snap ≠ slide — basis-1/3 多張並排時 1 dot 控多張,tab↔tabpanel 1:1 不成立\n // (2) tabs 鍵盤契約(方向鍵移 tablist 內 focus)與根容器方向鍵=切張(本檔 handleKeyDown)衝突\n // (3) compound API 下 CarouselItem 不知 index,aria-controls id 接線需額外 registration\n // 舊實作 role=\"tablist\"/\"tab\" + aria-selected 是半套 tabs(無 aria-controls、slide 非 tabpanel)= 非法混血。\n // 現張標記用 aria-current(非 APG 原文的 aria-disabled)— M23 DS 既有 canonical 優先:\n // FileViewer filmstrip(file-viewer.tsx aria-current={active ? 'true' : undefined})同類\n // 「視覺項導航 + 現張標記」已 codify group + button + aria-current;Bootstrap 5 indicators 同 idiom。\n role=\"group\"\n aria-label=\"輪播指示器\" /* i18n-allow: DS default; 對齊同檔其他 aria-label 語言 */\n {...props}\n >\n {scrollSnaps.map((_, i) => (\n <button\n key={i}\n type=\"button\"\n // 現張 dot aria-current(告知 SR「你在這張」;仍可 focus / 點擊為 no-op)\n aria-current={i === selectedIndex ? 'true' : undefined}\n aria-label={`跳至第 ${i + 1} 張`}\n onClick={() => scrollTo(i)}\n className={cn(\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」);只留選中指示點的寬度變化\n 'h-1.5 rounded-full transition-[width] motion-reduce:duration-0',\n // 2026-06-11 a11y(R2;Phase B codex 修重疊):hit-area 垂直擴至 24px、水平 ±3px(dot 6px+gap 6px → 中心距 12px,±3 恰相切零重疊;再寬必互搶點擊)\n // — 視覺 dot 維持 6×6 不變,僅點擊目標擴大(anatomy story 原宣稱的 hit-area 機制補真)\n 'relative before:absolute before:-inset-y-[9px] before:-inset-x-[3px] before:content-[\"\"]',\n // Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義\n // 跟其他「於飽和色底上的淺色前景」一致\n 'bg-on-emphasis/60 hover:bg-on-emphasis/80',\n // 2026-08-11 修偏移(item-anatomy「選中 × 互動疊加」同原則):現張 dot 再點 = no-op,\n // 但 base hover /80(0,2,0)蓋過 plain bg(0,1,0)→ hover 反而變淡 = 意外。釘住不變。\n i === selectedIndex ? 'w-6 bg-on-emphasis hover:bg-on-emphasis' : 'w-1.5',\n )}\n />\n ))}\n </div>\n )\n})\nCarouselDots.displayName = 'CarouselDots'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const carouselMeta = {\n component: 'Carousel',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — 全檔無按壓視覺(prev/next 按壓屬內嵌 Button meta,同 Notice/Alert 排除慣例;\n // 2026-07-07 詞彙統一 DS-wide 交叉掃補修)。dot hover 為自有 hover 態(bg-on-emphasis alpha 變體)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['--ring'],\n },\n} as const\n\nexport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n CarouselDots,\n type CarouselApi,\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@ import * as RechartsPrimitive from 'recharts';
|
|
|
17
17
|
* Legend text: text-fg-secondary / text-caption
|
|
18
18
|
* Grid: stroke-divider
|
|
19
19
|
* Axis tick: text-fg-muted / text-caption
|
|
20
|
+
* 滑過指示(Recharts tooltip cursor):長條圖的帶 fill-neutral-hover / 折線等的直線 stroke-border
|
|
20
21
|
*/
|
|
21
22
|
export type ChartConfig = {
|
|
22
23
|
[k in string]: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../../src/components/Chart/chart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,UAAU,CAAA;AAG7C
|
|
1
|
+
{"version":3,"file":"chart.d.ts","sourceRoot":"","sources":["../../../src/components/Chart/chart.tsx"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,14 @@ const ChartContainer = React.forwardRef(
|
|
|
30
30
|
"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-divider",
|
|
31
31
|
"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-divider",
|
|
32
32
|
"[&_.recharts-dot[stroke='#fff']]:stroke-transparent",
|
|
33
|
+
// 滑過指示(Recharts 叫 tooltip cursor):Recharts 3.8.1 把它寫死成 #ccc —— 長條圖的帶是
|
|
34
|
+
// fill '#ccc'(getCursorRectangle.ts:22),折線 / 面積 / 散佈 / 雷達的線是 stroke '#ccc'(Cursor.tsx:107)。
|
|
35
|
+
// 深色主題下是一條亮灰直條,不是 DS 的顏色。只換 Recharts 的預設值(看屬性值),consumer 自己傳的 cursor 樣式不蓋。
|
|
36
|
+
// 帶 = 資料表格列的滑過色 --neutral-hover(半透明,疊在圖表所在的底上,兩個主題方向都對);
|
|
37
|
+
// 線 = --border,比網格線 --divider 高一階,才看得出「這條是滑過指示、不是網格」。
|
|
38
|
+
// 為什麼可以有這條帶 → hit-area-canonical.md「滑過原則」三-2 例外清單②;顏色與實測 → chart.spec.md「滑過指示」。
|
|
39
|
+
"[&_.recharts-tooltip-cursor[fill='#ccc']]:fill-neutral-hover",
|
|
40
|
+
"[&_.recharts-tooltip-cursor[stroke='#ccc']]:stroke-border",
|
|
33
41
|
// @focus-suppress N — 不適用(不可操作 → 問題一);承擔者:SVG 內層 group,不是 tab stop(tab stop 是 .recharts-surface)
|
|
34
42
|
"[&_.recharts-layer]:outline-none",
|
|
35
43
|
"[&_.recharts-sector[stroke='#fff']]:stroke-transparent",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart.js","sources":["../../../src/components/Chart/chart.tsx"],"sourcesContent":["import * as React from 'react'\nimport * as RechartsPrimitive from 'recharts'\nimport { cn } from '@/lib/utils'\n\n/**\n * Chart — shadcn-style wrapper over Recharts + 本 DS token\n *\n * 結構對齊 shadcn chart(ChartContainer / ChartTooltipContent / ChartLegendContent),\n * 但所有視覺(tooltip / legend / grid / axis)改用本 DS token。\n *\n * ── Color mapping ──\n * ChartConfig.{key}.color 接受 2 種形式:\n * 1. CSS var 字串('var(--chart-1)' 等)\n * 2. 任何合法 CSS color\n * 預設建議使用 --chart-1..5(本 DS 提供的 5 色類別 token)\n *\n * ── 視覺 token ──\n * Tooltip: bg-surface-raised / border-border / shadow-[var(--elevation-200)] / rounded-md\n * Legend text: text-fg-secondary / text-caption\n * Grid: stroke-divider\n * Axis tick: text-fg-muted / text-caption\n */\n\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode\n icon?: React.ComponentType\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<'light' | 'dark', string> }\n )\n}\n\ntype ChartContextProps = { config: ChartConfig }\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n const ctx = React.useContext(ChartContext)\n if (!ctx) throw new Error('useChart 必須在 <ChartContainer> 內使用')\n return ctx\n}\n\n// ── ChartContainer ─────────────────────────────────────────────────────────\n\ninterface ChartContainerProps extends React.ComponentProps<'div'> {\n config: ChartConfig\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>['children']\n}\n\nconst ChartContainer = React.forwardRef<HTMLDivElement, ChartContainerProps>(\n ({ id, className, children, config, ...props }, ref) => {\n const uniqueId = React.useId()\n const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`\n // Memoize provider value(2026-04-22 D3 perf audit):避免 render 重建 wrapper object\n const ctxValue = React.useMemo(() => ({ config }), [config])\n\n return (\n <ChartContext.Provider value={ctxValue}>\n <div\n ref={ref}\n // 2026-07-14 audit Dim 9(passthrough 完整度):id 原被 destructure 只拿去組 data-chart,\n // native id attr 靜默吞掉 —— consumer 傳 id 期待 DOM id(anchor / aria 參照)必須落地。\n id={id}\n data-chart={chartId}\n className={cn(\n // 整體視覺套用本 DS typography + token\n // 預設 aspect-video(16:9)— Recharts ResponsiveContainer 需 parent 有高度。\n // Consumer 若需其他比例,包 <AspectRatio ratio={n}> 覆寫(AspectRatio 的 padding-bottom 高度會蓋過此 class)。\n 'flex aspect-video justify-center text-caption',\n // recharts 內部預設樣式覆寫:grid / axis / tooltip shadow 等\n \"[&_.recharts-cartesian-grid_line]:stroke-divider\",\n \"[&_.recharts-cartesian-axis-tick_text]:fill-fg-muted\",\n \"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-divider\",\n \"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-divider\",\n \"[&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n // @focus-suppress N — 不適用(不可操作 → 問題一);承擔者:SVG 內層 group,不是 tab stop(tab stop 是 .recharts-surface)\n \"[&_.recharts-layer]:outline-none\",\n \"[&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n // @focus-suppress N — 不適用(不可操作 → 問題一);承擔者:同上,SVG 扇形不是 tab stop\n \"[&_.recharts-sector]:outline-none\",\n // 2026-09-07 刪:recharts 3.x 的 accessibilityLayer 預設為 true,\n // RootSurface.js:54 `tabIndex = hasAccessibilityLayer ? 0 : undefined`、:59 `role = 'application'`\n // → surface 可 Tab、可用方向鍵瀏覽資料點 = 可操作,依 focus-canonical 必須有可見焦點指示。\n // 原本這行把全域 base.css:44-47 的焦點框關掉 = WCAG 2.4.7 違規。\n // 保留上一行的 .recharts-sector(扇形不可聚焦,那行是防禦性的)。\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer>\n {children}\n </RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n )\n },\n)\nChartContainer.displayName = 'ChartContainer'\n\n// ── ChartStyle ──────────────────────────────────────────────────────────────\n// 將 config 內每個 key 的 color 注入 scoped CSS variable(`--color-{key}`),\n// 供 Recharts `fill={`var(--color-${key})`}` 直接消費。\n\nconst THEMES = { light: '', dark: '[data-theme=dark] ' } as const\n\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const entries = Object.entries(config).filter(([, v]) => 'color' in v || 'theme' in v)\n if (entries.length === 0) return null\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(([theme, prefix]) => {\n const vars = entries\n .map(([key, item]) => {\n const color =\n 'theme' in item ? item.theme?.[theme as keyof typeof item.theme] : item.color\n return color ? ` --color-${key}: ${color};` : null\n })\n .filter(Boolean)\n .join('\\n')\n return `${prefix}[data-chart=${id}] {\\n${vars}\\n}`\n })\n .join('\\n'),\n }}\n />\n )\n}\n\n// ── ChartTooltip / ChartTooltipContent ─────────────────────────────────────\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\n\ntype RechartsTooltipPayloadItem = {\n value?: string | number\n name?: string | number\n dataKey?: string | number\n color?: string\n payload?: unknown\n [key: string]: unknown\n}\n\ninterface ChartTooltipContentProps extends Omit<React.ComponentProps<'div'>, 'color'> {\n active?: boolean\n payload?: RechartsTooltipPayloadItem[]\n label?: unknown\n labelFormatter?: (value: unknown, payload: RechartsTooltipPayloadItem[]) => React.ReactNode\n labelClassName?: string\n formatter?: (\n value: unknown,\n name: unknown,\n item: RechartsTooltipPayloadItem,\n index: number,\n payload: unknown,\n ) => React.ReactNode\n color?: string\n hideLabel?: boolean\n hideIndicator?: boolean\n indicator?: 'line' | 'dot' | 'dashed'\n nameKey?: string\n labelKey?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst ChartTooltipContent = React.forwardRef<HTMLDivElement, ChartTooltipContentProps>(\n (\n {\n active,\n payload,\n className,\n indicator = 'dot',\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n },\n ref,\n ) => {\n const { config } = useChart()\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) return null\n const [item] = payload\n const key = `${labelKey || item.dataKey || item.name || 'value'}`\n const itemConfig = getPayloadConfig(config, item, key)\n const value =\n !labelKey && typeof label === 'string'\n ? config[label as keyof typeof config]?.label || label\n : itemConfig?.label\n if (labelFormatter) return <div className={cn('font-medium', labelClassName)}>{labelFormatter(value, payload)}</div>\n if (!value) return null\n return <div className={cn('font-medium', labelClassName)}>{value}</div>\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])\n\n if (!active || !payload?.length) return null\n\n const nestLabel = payload.length === 1 && indicator !== 'dot'\n\n return (\n <div\n ref={ref}\n // 2026-05-31 #5:自訂 tooltip 補 role=status + aria-live,保留 Recharts 原生 SR 朗讀(spec L146 宣稱鍵盤可達+讀屏)\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n 'grid min-w-32 items-start gap-1.5 rounded-md border border-border bg-surface-raised px-2.5 py-1.5 text-caption shadow-[var(--elevation-200)]',\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || 'value'}`\n const itemConfig = getPayloadConfig(config, item, key)\n const indicatorColor = color || (item.payload as { fill?: string })?.fill || item.color\n\n return (\n <div\n key={item.dataKey || index}\n className={cn(\n 'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-fg-muted',\n indicator === 'dot' && 'items-center',\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn('shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)', {\n 'h-2.5 w-2.5': indicator === 'dot',\n 'w-1': indicator === 'line',\n 'w-0 border-[1.5px] border-dashed bg-transparent': indicator === 'dashed',\n 'my-0.5': nestLabel && indicator === 'dashed',\n })}\n style={\n {\n '--color-bg': indicatorColor,\n '--color-border': indicatorColor,\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n 'flex flex-1 justify-between leading-none',\n nestLabel ? 'items-end' : 'items-center',\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-fg-secondary\">{itemConfig?.label || item.name}</span>\n </div>\n {/* 2026-07-04 修:falsy check 會讓合法資料值 0 不渲染(shadcn 上游同病)→ null check */}\n {item.value != null && (\n <span className=\"text-foreground font-mono font-medium tabular-nums\">\n {item.value.toLocaleString()}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n },\n)\nChartTooltipContent.displayName = 'ChartTooltipContent'\n\n// ── ChartLegend / ChartLegendContent ───────────────────────────────────────\n\nconst ChartLegend = RechartsPrimitive.Legend\n\ntype RechartsLegendPayloadItem = {\n value?: unknown\n dataKey?: string | number\n color?: string\n payload?: unknown\n [key: string]: unknown\n}\n\ninterface ChartLegendContentProps extends React.ComponentProps<'div'> {\n payload?: RechartsLegendPayloadItem[]\n verticalAlign?: 'top' | 'middle' | 'bottom'\n hideIcon?: boolean\n nameKey?: string\n}\n\nconst ChartLegendContent = React.forwardRef<HTMLDivElement, ChartLegendContentProps>(\n ({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey }, ref) => {\n const { config } = useChart()\n if (!payload?.length) return null\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-center gap-4',\n verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n className,\n )}\n >\n {payload.map((item) => {\n const key = `${nameKey || item.dataKey || 'value'}`\n const itemConfig = getPayloadConfig(config, item, key)\n return (\n <div\n key={String(item.value)}\n className=\"flex items-center gap-1.5 text-fg-secondary [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-fg-muted\"\n >\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div className=\"h-2 w-2 shrink-0 rounded-xs\" style={{ backgroundColor: item.color }} />\n )}\n {itemConfig?.label}\n </div>\n )\n })}\n </div>\n )\n },\n)\nChartLegendContent.displayName = 'ChartLegendContent'\n\n// ── helpers ────────────────────────────────────────────────────────────────\n\nfunction getPayloadConfig(\n config: ChartConfig,\n payload: unknown,\n key: string,\n) {\n if (typeof payload !== 'object' || payload === null) return undefined\n const payloadPayload =\n 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n ? payload.payload\n : undefined\n let configLabelKey: string = key\n if (key in (payload as Record<string, unknown>) && typeof (payload as Record<string, unknown>)[key] === 'string') {\n configLabelKey = (payload as Record<string, string>)[key]\n } else if (payloadPayload && key in payloadPayload && typeof (payloadPayload as Record<string, unknown>)[key] === 'string') {\n configLabelKey = (payloadPayload as Record<string, string>)[key]\n }\n return configLabelKey in config ? config[configLabelKey] : config[key]\n}\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const chartMeta = {\n component: 'Chart',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default'],\n tokens: {\n bg: ['bg-surface-raised', 'bg-transparent'],\n fg: ['text-fg-muted', 'text-fg-secondary', 'text-foreground'],\n ring: [],\n },\n} as const\n\nexport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chart.js","sources":["../../../src/components/Chart/chart.tsx"],"sourcesContent":["import * as React from 'react'\nimport * as RechartsPrimitive from 'recharts'\nimport { cn } from '@/lib/utils'\n\n/**\n * Chart — shadcn-style wrapper over Recharts + 本 DS token\n *\n * 結構對齊 shadcn chart(ChartContainer / ChartTooltipContent / ChartLegendContent),\n * 但所有視覺(tooltip / legend / grid / axis)改用本 DS token。\n *\n * ── Color mapping ──\n * ChartConfig.{key}.color 接受 2 種形式:\n * 1. CSS var 字串('var(--chart-1)' 等)\n * 2. 任何合法 CSS color\n * 預設建議使用 --chart-1..5(本 DS 提供的 5 色類別 token)\n *\n * ── 視覺 token ──\n * Tooltip: bg-surface-raised / border-border / shadow-[var(--elevation-200)] / rounded-md\n * Legend text: text-fg-secondary / text-caption\n * Grid: stroke-divider\n * Axis tick: text-fg-muted / text-caption\n * 滑過指示(Recharts tooltip cursor):長條圖的帶 fill-neutral-hover / 折線等的直線 stroke-border\n */\n\nexport type ChartConfig = {\n [k in string]: {\n label?: React.ReactNode\n icon?: React.ComponentType\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<'light' | 'dark', string> }\n )\n}\n\ntype ChartContextProps = { config: ChartConfig }\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n const ctx = React.useContext(ChartContext)\n if (!ctx) throw new Error('useChart 必須在 <ChartContainer> 內使用')\n return ctx\n}\n\n// ── ChartContainer ─────────────────────────────────────────────────────────\n\ninterface ChartContainerProps extends React.ComponentProps<'div'> {\n config: ChartConfig\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>['children']\n}\n\nconst ChartContainer = React.forwardRef<HTMLDivElement, ChartContainerProps>(\n ({ id, className, children, config, ...props }, ref) => {\n const uniqueId = React.useId()\n const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`\n // Memoize provider value(2026-04-22 D3 perf audit):避免 render 重建 wrapper object\n const ctxValue = React.useMemo(() => ({ config }), [config])\n\n return (\n <ChartContext.Provider value={ctxValue}>\n <div\n ref={ref}\n // 2026-07-14 audit Dim 9(passthrough 完整度):id 原被 destructure 只拿去組 data-chart,\n // native id attr 靜默吞掉 —— consumer 傳 id 期待 DOM id(anchor / aria 參照)必須落地。\n id={id}\n data-chart={chartId}\n className={cn(\n // 整體視覺套用本 DS typography + token\n // 預設 aspect-video(16:9)— Recharts ResponsiveContainer 需 parent 有高度。\n // Consumer 若需其他比例,包 <AspectRatio ratio={n}> 覆寫(AspectRatio 的 padding-bottom 高度會蓋過此 class)。\n 'flex aspect-video justify-center text-caption',\n // recharts 內部預設樣式覆寫:grid / axis / tooltip shadow 等\n \"[&_.recharts-cartesian-grid_line]:stroke-divider\",\n \"[&_.recharts-cartesian-axis-tick_text]:fill-fg-muted\",\n \"[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-divider\",\n \"[&_.recharts-reference-line_[stroke='#ccc']]:stroke-divider\",\n \"[&_.recharts-dot[stroke='#fff']]:stroke-transparent\",\n // 滑過指示(Recharts 叫 tooltip cursor):Recharts 3.8.1 把它寫死成 #ccc —— 長條圖的帶是\n // fill '#ccc'(getCursorRectangle.ts:22),折線 / 面積 / 散佈 / 雷達的線是 stroke '#ccc'(Cursor.tsx:107)。\n // 深色主題下是一條亮灰直條,不是 DS 的顏色。只換 Recharts 的預設值(看屬性值),consumer 自己傳的 cursor 樣式不蓋。\n // 帶 = 資料表格列的滑過色 --neutral-hover(半透明,疊在圖表所在的底上,兩個主題方向都對);\n // 線 = --border,比網格線 --divider 高一階,才看得出「這條是滑過指示、不是網格」。\n // 為什麼可以有這條帶 → hit-area-canonical.md「滑過原則」三-2 例外清單②;顏色與實測 → chart.spec.md「滑過指示」。\n \"[&_.recharts-tooltip-cursor[fill='#ccc']]:fill-neutral-hover\",\n \"[&_.recharts-tooltip-cursor[stroke='#ccc']]:stroke-border\",\n // @focus-suppress N — 不適用(不可操作 → 問題一);承擔者:SVG 內層 group,不是 tab stop(tab stop 是 .recharts-surface)\n \"[&_.recharts-layer]:outline-none\",\n \"[&_.recharts-sector[stroke='#fff']]:stroke-transparent\",\n // @focus-suppress N — 不適用(不可操作 → 問題一);承擔者:同上,SVG 扇形不是 tab stop\n \"[&_.recharts-sector]:outline-none\",\n // 2026-09-07 刪:recharts 3.x 的 accessibilityLayer 預設為 true,\n // RootSurface.js:54 `tabIndex = hasAccessibilityLayer ? 0 : undefined`、:59 `role = 'application'`\n // → surface 可 Tab、可用方向鍵瀏覽資料點 = 可操作,依 focus-canonical 必須有可見焦點指示。\n // 原本這行把全域 base.css:44-47 的焦點框關掉 = WCAG 2.4.7 違規。\n // 保留上一行的 .recharts-sector(扇形不可聚焦,那行是防禦性的)。\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer>\n {children}\n </RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n )\n },\n)\nChartContainer.displayName = 'ChartContainer'\n\n// ── ChartStyle ──────────────────────────────────────────────────────────────\n// 將 config 內每個 key 的 color 注入 scoped CSS variable(`--color-{key}`),\n// 供 Recharts `fill={`var(--color-${key})`}` 直接消費。\n\nconst THEMES = { light: '', dark: '[data-theme=dark] ' } as const\n\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const entries = Object.entries(config).filter(([, v]) => 'color' in v || 'theme' in v)\n if (entries.length === 0) return null\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(([theme, prefix]) => {\n const vars = entries\n .map(([key, item]) => {\n const color =\n 'theme' in item ? item.theme?.[theme as keyof typeof item.theme] : item.color\n return color ? ` --color-${key}: ${color};` : null\n })\n .filter(Boolean)\n .join('\\n')\n return `${prefix}[data-chart=${id}] {\\n${vars}\\n}`\n })\n .join('\\n'),\n }}\n />\n )\n}\n\n// ── ChartTooltip / ChartTooltipContent ─────────────────────────────────────\n\nconst ChartTooltip = RechartsPrimitive.Tooltip\n\ntype RechartsTooltipPayloadItem = {\n value?: string | number\n name?: string | number\n dataKey?: string | number\n color?: string\n payload?: unknown\n [key: string]: unknown\n}\n\ninterface ChartTooltipContentProps extends Omit<React.ComponentProps<'div'>, 'color'> {\n active?: boolean\n payload?: RechartsTooltipPayloadItem[]\n label?: unknown\n labelFormatter?: (value: unknown, payload: RechartsTooltipPayloadItem[]) => React.ReactNode\n labelClassName?: string\n formatter?: (\n value: unknown,\n name: unknown,\n item: RechartsTooltipPayloadItem,\n index: number,\n payload: unknown,\n ) => React.ReactNode\n color?: string\n hideLabel?: boolean\n hideIndicator?: boolean\n indicator?: 'line' | 'dot' | 'dashed'\n nameKey?: string\n labelKey?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst ChartTooltipContent = React.forwardRef<HTMLDivElement, ChartTooltipContentProps>(\n (\n {\n active,\n payload,\n className,\n indicator = 'dot',\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n },\n ref,\n ) => {\n const { config } = useChart()\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) return null\n const [item] = payload\n const key = `${labelKey || item.dataKey || item.name || 'value'}`\n const itemConfig = getPayloadConfig(config, item, key)\n const value =\n !labelKey && typeof label === 'string'\n ? config[label as keyof typeof config]?.label || label\n : itemConfig?.label\n if (labelFormatter) return <div className={cn('font-medium', labelClassName)}>{labelFormatter(value, payload)}</div>\n if (!value) return null\n return <div className={cn('font-medium', labelClassName)}>{value}</div>\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])\n\n if (!active || !payload?.length) return null\n\n const nestLabel = payload.length === 1 && indicator !== 'dot'\n\n return (\n <div\n ref={ref}\n // 2026-05-31 #5:自訂 tooltip 補 role=status + aria-live,保留 Recharts 原生 SR 朗讀(spec L146 宣稱鍵盤可達+讀屏)\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\n 'grid min-w-32 items-start gap-1.5 rounded-md border border-border bg-surface-raised px-2.5 py-1.5 text-caption shadow-[var(--elevation-200)]',\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || 'value'}`\n const itemConfig = getPayloadConfig(config, item, key)\n const indicatorColor = color || (item.payload as { fill?: string })?.fill || item.color\n\n return (\n <div\n key={item.dataKey || index}\n className={cn(\n 'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-fg-muted',\n indicator === 'dot' && 'items-center',\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn('shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)', {\n 'h-2.5 w-2.5': indicator === 'dot',\n 'w-1': indicator === 'line',\n 'w-0 border-[1.5px] border-dashed bg-transparent': indicator === 'dashed',\n 'my-0.5': nestLabel && indicator === 'dashed',\n })}\n style={\n {\n '--color-bg': indicatorColor,\n '--color-border': indicatorColor,\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n 'flex flex-1 justify-between leading-none',\n nestLabel ? 'items-end' : 'items-center',\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-fg-secondary\">{itemConfig?.label || item.name}</span>\n </div>\n {/* 2026-07-04 修:falsy check 會讓合法資料值 0 不渲染(shadcn 上游同病)→ null check */}\n {item.value != null && (\n <span className=\"text-foreground font-mono font-medium tabular-nums\">\n {item.value.toLocaleString()}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n },\n)\nChartTooltipContent.displayName = 'ChartTooltipContent'\n\n// ── ChartLegend / ChartLegendContent ───────────────────────────────────────\n\nconst ChartLegend = RechartsPrimitive.Legend\n\ntype RechartsLegendPayloadItem = {\n value?: unknown\n dataKey?: string | number\n color?: string\n payload?: unknown\n [key: string]: unknown\n}\n\ninterface ChartLegendContentProps extends React.ComponentProps<'div'> {\n payload?: RechartsLegendPayloadItem[]\n verticalAlign?: 'top' | 'middle' | 'bottom'\n hideIcon?: boolean\n nameKey?: string\n}\n\nconst ChartLegendContent = React.forwardRef<HTMLDivElement, ChartLegendContentProps>(\n ({ className, hideIcon = false, payload, verticalAlign = 'bottom', nameKey }, ref) => {\n const { config } = useChart()\n if (!payload?.length) return null\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex items-center justify-center gap-4',\n verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n className,\n )}\n >\n {payload.map((item) => {\n const key = `${nameKey || item.dataKey || 'value'}`\n const itemConfig = getPayloadConfig(config, item, key)\n return (\n <div\n key={String(item.value)}\n className=\"flex items-center gap-1.5 text-fg-secondary [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-fg-muted\"\n >\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div className=\"h-2 w-2 shrink-0 rounded-xs\" style={{ backgroundColor: item.color }} />\n )}\n {itemConfig?.label}\n </div>\n )\n })}\n </div>\n )\n },\n)\nChartLegendContent.displayName = 'ChartLegendContent'\n\n// ── helpers ────────────────────────────────────────────────────────────────\n\nfunction getPayloadConfig(\n config: ChartConfig,\n payload: unknown,\n key: string,\n) {\n if (typeof payload !== 'object' || payload === null) return undefined\n const payloadPayload =\n 'payload' in payload && typeof payload.payload === 'object' && payload.payload !== null\n ? payload.payload\n : undefined\n let configLabelKey: string = key\n if (key in (payload as Record<string, unknown>) && typeof (payload as Record<string, unknown>)[key] === 'string') {\n configLabelKey = (payload as Record<string, string>)[key]\n } else if (payloadPayload && key in payloadPayload && typeof (payloadPayload as Record<string, unknown>)[key] === 'string') {\n configLabelKey = (payloadPayload as Record<string, string>)[key]\n }\n return configLabelKey in config ? config[configLabelKey] : config[key]\n}\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const chartMeta = {\n component: 'Chart',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default'],\n tokens: {\n bg: ['bg-surface-raised', 'bg-transparent'],\n fg: ['text-fg-muted', 'text-fg-secondary', 'text-foreground'],\n ring: [],\n },\n} as const\n\nexport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -74,7 +74,7 @@ export declare const checkboxMeta: {
|
|
|
74
74
|
readonly tokens: {
|
|
75
75
|
readonly bg: readonly ["bg-disabled", "bg-primary", "bg-primary-hover", "bg-surface"];
|
|
76
76
|
readonly fg: readonly ["text-fg-disabled", "text-fg-secondary", "text-foreground"];
|
|
77
|
-
readonly ring: readonly ["ring
|
|
77
|
+
readonly ring: readonly ["--ring"];
|
|
78
78
|
};
|
|
79
79
|
readonly defaultSize: "md";
|
|
80
80
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAE7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAM3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAM1E,QAAA,MAAM,gBAAgB;;
|
|
1
|
+
{"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAE7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAM3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAM1E,QAAA,MAAM,gBAAgB;;8EAuCrB,CAAA;AAsCD,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,iBAAiB,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC,EACjG,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,uHAAuH;IACvH,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,iDAAiD;IACjD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAID,QAAA,MAAM,QAAQ,yFAuOb,CAAA;AAKD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -27,7 +27,15 @@ const checkboxVariants = cva(
|
|
|
27
27
|
"disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent",
|
|
28
28
|
// readOnly:鎖定互動但維持 checked/unchecked 視覺
|
|
29
29
|
"data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default",
|
|
30
|
-
|
|
30
|
+
// readOnly × hover:每個勾選狀態釘在**自己的**靜止值,不升 hover 階(checkbox.spec.md「狀態」表 readonly 列)。
|
|
31
|
+
// pointer-events-none 擋不住:指標停在 SelectionItem 的 <label for> 上時,HTML 讓被標記的控件一起進入 :hover。
|
|
32
|
+
// 原本只有一條 `data-[readonly=true]:hover:border-border`:bg 沒釘、已勾選的框與它同特異度且排在後面 → 滑過 label
|
|
33
|
+
// 仍轉 primary-hover;而且 border-border 是「未勾選」的靜止值,就算贏了也會把已勾選的框變灰。
|
|
34
|
+
// 寫法同本檔 disabled 守衛(`disabled:hover:border-transparent`)與 switch.tsx 的 `disabled:data-[state=…]:hover:`。
|
|
35
|
+
// `enabled:`:readOnly 與 disabled 同時成立時靜止外觀本來就是 disabled,hover 交給 disabled 自己的規則,這裡不搶。
|
|
36
|
+
"data-[readonly=true]:enabled:data-[state=unchecked]:hover:border-border",
|
|
37
|
+
"data-[readonly=true]:enabled:data-[state=checked]:hover:bg-primary data-[readonly=true]:enabled:data-[state=checked]:hover:border-primary",
|
|
38
|
+
"data-[readonly=true]:enabled:data-[state=indeterminate]:hover:bg-primary data-[readonly=true]:enabled:data-[state=indeterminate]:hover:border-primary"
|
|
31
39
|
],
|
|
32
40
|
{
|
|
33
41
|
variants: {
|
|
@@ -212,7 +220,7 @@ const checkboxMeta = {
|
|
|
212
220
|
tokens: {
|
|
213
221
|
bg: ["bg-disabled", "bg-primary", "bg-primary-hover", "bg-surface"],
|
|
214
222
|
fg: ["text-fg-disabled", "text-fg-secondary", "text-foreground"],
|
|
215
|
-
ring: ["ring
|
|
223
|
+
ring: ["--ring"]
|
|
216
224
|
},
|
|
217
225
|
defaultSize: "md"
|
|
218
226
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from \"@/design-system/components/Field/field-context\"\nimport { useControllable } from \"@/design-system/hooks/use-controllable\"\nimport { fieldWrapperStyles } from \"@/design-system/components/Field/field-wrapper\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport { BooleanValueIcon } from \"@/design-system/components/SelectionControl/boolean-value\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n // @hover-transition-allow: 這條過渡的主人是 checked ↔ unchecked 的狀態切換(Ant / Material 的核取框與切換鈕同樣會動),\n // 不是 hover;而且它是控件大小的點目標,不是指標掃過去的列面。hover 一律瞬間的規則見 tokens/motion/motion.spec.md。\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\n// ⚠️ 2026-06-12:發現 base.css `.lucide{stroke-width:1.75}` 全域規則自 2026-04-08 起\n// 無條件蓋掉本 prop(CSS class > SVG attribute)→ 本表從未真渲染過,上述 05-18 視覺\n// 測試兩者實為 0.875px(證據污染)。base.css 已改 `[stroke-width='2']` 限定,本表自此\n// 真實生效(pixel 驗證 1.50px)。SSOT → packages/design-system/ds-canonical/references/ui-dev-rules.md「小尺寸 icon stroke 補償」\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n// asChild / children 從公開型別 Omit(2026-07-17 d9i6):Checkbox 有**固定 anatomy** —\n// 內部恆渲染 Indicator(Check / Minus 兩子節點),非 Slot host。若保留 Radix `asChild`,\n// consumer 傳入的元素會被 Radix Slot `React.Children.only` 因多子節點在執行期拋錯(失敗記憶索引\n// 已 codify 此 asChild 多子節點 bug class);`children` 同理會被固定 Indicator 覆蓋。需自訂佈局\n// 請用 `label` / `description` / `icon` / `avatar` 一級 props(內部自動包 SelectionItem)。\nexport interface CheckboxProps\n extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild' | 'children'>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldLabel 接管);\n * CheckboxGroup 內每個 checkbox 的 label 是它自己的選項名,必保留\n * (同檔 shouldSuppressLabel = insideField && !insideGroup,AR50 anchor)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldDescription 接管);\n * CheckboxGroup 內的 description 屬各選項自有,必保留(同 label 的 group 例外)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * view — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n // controllable checked triplet(2026-07-17 d26i0):於 wrapper 層取出,交由 useControllable 持有\n // resolved 值,供 edit / view / readonly 三分支共用。原本 view / readonly 分支各自讀 raw prop →\n // uncontrolled(只傳 defaultChecked)在 edit 態 toggle 後切 mode 會丟失目前值(Radix 內部狀態\n // wrapper 讀不到)。移出 props 後 edit 主路徑改由 checked={resolvedChecked} 受控(見下方 rootEl)。\n checked: checkedProp,\n defaultChecked: defaultCheckedProp,\n onCheckedChange,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='view' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"view\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled'(如 DataTable disabled cell 直傳)必須落到真 disabled chrome —\n // 2026-06-12 修:原本只看 resolvedDisabled(prop/fieldCtx),mode='disabled' 無人消費\n // → disabled boolean cell 渲出可 focus 的正常外觀 checkbox(違 field-controls.spec L286)\n const effectiveDisabled = resolvedDisabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n\n // 2026-07-04 修:view / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props\n // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。\n // 抽出 Radix 專屬 form props(required/name/value 不可落到裸 span/div),其餘 DOM props 於兩分支\n // spread 轉發(與 Switch/RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。\n // (checked/defaultChecked/onCheckedChange 已於上方 top-level destructure 取出交 useControllable;\n // asChild/children 已從 CheckboxProps 型別 Omit — 固定 anatomy 非 Slot host,見型別註解。)\n const {\n required: _required,\n name: _name,\n value: _value,\n ...restDomProps\n } = props\n\n // ── Resolved checked SSOT(controlled / uncontrolled dual-mode,2026-07-17 d26i0)──────────\n // 對齊 Input / Textarea 既有 canonical(field-controls.spec.md「有值判定」:useControllable 內部\n // resolved value + form.reset bridge)。修 view / readonly 分支原各自讀 raw prop 導致「uncontrolled\n // 切 mode 丟失目前值」的 dual-mode 缺口。controlled(傳 checked)時 useControllable 純 passthrough,\n // edit 行為 Δ=0。\n const isControlled = checkedProp !== undefined\n const [resolvedChecked, setResolvedChecked] = useControllable<boolean | 'indeterminate'>({\n value: checkedProp,\n defaultValue: defaultCheckedProp ?? false,\n })\n\n // form.reset() bridge(uncontrolled only):wrapper 取得 controllable 主導後,HTML 標準 reset 不發\n // 變更事件 → 手動把 resolvedChecked 歸位 defaultChecked(對齊 input.tsx 同名 bridge)。keyed on\n // resolvedMode:切回 edit 重掛 native Root 時 effect 重跑補掛 listener;view / readonly(Root\n // unmount)時 innerRef 為 null → early return 不掛。\n const innerRef = React.useRef<HTMLButtonElement | null>(null)\n React.useEffect(() => {\n if (isControlled) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false)\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, resolvedMode, defaultCheckedProp, setResolvedChecked])\n // Merge refs(input.tsx setRef idiom):innerRef 供 reset bridge 取 form,同時轉發 forwardRef ref。\n const setRef = React.useCallback((el: HTMLButtonElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = el\n }, [ref])\n\n // ── mode='view'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 Check / X icon(true=勾 / false=叉,中性 foreground 色)。\n // boolean 值展示符號 SSOT = SelectionControl/boolean-value.tsx(勾/叉 icon + 中性色 + M22 世界級對照)。取代 BooleanDisplay。\n if (resolvedMode === 'view') {\n const isChecked = resolvedChecked === true\n // a11y(2026-07-17 d10i2):BooleanValueIcon 帶 aria-hidden → 裸 span 在 a11y tree 無名,\n // 布林值對螢幕閱讀器完全消失。給 view 容器 role=\"img\" + aria-label 承載值語意(對齊 MUI X\n // DataGrid boolean cell 的 icon role/label 世界級做法);consumer 若自帶 aria-label 則優先。\n const viewAriaLabel = (restDomProps as { 'aria-label'?: string })['aria-label'] ?? (isChecked ? '是' : '否')\n return (\n <span\n {...restDomProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n role=\"img\"\n aria-label={viewAriaLabel}\n className=\"inline-flex\"\n >\n <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />\n </span>\n )\n }\n\n // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」;2026-07-09 ✓/— glyph → Check/X icon)─\n // Field 內 readonly boolean = readonly 灰框 chrome + view 同款值語言(勾/叉 icon)。\n // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。\n // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩\n // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /\n // SAP=靜態文字 / Atlassian=readView / Ant Pro=文字)。\n // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);\n // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。\n if (effectiveReadOnly && insideField && effectiveLabel == null) {\n const isChecked = resolvedChecked === true\n const boxSize = resolvedBoxSize\n return (\n <div\n {...(restDomProps as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref as React.Ref<HTMLDivElement>}\n role=\"checkbox\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n className,\n )}\n >\n <BooleanValueIcon checked={isChecked} size={boxSize} />\n </div>\n )\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={setRef}\n disabled={effectiveDisabled}\n // controllable checked SSOT(2026-07-17 d26i0):Root 受 wrapper resolvedChecked 主導,\n // onCheckedChange 先同步 wrapper 內部 state 再 forward consumer → uncontrolled(只傳\n // defaultChecked)toggle 後切 mode(view / readonly)仍保留目前值。controlled(傳 checked)\n // 時 useControllable 純 passthrough,edit 行為 Δ=0。\n checked={resolvedChecked}\n onCheckedChange={(next) => {\n setResolvedChecked(next)\n onCheckedChange?.(next)\n }}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click\n // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍 toggle。\n // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 toggle;\n // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(readOnly 時 consumer onClick 一併鎖,\n // 與 pointer-events-none 擋直接點擊的行為一致)。\n onClick={(event) => {\n if (effectiveReadOnly) {\n event.preventDefault()\n return\n }\n props.onClick?.(event)\n }}\n >\n {/* 圖示切換走 Radix data-state(CSS),非 props.checked(JS)—— uncontrolled\n defaultChecked=\"indeterminate\" 時 props.checked=undefined,JS 判斷會錯渲 Check;\n data-state 涵蓋 controlled/uncontrolled 全部轉換(Radix 為 SSOT)。 */}\n <CheckboxPrimitive.Indicator className=\"group/indicator grid place-content-center text-current\">\n <Minus className=\"hidden group-data-[state=indeterminate]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n <Check className=\"hidden group-data-[state=checked]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={effectiveDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n // 'checked' = 勾選持續態(bg-primary;2026-07-07 meta 詞彙統一補列,含 indeterminate 同族)\n states: ['default', 'hover', 'checked', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from \"@/design-system/components/Field/field-context\"\nimport { useControllable } from \"@/design-system/hooks/use-controllable\"\nimport { fieldWrapperStyles } from \"@/design-system/components/Field/field-wrapper\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport { BooleanValueIcon } from \"@/design-system/components/SelectionControl/boolean-value\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n // @hover-transition-allow: 這條過渡的主人是 checked ↔ unchecked 的狀態切換(Ant / Material 的核取框與切換鈕同樣會動),\n // 不是 hover;而且它是控件大小的點目標,不是指標掃過去的列面。hover 一律瞬間的規則見 tokens/motion/motion.spec.md。\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n // readOnly × hover:每個勾選狀態釘在**自己的**靜止值,不升 hover 階(checkbox.spec.md「狀態」表 readonly 列)。\n // pointer-events-none 擋不住:指標停在 SelectionItem 的 <label for> 上時,HTML 讓被標記的控件一起進入 :hover。\n // 原本只有一條 `data-[readonly=true]:hover:border-border`:bg 沒釘、已勾選的框與它同特異度且排在後面 → 滑過 label\n // 仍轉 primary-hover;而且 border-border 是「未勾選」的靜止值,就算贏了也會把已勾選的框變灰。\n // 寫法同本檔 disabled 守衛(`disabled:hover:border-transparent`)與 switch.tsx 的 `disabled:data-[state=…]:hover:`。\n // `enabled:`:readOnly 與 disabled 同時成立時靜止外觀本來就是 disabled,hover 交給 disabled 自己的規則,這裡不搶。\n 'data-[readonly=true]:enabled:data-[state=unchecked]:hover:border-border',\n 'data-[readonly=true]:enabled:data-[state=checked]:hover:bg-primary data-[readonly=true]:enabled:data-[state=checked]:hover:border-primary',\n 'data-[readonly=true]:enabled:data-[state=indeterminate]:hover:bg-primary data-[readonly=true]:enabled:data-[state=indeterminate]:hover:border-primary',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\n// ⚠️ 2026-06-12:發現 base.css `.lucide{stroke-width:1.75}` 全域規則自 2026-04-08 起\n// 無條件蓋掉本 prop(CSS class > SVG attribute)→ 本表從未真渲染過,上述 05-18 視覺\n// 測試兩者實為 0.875px(證據污染)。base.css 已改 `[stroke-width='2']` 限定,本表自此\n// 真實生效(pixel 驗證 1.50px)。SSOT → packages/design-system/ds-canonical/references/ui-dev-rules.md「小尺寸 icon stroke 補償」\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n// asChild / children 從公開型別 Omit(2026-07-17 d9i6):Checkbox 有**固定 anatomy** —\n// 內部恆渲染 Indicator(Check / Minus 兩子節點),非 Slot host。若保留 Radix `asChild`,\n// consumer 傳入的元素會被 Radix Slot `React.Children.only` 因多子節點在執行期拋錯(失敗記憶索引\n// 已 codify 此 asChild 多子節點 bug class);`children` 同理會被固定 Indicator 覆蓋。需自訂佈局\n// 請用 `label` / `description` / `icon` / `avatar` 一級 props(內部自動包 SelectionItem)。\nexport interface CheckboxProps\n extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild' | 'children'>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldLabel 接管);\n * CheckboxGroup 內每個 checkbox 的 label 是它自己的選項名,必保留\n * (同檔 shouldSuppressLabel = insideField && !insideGroup,AR50 anchor)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldDescription 接管);\n * CheckboxGroup 內的 description 屬各選項自有,必保留(同 label 的 group 例外)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * view — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n // controllable checked triplet(2026-07-17 d26i0):於 wrapper 層取出,交由 useControllable 持有\n // resolved 值,供 edit / view / readonly 三分支共用。原本 view / readonly 分支各自讀 raw prop →\n // uncontrolled(只傳 defaultChecked)在 edit 態 toggle 後切 mode 會丟失目前值(Radix 內部狀態\n // wrapper 讀不到)。移出 props 後 edit 主路徑改由 checked={resolvedChecked} 受控(見下方 rootEl)。\n checked: checkedProp,\n defaultChecked: defaultCheckedProp,\n onCheckedChange,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='view' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"view\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled'(如 DataTable disabled cell 直傳)必須落到真 disabled chrome —\n // 2026-06-12 修:原本只看 resolvedDisabled(prop/fieldCtx),mode='disabled' 無人消費\n // → disabled boolean cell 渲出可 focus 的正常外觀 checkbox(違 field-controls.spec L286)\n const effectiveDisabled = resolvedDisabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n\n // 2026-07-04 修:view / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props\n // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。\n // 抽出 Radix 專屬 form props(required/name/value 不可落到裸 span/div),其餘 DOM props 於兩分支\n // spread 轉發(與 Switch/RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。\n // (checked/defaultChecked/onCheckedChange 已於上方 top-level destructure 取出交 useControllable;\n // asChild/children 已從 CheckboxProps 型別 Omit — 固定 anatomy 非 Slot host,見型別註解。)\n const {\n required: _required,\n name: _name,\n value: _value,\n ...restDomProps\n } = props\n\n // ── Resolved checked SSOT(controlled / uncontrolled dual-mode,2026-07-17 d26i0)──────────\n // 對齊 Input / Textarea 既有 canonical(field-controls.spec.md「有值判定」:useControllable 內部\n // resolved value + form.reset bridge)。修 view / readonly 分支原各自讀 raw prop 導致「uncontrolled\n // 切 mode 丟失目前值」的 dual-mode 缺口。controlled(傳 checked)時 useControllable 純 passthrough,\n // edit 行為 Δ=0。\n const isControlled = checkedProp !== undefined\n const [resolvedChecked, setResolvedChecked] = useControllable<boolean | 'indeterminate'>({\n value: checkedProp,\n defaultValue: defaultCheckedProp ?? false,\n })\n\n // form.reset() bridge(uncontrolled only):wrapper 取得 controllable 主導後,HTML 標準 reset 不發\n // 變更事件 → 手動把 resolvedChecked 歸位 defaultChecked(對齊 input.tsx 同名 bridge)。keyed on\n // resolvedMode:切回 edit 重掛 native Root 時 effect 重跑補掛 listener;view / readonly(Root\n // unmount)時 innerRef 為 null → early return 不掛。\n const innerRef = React.useRef<HTMLButtonElement | null>(null)\n React.useEffect(() => {\n if (isControlled) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false)\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, resolvedMode, defaultCheckedProp, setResolvedChecked])\n // Merge refs(input.tsx setRef idiom):innerRef 供 reset bridge 取 form,同時轉發 forwardRef ref。\n const setRef = React.useCallback((el: HTMLButtonElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = el\n }, [ref])\n\n // ── mode='view'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 Check / X icon(true=勾 / false=叉,中性 foreground 色)。\n // boolean 值展示符號 SSOT = SelectionControl/boolean-value.tsx(勾/叉 icon + 中性色 + M22 世界級對照)。取代 BooleanDisplay。\n if (resolvedMode === 'view') {\n const isChecked = resolvedChecked === true\n // a11y(2026-07-17 d10i2):BooleanValueIcon 帶 aria-hidden → 裸 span 在 a11y tree 無名,\n // 布林值對螢幕閱讀器完全消失。給 view 容器 role=\"img\" + aria-label 承載值語意(對齊 MUI X\n // DataGrid boolean cell 的 icon role/label 世界級做法);consumer 若自帶 aria-label 則優先。\n const viewAriaLabel = (restDomProps as { 'aria-label'?: string })['aria-label'] ?? (isChecked ? '是' : '否')\n return (\n <span\n {...restDomProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n role=\"img\"\n aria-label={viewAriaLabel}\n className=\"inline-flex\"\n >\n <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />\n </span>\n )\n }\n\n // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」;2026-07-09 ✓/— glyph → Check/X icon)─\n // Field 內 readonly boolean = readonly 灰框 chrome + view 同款值語言(勾/叉 icon)。\n // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。\n // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩\n // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /\n // SAP=靜態文字 / Atlassian=readView / Ant Pro=文字)。\n // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);\n // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。\n if (effectiveReadOnly && insideField && effectiveLabel == null) {\n const isChecked = resolvedChecked === true\n const boxSize = resolvedBoxSize\n return (\n <div\n {...(restDomProps as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref as React.Ref<HTMLDivElement>}\n role=\"checkbox\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n className,\n )}\n >\n <BooleanValueIcon checked={isChecked} size={boxSize} />\n </div>\n )\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={setRef}\n disabled={effectiveDisabled}\n // controllable checked SSOT(2026-07-17 d26i0):Root 受 wrapper resolvedChecked 主導,\n // onCheckedChange 先同步 wrapper 內部 state 再 forward consumer → uncontrolled(只傳\n // defaultChecked)toggle 後切 mode(view / readonly)仍保留目前值。controlled(傳 checked)\n // 時 useControllable 純 passthrough,edit 行為 Δ=0。\n checked={resolvedChecked}\n onCheckedChange={(next) => {\n setResolvedChecked(next)\n onCheckedChange?.(next)\n }}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click\n // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍 toggle。\n // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 toggle;\n // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(readOnly 時 consumer onClick 一併鎖,\n // 與 pointer-events-none 擋直接點擊的行為一致)。\n onClick={(event) => {\n if (effectiveReadOnly) {\n event.preventDefault()\n return\n }\n props.onClick?.(event)\n }}\n >\n {/* 圖示切換走 Radix data-state(CSS),非 props.checked(JS)—— uncontrolled\n defaultChecked=\"indeterminate\" 時 props.checked=undefined,JS 判斷會錯渲 Check;\n data-state 涵蓋 controlled/uncontrolled 全部轉換(Radix 為 SSOT)。 */}\n <CheckboxPrimitive.Indicator className=\"group/indicator grid place-content-center text-current\">\n <Minus className=\"hidden group-data-[state=indeterminate]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n <Check className=\"hidden group-data-[state=checked]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={effectiveDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n // 'checked' = 勾選持續態(bg-primary;2026-07-07 meta 詞彙統一補列,含 indeterminate 同族)\n states: ['default', 'hover', 'checked', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['--ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}
|
|
@@ -66,7 +66,7 @@ export declare const chipMeta: {
|
|
|
66
66
|
readonly tokens: {
|
|
67
67
|
readonly bg: readonly ["bg-surface"];
|
|
68
68
|
readonly fg: readonly ["text-fg-disabled", "text-fg-secondary", "text-foreground", "text-primary"];
|
|
69
|
-
readonly ring: readonly ["ring
|
|
69
|
+
readonly ring: readonly ["--ring"];
|
|
70
70
|
};
|
|
71
71
|
};
|
|
72
72
|
export { Chip, ChipGroup, chipVariants };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/components/Chip/chip.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,oBAAoB,MAAM,8BAA8B,CAAA;AAEpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAiB9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,QAAA,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"chip.d.ts","sourceRoot":"","sources":["../../../src/components/Chip/chip.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,oBAAoB,MAAM,8BAA8B,CAAA;AAEpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAiB9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,QAAA,MAAM,YAAY,oFAmCjB,CAAA;AAED,UAAU,aAAa;IACrB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,wBAAwB;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,uCAAuC;IACvC,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB;AAED,MAAM,WAAW,eAKf,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,oBAAoB,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACvF,aAAa;IACf,yDAAyD;IACzD,OAAO,CAAC,EAAE,QAAQ,CAAA;CACnB;AAED,MAAM,WAAW,eACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EACnD,aAAa;IACf,gEAAgE;IAChE,OAAO,EAAE,QAAQ,CAAA;CAClB;AAED,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,eAAe,CAAA;AAEzD,QAAA,MAAM,IAAI,qFAsCT,CAAA;AAKD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAExD,MAAM,MAAM,cAAc,GAAG,KAAK,CAAC,wBAAwB,CACzD,OAAO,oBAAoB,CAAC,IAAI,CACjC,GAAG;IACF,qDAAqD;IACrD,MAAM,CAAC,EAAE,eAAe,CAAA;CACzB,CAAA;AAED,QAAA,MAAM,SAAS,uFA2Bd,CAAA;AAwOD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;CAgBX,CAAA;AAEV,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -14,7 +14,9 @@ const chipVariants = cva(
|
|
|
14
14
|
// 預設文字: text-fg-secondary (neutral-8) — 對齊 SegmentedControl / Tabs 未選狀態
|
|
15
15
|
"bg-surface text-fg-secondary",
|
|
16
16
|
"text-body leading-compact font-medium whitespace-nowrap",
|
|
17
|
-
|
|
17
|
+
// 不寫 transition-colors:hover 的外框與字色一律瞬間(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色與外框,待辦總帳 L9 / N4(3))。
|
|
18
|
+
// 選中的外框與字色跟 hover 是同一組 CSS 屬性 —— 過渡綁的是屬性,做不到「hover 瞬間、選中過渡」(滑鼠點選的當下
|
|
19
|
+
// 指標就在 chip 上)→ 選中變色一併瞬間。選中切換保留過渡的例外只有勾選框 / 單選圓 / 開關(motion.spec.md「唯一的例外」)。
|
|
18
20
|
"cursor-pointer select-none",
|
|
19
21
|
// hover(未選):border 加深一階 + 文字轉深,對齊 Input / SegmentedControl hover
|
|
20
22
|
"hover:border-border-hover hover:text-foreground",
|
|
@@ -261,7 +263,7 @@ const chipMeta = {
|
|
|
261
263
|
tokens: {
|
|
262
264
|
bg: ["bg-surface"],
|
|
263
265
|
fg: ["text-fg-disabled", "text-fg-secondary", "text-foreground", "text-primary"],
|
|
264
|
-
ring: ["ring
|
|
266
|
+
ring: ["--ring"]
|
|
265
267
|
}
|
|
266
268
|
};
|
|
267
269
|
export {
|