@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":"calendar.js","sources":["../../../src/components/Calendar/calendar.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { ChevronLeft, ChevronRight, Plus } from 'lucide-react'\nimport {\n startOfMonth,\n endOfMonth,\n startOfWeek,\n endOfWeek,\n eachDayOfInterval,\n format,\n isSameMonth,\n isSameDay,\n addMonths,\n subMonths,\n} from 'date-fns'\nimport { cn } from '@/lib/utils'\nimport { CAT_EVENT, CAT_ACCENT, type CategoricalHue } from '@/design-system/tokens/categorical-color'\nimport { Button } from '@/design-system/components/Button/button'\nimport { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'\n\n/**\n * Calendar — 事件檢視 canvas(月 view MVP)\n *\n * 定位:看事件的 page-level canvas,對齊 Notion Calendar / Google Calendar。\n * 完整 spec 見 `event-calendar.spec.md`。\n *\n * ── Layout Family ──\n * 非 4-Family,屬 page-composite(多區塊 Toolbar + Grid + EventTile)。\n *\n * ── MVP scope(本次 session)──\n * - 月 view 完整(toolbar / grid / event tile / today highlight / outside days)\n * - 週 / 日 view 是 tech debt\n * - 拖拉增刪 event 是 tech debt\n *\n * ── 與 DatePicker 的區分 ──\n * DatePicker 是「選日期」form control;Calendar 是「看事件」page canvas。\n * 名字相近但職責完全不同,spec 頂段明示分界。\n */\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface CalendarEvent {\n id: string\n title: string\n /** ISO 字串 \"YYYY-MM-DD\"(all-day)或 \"YYYY-MM-DDTHH:mm\"(timed) */\n start: string | Date\n end: string | Date\n allDay?: boolean\n /**\n * 事件類別色(categorical 色相,1:1 對 `--color-{hue}-*`)。**消費 categorical-color SSOT**,\n * 與 Tag / Avatar 共用同一組 12 色相。2026-06-04 修:原 `orange` 與 `red` 都誤接 deep-orange;\n * 改消費 SSOT 後 orange→`--color-orange-*`、red→`--color-red-*`(品牌紅 hue 25),各自獨立。\n */\n color?: CategoricalHue\n metadata?: Record<string, unknown>\n}\n\nexport type CalendarView = 'month' | 'week' | 'day'\n\nexport interface CalendarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** 當前 view(MVP 只 'month',其餘 view tech debt) */\n view?: CalendarView\n defaultView?: CalendarView\n onViewChange?: (view: CalendarView) => void\n\n /** 聚焦日期(月 view 的那個月) */\n referenceDate?: Date\n defaultReferenceDate?: Date\n onReferenceDateChange?: (date: Date) => void\n\n /** 事件資料 */\n events?: CalendarEvent[]\n\n /** 點 event tile 回調 */\n onEventClick?: (event: CalendarEvent) => void\n /** 點月 cell 回調(用於新增) */\n onDateClick?: (date: Date) => void\n /** 點新事件 CTA 回調 */\n onCreateEvent?: () => void\n\n /** 0 = Sunday, 1 = Monday。預設 0(對齊 Google Calendar 美系預設) */\n weekStartsOn?: 0 | 1\n\n /** 自訂 event tile 渲染 */\n renderEventTile?: (event: CalendarEvent) => React.ReactNode\n\n /** size(MVP 只 md;lg 為 tech debt) */\n size?: 'md' | 'lg'\n className?: string\n\n /** locale(預設 'en-US') */\n locale?: string\n\n /** ARIA labels for chrome controls. Override for i18n. */\n prevAriaLabel?: string\n nextAriaLabel?: string\n /** 月份導覽 <nav> landmark 的 aria-label。Override for i18n. */\n navAriaLabel?: string\n viewToggleAriaLabel?: string\n todayLabel?: string\n}\n\n// ── Event tile color tokens ─────────────────────────────────────────────────\n// **消費 categorical-color SSOT**(CAT_EVENT = subtle 底 + hover step-2;CAT_ACCENT = 左側 step-6\n// 實心條),與 Tag / Avatar 共用 12 色相,key X 一律對 `--color-X-*`(1:1)。\n// 2026-06-01 allDay:全天事件 = 淡底 tile + 左側實心 accent 條 + medium,視覺區分「全天長條」vs\n// 有時間事件;用 accent border 而非 solid fill 保文字對比安全。對齊 Google Calendar / Outlook 慣例。\nconst EVENT_COLOR_CLASSES = CAT_EVENT\nconst EVENT_ALLDAY_ACCENT = CAT_ACCENT\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction coerceDate(value: string | Date): Date {\n return value instanceof Date ? value : new Date(value)\n}\n\nfunction eventsOnDate(events: CalendarEvent[], date: Date): CalendarEvent[] {\n return events.filter((e) => {\n const start = coerceDate(e.start)\n const end = coerceDate(e.end)\n // 日期落在 [start, end] 範圍內(日精度)\n const d = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n const s = new Date(start.getFullYear(), start.getMonth(), start.getDate()).getTime()\n const eEnd = new Date(end.getFullYear(), end.getMonth(), end.getDate()).getTime()\n return d >= s && d <= eEnd\n })\n}\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nconst MAX_TILES_PER_CELL = 3\n\nconst Calendar = React.forwardRef<HTMLDivElement, CalendarProps>(function Calendar({\n view: viewProp,\n defaultView = 'month',\n onViewChange,\n referenceDate: referenceDateProp,\n defaultReferenceDate,\n onReferenceDateChange,\n events = [],\n onEventClick,\n onDateClick,\n onCreateEvent,\n weekStartsOn = 0,\n renderEventTile,\n size = 'md',\n className,\n locale = 'en-US',\n prevAriaLabel = '上個月', // i18n-allow: DS default; consumer override via prevAriaLabel prop\n nextAriaLabel = '下個月', // i18n-allow: DS default; consumer override via nextAriaLabel prop\n navAriaLabel = '行事曆月份導覽', // i18n-allow: DS default; consumer override via navAriaLabel prop\n viewToggleAriaLabel = '檢視切換', // i18n-allow: DS default; consumer override via viewToggleAriaLabel prop\n todayLabel = '今天', // i18n-allow: DS default; consumer override via todayLabel prop\n ...props\n}, ref) {\n // Controlled / uncontrolled refDate\n const [internalRef, setInternalRef] = React.useState<Date>(\n defaultReferenceDate ?? new Date(),\n )\n const refDate = referenceDateProp ?? internalRef\n const setRefDate = React.useCallback(\n (next: Date) => {\n if (referenceDateProp === undefined) setInternalRef(next)\n onReferenceDateChange?.(next)\n },\n [referenceDateProp, onReferenceDateChange],\n )\n\n // View state(MVP 只用 month,其他 tech debt)\n const [internalView, setInternalView] = React.useState<CalendarView>(defaultView)\n const currentView = viewProp ?? internalView\n const setView = React.useCallback(\n (next: CalendarView) => {\n if (viewProp === undefined) setInternalView(next)\n onViewChange?.(next)\n },\n [viewProp, onViewChange],\n )\n\n // Build month grid\n const days = React.useMemo(() => {\n const monthStart = startOfMonth(refDate)\n const monthEnd = endOfMonth(refDate)\n const gridStart = startOfWeek(monthStart, { weekStartsOn })\n const gridEnd = endOfWeek(monthEnd, { weekStartsOn })\n return eachDayOfInterval({ start: gridStart, end: gridEnd })\n }, [refDate, weekStartsOn])\n\n const monthTitle = new Intl.DateTimeFormat(locale, {\n year: 'numeric',\n month: 'long',\n }).format(refDate)\n\n const today = new Date()\n\n const weekdayNames = React.useMemo(() => {\n // 取 `days[0..6]` 的名字(gridStart 開始 7 天,正好一週)\n return days.slice(0, 7).map((d) =>\n new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(d),\n )\n }, [days, locale])\n\n const handleToday = () => setRefDate(new Date())\n const handlePrev = () => setRefDate(subMonths(refDate, 1))\n const handleNext = () => setRefDate(addMonths(refDate, 1))\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col w-full h-full bg-surface rounded-md border border-divider overflow-hidden',\n className,\n )}\n data-view={currentView}\n data-size={size}\n {...props}\n >\n {/* Toolbar:[◀] [今天] [▶] title [view tabs] [+ new] */}\n <div\n className={cn(\n 'flex items-center gap-2 shrink-0 border-b border-divider',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n )}\n >\n {/* 月份導覽 landmark:prev / 今天 / next 包成 <nav> 給 SR landmark 導航(per calendar.spec.md a11y 段) */}\n <nav className=\"flex items-center gap-2\" aria-label={navAriaLabel}>\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={prevAriaLabel}\n onClick={handlePrev}\n />\n <Button variant=\"tertiary\" size=\"sm\" onClick={handleToday}>\n {todayLabel}\n </Button>\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={nextAriaLabel}\n onClick={handleNext}\n />\n </nav>\n\n <h2 className=\"text-body-lg font-medium text-foreground flex-1 min-w-0 truncate ml-2\">\n {monthTitle}\n </h2>\n\n {/* View switcher:用 SegmentedControl(互斥多選一 canonical)——\n 對齊 CLAUDE.md「互斥分類選擇走 SegmentedControl,非 checked Button group」原則。\n Button 的 pressed 是「toggle 持續狀態」語意,不適合「單選 view 切換」 */}\n <SegmentedControl\n size=\"sm\"\n value={currentView}\n onValueChange={(v) => setView(v as CalendarView)}\n aria-label={viewToggleAriaLabel}\n >\n <SegmentedControlItem value=\"day\" disabled>日</SegmentedControlItem>\n <SegmentedControlItem value=\"week\" disabled>週</SegmentedControlItem>\n <SegmentedControlItem value=\"month\">月</SegmentedControlItem>\n </SegmentedControl>\n\n {onCreateEvent && (\n <Button variant=\"primary\" size=\"sm\" startIcon={Plus} onClick={onCreateEvent}>\n 新事件\n </Button>\n )}\n </div>\n\n {/* Weekday header */}\n <div className=\"grid grid-cols-7 border-b border-divider bg-muted\">\n {weekdayNames.map((name, i) => (\n <div\n key={i}\n className=\"px-2 py-1.5 text-caption text-fg-muted font-normal text-center\"\n >\n {name}\n </div>\n ))}\n </div>\n\n {/* Month grid:7 cols, ~5-6 rows。a11y(2026-04-25):WAI-ARIA grid 要求 row > gridcell\n 階層,chunk days 7 一組,wrap 成 role='row'(display:contents 保 CSS grid 佈局)。 */}\n <div\n className=\"grid grid-cols-7 flex-1 min-h-0\"\n role=\"grid\"\n aria-label={`月行事曆,${monthTitle}`}\n >\n {Array.from({ length: Math.ceil(days.length / 7) }, (_, rowIdx) => (\n <div key={rowIdx} role=\"row\" style={{ display: 'contents' }}>\n {days.slice(rowIdx * 7, rowIdx * 7 + 7).map((date) => {\n const inMonth = isSameMonth(date, refDate)\n const isToday = isSameDay(date, today)\n // 2026-06-01 allDay:全天事件排 cell 頂端(對齊 Google Calendar 全天列在上)\n const dayEvents = eventsOnDate(events, date).slice().sort((a, b) => Number(b.allDay ?? false) - Number(a.allDay ?? false))\n const visibleEvents = dayEvents.slice(0, MAX_TILES_PER_CELL)\n const overflowCount = dayEvents.length - visibleEvents.length\n\n return (\n // 2026-06-11 a11y(user 拍板 2c 修 code):cell 從 <button role=\"gridcell\"> 改非互動容器 —\n // W3C button 語義禁止互動後代,cell 內含 role=\"button\" 事件 tile = nested-interactive 違規。\n // 對齊 Google Calendar:gridcell = 容器,日期數字按鈕 = 日期級 keyboard 入口,tile 各自為 button。\n // div onClick 保留滑鼠「點 cell 空白處等同點日期」便利(keyboard 走日期數字按鈕,功能等價)。\n <div\n key={date.toISOString()}\n role=\"gridcell\"\n onClick={() => onDateClick?.(date)}\n className={cn(\n 'flex flex-col gap-1 min-h-28 p-1.5 text-left',\n 'border-r border-b border-divider last:border-r-0',\n '[&:nth-child(7n)]:border-r-0',\n 'hover:bg-neutral-hover transition-colors',\n !inMonth && 'bg-muted',\n )}\n >\n {/* Date number = keyboard 入口;今天/平日統一 24px 圓形 hit-area(WCAG 2.5.8 ≥24,\n 今天 pill 本就 24px,平日跟齊 → 跨 cell 數字光學對齊) */}\n <div className=\"flex items-start justify-end\">\n <button\n type=\"button\"\n aria-label={`${format(date, 'yyyy-MM-dd')},${dayEvents.length} 個事件`}\n onClick={(e) => {\n e.stopPropagation()\n onDateClick?.(date)\n }}\n className={cn(\n 'inline-flex items-center justify-center min-w-6 h-6 rounded-full text-body font-medium',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n isToday && 'px-2 bg-info text-on-emphasis',\n !isToday && !inMonth && 'text-fg-disabled',\n )}\n >\n {format(date, 'd')}\n </button>\n </div>\n\n {/* Event tiles */}\n <div className=\"flex flex-col gap-0.5 min-h-0\">\n {visibleEvents.map((event) => {\n const ec = event.color ?? 'blue'\n // 2026-06-01 allDay:淡底 + 左 accent 條 + medium = 「全天長條」視覺(區分有時間事件)\n const colorClass = event.allDay\n ? cn(EVENT_COLOR_CLASSES[ec], EVENT_ALLDAY_ACCENT[ec], 'font-medium')\n : EVENT_COLOR_CLASSES[ec]\n if (renderEventTile) {\n return (\n <div\n key={event.id}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n >\n {renderEventTile(event)}\n </div>\n )\n }\n return (\n <div\n key={event.id}\n role=\"button\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onEventClick?.(event)\n }\n }}\n aria-label={`事件:${event.title}`}\n className={cn(\n 'rounded-md px-1.5 py-0.5 text-caption truncate cursor-pointer transition-colors',\n // 2026-05-31 #22:事件 tile 是 focusable(tabIndex=0 role=button)但原無 focus ring\n // → WCAG 2.4.7 不合規。補 focus-visible ring 對齊日期格按鈕。\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n colorClass,\n )}\n >\n {event.title}\n </div>\n )\n })}\n {overflowCount > 0 && (\n <div className=\"text-caption text-fg-muted px-1.5\">\n +{overflowCount} more\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n ))}\n </div>\n </div>\n )\n})\nCalendar.displayName = \"Calendar\"\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 calendarMeta = {\n component: 'Calendar',\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: ['bg-muted', 'bg-neutral-hover', 'bg-info', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground'],\n ring: ['ring-ring'],\n },\n} as const\n\nexport { Calendar }\n"],"names":["Calendar"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"calendar.js","sources":["../../../src/components/Calendar/calendar.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { ChevronLeft, ChevronRight, Plus } from 'lucide-react'\nimport {\n startOfMonth,\n endOfMonth,\n startOfWeek,\n endOfWeek,\n eachDayOfInterval,\n format,\n isSameMonth,\n isSameDay,\n addMonths,\n subMonths,\n} from 'date-fns'\nimport { cn } from '@/lib/utils'\nimport { CAT_EVENT, CAT_ACCENT, type CategoricalHue } from '@/design-system/tokens/categorical-color'\nimport { Button } from '@/design-system/components/Button/button'\nimport { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'\n\n/**\n * Calendar — 事件檢視 canvas(月 view MVP)\n *\n * 定位:看事件的 page-level canvas,對齊 Notion Calendar / Google Calendar。\n * 完整 spec 見 `calendar.spec.md`。\n *\n * ── Layout Family ──\n * 非 4-Family,屬 page-composite(多區塊 Toolbar + Grid + EventTile)。\n *\n * ── MVP scope(本次 session)──\n * - 月 view 完整(toolbar / grid / event tile / today highlight / outside days)\n * - 週 / 日 view 是 tech debt\n * - 拖拉增刪 event 是 tech debt\n *\n * ── 與 DatePicker 的區分 ──\n * DatePicker 是「選日期」form control;Calendar 是「看事件」page canvas。\n * 名字相近但職責完全不同,spec 頂段明示分界。\n */\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface CalendarEvent {\n id: string\n title: string\n /** ISO 字串 \"YYYY-MM-DD\"(all-day)或 \"YYYY-MM-DDTHH:mm\"(timed) */\n start: string | Date\n end: string | Date\n allDay?: boolean\n /**\n * 事件類別色(categorical 色相,1:1 對 `--color-{hue}-*`)。**消費 categorical-color SSOT**,\n * 與 Tag / Avatar 共用同一組 12 色相。2026-06-04 修:原 `orange` 與 `red` 都誤接 deep-orange;\n * 改消費 SSOT 後 orange→`--color-orange-*`、red→`--color-red-*`(品牌紅 hue 25),各自獨立。\n */\n color?: CategoricalHue\n metadata?: Record<string, unknown>\n}\n\nexport type CalendarView = 'month' | 'week' | 'day'\n\nexport interface CalendarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** 當前 view(MVP 只 'month',其餘 view tech debt) */\n view?: CalendarView\n defaultView?: CalendarView\n onViewChange?: (view: CalendarView) => void\n\n /** 聚焦日期(月 view 的那個月) */\n referenceDate?: Date\n defaultReferenceDate?: Date\n onReferenceDateChange?: (date: Date) => void\n\n /** 事件資料 */\n events?: CalendarEvent[]\n\n /** 點 event tile 回調 */\n onEventClick?: (event: CalendarEvent) => void\n /** 點月 cell 回調(用於新增) */\n onDateClick?: (date: Date) => void\n /** 點新事件 CTA 回調 */\n onCreateEvent?: () => void\n\n /** 0 = Sunday, 1 = Monday。預設 0(對齊 Google Calendar 美系預設) */\n weekStartsOn?: 0 | 1\n\n /** 自訂 event tile 渲染 */\n renderEventTile?: (event: CalendarEvent) => React.ReactNode\n\n /** size(MVP 只 md;lg 為 tech debt) */\n size?: 'md' | 'lg'\n className?: string\n\n /** locale(預設 'en-US') */\n locale?: string\n\n /** ARIA labels for chrome controls. Override for i18n. */\n prevAriaLabel?: string\n nextAriaLabel?: string\n /** 月份導覽 <nav> landmark 的 aria-label。Override for i18n. */\n navAriaLabel?: string\n viewToggleAriaLabel?: string\n todayLabel?: string\n}\n\n// ── Event tile color tokens ─────────────────────────────────────────────────\n// **消費 categorical-color SSOT**(CAT_EVENT = subtle 底 + hover step-2;CAT_ACCENT = 左側 step-6\n// 實心條),與 Tag / Avatar 共用 12 色相,key X 一律對 `--color-X-*`(1:1)。\n// 2026-06-01 allDay:全天事件 = 淡底 tile + 左側實心 accent 條 + medium,視覺區分「全天長條」vs\n// 有時間事件;用 accent border 而非 solid fill 保文字對比安全。對齊 Google Calendar / Outlook 慣例。\nconst EVENT_COLOR_CLASSES = CAT_EVENT\nconst EVENT_ALLDAY_ACCENT = CAT_ACCENT\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction coerceDate(value: string | Date): Date {\n return value instanceof Date ? value : new Date(value)\n}\n\nfunction eventsOnDate(events: CalendarEvent[], date: Date): CalendarEvent[] {\n return events.filter((e) => {\n const start = coerceDate(e.start)\n const end = coerceDate(e.end)\n // 日期落在 [start, end] 範圍內(日精度)\n const d = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n const s = new Date(start.getFullYear(), start.getMonth(), start.getDate()).getTime()\n const eEnd = new Date(end.getFullYear(), end.getMonth(), end.getDate()).getTime()\n return d >= s && d <= eEnd\n })\n}\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nconst MAX_TILES_PER_CELL = 3\n\nconst Calendar = React.forwardRef<HTMLDivElement, CalendarProps>(function Calendar({\n view: viewProp,\n defaultView = 'month',\n onViewChange,\n referenceDate: referenceDateProp,\n defaultReferenceDate,\n onReferenceDateChange,\n events = [],\n onEventClick,\n onDateClick,\n onCreateEvent,\n weekStartsOn = 0,\n renderEventTile,\n size = 'md',\n className,\n locale = 'en-US',\n prevAriaLabel = '上個月', // i18n-allow: DS default; consumer override via prevAriaLabel prop\n nextAriaLabel = '下個月', // i18n-allow: DS default; consumer override via nextAriaLabel prop\n navAriaLabel = '行事曆月份導覽', // i18n-allow: DS default; consumer override via navAriaLabel prop\n viewToggleAriaLabel = '檢視切換', // i18n-allow: DS default; consumer override via viewToggleAriaLabel prop\n todayLabel = '今天', // i18n-allow: DS default; consumer override via todayLabel prop\n ...props\n}, ref) {\n // Controlled / uncontrolled refDate\n const [internalRef, setInternalRef] = React.useState<Date>(\n defaultReferenceDate ?? new Date(),\n )\n const refDate = referenceDateProp ?? internalRef\n const setRefDate = React.useCallback(\n (next: Date) => {\n if (referenceDateProp === undefined) setInternalRef(next)\n onReferenceDateChange?.(next)\n },\n [referenceDateProp, onReferenceDateChange],\n )\n\n // View state(MVP 只用 month,其他 tech debt)\n const [internalView, setInternalView] = React.useState<CalendarView>(defaultView)\n const currentView = viewProp ?? internalView\n const setView = React.useCallback(\n (next: CalendarView) => {\n if (viewProp === undefined) setInternalView(next)\n onViewChange?.(next)\n },\n [viewProp, onViewChange],\n )\n\n // Build month grid\n const days = React.useMemo(() => {\n const monthStart = startOfMonth(refDate)\n const monthEnd = endOfMonth(refDate)\n const gridStart = startOfWeek(monthStart, { weekStartsOn })\n const gridEnd = endOfWeek(monthEnd, { weekStartsOn })\n return eachDayOfInterval({ start: gridStart, end: gridEnd })\n }, [refDate, weekStartsOn])\n\n const monthTitle = new Intl.DateTimeFormat(locale, {\n year: 'numeric',\n month: 'long',\n }).format(refDate)\n\n const today = new Date()\n\n const weekdayNames = React.useMemo(() => {\n // 取 `days[0..6]` 的名字(gridStart 開始 7 天,正好一週)\n return days.slice(0, 7).map((d) =>\n new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(d),\n )\n }, [days, locale])\n\n const handleToday = () => setRefDate(new Date())\n const handlePrev = () => setRefDate(subMonths(refDate, 1))\n const handleNext = () => setRefDate(addMonths(refDate, 1))\n\n return (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col w-full h-full bg-surface rounded-md border border-divider overflow-hidden',\n className,\n )}\n data-view={currentView}\n data-size={size}\n {...props}\n >\n {/* Toolbar:[◀] [今天] [▶] title [view tabs] [+ new] */}\n <div\n className={cn(\n 'flex items-center gap-2 shrink-0 border-b border-divider',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n )}\n >\n {/* 月份導覽 landmark:prev / 今天 / next 包成 <nav> 給 SR landmark 導航(per calendar.spec.md a11y 段) */}\n <nav className=\"flex items-center gap-2\" aria-label={navAriaLabel}>\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={prevAriaLabel}\n onClick={handlePrev}\n />\n <Button variant=\"tertiary\" size=\"sm\" onClick={handleToday}>\n {todayLabel}\n </Button>\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={nextAriaLabel}\n onClick={handleNext}\n />\n </nav>\n\n <h2 className=\"text-body-lg font-medium text-foreground flex-1 min-w-0 truncate ml-2\">\n {monthTitle}\n </h2>\n\n {/* View switcher:用 SegmentedControl(互斥多選一 canonical)——\n 對齊 CLAUDE.md「互斥分類選擇走 SegmentedControl,非 checked Button group」原則。\n Button 的 pressed 是「toggle 持續狀態」語意,不適合「單選 view 切換」 */}\n <SegmentedControl\n size=\"sm\"\n value={currentView}\n onValueChange={(v) => setView(v as CalendarView)}\n aria-label={viewToggleAriaLabel}\n >\n <SegmentedControlItem value=\"day\" disabled>日</SegmentedControlItem>\n <SegmentedControlItem value=\"week\" disabled>週</SegmentedControlItem>\n <SegmentedControlItem value=\"month\">月</SegmentedControlItem>\n </SegmentedControl>\n\n {onCreateEvent && (\n <Button variant=\"primary\" size=\"sm\" startIcon={Plus} onClick={onCreateEvent}>\n 新事件\n </Button>\n )}\n </div>\n\n {/* Weekday header */}\n <div className=\"grid grid-cols-7 border-b border-divider bg-muted\">\n {weekdayNames.map((name, i) => (\n <div\n key={i}\n className=\"px-2 py-1.5 text-caption text-fg-muted font-normal text-center\"\n >\n {name}\n </div>\n ))}\n </div>\n\n {/* Month grid:7 cols, ~5-6 rows。a11y(2026-04-25):WAI-ARIA grid 要求 row > gridcell\n 階層,chunk days 7 一組,wrap 成 role='row'(display:contents 保 CSS grid 佈局)。 */}\n <div\n className=\"grid grid-cols-7 flex-1 min-h-0\"\n role=\"grid\"\n aria-label={`月行事曆,${monthTitle}`}\n >\n {Array.from({ length: Math.ceil(days.length / 7) }, (_, rowIdx) => (\n <div key={rowIdx} role=\"row\" style={{ display: 'contents' }}>\n {days.slice(rowIdx * 7, rowIdx * 7 + 7).map((date) => {\n const inMonth = isSameMonth(date, refDate)\n const isToday = isSameDay(date, today)\n // 2026-06-01 allDay:全天事件排 cell 頂端(對齊 Google Calendar 全天列在上)\n const dayEvents = eventsOnDate(events, date).slice().sort((a, b) => Number(b.allDay ?? false) - Number(a.allDay ?? false))\n const visibleEvents = dayEvents.slice(0, MAX_TILES_PER_CELL)\n const overflowCount = dayEvents.length - visibleEvents.length\n\n return (\n // 2026-06-11 a11y(user 拍板 2c 修 code):cell 從 <button role=\"gridcell\"> 改非互動容器 —\n // W3C button 語義禁止互動後代,cell 內含 role=\"button\" 事件 tile = nested-interactive 違規。\n // 對齊 Google Calendar:gridcell = 容器,日期數字按鈕 = 日期級 keyboard 入口,tile 各自為 button。\n // div onClick 保留滑鼠「點 cell 空白處等同點日期」便利(keyboard 走日期數字按鈕,功能等價)。\n <div\n key={date.toISOString()}\n role=\"gridcell\"\n onClick={() => onDateClick?.(date)}\n className={cn(\n 'flex flex-col gap-1 min-h-28 p-1.5 text-left',\n 'border-r border-b border-divider last:border-r-0',\n '[&:nth-child(7n)]:border-r-0',\n 'hover:bg-neutral-hover transition-colors',\n !inMonth && 'bg-muted',\n )}\n >\n {/* Date number = keyboard 入口;今天/平日統一 24px 圓形 hit-area(WCAG 2.5.8 ≥24,\n 今天 pill 本就 24px,平日跟齊 → 跨 cell 數字光學對齊) */}\n <div className=\"flex items-start justify-end\">\n <button\n type=\"button\"\n aria-label={`${format(date, 'yyyy-MM-dd')},${dayEvents.length} 個事件`}\n onClick={(e) => {\n e.stopPropagation()\n onDateClick?.(date)\n }}\n className={cn(\n 'inline-flex items-center justify-center min-w-6 h-6 rounded-full text-body font-medium',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n isToday && 'px-2 bg-info text-on-emphasis',\n !isToday && !inMonth && 'text-fg-disabled',\n )}\n >\n {format(date, 'd')}\n </button>\n </div>\n\n {/* Event tiles */}\n <div className=\"flex flex-col gap-0.5 min-h-0\">\n {visibleEvents.map((event) => {\n const ec = event.color ?? 'blue'\n // 2026-06-01 allDay:淡底 + 左 accent 條 + medium = 「全天長條」視覺(區分有時間事件)\n const colorClass = event.allDay\n ? cn(EVENT_COLOR_CLASSES[ec], EVENT_ALLDAY_ACCENT[ec], 'font-medium')\n : EVENT_COLOR_CLASSES[ec]\n if (renderEventTile) {\n return (\n <div\n key={event.id}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n >\n {renderEventTile(event)}\n </div>\n )\n }\n return (\n <div\n key={event.id}\n role=\"button\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation()\n onEventClick?.(event)\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onEventClick?.(event)\n }\n }}\n aria-label={`事件:${event.title}`}\n className={cn(\n 'rounded-md px-1.5 py-0.5 text-caption truncate cursor-pointer transition-colors',\n // 2026-05-31 #22:事件 tile 是 focusable(tabIndex=0 role=button)但原無 focus ring\n // → WCAG 2.4.7 不合規。補 focus-visible ring 對齊日期格按鈕。\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n colorClass,\n )}\n >\n {event.title}\n </div>\n )\n })}\n {overflowCount > 0 && (\n <div className=\"text-caption text-fg-muted px-1.5\">\n +{overflowCount} more\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n ))}\n </div>\n </div>\n )\n})\nCalendar.displayName = \"Calendar\"\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 calendarMeta = {\n component: 'Calendar',\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: ['bg-muted', 'bg-neutral-hover', 'bg-info', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground'],\n ring: ['ring-ring'],\n },\n} as const\n\nexport { Calendar }\n"],"names":["Calendar"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../../../src/components/Carousel/carousel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,gBAAgB,EAAE,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAKlF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,KAAK,WAAW,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAA;AAC1C,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAChE,KAAK,eAAe,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAA;AAC/C,KAAK,cAAc,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAA;AAE9C,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,CAAA;CACpC;AAyBD,QAAA,MAAM,QAAQ,6HA0Gb,CAAA;AAKD,QAAA,MAAM,eAAe,6GAkBnB,CAAA;AAGF,QAAA,MAAM,YAAY,6GAkBhB,CAAA;AAQF,KAAK,UAAU,GAAG;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAWD,QAAA,MAAM,gBAAgB,sFA8BrB,CAAA;AAGD,QAAA,MAAM,YAAY,sFA4BjB,CAAA;AAKD,QAAA,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../../../src/components/Carousel/carousel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,gBAAgB,EAAE,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAKlF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,KAAK,WAAW,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAA;AAC1C,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAChE,KAAK,eAAe,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAA;AAC/C,KAAK,cAAc,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAA;AAE9C,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAA;IACvC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,CAAA;CACpC;AAyBD,QAAA,MAAM,QAAQ,6HA0Gb,CAAA;AAKD,QAAA,MAAM,eAAe,6GAkBnB,CAAA;AAGF,QAAA,MAAM,YAAY,6GAkBhB,CAAA;AAQF,KAAK,UAAU,GAAG;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAWD,QAAA,MAAM,gBAAgB,sFA8BrB,CAAA;AAGD,QAAA,MAAM,YAAY,sFA4BjB,CAAA;AAKD,QAAA,MAAM,YAAY,6GAqDhB,CAAA;AAKF,eAAO,MAAM,YAAY;;;;;;;;;;;CAef,CAAA;AAEV,OAAO,EACL,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,KAAK,WAAW,GACjB,CAAA"}
|
|
@@ -228,19 +228,21 @@ const CarouselDots = React.forwardRef(({ className, ...props }, ref) => {
|
|
|
228
228
|
"flex items-center gap-1.5",
|
|
229
229
|
className
|
|
230
230
|
),
|
|
231
|
-
role: "
|
|
231
|
+
role: "group",
|
|
232
232
|
"aria-label": "輪播指示器",
|
|
233
233
|
...props,
|
|
234
234
|
children: scrollSnaps.map((_, i) => /* @__PURE__ */ jsx(
|
|
235
235
|
"button",
|
|
236
236
|
{
|
|
237
237
|
type: "button",
|
|
238
|
-
|
|
239
|
-
"aria-selected": i === selectedIndex,
|
|
238
|
+
"aria-current": i === selectedIndex ? "true" : void 0,
|
|
240
239
|
"aria-label": `跳至第 ${i + 1} 張`,
|
|
241
240
|
onClick: () => scrollTo(i),
|
|
242
241
|
className: cn(
|
|
243
242
|
"h-1.5 rounded-full transition-all",
|
|
243
|
+
// 2026-06-11 a11y(R2;Phase B codex 修重疊):hit-area 垂直擴至 24px、水平 ±3px(dot 6px+gap 6px → 中心距 12px,±3 恰相切零重疊;再寬必互搶點擊)
|
|
244
|
+
// — 視覺 dot 維持 6×6 不變,僅點擊目標擴大(anatomy story 原宣稱的 hit-area 機制補真)
|
|
245
|
+
'relative before:absolute before:-inset-y-[9px] before:-inset-x-[3px] before:content-[""]',
|
|
244
246
|
// Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義
|
|
245
247
|
// 跟其他「於飽和色底上的淺色前景」一致
|
|
246
248
|
"bg-on-emphasis/60 hover:bg-on-emphasis/80",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"carousel.js","sources":["../../../src/components/Carousel/carousel.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * Carousel — 圖片 / 內容水平(或垂直)輪播\n *\n * 實作基礎:shadcn `Carousel` 結構 + `embla-carousel-react` v8 engine + 本 DS token。\n *\n * ── 世界級對照 ──\n * shadcn Carousel(本元件主要參考)/ Ant Carousel / Polaris 無 /\n * Swiper(獨立 lib,功能更多但不在 DS 範疇)\n *\n * ── 視覺慣例(user 指示) ──\n * 預設「hover 上去」左右兩邊才出現 prev/next 按鈕,不打擾主視覺;\n * 指示器(dots)在底部中央,clickable。\n *\n * ── API(shadcn parity) ──\n * <Carousel opts plugins orientation>\n * <CarouselContent>\n * <CarouselItem>...</CarouselItem>\n * <CarouselItem>...</CarouselItem>\n * </CarouselContent>\n * <CarouselPrevious /> ← 左箭頭\n * <CarouselNext /> ← 右箭頭\n * <CarouselDots /> ← 本 DS 擴充(shadcn 無,Ant/Swiper 慣例)\n * </Carousel>\n */\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ninterface CarouselProps {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: 'horizontal' | 'vertical'\n setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n scrollTo: (i: number) => void\n canScrollPrev: boolean\n canScrollNext: boolean\n selectedIndex: number\n scrollSnaps: number[]\n orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const ctx = React.useContext(CarouselContext)\n if (!ctx) throw new Error('useCarousel 必須在 <Carousel> 內使用')\n return ctx\n}\n\n// ── Root ────────────────────────────────────────────────────────────────────\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = 'horizontal',\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },\n plugins,\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([])\n\n const onSelect = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setCanScrollPrev(a.canScrollPrev())\n setCanScrollNext(a.canScrollNext())\n setSelectedIndex(a.selectedScrollSnap())\n }, [])\n\n const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api])\n const scrollNext = React.useCallback(() => api?.scrollNext(), [api])\n const scrollTo = React.useCallback((i: number) => api?.scrollTo(i), [api])\n\n React.useEffect(() => {\n if (!api || !setApi) return\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) return\n setScrollSnaps(api.scrollSnapList())\n onSelect(api)\n api.on('reInit', onSelect)\n api.on('select', onSelect)\n return () => {\n api?.off('select', onSelect)\n api?.off('reInit', onSelect) // D3 fix: previously leaked — stale closure on remount\n }\n }, [api, onSelect])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // 鍵盤方向對齊內容捲動方向(APG 建議):horizontal → ←/→;vertical → ↑/↓\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'\n if (e.key === prevKey) { e.preventDefault(); scrollPrev() }\n else if (e.key === nextKey) { e.preventDefault(); scrollNext() }\n }\n\n const contextValue = React.useMemo(\n () => ({\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n }),\n [\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n ]\n )\n\n return (\n <CarouselContext.Provider value={contextValue}>\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn('group/carousel relative', className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"輪播\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n },\n)\nCarousel.displayName = 'Carousel'\n\n// ── Content / Item ──────────────────────────────────────────────────────────\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n className,\n )}\n {...props}\n />\n </div>\n )\n})\nCarouselContent.displayName = 'CarouselContent'\n\nconst CarouselItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { orientation } = useCarousel()\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n className={cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n className,\n )}\n {...props}\n />\n )\n})\nCarouselItem.displayName = 'CarouselItem'\n\n// ── Arrow buttons(hover 才顯示)────────────────────────────────────────────\n// 使用 DS Button (tertiary + iconOnly size md);hover-only 顯示由 wrapper 的\n// opacity transition 控制(Button 本身不負責)。此 wrapper 存在僅為絕對定位 +\n// hover/focus 可見性,不再覆寫 Button 的視覺 token。\n\ntype ArrowProps = {\n className?: string\n /** ARIA label. Override for i18n. Prev default: 「上一張」;Next default: 「下一張」 */\n 'aria-label'?: string\n}\n\nconst arrowWrapperClass = cn(\n 'absolute z-10',\n 'transition-opacity duration-200',\n 'opacity-0 group-hover/carousel:opacity-100',\n 'focus-within:opacity-100',\n '[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none',\n)\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(\n ({ className, 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'left-3 top-1/2 -translate-y-1/2'\n : 'top-3 left-1/2 -translate-x-1/2 rotate-90',\n className,\n )}\n >\n <Button\n ref={ref}\n variant=\"tertiary\"\n size=\"md\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={ariaLabel}\n disabled={!canScrollPrev}\n onClick={scrollPrev}\n // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,\n // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos\n // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。\n className=\"rounded-full\"\n />\n </div>\n )\n },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\nconst CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(\n ({ className, 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'right-3 top-1/2 -translate-y-1/2'\n : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n className,\n )}\n >\n <Button\n ref={ref}\n variant=\"tertiary\"\n size=\"md\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={ariaLabel}\n disabled={!canScrollNext}\n onClick={scrollNext}\n // documented exception:同 Previous,媒體導向 carousel 箭頭圓形\n className=\"rounded-full\"\n />\n </div>\n )\n },\n)\nCarouselNext.displayName = 'CarouselNext'\n\n// ── Dots indicator(底部中央)───────────────────────────────────────────────\n\nconst CarouselDots = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { scrollSnaps, selectedIndex, scrollTo } = useCarousel()\n if (scrollSnaps.length <= 1) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'absolute bottom-3 left-1/2 -translate-x-1/2 z-10',\n 'flex items-center gap-1.5',\n className,\n )}\n role=\"tablist\"\n aria-label=\"輪播指示器\" /* i18n-allow: DS default; 對齊同檔其他 aria-label 語言 */\n {...props}\n >\n {scrollSnaps.map((_, i) => (\n <button\n key={i}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === selectedIndex}\n aria-label={`跳至第 ${i + 1} 張`}\n onClick={() => scrollTo(i)}\n className={cn(\n 'h-1.5 rounded-full transition-all',\n // Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義\n // 跟其他「於飽和色底上的淺色前景」一致\n 'bg-on-emphasis/60 hover:bg-on-emphasis/80',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n i === selectedIndex ? 'w-6 bg-on-emphasis' : 'w-1.5',\n )}\n />\n ))}\n </div>\n )\n})\nCarouselDots.displayName = 'CarouselDots'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const carouselMeta = {\n component: 'Carousel',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n CarouselDots,\n type CarouselApi,\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"carousel.js","sources":["../../../src/components/Carousel/carousel.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * Carousel — 圖片 / 內容水平(或垂直)輪播\n *\n * 實作基礎:shadcn `Carousel` 結構 + `embla-carousel-react` v8 engine + 本 DS token。\n *\n * ── 世界級對照 ──\n * shadcn Carousel(本元件主要參考)/ Ant Carousel / Polaris 無 /\n * Swiper(獨立 lib,功能更多但不在 DS 範疇)\n *\n * ── 視覺慣例(user 指示) ──\n * 預設「hover 上去」左右兩邊才出現 prev/next 按鈕,不打擾主視覺;\n * 指示器(dots)在底部中央,clickable。\n *\n * ── API(shadcn parity) ──\n * <Carousel opts plugins orientation>\n * <CarouselContent>\n * <CarouselItem>...</CarouselItem>\n * <CarouselItem>...</CarouselItem>\n * </CarouselContent>\n * <CarouselPrevious /> ← 左箭頭\n * <CarouselNext /> ← 右箭頭\n * <CarouselDots /> ← 本 DS 擴充(shadcn 無,Ant/Swiper 慣例)\n * </Carousel>\n */\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ninterface CarouselProps {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: 'horizontal' | 'vertical'\n setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n scrollTo: (i: number) => void\n canScrollPrev: boolean\n canScrollNext: boolean\n selectedIndex: number\n scrollSnaps: number[]\n orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const ctx = React.useContext(CarouselContext)\n if (!ctx) throw new Error('useCarousel 必須在 <Carousel> 內使用')\n return ctx\n}\n\n// ── Root ────────────────────────────────────────────────────────────────────\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = 'horizontal',\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },\n plugins,\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([])\n\n const onSelect = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setCanScrollPrev(a.canScrollPrev())\n setCanScrollNext(a.canScrollNext())\n setSelectedIndex(a.selectedScrollSnap())\n }, [])\n\n const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api])\n const scrollNext = React.useCallback(() => api?.scrollNext(), [api])\n const scrollTo = React.useCallback((i: number) => api?.scrollTo(i), [api])\n\n React.useEffect(() => {\n if (!api || !setApi) return\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) return\n setScrollSnaps(api.scrollSnapList())\n onSelect(api)\n api.on('reInit', onSelect)\n api.on('select', onSelect)\n return () => {\n api?.off('select', onSelect)\n api?.off('reInit', onSelect) // D3 fix: previously leaked — stale closure on remount\n }\n }, [api, onSelect])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // 鍵盤方向對齊內容捲動方向(APG 建議):horizontal → ←/→;vertical → ↑/↓\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'\n if (e.key === prevKey) { e.preventDefault(); scrollPrev() }\n else if (e.key === nextKey) { e.preventDefault(); scrollNext() }\n }\n\n const contextValue = React.useMemo(\n () => ({\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n }),\n [\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n ]\n )\n\n return (\n <CarouselContext.Provider value={contextValue}>\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn('group/carousel relative', className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"輪播\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n },\n)\nCarousel.displayName = 'Carousel'\n\n// ── Content / Item ──────────────────────────────────────────────────────────\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n className,\n )}\n {...props}\n />\n </div>\n )\n})\nCarouselContent.displayName = 'CarouselContent'\n\nconst CarouselItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { orientation } = useCarousel()\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n className={cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n className,\n )}\n {...props}\n />\n )\n})\nCarouselItem.displayName = 'CarouselItem'\n\n// ── Arrow buttons(hover 才顯示)────────────────────────────────────────────\n// 使用 DS Button (tertiary + iconOnly size md);hover-only 顯示由 wrapper 的\n// opacity transition 控制(Button 本身不負責)。此 wrapper 存在僅為絕對定位 +\n// hover/focus 可見性,不再覆寫 Button 的視覺 token。\n\ntype ArrowProps = {\n className?: string\n /** ARIA label. Override for i18n. Prev default: 「上一張」;Next default: 「下一張」 */\n 'aria-label'?: string\n}\n\nconst arrowWrapperClass = cn(\n 'absolute z-10',\n 'transition-opacity duration-200',\n 'opacity-0 group-hover/carousel:opacity-100',\n 'focus-within:opacity-100',\n '[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none',\n)\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(\n ({ className, 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'left-3 top-1/2 -translate-y-1/2'\n : 'top-3 left-1/2 -translate-x-1/2 rotate-90',\n className,\n )}\n >\n <Button\n ref={ref}\n variant=\"tertiary\"\n size=\"md\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={ariaLabel}\n disabled={!canScrollPrev}\n onClick={scrollPrev}\n // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,\n // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos\n // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。\n className=\"rounded-full\"\n />\n </div>\n )\n },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\nconst CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(\n ({ className, 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'right-3 top-1/2 -translate-y-1/2'\n : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n className,\n )}\n >\n <Button\n ref={ref}\n variant=\"tertiary\"\n size=\"md\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={ariaLabel}\n disabled={!canScrollNext}\n onClick={scrollNext}\n // documented exception:同 Previous,媒體導向 carousel 箭頭圓形\n className=\"rounded-full\"\n />\n </div>\n )\n },\n)\nCarouselNext.displayName = 'CarouselNext'\n\n// ── Dots indicator(底部中央)───────────────────────────────────────────────\n\nconst CarouselDots = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { scrollSnaps, selectedIndex, scrollTo } = useCarousel()\n if (scrollSnaps.length <= 1) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'absolute bottom-3 left-1/2 -translate-x-1/2 z-10',\n 'flex items-center gap-1.5',\n className,\n )}\n // 2026-06-12 a11y(deep-audit R2 verdict b):APG grouped carousel 模型,非 tabs 模型。\n // APG:「picker buttons 收在 group 容器;現張的 picker button aria-disabled=true」\n // (https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)+ Embla 官方 Accessibility plugin\n // 同採 plain button + label、無 tab roles(https://www.embla-carousel.com/docs/plugins/accessibility)。\n // 為何不用完整 tabs 模型(slide=tabpanel + dot aria-controls):\n // (1) Embla snap ≠ slide — basis-1/3 多張並排時 1 dot 控多張,tab↔tabpanel 1:1 不成立\n // (2) tabs 鍵盤契約(方向鍵移 tablist 內 focus)與根容器方向鍵=切張(本檔 handleKeyDown)衝突\n // (3) compound API 下 CarouselItem 不知 index,aria-controls id 接線需額外 registration\n // 舊實作 role=\"tablist\"/\"tab\" + aria-selected 是半套 tabs(無 aria-controls、slide 非 tabpanel)= 非法混血。\n // 現張標記用 aria-current(非 APG 原文的 aria-disabled)— M23 DS 既有 canonical 優先:\n // FileViewer filmstrip(file-viewer.tsx aria-current={active ? 'true' : undefined})同類\n // 「視覺項導航 + 現張標記」已 codify group + button + aria-current;Bootstrap 5 indicators 同 idiom。\n role=\"group\"\n aria-label=\"輪播指示器\" /* i18n-allow: DS default; 對齊同檔其他 aria-label 語言 */\n {...props}\n >\n {scrollSnaps.map((_, i) => (\n <button\n key={i}\n type=\"button\"\n // 現張 dot aria-current(告知 SR「你在這張」;仍可 focus / 點擊為 no-op)\n aria-current={i === selectedIndex ? 'true' : undefined}\n aria-label={`跳至第 ${i + 1} 張`}\n onClick={() => scrollTo(i)}\n className={cn(\n 'h-1.5 rounded-full transition-all',\n // 2026-06-11 a11y(R2;Phase B codex 修重疊):hit-area 垂直擴至 24px、水平 ±3px(dot 6px+gap 6px → 中心距 12px,±3 恰相切零重疊;再寬必互搶點擊)\n // — 視覺 dot 維持 6×6 不變,僅點擊目標擴大(anatomy story 原宣稱的 hit-area 機制補真)\n 'relative before:absolute before:-inset-y-[9px] before:-inset-x-[3px] before:content-[\"\"]',\n // Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義\n // 跟其他「於飽和色底上的淺色前景」一致\n 'bg-on-emphasis/60 hover:bg-on-emphasis/80',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n i === selectedIndex ? 'w-6 bg-on-emphasis' : 'w-1.5',\n )}\n />\n ))}\n </div>\n )\n})\nCarouselDots.displayName = 'CarouselDots'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const carouselMeta = {\n component: 'Carousel',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n CarouselDots,\n type CarouselApi,\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,8 @@ export declare const CheckboxGroupContext: React.Context<{
|
|
|
14
14
|
* **禁止**外層加 `gap-y-*` / `space-y-*` / margin —— 會 double padding,違反 canonical。
|
|
15
15
|
*
|
|
16
16
|
* ── 對齊 RadioGroup canonical ──
|
|
17
|
-
*
|
|
17
|
+
* 本 DS `RadioGroup` wrapper 用 `grid`(無 gap;radio-group.tsx 自加——Radix primitive
|
|
18
|
+
* 本身 unstyled,無預設 layout);本元件垂直也用 `grid`(無 gap),
|
|
18
19
|
* horizontal 用 `flex flex-wrap gap-4`(短 label 並排才需水平 gap)。
|
|
19
20
|
*
|
|
20
21
|
* ── 為什麼 vertical 不給 gap 也能好看 ──
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox-group.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,eAAO,MAAM,oBAAoB;aAAkC,IAAI;SAAgB,CAAA;AAEvF
|
|
1
|
+
{"version":3,"file":"checkbox-group.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,eAAO,MAAM,oBAAoB;aAAkC,IAAI;SAAgB,CAAA;AAEvF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E;;;;OAIG;IACH,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAA;CACxC;AAKD,QAAA,MAAM,aAAa,2FAgBlB,CAAA;AAMD,OAAO,EAAE,aAAa,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox-group.js","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\n// ── CheckboxGroupContext ────────────────────────────────────────────────────\n// 讓內部 `<Checkbox>` 知道「我在 CheckboxGroup 裡」→ 即使 Field context 也存在,\n// checkbox 仍然保留自己的 label(每個 checkbox 是 group 的一個選項,FieldLabel 只是群組名稱)。\n//\n// 反之:Checkbox **單獨**塞進 Field(無 CheckboxGroup 包)時,Checkbox 才忽略自己的 label\n// 讓 FieldLabel 接管 —— 那是「binary toggle in Field」的場景。\n//\n// 這是 AR50 的根因:沒這個 context 前,CheckboxGroup 內部的 Checkbox 會誤以為「被 Field 包了」\n// 就把 label 全丟掉,導致 Sheet 範例的 Checkboxes 全部沒 label。\nexport const CheckboxGroupContext = React.createContext<{ inGroup: true } | null>(null)\n\n/**\n * CheckboxGroup — 多選 Checkbox 的 layout primitive\n *\n * ── Canonical 鐵律(2026-04-21 user 明示 + codified)──\n *\n * **垂直 CheckboxGroup 的 item 之間沒有外部 gap**。間距完全靠每個 Checkbox 內部的\n * `SelectionItem py = (field-height - 1lh) / 2` 公式生成 —— 單行高度對齊 field-height,\n * 多 row stacked 時 row-to-row 自然有 py × 2 的呼吸空間。\n *\n * **禁止**外層加 `gap-y-*` / `space-y-*` / margin —— 會 double padding,違反 canonical。\n *\n * ── 對齊 RadioGroup canonical ──\n *
|
|
1
|
+
{"version":3,"file":"checkbox-group.js","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\n// ── CheckboxGroupContext ────────────────────────────────────────────────────\n// 讓內部 `<Checkbox>` 知道「我在 CheckboxGroup 裡」→ 即使 Field context 也存在,\n// checkbox 仍然保留自己的 label(每個 checkbox 是 group 的一個選項,FieldLabel 只是群組名稱)。\n//\n// 反之:Checkbox **單獨**塞進 Field(無 CheckboxGroup 包)時,Checkbox 才忽略自己的 label\n// 讓 FieldLabel 接管 —— 那是「binary toggle in Field」的場景。\n//\n// 這是 AR50 的根因:沒這個 context 前,CheckboxGroup 內部的 Checkbox 會誤以為「被 Field 包了」\n// 就把 label 全丟掉,導致 Sheet 範例的 Checkboxes 全部沒 label。\nexport const CheckboxGroupContext = React.createContext<{ inGroup: true } | null>(null)\n\n/**\n * CheckboxGroup — 多選 Checkbox 的 layout primitive\n *\n * ── Canonical 鐵律(2026-04-21 user 明示 + codified)──\n *\n * **垂直 CheckboxGroup 的 item 之間沒有外部 gap**。間距完全靠每個 Checkbox 內部的\n * `SelectionItem py = (field-height - 1lh) / 2` 公式生成 —— 單行高度對齊 field-height,\n * 多 row stacked 時 row-to-row 自然有 py × 2 的呼吸空間。\n *\n * **禁止**外層加 `gap-y-*` / `space-y-*` / margin —— 會 double padding,違反 canonical。\n *\n * ── 對齊 RadioGroup canonical ──\n * 本 DS `RadioGroup` wrapper 用 `grid`(無 gap;radio-group.tsx 自加——Radix primitive\n * 本身 unstyled,無預設 layout);本元件垂直也用 `grid`(無 gap),\n * horizontal 用 `flex flex-wrap gap-4`(短 label 並排才需水平 gap)。\n *\n * ── 為什麼 vertical 不給 gap 也能好看 ──\n * SelectionItem py 的公式讓每個 Checkbox row 的「單行高度 = field-height」。row 堆疊時\n * 相鄰 row 的 py 相加 = 2×py ≈ 10-12px 真實 row-to-row 視覺呼吸空間。Atlassian / Ant /\n * Chakra / GitHub CheckboxGroup 皆同流派 —— row 高度定義 gap,不加外部 gap。\n *\n * ── 本 session 曾經的錯誤 + 釐清 ──\n * 早先曾以為「gap 要加」是因為「row 黏在一起」——但實際根因是 `Checkbox` 在 Field\n * context 內誤吞自己的 label(見 checkbox.tsx 的 CheckboxGroupContext 修正)。\n * label 回來後 row 自然撐開,不需要 gap。修正後的 canonical 鐵律:**zero gap,\n * 間距由 SelectionItem py 獨家擁有**。\n *\n * ── fieldLayout:block ──\n * 在 `<Field orientation=\"horizontal\">` 內,control area auto `items-start` +\n * padding-top 公式對齊第一個 item 的 label 第一行(見 field.spec.md)。\n *\n * ── 用法範例 ──\n * <CheckboxGroup>\n * <Checkbox label=\"待處理\" defaultChecked />\n * <Checkbox label=\"進行中\" defaultChecked />\n * <Checkbox label=\"已完成\" />\n * </CheckboxGroup>\n *\n * <CheckboxGroup orientation=\"horizontal\">\n * <Checkbox label=\"Email\" />\n * <Checkbox label=\"Push\" />\n * <Checkbox label=\"SMS\" />\n * </CheckboxGroup>\n */\n\nexport interface CheckboxGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * 排列方向。\n * - `vertical`(預設):多選項目堆疊,row 間距由 SelectionItem 擁有(外層 0 gap)\n * - `horizontal`:選項並排,gap-4 分隔(短 label 場景,如「Email / Push / SMS」)\n */\n orientation?: 'vertical' | 'horizontal'\n}\n\n// Module-level 常數(2026-04-22 D3 perf audit):provider value 無狀態,hoist 避免 render 重建\nconst CHECKBOX_GROUP_CTX_VALUE = { inGroup: true } as const\n\nconst CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(\n ({ className, orientation = 'vertical', ...props }, ref) => (\n <CheckboxGroupContext.Provider value={CHECKBOX_GROUP_CTX_VALUE}>\n <div\n ref={ref}\n role=\"group\"\n className={cn(\n // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)\n // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)\n orientation === 'vertical' ? 'grid' : 'flex flex-wrap gap-4',\n className\n )}\n {...props}\n />\n </CheckboxGroupContext.Provider>\n )\n)\nCheckboxGroup.displayName = 'CheckboxGroup'\n// Field layout declaration:block primitive(多項堆疊)——進入 <Field> 時\n// control area 自動切 items-start + padding-top 公式。對齊 RadioGroup 做法。\n;(CheckboxGroup as unknown as { fieldLayout: 'block' }).fieldLayout = 'block'\n\nexport { CheckboxGroup }\n"],"names":[],"mappings":";;;AAaO,MAAM,uBAAuB,MAAM,cAAwC,IAAI;AAyDtF,MAAM,2BAA2B,EAAE,SAAS,KAAA;AAE5C,MAAM,gBAAgB,MAAM;AAAA,EAC1B,CAAC,EAAE,WAAW,cAAc,YAAY,GAAG,MAAA,GAAS,QAClD,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,0BACpC,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,WAAW;AAAA;AAAA;AAAA,QAGT,gBAAgB,aAAa,SAAS;AAAA,QACtC;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA,EACN,CACF;AAEJ;AACA,cAAc,cAAc;AAG1B,cAAsD,cAAc;"}
|
|
@@ -2,6 +2,8 @@ import * as React from "react";
|
|
|
2
2
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
3
3
|
import { type VariantProps } from "class-variance-authority";
|
|
4
4
|
import type { FieldMode, FieldVariant } from "../../components/Field/field-types";
|
|
5
|
+
import type { LucideIcon } from "lucide-react";
|
|
6
|
+
import type { AvatarData } from "../../components/Avatar/avatar";
|
|
5
7
|
declare const checkboxVariants: (props?: ({
|
|
6
8
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
7
9
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
@@ -18,6 +20,10 @@ export interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof Che
|
|
|
18
20
|
* 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。
|
|
19
21
|
*/
|
|
20
22
|
description?: React.ReactNode;
|
|
23
|
+
/** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/
|
|
24
|
+
icon?: LucideIcon;
|
|
25
|
+
/** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */
|
|
26
|
+
avatar?: AvatarData;
|
|
21
27
|
/**
|
|
22
28
|
* readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。
|
|
23
29
|
* 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAE7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;
|
|
1
|
+
{"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAE7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAG3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAM1E,QAAA,MAAM,gBAAgB;;8EA8BrB,CAAA;AA6BD,MAAM,WAAW,aACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,iBAAiB,CAAC,IAAI,CAAC,EACnE,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,uHAAuH;IACvH,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,iDAAiD;IACjD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAID,QAAA,MAAM,QAAQ,yFA2Gb,CAAA;AAKD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -45,6 +45,8 @@ const Checkbox = React.forwardRef(
|
|
|
45
45
|
className,
|
|
46
46
|
size,
|
|
47
47
|
label,
|
|
48
|
+
icon,
|
|
49
|
+
avatar,
|
|
48
50
|
description,
|
|
49
51
|
readOnly = false,
|
|
50
52
|
disabled,
|
|
@@ -95,6 +97,8 @@ const Checkbox = React.forwardRef(
|
|
|
95
97
|
control: rootEl,
|
|
96
98
|
label: effectiveLabel,
|
|
97
99
|
description: effectiveDescription,
|
|
100
|
+
icon,
|
|
101
|
+
avatar,
|
|
98
102
|
htmlFor: inputId,
|
|
99
103
|
disabled: resolvedDisabled,
|
|
100
104
|
size: sizeKey
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode } from \"@/design-system/components/Field/field-context\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CheckboxProps\n extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldLabel 接管)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。\n */\n description?: React.ReactNode\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * display — **純展示**:渲染 ✓ / —(無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='display' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"display\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n\n // ── mode='display'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 ✓ / —(checked=true → ✓ / 其他 → —)。取代 BooleanDisplay。\n if (resolvedMode === 'display') {\n const isChecked = props.checked === true\n return isChecked\n ? <span className=\"text-foreground\">✓</span>\n : <span className=\"text-fg-muted\">—</span>\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={ref}\n disabled={resolvedDisabled}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"grid place-content-center text-current\">\n {props.checked === 'indeterminate'\n ? <Minus style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n : <Check style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n }\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n htmlFor={inputId}\n disabled={resolvedDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body' },\n },\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode } from \"@/design-system/components/Field/field-context\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CheckboxProps\n extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldLabel 接管)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * display — **純展示**:渲染 ✓ / —(無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='display' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"display\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n\n // ── mode='display'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 ✓ / —(checked=true → ✓ / 其他 → —)。取代 BooleanDisplay。\n if (resolvedMode === 'display') {\n const isChecked = props.checked === true\n return isChecked\n ? <span className=\"text-foreground\">✓</span>\n : <span className=\"text-fg-muted\">—</span>\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={ref}\n disabled={resolvedDisabled}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"grid place-content-center text-current\">\n {props.checked === 'indeterminate'\n ? <Minus style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n : <Check style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n }\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={resolvedDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body' },\n },\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,mBAAmB;AAAA,EACvB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MAAA;AAAA,IACN;AAAA,IAEF,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAGA,MAAM,gBAAwC,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAA;AAsBpE,MAAM,mBAA2C,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,IAAA;AA+CrE,MAAM,WAAW,MAAM;AAAA,EAIrB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA;AAAA,IAEA,SAAS;AAAA,IACT,IAAI;AAAA,IACJ,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UAAU,QAAQ;AACxB,UAAM,SAAS,cAAc,OAAO;AACpC,UAAM,kBAAkB,iBAAiB,OAAO;AAUhD,UAAM,WAAW,gBAAA;AACjB,UAAM,mBAAmB,MAAM,WAAW,oBAAoB;AAC9D,UAAM,eAAc,qCAAU,qBAAoB;AAClD,UAAM,eAAc,qDAAkB,aAAY;AAClD,UAAM,sBAAsB,eAAe,CAAC;AAC5C,UAAM,iBAAiB,sBAAsB,SAAY;AACzD,UAAM,uBAAuB,sBAAsB,SAAY;AAY/D,UAAM,cAAc,MAAM,MAAA;AAC1B,UAAM,UAAU,WAAW,cAAc,eAAe,qCAAU,OAAM;AAGxE,UAAM,mBAAmB,yBAAyB,QAAQ;AAC1D,UAAM,eAAe,qBAAqB,EAAE,MAAM,UAAU,UAAU;AACtE,UAAM,oBAAoB,YAAY,iBAAiB;AAIvD,QAAI,iBAAiB,WAAW;AAC9B,YAAM,YAAY,MAAM,YAAY;AACpC,aAAO,YACH,oBAAC,QAAA,EAAK,WAAU,mBAAkB,UAAA,IAAA,CAAC,IACnC,oBAAC,QAAA,EAAK,WAAU,iBAAgB,UAAA,KAAC;AAAA,IACvC;AAEA,UAAM,SACJ;AAAA,MAAC,kBAAkB;AAAA,MAAlB;AAAA,QACC,IAAI;AAAA,QACJ;AAAA,QACA,UAAU;AAAA,QACV,iBAAe,qBAAqB;AAAA,QACpC,iBAAe,qBAAqB;AAAA,QACpC,UAAU,oBAAoB,KAAK;AAAA,QACnC,oBAAkB,qCAAU;AAAA,QAC5B,WAAW,GAAG,iBAAiB,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,QAClD,GAAG;AAAA,QAEJ,UAAA,oBAAC,kBAAkB,WAAlB,EAA4B,WAAU,0CACpC,UAAA,MAAM,YAAY,kBACf,oBAAC,OAAA,EAAM,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAA,GAAU,aAAa,gBAAA,CAAiB,IAC/E,oBAAC,SAAM,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAA,GAAU,aAAa,iBAAiB,EAAA,CAErF;AAAA,MAAA;AAAA,IAAA;AAKJ,QAAI,kBAAkB,KAAM,QAAO;AAGnC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO;AAAA,QACP,aAAa;AAAA,QACb;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,UAAU;AAAA,QACV,MAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAGZ;AACF;AACA,SAAS,cAAc,kBAAkB,KAAK;AAIvC,MAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO;AAAA;AAAA,IAEL,IAAI,EAAE,aAAa,IAAI,UAAU,IAAI,YAAY,OAAA;AAAA,IACjD,IAAI,EAAE,aAAa,IAAI,UAAU,IAAI,YAAY,OAAA;AAAA,IACjD,IAAI,EAAE,aAAa,IAAI,UAAU,IAAI,YAAY,OAAA;AAAA,EAAO;AAAA,EAE1D,QAAQ,CAAC,WAAW,SAAS,iBAAiB,UAAU;AAAA,EACxD,QAAQ;AAAA,IACN,IAAI,CAAC,eAAe,cAAc,oBAAoB,YAAY;AAAA,IAClE,IAAI,CAAC,oBAAoB,qBAAqB,iBAAiB;AAAA,IAC/D,MAAM,CAAC,WAAW;AAAA,EAAA;AAAA,EAEpB,aAAa;AACf;"}
|
|
@@ -43,10 +43,10 @@ export declare const chipMeta: {
|
|
|
43
43
|
readonly family: 3;
|
|
44
44
|
readonly variants: {};
|
|
45
45
|
readonly sizes: {};
|
|
46
|
-
readonly states: readonly ["default", "hover", "
|
|
46
|
+
readonly states: readonly ["default", "hover", "selected", "focus-visible", "disabled"];
|
|
47
47
|
readonly tokens: {
|
|
48
48
|
readonly bg: readonly ["bg-surface"];
|
|
49
|
-
readonly fg: readonly ["text-fg-disabled", "text-fg-secondary", "text-foreground"];
|
|
49
|
+
readonly fg: readonly ["text-fg-disabled", "text-fg-secondary", "text-foreground", "text-primary-hover"];
|
|
50
50
|
readonly ring: readonly ["ring-ring"];
|
|
51
51
|
};
|
|
52
52
|
};
|
|
@@ -208,10 +208,11 @@ const chipMeta = {
|
|
|
208
208
|
family: 3,
|
|
209
209
|
variants: {},
|
|
210
210
|
sizes: {},
|
|
211
|
-
states: ["default", "hover", "
|
|
211
|
+
states: ["default", "hover", "selected", "focus-visible", "disabled"],
|
|
212
|
+
// selected = data-[state=on](primary-hover);cva 無 active 樣式
|
|
212
213
|
tokens: {
|
|
213
214
|
bg: ["bg-surface"],
|
|
214
|
-
fg: ["text-fg-disabled", "text-fg-secondary", "text-foreground"],
|
|
215
|
+
fg: ["text-fg-disabled", "text-fg-secondary", "text-foreground", "text-primary-hover"],
|
|
215
216
|
ring: ["ring-ring"]
|
|
216
217
|
}
|
|
217
218
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chip.js","sources":["../../../src/components/Chip/chip.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'\nimport { cva } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuCheckboxItem,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\nimport {\n useScrollEdges,\n useScrollByPage,\n buildFadeMask,\n ARROW_BUTTON_WIDTH,\n OverflowScrollArrow,\n OverflowMenuTriggerButton,\n} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'\n\n/**\n * Chip — Material Design Filter Chip\n *\n * 基於 Radix ToggleGroup,橋接設計系統 token。\n * 必須在 <ChipGroup> 內使用。\n *\n * ── 內部結構(鏡射 Button)──\n * [startIcon?] [<span px-1>label</span>] [<span gap-1>badge? + endIcon?</span>]\n *\n * ── Size ──\n * 單一 size = h-field-sm(28/32 density-aware)\n * 對齊 Material 3 / Atlassian / Polaris 世界級共識\n *\n * ── State ──\n * default bg-surface border-border text-fg-secondary\n * hover border-border-hover text-foreground(對齊 Input / SegmentedControl)\n * selected bg-surface(不變) border-primary-hover text-primary-hover\n * disabled cursor-not-allowed text-fg-disabled\n *\n * ── 詳見 chip.spec.md ──\n */\n\n// ── Chip item ────────────────────────────────────────────────────────────────\n\nconst chipVariants = cva(\n [\n 'inline-flex items-center justify-center',\n 'h-field-sm px-3 gap-1',\n 'rounded-full border border-border',\n // 預設文字: text-fg-secondary (neutral-8) — 對齊 SegmentedControl / Tabs 未選狀態\n 'bg-surface text-fg-secondary',\n 'text-body leading-compact font-medium whitespace-nowrap',\n 'transition-colors duration-150',\n 'cursor-pointer select-none',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n // hover(未選):border 加深一階 + 文字轉深,對齊 Input / SegmentedControl hover\n 'hover:border-border-hover hover:text-foreground',\n // selected: 文字 + 邊框都用 primary-hover,底色維持 bg-surface 不變\n // ── pill 風格 canonical 選中規則,跟 SegmentedControl 完全一致:\n // primary-hover 同時染文字和線條;底色不改 (不用 primary-subtle)。\n 'data-[state=on]:border-primary-hover data-[state=on]:text-primary-hover',\n // disabled:cursor-not-allowed + 鎖 hover 不變色\n // 不用 pointer-events-none(否則 cursor-not-allowed 不會顯示)\n 'disabled:cursor-not-allowed disabled:text-fg-disabled',\n 'disabled:hover:border-border disabled:hover:text-fg-disabled',\n ]\n)\n\nexport interface ChipProps\n extends React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> {\n /** 左側 icon(LucideIcon),最多一個 */\n startIcon?: LucideIcon\n /** 右側 badge(通常是計數指示器)*/\n badge?: React.ReactNode\n /** 右側 icon(少見,通常是 ChevronDown 指示可展開)*/\n endIcon?: LucideIcon\n}\n\nconst Chip = React.forwardRef<\n React.ElementRef<typeof ToggleGroupPrimitive.Item>,\n ChipProps\n>(({ className, startIcon: StartIcon, badge, endIcon: EndIcon, children, ...props }, ref) => {\n const hasSuffix = badge != null || EndIcon !== undefined\n\n return (\n <ToggleGroupPrimitive.Item\n ref={ref}\n className={cn(chipVariants(), className)}\n {...props}\n >\n {StartIcon && <StartIcon size={16} aria-hidden />}\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={16} aria-hidden />}\n </span>\n )}\n </ToggleGroupPrimitive.Item>\n )\n})\nChip.displayName = 'Chip'\n\n// ── ChipGroup ────────────────────────────────────────────────────────────────\n\nexport type ChipGroupLayout = 'wrap' | 'scroll' | 'menu'\n\nexport type ChipGroupProps = React.ComponentPropsWithoutRef<\n typeof ToggleGroupPrimitive.Root\n> & {\n /** Overflow 處理模式。預設 `wrap`(塞不下換行)。詳見 chip.spec.md */\n layout?: ChipGroupLayout\n}\n\nconst ChipGroup = React.forwardRef<HTMLDivElement, ChipGroupProps>(\n ({ layout = 'wrap', className, children, ...props }, ref) => {\n if (layout === 'scroll') {\n return (\n <ScrollChipGroup ref={ref} className={className} {...props}>\n {children}\n </ScrollChipGroup>\n )\n }\n if (layout === 'menu') {\n return (\n <MenuChipGroup ref={ref} className={className} {...props}>\n {children}\n </MenuChipGroup>\n )\n }\n // wrap(預設)\n return (\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn('flex flex-wrap gap-2', className)}\n {...props}\n >\n {children}\n </ToggleGroupPrimitive.Root>\n )\n }\n)\nChipGroup.displayName = 'ChipGroup'\n\n// ── Scroll / Menu modes ──\n// Fade mask / arrows / menu trigger 全部從 horizontal-overflow pattern module 取用。\n// 詳見 `patterns/horizontal-overflow/horizontal-overflow.spec.md`。\n//\n// Canonical 規則:所有 overflow affordance 一律是 Button text sm iconOnly,\n// 不論在 Chip / Tab / Step / SegmentedControl。Chip menu trigger 曾經用\n// chip-shape 圓形 button,已改回 text button 對齊 mental model。\n\n// ── ScrollChipGroup ──────────────────────────────────────────────────────────\n\nconst ScrollChipGroup = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const scrollByPage = useScrollByPage(scrollRef)\n const maskImage = buildFadeMask({\n canScroll,\n atStart,\n atEnd,\n reserveArrowWidth: ARROW_BUTTON_WIDTH,\n })\n\n return (\n <div className=\"relative\">\n <div\n ref={scrollRef}\n className=\"overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage, WebkitMaskImage: maskImage }}\n >\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn('flex flex-nowrap gap-2 w-fit', className)}\n {...props}\n >\n {children}\n </ToggleGroupPrimitive.Root>\n </div>\n {!atStart && canScroll && (\n <OverflowScrollArrow direction=\"left\" onClick={() => scrollByPage('left')} />\n )}\n {!atEnd && canScroll && (\n <OverflowScrollArrow direction=\"right\" onClick={() => scrollByPage('right')} />\n )}\n </div>\n )\n})\nScrollChipGroup.displayName = 'ScrollChipGroup'\n\n// ── MenuChipGroup ────────────────────────────────────────────────────────────\n// Show-all navigator pattern (Chrome tab dropdown / VS Code editor tabs):\n// - Menu 永遠顯示全部 chips,每個都用 DropdownMenuCheckboxItem + checked 反映 selection\n// - 點 menu item = toggle selection + scrollIntoView,把該 chip 捲到中央\n// - 不用動態 overflow 計算,menu 內容穩定\n//\n// Menu trigger 使用 canonical `<OverflowMenuTriggerButton>`(= Button text sm iconOnly\n// + ChevronDown),跟 Tabs menu trigger 完全同一套——overflow affordance 屬於工具層,\n// 不該用 chip 自己的視覺語言(圓形 outlined)去渲染,否則 mental model 錯誤(使用者\n// 會誤以為是第 N+1 個可選 chip)。\n//\n// 過去 Chip menu trigger 曾用 `chipVariants() + aspect-square + p-0` 做成圓形 pill,\n// 已按 `horizontal-overflow.spec.md` 的 canonical 規則改回 text button。\n//\n// Fade mask 仍保留(reserveArrowWidth: 0),軟化內容硬邊。\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuChipGroup = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n\n // Local ref map — 追蹤每個 chip 的 DOM 元素供 scrollIntoView 使用。\n // 不用 useOverflowIndices 因為 menu 永遠顯示全部, 不需要動態 overflow 計算。\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const itemRefs = React.useRef<Map<number, HTMLElement>>(new Map())\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const scrollRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current) }, [])\n const registerItem = React.useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (el) itemRefs.current.set(index, el)\n else itemRefs.current.delete(index)\n },\n []\n )\n\n const menuMaskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: 0 })\n\n const items = React.useMemo(\n () => React.Children.toArray(children).filter(React.isValidElement) as React.ReactElement[],\n [children]\n )\n\n const groupType = (props as { type?: 'single' | 'multiple' }).type ?? 'single'\n const groupValue = (props as { value?: string | string[] }).value\n const groupOnValueChange = (\n props as { onValueChange?: (value: string | string[]) => void }\n ).onValueChange\n\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const isSelected = React.useCallback(\n (chipValue: string): boolean => {\n if (groupType === 'multiple') {\n return Array.isArray(groupValue) && groupValue.includes(chipValue)\n }\n return groupValue === chipValue\n },\n [groupType, groupValue]\n )\n\n // code-quality-allow: long-function — multi / single / none selection mode each has 事務性 branch,拆 fn 會跨 fn 傳 chipValue/index/groupOnValueChange state 反而難讀\n const toggleFromMenu = React.useCallback(\n (chipValue: string, index: number) => {\n if (!groupOnValueChange) {\n if (import.meta.env?.DEV) {\n console.warn(\n '[ChipGroup] layout=\"menu\" 需要 controlled 使用(請傳 value + onValueChange),否則 menu items 無法同步主 chip 選擇狀態。'\n )\n }\n return\n }\n if (groupType === 'multiple') {\n const current = (Array.isArray(groupValue) ? groupValue : []) as string[]\n const next = current.includes(chipValue)\n ? current.filter((v) => v !== chipValue)\n : [...current, chipValue]\n ;(groupOnValueChange as (v: string[]) => void)(next)\n } else {\n ;(groupOnValueChange as (v: string) => void)(chipValue)\n }\n // scrollIntoView: 讓剛選中的 chip 出現在視圖中央\n if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current)\n scrollRafIdRef.current = requestAnimationFrame(() => {\n scrollRafIdRef.current = 0\n itemRefs.current.get(index)?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })\n })\n },\n [groupType, groupValue, groupOnValueChange]\n )\n\n const enhancedItems = items.map((child, i) =>\n React.cloneElement(\n child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,\n { ref: registerItem(i) }\n )\n )\n\n return (\n <div className=\"flex items-center gap-2\">\n <div\n ref={scrollRef}\n className=\"flex-1 min-w-0 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage: menuMaskImage, WebkitMaskImage: menuMaskImage }}\n >\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn('flex flex-nowrap gap-2 w-fit', className)}\n {...props}\n >\n {enhancedItems}\n </ToggleGroupPrimitive.Root>\n </div>\n {canScroll && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <OverflowMenuTriggerButton\n aria-label={`選項選單(共 ${items.length} 個)`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {items.map((chip, index) => {\n const chipProps = chip.props as { value?: string; children?: React.ReactNode; disabled?: boolean }\n const chipValue = chipProps.value\n if (typeof chipValue !== 'string') return null\n return (\n <DropdownMenuCheckboxItem\n key={chipValue}\n checked={isSelected(chipValue)}\n disabled={chipProps.disabled}\n onCheckedChange={() => toggleFromMenu(chipValue, index)}\n >\n {chipProps.children}\n </DropdownMenuCheckboxItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n )\n})\nMenuChipGroup.displayName = 'MenuChipGroup'\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 chipMeta = {\n component: 'Chip',\n family: 3,\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n} as const\n\nexport { Chip, ChipGroup, chipVariants }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chip.js","sources":["../../../src/components/Chip/chip.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'\nimport { cva } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuCheckboxItem,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\nimport {\n useScrollEdges,\n useScrollByPage,\n buildFadeMask,\n ARROW_BUTTON_WIDTH,\n OverflowScrollArrow,\n OverflowMenuTriggerButton,\n} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'\n\n/**\n * Chip — Material Design Filter Chip\n *\n * 基於 Radix ToggleGroup,橋接設計系統 token。\n * 必須在 <ChipGroup> 內使用。\n *\n * ── 內部結構(鏡射 Button)──\n * [startIcon?] [<span px-1>label</span>] [<span gap-1>badge? + endIcon?</span>]\n *\n * ── Size ──\n * 單一 size = h-field-sm(28/32 density-aware)\n * 對齊 Material 3 / Atlassian / Polaris 世界級共識\n *\n * ── State ──\n * default bg-surface border-border text-fg-secondary\n * hover border-border-hover text-foreground(對齊 Input / SegmentedControl)\n * selected bg-surface(不變) border-primary-hover text-primary-hover\n * disabled cursor-not-allowed text-fg-disabled\n *\n * ── 詳見 chip.spec.md ──\n */\n\n// ── Chip item ────────────────────────────────────────────────────────────────\n\nconst chipVariants = cva(\n [\n 'inline-flex items-center justify-center',\n 'h-field-sm px-3 gap-1',\n 'rounded-full border border-border',\n // 預設文字: text-fg-secondary (neutral-8) — 對齊 SegmentedControl / Tabs 未選狀態\n 'bg-surface text-fg-secondary',\n 'text-body leading-compact font-medium whitespace-nowrap',\n 'transition-colors duration-150',\n 'cursor-pointer select-none',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n // hover(未選):border 加深一階 + 文字轉深,對齊 Input / SegmentedControl hover\n 'hover:border-border-hover hover:text-foreground',\n // selected: 文字 + 邊框都用 primary-hover,底色維持 bg-surface 不變\n // ── pill 風格 canonical 選中規則,跟 SegmentedControl 完全一致:\n // primary-hover 同時染文字和線條;底色不改 (不用 primary-subtle)。\n 'data-[state=on]:border-primary-hover data-[state=on]:text-primary-hover',\n // disabled:cursor-not-allowed + 鎖 hover 不變色\n // 不用 pointer-events-none(否則 cursor-not-allowed 不會顯示)\n 'disabled:cursor-not-allowed disabled:text-fg-disabled',\n 'disabled:hover:border-border disabled:hover:text-fg-disabled',\n ]\n)\n\nexport interface ChipProps\n extends React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> {\n /** 左側 icon(LucideIcon),最多一個 */\n startIcon?: LucideIcon\n /** 右側 badge(通常是計數指示器)*/\n badge?: React.ReactNode\n /** 右側 icon(少見,通常是 ChevronDown 指示可展開)*/\n endIcon?: LucideIcon\n}\n\nconst Chip = React.forwardRef<\n React.ElementRef<typeof ToggleGroupPrimitive.Item>,\n ChipProps\n>(({ className, startIcon: StartIcon, badge, endIcon: EndIcon, children, ...props }, ref) => {\n const hasSuffix = badge != null || EndIcon !== undefined\n\n return (\n <ToggleGroupPrimitive.Item\n ref={ref}\n className={cn(chipVariants(), className)}\n {...props}\n >\n {StartIcon && <StartIcon size={16} aria-hidden />}\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={16} aria-hidden />}\n </span>\n )}\n </ToggleGroupPrimitive.Item>\n )\n})\nChip.displayName = 'Chip'\n\n// ── ChipGroup ────────────────────────────────────────────────────────────────\n\nexport type ChipGroupLayout = 'wrap' | 'scroll' | 'menu'\n\nexport type ChipGroupProps = React.ComponentPropsWithoutRef<\n typeof ToggleGroupPrimitive.Root\n> & {\n /** Overflow 處理模式。預設 `wrap`(塞不下換行)。詳見 chip.spec.md */\n layout?: ChipGroupLayout\n}\n\nconst ChipGroup = React.forwardRef<HTMLDivElement, ChipGroupProps>(\n ({ layout = 'wrap', className, children, ...props }, ref) => {\n if (layout === 'scroll') {\n return (\n <ScrollChipGroup ref={ref} className={className} {...props}>\n {children}\n </ScrollChipGroup>\n )\n }\n if (layout === 'menu') {\n return (\n <MenuChipGroup ref={ref} className={className} {...props}>\n {children}\n </MenuChipGroup>\n )\n }\n // wrap(預設)\n return (\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn('flex flex-wrap gap-2', className)}\n {...props}\n >\n {children}\n </ToggleGroupPrimitive.Root>\n )\n }\n)\nChipGroup.displayName = 'ChipGroup'\n\n// ── Scroll / Menu modes ──\n// Fade mask / arrows / menu trigger 全部從 horizontal-overflow pattern module 取用。\n// 詳見 `patterns/horizontal-overflow/horizontal-overflow.spec.md`。\n//\n// Canonical 規則:所有 overflow affordance 一律是 Button text sm iconOnly,\n// 不論在 Chip / Tab / Step / SegmentedControl。Chip menu trigger 曾經用\n// chip-shape 圓形 button,已改回 text button 對齊 mental model。\n\n// ── ScrollChipGroup ──────────────────────────────────────────────────────────\n\nconst ScrollChipGroup = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const scrollByPage = useScrollByPage(scrollRef)\n const maskImage = buildFadeMask({\n canScroll,\n atStart,\n atEnd,\n reserveArrowWidth: ARROW_BUTTON_WIDTH,\n })\n\n return (\n <div className=\"relative\">\n <div\n ref={scrollRef}\n className=\"overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage, WebkitMaskImage: maskImage }}\n >\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn('flex flex-nowrap gap-2 w-fit', className)}\n {...props}\n >\n {children}\n </ToggleGroupPrimitive.Root>\n </div>\n {!atStart && canScroll && (\n <OverflowScrollArrow direction=\"left\" onClick={() => scrollByPage('left')} />\n )}\n {!atEnd && canScroll && (\n <OverflowScrollArrow direction=\"right\" onClick={() => scrollByPage('right')} />\n )}\n </div>\n )\n})\nScrollChipGroup.displayName = 'ScrollChipGroup'\n\n// ── MenuChipGroup ────────────────────────────────────────────────────────────\n// Show-all navigator pattern (Chrome tab dropdown / VS Code editor tabs):\n// - Menu 永遠顯示全部 chips,每個都用 DropdownMenuCheckboxItem + checked 反映 selection\n// - 點 menu item = toggle selection + scrollIntoView,把該 chip 捲到中央\n// - 不用動態 overflow 計算,menu 內容穩定\n//\n// Menu trigger 使用 canonical `<OverflowMenuTriggerButton>`(= Button text sm iconOnly\n// + ChevronDown),跟 Tabs menu trigger 完全同一套——overflow affordance 屬於工具層,\n// 不該用 chip 自己的視覺語言(圓形 outlined)去渲染,否則 mental model 錯誤(使用者\n// 會誤以為是第 N+1 個可選 chip)。\n//\n// 過去 Chip menu trigger 曾用 `chipVariants() + aspect-square + p-0` 做成圓形 pill,\n// 已按 `horizontal-overflow.spec.md` 的 canonical 規則改回 text button。\n//\n// Fade mask 仍保留(reserveArrowWidth: 0),軟化內容硬邊。\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuChipGroup = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n\n // Local ref map — 追蹤每個 chip 的 DOM 元素供 scrollIntoView 使用。\n // 不用 useOverflowIndices 因為 menu 永遠顯示全部, 不需要動態 overflow 計算。\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const itemRefs = React.useRef<Map<number, HTMLElement>>(new Map())\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const scrollRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current) }, [])\n const registerItem = React.useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (el) itemRefs.current.set(index, el)\n else itemRefs.current.delete(index)\n },\n []\n )\n\n const menuMaskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: 0 })\n\n const items = React.useMemo(\n () => React.Children.toArray(children).filter(React.isValidElement) as React.ReactElement[],\n [children]\n )\n\n const groupType = (props as { type?: 'single' | 'multiple' }).type ?? 'single'\n const groupValue = (props as { value?: string | string[] }).value\n const groupOnValueChange = (\n props as { onValueChange?: (value: string | string[]) => void }\n ).onValueChange\n\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const isSelected = React.useCallback(\n (chipValue: string): boolean => {\n if (groupType === 'multiple') {\n return Array.isArray(groupValue) && groupValue.includes(chipValue)\n }\n return groupValue === chipValue\n },\n [groupType, groupValue]\n )\n\n // code-quality-allow: long-function — multi / single / none selection mode each has 事務性 branch,拆 fn 會跨 fn 傳 chipValue/index/groupOnValueChange state 反而難讀\n const toggleFromMenu = React.useCallback(\n (chipValue: string, index: number) => {\n if (!groupOnValueChange) {\n if (import.meta.env?.DEV) {\n console.warn(\n '[ChipGroup] layout=\"menu\" 需要 controlled 使用(請傳 value + onValueChange),否則 menu items 無法同步主 chip 選擇狀態。'\n )\n }\n return\n }\n if (groupType === 'multiple') {\n const current = (Array.isArray(groupValue) ? groupValue : []) as string[]\n const next = current.includes(chipValue)\n ? current.filter((v) => v !== chipValue)\n : [...current, chipValue]\n ;(groupOnValueChange as (v: string[]) => void)(next)\n } else {\n ;(groupOnValueChange as (v: string) => void)(chipValue)\n }\n // scrollIntoView: 讓剛選中的 chip 出現在視圖中央\n if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current)\n scrollRafIdRef.current = requestAnimationFrame(() => {\n scrollRafIdRef.current = 0\n itemRefs.current.get(index)?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })\n })\n },\n [groupType, groupValue, groupOnValueChange]\n )\n\n const enhancedItems = items.map((child, i) =>\n React.cloneElement(\n child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,\n { ref: registerItem(i) }\n )\n )\n\n return (\n <div className=\"flex items-center gap-2\">\n <div\n ref={scrollRef}\n className=\"flex-1 min-w-0 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage: menuMaskImage, WebkitMaskImage: menuMaskImage }}\n >\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn('flex flex-nowrap gap-2 w-fit', className)}\n {...props}\n >\n {enhancedItems}\n </ToggleGroupPrimitive.Root>\n </div>\n {canScroll && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <OverflowMenuTriggerButton\n aria-label={`選項選單(共 ${items.length} 個)`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {items.map((chip, index) => {\n const chipProps = chip.props as { value?: string; children?: React.ReactNode; disabled?: boolean }\n const chipValue = chipProps.value\n if (typeof chipValue !== 'string') return null\n return (\n <DropdownMenuCheckboxItem\n key={chipValue}\n checked={isSelected(chipValue)}\n disabled={chipProps.disabled}\n onCheckedChange={() => toggleFromMenu(chipValue, index)}\n >\n {chipProps.children}\n </DropdownMenuCheckboxItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n )\n})\nMenuChipGroup.displayName = 'MenuChipGroup'\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 chipMeta = {\n component: 'Chip',\n family: 3,\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'], // selected = data-[state=on](primary-hover);cva 無 active 樣式\n tokens: {\n bg: ['bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary-hover'],\n ring: ['ring-ring'],\n },\n} as const\n\nexport { Chip, ChipGroup, chipVariants }\n"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,7 @@ export declare const circularProgressMeta: {
|
|
|
28
28
|
readonly family: null;
|
|
29
29
|
readonly variants: {};
|
|
30
30
|
readonly sizes: {};
|
|
31
|
-
readonly states: readonly ["default"
|
|
31
|
+
readonly states: readonly ["default"];
|
|
32
32
|
readonly tokens: {
|
|
33
33
|
readonly bg: readonly [];
|
|
34
34
|
readonly fg: readonly ["text-fg-muted", "text-foreground", "text-info"];
|
|
@@ -104,7 +104,8 @@ const circularProgressMeta = {
|
|
|
104
104
|
// non-family composite / overlay / layout
|
|
105
105
|
variants: {},
|
|
106
106
|
sizes: {},
|
|
107
|
-
states: ["default"
|
|
107
|
+
states: ["default"],
|
|
108
|
+
// 無 hover / focus / active(spec);disabled 屬 consumer host(spec「邊界案例」)
|
|
108
109
|
tokens: {
|
|
109
110
|
bg: [],
|
|
110
111
|
fg: ["text-fg-muted", "text-foreground", "text-info"],
|