@qijenchen/design-system 0.1.0-beta.83 → 0.1.0-beta.85
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/CLAUDE.md +8 -8
- package/README.md +3 -2
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +22 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +4 -4
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +1 -1
- package/dist/components/Breadcrumb/breadcrumb.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 +12 -3
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +13 -5
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +7 -1
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +8 -3
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +1 -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 +8 -4
- 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.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +10 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +5 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +30 -13
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +53 -62
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +10 -4
- package/dist/components/DataTable/column-types.d.ts.map +1 -1
- package/dist/components/DataTable/column-types.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +183 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts +47 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.js +154 -0
- package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +285 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +29 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js +187 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +11 -15
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +140 -0
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +5 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +36 -15
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-operators.js +140 -0
- package/dist/components/DataTable/filter-operators.js.map +1 -0
- package/dist/components/DataTable/filter-tree.js +312 -0
- package/dist/components/DataTable/filter-tree.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +3 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +12 -1
- package/dist/components/DataTable/index.js.map +1 -1
- package/dist/components/DataTable/lib/column-meta.js +25 -0
- package/dist/components/DataTable/lib/column-meta.js.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +3 -1
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +7 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +105 -74
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +20 -3
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +38 -20
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +3 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +45 -5
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +19 -0
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +21 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
- package/dist/components/Field/field-edit-keys.js +19 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +20 -1
- package/dist/components/Field/field-types.d.ts.map +1 -1
- package/dist/components/Field/field-types.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +3 -2
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +38 -39
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +2 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/index.d.ts +1 -0
- package/dist/components/Field/index.d.ts.map +1 -1
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/index.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +1 -1
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +7 -2
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +22 -1
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +5 -3
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.d.ts.map +1 -0
- package/dist/components/InlineEdit/index.js +5 -0
- package/dist/components/InlineEdit/index.js.map +1 -0
- package/dist/components/InlineEdit/inline-edit.d.ts +59 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
- package/dist/components/InlineEdit/inline-edit.js +164 -0
- package/dist/components/InlineEdit/inline-edit.js.map +1 -0
- package/dist/components/Input/input.d.ts +6 -11
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +43 -13
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +2 -2
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +6 -5
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts +14 -2
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +25 -3
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +3 -3
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +6 -5
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -6
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/pagination.js +1 -1
- package/dist/components/Pagination/pagination.js.map +1 -1
- package/dist/components/PeoplePicker/index.js +7 -1
- package/dist/components/PeoplePicker/index.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +9 -2
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +23 -10
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +6 -3
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +19 -14
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +3 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +2 -2
- package/dist/components/ProfileCard/profile-card.js +3 -3
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +15 -9
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +11 -1
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +6 -3
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts +46 -5
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +109 -28
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +3 -1
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +8 -1
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
- package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
- package/dist/components/SelectionControl/boolean-value.js +23 -0
- package/dist/components/SelectionControl/boolean-value.js.map +1 -0
- package/dist/components/SelectionControl/index.d.ts +1 -0
- package/dist/components/SelectionControl/index.d.ts.map +1 -1
- package/dist/components/SelectionControl/index.js +2 -0
- package/dist/components/SelectionControl/index.js.map +1 -1
- package/dist/components/Sheet/index.js +2 -1
- package/dist/components/Sheet/sheet.d.ts +7 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +5 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +9 -4
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +8 -10
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +6 -6
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +3 -2
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +15 -1
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -2
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +50 -42
- 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 +42 -38
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +8 -6
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +7 -5
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +56 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +211 -12
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +115 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -108
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +7 -6
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/tokens/categorical-color.js +15 -0
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/motion/motion.d.ts +7 -6
- package/dist/tokens/motion/motion.d.ts.map +1 -1
- package/dist/tokens/motion/motion.js +6 -6
- package/dist/tokens/motion/motion.js.map +1 -1
- package/dist/tokens/motion/overlay-motion.d.ts +19 -0
- package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
- package/dist/tokens/motion/overlay-motion.js +9 -0
- package/dist/tokens/motion/overlay-motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.js +5 -0
- package/dist/tokens/uiSize/icon-size.js.map +1 -0
- package/ds-canonical/fork/governance.lock +31 -19
- package/ds-canonical/fork/hooks/_log-fire.sh +46 -0
- package/ds-canonical/fork/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +2 -1
- package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/fork/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +28 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +2 -0
- package/ds-canonical/fork/hooks/check_story_invariants.sh +119 -0
- package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/fork/manifest.json +15 -0
- package/ds-canonical/fork/preamble.md +20 -18
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +1 -1
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +2 -2
- package/ds-canonical/hooks/README.md +1 -1
- package/ds-canonical/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +65 -7
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +28 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/hooks/check_datatable_invariants.sh +6 -3
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -1
- package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +1 -1
- package/ds-canonical/hooks/check_opacity_token_usage.sh +28 -0
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +3 -2
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +6 -1
- package/ds-canonical/hooks/check_ssot_header_declaration.sh +3 -1
- package/ds-canonical/hooks/check_story_determinism.sh +2 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +8 -7
- package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +1 -1
- package/ds-canonical/hooks/lib/_token_hygiene.sh +10 -1
- package/ds-canonical/hooks/stop_self_audit.sh +31 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +44 -0
- package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +43 -2
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +112 -0
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +9 -2
- package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +27 -2
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +39 -0
- package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
- package/ds-canonical/references/build-ui-canonicals.md +2 -1
- package/ds-canonical/references/failure-class-registry.json +145 -0
- package/ds-canonical/references/governance-audit-coverage.md +33 -0
- package/ds-canonical/references/item-anatomy-recipe.md +2 -2
- package/ds-canonical/references/scenario-definition.md +6 -9
- package/ds-canonical/references/ssot-consultation.md +2 -2
- package/ds-canonical/references/ssot-index.md +5 -4
- package/ds-canonical/references/story-baseline-registry.json +8 -2
- package/ds-canonical/references/structural-token-retention.md +6 -9
- package/ds-canonical/rules/meta-patterns.md +8 -8
- package/ds-canonical/rules/self-verify.md +4 -3
- package/ds-canonical/rules/story-rules.md +2 -2
- package/ds-canonical/rules/ui-development.md +2 -2
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/skills/codex-collab/SKILL.md +9 -7
- package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
- package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
- package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +2 -2
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +51 -58
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +29 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +29 -10
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +21 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +26 -25
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +13 -1
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +1 -1
- package/ds-canonical/skills/governance-health/SKILL.md +1 -1
- package/ds-canonical/skills/knowledge-prune/SKILL.md +3 -0
- package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +1 -0
- package/ds-canonical/skills/new-component/SKILL.md +5 -5
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
- package/ds-canonical/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +3 -3
- package/ds-canonical/skills/story-writing/SKILL.md +3 -3
- package/ds-canonical/skills/visual-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +29 -20
- package/llms-full.txt +22 -1
- package/llms.txt +5 -4
- package/package.json +7 -3
- package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
- package/src/components/Accordion/accordion.stories.tsx +16 -1
- package/src/components/Alert/alert.principles.stories.tsx +1 -1
- package/src/components/Alert/alert.spec.md +8 -10
- package/src/components/Alert/alert.stories.tsx +28 -28
- package/src/components/Alert/alert.tsx +1 -1
- package/src/components/AppShell/_demo-helpers.tsx +6 -3
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +28 -2
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
- package/src/components/AspectRatio/aspect-ratio.stories.tsx +7 -89
- package/src/components/Avatar/avatar.anatomy.stories.tsx +7 -3
- package/src/components/Avatar/avatar.principles.stories.tsx +19 -15
- package/src/components/Avatar/avatar.spec.md +2 -2
- package/src/components/Avatar/avatar.stories.tsx +8 -6
- package/src/components/Avatar/avatar.tsx +13 -5
- package/src/components/Badge/badge.principles.stories.tsx +1 -1
- package/src/components/Badge/badge.spec.md +2 -0
- package/src/components/Badge/badge.stories.tsx +4 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +6 -4
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +6 -5
- package/src/components/Breadcrumb/breadcrumb.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/Button/button.principles.stories.tsx +1 -1
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.tsx +18 -3
- package/src/components/Calendar/calendar.anatomy.stories.tsx +12 -10
- package/src/components/Calendar/calendar.principles.stories.tsx +14 -6
- package/src/components/Calendar/calendar.spec.md +5 -4
- package/src/components/Calendar/calendar.stories.tsx +7 -4
- package/src/components/Calendar/calendar.tsx +26 -7
- package/src/components/Carousel/carousel.anatomy.stories.tsx +5 -4
- package/src/components/Carousel/carousel.spec.md +2 -2
- package/src/components/Carousel/carousel.tsx +11 -3
- package/src/components/Chart/chart.anatomy.stories.tsx +7 -4
- package/src/components/Chart/chart.principles.stories.tsx +1 -1
- package/src/components/Chart/chart.stories.tsx +2 -8
- package/src/components/Chart/chart.tsx +3 -0
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +28 -49
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +13 -26
- package/src/components/Checkbox/checkbox.stories.tsx +11 -8
- package/src/components/Checkbox/checkbox.tsx +18 -13
- package/src/components/Chip/chip.anatomy.stories.tsx +44 -24
- package/src/components/Chip/chip.principles.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +5 -4
- package/src/components/Chip/chip.tsx +5 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -4
- package/src/components/CircularProgress/circular-progress.spec.md +3 -3
- package/src/components/CircularProgress/circular-progress.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +2 -1
- package/src/components/Coachmark/coachmark.spec.md +5 -5
- package/src/components/Coachmark/coachmark.stories.tsx +3 -1
- package/src/components/Coachmark/coachmark.tsx +10 -3
- package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
- package/src/components/Combobox/combobox.tsx +53 -19
- package/src/components/Command/command.anatomy.stories.tsx +2 -2
- package/src/components/Command/command.principles.stories.tsx +1 -1
- package/src/components/Command/command.stories.tsx +42 -10
- package/src/components/DataTable/cell-registry.tsx +86 -63
- package/src/components/DataTable/column-types.ts +10 -1
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +1 -1
- package/src/components/DataTable/data-table-filter-group.tsx +231 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +16 -430
- package/src/components/DataTable/data-table-filter-value-picker.tsx +237 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +15 -19
- package/src/components/DataTable/data-table.anatomy.stories.tsx +15 -13
- package/src/components/DataTable/data-table.principles.stories.tsx +9 -9
- package/src/components/DataTable/data-table.spec.md +33 -13
- package/src/components/DataTable/data-table.stories.tsx +16 -17
- package/src/components/DataTable/data-table.tsx +107 -61
- package/src/components/DataTable/filter-operators.spec.md +3 -3
- package/src/components/DataTable/index.ts +3 -0
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.principles.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DateGrid/date-grid.tsx +3 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +8 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +3 -3
- package/src/components/DatePicker/date-picker.spec.md +3 -3
- package/src/components/DatePicker/date-picker.stories.tsx +4 -5
- package/src/components/DatePicker/date-picker.tsx +99 -45
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +12 -10
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.spec.md +3 -3
- package/src/components/DescriptionList/description-list.stories.tsx +7 -6
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -0
- package/src/components/Dialog/dialog.spec.md +9 -0
- package/src/components/Dialog/dialog.stories.tsx +66 -5
- package/src/components/Dialog/dialog.tsx +60 -25
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +8 -6
- package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +18 -9
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Field/field-context.ts +69 -5
- package/src/components/Field/field-controls.spec.md +62 -8
- package/src/components/Field/field-edit-keys.ts +66 -0
- package/src/components/Field/field-types.ts +58 -9
- package/src/components/Field/field-wrapper.tsx +50 -42
- package/src/components/Field/field.anatomy.stories.tsx +9 -6
- package/src/components/Field/field.spec.md +1 -1
- package/src/components/Field/field.stories.tsx +1 -1
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +3 -3
- package/src/components/Field/index.ts +1 -0
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +6 -6
- package/src/components/FieldControlGroup/field-control-group.spec.md +9 -4
- package/src/components/FieldControlGroup/field-control-group.tsx +1 -1
- package/src/components/FileItem/file-item.anatomy.stories.tsx +1 -1
- package/src/components/FileItem/file-item.spec.md +5 -8
- package/src/components/FileItem/file-item.stories.tsx +36 -25
- package/src/components/FileItem/file-item.tsx +8 -6
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +6 -6
- package/src/components/FileUpload/file-upload.principles.stories.tsx +5 -5
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/FileUpload/file-upload.tsx +1 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -3
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +3 -3
- package/src/components/FileViewer/file-viewer.spec.md +30 -12
- package/src/components/FileViewer/file-viewer.stories.tsx +1 -2
- package/src/components/FileViewer/file-viewer.tsx +12 -4
- package/src/components/FileViewer/image-renderer.tsx +35 -7
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -2
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -3
- package/src/components/HoverCard/hover-card.spec.md +5 -5
- package/src/components/HoverCard/hover-card.stories.tsx +16 -16
- package/src/components/HoverCard/hover-card.tsx +5 -3
- package/src/components/InlineEdit/index.ts +5 -0
- package/src/components/InlineEdit/inline-edit.spec.md +149 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +246 -0
- package/src/components/InlineEdit/inline-edit.tsx +287 -0
- package/src/components/Input/input.anatomy.stories.tsx +8 -7
- package/src/components/Input/input.principles.stories.tsx +2 -12
- package/src/components/Input/input.spec.md +13 -18
- package/src/components/Input/input.tsx +69 -22
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.tsx +11 -10
- package/src/components/Menu/menu-item.anatomy.stories.tsx +3 -3
- package/src/components/Menu/menu-item.principles.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +22 -15
- package/src/components/Menu/menu-item.stories.tsx +39 -11
- package/src/components/Menu/menu-item.tsx +38 -4
- package/src/components/Notice/notice.principles.stories.tsx +1 -1
- package/src/components/Notice/notice.spec.md +16 -3
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +3 -3
- package/src/components/NumberInput/number-input.principles.stories.tsx +1 -1
- package/src/components/NumberInput/number-input.spec.md +4 -4
- package/src/components/NumberInput/number-input.tsx +19 -12
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +10 -8
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +4 -2
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +71 -63
- package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -8
- package/src/components/Pagination/pagination.spec.md +1 -1
- package/src/components/Pagination/pagination.stories.tsx +2 -6
- package/src/components/Pagination/pagination.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +4 -4
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +2 -2
- package/src/components/PeoplePicker/people-picker.spec.md +6 -6
- package/src/components/PeoplePicker/people-picker.tsx +56 -16
- package/src/components/PeoplePicker/person-display.tsx +32 -16
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.tsx +3 -1
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +12 -23
- package/src/components/ProfileCard/profile-card.spec.md +3 -3
- package/src/components/ProfileCard/profile-card.tsx +6 -6
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +3 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.stories.tsx +50 -42
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +10 -28
- package/src/components/RadioGroup/radio-group.stories.tsx +29 -32
- package/src/components/RadioGroup/radio-group.tsx +41 -15
- package/src/components/Rating/rating.spec.md +11 -3
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +7 -3
- package/src/components/ScrollArea/scroll-area.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.tsx +17 -4
- package/src/components/Select/select.anatomy.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +29 -17
- package/src/components/Select/select.stories.tsx +33 -0
- package/src/components/Select/select.tsx +190 -36
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +4 -4
- package/src/components/SelectMenu/select-menu.spec.md +15 -7
- package/src/components/SelectMenu/select-menu.tsx +16 -3
- package/src/components/SelectionControl/boolean-value.tsx +39 -0
- package/src/components/SelectionControl/index.ts +1 -0
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +3 -3
- package/src/components/SelectionControl/selection-item.stories.tsx +3 -3
- package/src/components/Separator/separator.spec.md +2 -2
- package/src/components/Separator/separator.stories.tsx +6 -27
- package/src/components/Sheet/sheet.anatomy.stories.tsx +1 -1
- package/src/components/Sheet/sheet.principles.stories.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -2
- package/src/components/Sheet/sheet.tsx +9 -5
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +16 -12
- package/src/components/Sidebar/sidebar.principles.stories.tsx +10 -8
- package/src/components/Sidebar/sidebar.spec.md +13 -15
- package/src/components/Sidebar/sidebar.stories.tsx +8 -7
- package/src/components/Sidebar/sidebar.tsx +37 -25
- package/src/components/Skeleton/skeleton.stories.tsx +4 -11
- package/src/components/Slider/slider.anatomy.stories.tsx +3 -3
- package/src/components/Slider/slider.principles.stories.tsx +2 -2
- package/src/components/Slider/slider.spec.md +5 -5
- package/src/components/Slider/slider.tsx +3 -1
- package/src/components/Steps/steps.anatomy.stories.tsx +4 -4
- package/src/components/Steps/steps.principles.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +1 -1
- package/src/components/Switch/switch.anatomy.stories.tsx +7 -7
- package/src/components/Switch/switch.principles.stories.tsx +4 -4
- package/src/components/Switch/switch.spec.md +8 -8
- package/src/components/Switch/switch.stories.tsx +9 -24
- package/src/components/Switch/switch.tsx +17 -13
- package/src/components/Tabs/tabs.anatomy.stories.tsx +10 -7
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tabs/tabs.tsx +21 -1
- package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
- package/src/components/Textarea/textarea.principles.stories.tsx +1 -1
- package/src/components/Textarea/textarea.spec.md +4 -4
- package/src/components/Textarea/textarea.tsx +72 -46
- package/src/components/TimePicker/time-columns.tsx +6 -2
- package/src/components/TimePicker/time-picker.principles.stories.tsx +1 -1
- package/src/components/TimePicker/time-picker.spec.md +3 -3
- package/src/components/TimePicker/time-picker.tsx +16 -10
- package/src/components/Toast/toast.stories.tsx +7 -4
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +4 -4
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/Tooltip/tooltip.spec.md +1 -1
- package/src/components/Tooltip/tooltip.stories.tsx +4 -39
- package/src/components/Tooltip/tooltip.tsx +8 -6
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -3
- package/src/components/TreeView/tree-view.spec.md +42 -11
- package/src/components/TreeView/tree-view.stories.tsx +1 -0
- package/src/components/TreeView/tree-view.tsx +322 -17
- package/src/index.ts +588 -60
- package/src/patterns/action-bar/action-bar.spec.md +9 -0
- package/src/patterns/action-bar/action-bar.stories.tsx +6 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +2 -0
- package/src/patterns/element-anatomy/item-anatomy.spec.md +14 -5
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -4
- package/src/patterns/element-anatomy/item-anatomy.tsx +7 -6
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -4
- package/src/patterns/overlay-surface/overlay-surface.spec.md +22 -1
- package/src/styles/tokens.css +3 -0
- package/src/tokens/README.md +1 -1
- package/src/tokens/color/color.spec.md +4 -4
- package/src/tokens/color/color.stories.tsx +3 -3
- package/src/tokens/density/density.stories.tsx +16 -3
- package/src/tokens/layoutSpace/layoutSpace.spec.md +3 -1
- package/src/tokens/motion/motion.css +33 -21
- package/src/tokens/motion/motion.spec.md +32 -15
- package/src/tokens/motion/motion.stories.tsx +58 -5
- package/src/tokens/motion/motion.ts +7 -6
- package/src/tokens/motion/overlay-motion.ts +26 -0
- package/src/tokens/orphan-tokens.spec.md +4 -4
- package/src/tokens/token-system.spec.md +1 -0
- package/src/tokens/uiSize/uiSize.css +1 -1
- package/src/tokens/uiSize/uiSize.spec.md +1 -1
- package/src/tokens/uiSize/uiSize.stories.tsx +4 -2
- package/src/tokens/utility-registry.json +160 -23
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.js","sources":["../../../src/components/Breadcrumb/breadcrumb.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.\n// code-quality-allow: file-size — Breadcrumb 含 BreadcrumbList(主)+ BreadcrumbItem + BreadcrumbEllipsis + items-collapse logic,split 會破壞 collapse/overflow Tooltip subtree\nimport * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { ChevronRight, MoreHorizontal, type LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { ItemInlineActionButton } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { Tooltip, TooltipTrigger, TooltipContent } from '@/design-system/components/Tooltip/tooltip'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\n\n// ── TruncatedLabel ────────────────────────────────────────────────────────────\n// 同 `data-table.tsx TruncateCell` + `tag.tsx isTruncated` SSOT pattern\n// (shared ResizeObserver + scrollWidth > clientWidth → wrap Tooltip)。\n// **TODO** future:Rule-of-3 達 → 抽 `patterns/element-anatomy/truncated-text.tsx` 共用\n// (本 component / DataTable TruncateCell / Tag inner 三處同 idiom,符合 M30 SSOT 抽取門檻)。\n\ntype RoCallback = (entry: ResizeObserverEntry) => void\nlet sharedRO: ResizeObserver | null = null\nconst sharedROCallbacks = new WeakMap<Element, RoCallback>()\nfunction getSharedRO(): ResizeObserver {\n if (sharedRO) return sharedRO\n sharedRO = new ResizeObserver((entries) => {\n entries.forEach((e) => {\n const cb = sharedROCallbacks.get(e.target)\n if (cb) cb(e)\n })\n })\n return sharedRO\n}\nfunction observeShared(el: Element, cb: RoCallback): () => void {\n const obs = getSharedRO()\n sharedROCallbacks.set(el, cb)\n obs.observe(el)\n return () => { sharedROCallbacks.delete(el); obs.unobserve(el) }\n}\n\nfunction TruncatedLabel({ children, fullText }: { children: React.ReactNode; fullText?: string }) {\n const ref = React.useRef<HTMLSpanElement>(null)\n const [isTruncated, setIsTruncated] = React.useState(false)\n React.useEffect(() => {\n const el = ref.current\n if (!el) return\n const check = () => setIsTruncated(el.scrollWidth > el.clientWidth)\n check()\n // 2026-05-11 fix:首幀 layout 未完成 / 字型 async load → scrollWidth=clientWidth 假陰性。\n // RAF + 短延遲再驗一次,捕獲字型 / 容器尺寸後變(對齊 TruncateCell / Tag SSOT pattern)。\n const raf = requestAnimationFrame(check)\n const t = setTimeout(check, 100)\n const cleanup = observeShared(el, check)\n return () => {\n cancelAnimationFrame(raf)\n clearTimeout(t)\n cleanup()\n }\n }, [])\n // Tooltip canonical:per `tooltip.principles.stories.tsx:190`「Tooltip 是資訊補救 — 文字被\n // truncate 時才顯示完整內容。沒被截斷就不該顯示 tooltip」\n //\n // 2026-05-11 fix(playwright tooltip-on-truncate 卡 hover 沒 tooltip):\n // 原本 isTruncated=false 直接 return 裸 span / true 才 wrap Tooltip → JSX 樹結構改變\n // → React 把 span unmount + remount(因為 wrapper component 變),ref 換到新 span,\n // useEffect [] 不重跑(同 component instance)→ 觀察的 DOM 跟實際 DOM 對不上。\n // Fix:**永遠 wrap Tooltip**(同 DOM 節點生命週期);`open` 由 isTruncated 控制 —\n // 沒被 truncate 就 force `open={false}`,有 truncate 就 `undefined`(uncontrolled,\n // hover 走 Radix default behavior)。對齊 canonical「沒被截斷就不該 tooltip」。\n return (\n <Tooltip open={isTruncated ? undefined : false}>\n <TooltipTrigger asChild>\n <span ref={ref} className=\"truncate min-w-0 block\">{children}</span>\n </TooltipTrigger>\n <TooltipContent>{fullText ?? children}</TooltipContent>\n </Tooltip>\n )\n}\n\n/**\n * Breadcrumb — 顯示當前頁面在階層中的位置\n *\n * 基於 shadcn/ui Breadcrumb 結構(純 HTML nav + ol + li + a/span),\n * 橋接設計系統 token。\n *\n * ── 結構 ──\n * <Breadcrumb>\n * <BreadcrumbList size=\"md\">\n * <BreadcrumbItem>\n * <BreadcrumbLink href=\"/projects\">專案</BreadcrumbLink>\n * </BreadcrumbItem>\n * <BreadcrumbSeparator />\n * <BreadcrumbItem>\n * <BreadcrumbPage>目前頁面</BreadcrumbPage>\n * </BreadcrumbItem>\n * </BreadcrumbList>\n * </Breadcrumb>\n *\n * ── Size(跟 page title 配對) ──\n * sm text-body(14) → 建議配 text-h4(20) title —— Dialog / panel / drawer\n * md text-body(14) → 建議配 text-h3(24) title —— 一般頁面 header (預設)\n * lg text-body-lg(16) → 建議配 text-h2(32) title —— Detail page hero / landing\n *\n * ── 視覺 ──\n * Link (預設): text-fg-secondary\n * Link hover: text-primary-hover (canonical「互動高亮」, 跟 Tabs / Chip 用法一致)\n * Page (當前): text-foreground(不加粗 — 加粗會讓 breadcrumb 最右端視覺過重,見 spec)\n * Separator: ChevronRight (size 跟 list 一致), text-fg-muted\n *\n * ── 詳見 breadcrumb.spec.md ──\n */\n\n// ── Size context ─────────────────────────────────────────────────────────────\n\ntype BreadcrumbSize = 'sm' | 'md' | 'lg'\n\ninterface BreadcrumbContextValue {\n size: BreadcrumbSize\n}\n\nconst BreadcrumbContext = React.createContext<BreadcrumbContextValue>({ size: 'md' })\n\nconst BREADCRUMB_TEXT_CLASS: Record<BreadcrumbSize, string> = {\n sm: 'text-body',\n md: 'text-body',\n lg: 'text-body-lg',\n}\n\n// Separator / ellipsis icon 尺寸 — 對齊 uiSize.spec.md「Icon Size Tier」(field-height-sm/md→16,lg→20)\n// 2026-05-18 改 per user 拍板「A 先改 16/16/20」+「做完」approval:\n// 撤回 text-flow 例外設計,Breadcrumb chevron 跟其他 chrome icon 同 tier。\n// World-class 對齊:Atlassian Breadcrumb chevron 16 default / Material 3 / Ant Design 同。\nconst BREADCRUMB_ICON_SIZE: Record<BreadcrumbSize, number> = {\n sm: 16,\n md: 16,\n lg: 20,\n}\n\n// ── Breadcrumb (nav root) ────────────────────────────────────────────────────\n\nconst Breadcrumb = React.forwardRef<\n HTMLElement,\n React.ComponentPropsWithoutRef<'nav'>\n>(({ ...props }, ref) => (\n <nav ref={ref} aria-label=\"Breadcrumb\" {...props} />\n))\nBreadcrumb.displayName = 'Breadcrumb'\n\n// ── BreadcrumbList (ol) ──────────────────────────────────────────────────────\n\n/**\n * Phase B(2026-05-10):declarative items mode 啟用 auto-collapse + auto-separator + auto-page-end。\n * 對齊 Material UI source `Breadcrumbs.js renderItemsBeforeAndAfter` mechanism(`maxItems`\n * default 8;本 DS 採 user-tuned 4 — 更積極 collapse 適合 single-line 緊湊 layout)。\n */\n// code-quality-allow: dead-export — public consumer-facing item spec type;對齊 BreadcrumbProps API contract,允許 consumer 構造 items array 外部\nexport interface BreadcrumbItemSpec {\n label: React.ReactNode\n href?: string\n asChild?: boolean\n /**\n * 起始 icon(per `ui-development.md`「icon prop 命名 4 條」:slot 只接 icon → `startIcon`)。\n * 業界慣例:Breadcrumb 首項用 Home icon 強化視覺錨點(Material / Atlassian)。\n * 內部消費 `BREADCRUMB_ICON_SIZE[size]` SSOT(sm/md=16, lg=20,對齊 uiSize.spec.md Icon Size Tier)。\n * Consumer **不傳** size,DS 統一管。\n */\n startIcon?: LucideIcon\n}\n\ninterface BreadcrumbListProps extends Omit<React.ComponentPropsWithoutRef<'ol'>, 'children'> {\n /**\n * 字體尺寸 — 依據與之配對的 page title 選擇:\n * sm → 配 text-h4(20) title (Dialog / panel / drawer)\n * md → 配 text-h3(24) title (一般頁面 header,預設)\n * lg → 配 text-h2(32) title (Detail page hero / landing)\n */\n size?: BreadcrumbSize\n /**\n * Declarative items mode(opt-in)。當 provided 時 `children` 被忽略,List 內部自動:\n * - 插 separator\n * - 末位 spec(無 `href`)自動 BreadcrumbPage(per Title-breadcrumb-end SSOT)\n * - 超 `maxItems` auto-collapse 中段成 BreadcrumbEllipsis + DropdownMenu(對齊 Material UI\n * source `renderItemsBeforeAndAfter`)\n */\n items?: BreadcrumbItemSpec[]\n /**\n * Auto-collapse 閾值。Default 4(user-tuned;Material UI source 預設 8)。`items.length > maxItems`\n * 才 collapse。\n */\n maxItems?: number\n /** Collapse 後保留首 N 個(default 1)。對齊 Material UI source default。 */\n itemsBeforeCollapse?: number\n /** Collapse 後保留末 N 個(default 1)。對齊 Material UI source default。 */\n itemsAfterCollapse?: number\n children?: React.ReactNode\n}\n\n// code-quality-allow: long-function — items props × children narrowing × collapse-with-overflow × tooltip 4 軸組合在 BreadcrumbList,拆 sub-fn 會跨 fn 傳 itemsBeforeCollapse/After collapsed-tooltip refs\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, BreadcrumbListProps>(\n ({ className, size = 'md', items, maxItems = 4, itemsBeforeCollapse = 1, itemsAfterCollapse = 1, children, ...props }, ref) => {\n // Memoize provider value(2026-04-22 D3 perf audit):單 field wrapper memoize\n const ctxValue = React.useMemo(() => ({ size }), [size])\n\n // Declarative mode(items prop provided):自動 render + auto-collapse\n const declarativeContent = React.useMemo(() => {\n if (!items) return null\n // 2026-07-04 修:非字串 label 的 key 原用 Math.random() → 每次 render 新 key 造成\n // BreadcrumbItem unmount/remount(重置 TruncatedLabel ResizeObserver 與 state)→ 改 stable idx key\n const renderItem = (spec: BreadcrumbItemSpec, role: 'root' | 'middle' | 'current', idx: number) => (\n <BreadcrumbItem key={`${role}-${typeof spec.label === 'string' ? spec.label : idx}`} role={role}>\n {role === 'current'\n ? <BreadcrumbPage startIcon={spec.startIcon}>\n <TruncatedLabel fullText={typeof spec.label === 'string' ? spec.label : undefined}>{spec.label}</TruncatedLabel>\n </BreadcrumbPage>\n : <BreadcrumbLink href={spec.href} asChild={spec.asChild} startIcon={spec.startIcon}>\n <TruncatedLabel fullText={typeof spec.label === 'string' ? spec.label : undefined}>{spec.label}</TruncatedLabel>\n </BreadcrumbLink>\n }\n </BreadcrumbItem>\n )\n\n const shouldCollapse = items.length > maxItems\n const beforeN = Math.max(0, itemsBeforeCollapse)\n const afterN = Math.max(1, itemsAfterCollapse) // 末位永遠 ≥ 1(current page)\n\n type VisibleItem = { spec: BreadcrumbItemSpec; role: 'root' | 'middle' | 'current' }\n let visible: Array<VisibleItem | { ellipsisOf: BreadcrumbItemSpec[] }>\n if (!shouldCollapse) {\n visible = items.map((spec, i) => ({\n spec,\n role: i === 0 ? 'root' : (i === items.length - 1 ? 'current' : 'middle') as 'root' | 'middle' | 'current',\n }))\n } else {\n const before = items.slice(0, beforeN).map((spec, i) => ({\n spec,\n role: (i === 0 ? 'root' : 'middle') as 'root' | 'middle' | 'current',\n }))\n const collapsed = items.slice(beforeN, items.length - afterN)\n const after = items.slice(items.length - afterN).map((spec, i, arr) => ({\n spec,\n role: (i === arr.length - 1 ? 'current' : 'middle') as 'root' | 'middle' | 'current',\n }))\n visible = [...before, { ellipsisOf: collapsed }, ...after]\n }\n\n // Interleave with separators\n const rendered: React.ReactNode[] = []\n visible.forEach((entry, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-${i}`} />)\n if ('ellipsisOf' in entry) {\n rendered.push(\n <BreadcrumbItem key=\"ellipsis\" role=\"ellipsis\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <BreadcrumbEllipsis />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {entry.ellipsisOf.map((s, j) => (\n <DropdownMenuItem key={j} asChild={!!s.href}>\n {s.href ? <a href={s.href}>{s.label}</a> : <span>{s.label}</span>}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n )\n } else {\n rendered.push(renderItem(entry.spec, entry.role, i))\n }\n })\n return rendered\n }, [items, maxItems, itemsBeforeCollapse, itemsAfterCollapse])\n\n // 2026-05-12 fix(user 抓 image 2 Deep story 違反 single-line + max-levels canonical):\n // Compositional path 也走 auto-collapse + flex-shrink hierarchy。Walk children, 找\n // BreadcrumbItem 並按 index 分派 role (first=root / last=current / middle=middle)。\n // > maxItems 自動 collapse 中段成 ellipsis(對齊 declarative path canonical SSOT)。\n const compositionalContent = React.useMemo(() => {\n if (items) return null\n const childArr = React.Children.toArray(children)\n // 抓 BreadcrumbItem children(skip BreadcrumbSeparator — auto re-interleave)\n // 2026-05-12 Round 4.5 fix(codex M31 Layer C 抓):type-identity primary path + displayName fallback。\n // 純 `displayName` check 在 HOC / React.memo / consumer alias 場景脆弱(production build / wrap\n // 可能改寫 displayName)。`c.type === BreadcrumbItem` 是 React fiber reference-identity 最穩\n // primary(對齊 Radix children-walk pattern);displayName fallback 給 HOC 場景。\n const itemChildren = childArr.filter((c): c is React.ReactElement<BreadcrumbItemProps> =>\n React.isValidElement(c) && (c.type === BreadcrumbItem ||\n (c.type as React.ComponentType)?.displayName === 'BreadcrumbItem')\n )\n // 無 item 或全是 separator → pass-through(consumer raw children,e.g. spinners)\n if (itemChildren.length === 0) return children\n // Assign role by position; clone with role prop\n const total = itemChildren.length\n const cloneWithRole = (item: React.ReactElement<BreadcrumbItemProps>, idx: number, role: 'root' | 'middle' | 'current') =>\n React.cloneElement(item, { role: item.props.role ?? role, key: `bc-${role}-${idx}` })\n const shouldCollapse = total > maxItems\n const beforeN = Math.max(0, itemsBeforeCollapse)\n const afterN = Math.max(1, itemsAfterCollapse)\n const rendered: React.ReactNode[] = []\n if (!shouldCollapse) {\n itemChildren.forEach((item, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-${i}`} />)\n const role: 'root' | 'middle' | 'current' = i === 0 ? 'root' : (i === total - 1 ? 'current' : 'middle')\n rendered.push(cloneWithRole(item, i, role))\n })\n } else {\n // before(first N) + ellipsis + after(last M)\n const beforeItems = itemChildren.slice(0, beforeN)\n const collapsedItems = itemChildren.slice(beforeN, total - afterN)\n const afterItems = itemChildren.slice(total - afterN)\n beforeItems.forEach((item, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-bef-${i}`} />)\n const role: 'root' | 'middle' = i === 0 ? 'root' : 'middle'\n rendered.push(cloneWithRole(item, i, role))\n })\n if (rendered.length > 0) rendered.push(<BreadcrumbSeparator key=\"sep-ellipsis-before\" />)\n rendered.push(\n <BreadcrumbItem key=\"bc-ellipsis\" role=\"ellipsis\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <BreadcrumbEllipsis />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {collapsedItems.map((item, j) => {\n // 2026-05-12 Round 4.5 fix(codex M31 Layer C 抓):consumer BreadcrumbItem children 常包\n // `<BreadcrumbLink href>` = anchor button-like。直接放進 `<DropdownMenuItem>` 會 nested\n // interactive(menuitem within button violates HTML / a11y)。Fix:extract href + label,\n // 用 `asChild` 把 anchor 接到 menuitem 對齊 declarative path line 245 canonical pattern。\n const innerChildren = (item.props as { children?: React.ReactNode }).children\n let href: string | undefined\n let label: React.ReactNode = innerChildren\n React.Children.forEach(innerChildren, (c) => {\n if (React.isValidElement<{ href?: string; children?: React.ReactNode }>(c)) {\n if (c.props.href) href = c.props.href\n if (c.props.children != null) label = c.props.children\n }\n })\n return href\n ? <DropdownMenuItem key={`collapsed-${j}`} asChild><a href={href}>{label}</a></DropdownMenuItem>\n : <DropdownMenuItem key={`collapsed-${j}`}>{label}</DropdownMenuItem>\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n )\n rendered.push(<BreadcrumbSeparator key=\"sep-ellipsis-after\" />)\n afterItems.forEach((item, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-aft-${i}`} />)\n const role: 'middle' | 'current' = i === afterItems.length - 1 ? 'current' : 'middle'\n rendered.push(cloneWithRole(item, i, role))\n })\n }\n return rendered\n }, [items, children, maxItems, itemsBeforeCollapse, itemsAfterCollapse])\n\n return (\n <BreadcrumbContext.Provider value={ctxValue}>\n <ol\n ref={ref}\n // gap-1 (4px) — separator 與兩邊 items 間距;緊湊節奏,符合 breadcrumb 密集流動感。\n // 2026-05-10 Phase A single-line canonical(per user + Material UI source verified):\n // `flex-nowrap` 不 wrap。長路徑走中段折疊。\n // 2026-05-12 fix:compositional 也走 auto-collapse + role-assignment(`compositionalContent`)\n // → declarative / compositional 兩 path 都符合 single-line + max-levels + width 分配 canonical SSOT。\n className={cn(\n 'flex flex-nowrap items-center gap-1 text-fg-secondary leading-compact min-w-0',\n BREADCRUMB_TEXT_CLASS[size],\n className\n )}\n {...props}\n >\n {items ? declarativeContent : compositionalContent}\n </ol>\n </BreadcrumbContext.Provider>\n )\n },\n)\nBreadcrumbList.displayName = 'BreadcrumbList'\n\n// ── BreadcrumbItem (li) ──────────────────────────────────────────────────────\n\n/**\n * Phase B(2026-05-10):`role` prop emit `data-bc-role` attr → CSS flex-shrink hierarchy。\n * Per BreadcrumbItem 在 row 中的角色決定 shrink 優先級:\n * - `root`(首位)→ shrink:3(縮最積極;root context 可弱化)\n * - `middle`(中段)→ shrink:2\n * - `current`(末位 / page)→ shrink:1(最後縮;a11y current page anchor)\n * - `ellipsis`(BreadcrumbEllipsis 包裝)→ shrink:0(永遠完整 ⋯)\n *\n * 設計回應 user 兩 challenges:\n * (a) Root 也 truncate(shrink:3,不是 shrink-0)\n * (b) 不用 fixed max-width — flex-shrink hierarchy 容器寬時自然展開不浪費空間,\n * 窄時按優先級縮 + TruncatedLabel 內部 CSS truncate + tooltip。\n */\ninterface BreadcrumbItemProps extends React.ComponentPropsWithoutRef<'li'> {\n role?: 'root' | 'middle' | 'current' | 'ellipsis'\n}\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, BreadcrumbItemProps>(\n ({ className, role, style, ...props }, ref) => {\n // 2026-05-20 fix v3(user 抓「專案 後方多 4px 間距 / 我的新專案 沒有」chevron 不對稱):\n // v2 `minWidth: '2rem'`(32px)在寬容器強制 li ≥ 32px → 短 label「專案」(natural ~28px)\n // 被撐 4px,長 label「我的新專案」(natural ~70px)hug content → chevron 兩側不對稱。\n //\n // v3 解法:minWidth `2rem` → `1.5rem`(24px)\n // 數學:中文「X…」最小寬度 = 1 char(~14-16px)+ ellipsis(~6-8px)≈ 22-24px → 24px 剛 cover\n // 結果:\n // - 寬容器:所有自然 label ≥ 24px → li hug content,chevron 緊貼,對稱(本 fix 主目的)\n // - 窄容器 truncate:shrink 不過 24px → 「X…」仍可見 ellipsis 保險\n // - 短英文「OK / ID」(natural ~20px)→ 多 ~4px(原 12px → 縮到 4px,顯著改善)\n // 對齊 user verbatim「minWidth 再調小一點」+ ellipsis 數學最小值。\n const shrinkStyle: React.CSSProperties = role === 'root' ? { flexShrink: 3, minWidth: '1.5rem' }\n : role === 'middle' ? { flexShrink: 2, minWidth: '1.5rem' }\n : role === 'current' ? { flexShrink: 1, minWidth: '1.5rem' }\n : role === 'ellipsis' ? { flexShrink: 0 }\n : {}\n return (\n <li\n ref={ref}\n data-bc-role={role}\n className={cn('inline-flex items-center min-w-0', className)}\n style={{ ...shrinkStyle, ...style }}\n {...props}\n />\n )\n }\n)\nBreadcrumbItem.displayName = 'BreadcrumbItem'\n\n// ── BreadcrumbLink (a) ───────────────────────────────────────────────────────\n\ninterface BreadcrumbLinkProps extends React.ComponentPropsWithoutRef<'a'> {\n /** 將樣式套用至子元件(e.g. React Router Link) */\n asChild?: boolean\n /**\n * 起始 icon(per `ui-development.md`「icon prop 命名 4 條」:slot 只接 icon → `startIcon`)。\n * 內部消費 `BREADCRUMB_ICON_SIZE[size]` SSOT,DS 統一尺寸不允許 consumer override。\n * 對齊 uiSize.spec.md Icon Size Tier(2026-05-18 撤回 14 例外,統一 16/16/20)。\n */\n startIcon?: LucideIcon\n}\n\nconst BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(\n ({ asChild, className, children, startIcon: StartIcon, ...props }, ref) => {\n const { size } = React.useContext(BreadcrumbContext)\n // 2026-05-12 fix(user 抓 image 2 Deep story 麵包屑沒符合 single-line + truncate canonical):\n // 純文字 children → auto-wrap TruncatedLabel(canonical「single-line + ellipsis + tooltip\n // on truncate」per spec.md / Polaris breadcrumb)。Non-string children(consumer 自訂 icon+text\n // 結構)→ pass-through 不 force-wrap(consumer own truncate)。\n const wrappedChildren = typeof children === 'string'\n ? <TruncatedLabel fullText={children}>{children}</TruncatedLabel>\n : children\n const sharedClassName = cn(\n 'inline-flex items-center gap-2',\n 'min-w-0 max-w-full',\n 'text-fg-secondary',\n 'hover:text-primary-hover',\n 'transition-colors duration-150',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'rounded-md',\n className\n )\n // 2026-05-25 fix(user 抓 Breadcrumb asChild story React.Children.only runtime fail):\n // Radix Slot 規範 children 必為單 element;原 unified Comp render 在 asChild path 內\n // 仍輸出「{StartIcon && ...} + {wrappedChildren}」雙 JSX expression → Slot 收到 array\n // → React.Children.only(array) throws「expected to receive a single React element child」。\n // 分支 render 解 — asChild path 只傳 consumer-supplied child(icon 由 consumer 自管,\n // 對齊 Radix Slot canonical「single child contract」);非 asChild path 維持原 native\n // <a> + DS-controlled icon + wrapped label。\n if (asChild) {\n return (\n <Slot ref={ref} className={sharedClassName} {...props}>\n {wrappedChildren}\n </Slot>\n )\n }\n return (\n <a ref={ref} className={sharedClassName} {...props}>\n {StartIcon && <StartIcon size={BREADCRUMB_ICON_SIZE[size]} aria-hidden className=\"shrink-0\" />}\n {wrappedChildren}\n </a>\n )\n }\n)\nBreadcrumbLink.displayName = 'BreadcrumbLink'\n\n// ── BreadcrumbPage (current, non-clickable) ──────────────────────────────────\n\ninterface BreadcrumbPageProps extends React.ComponentPropsWithoutRef<'span'> {\n /** 起始 icon。內部消費 `BREADCRUMB_ICON_SIZE[size]` SSOT。對齊 BreadcrumbLink. */\n startIcon?: LucideIcon\n}\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, BreadcrumbPageProps>(\n ({ className, children, startIcon: StartIcon, ...props }, ref) => {\n const { size } = React.useContext(BreadcrumbContext)\n // 2026-05-12 fix(同 BreadcrumbLink):純文字 children → auto-wrap TruncatedLabel。\n const wrappedChildren = typeof children === 'string'\n ? <TruncatedLabel fullText={children}>{children}</TruncatedLabel>\n : children\n return (\n <span\n ref={ref}\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('inline-flex items-center gap-2 min-w-0 max-w-full text-foreground', className)}\n {...props}\n >\n {StartIcon && <StartIcon size={BREADCRUMB_ICON_SIZE[size]} aria-hidden className=\"shrink-0\" />}\n {wrappedChildren}\n </span>\n )\n }\n)\nBreadcrumbPage.displayName = 'BreadcrumbPage'\n\n// ── BreadcrumbSeparator ──────────────────────────────────────────────────────\n\ninterface BreadcrumbSeparatorProps extends React.ComponentPropsWithoutRef<'li'> {\n children?: React.ReactNode\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst BreadcrumbSeparator = React.forwardRef<HTMLLIElement, BreadcrumbSeparatorProps>(\n ({ children, className, ...props }, ref) => {\n const { size } = React.useContext(BreadcrumbContext)\n return (\n <li\n ref={ref}\n role=\"presentation\"\n aria-hidden=\"true\"\n // Phase B(2026-05-10):separator 永遠 shrink-0(必完整顯示,否則 path 視覺斷裂)\n className={cn('inline-flex items-center text-fg-muted shrink-0', className)}\n {...props}\n >\n {children ?? <ChevronRight size={BREADCRUMB_ICON_SIZE[size]} aria-hidden />}\n </li>\n )\n }\n)\nBreadcrumbSeparator.displayName = 'BreadcrumbSeparator'\n\n// ── BreadcrumbEllipsis ───────────────────────────────────────────────────────\n\n/**\n * BreadcrumbEllipsis — 折疊路徑的 \"⋯\" 按鈕\n *\n * 2026-05-10 重寫:消費 `ItemInlineActionButton`(primitive SSOT)取代自刻 `<button>`。\n * Per inline-action.spec.md L106-131 predicate Q1+Q2+Q3 全指向 Inline Action:\n * - Q1 點了要做事嗎?是(展開折疊路徑 dropdown)\n * - Q2 位置?BreadcrumbList row inline flow(host 內)\n * - Q3 row 多大?14-16px text row(compact tier)→ Inline Action\n * + 對齊 M1「視覺決策前必消費 SSOT」+ Mindset #2「優先消費既有」。\n *\n * 配合 DropdownMenuTrigger asChild 使用:\n *\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <BreadcrumbEllipsis />\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem asChild><a href=\"/org\">組織</a></DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n *\n * `overlayTrigger=true` 視覺鎖:DropdownMenu open 期間 button 維持 hover bg(對齊\n * shadcn / Radix Themes / Material 的 overlay trigger canonical,inline-action.spec.md\n * 「Overlay trigger canonical」段)。\n */\ntype BreadcrumbEllipsisProps = Omit<React.ComponentPropsWithoutRef<typeof ItemInlineActionButton>, 'icon' | 'size'>\n\nconst BreadcrumbEllipsis = React.forwardRef<HTMLButtonElement, BreadcrumbEllipsisProps>(\n ({ 'aria-label': ariaLabel = '顯示折疊路徑' /* i18n-allow: DS default; consumer override via aria-label prop */, ...props }, ref) => {\n return (\n <ItemInlineActionButton\n ref={ref}\n icon={MoreHorizontal}\n size=\"md\" // Breadcrumb 不在 RowSizeProvider 樹內,固定 md(16px icon + 18px hover bg,對齊 inline-action.spec.md 尺寸表)\n aria-label={ariaLabel}\n overlayTrigger\n {...props}\n />\n )\n }\n)\nBreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis'\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 breadcrumbMeta = {\n component: 'Breadcrumb',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n // 值源 = code SSOT:BREADCRUMB_SIZE_TEXT(sm/md=text-body, lg=text-body-lg)+ BREADCRUMB_ICON_SIZE(sm/md=16, lg=20)。\n // Breadcrumb 是文字列無固定高度 token,故不列 height(對照 Tag meta 有自身 h-* 才列)。\n sm: { iconSize: 16, typography: 'body' },\n md: { iconSize: 16, typography: 'body' },\n lg: { iconSize: 20, typography: 'body-lg' },\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-muted', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n}\nexport type { BreadcrumbSize, BreadcrumbListProps, BreadcrumbEllipsisProps }\n// BreadcrumbItemSpec 已在上方 `export interface BreadcrumbItemSpec` 直接 export\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","sources":["../../../src/components/Breadcrumb/breadcrumb.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.\n// code-quality-allow: file-size — Breadcrumb 含 BreadcrumbList(主)+ BreadcrumbItem + BreadcrumbEllipsis + items-collapse logic,split 會破壞 collapse/overflow Tooltip subtree\nimport * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { ChevronRight, MoreHorizontal, type LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { ItemInlineActionButton } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { Tooltip, TooltipTrigger, TooltipContent } from '@/design-system/components/Tooltip/tooltip'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\n\n// ── TruncatedLabel ────────────────────────────────────────────────────────────\n// 同 `data-table.tsx TruncateCell` + `tag.tsx isTruncated` SSOT pattern\n// (shared ResizeObserver + scrollWidth > clientWidth → wrap Tooltip)。\n// **TODO** future:Rule-of-3 達 → 抽 `patterns/element-anatomy/truncated-text.tsx` 共用\n// (本 component / DataTable TruncateCell / Tag inner 三處同 idiom,符合 M30 SSOT 抽取門檻)。\n\ntype RoCallback = (entry: ResizeObserverEntry) => void\nlet sharedRO: ResizeObserver | null = null\nconst sharedROCallbacks = new WeakMap<Element, RoCallback>()\nfunction getSharedRO(): ResizeObserver {\n if (sharedRO) return sharedRO\n sharedRO = new ResizeObserver((entries) => {\n entries.forEach((e) => {\n const cb = sharedROCallbacks.get(e.target)\n if (cb) cb(e)\n })\n })\n return sharedRO\n}\nfunction observeShared(el: Element, cb: RoCallback): () => void {\n const obs = getSharedRO()\n sharedROCallbacks.set(el, cb)\n obs.observe(el)\n return () => { sharedROCallbacks.delete(el); obs.unobserve(el) }\n}\n\nfunction TruncatedLabel({ children, fullText }: { children: React.ReactNode; fullText?: string }) {\n const ref = React.useRef<HTMLSpanElement>(null)\n const [isTruncated, setIsTruncated] = React.useState(false)\n React.useEffect(() => {\n const el = ref.current\n if (!el) return\n const check = () => setIsTruncated(el.scrollWidth > el.clientWidth)\n check()\n // 2026-05-11 fix:首幀 layout 未完成 / 字型 async load → scrollWidth=clientWidth 假陰性。\n // RAF + 短延遲再驗一次,捕獲字型 / 容器尺寸後變(對齊 TruncateCell / Tag SSOT pattern)。\n const raf = requestAnimationFrame(check)\n const t = setTimeout(check, 100)\n const cleanup = observeShared(el, check)\n return () => {\n cancelAnimationFrame(raf)\n clearTimeout(t)\n cleanup()\n }\n }, [])\n // Tooltip canonical:per `tooltip.principles.stories.tsx:190`「Tooltip 是資訊補救 — 文字被\n // truncate 時才顯示完整內容。沒被截斷就不該顯示 tooltip」\n //\n // 2026-05-11 fix(playwright tooltip-on-truncate 卡 hover 沒 tooltip):\n // 原本 isTruncated=false 直接 return 裸 span / true 才 wrap Tooltip → JSX 樹結構改變\n // → React 把 span unmount + remount(因為 wrapper component 變),ref 換到新 span,\n // useEffect [] 不重跑(同 component instance)→ 觀察的 DOM 跟實際 DOM 對不上。\n // Fix:**永遠 wrap Tooltip**(同 DOM 節點生命週期);`open` 由 isTruncated 控制 —\n // 沒被 truncate 就 force `open={false}`,有 truncate 就 `undefined`(uncontrolled,\n // hover 走 Radix default behavior)。對齊 canonical「沒被截斷就不該 tooltip」。\n return (\n <Tooltip open={isTruncated ? undefined : false}>\n <TooltipTrigger asChild>\n <span ref={ref} className=\"truncate min-w-0 block\">{children}</span>\n </TooltipTrigger>\n <TooltipContent>{fullText ?? children}</TooltipContent>\n </Tooltip>\n )\n}\n\n/**\n * Breadcrumb — 顯示當前頁面在階層中的位置\n *\n * 基於 shadcn/ui Breadcrumb 結構(純 HTML nav + ol + li + a/span),\n * 橋接設計系統 token。\n *\n * ── 結構 ──\n * <Breadcrumb>\n * <BreadcrumbList size=\"md\">\n * <BreadcrumbItem>\n * <BreadcrumbLink href=\"/projects\">專案</BreadcrumbLink>\n * </BreadcrumbItem>\n * <BreadcrumbSeparator />\n * <BreadcrumbItem>\n * <BreadcrumbPage>目前頁面</BreadcrumbPage>\n * </BreadcrumbItem>\n * </BreadcrumbList>\n * </Breadcrumb>\n *\n * ── Size(跟 page title 配對) ──\n * sm text-body(14) → 建議配 text-h4(20) title —— Dialog / panel / drawer\n * md text-body(14) → 建議配 text-h3(24) title —— 一般頁面 header (預設)\n * lg text-body-lg(16) → 建議配 text-h2(32) title —— Detail page hero / landing\n *\n * ── 視覺 ──\n * Link (預設): text-fg-secondary\n * Link hover: text-primary-hover (canonical「互動高亮」, 跟 Tabs / Chip 用法一致)\n * Page (當前): text-foreground(不加粗 — 加粗會讓 breadcrumb 最右端視覺過重,見 spec)\n * Separator: ChevronRight (size 跟 list 一致), text-fg-muted\n *\n * ── 詳見 breadcrumb.spec.md ──\n */\n\n// ── Size context ─────────────────────────────────────────────────────────────\n\ntype BreadcrumbSize = 'sm' | 'md' | 'lg'\n\ninterface BreadcrumbContextValue {\n size: BreadcrumbSize\n}\n\nconst BreadcrumbContext = React.createContext<BreadcrumbContextValue>({ size: 'md' })\n\nconst BREADCRUMB_TEXT_CLASS: Record<BreadcrumbSize, string> = {\n sm: 'text-body',\n md: 'text-body',\n lg: 'text-body-lg',\n}\n\n// Separator chevron + BreadcrumbLink/Item start icon 尺寸 — 對齊 uiSize.spec.md「Icon Size Tier」(field-height-sm/md→16,lg→20)。\n// 注意:BreadcrumbEllipsis 不消費此常數,固定 md(16px icon,見 BreadcrumbEllipsis 定義),size='lg' 時不隨 chevron 放大到 20\n// 2026-05-18 改 per user 拍板「A 先改 16/16/20」+「做完」approval:\n// 撤回 text-flow 例外設計,Breadcrumb chevron 跟其他 chrome icon 同 tier。\n// World-class 對齊:Atlassian Breadcrumb chevron 16 default / Material 3 / Ant Design 同。\nconst BREADCRUMB_ICON_SIZE: Record<BreadcrumbSize, number> = {\n sm: 16,\n md: 16,\n lg: 20,\n}\n\n// ── Breadcrumb (nav root) ────────────────────────────────────────────────────\n\nconst Breadcrumb = React.forwardRef<\n HTMLElement,\n React.ComponentPropsWithoutRef<'nav'>\n>(({ ...props }, ref) => (\n <nav ref={ref} aria-label=\"Breadcrumb\" {...props} />\n))\nBreadcrumb.displayName = 'Breadcrumb'\n\n// ── BreadcrumbList (ol) ──────────────────────────────────────────────────────\n\n/**\n * Phase B(2026-05-10):declarative items mode 啟用 auto-collapse + auto-separator + auto-page-end。\n * 對齊 Material UI source `Breadcrumbs.js renderItemsBeforeAndAfter` mechanism(`maxItems`\n * default 8;本 DS 採 user-tuned 4 — 更積極 collapse 適合 single-line 緊湊 layout)。\n */\n// code-quality-allow: dead-export — public consumer-facing item spec type;對齊 BreadcrumbProps API contract,允許 consumer 構造 items array 外部\nexport interface BreadcrumbItemSpec {\n label: React.ReactNode\n href?: string\n asChild?: boolean\n /**\n * 起始 icon(per `ui-development.md`「icon prop 命名 4 條」:slot 只接 icon → `startIcon`)。\n * 業界慣例:Breadcrumb 首項用 Home icon 強化視覺錨點(Material / Atlassian)。\n * 內部消費 `BREADCRUMB_ICON_SIZE[size]` SSOT(sm/md=16, lg=20,對齊 uiSize.spec.md Icon Size Tier)。\n * Consumer **不傳** size,DS 統一管。\n */\n startIcon?: LucideIcon\n}\n\ninterface BreadcrumbListProps extends Omit<React.ComponentPropsWithoutRef<'ol'>, 'children'> {\n /**\n * 字體尺寸 — 依據與之配對的 page title 選擇:\n * sm → 配 text-h4(20) title (Dialog / panel / drawer)\n * md → 配 text-h3(24) title (一般頁面 header,預設)\n * lg → 配 text-h2(32) title (Detail page hero / landing)\n */\n size?: BreadcrumbSize\n /**\n * Declarative items mode(opt-in)。當 provided 時 `children` 被忽略,List 內部自動:\n * - 插 separator\n * - 末位 spec(無 `href`)自動 BreadcrumbPage(per Title-breadcrumb-end SSOT)\n * - 超 `maxItems` auto-collapse 中段成 BreadcrumbEllipsis + DropdownMenu(對齊 Material UI\n * source `renderItemsBeforeAndAfter`)\n */\n items?: BreadcrumbItemSpec[]\n /**\n * Auto-collapse 閾值。Default 4(user-tuned;Material UI source 預設 8)。`items.length > maxItems`\n * 才 collapse。\n */\n maxItems?: number\n /** Collapse 後保留首 N 個(default 1)。對齊 Material UI source default。 */\n itemsBeforeCollapse?: number\n /** Collapse 後保留末 N 個(default 1)。對齊 Material UI source default。 */\n itemsAfterCollapse?: number\n children?: React.ReactNode\n}\n\n// code-quality-allow: long-function — items props × children narrowing × collapse-with-overflow × tooltip 4 軸組合在 BreadcrumbList,拆 sub-fn 會跨 fn 傳 itemsBeforeCollapse/After collapsed-tooltip refs\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, BreadcrumbListProps>(\n ({ className, size = 'md', items, maxItems = 4, itemsBeforeCollapse = 1, itemsAfterCollapse = 1, children, ...props }, ref) => {\n // Memoize provider value(2026-04-22 D3 perf audit):單 field wrapper memoize\n const ctxValue = React.useMemo(() => ({ size }), [size])\n\n // Declarative mode(items prop provided):自動 render + auto-collapse\n const declarativeContent = React.useMemo(() => {\n if (!items) return null\n // 2026-07-04 修:非字串 label 的 key 原用 Math.random() → 每次 render 新 key 造成\n // BreadcrumbItem unmount/remount(重置 TruncatedLabel ResizeObserver 與 state)→ 改 stable idx key\n const renderItem = (spec: BreadcrumbItemSpec, role: 'root' | 'middle' | 'current', idx: number) => (\n <BreadcrumbItem key={`${role}-${typeof spec.label === 'string' ? spec.label : idx}`} role={role}>\n {role === 'current'\n ? <BreadcrumbPage startIcon={spec.startIcon}>\n <TruncatedLabel fullText={typeof spec.label === 'string' ? spec.label : undefined}>{spec.label}</TruncatedLabel>\n </BreadcrumbPage>\n : <BreadcrumbLink href={spec.href} asChild={spec.asChild} startIcon={spec.startIcon}>\n <TruncatedLabel fullText={typeof spec.label === 'string' ? spec.label : undefined}>{spec.label}</TruncatedLabel>\n </BreadcrumbLink>\n }\n </BreadcrumbItem>\n )\n\n const shouldCollapse = items.length > maxItems\n const beforeN = Math.max(0, itemsBeforeCollapse)\n const afterN = Math.max(1, itemsAfterCollapse) // 末位永遠 ≥ 1(current page)\n\n type VisibleItem = { spec: BreadcrumbItemSpec; role: 'root' | 'middle' | 'current' }\n let visible: Array<VisibleItem | { ellipsisOf: BreadcrumbItemSpec[] }>\n if (!shouldCollapse) {\n visible = items.map((spec, i) => ({\n spec,\n role: i === 0 ? 'root' : (i === items.length - 1 ? 'current' : 'middle') as 'root' | 'middle' | 'current',\n }))\n } else {\n const before = items.slice(0, beforeN).map((spec, i) => ({\n spec,\n role: (i === 0 ? 'root' : 'middle') as 'root' | 'middle' | 'current',\n }))\n const collapsed = items.slice(beforeN, items.length - afterN)\n const after = items.slice(items.length - afterN).map((spec, i, arr) => ({\n spec,\n role: (i === arr.length - 1 ? 'current' : 'middle') as 'root' | 'middle' | 'current',\n }))\n visible = [...before, { ellipsisOf: collapsed }, ...after]\n }\n\n // Interleave with separators\n const rendered: React.ReactNode[] = []\n visible.forEach((entry, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-${i}`} />)\n if ('ellipsisOf' in entry) {\n rendered.push(\n <BreadcrumbItem key=\"ellipsis\" role=\"ellipsis\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <BreadcrumbEllipsis />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {entry.ellipsisOf.map((s, j) => (\n <DropdownMenuItem key={j} asChild={!!s.href}>\n {s.href ? <a href={s.href}>{s.label}</a> : <span>{s.label}</span>}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n )\n } else {\n rendered.push(renderItem(entry.spec, entry.role, i))\n }\n })\n return rendered\n }, [items, maxItems, itemsBeforeCollapse, itemsAfterCollapse])\n\n // 2026-05-12 fix(user 抓 image 2 Deep story 違反 single-line + max-levels canonical):\n // Compositional path 也走 auto-collapse + flex-shrink hierarchy。Walk children, 找\n // BreadcrumbItem 並按 index 分派 role (first=root / last=current / middle=middle)。\n // > maxItems 自動 collapse 中段成 ellipsis(對齊 declarative path canonical SSOT)。\n const compositionalContent = React.useMemo(() => {\n if (items) return null\n const childArr = React.Children.toArray(children)\n // 抓 BreadcrumbItem children(skip BreadcrumbSeparator — auto re-interleave)\n // 2026-05-12 Round 4.5 fix(codex M31 Layer C 抓):type-identity primary path + displayName fallback。\n // 純 `displayName` check 在 HOC / React.memo / consumer alias 場景脆弱(production build / wrap\n // 可能改寫 displayName)。`c.type === BreadcrumbItem` 是 React fiber reference-identity 最穩\n // primary(對齊 Radix children-walk pattern);displayName fallback 給 HOC 場景。\n const itemChildren = childArr.filter((c): c is React.ReactElement<BreadcrumbItemProps> =>\n React.isValidElement(c) && (c.type === BreadcrumbItem ||\n (c.type as React.ComponentType)?.displayName === 'BreadcrumbItem')\n )\n // 無 item 或全是 separator → pass-through(consumer raw children,e.g. spinners)\n if (itemChildren.length === 0) return children\n // Assign role by position; clone with role prop\n const total = itemChildren.length\n const cloneWithRole = (item: React.ReactElement<BreadcrumbItemProps>, idx: number, role: 'root' | 'middle' | 'current') =>\n React.cloneElement(item, { role: item.props.role ?? role, key: `bc-${role}-${idx}` })\n const shouldCollapse = total > maxItems\n const beforeN = Math.max(0, itemsBeforeCollapse)\n const afterN = Math.max(1, itemsAfterCollapse)\n const rendered: React.ReactNode[] = []\n if (!shouldCollapse) {\n itemChildren.forEach((item, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-${i}`} />)\n const role: 'root' | 'middle' | 'current' = i === 0 ? 'root' : (i === total - 1 ? 'current' : 'middle')\n rendered.push(cloneWithRole(item, i, role))\n })\n } else {\n // before(first N) + ellipsis + after(last M)\n const beforeItems = itemChildren.slice(0, beforeN)\n const collapsedItems = itemChildren.slice(beforeN, total - afterN)\n const afterItems = itemChildren.slice(total - afterN)\n beforeItems.forEach((item, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-bef-${i}`} />)\n const role: 'root' | 'middle' = i === 0 ? 'root' : 'middle'\n rendered.push(cloneWithRole(item, i, role))\n })\n if (rendered.length > 0) rendered.push(<BreadcrumbSeparator key=\"sep-ellipsis-before\" />)\n rendered.push(\n <BreadcrumbItem key=\"bc-ellipsis\" role=\"ellipsis\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <BreadcrumbEllipsis />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n {collapsedItems.map((item, j) => {\n // 2026-05-12 Round 4.5 fix(codex M31 Layer C 抓):consumer BreadcrumbItem children 常包\n // `<BreadcrumbLink href>` = anchor button-like。直接放進 `<DropdownMenuItem>` 會 nested\n // interactive(menuitem within button violates HTML / a11y)。Fix:extract href + label,\n // 用 `asChild` 把 anchor 接到 menuitem 對齊 declarative path line 245 canonical pattern。\n const innerChildren = (item.props as { children?: React.ReactNode }).children\n let href: string | undefined\n let label: React.ReactNode = innerChildren\n React.Children.forEach(innerChildren, (c) => {\n if (React.isValidElement<{ href?: string; children?: React.ReactNode }>(c)) {\n if (c.props.href) href = c.props.href\n if (c.props.children != null) label = c.props.children\n }\n })\n return href\n ? <DropdownMenuItem key={`collapsed-${j}`} asChild><a href={href}>{label}</a></DropdownMenuItem>\n : <DropdownMenuItem key={`collapsed-${j}`}>{label}</DropdownMenuItem>\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </BreadcrumbItem>\n )\n rendered.push(<BreadcrumbSeparator key=\"sep-ellipsis-after\" />)\n afterItems.forEach((item, i) => {\n if (i > 0) rendered.push(<BreadcrumbSeparator key={`sep-aft-${i}`} />)\n const role: 'middle' | 'current' = i === afterItems.length - 1 ? 'current' : 'middle'\n rendered.push(cloneWithRole(item, i, role))\n })\n }\n return rendered\n }, [items, children, maxItems, itemsBeforeCollapse, itemsAfterCollapse])\n\n return (\n <BreadcrumbContext.Provider value={ctxValue}>\n <ol\n ref={ref}\n // gap-1 (4px) — separator 與兩邊 items 間距;緊湊節奏,符合 breadcrumb 密集流動感。\n // 2026-05-10 Phase A single-line canonical(per user + Material UI source verified):\n // `flex-nowrap` 不 wrap。長路徑走中段折疊。\n // 2026-05-12 fix:compositional 也走 auto-collapse + role-assignment(`compositionalContent`)\n // → declarative / compositional 兩 path 都符合 single-line + max-levels + width 分配 canonical SSOT。\n className={cn(\n 'flex flex-nowrap items-center gap-1 text-fg-secondary leading-compact min-w-0',\n BREADCRUMB_TEXT_CLASS[size],\n className\n )}\n {...props}\n >\n {items ? declarativeContent : compositionalContent}\n </ol>\n </BreadcrumbContext.Provider>\n )\n },\n)\nBreadcrumbList.displayName = 'BreadcrumbList'\n\n// ── BreadcrumbItem (li) ──────────────────────────────────────────────────────\n\n/**\n * Phase B(2026-05-10):`role` prop emit `data-bc-role` attr → CSS flex-shrink hierarchy。\n * Per BreadcrumbItem 在 row 中的角色決定 shrink 優先級:\n * - `root`(首位)→ shrink:3(縮最積極;root context 可弱化)\n * - `middle`(中段)→ shrink:2\n * - `current`(末位 / page)→ shrink:1(最後縮;a11y current page anchor)\n * - `ellipsis`(BreadcrumbEllipsis 包裝)→ shrink:0(永遠完整 ⋯)\n *\n * 設計回應 user 兩 challenges:\n * (a) Root 也 truncate(shrink:3,不是 shrink-0)\n * (b) 不用 fixed max-width — flex-shrink hierarchy 容器寬時自然展開不浪費空間,\n * 窄時按優先級縮 + TruncatedLabel 內部 CSS truncate + tooltip。\n */\ninterface BreadcrumbItemProps extends React.ComponentPropsWithoutRef<'li'> {\n role?: 'root' | 'middle' | 'current' | 'ellipsis'\n}\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, BreadcrumbItemProps>(\n ({ className, role, style, ...props }, ref) => {\n // 2026-05-20 fix v3(user 抓「專案 後方多 4px 間距 / 我的新專案 沒有」chevron 不對稱):\n // v2 `minWidth: '2rem'`(32px)在寬容器強制 li ≥ 32px → 短 label「專案」(natural ~28px)\n // 被撐 4px,長 label「我的新專案」(natural ~70px)hug content → chevron 兩側不對稱。\n //\n // v3 解法:minWidth `2rem` → `1.5rem`(24px)\n // 數學:中文「X…」最小寬度 = 1 char(~14-16px)+ ellipsis(~6-8px)≈ 22-24px → 24px 剛 cover\n // 結果:\n // - 寬容器:所有自然 label ≥ 24px → li hug content,chevron 緊貼,對稱(本 fix 主目的)\n // - 窄容器 truncate:shrink 不過 24px → 「X…」仍可見 ellipsis 保險\n // - 短英文「OK / ID」(natural ~20px)→ 多 ~4px(原 12px → 縮到 4px,顯著改善)\n // 對齊 user verbatim「minWidth 再調小一點」+ ellipsis 數學最小值。\n const shrinkStyle: React.CSSProperties = role === 'root' ? { flexShrink: 3, minWidth: '1.5rem' }\n : role === 'middle' ? { flexShrink: 2, minWidth: '1.5rem' }\n : role === 'current' ? { flexShrink: 1, minWidth: '1.5rem' }\n : role === 'ellipsis' ? { flexShrink: 0 }\n : {}\n return (\n <li\n ref={ref}\n data-bc-role={role}\n className={cn('inline-flex items-center min-w-0', className)}\n style={{ ...shrinkStyle, ...style }}\n {...props}\n />\n )\n }\n)\nBreadcrumbItem.displayName = 'BreadcrumbItem'\n\n// ── BreadcrumbLink (a) ───────────────────────────────────────────────────────\n\ninterface BreadcrumbLinkProps extends React.ComponentPropsWithoutRef<'a'> {\n /** 將樣式套用至子元件(e.g. React Router Link) */\n asChild?: boolean\n /**\n * 起始 icon(per `ui-development.md`「icon prop 命名 4 條」:slot 只接 icon → `startIcon`)。\n * 內部消費 `BREADCRUMB_ICON_SIZE[size]` SSOT,DS 統一尺寸不允許 consumer override。\n * 對齊 uiSize.spec.md Icon Size Tier(2026-05-18 撤回 14 例外,統一 16/16/20)。\n */\n startIcon?: LucideIcon\n}\n\nconst BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(\n ({ asChild, className, children, startIcon: StartIcon, ...props }, ref) => {\n const { size } = React.useContext(BreadcrumbContext)\n // 2026-05-12 fix(user 抓 image 2 Deep story 麵包屑沒符合 single-line + truncate canonical):\n // 純文字 children → auto-wrap TruncatedLabel(canonical「single-line + ellipsis + tooltip\n // on truncate」per spec.md / Polaris breadcrumb)。Non-string children(consumer 自訂 icon+text\n // 結構)→ pass-through 不 force-wrap(consumer own truncate)。\n const wrappedChildren = typeof children === 'string'\n ? <TruncatedLabel fullText={children}>{children}</TruncatedLabel>\n : children\n const sharedClassName = cn(\n 'inline-flex items-center gap-2',\n 'min-w-0 max-w-full',\n 'text-fg-secondary',\n 'hover:text-primary-hover',\n 'transition-colors duration-150',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'rounded-md',\n className\n )\n // 2026-05-25 fix(user 抓 Breadcrumb asChild story React.Children.only runtime fail):\n // Radix Slot 規範 children 必為單 element;原 unified Comp render 在 asChild path 內\n // 仍輸出「{StartIcon && ...} + {wrappedChildren}」雙 JSX expression → Slot 收到 array\n // → React.Children.only(array) throws「expected to receive a single React element child」。\n // 分支 render 解 — asChild path 只傳 consumer-supplied child(icon 由 consumer 自管,\n // 對齊 Radix Slot canonical「single child contract」);非 asChild path 維持原 native\n // <a> + DS-controlled icon + wrapped label。\n if (asChild) {\n return (\n <Slot ref={ref} className={sharedClassName} {...props}>\n {wrappedChildren}\n </Slot>\n )\n }\n return (\n <a ref={ref} className={sharedClassName} {...props}>\n {StartIcon && <StartIcon size={BREADCRUMB_ICON_SIZE[size]} aria-hidden className=\"shrink-0\" />}\n {wrappedChildren}\n </a>\n )\n }\n)\nBreadcrumbLink.displayName = 'BreadcrumbLink'\n\n// ── BreadcrumbPage (current, non-clickable) ──────────────────────────────────\n\ninterface BreadcrumbPageProps extends React.ComponentPropsWithoutRef<'span'> {\n /** 起始 icon。內部消費 `BREADCRUMB_ICON_SIZE[size]` SSOT。對齊 BreadcrumbLink. */\n startIcon?: LucideIcon\n}\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, BreadcrumbPageProps>(\n ({ className, children, startIcon: StartIcon, ...props }, ref) => {\n const { size } = React.useContext(BreadcrumbContext)\n // 2026-05-12 fix(同 BreadcrumbLink):純文字 children → auto-wrap TruncatedLabel。\n const wrappedChildren = typeof children === 'string'\n ? <TruncatedLabel fullText={children}>{children}</TruncatedLabel>\n : children\n return (\n <span\n ref={ref}\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn('inline-flex items-center gap-2 min-w-0 max-w-full text-foreground', className)}\n {...props}\n >\n {StartIcon && <StartIcon size={BREADCRUMB_ICON_SIZE[size]} aria-hidden className=\"shrink-0\" />}\n {wrappedChildren}\n </span>\n )\n }\n)\nBreadcrumbPage.displayName = 'BreadcrumbPage'\n\n// ── BreadcrumbSeparator ──────────────────────────────────────────────────────\n\ninterface BreadcrumbSeparatorProps extends React.ComponentPropsWithoutRef<'li'> {\n children?: React.ReactNode\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst BreadcrumbSeparator = React.forwardRef<HTMLLIElement, BreadcrumbSeparatorProps>(\n ({ children, className, ...props }, ref) => {\n const { size } = React.useContext(BreadcrumbContext)\n return (\n <li\n ref={ref}\n role=\"presentation\"\n aria-hidden=\"true\"\n // Phase B(2026-05-10):separator 永遠 shrink-0(必完整顯示,否則 path 視覺斷裂)\n className={cn('inline-flex items-center text-fg-muted shrink-0', className)}\n {...props}\n >\n {children ?? <ChevronRight size={BREADCRUMB_ICON_SIZE[size]} aria-hidden />}\n </li>\n )\n }\n)\nBreadcrumbSeparator.displayName = 'BreadcrumbSeparator'\n\n// ── BreadcrumbEllipsis ───────────────────────────────────────────────────────\n\n/**\n * BreadcrumbEllipsis — 折疊路徑的 \"⋯\" 按鈕\n *\n * 2026-05-10 重寫:消費 `ItemInlineActionButton`(primitive SSOT)取代自刻 `<button>`。\n * Per inline-action.spec.md L106-131 predicate Q1+Q2+Q3 全指向 Inline Action:\n * - Q1 點了要做事嗎?是(展開折疊路徑 dropdown)\n * - Q2 位置?BreadcrumbList row inline flow(host 內)\n * - Q3 row 多大?14-16px text row(compact tier)→ Inline Action\n * + 對齊 M1「視覺決策前必消費 SSOT」+ Mindset #2「優先消費既有」。\n *\n * 配合 DropdownMenuTrigger asChild 使用:\n *\n * ```tsx\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild>\n * <BreadcrumbEllipsis />\n * </DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem asChild><a href=\"/org\">組織</a></DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n * ```\n *\n * `overlayTrigger=true` 視覺鎖:DropdownMenu open 期間 button 維持 hover bg(對齊\n * shadcn / Radix Themes / Material 的 overlay trigger canonical,inline-action.spec.md\n * 「Overlay trigger canonical」段)。\n */\ntype BreadcrumbEllipsisProps = Omit<React.ComponentPropsWithoutRef<typeof ItemInlineActionButton>, 'icon' | 'size'>\n\nconst BreadcrumbEllipsis = React.forwardRef<HTMLButtonElement, BreadcrumbEllipsisProps>(\n ({ 'aria-label': ariaLabel = '顯示折疊路徑' /* i18n-allow: DS default; consumer override via aria-label prop */, ...props }, ref) => {\n return (\n <ItemInlineActionButton\n ref={ref}\n icon={MoreHorizontal}\n size=\"md\" // Breadcrumb 不在 RowSizeProvider 樹內,固定 md(16px icon + 18px hover bg,對齊 inline-action.spec.md 尺寸表)\n aria-label={ariaLabel}\n overlayTrigger\n {...props}\n />\n )\n }\n)\nBreadcrumbEllipsis.displayName = 'BreadcrumbEllipsis'\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 breadcrumbMeta = {\n component: 'Breadcrumb',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n // 值源 = code SSOT:BREADCRUMB_TEXT_CLASS(sm/md=text-body, lg=text-body-lg)+ BREADCRUMB_ICON_SIZE(sm/md=16, lg=20)。\n // Breadcrumb 是文字列無固定高度 token,故不列 height(對照 Tag meta 有自身 h-* 才列)。\n sm: { iconSize: 16, typography: 'body' },\n md: { iconSize: 16, typography: 'body' },\n lg: { iconSize: 20, typography: 'body-lg' },\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-muted', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n}\nexport type { BreadcrumbSize, BreadcrumbListProps, BreadcrumbEllipsisProps }\n// BreadcrumbItemSpec 已在上方 `export interface BreadcrumbItemSpec` 直接 export\n"],"names":[],"mappings":"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"}
|
|
@@ -190,9 +190,9 @@ export declare const buttonMeta: {
|
|
|
190
190
|
readonly typography: "body-lg";
|
|
191
191
|
};
|
|
192
192
|
};
|
|
193
|
-
readonly states: readonly ["default", "hover", "active", "pressed", "focus-visible", "disabled"];
|
|
193
|
+
readonly states: readonly ["default", "hover", "active", "pressed", "loading", "focus-visible", "disabled"];
|
|
194
194
|
readonly tokens: {
|
|
195
|
-
readonly bg: readonly ["--primary", "--primary-hover", "--primary-active", "--primary-subtle", "--bg-disabled", "--surface", "--neutral-hover", "--neutral-active", "--neutral-selected"];
|
|
195
|
+
readonly bg: readonly ["--primary", "--primary-hover", "--primary-active", "--primary-subtle", "--bg-disabled", "--surface", "--neutral-hover", "--neutral-active", "--neutral-selected", "--neutral-selected-hover", "--neutral-selected-active", "--error", "--error-hover", "--error-active"];
|
|
196
196
|
readonly fg: readonly ["--on-emphasis", "--fg-disabled", "--foreground", "--primary", "--error", "--error-hover", "--error-active"];
|
|
197
197
|
readonly ring: readonly ["--ring"];
|
|
198
198
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/Button/button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAK9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,QAAA,MAAM,cAAc;;;;;8EAoKnB,CAAA;AAKD,UAAU,uBAAuB;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AACD,QAAA,MAAM,kBAAkB,wCAAmD,CAAA;AAI3E,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EACnD,IAAI,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjE;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;IAChE,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IACpC,sDAAsD;IACtD,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,oCAAoC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAcD,QAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/Button/button.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAK9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,QAAA,MAAM,cAAc;;;;;8EAoKnB,CAAA;AAKD,UAAU,uBAAuB;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AACD,QAAA,MAAM,kBAAkB,wCAAmD,CAAA;AAI3E,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EACnD,IAAI,CAAC,YAAY,CAAC,OAAO,cAAc,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjE;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;IAChE,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IACpC,sDAAsD;IACtD,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,oCAAoC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAcD,QAAA,MAAM,MAAM,uFAwNX,CAAA;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Bb,CAAA;AAEV,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -210,6 +210,11 @@ const Button = React.forwardRef(
|
|
|
210
210
|
}
|
|
211
211
|
const resolvedVariant = dismiss ? "text" : variantProp === "destructive" ? "primary" : variantProp === "ghost" ? "text" : variantProp ?? (iconOnly ? "text" : "tertiary");
|
|
212
212
|
const resolvedDanger = dangerProp || variantProp === "destructive";
|
|
213
|
+
if (process.env.NODE_ENV !== "production" && resolvedDanger && (resolvedVariant === "tertiary" || resolvedVariant === "link")) {
|
|
214
|
+
console.warn(
|
|
215
|
+
`[DS Button] \`danger\` 不支援 variant="${resolvedVariant}"(無對應 compoundVariant,會靜默渲染成一般 ${resolvedVariant},無紅色 danger 視覺)。危險操作請改用 primary(立即不可逆)/ secondary(可反悔)/ text(低度)。SSOT:button.spec.md「禁止事項」danger 條。`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
213
218
|
const groupCtx = React.useContext(ButtonGroupContext);
|
|
214
219
|
const resolvedFullWidth = fullWidth || !!groupCtx.fullWidth;
|
|
215
220
|
const Comp = asChild ? Slot : "button";
|
|
@@ -320,11 +325,15 @@ const buttonMeta = {
|
|
|
320
325
|
lg: { fieldHeight: 36, iconSize: 20, typography: "body-lg" }
|
|
321
326
|
},
|
|
322
327
|
// 'pressed' = toggle on 持續態(data-[state=on],emphasis 淡藍底/neutral 灰底;2026-07-07 meta 詞彙統一補列)
|
|
323
|
-
|
|
328
|
+
// 'loading' = CircularProgress spinner 取代 startIcon + aria-busy + click guard(2026-07-14 deep-audit
|
|
329
|
+
// 補列,原漏 → auto-compile 生成矩陣缺 loading;對齊 anatomy StateBehavior Loading 行 + tsx loading 分支)
|
|
330
|
+
states: ["default", "hover", "active", "pressed", "loading", "focus-visible", "disabled"],
|
|
324
331
|
tokens: {
|
|
325
332
|
// 2026-07-04 audit 補齊:原只列 primary variant 家族;按 buttonVariants cva 真實 class 補
|
|
326
|
-
// secondary/tertiary/text/link/danger 消費的 token(auto-compile token 矩陣依此)
|
|
327
|
-
|
|
333
|
+
// secondary/tertiary/text/link/danger 消費的 token(auto-compile token 矩陣依此)。
|
|
334
|
+
// 2026-07-14 deep-audit 二次補齊(掃 cva 全 bg-* class):+ neutral-selected-hover / neutral-selected-active
|
|
335
|
+
// (neutral pressedTone compound 消費)+ error / error-hover / error-active(primary+danger bg-error 系)
|
|
336
|
+
bg: ["--primary", "--primary-hover", "--primary-active", "--primary-subtle", "--bg-disabled", "--surface", "--neutral-hover", "--neutral-active", "--neutral-selected", "--neutral-selected-hover", "--neutral-selected-active", "--error", "--error-hover", "--error-active"],
|
|
328
337
|
fg: ["--on-emphasis", "--fg-disabled", "--foreground", "--primary", "--error", "--error-hover", "--error-active"],
|
|
329
338
|
ring: ["--ring"]
|
|
330
339
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","sources":["../../../src/components/Button/button.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.\n// code-quality-allow: file-size — foundational composite(Button + iconOnly + danger + loading + asChild + dismiss + pressedTone)— 跨 7 axis variant 集中 SSOT 一處,拆分會 fragment cva variant catalog。當前 528 < cap 800。\nimport * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { useResolvedFieldSize } from '@/design-system/components/Field/field-context'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'\n\n/**\n * Button — shadcn 風格,橋接設計系統 token\n *\n * ── Variants ──\n * primary 主要操作,藍底白字\n * secondary 次要品牌操作,藍框藍字;正面 vs 負面並存時用於正面那個\n * tertiary 一般輔助操作,灰框灰字,hover 轉藍(最常用的非主要按鈕)\n * text 無底色無邊框,hover 顯示灰底(工具列、密集 UI)\n * link 外觀像連結的按鈕(本質仍是 button)\n *\n * ── danger prop ──\n * danger 套用危險色(紅色);僅 primary / secondary / text 支援(tertiary / link 不支援,見 spec 禁止事項)\n *\n * <Button variant=\"primary\" danger>永久刪除</Button> → 紅底白字(立即不可逆)\n * <Button variant=\"secondary\" danger>移至垃圾桶</Button> → 紅框紅字(點下去還可反悔)\n *\n * ── pressed prop(toggle)──\n * pressed Toggle 按下狀態(持續 on/off),寫入 aria-pressed + data-state\n * 僅 secondary / tertiary / text 三個 variant 支援 toggle 視覺:\n * - secondary + pressed → primary-subtle 底、primary 字、透明邊框\n * - tertiary + pressed → primary-subtle 底、primary 字、透明邊框(同 secondary 按下視覺)\n * - text + pressed → primary-subtle 底、primary 字、透明邊框(預設 pressedTone='emphasis',同 secondary/tertiary 按下視覺);pressedTone='neutral' 時才走 neutral-selected 灰底\n * primary / link 傳入 pressed 無視覺效果(語意不符)\n *\n * ── Sizes(預設 md)──\n * xs h-field-xs(24px 固定),不隨 density 縮放\n * sm h-field-sm,md=28px / lg=32px\n * md h-field-md,md=32px / lg=36px ← 預設(跟 Field/Input 對齊)\n * lg h-field-lg,md=36px / lg=40px\n * icon-only 不是獨立尺寸 — 加 iconOnly prop 讓任何尺寸變正方形\n *\n * ── 內部結構 ──\n * [startIcon?] [label] [badge? + endIcon?]\n *\n * ── 用法範例 ──\n * <Button startIcon={Plus}>新增</Button>\n * <Button variant=\"tertiary\">取消</Button>\n * <Button variant=\"primary\" danger>永久刪除</Button>\n * <Button variant=\"text\" pressed={isPinned} startIcon={Pin} aria-label=\"釘選\" iconOnly />\n * <Button badge={<Badge count={3} />} endIcon={ChevronDown}>通知</Button>\n * <Button size=\"sm\" iconOnly startIcon={Plus} aria-label=\"新增\" />\n * <Button iconOnly startIcon={Bell} aria-label=\"通知 (3 則)\"\n * overlayBadge={<Badge count={3} />} /> ← badge 自動貼 icon 右上角\n *\n * ── asChild ──\n * <Button asChild><Link to=\"/home\">回首頁</Link></Button>\n */\nconst buttonVariants = cva(\n [\n 'inline-flex items-center justify-center',\n 'whitespace-nowrap font-medium',\n 'border border-transparent',\n 'transition-colors duration-150',\n 'cursor-pointer select-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n // 2026-05-12 Round 4.5 fix(codex M31 Layer C 抓):`aria-disabled` visual 分支補(per WAI-ARIA APG —\n // aria-disabled 給語意 + visual,但不 suppress functionality;functionality 由 consumer 阻 e.g.\n // RowDragHandle listeners 只在 canDrag spread)。**故意不加** `aria-disabled:pointer-events-none`\n // — Round 4 RowDragHandle Tooltip flicker fix root cause:aria-disabled buttons 必保 pointer events\n // 讓 Radix Tooltip pointerenter 通過。HTML `disabled` 保 PE:none(完全 inactive)。\n 'disabled:pointer-events-none',\n // 2026-05-12 fix v2(playwright pixel-quantified verify 抓 opacity=1 不生效):\n // `opacity-disabled` 是 custom Tailwind v4 `@utility`(opacity.css:21),variant prefix\n // `aria-disabled:` 跟 custom @utility 不 compose(`aria-disabled:cursor-not-allowed` 標準\n // utility 能 work,custom 不行)。改 arbitrary value `opacity-[var(--opacity-disabled)]`\n // 繞 custom @utility 限制(Tailwind v4 arbitrary value 直接生 `opacity: var(--opacity-disabled)`)。\n 'aria-disabled:opacity-[var(--opacity-disabled)]',\n 'rounded-md',\n // Defensive:SVG 不被 flex shrink 擠扁(防 inner-area 計算誤差導致 icon 被擠成\n // width<intrinsic 的 asymmetric 顯示)。詳 ICON_ONLY_PX 段 rationale。\n '[&_svg]:shrink-0',\n ],\n {\n variants: {\n variant: {\n primary: [\n 'bg-primary text-on-emphasis',\n 'hover:bg-primary-hover',\n 'active:bg-primary-active',\n 'disabled:bg-disabled disabled:text-fg-disabled disabled:border-transparent',\n ],\n secondary: [\n 'bg-surface text-primary border-primary',\n 'hover:text-primary-hover hover:border-primary-hover',\n 'active:text-primary-active active:border-primary-active',\n // Overlay trigger active(asChild Popover/DropdownMenu trigger)— 維持 hover 樣式\n // (canonical 對齊 inline-action.spec.md:trigger 維持 host hover 直到 overlay 關閉)\n 'data-[state=open]:text-primary-hover data-[state=open]:border-primary-hover',\n 'disabled:bg-transparent disabled:text-fg-disabled disabled:border-border',\n // 2026-05-21 v12:Toggle pressed 視覺移到 compoundVariants(variant × pressedTone),\n // 同時支援 emphasis(藍底)/ neutral(灰底)兩 tone。詳 cva.compoundVariants 段。\n ],\n tertiary: [\n 'bg-surface text-foreground border-border',\n 'hover:text-primary-hover hover:border-primary-hover',\n 'active:text-primary-active active:border-primary-active',\n // Overlay trigger active — 維持 hover 樣式(同 secondary 邏輯)\n 'data-[state=open]:text-primary-hover data-[state=open]:border-primary-hover',\n 'disabled:bg-transparent disabled:text-fg-disabled disabled:border-border',\n ],\n text: [\n 'bg-transparent text-foreground border-transparent',\n 'hover:bg-neutral-hover',\n 'active:bg-neutral-active',\n // Overlay trigger active — 維持 hover 樣式(canonical 2026-05-02 改:hover token,\n // 不另開 selected 4% — 對齊 shadcn/Radix/Material 狀態極簡派,跨 host 一致)\n 'data-[state=open]:bg-neutral-hover',\n 'disabled:bg-transparent disabled:text-fg-disabled',\n ],\n link: [\n 'bg-transparent text-primary border-transparent',\n 'hover:text-primary-hover',\n 'active:text-primary-active',\n 'disabled:text-fg-disabled',\n ],\n },\n danger: {\n true: '', // 實際樣式由 compoundVariants 提供\n },\n /**\n * 2026-05-21 v12 — pressed visual tone(per user「我認同這一個方向,然後預設emphasis」):\n * emphasis = 淡藍底(toolbar functional toggle / 篩選啟用 / 面板開關 — Figma toolbar /\n * Linear toolbar / Material ToggleButton 共識)\n * neutral = 灰底(sidebar/contextual nav row pressed — Linear / Notion / VS Code Activity Bar 共識)\n * 預設 `emphasis` per user directive。實際樣式由 compoundVariants(variant × pressedTone)套用。\n * 只在 secondary / tertiary / text variant 觸發 toggle 視覺;primary / link 無視覺效果。\n */\n pressedTone: {\n emphasis: '',\n neutral: '',\n },\n size: {\n xs: 'h-field-xs px-2 text-caption leading-compact gap-0',\n sm: 'h-field-sm px-3 min-w-14 text-body leading-compact gap-1',\n md: 'h-field-md px-3 min-w-16 text-body leading-compact gap-1',\n lg: 'h-field-lg px-3 min-w-20 text-body-lg leading-compact gap-1',\n },\n },\n compoundVariants: [\n // primary + danger → 紅底白字(立即不可逆操作)\n {\n variant: 'primary',\n danger: true,\n class: [\n 'bg-error text-on-emphasis border-transparent',\n 'hover:bg-error-hover',\n 'active:bg-error-active',\n ],\n },\n // secondary + danger → 紅框紅字(有確認步驟的危險操作)\n {\n variant: 'secondary',\n danger: true,\n class: [\n 'bg-surface text-error border-error',\n 'hover:text-error-hover hover:border-error-hover',\n 'active:text-error-active active:border-error-active',\n ],\n },\n // text + danger → 紅字,hover 灰底\n {\n variant: 'text',\n danger: true,\n class: [\n 'text-error',\n 'hover:bg-neutral-hover hover:text-error-hover',\n 'active:bg-neutral-active active:text-error-active',\n ],\n },\n // ── 2026-05-21 v12 Toggle pressed(variant × pressedTone)──────────────────\n // 視覺由 data-[state=on] + aria-pressed(Radix overlay trigger fallback)觸發\n //\n // emphasis tone:藍底(primary-subtle / primary 字)— functional toggle\n {\n variant: ['secondary', 'tertiary', 'text'],\n pressedTone: 'emphasis',\n class: [\n 'data-[state=on]:bg-primary-subtle data-[state=on]:text-primary data-[state=on]:border-transparent',\n 'data-[state=on]:hover:text-primary-hover',\n 'data-[state=on]:active:text-primary-active',\n 'data-[state=on]:disabled:bg-disabled data-[state=on]:disabled:text-fg-disabled data-[state=on]:disabled:border-transparent',\n // aria-pressed fallback(Radix overlay trigger override data-state 時仍生效)\n 'aria-pressed:bg-primary-subtle aria-pressed:text-primary aria-pressed:border-transparent',\n 'aria-pressed:hover:text-primary-hover',\n // 2026-07-07 補齊第三階:fallback 分支與 data-[state=on] 分支同拼寫(選中之上按壓 = active 階)\n 'aria-pressed:active:text-primary-active',\n ],\n },\n // neutral tone:灰底(neutral-selected family)— sidebar/contextual nav pressed\n {\n variant: ['secondary', 'tertiary', 'text'],\n pressedTone: 'neutral',\n class: [\n 'data-[state=on]:bg-neutral-selected data-[state=on]:text-foreground data-[state=on]:border-transparent',\n 'data-[state=on]:hover:bg-neutral-selected-hover',\n 'data-[state=on]:active:bg-neutral-selected-active',\n 'data-[state=on]:disabled:bg-transparent data-[state=on]:disabled:text-fg-disabled',\n // aria-pressed fallback\n 'aria-pressed:bg-neutral-selected aria-pressed:text-foreground aria-pressed:border-transparent',\n 'aria-pressed:hover:bg-neutral-selected-hover',\n ],\n },\n ],\n defaultVariants: {\n // labeled 預設 = tertiary(2026-06-06 從 primary 改;iconOnly 預設 = text,由 resolvedVariant 注入)。\n // 真正預設邏輯在元件內 resolvedVariant(相對指法,免行號漂移);此 cva default 供直接 buttonVariants() 呼叫者一致。\n variant: 'tertiary',\n size: 'md',\n pressedTone: 'emphasis',\n },\n }\n)\n\n// ── ButtonGroup Context ──────────────────────────────────────────────────────\n// ButtonGroup 提供此 context;Button 讀取它注入 fullWidth。\n// Context 放本檔(不放 button-group.tsx)以避免循環 import。\ninterface ButtonGroupContextValue {\n fullWidth?: boolean\n}\nconst ButtonGroupContext = React.createContext<ButtonGroupContextValue>({})\n\ntype InternalVariant = VariantProps<typeof buttonVariants>['variant']\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n Omit<VariantProps<typeof buttonVariants>, 'variant' | 'danger'> {\n /**\n * 將樣式套用至子元件(e.g. React Router Link)。\n *\n * ⚠️ **icon-only + asChild 警告**:當 `iconOnly={true}` 且 `asChild={true}` 時,內建\n * Tooltip wrapper 不啟動(Radix Slot 不接受多 child)。consumer 必須**自管 child 的 `aria-label`**\n * 給 screen reader,並視需要自行包 `<Tooltip>`(對齊 Polaris / Radix asChild idiom)。\n * Button 不會主動補 tooltip — 否則會破壞 Slot 單 child 規則。\n */\n asChild?: boolean\n /**\n * 按鈕視覺強調等級。\n * `destructive` / `ghost` 為 shadcn 內部 compat,請勿在應用程式碼中直接使用。\n */\n variant?: 'primary' | 'secondary' | 'tertiary' | 'text' | 'link'\n /** 套用危險色(紅色)。僅 primary / secondary / text 支援(tertiary / link 不支援,見 spec 禁止事項)。 */\n danger?: boolean\n /**\n * Toggle 按下狀態(持續 on/off)。設定時 Button 變為 toggle:\n * - 自動寫入 `aria-pressed` + `data-state=\"on\" | \"off\"`\n * - 樣式由 variant × `pressedTone` 的 compoundVariants 套用\n * - 僅 secondary / tertiary / text 有 toggle 視覺;primary / link 傳入無效果\n *\n * 不傳此 prop 時 Button 就是一般按鈕,不帶 aria-pressed。\n */\n pressed?: boolean\n /**\n * Pressed 視覺色調(2026-05-21 v12 加):\n * - `'emphasis'`(預設)→ 淡藍底 / primary 字(對齊 Figma toolbar / Linear toolbar /\n * Material ToggleButton 共識 — toolbar functional toggle / 篩選啟用 / 面板開關)\n * - `'neutral'` → 灰底 / foreground 字(對齊 Linear / Notion / VS Code Activity Bar\n * 共識 — sidebar/contextual nav row pressed)\n *\n * 僅在 `pressed` 啟用且 variant ∈ {secondary, tertiary, text} 時生效。\n * 跨 toggle context 維持單一 prop API,consumer 視語意選 tone 不另開 variant。\n */\n pressedTone?: 'emphasis' | 'neutral'\n /** 左側 icon(LucideIcon),最多一個,loading 時自動替換為 spinner */\n startIcon?: LucideIcon\n /** 右側 badge(ReactNode),通常傳入計數指示器 */\n badge?: React.ReactNode\n /**\n * Overlay badge(iconOnly 專用)。接收 `<Badge>` 元素,Button 內部**自動定位在 startIcon 右上角**——\n * badge 中心對齊 icon 的 top-right corner(Material BadgedBox / iOS App icon canonical),不是按鈕邊緣。\n * 解決手刻 `relative + absolute -top-1 -right-1` 讓 badge 飄在按鈕 chrome 右上的問題。\n *\n * 世界級對照:Material BadgedBox、iOS App Icon、Ant Badge wrap icon,badge 相對於**視覺重心**(icon)。\n * 只在 `iconOnly=true` 時生效;非 iconOnly 時應該用 inline `badge` prop 放 suffix 位置。\n */\n overlayBadge?: React.ReactNode\n /** 右側 icon(LucideIcon),放在 badge 右邊,通常用於 ChevronDown 等方向指示 */\n endIcon?: LucideIcon\n /** Icon-only 模式:移除 padding,變為正方形(必須同時設定 aria-label) */\n iconOnly?: boolean\n /**\n * Dismiss 視覺類(X close only canonical)。專用於 **X(close)icon 的 dismiss 語意** —\n * 「關閉 surface / 忽略訊息」。**不適用 Trash / Delete / Clear / Remove 等 destructive / clear 操作**。\n *\n * 自動套用:\n * - `variant=\"text\"`(強制 override 其他 variant)\n * - `iconOnly=true`(強制)\n * - Icon 色 override:`fg-muted` → hover `foreground`(跟 Inline Action dismiss 視覺一致)\n *\n * 典型 case:Dialog / Sheet / Popover / Alert / Toast / Coachmark 的 **chrome corner close X**\n * (action group region — corner 可多 action,close 左側加 Separator + refresh / share 等)。\n *\n * 非 dismiss(**不套此 prop**):\n * - Trash / Delete → destructive action,Button 用一般 variant 或 Inline Action(按 row size 判)\n * - Clear → 欄位清空,用 Inline Action\n * - Remove → collection 移除,用一般 Button / Inline Action\n *\n * 詳見 button.spec.md「Dismiss 視覺類」段 + patterns/element-anatomy/inline-action.spec.md\n * 「Dismiss canonical — X close only」段。\n */\n dismiss?: boolean\n /** 載入中狀態:startIcon 替換為 spinner,自動 disabled;badge / endIcon 維持顯示以避免 layout shift */\n loading?: boolean\n /** 撐滿父容器寬度 */\n fullWidth?: boolean\n}\n\n// IconOnly 用 padding-free + aspect-square + flex-center 的 Polaris/Atlassian idiom\n// (M17 SSOT 必可傳播 — 取代 4 個 size 的 magic-number 公式):\n// - aspect-square 鎖 width=height(來自 h-field-X)\n// - p-0 移除 px-3 (label 模式) override\n// - flex justify-center items-center(base 已有)→ SVG 自動視覺置中\n// 結果:0 magic number,0 公式,0 border-deduction,任何 size / icon size 都自然正方形。\n// World-class 對照:Polaris + Atlassian iconOnly 走 padding-free 派(Material/Ant 走\n// padding-based)。我們選 padding-free 因 SSOT 性更強(SegmentedControl / Tag dismiss\n// 等 host 全可共用同 utility class,無需各自抄公式)。詳 button.spec.md「iconOnly 鐵律」。\nconst ICON_ONLY_BASE = 'aspect-square p-0 min-w-0 gap-0'\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant: variantProp,\n danger: dangerProp,\n size,\n asChild = false,\n startIcon: StartIcon,\n badge,\n overlayBadge,\n endIcon: EndIcon,\n iconOnly = false,\n dismiss = false,\n loading = false,\n fullWidth = false,\n pressed,\n pressedTone, // 2026-06-05 fix(deep-audit P0):原漏 destructure → 落入 ...props 噴到 DOM + 永遠用 cva default\n children,\n disabled,\n ...props\n },\n ref\n ) => {\n // ── FieldContext:在 Field 內自動讀 size,讓 Button 跟 Input 同高(SSOT:useResolvedFieldSize)──\n const resolvedSize = useResolvedFieldSize(size, 'md')\n\n // ── Dismiss 視覺類 override(2026-04-22 cross-implementation dimming canonical) ──\n // dismiss=true 強制:variant=\"text\" + iconOnly=true + icon 色弱化(fg-muted → hover foreground)\n // 跟 Inline Action dismiss 視覺一致。詳見 button.spec.md「Dismiss 視覺類」。\n const resolvedIconOnly = iconOnly || dismiss\n\n // ── Dev-mode warning:overlayBadge 只適用 iconOnly ──\n // 有 label 的 Button 傳入 overlayBadge 會被忽略(只 render icon / 不渲染 overlay slot),\n // 靜默忽略會讓 consumer 誤以為「傳了但位置錯」。Dev mode 印 warning 引導改用 `badge` prop\n // (inline 位置,跟 label 並列)或改 `iconOnly`。Spec SSOT:badge.spec.md「Overlay 適用元件」。\n if (process.env.NODE_ENV !== 'production' && overlayBadge && !iconOnly) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS Button] `overlayBadge` 只適用於 `iconOnly` Button。有 label 的 Button 請改用 `badge` prop(inline 位置,跟 label 並列),或移除 label 改為 iconOnly。SSOT:badge.spec.md「Overlay 適用元件 canonical」節。'\n )\n }\n\n // 2026-05-23 deep-audit Phase A.4 Decision 1(user verbatim「決策ㄧ照你建議做」):dev-warn `iconOnly + (endIcon|badge)` 並用。\n // Spec SSOT:button.spec.md「iconOnly 嚴格定義為『只有一個 icon,正方形』,不可與 endIcon 或 badge 並用」。\n // 例外:overlayBadge 跟 iconOnly 並用 canonical(上方 overlayBadge 反向 warn 已 cover;相對指法,免行號漂移)。\n // 不擋 image canonical「icon + 下拉指示 = 不加 iconOnly + startIcon + endIcon + aria-label」(那 case iconOnly=false 不 trigger)。\n if (process.env.NODE_ENV !== 'production' && iconOnly && (EndIcon !== undefined || badge != null)) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS Button] `iconOnly` 嚴格定義為「只有一個 icon,正方形」,不可與 `endIcon` 或 `badge` 並用。若需 icon + 下拉指示 → 不加 iconOnly + startIcon + endIcon + aria-label;若需 icon + 角標 → iconOnly + overlayBadge。SSOT:button.spec.md `iconOnly 的邊界` 節 + `badge.spec.md` Overlay 適用元件 canonical。'\n )\n }\n\n // shadcn compat:AlertDialog、Toast 等元件內部會傳入這些 alias,\n // 在此靜默轉換,不暴露到型別或自動完成。\n // dismiss=true 強制 variant=text(dismiss canonical);override 其他 variant 傳入。\n // 2026-06-06 預設 emphasis 改低(對齊世界級:MUI 預設 text / Ant 預設 default / Polaris 低 emphasis —\n // 預設 primary CTA 是 outlier)。labeled 無 variant → `tertiary`(中性外框,清楚可點性,再按重要程度升 primary);\n // iconOnly 無 variant → `text`(toolbar ghost,對齊 action-bar.spec.md「純動作工具/低重量輔助 → text」+\n // Material 3「icon-only = no fill」)。**CTA 必 explicit `variant=\"primary\"`**(不靠預設)。dismiss 仍強制 text。\n const resolvedVariant: InternalVariant =\n dismiss ? 'text' :\n (variantProp as string) === 'destructive' ? 'primary' :\n (variantProp as string) === 'ghost' ? 'text' :\n (variantProp as InternalVariant) ?? (iconOnly ? 'text' : 'tertiary')\n\n const resolvedDanger = dangerProp || (variantProp as string) === 'destructive'\n\n // ButtonGroup context:vertical group 自動注入 fullWidth\n const groupCtx = React.useContext(ButtonGroupContext)\n const resolvedFullWidth = fullWidth || !!groupCtx.fullWidth\n\n const Comp = asChild ? Slot : 'button'\n const iconSize = resolvedSize === 'lg' ? 20 : 16\n\n // loading 行為:spinner 永遠在 prefix 位置\n // 有 prefix icon → icon 換成 spinner(同位置,零 layout shift)\n // 無 prefix icon → spinner 加在文字左邊(按鈕略微變寬,可接受)\n const hasSuffix = badge != null || EndIcon !== undefined\n\n // icon-only 自動 tooltip:從 props 提取 aria-label,同時保留在 DOM\n const { 'aria-label': ariaLabel, ...restProps } = props\n\n // Toggle 狀態:pressed 定義時自動寫入 aria-pressed + data-state。\n // 未定義時不寫入任何 toggle 屬性(按鈕為一般 action button)。\n // 樣式由 cva data-[state=on] compoundVariants(variant × pressedTone)套用——\n // secondary/tertiary/text 預設 pressedTone='emphasis' 走 primary-subtle,\n // pressedTone='neutral' 才走 neutral-selected family;primary/link 不定義 on 分支,傳入無效果。\n const toggleAttrs =\n pressed === undefined\n ? {}\n : { 'aria-pressed': pressed, 'data-state': pressed ? 'on' : 'off' }\n\n // Chrome-unbounded marker(2026-04-22 v5 canonical):button 若無視覺邊界(text variant 或 dismiss),\n // 標記 data-unbounded=\"true\"。SurfaceHeader 透過 [&_[data-unbounded]]:my-[...] 套負 margin\n // 讓 layout 佔位縮到 24(chrome-header-height 幾何)— button native size 與 touch target 不變。\n // 詳 overlay-surface.spec.md「Chrome dismiss size canonical」\n const unboundedAttr =\n resolvedVariant === 'text' || dismiss ? { 'data-unbounded': 'true' } : {}\n\n // Native path 內部結構:[loading spinner | startIcon(+overlayBadge)] [label] [badge + endIcon]。\n // asChild 時**不渲染**(2026-07-04 fix):Radix Slot 規範 children 必為單一 element\n // (React.Children.only)— 內部多 expression children 變 array 必 runtime throw;\n // asChild 分支只 render consumer child,內容由 consumer 自帶。\n // 詳 CLAUDE.md 失敗記憶索引「asChild ? Slot : Native 內部 JSX 仍渲染多 children」條。\n const nativeChildren = (\n <>\n {loading ? (\n <CircularProgress size={iconSize} className=\"text-current\" />\n ) : StartIcon ? (\n resolvedIconOnly && overlayBadge ? (\n // Overlay badge canonical:wrapper 貼 icon 尺寸,badge 中心對齊 icon top-right corner\n // (Material BadgedBox / iOS App icon),不是 button chrome 角。\n //\n // CSS 細節(2026-04-20 bug fix):用 `inline-block` + 明確 width/height + `leading-none`\n // 避免 `inline-flex` span 在 Button flex container 內被撐高/撐寬(inline-flex 的 span\n // 在某些瀏覽器下會把絕對定位子元素的 translate 計算基準搞錯,造成 badge 噴飛、Button\n // aspect-square 失效)。明確給 span width/height = iconSize 鎖住 positioning context。\n <span\n className=\"relative inline-block leading-none shrink-0 pointer-events-none\"\n style={{ width: iconSize, height: iconSize }}\n >\n <StartIcon size={iconSize} aria-hidden />\n <span className=\"absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 pointer-events-auto\">\n {overlayBadge}\n </span>\n </span>\n ) : (\n <StartIcon size={iconSize} aria-hidden />\n )\n ) : null}\n {children != null && <span className=\"px-1\">{children}</span>}\n {hasSuffix && (\n <span className=\"inline-flex items-center gap-1\">\n {badge}\n {EndIcon && <EndIcon size={iconSize} aria-hidden />}\n </span>\n )}\n </>\n )\n\n const sharedClassName = cn(\n buttonVariants({ variant: resolvedVariant, danger: resolvedDanger, size: resolvedSize, pressedTone, className }),\n // iconOnly 鐵律:padding-free + aspect-square + flex-center (Polaris idiom)\n // 0 magic-number 0 公式自動正方形。詳 ICON_ONLY_BASE rationale。\n resolvedIconOnly && ICON_ONLY_BASE,\n // Dismiss 視覺弱化:override Button text variant 預設 foreground 為 fg-muted → hover foreground\n // 跟 Inline Action dismiss 視覺一致(cross-implementation dimming canonical)\n dismiss && 'text-fg-muted hover:text-foreground',\n resolvedFullWidth && 'w-full',\n )\n\n // 2026-07-05 D4 修:asChild 原 silently drop loading/disabled 語意(無 aria-busy/aria-disabled/\n // click guard → <a> loading 期間可重複點擊)。補 ARIA(任何 element 合法;aria-disabled 自動點亮\n // cva 既有視覺)+ functional guard(APG aria-disabled 模式:語意+視覺由元件、功能阻斷由 guard)。\n const { onClick: slotConsumerOnClick, ...slotRest } = restProps as typeof restProps & { onClick?: React.MouseEventHandler<HTMLElement> }\n const buttonEl = asChild ? (\n // Slot 分支:children 必為單一 consumer element(React.Children.only),\n // 內部 slot(loading/startIcon/badge)不渲染;不硬塞 type/disabled(consumer child 可能是 <a>)。\n <Comp\n className={sharedClassName}\n ref={ref}\n aria-label={ariaLabel}\n aria-busy={loading || undefined}\n aria-disabled={disabled || loading || undefined}\n onClick={disabled || loading\n ? (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); e.stopPropagation() }\n : slotConsumerOnClick}\n {...toggleAttrs}\n {...unboundedAttr}\n {...slotRest}\n >\n {children}\n </Comp>\n ) : (\n <Comp\n className={sharedClassName}\n ref={ref}\n type=\"button\"\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n aria-label={ariaLabel}\n {...toggleAttrs}\n {...unboundedAttr}\n {...restProps}\n >\n {nativeChildren}\n </Comp>\n )\n\n // icon-only + aria-label → 自動包 Tooltip(tooltip 是元件保證的行為)\n // 不建立獨立 TooltipProvider——依賴全域 Provider,\n // 這樣所有 tooltip 共享同一組 delay 參數和 warm-up 機制\n if (resolvedIconOnly && typeof ariaLabel === 'string' && !asChild) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{buttonEl}</TooltipTrigger>\n <TooltipContent>{ariaLabel}</TooltipContent>\n </Tooltip>\n )\n }\n\n return buttonEl\n }\n)\nButton.displayName = 'Button'\n\n/**\n * componentMeta — Story Auto-Compile 系統消費的結構化 canonical\n * (見 .claude/planning/story-auto-compile.md Phase 1)\n *\n * compile-stories.mjs 讀本 export + spec.md frontmatter 產出\n * anatomy.stories.tsx 的 variant/size/state/token 矩陣 canonical section。\n *\n * Keys 必跟 buttonVariants cva + spec frontmatter 對齊(compile-time 驗證)。\n */\nexport const buttonMeta = {\n component: 'Button',\n family: 3, // Pill Layout\n variants: {\n primary: { purpose: '主要 action / CTA' },\n secondary: { purpose: '次要 action(陪襯 primary)' },\n tertiary: { purpose: '第三級 action(tool-like)' },\n text: { purpose: '文字樣式 action(low emphasis / toolbar)' },\n link: { purpose: '內文連結(inline reading)' },\n },\n sizes: {\n xs: { fieldHeight: 24, iconSize: 16, typography: 'caption' },\n sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },\n },\n // 'pressed' = toggle on 持續態(data-[state=on],emphasis 淡藍底/neutral 灰底;2026-07-07 meta 詞彙統一補列)\n states: ['default', 'hover', 'active', 'pressed', 'focus-visible', 'disabled'],\n tokens: {\n // 2026-07-04 audit 補齊:原只列 primary variant 家族;按 buttonVariants cva 真實 class 補\n // secondary/tertiary/text/link/danger 消費的 token(auto-compile token 矩陣依此)\n bg: ['--primary', '--primary-hover', '--primary-active', '--primary-subtle', '--bg-disabled', '--surface', '--neutral-hover', '--neutral-active', '--neutral-selected'],\n fg: ['--on-emphasis', '--fg-disabled', '--foreground', '--primary', '--error', '--error-hover', '--error-active'],\n ring: ['--ring'],\n },\n defaultVariant: 'tertiary', // 2026-06-10 修 stale:對齊 cva defaultVariants(2026-06-06 labeled 預設改 tertiary,meta 漏同步)\n defaultSize: 'md',\n} as const\n\nexport { Button, buttonVariants, ButtonGroupContext }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.js","sources":["../../../src/components/Button/button.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.\n// code-quality-allow: file-size — foundational composite(Button + iconOnly + danger + loading + asChild + dismiss + pressedTone)— 跨 7 axis variant 集中 SSOT 一處,拆分會 fragment cva variant catalog。當前 528 < cap 800。\nimport * as React from 'react'\nimport { Slot } from '@radix-ui/react-slot'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { useResolvedFieldSize } from '@/design-system/components/Field/field-context'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'\n\n/**\n * Button — shadcn 風格,橋接設計系統 token\n *\n * ── Variants ──\n * primary 主要操作,藍底白字\n * secondary 次要品牌操作,藍框藍字;正面 vs 負面並存時用於正面那個\n * tertiary 一般輔助操作,灰框灰字,hover 轉藍(最常用的非主要按鈕)\n * text 無底色無邊框,hover 顯示灰底(工具列、密集 UI)\n * link 外觀像連結的按鈕(本質仍是 button)\n *\n * ── danger prop ──\n * danger 套用危險色(紅色);僅 primary / secondary / text 支援(tertiary / link 不支援,見 spec 禁止事項)\n *\n * <Button variant=\"primary\" danger>永久刪除</Button> → 紅底白字(立即不可逆)\n * <Button variant=\"secondary\" danger>移至垃圾桶</Button> → 紅框紅字(點下去還可反悔)\n *\n * ── pressed prop(toggle)──\n * pressed Toggle 按下狀態(持續 on/off),寫入 aria-pressed + data-state\n * 僅 secondary / tertiary / text 三個 variant 支援 toggle 視覺:\n * - secondary + pressed → primary-subtle 底、primary 字、透明邊框\n * - tertiary + pressed → primary-subtle 底、primary 字、透明邊框(同 secondary 按下視覺)\n * - text + pressed → primary-subtle 底、primary 字、透明邊框(預設 pressedTone='emphasis',同 secondary/tertiary 按下視覺);pressedTone='neutral' 時才走 neutral-selected 灰底\n * primary / link 傳入 pressed 無視覺效果(語意不符)\n *\n * ── Sizes(預設 md)──\n * xs h-field-xs(24px 固定),不隨 density 縮放\n * sm h-field-sm,md=28px / lg=32px\n * md h-field-md,md=32px / lg=36px ← 預設(跟 Field/Input 對齊)\n * lg h-field-lg,md=36px / lg=40px\n * icon-only 不是獨立尺寸 — 加 iconOnly prop 讓任何尺寸變正方形\n *\n * ── 內部結構 ──\n * [startIcon?] [label] [badge? + endIcon?]\n *\n * ── 用法範例 ──\n * <Button startIcon={Plus}>新增</Button>\n * <Button variant=\"tertiary\">取消</Button>\n * <Button variant=\"primary\" danger>永久刪除</Button>\n * <Button variant=\"text\" pressed={isPinned} startIcon={Pin} aria-label=\"釘選\" iconOnly />\n * <Button badge={<Badge count={3} />} endIcon={ChevronDown}>通知</Button>\n * <Button size=\"sm\" iconOnly startIcon={Plus} aria-label=\"新增\" />\n * <Button iconOnly startIcon={Bell} aria-label=\"通知 (3 則)\"\n * overlayBadge={<Badge count={3} />} /> ← badge 自動貼 icon 右上角\n *\n * ── asChild ──\n * <Button asChild><Link to=\"/home\">回首頁</Link></Button>\n */\nconst buttonVariants = cva(\n [\n 'inline-flex items-center justify-center',\n 'whitespace-nowrap font-medium',\n 'border border-transparent',\n 'transition-colors duration-150',\n 'cursor-pointer select-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n // 2026-05-12 Round 4.5 fix(codex M31 Layer C 抓):`aria-disabled` visual 分支補(per WAI-ARIA APG —\n // aria-disabled 給語意 + visual,但不 suppress functionality;functionality 由 consumer 阻 e.g.\n // RowDragHandle listeners 只在 canDrag spread)。**故意不加** `aria-disabled:pointer-events-none`\n // — Round 4 RowDragHandle Tooltip flicker fix root cause:aria-disabled buttons 必保 pointer events\n // 讓 Radix Tooltip pointerenter 通過。HTML `disabled` 保 PE:none(完全 inactive)。\n 'disabled:pointer-events-none',\n // 2026-05-12 fix v2(playwright pixel-quantified verify 抓 opacity=1 不生效):\n // `opacity-disabled` 是 custom Tailwind v4 `@utility`(opacity.css:21),variant prefix\n // `aria-disabled:` 跟 custom @utility 不 compose(`aria-disabled:cursor-not-allowed` 標準\n // utility 能 work,custom 不行)。改 arbitrary value `opacity-[var(--opacity-disabled)]`\n // 繞 custom @utility 限制(Tailwind v4 arbitrary value 直接生 `opacity: var(--opacity-disabled)`)。\n 'aria-disabled:opacity-[var(--opacity-disabled)]',\n 'rounded-md',\n // Defensive:SVG 不被 flex shrink 擠扁(防 inner-area 計算誤差導致 icon 被擠成\n // width<intrinsic 的 asymmetric 顯示)。詳 ICON_ONLY_PX 段 rationale。\n '[&_svg]:shrink-0',\n ],\n {\n variants: {\n variant: {\n primary: [\n 'bg-primary text-on-emphasis',\n 'hover:bg-primary-hover',\n 'active:bg-primary-active',\n 'disabled:bg-disabled disabled:text-fg-disabled disabled:border-transparent',\n ],\n secondary: [\n 'bg-surface text-primary border-primary',\n 'hover:text-primary-hover hover:border-primary-hover',\n 'active:text-primary-active active:border-primary-active',\n // Overlay trigger active(asChild Popover/DropdownMenu trigger)— 維持 hover 樣式\n // (canonical 對齊 inline-action.spec.md:trigger 維持 host hover 直到 overlay 關閉)\n 'data-[state=open]:text-primary-hover data-[state=open]:border-primary-hover',\n 'disabled:bg-transparent disabled:text-fg-disabled disabled:border-border',\n // 2026-05-21 v12:Toggle pressed 視覺移到 compoundVariants(variant × pressedTone),\n // 同時支援 emphasis(藍底)/ neutral(灰底)兩 tone。詳 cva.compoundVariants 段。\n ],\n tertiary: [\n 'bg-surface text-foreground border-border',\n 'hover:text-primary-hover hover:border-primary-hover',\n 'active:text-primary-active active:border-primary-active',\n // Overlay trigger active — 維持 hover 樣式(同 secondary 邏輯)\n 'data-[state=open]:text-primary-hover data-[state=open]:border-primary-hover',\n 'disabled:bg-transparent disabled:text-fg-disabled disabled:border-border',\n ],\n text: [\n 'bg-transparent text-foreground border-transparent',\n 'hover:bg-neutral-hover',\n 'active:bg-neutral-active',\n // Overlay trigger active — 維持 hover 樣式(canonical 2026-05-02 改:hover token,\n // 不另開 selected 4% — 對齊 shadcn/Radix/Material 狀態極簡派,跨 host 一致)\n 'data-[state=open]:bg-neutral-hover',\n 'disabled:bg-transparent disabled:text-fg-disabled',\n ],\n link: [\n 'bg-transparent text-primary border-transparent',\n 'hover:text-primary-hover',\n 'active:text-primary-active',\n 'disabled:text-fg-disabled',\n ],\n },\n danger: {\n true: '', // 實際樣式由 compoundVariants 提供\n },\n /**\n * 2026-05-21 v12 — pressed visual tone(per user「我認同這一個方向,然後預設emphasis」):\n * emphasis = 淡藍底(toolbar functional toggle / 篩選啟用 / 面板開關 — Figma toolbar /\n * Linear toolbar / Material ToggleButton 共識)\n * neutral = 灰底(sidebar/contextual nav row pressed — Linear / Notion / VS Code Activity Bar 共識)\n * 預設 `emphasis` per user directive。實際樣式由 compoundVariants(variant × pressedTone)套用。\n * 只在 secondary / tertiary / text variant 觸發 toggle 視覺;primary / link 無視覺效果。\n */\n pressedTone: {\n emphasis: '',\n neutral: '',\n },\n size: {\n xs: 'h-field-xs px-2 text-caption leading-compact gap-0',\n sm: 'h-field-sm px-3 min-w-14 text-body leading-compact gap-1',\n md: 'h-field-md px-3 min-w-16 text-body leading-compact gap-1',\n lg: 'h-field-lg px-3 min-w-20 text-body-lg leading-compact gap-1',\n },\n },\n compoundVariants: [\n // primary + danger → 紅底白字(立即不可逆操作)\n {\n variant: 'primary',\n danger: true,\n class: [\n 'bg-error text-on-emphasis border-transparent',\n 'hover:bg-error-hover',\n 'active:bg-error-active',\n ],\n },\n // secondary + danger → 紅框紅字(有確認步驟的危險操作)\n {\n variant: 'secondary',\n danger: true,\n class: [\n 'bg-surface text-error border-error',\n 'hover:text-error-hover hover:border-error-hover',\n 'active:text-error-active active:border-error-active',\n ],\n },\n // text + danger → 紅字,hover 灰底\n {\n variant: 'text',\n danger: true,\n class: [\n 'text-error',\n 'hover:bg-neutral-hover hover:text-error-hover',\n 'active:bg-neutral-active active:text-error-active',\n ],\n },\n // ── 2026-05-21 v12 Toggle pressed(variant × pressedTone)──────────────────\n // 視覺由 data-[state=on] + aria-pressed(Radix overlay trigger fallback)觸發\n //\n // emphasis tone:藍底(primary-subtle / primary 字)— functional toggle\n {\n variant: ['secondary', 'tertiary', 'text'],\n pressedTone: 'emphasis',\n class: [\n 'data-[state=on]:bg-primary-subtle data-[state=on]:text-primary data-[state=on]:border-transparent',\n 'data-[state=on]:hover:text-primary-hover',\n 'data-[state=on]:active:text-primary-active',\n 'data-[state=on]:disabled:bg-disabled data-[state=on]:disabled:text-fg-disabled data-[state=on]:disabled:border-transparent',\n // aria-pressed fallback(Radix overlay trigger override data-state 時仍生效)\n 'aria-pressed:bg-primary-subtle aria-pressed:text-primary aria-pressed:border-transparent',\n 'aria-pressed:hover:text-primary-hover',\n // 2026-07-07 補齊第三階:fallback 分支與 data-[state=on] 分支同拼寫(選中之上按壓 = active 階)\n 'aria-pressed:active:text-primary-active',\n ],\n },\n // neutral tone:灰底(neutral-selected family)— sidebar/contextual nav pressed\n {\n variant: ['secondary', 'tertiary', 'text'],\n pressedTone: 'neutral',\n class: [\n 'data-[state=on]:bg-neutral-selected data-[state=on]:text-foreground data-[state=on]:border-transparent',\n 'data-[state=on]:hover:bg-neutral-selected-hover',\n 'data-[state=on]:active:bg-neutral-selected-active',\n 'data-[state=on]:disabled:bg-transparent data-[state=on]:disabled:text-fg-disabled',\n // aria-pressed fallback\n 'aria-pressed:bg-neutral-selected aria-pressed:text-foreground aria-pressed:border-transparent',\n 'aria-pressed:hover:bg-neutral-selected-hover',\n ],\n },\n ],\n defaultVariants: {\n // labeled 預設 = tertiary(2026-06-06 從 primary 改;iconOnly 預設 = text,由 resolvedVariant 注入)。\n // 真正預設邏輯在元件內 resolvedVariant(相對指法,免行號漂移);此 cva default 供直接 buttonVariants() 呼叫者一致。\n variant: 'tertiary',\n size: 'md',\n pressedTone: 'emphasis',\n },\n }\n)\n\n// ── ButtonGroup Context ──────────────────────────────────────────────────────\n// ButtonGroup 提供此 context;Button 讀取它注入 fullWidth。\n// Context 放本檔(不放 button-group.tsx)以避免循環 import。\ninterface ButtonGroupContextValue {\n fullWidth?: boolean\n}\nconst ButtonGroupContext = React.createContext<ButtonGroupContextValue>({})\n\ntype InternalVariant = VariantProps<typeof buttonVariants>['variant']\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n Omit<VariantProps<typeof buttonVariants>, 'variant' | 'danger'> {\n /**\n * 將樣式套用至子元件(e.g. React Router Link)。\n *\n * ⚠️ **icon-only + asChild 警告**:當 `iconOnly={true}` 且 `asChild={true}` 時,內建\n * Tooltip wrapper 不啟動(Radix Slot 不接受多 child)。consumer 必須**自管 child 的 `aria-label`**\n * 給 screen reader,並視需要自行包 `<Tooltip>`(對齊 Polaris / Radix asChild idiom)。\n * Button 不會主動補 tooltip — 否則會破壞 Slot 單 child 規則。\n */\n asChild?: boolean\n /**\n * 按鈕視覺強調等級。\n * `destructive` / `ghost` 為 shadcn 內部 compat,請勿在應用程式碼中直接使用。\n */\n variant?: 'primary' | 'secondary' | 'tertiary' | 'text' | 'link'\n /** 套用危險色(紅色)。僅 primary / secondary / text 支援(tertiary / link 不支援,見 spec 禁止事項)。 */\n danger?: boolean\n /**\n * Toggle 按下狀態(持續 on/off)。設定時 Button 變為 toggle:\n * - 自動寫入 `aria-pressed` + `data-state=\"on\" | \"off\"`\n * - 樣式由 variant × `pressedTone` 的 compoundVariants 套用\n * - 僅 secondary / tertiary / text 有 toggle 視覺;primary / link 傳入無效果\n *\n * 不傳此 prop 時 Button 就是一般按鈕,不帶 aria-pressed。\n */\n pressed?: boolean\n /**\n * Pressed 視覺色調(2026-05-21 v12 加):\n * - `'emphasis'`(預設)→ 淡藍底 / primary 字(對齊 Figma toolbar / Linear toolbar /\n * Material ToggleButton 共識 — toolbar functional toggle / 篩選啟用 / 面板開關)\n * - `'neutral'` → 灰底 / foreground 字(對齊 Linear / Notion / VS Code Activity Bar\n * 共識 — sidebar/contextual nav row pressed)\n *\n * 僅在 `pressed` 啟用且 variant ∈ {secondary, tertiary, text} 時生效。\n * 跨 toggle context 維持單一 prop API,consumer 視語意選 tone 不另開 variant。\n */\n pressedTone?: 'emphasis' | 'neutral'\n /** 左側 icon(LucideIcon),最多一個,loading 時自動替換為 spinner */\n startIcon?: LucideIcon\n /** 右側 badge(ReactNode),通常傳入計數指示器 */\n badge?: React.ReactNode\n /**\n * Overlay badge(iconOnly 專用)。接收 `<Badge>` 元素,Button 內部**自動定位在 startIcon 右上角**——\n * badge 中心對齊 icon 的 top-right corner(Material BadgedBox / iOS App icon canonical),不是按鈕邊緣。\n * 解決手刻 `relative + absolute -top-1 -right-1` 讓 badge 飄在按鈕 chrome 右上的問題。\n *\n * 世界級對照:Material BadgedBox、iOS App Icon、Ant Badge wrap icon,badge 相對於**視覺重心**(icon)。\n * 只在 `iconOnly=true` 時生效;非 iconOnly 時應該用 inline `badge` prop 放 suffix 位置。\n */\n overlayBadge?: React.ReactNode\n /** 右側 icon(LucideIcon),放在 badge 右邊,通常用於 ChevronDown 等方向指示 */\n endIcon?: LucideIcon\n /** Icon-only 模式:移除 padding,變為正方形(必須同時設定 aria-label) */\n iconOnly?: boolean\n /**\n * Dismiss 視覺類(X close only canonical)。專用於 **X(close)icon 的 dismiss 語意** —\n * 「關閉 surface / 忽略訊息」。**不適用 Trash / Delete / Clear / Remove 等 destructive / clear 操作**。\n *\n * 自動套用:\n * - `variant=\"text\"`(強制 override 其他 variant)\n * - `iconOnly=true`(強制)\n * - Icon 色 override:`fg-muted` → hover `foreground`(跟 Inline Action dismiss 視覺一致)\n *\n * 典型 case:Dialog / Sheet / Popover / Alert / Toast / Coachmark 的 **chrome corner close X**\n * (action group region — corner 可多 action,close 左側加 Separator + refresh / share 等)。\n *\n * 非 dismiss(**不套此 prop**):\n * - Trash / Delete → destructive action,Button 用一般 variant 或 Inline Action(按 row size 判)\n * - Clear → 欄位清空,用 Inline Action\n * - Remove → collection 移除,用一般 Button / Inline Action\n *\n * 詳見 button.spec.md「Dismiss 視覺類」段 + patterns/element-anatomy/inline-action.spec.md\n * 「Dismiss canonical — X close only」段。\n */\n dismiss?: boolean\n /** 載入中狀態:startIcon 替換為 spinner,自動 disabled;badge / endIcon 維持顯示以避免 layout shift */\n loading?: boolean\n /** 撐滿父容器寬度 */\n fullWidth?: boolean\n}\n\n// IconOnly 用 padding-free + aspect-square + flex-center 的 Polaris/Atlassian idiom\n// (M17 SSOT 必可傳播 — 取代 4 個 size 的 magic-number 公式):\n// - aspect-square 鎖 width=height(來自 h-field-X)\n// - p-0 移除 px-3 (label 模式) override\n// - flex justify-center items-center(base 已有)→ SVG 自動視覺置中\n// 結果:0 magic number,0 公式,0 border-deduction,任何 size / icon size 都自然正方形。\n// World-class 對照:Polaris + Atlassian iconOnly 走 padding-free 派(Material/Ant 走\n// padding-based)。我們選 padding-free 因 SSOT 性更強(SegmentedControl / Tag dismiss\n// 等 host 全可共用同 utility class,無需各自抄公式)。詳 button.spec.md「iconOnly 鐵律」。\nconst ICON_ONLY_BASE = 'aspect-square p-0 min-w-0 gap-0'\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n className,\n variant: variantProp,\n danger: dangerProp,\n size,\n asChild = false,\n startIcon: StartIcon,\n badge,\n overlayBadge,\n endIcon: EndIcon,\n iconOnly = false,\n dismiss = false,\n loading = false,\n fullWidth = false,\n pressed,\n pressedTone, // 2026-06-05 fix(deep-audit P0):原漏 destructure → 落入 ...props 噴到 DOM + 永遠用 cva default\n children,\n disabled,\n ...props\n },\n ref\n ) => {\n // ── FieldContext:在 Field 內自動讀 size,讓 Button 跟 Input 同高(SSOT:useResolvedFieldSize)──\n const resolvedSize = useResolvedFieldSize(size, 'md')\n\n // ── Dismiss 視覺類 override(2026-04-22 cross-implementation dimming canonical) ──\n // dismiss=true 強制:variant=\"text\" + iconOnly=true + icon 色弱化(fg-muted → hover foreground)\n // 跟 Inline Action dismiss 視覺一致。詳見 button.spec.md「Dismiss 視覺類」。\n const resolvedIconOnly = iconOnly || dismiss\n\n // ── Dev-mode warning:overlayBadge 只適用 iconOnly ──\n // 有 label 的 Button 傳入 overlayBadge 會被忽略(只 render icon / 不渲染 overlay slot),\n // 靜默忽略會讓 consumer 誤以為「傳了但位置錯」。Dev mode 印 warning 引導改用 `badge` prop\n // (inline 位置,跟 label 並列)或改 `iconOnly`。Spec SSOT:badge.spec.md「Overlay 適用元件」。\n if (process.env.NODE_ENV !== 'production' && overlayBadge && !iconOnly) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS Button] `overlayBadge` 只適用於 `iconOnly` Button。有 label 的 Button 請改用 `badge` prop(inline 位置,跟 label 並列),或移除 label 改為 iconOnly。SSOT:badge.spec.md「Overlay 適用元件 canonical」節。'\n )\n }\n\n // 2026-05-23 deep-audit Phase A.4 Decision 1(user verbatim「決策ㄧ照你建議做」):dev-warn `iconOnly + (endIcon|badge)` 並用。\n // Spec SSOT:button.spec.md「iconOnly 嚴格定義為『只有一個 icon,正方形』,不可與 endIcon 或 badge 並用」。\n // 例外:overlayBadge 跟 iconOnly 並用 canonical(上方 overlayBadge 反向 warn 已 cover;相對指法,免行號漂移)。\n // 不擋 image canonical「icon + 下拉指示 = 不加 iconOnly + startIcon + endIcon + aria-label」(那 case iconOnly=false 不 trigger)。\n if (process.env.NODE_ENV !== 'production' && iconOnly && (EndIcon !== undefined || badge != null)) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS Button] `iconOnly` 嚴格定義為「只有一個 icon,正方形」,不可與 `endIcon` 或 `badge` 並用。若需 icon + 下拉指示 → 不加 iconOnly + startIcon + endIcon + aria-label;若需 icon + 角標 → iconOnly + overlayBadge。SSOT:button.spec.md `iconOnly 的邊界` 節 + `badge.spec.md` Overlay 適用元件 canonical。'\n )\n }\n\n // shadcn compat:AlertDialog、Toast 等元件內部會傳入這些 alias,\n // 在此靜默轉換,不暴露到型別或自動完成。\n // dismiss=true 強制 variant=text(dismiss canonical);override 其他 variant 傳入。\n // 2026-06-06 預設 emphasis 改低(對齊世界級:MUI 預設 text / Ant 預設 default / Polaris 低 emphasis —\n // 預設 primary CTA 是 outlier)。labeled 無 variant → `tertiary`(中性外框,清楚可點性,再按重要程度升 primary);\n // iconOnly 無 variant → `text`(toolbar ghost,對齊 action-bar.spec.md「純動作工具/低重量輔助 → text」+\n // Material 3「icon-only = no fill」)。**CTA 必 explicit `variant=\"primary\"`**(不靠預設)。dismiss 仍強制 text。\n const resolvedVariant: InternalVariant =\n dismiss ? 'text' :\n (variantProp as string) === 'destructive' ? 'primary' :\n (variantProp as string) === 'ghost' ? 'text' :\n (variantProp as InternalVariant) ?? (iconOnly ? 'text' : 'tertiary')\n\n const resolvedDanger = dangerProp || (variantProp as string) === 'destructive'\n\n // Dev-mode warning:danger 僅 primary / secondary / text 有 compoundVariant。\n // tertiary / link + danger 無匹配 compound → **靜默渲染成一般 tertiary / link**(灰、無紅色\n // danger 視覺)— consumer 可能出「不紅的刪除鍵」而不自知。Spec SSOT:button.spec.md\n // 「禁止事項」danger 條(2026-07-14 deep-audit 補 warn,對齊既有 overlayBadge / iconOnly warn pattern)。\n if (process.env.NODE_ENV !== 'production' && resolvedDanger && (resolvedVariant === 'tertiary' || resolvedVariant === 'link')) {\n // eslint-disable-next-line no-console\n console.warn(\n `[DS Button] \\`danger\\` 不支援 variant=\"${resolvedVariant}\"(無對應 compoundVariant,會靜默渲染成一般 ${resolvedVariant},無紅色 danger 視覺)。危險操作請改用 primary(立即不可逆)/ secondary(可反悔)/ text(低度)。SSOT:button.spec.md「禁止事項」danger 條。`\n )\n }\n\n // ButtonGroup context:vertical group 自動注入 fullWidth\n const groupCtx = React.useContext(ButtonGroupContext)\n const resolvedFullWidth = fullWidth || !!groupCtx.fullWidth\n\n const Comp = asChild ? Slot : 'button'\n const iconSize = resolvedSize === 'lg' ? 20 : 16\n\n // loading 行為:spinner 永遠在 prefix 位置\n // 有 prefix icon → icon 換成 spinner(同位置,零 layout shift)\n // 無 prefix icon → spinner 加在文字左邊(按鈕略微變寬,可接受)\n const hasSuffix = badge != null || EndIcon !== undefined\n\n // icon-only 自動 tooltip:從 props 提取 aria-label,同時保留在 DOM\n const { 'aria-label': ariaLabel, ...restProps } = props\n\n // Toggle 狀態:pressed 定義時自動寫入 aria-pressed + data-state。\n // 未定義時不寫入任何 toggle 屬性(按鈕為一般 action button)。\n // 樣式由 cva data-[state=on] compoundVariants(variant × pressedTone)套用——\n // secondary/tertiary/text 預設 pressedTone='emphasis' 走 primary-subtle,\n // pressedTone='neutral' 才走 neutral-selected family;primary/link 不定義 on 分支,傳入無效果。\n const toggleAttrs =\n pressed === undefined\n ? {}\n : { 'aria-pressed': pressed, 'data-state': pressed ? 'on' : 'off' }\n\n // Chrome-unbounded marker(2026-04-22 v5 canonical):button 若無視覺邊界(text variant 或 dismiss),\n // 標記 data-unbounded=\"true\"。SurfaceHeader 透過 [&_[data-unbounded]]:my-[...] 套負 margin\n // 讓 layout 佔位縮到 24(chrome-header-height 幾何)— button native size 與 touch target 不變。\n // 詳 overlay-surface.spec.md「Chrome dismiss size canonical」\n const unboundedAttr =\n resolvedVariant === 'text' || dismiss ? { 'data-unbounded': 'true' } : {}\n\n // Native path 內部結構:[loading spinner | startIcon(+overlayBadge)] [label] [badge + endIcon]。\n // asChild 時**不渲染**(2026-07-04 fix):Radix Slot 規範 children 必為單一 element\n // (React.Children.only)— 內部多 expression children 變 array 必 runtime throw;\n // asChild 分支只 render consumer child,內容由 consumer 自帶。\n // 詳 CLAUDE.md 失敗記憶索引「asChild ? Slot : Native 內部 JSX 仍渲染多 children」條。\n const nativeChildren = (\n <>\n {loading ? (\n <CircularProgress size={iconSize} className=\"text-current\" />\n ) : StartIcon ? (\n resolvedIconOnly && overlayBadge ? (\n // Overlay badge canonical:wrapper 貼 icon 尺寸,badge 中心對齊 icon top-right corner\n // (Material BadgedBox / iOS App icon),不是 button chrome 角。\n //\n // CSS 細節(2026-04-20 bug fix):用 `inline-block` + 明確 width/height + `leading-none`\n // 避免 `inline-flex` span 在 Button flex container 內被撐高/撐寬(inline-flex 的 span\n // 在某些瀏覽器下會把絕對定位子元素的 translate 計算基準搞錯,造成 badge 噴飛、Button\n // aspect-square 失效)。明確給 span width/height = iconSize 鎖住 positioning context。\n <span\n className=\"relative inline-block leading-none shrink-0 pointer-events-none\"\n style={{ width: iconSize, height: iconSize }}\n >\n <StartIcon size={iconSize} aria-hidden />\n <span className=\"absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 pointer-events-auto\">\n {overlayBadge}\n </span>\n </span>\n ) : (\n <StartIcon size={iconSize} aria-hidden />\n )\n ) : null}\n {children != null && <span className=\"px-1\">{children}</span>}\n {hasSuffix && (\n <span className=\"inline-flex items-center gap-1\">\n {badge}\n {EndIcon && <EndIcon size={iconSize} aria-hidden />}\n </span>\n )}\n </>\n )\n\n const sharedClassName = cn(\n buttonVariants({ variant: resolvedVariant, danger: resolvedDanger, size: resolvedSize, pressedTone, className }),\n // iconOnly 鐵律:padding-free + aspect-square + flex-center (Polaris idiom)\n // 0 magic-number 0 公式自動正方形。詳 ICON_ONLY_BASE rationale。\n resolvedIconOnly && ICON_ONLY_BASE,\n // Dismiss 視覺弱化:override Button text variant 預設 foreground 為 fg-muted → hover foreground\n // 跟 Inline Action dismiss 視覺一致(cross-implementation dimming canonical)\n dismiss && 'text-fg-muted hover:text-foreground',\n resolvedFullWidth && 'w-full',\n )\n\n // 2026-07-05 D4 修:asChild 原 silently drop loading/disabled 語意(無 aria-busy/aria-disabled/\n // click guard → <a> loading 期間可重複點擊)。補 ARIA(任何 element 合法;aria-disabled 自動點亮\n // cva 既有視覺)+ functional guard(APG aria-disabled 模式:語意+視覺由元件、功能阻斷由 guard)。\n const { onClick: slotConsumerOnClick, ...slotRest } = restProps as typeof restProps & { onClick?: React.MouseEventHandler<HTMLElement> }\n const buttonEl = asChild ? (\n // Slot 分支:children 必為單一 consumer element(React.Children.only),\n // 內部 slot(loading/startIcon/badge)不渲染;不硬塞 type/disabled(consumer child 可能是 <a>)。\n <Comp\n className={sharedClassName}\n ref={ref}\n aria-label={ariaLabel}\n aria-busy={loading || undefined}\n aria-disabled={disabled || loading || undefined}\n onClick={disabled || loading\n ? (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); e.stopPropagation() }\n : slotConsumerOnClick}\n {...toggleAttrs}\n {...unboundedAttr}\n {...slotRest}\n >\n {children}\n </Comp>\n ) : (\n <Comp\n className={sharedClassName}\n ref={ref}\n type=\"button\"\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n aria-label={ariaLabel}\n {...toggleAttrs}\n {...unboundedAttr}\n {...restProps}\n >\n {nativeChildren}\n </Comp>\n )\n\n // icon-only + aria-label → 自動包 Tooltip(tooltip 是元件保證的行為)\n // 不建立獨立 TooltipProvider——依賴全域 Provider,\n // 這樣所有 tooltip 共享同一組 delay 參數和 warm-up 機制\n if (resolvedIconOnly && typeof ariaLabel === 'string' && !asChild) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{buttonEl}</TooltipTrigger>\n <TooltipContent>{ariaLabel}</TooltipContent>\n </Tooltip>\n )\n }\n\n return buttonEl\n }\n)\nButton.displayName = 'Button'\n\n/**\n * componentMeta — Story Auto-Compile 系統消費的結構化 canonical\n * (見 .claude/planning/story-auto-compile.md Phase 1)\n *\n * compile-stories.mjs 讀本 export + spec.md frontmatter 產出\n * anatomy.stories.tsx 的 variant/size/state/token 矩陣 canonical section。\n *\n * Keys 必跟 buttonVariants cva + spec frontmatter 對齊(compile-time 驗證)。\n */\nexport const buttonMeta = {\n component: 'Button',\n family: 3, // Pill Layout\n variants: {\n primary: { purpose: '主要 action / CTA' },\n secondary: { purpose: '次要 action(陪襯 primary)' },\n tertiary: { purpose: '第三級 action(tool-like)' },\n text: { purpose: '文字樣式 action(low emphasis / toolbar)' },\n link: { purpose: '內文連結(inline reading)' },\n },\n sizes: {\n xs: { fieldHeight: 24, iconSize: 16, typography: 'caption' },\n sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },\n },\n // 'pressed' = toggle on 持續態(data-[state=on],emphasis 淡藍底/neutral 灰底;2026-07-07 meta 詞彙統一補列)\n // 'loading' = CircularProgress spinner 取代 startIcon + aria-busy + click guard(2026-07-14 deep-audit\n // 補列,原漏 → auto-compile 生成矩陣缺 loading;對齊 anatomy StateBehavior Loading 行 + tsx loading 分支)\n states: ['default', 'hover', 'active', 'pressed', 'loading', 'focus-visible', 'disabled'],\n tokens: {\n // 2026-07-04 audit 補齊:原只列 primary variant 家族;按 buttonVariants cva 真實 class 補\n // secondary/tertiary/text/link/danger 消費的 token(auto-compile token 矩陣依此)。\n // 2026-07-14 deep-audit 二次補齊(掃 cva 全 bg-* class):+ neutral-selected-hover / neutral-selected-active\n // (neutral pressedTone compound 消費)+ error / error-hover / error-active(primary+danger bg-error 系)\n bg: ['--primary', '--primary-hover', '--primary-active', '--primary-subtle', '--bg-disabled', '--surface', '--neutral-hover', '--neutral-active', '--neutral-selected', '--neutral-selected-hover', '--neutral-selected-active', '--error', '--error-hover', '--error-active'],\n fg: ['--on-emphasis', '--fg-disabled', '--foreground', '--primary', '--error', '--error-hover', '--error-active'],\n ring: ['--ring'],\n },\n defaultVariant: 'tertiary', // 2026-06-10 修 stale:對齊 cva defaultVariants(2026-06-06 labeled 預設改 tertiary,meta 漏同步)\n defaultSize: 'md',\n} as const\n\nexport { Button, buttonVariants, ButtonGroupContext }\n"],"names":[],"mappings":"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"}
|
|
@@ -9,10 +9,11 @@ import { type CategoricalHue } from '../../tokens/categorical-color';
|
|
|
9
9
|
* ── Layout Family ──
|
|
10
10
|
* 非 4-Family,屬 page-composite(多區塊 Toolbar + Grid + EventTile)。
|
|
11
11
|
*
|
|
12
|
-
* ── MVP scope
|
|
12
|
+
* ── MVP scope ──
|
|
13
13
|
* - 月 view 完整(toolbar / grid / event tile / today highlight / outside days)
|
|
14
|
-
* - 週 / 日 view
|
|
15
|
-
* -
|
|
14
|
+
* - 週 / 日 view + size lg = **roadmap:尚未實作,目前只渲染月檢視**(誠實 API,
|
|
15
|
+
* props 保留給未來增量,2026-07-14 user 拍板;行為明細見 spec「狀態 > 邊界案例」)
|
|
16
|
+
* - 拖拉增刪 event 是 roadmap(見 spec「MVP vs 後續增量」)
|
|
16
17
|
*
|
|
17
18
|
* ── 與 DatePicker 的區分 ──
|
|
18
19
|
* DatePicker 是「選日期」form control;Calendar 是「看事件」page canvas。
|
|
@@ -35,7 +36,11 @@ export interface CalendarEvent {
|
|
|
35
36
|
}
|
|
36
37
|
export type CalendarView = 'month' | 'week' | 'day';
|
|
37
38
|
export interface CalendarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
38
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* 當前 view。**roadmap:`'week'` / `'day'` 尚未實作,目前只渲染月檢視**——
|
|
41
|
+
* 傳入不報錯:grid 仍為月檢視,僅 `data-view` 同步 + SegmentedControl 週/日項 disabled
|
|
42
|
+
* (誠實 API;見 calendar.spec.md「狀態 > 邊界案例」+「MVP vs 後續增量」)。
|
|
43
|
+
*/
|
|
39
44
|
view?: CalendarView;
|
|
40
45
|
defaultView?: CalendarView;
|
|
41
46
|
onViewChange?: (view: CalendarView) => void;
|
|
@@ -55,7 +60,10 @@ export interface CalendarProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
55
60
|
weekStartsOn?: 0 | 1;
|
|
56
61
|
/** 自訂 event tile 渲染 */
|
|
57
62
|
renderEventTile?: (event: CalendarEvent) => React.ReactNode;
|
|
58
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* size。**roadmap:`'lg'` 尚未實作,視覺與 `'md'` 相同**——傳入不報錯,
|
|
65
|
+
* 僅設 `data-size` attribute,無任何 CSS 消費(誠實 API;prop 保留給未來 cell 密度增量)。
|
|
66
|
+
*/
|
|
59
67
|
size?: 'md' | 'lg';
|
|
60
68
|
className?: string;
|
|
61
69
|
/** locale(預設 'en-US') */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/components/Calendar/calendar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAe9B,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,0CAA0C,CAAA;AAIrG
|
|
1
|
+
{"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/components/Calendar/calendar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAe9B,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,0CAA0C,CAAA;AAIrG;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;AAEnD,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC3F;;;;OAIG;IACH,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,WAAW,CAAC,EAAE,YAAY,CAAA;IAC1B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;IAE3C,wBAAwB;IACxB,aAAa,CAAC,EAAE,IAAI,CAAA;IACpB,oBAAoB,CAAC,EAAE,IAAI,CAAA;IAC3B,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAE5C,WAAW;IACX,MAAM,CAAC,EAAE,aAAa,EAAE,CAAA;IAExB,sBAAsB;IACtB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAC7C,uBAAuB;IACvB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAClC,kBAAkB;IAClB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAE1B,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAEpB,uBAAuB;IACvB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAA;IAE3D;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAkCD,QAAA,MAAM,QAAQ,sFAsTZ,CAAA;AAKF,eAAO,MAAM,YAAY;;;;;;;;;;;CAgBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -9,7 +9,13 @@ import { SegmentedControl, SegmentedControlItem } from "../SegmentedControl/segm
|
|
|
9
9
|
const EVENT_COLOR_CLASSES = CAT_EVENT;
|
|
10
10
|
const EVENT_ALLDAY_ACCENT = CAT_ACCENT;
|
|
11
11
|
function coerceDate(value) {
|
|
12
|
-
|
|
12
|
+
if (value instanceof Date) return value;
|
|
13
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.exec(value);
|
|
14
|
+
if (dateOnly) {
|
|
15
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
16
|
+
return new Date(y, m - 1, d);
|
|
17
|
+
}
|
|
18
|
+
return new Date(value);
|
|
13
19
|
}
|
|
14
20
|
const MAX_TILES_PER_CELL = 3;
|
|
15
21
|
const Calendar = React.forwardRef(function Calendar2({
|