@qijenchen/design-system 0.1.0-beta.62 → 0.1.0-beta.63
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 +2 -3
- package/dist/components/Accordion/accordion.d.ts +1 -1
- package/dist/components/Accordion/accordion.js +1 -1
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/Alert/alert.d.ts +2 -2
- package/dist/components/Alert/alert.js +4 -3
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +4 -3
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +1 -1
- package/dist/components/Avatar/avatar.js +2 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Badge/badge.d.ts +1 -1
- package/dist/components/Badge/badge.js +2 -1
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +4 -4
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +2 -2
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +1 -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 +5 -3
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Checkbox/checkbox-group.d.ts +2 -1
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox-group.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +6 -0
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +4 -0
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +2 -2
- package/dist/components/Chip/chip.js +3 -2
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/CircularProgress/circular-progress.d.ts +1 -1
- package/dist/components/CircularProgress/circular-progress.js +2 -1
- package/dist/components/CircularProgress/circular-progress.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +4 -4
- package/dist/components/Coachmark/coachmark.js +2 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +1 -1
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +4 -0
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +8 -4
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +2 -2
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.js +1 -1
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.js +1 -1
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +0 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Empty/empty.d.ts +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +8 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +7 -3
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +25 -14
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts +4 -0
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts +6 -2
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +5 -1
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +2 -1
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +4 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/Popover/popover.d.ts +1 -1
- package/dist/components/Popover/popover.js +2 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +9 -4
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +6 -0
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +4 -0
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +5 -1
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +4 -0
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +6 -3
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/selection-item.d.ts +5 -1
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
- package/dist/components/SelectionControl/selection-item.js +2 -1
- package/dist/components/SelectionControl/selection-item.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +6 -5
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +3 -2
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/skeleton.d.ts +1 -1
- package/dist/components/Skeleton/skeleton.js +2 -1
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.js +2 -1
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +9 -6
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +5 -5
- package/dist/components/Switch/switch.js +3 -3
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/TimePicker/time-columns.js +1 -1
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts +1 -1
- package/dist/components/Tooltip/tooltip.js +2 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +4 -4
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +3 -3
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +3 -3
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.d.ts +3 -2
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +3 -2
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
- package/ds-canonical/hooks/check_chrome_header_avatar_canonical.sh +8 -0
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +4 -4
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +15 -1
- package/ds-canonical/hooks/check_field_family_invariants.sh +12 -2
- package/ds-canonical/hooks/check_solo_workflow.sh +11 -1
- package/ds-canonical/hooks/check_story_invariants.sh +63 -9
- package/ds-canonical/hooks/check_tailwind_wildcard_in_docs.sh +8 -2
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +25 -4
- package/ds-canonical/hooks/lib/_token_hygiene.sh +9 -1
- package/ds-canonical/hooks/session_start_governance_check.sh +6 -1
- package/ds-canonical/hooks/tests/test_check_addon_subdir_ship.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +12 -0
- package/ds-canonical/hooks/tests/test_check_consumer_app_story_title.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +6 -4
- package/ds-canonical/hooks/tests/test_check_consumer_no_ds_catalog.sh +7 -4
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +6 -4
- package/ds-canonical/hooks/tests/test_check_data_table_size_num_to_meta_width.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_fork_user_plugin_install.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +9 -0
- package/ds-canonical/hooks/tests/test_check_propose_cite_required.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +10 -0
- package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +10 -0
- package/ds-canonical/hooks/tests/test_check_storybook_addon_preset_cjs.sh +3 -2
- package/ds-canonical/references/build-ui-canonicals.md +1 -1
- package/ds-canonical/references/composition-fidelity.md +1 -1
- package/ds-canonical/references/naming-conventions.md +1 -0
- package/ds-canonical/references/ssot-consultation.md +1 -1
- package/ds-canonical/references/ssot-index.md +7 -7
- package/ds-canonical/rules/meta-patterns.md +3 -3
- package/ds-canonical/rules/story-rules.md +2 -0
- package/ds-canonical/rules/ui-development.md +1 -1
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +2 -0
- package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +4 -3
- package/ds-story-manifest.json +7 -6
- package/llms-full.txt +7 -3
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/components/Accordion/accordion.spec.md +1 -1
- package/src/components/Accordion/accordion.tsx +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
- package/src/components/Alert/alert.spec.md +6 -6
- package/src/components/Alert/alert.tsx +2 -2
- package/src/components/AppShell/app-shell.principles.stories.tsx +46 -10
- package/src/components/AppShell/app-shell.spec.md +7 -6
- package/src/components/AppShell/app-shell.tsx +4 -3
- package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +3 -3
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
- package/src/components/Avatar/avatar.anatomy.stories.tsx +1 -1
- package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.spec.md +14 -14
- package/src/components/Avatar/avatar.tsx +2 -2
- package/src/components/Badge/badge.principles.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +14 -13
- package/src/components/Badge/badge.tsx +1 -1
- package/src/components/Breadcrumb/breadcrumb.spec.md +4 -4
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +3 -2
- package/src/components/Breadcrumb/breadcrumb.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +3 -4
- package/src/components/BulkActionBar/bulk-action-bar.tsx +2 -2
- package/src/components/Button/button.anatomy.stories.tsx +2 -2
- package/src/components/Button/button.spec.md +8 -9
- package/src/components/Button/button.stories.tsx +1 -1
- package/src/components/Button/button.tsx +9 -8
- package/src/components/Calendar/calendar.spec.md +6 -5
- package/src/components/Calendar/calendar.tsx +1 -1
- package/src/components/Carousel/carousel.anatomy.stories.tsx +2 -2
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.spec.md +14 -2
- package/src/components/Carousel/carousel.tsx +18 -3
- package/src/components/Chart/chart.spec.md +2 -1
- package/src/components/Checkbox/checkbox-group.tsx +2 -1
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +4 -4
- package/src/components/Checkbox/checkbox.spec.md +6 -2
- package/src/components/Checkbox/checkbox.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.tsx +10 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +5 -5
- package/src/components/Chip/chip.principles.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +5 -3
- package/src/components/Chip/chip.tsx +2 -2
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +6 -6
- package/src/components/CircularProgress/circular-progress.spec.md +3 -3
- package/src/components/CircularProgress/circular-progress.tsx +1 -1
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.principles.stories.tsx +5 -5
- package/src/components/Coachmark/coachmark.spec.md +4 -3
- package/src/components/Coachmark/coachmark.tsx +4 -4
- package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
- package/src/components/Combobox/combobox.principles.stories.tsx +1 -1
- package/src/components/Combobox/combobox.spec.md +16 -5
- package/src/components/Combobox/combobox.tsx +4 -2
- package/src/components/Command/command.anatomy.stories.tsx +1 -1
- package/src/components/Command/command.spec.md +7 -7
- package/src/components/Command/command.tsx +8 -3
- package/src/components/DataTable/data-table-sort-manager.tsx +1 -1
- package/src/components/DataTable/data-table.anatomy.stories.tsx +9 -7
- package/src/components/DataTable/data-table.spec.md +7 -4
- package/src/components/DataTable/data-table.stories.tsx +54 -9
- package/src/components/DataTable/data-table.tsx +5 -2
- package/src/components/DataTable/filter-operators.spec.md +5 -5
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +8 -8
- package/src/components/DateGrid/date-grid.principles.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.spec.md +6 -7
- package/src/components/DateGrid/date-grid.tsx +8 -4
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -13
- package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
- package/src/components/DatePicker/date-picker.spec.md +21 -16
- package/src/components/DatePicker/date-picker.tsx +1 -1
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.stories.tsx +2 -18
- package/src/components/DescriptionList/description-list.tsx +1 -1
- package/src/components/Dialog/dialog.spec.md +2 -1
- package/src/components/Dialog/dialog.tsx +2 -1
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +2 -2
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +11 -12
- package/src/components/DropdownMenu/dropdown-menu.spec.md +3 -3
- package/src/components/DropdownMenu/dropdown-menu.tsx +12 -2
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Empty/empty.tsx +1 -1
- package/src/components/Field/field-controls.spec.md +35 -34
- package/src/components/Field/field.anatomy.stories.tsx +4 -3
- package/src/components/Field/field.spec.md +16 -20
- package/src/components/Field/field.tsx +3 -3
- package/src/components/Field/form-validation.spec.md +10 -10
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +16 -15
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +5 -1
- package/src/components/FieldControlGroup/field-control-group.spec.md +1 -1
- package/src/components/FieldControlGroup/field-control-group.stories.tsx +5 -5
- package/src/components/FieldControlGroup/field-control-group.tsx +4 -3
- package/src/components/FileItem/file-item.anatomy.stories.tsx +4 -4
- package/src/components/FileItem/file-item.principles.stories.tsx +1 -1
- package/src/components/FileItem/file-item.spec.md +2 -2
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +4 -2
- package/src/components/FileUpload/file-upload.principles.stories.tsx +6 -8
- package/src/components/FileUpload/file-upload.spec.md +11 -1
- package/src/components/FileUpload/file-upload.tsx +5 -3
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +16 -11
- package/src/components/FileViewer/file-viewer.spec.md +5 -5
- package/src/components/FileViewer/file-viewer.tsx +42 -16
- package/src/components/HoverCard/hover-card.spec.md +4 -1
- package/src/components/HoverCard/hover-card.tsx +4 -0
- package/src/components/Input/input.anatomy.stories.tsx +2 -2
- package/src/components/Input/input.spec.md +1 -1
- package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.spec.md +5 -1
- package/src/components/Menu/menu-item.spec.md +4 -3
- package/src/components/Menu/menu-item.tsx +7 -3
- package/src/components/Notice/notice.principles.stories.tsx +2 -2
- package/src/components/Notice/notice.spec.md +2 -2
- package/src/components/Notice/notice.tsx +5 -1
- 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 -0
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -2
- package/src/components/OverflowIndicator/overflow-indicator.tsx +4 -0
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +18 -10
- package/src/components/PeoplePicker/people-picker.tsx +5 -4
- package/src/components/Popover/popover.spec.md +3 -4
- package/src/components/Popover/popover.tsx +2 -2
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +23 -24
- package/src/components/ProfileCard/profile-card.spec.md +6 -6
- package/src/components/ProfileCard/profile-card.tsx +9 -4
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +1 -1
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +6 -3
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +2 -2
- package/src/components/RadioGroup/radio-group.spec.md +2 -1
- package/src/components/RadioGroup/radio-group.tsx +10 -0
- package/src/components/Rating/rating.anatomy.stories.tsx +1 -0
- package/src/components/Rating/rating.spec.md +2 -1
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +44 -24
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +51 -57
- package/src/components/ScrollArea/scroll-area.spec.md +3 -5
- package/src/components/ScrollArea/scroll-area.stories.tsx +110 -60
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.spec.md +3 -5
- package/src/components/SegmentedControl/segmented-control.stories.tsx +1 -1
- package/src/components/Select/select.anatomy.stories.tsx +10 -9
- package/src/components/Select/select.principles.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +13 -3
- package/src/components/Select/select.stories.tsx +2 -2
- package/src/components/Select/select.tsx +6 -1
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +2 -2
- package/src/components/SelectMenu/select-menu.spec.md +7 -6
- package/src/components/SelectMenu/select-menu.tsx +14 -3
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.spec.md +3 -0
- package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.tsx +6 -2
- package/src/components/Separator/separator.spec.md +5 -5
- package/src/components/Sheet/sheet.anatomy.stories.tsx +7 -6
- package/src/components/Sheet/sheet.principles.stories.tsx +13 -17
- package/src/components/Sheet/sheet.spec.md +2 -2
- package/src/components/Sheet/sheet.tsx +6 -5
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +5 -5
- package/src/components/Sidebar/sidebar.spec.md +10 -10
- package/src/components/Sidebar/sidebar.stories.tsx +16 -0
- package/src/components/Sidebar/sidebar.tsx +15 -7
- package/src/components/Skeleton/skeleton.anatomy.stories.tsx +2 -2
- package/src/components/Skeleton/skeleton.spec.md +3 -3
- package/src/components/Skeleton/skeleton.stories.tsx +34 -26
- package/src/components/Skeleton/skeleton.tsx +1 -1
- package/src/components/Slider/slider.spec.md +2 -2
- package/src/components/Slider/slider.tsx +1 -1
- package/src/components/Steps/steps.anatomy.stories.tsx +3 -3
- package/src/components/Steps/steps.spec.md +28 -23
- package/src/components/Steps/steps.stories.tsx +2 -2
- package/src/components/Steps/steps.tsx +17 -12
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Switch/switch.tsx +3 -3
- package/src/components/Tabs/tabs.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.spec.md +4 -4
- package/src/components/Tabs/tabs.tsx +1 -1
- package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
- package/src/components/Tag/tag.principles.stories.tsx +1 -1
- package/src/components/Tag/tag.spec.md +5 -4
- package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
- package/src/components/Textarea/textarea.spec.md +2 -2
- package/src/components/TimePicker/time-columns.tsx +1 -1
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +8 -2
- package/src/components/TimePicker/time-picker.spec.md +15 -3
- package/src/components/TimePicker/time-picker.tsx +2 -2
- package/src/components/Toast/toast.anatomy.stories.tsx +1 -1
- package/src/components/Toast/toast.spec.md +4 -4
- package/src/components/Toast/toast.tsx +1 -1
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +1 -1
- package/src/components/Tooltip/tooltip.spec.md +2 -2
- package/src/components/Tooltip/tooltip.tsx +1 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +24 -8
- package/src/components/TreeView/tree-view.principles.stories.tsx +1 -1
- package/src/components/TreeView/tree-view.spec.md +9 -9
- package/src/components/TreeView/tree-view.stories.tsx +3 -1
- package/src/components/TreeView/tree-view.tsx +8 -5
- package/src/patterns/action-bar/action-bar.spec.md +3 -3
- package/src/patterns/element-anatomy/element-anatomy.spec.md +6 -5
- package/src/patterns/element-anatomy/inline-action.spec.md +10 -7
- package/src/patterns/element-anatomy/item-anatomy.spec.md +31 -34
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -6
- package/src/patterns/element-anatomy/item-anatomy.tsx +3 -3
- package/src/patterns/header-canonical/chrome-header.tsx +3 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +9 -9
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +3 -4
- package/src/patterns/overlay-surface/overlay-surface.spec.md +26 -23
- package/src/patterns/overlay-surface/overlay-surface.tsx +3 -2
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/patterns/resize-handle/resize-handle.stories.tsx +1 -1
- package/src/tokens/README.md +5 -2
- package/src/tokens/color/color.spec.md +5 -2
- package/src/tokens/layoutSpace/layoutSpace.spec.md +6 -6
- package/src/tokens/motion/motion.spec.md +5 -5
- package/src/tokens/opacity/opacity.spec.md +9 -7
- package/src/tokens/orphan-tokens.spec.md +2 -2
- package/src/tokens/radius/radius.spec.md +7 -7
- package/src/tokens/typography/typography.spec.md +5 -5
- package/src/tokens/uiSize/uiSize.spec.md +7 -9
|
@@ -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 + font-medium\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 const renderItem = (spec: BreadcrumbItemSpec, role: 'root' | 'middle' | 'current') => (\n <BreadcrumbItem key={`${role}-${typeof spec.label === 'string' ? spec.label : Math.random()}`} 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))\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\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 / 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 const renderItem = (spec: BreadcrumbItemSpec, role: 'root' | 'middle' | 'current') => (\n <BreadcrumbItem key={`${role}-${typeof spec.label === 'string' ? spec.label : Math.random()}`} 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))\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\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-action-bar.js","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"sourcesContent":["import * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\n\n// ── 消費的 SSOT ───────────────────────────────────────────────────────────────\n// - bulk-action-bar.spec.md(本元件 SSOT)\n// - DataTable/data-table.spec.md「L2 選取」(整合方式)\n// - button.spec.md + button-group.tsx(action variant=tertiary,size=md,\n// gap-2 + ButtonDivider 自帶 mx-1 = 12px 視覺距離)\n// - inline-action.spec.md「same-row consistency rule」(close X 同尺寸 md)\n// - tokens/layoutSpace/layoutSpace.spec.md(footer 用 px-loose py-tight)\n// - patterns/overlay-surface/overlay-surface.spec.md SurfaceFooter canonical\n// - Alert(banner)用
|
|
1
|
+
{"version":3,"file":"bulk-action-bar.js","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"sourcesContent":["import * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\n\n// ── 消費的 SSOT ───────────────────────────────────────────────────────────────\n// - bulk-action-bar.spec.md(本元件 SSOT)\n// - DataTable/data-table.spec.md「L2 選取」(整合方式)\n// - button.spec.md + button-group.tsx(action variant=tertiary,size=md,\n// gap-2 + ButtonDivider 自帶 mx-1 = 12px 視覺距離)\n// - inline-action.spec.md「same-row consistency rule」(close X 同尺寸 md)\n// - tokens/layoutSpace/layoutSpace.spec.md(footer 用 px-loose py-tight)\n// - patterns/overlay-surface/overlay-surface.spec.md SurfaceFooter canonical\n// - Alert(banner)用 title ReactNode 帶 inline link CTA\n\n// ── i18n labels ─────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export — public API per spec.md(consumer i18n override hook)\nexport interface BulkActionBarLabels {\n count: (n: number) => string\n clear: string\n hiddenSuffix: (hidden: number) => string\n toolbarAriaLabel: string\n}\n\n// code-quality-allow: dead-export — public API per spec.md(consumer spread + override)\nexport const BULK_ACTION_BAR_DEFAULT_LABELS: BulkActionBarLabels = {\n count: (n) => `已選 ${n} 項`,\n clear: '清除選取',\n hiddenSuffix: (hidden) => `· ${hidden} 個被 filter 隱藏`,\n toolbarAriaLabel: '批次操作',\n}\n\n// ── Props ───────────────────────────────────────────────────────────────────\n\nexport interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 已選 ID,length === 0 時自動隱藏(回傳 null) */\n selection: readonly string[]\n /** Clear 觸發,user 點 X icon 或 Esc(consumer 在 page-level 監聽) */\n onClear?: () => void\n /** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger;不用 primary) */\n actions?: React.ReactNode\n /** Filter 模式:hidden 數量,顯示在 count 區 inline「{N} 已選 · {M} 個被 filter 隱藏」 */\n hiddenByFilter?: number\n /**\n * 「擴選整個 dataset」狀態(2026-05-13 ship,per user 抓 Alert「已選 5370」但 BulkActionBar\n * 仍顯「已選 50 項」regression):\n * - undefined / null(default):count 走 `selection.length`(page-level 視覺選取)\n * - number:count 走此數值(整個 dataset 擴選後 user 已選的真總數)\n *\n * Canonical pattern:consumer 把 BulkActionBar 跟「Alert info banner(提示擴選 dataset)」\n * 一起 mount,Alert 點「點此選取全部 N 個」→ setTotalSelected(N) → BulkActionBar count 同步。\n * 對齊 Gmail / Linear / Notion 全選 dataset hint pattern。\n * 詳 `bulk-action-bar.spec.md`「Extend dataset pattern」段。\n */\n totalSelected?: number | null\n /** i18n labels(Partial,merge with default) */\n labels?: Partial<BulkActionBarLabels>\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n// 視覺結構(同 SurfaceFooter / DataTable toolbar canonical):\n// px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]\n// gap-2 between elements\n// 全 md Buttons(same-row consistency)\n// <ButtonDivider /> 自帶 mx-1 = 12px 視覺距離\n//\n// Hint banner(擴 dataset 提示)完全外包給 Alert 元件 — consumer 視 ref 圖\n// 黏在 BulkActionBar 上方/下方,用 Alert variant=\"info\" placement=\"fixed\"\n// title={inline link JSX}。BulkActionBar 自己不再有 hint banner slot。\n//\n// 浮起 / fixed positioning 由 consumer wrap 決定(BulkActionBar 不限定 placement)。\n\nconst BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(\n function BulkActionBar(\n { selection, onClear, actions, hiddenByFilter, totalSelected, labels: labelsOverride, className, ...props },\n ref\n ) {\n const labels: BulkActionBarLabels = React.useMemo(\n () => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),\n [labelsOverride]\n )\n\n // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)\n if (selection.length === 0) return null\n\n return (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={labels.toolbarAriaLabel}\n className={cn(\n 'flex items-center gap-2',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n // 上分隔線:bottom toolbar canonical(Linear / Apple Mail / Notion 共識)— 視覺從上方內容收尾\n 'border-t border-divider',\n className\n )}\n {...props}\n >\n {/* X close — md dismiss(2026-05-04 spec update:default placement = footer variant,\n visual weight 對齊 Dialog footer commitment buttons md;same-row consistency 維持)\n 未來若有 top-toolbar variant(覆蓋 sm-density toolbar)→ 該 variant override sm */}\n {onClear && (\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n dismiss\n startIcon={X}\n aria-label={labels.clear}\n onClick={onClear}\n />\n )}\n\n {/* count + filter hidden inline\n color canonical(2026-05-04):count = primary foreground + medium weight\n 理由:count 是 state-bearing 主資訊(「你在 selection mode + N items」),非裝飾\n World-class 共識:Linear/Notion/Carbon/Polaris 均用 primary foreground;muted 化弱化 state signal\n hiddenByFilter suffix 維持 muted(這是次資訊,視覺層次正確) */}\n {/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec L90 + 上方 comment(state-bearing 主資訊) */}\n <span className=\"text-body text-foreground font-medium tabular-nums\" aria-live=\"polite\" aria-atomic=\"true\">\n {/* 2026-05-13:totalSelected override 走 dataset 擴選後真總數,否則 fallback page-level selection.length */}\n {labels.count(typeof totalSelected === 'number' ? totalSelected : selection.length)}\n {hiddenByFilter !== undefined && hiddenByFilter > 0 && (\n <span className=\"text-fg-muted font-normal\"> {labels.hiddenSuffix(hiddenByFilter)}</span>\n )}\n </span>\n\n {/* divider */}\n {actions && <ButtonDivider />}\n\n {/* batch actions slot(consumer 提供 md Buttons) */}\n {actions && (\n <div className=\"flex items-center gap-2 flex-1 min-w-0\">{actions}</div>\n )}\n </div>\n )\n }\n)\nBulkActionBar.displayName = 'BulkActionBar'\n\n// Story auto-compile metadata\nexport const bulkActionBarMeta = {\n component: 'BulkActionBar',\n family: null,\n variants: {},\n sizes: {},\n states: ['default'],\n tokens: {\n fg: ['text-foreground', 'text-fg-muted'],\n border: ['border-divider'],\n },\n} as const\n\nexport { BulkActionBar }\n"],"names":["BulkActionBar"],"mappings":";;;;;;AA2BO,MAAM,iCAAsD;AAAA,EACjE,OAAO,CAAC,MAAM,MAAM,CAAC;AAAA,EACrB,OAAO;AAAA,EACP,cAAc,CAAC,WAAW,KAAK,MAAM;AAAA,EACrC,kBAAkB;AACpB;AA0CA,MAAM,gBAAgB,MAAM;AAAA,EAC1B,SAASA,eACP,EAAE,WAAW,SAAS,SAAS,gBAAgB,eAAe,QAAQ,gBAAgB,WAAW,GAAG,MAAA,GACpG,KACA;AACA,UAAM,SAA8B,MAAM;AAAA,MACxC,OAAO,EAAE,GAAG,gCAAgC,GAAG;MAC/C,CAAC,cAAc;AAAA,IAAA;AAIjB,QAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAY,OAAO;AAAA,QACnB,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAKH,UAAA;AAAA,UAAA,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,SAAO;AAAA,cACP,WAAW;AAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,+BAUZ,QAAA,EAAK,WAAU,sDAAqD,aAAU,UAAS,eAAY,QAEjG,UAAA;AAAA,YAAA,OAAO,MAAM,OAAO,kBAAkB,WAAW,gBAAgB,UAAU,MAAM;AAAA,YACjF,mBAAmB,UAAa,iBAAiB,KAChD,qBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA;AAAA,cAAA;AAAA,cAAE,OAAO,aAAa,cAAc;AAAA,YAAA,EAAA,CAAE;AAAA,UAAA,GAEtF;AAAA,UAGC,+BAAY,eAAA,EAAc;AAAA,UAG1B,WACC,oBAAC,OAAA,EAAI,WAAU,0CAA0C,UAAA,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIzE;AACF;AACA,cAAc,cAAc;AAGrB,MAAM,oBAAoB;AAAA,EAC/B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO,CAAA;AAAA,EACP,QAAQ,CAAC,SAAS;AAAA,EAClB,QAAQ;AAAA,IACN,IAAI,CAAC,mBAAmB,eAAe;AAAA,IACvC,QAAQ,CAAC,gBAAgB;AAAA,EAAA;AAE7B;"}
|
|
@@ -12,7 +12,7 @@ import type { LucideIcon } from 'lucide-react';
|
|
|
12
12
|
* link 外觀像連結的按鈕(本質仍是 button)
|
|
13
13
|
*
|
|
14
14
|
* ── danger prop ──
|
|
15
|
-
* danger
|
|
15
|
+
* danger 套用危險色(紅色);僅 primary / secondary / text 支援(tertiary / link 不支援,見 spec 禁止事項)
|
|
16
16
|
*
|
|
17
17
|
* <Button variant="primary" danger>永久刪除</Button> → 紅底白字(立即不可逆)
|
|
18
18
|
* <Button variant="secondary" danger>移至垃圾桶</Button> → 紅框紅字(點下去還可反悔)
|
|
@@ -73,7 +73,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
|
|
73
73
|
* `destructive` / `ghost` 為 shadcn 內部 compat,請勿在應用程式碼中直接使用。
|
|
74
74
|
*/
|
|
75
75
|
variant?: 'primary' | 'secondary' | 'tertiary' | 'text' | 'link';
|
|
76
|
-
/**
|
|
76
|
+
/** 套用危險色(紅色)。僅 primary / secondary / text 支援(tertiary / link 不支援,見 spec 禁止事項)。 */
|
|
77
77
|
danger?: boolean;
|
|
78
78
|
/**
|
|
79
79
|
* Toggle 按下狀態(持續 on/off)。設定時 Button 變為 toggle:
|
|
@@ -172,7 +172,7 @@ export declare const buttonMeta: {
|
|
|
172
172
|
readonly xs: {
|
|
173
173
|
readonly fieldHeight: 24;
|
|
174
174
|
readonly iconSize: 16;
|
|
175
|
-
readonly typography: "
|
|
175
|
+
readonly typography: "caption";
|
|
176
176
|
};
|
|
177
177
|
readonly sm: {
|
|
178
178
|
readonly fieldHeight: 28;
|
|
@@ -185,7 +185,7 @@ export declare const buttonMeta: {
|
|
|
185
185
|
readonly typography: "body";
|
|
186
186
|
};
|
|
187
187
|
readonly lg: {
|
|
188
|
-
readonly fieldHeight:
|
|
188
|
+
readonly fieldHeight: 36;
|
|
189
189
|
readonly iconSize: 20;
|
|
190
190
|
readonly typography: "body-lg";
|
|
191
191
|
};
|
|
@@ -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;;;;;8EAkKnB,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,
|
|
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;;;;;8EAkKnB,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;AAwBD,QAAA,MAAM,MAAM,uFA0KX,CAAA;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwBb,CAAA;AAEV,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -288,10 +288,10 @@ const buttonMeta = {
|
|
|
288
288
|
link: { purpose: "內文連結(inline reading)" }
|
|
289
289
|
},
|
|
290
290
|
sizes: {
|
|
291
|
-
xs: { fieldHeight: 24, iconSize: 16, typography: "
|
|
291
|
+
xs: { fieldHeight: 24, iconSize: 16, typography: "caption" },
|
|
292
292
|
sm: { fieldHeight: 28, iconSize: 16, typography: "body" },
|
|
293
293
|
md: { fieldHeight: 32, iconSize: 16, typography: "body" },
|
|
294
|
-
lg: { fieldHeight:
|
|
294
|
+
lg: { fieldHeight: 36, iconSize: 20, typography: "body-lg" }
|
|
295
295
|
},
|
|
296
296
|
states: ["default", "hover", "active", "focus-visible", "disabled"],
|
|
297
297
|
tokens: {
|
|
@@ -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 套用在任何 variant 上,將顏色改為危險色(紅色)\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 ],\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(L394);此 cva default 供直接 buttonVariants() 呼叫者一致。\n variant: 'tertiary',\n size: 'md',\n pressedTone: 'emphasis',\n },\n }\n)\n\n// ── ButtonGroup Context ──────────────────────────────────────────────────────\n// ButtonGroup provides this context; Button reads it for fullWidth injection.\n// Context lives here (not in button-group.tsx) so there is no circular 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 /** 套用危險色(紅色)。可與任何 variant 組合使用。 */\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/item-anatomy.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/**\n * Icon-only padding — calc `(field-height - icon-size) / 2` per size。\n *\n * 設計:startIcon 到左邊距離 = padding = `(height - icon) / 2`。\n * 純 icon-only 時 width = 2*padding + icon = height → **自然正方形**,不需要 aspect-square。\n * 有 suffix(badge / endIcon)時 width = 2*pad + icon + gap + suffix > height → **自然長方形**。\n * StartIcon 到左邊距離始終不變,形狀自動適應內容。\n *\n * 用 CSS var 讓 density 切換時 padding 自動跟著算(field-height 會變)。\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(L372 反向 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] 分支套用——secondary/tertiary 走 primary-subtle,\n // text 走 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 const buttonEl = (\n <Comp\n className={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 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 {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 </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: 'body' },\n sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 40, iconSize: 20, typography: 'body-lg' },\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['--primary', '--primary-hover', '--primary-active', '--bg-disabled'],\n fg: ['--on-emphasis', '--fg-disabled'],\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 ],\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(L394);此 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/item-anatomy.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/**\n * Icon-only padding — calc `(field-height - icon-size) / 2` per size。\n *\n * 設計:startIcon 到左邊距離 = padding = `(height - icon) / 2`。\n * 純 icon-only 時 width = 2*padding + icon = height → **自然正方形**,不需要 aspect-square。\n * 有 suffix(badge / endIcon)時 width = 2*pad + icon + gap + suffix > height → **自然長方形**。\n * StartIcon 到左邊距離始終不變,形狀自動適應內容。\n *\n * 用 CSS var 讓 density 切換時 padding 自動跟著算(field-height 會變)。\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(L372 反向 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 const buttonEl = (\n <Comp\n className={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 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 {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 </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 states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['--primary', '--primary-hover', '--primary-active', '--bg-disabled'],\n fg: ['--on-emphasis', '--fg-disabled'],\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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"}
|
|
@@ -4,7 +4,7 @@ import { type CategoricalHue } from '../../tokens/categorical-color';
|
|
|
4
4
|
* Calendar — 事件檢視 canvas(月 view MVP)
|
|
5
5
|
*
|
|
6
6
|
* 定位:看事件的 page-level canvas,對齊 Notion Calendar / Google Calendar。
|
|
7
|
-
* 完整 spec 見 `
|
|
7
|
+
* 完整 spec 見 `calendar.spec.md`。
|
|
8
8
|
*
|
|
9
9
|
* ── Layout Family ──
|
|
10
10
|
* 非 4-Family,屬 page-composite(多區塊 Toolbar + Grid + EventTile)。
|