@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.82
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 +1 -1
- package/dist/components/Alert/alert.d.ts +5 -0
- package/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +4 -1
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +3 -2
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +8 -3
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Badge/badge.d.ts.map +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 +14 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +9 -3
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +4 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
- 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 +73 -44
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +3 -0
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +3 -1
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +40 -17
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +3 -1
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +6 -3
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +26 -4
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +3 -0
- package/dist/components/Checkbox/index.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +9 -3
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +46 -13
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +5 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js +10 -7
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +3 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +11 -7
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +2 -2
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +4 -2
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +3 -0
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +38 -7
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +213 -63
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-tree.d.ts +5 -0
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +3 -3
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +52 -6
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +2 -8
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +139 -109
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts.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 +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +2 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +1 -0
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +27 -7
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +2 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/use-form-validation.d.ts +5 -1
- package/dist/components/Field/use-form-validation.d.ts.map +1 -1
- package/dist/components/Field/use-form-validation.js +31 -21
- package/dist/components/Field/use-form-validation.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +3 -2
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +22 -10
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/can-render-image.d.ts +6 -0
- package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
- package/dist/components/FileViewer/can-render-image.js +11 -0
- package/dist/components/FileViewer/can-render-image.js.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +2 -2
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +24 -16
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts +0 -5
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +1 -9
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +3 -1
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/Input/input.d.ts +26 -3
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +12 -8
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +3 -3
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +29 -28
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +5 -4
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +5 -0
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +11 -5
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +17 -8
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +6 -0
- package/dist/components/Pagination/index.js.map +1 -0
- package/dist/components/Pagination/pagination.d.ts +49 -0
- package/dist/components/Pagination/pagination.d.ts.map +1 -0
- package/dist/components/Pagination/pagination.js +182 -0
- package/dist/components/Pagination/pagination.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +38 -36
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +4 -0
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +3 -3
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +58 -32
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +11 -3
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js +14 -4
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +28 -3
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.js +3 -3
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
- package/dist/components/ScrollArea/scroll-area.js +2 -2
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +23 -10
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +10 -4
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +57 -14
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +4 -2
- package/dist/components/SelectMenu/select-menu.d.ts +31 -6
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +169 -85
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/Separator/separator.js +1 -1
- package/dist/components/Separator/separator.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +1 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +3 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +2 -2
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +28 -10
- 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 -2
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +7 -3
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +6 -3
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +2 -2
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +47 -9
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +8 -5
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +45 -33
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +20 -4
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -1
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +24 -13
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +2 -2
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +23 -12
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/toast.d.ts +1 -1
- package/dist/components/Toast/toast.d.ts.map +1 -1
- package/dist/components/Toast/toast.js +7 -3
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/tooltip.js +1 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +11 -5
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-controllable.d.ts.map +1 -1
- package/dist/hooks/use-controllable.js +4 -2
- package/dist/hooks/use-controllable.js.map +1 -1
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
- package/dist/hooks/use-is-touch-device.js +3 -1
- package/dist/hooks/use-is-touch-device.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +1 -1
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/uiSize/icon-size.d.ts +2 -2
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
- package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
- package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
- package/ds-canonical/hooks/check_story_invariants.sh +17 -5
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
- package/ds-canonical/references/principle-dim-map.json +100 -0
- package/ds-canonical/references/story-baseline-registry.json +4 -3
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +41 -18
- package/llms-full.txt +11 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
- package/src/components/Alert/alert.spec.md +3 -3
- package/src/components/Alert/alert.tsx +9 -1
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
- package/src/components/AppShell/app-shell.spec.md +3 -2
- package/src/components/AppShell/app-shell.tsx +1 -1
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
- package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
- package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.spec.md +10 -30
- package/src/components/Avatar/avatar.stories.tsx +1 -1
- package/src/components/Avatar/avatar.tsx +30 -16
- package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +5 -4
- package/src/components/Badge/badge.tsx +4 -1
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
- package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
- package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
- package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
- package/src/components/Button/button.anatomy.stories.tsx +6 -6
- package/src/components/Button/button.spec.md +3 -2
- package/src/components/Button/button.tsx +67 -27
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
- package/src/components/Calendar/calendar.spec.md +9 -2
- package/src/components/Calendar/calendar.stories.tsx +3 -1
- package/src/components/Calendar/calendar.tsx +53 -19
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.tsx +4 -1
- package/src/components/Chart/chart.spec.md +1 -1
- package/src/components/Chart/chart.tsx +2 -1
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +1 -1
- package/src/components/Checkbox/checkbox.tsx +41 -7
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Chip/chip.spec.md +7 -4
- package/src/components/Chip/chip.tsx +60 -15
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
- package/src/components/CircularProgress/circular-progress.spec.md +2 -0
- package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.spec.md +3 -3
- package/src/components/Coachmark/coachmark.tsx +23 -13
- package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +30 -17
- package/src/components/Command/command.principles.stories.tsx +10 -12
- package/src/components/Command/command.spec.md +5 -5
- package/src/components/Command/command.stories.tsx +4 -4
- package/src/components/Command/command.tsx +3 -2
- package/src/components/DataTable/active-editor-controller.ts +4 -3
- package/src/components/DataTable/cell-registry.tsx +8 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
- package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
- package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
- package/src/components/DataTable/data-table.css +2 -2
- package/src/components/DataTable/data-table.spec.md +72 -115
- package/src/components/DataTable/data-table.stories.tsx +51 -3
- package/src/components/DataTable/data-table.tsx +348 -63
- package/src/components/DataTable/filter-operators.spec.md +4 -4
- package/src/components/DataTable/filter-tree.ts +192 -42
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +11 -8
- package/src/components/DateGrid/date-grid.tsx +63 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
- package/src/components/DatePicker/date-picker.spec.md +1 -0
- package/src/components/DatePicker/date-picker.stories.tsx +19 -0
- package/src/components/DatePicker/date-picker.tsx +52 -15
- package/src/components/DescriptionList/description-list.spec.md +1 -1
- package/src/components/DescriptionList/description-list.tsx +3 -1
- package/src/components/Dialog/dialog.spec.md +1 -1
- package/src/components/Dialog/dialog.tsx +2 -1
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Empty/empty.stories.tsx +4 -15
- package/src/components/Empty/empty.tsx +4 -1
- package/src/components/Field/field-controls.spec.md +2 -1
- package/src/components/Field/field-wrapper.tsx +28 -8
- package/src/components/Field/field.anatomy.stories.tsx +7 -7
- package/src/components/Field/field.principles.stories.tsx +1 -1
- package/src/components/Field/field.spec.md +9 -7
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +6 -3
- package/src/components/Field/use-form-validation.ts +49 -21
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
- package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
- package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
- package/src/components/FileItem/file-item.tsx +3 -2
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
- package/src/components/FileUpload/file-upload.spec.md +4 -1
- package/src/components/FileUpload/file-upload.tsx +36 -10
- package/src/components/FileViewer/can-render-image.ts +15 -0
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
- package/src/components/FileViewer/file-viewer.spec.md +2 -2
- package/src/components/FileViewer/file-viewer.tsx +47 -21
- package/src/components/FileViewer/image-renderer.tsx +3 -8
- package/src/components/HoverCard/hover-card.spec.md +1 -1
- package/src/components/HoverCard/hover-card.tsx +3 -1
- package/src/components/Input/input.anatomy.stories.tsx +4 -4
- package/src/components/Input/input.principles.stories.tsx +1 -1
- package/src/components/Input/input.spec.md +1 -1
- package/src/components/Input/input.stories.tsx +11 -5
- package/src/components/Input/input.tsx +11 -9
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
- package/src/components/LinkInput/link-input.tsx +49 -34
- package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +4 -3
- package/src/components/Menu/menu-item.tsx +15 -4
- package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
- package/src/components/Notice/notice.spec.md +4 -3
- package/src/components/Notice/notice.tsx +22 -5
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
- package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
- package/src/components/NumberInput/number-input.spec.md +2 -1
- package/src/components/NumberInput/number-input.tsx +25 -9
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
- package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
- package/src/components/Pagination/pagination.spec.md +150 -0
- package/src/components/Pagination/pagination.stories.tsx +98 -0
- package/src/components/Pagination/pagination.tsx +263 -0
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +4 -4
- package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.tsx +65 -55
- package/src/components/PeoplePicker/person-display.tsx +10 -4
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.spec.md +13 -14
- package/src/components/Popover/popover.tsx +88 -39
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
- package/src/components/ProfileCard/profile-card.spec.md +1 -2
- package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +29 -8
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
- package/src/components/RadioGroup/radio-group.spec.md +2 -2
- package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
- package/src/components/RadioGroup/radio-group.tsx +37 -3
- package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
- package/src/components/Rating/rating.principles.stories.tsx +1 -1
- package/src/components/Rating/rating.spec.md +2 -2
- package/src/components/Rating/rating.stories.tsx +1 -1
- package/src/components/Rating/rating.tsx +3 -3
- package/src/components/ScrollArea/scroll-area.spec.md +1 -0
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
- package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
- package/src/components/SegmentedControl/segmented-control.tsx +29 -11
- package/src/components/Select/select.anatomy.stories.tsx +2 -2
- package/src/components/Select/select.spec.md +3 -2
- package/src/components/Select/select.tsx +62 -19
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.spec.md +12 -8
- package/src/components/SelectMenu/select-menu.tsx +176 -20
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +2 -1
- package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
- package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
- package/src/components/Separator/separator.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -1
- package/src/components/Sheet/sheet.tsx +4 -2
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +2 -1
- package/src/components/Sidebar/sidebar.stories.tsx +6 -4
- package/src/components/Sidebar/sidebar.tsx +41 -13
- package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
- package/src/components/Skeleton/skeleton.spec.md +1 -2
- package/src/components/Skeleton/skeleton.tsx +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
- package/src/components/Slider/slider.spec.md +19 -85
- package/src/components/Slider/slider.tsx +13 -5
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +6 -4
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Switch/switch.spec.md +4 -3
- package/src/components/Switch/switch.tsx +59 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
- package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
- package/src/components/Tabs/tabs.spec.md +15 -48
- package/src/components/Tabs/tabs.tsx +70 -34
- package/src/components/Tag/tag.principles.stories.tsx +2 -2
- package/src/components/Tag/tag.spec.md +11 -43
- package/src/components/Tag/tag.tsx +30 -4
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
- package/src/components/Textarea/textarea.spec.md +2 -1
- package/src/components/Textarea/textarea.tsx +20 -10
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
- package/src/components/TimePicker/time-picker.spec.md +2 -2
- package/src/components/TimePicker/time-picker.tsx +35 -16
- package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
- package/src/components/Toast/toast.principles.stories.tsx +10 -11
- package/src/components/Toast/toast.spec.md +5 -5
- package/src/components/Toast/toast.stories.tsx +5 -1
- package/src/components/Toast/toast.tsx +13 -7
- package/src/components/Tooltip/tooltip.spec.md +3 -3
- package/src/components/Tooltip/tooltip.tsx +1 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
- package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
- package/src/components/TreeView/tree-view.spec.md +2 -2
- package/src/components/TreeView/tree-view.tsx +27 -5
- package/src/hooks/use-controllable.ts +7 -2
- package/src/hooks/use-is-touch-device.ts +8 -1
- package/src/index.ts +2 -1
- package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
- package/src/patterns/header-canonical/header-canonical.css +1 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/categorical-color.ts +1 -1
- package/src/tokens/color/color.spec.md +11 -13
- package/src/tokens/color/color.stories.tsx +1 -1
- package/src/tokens/color/primitives.css +7 -5
- package/src/tokens/color/semantic.css +8 -2
- package/src/tokens/density/density.spec.md +2 -0
- package/src/tokens/elevation/elevation.spec.md +1 -1
- package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
- package/src/tokens/motion/motion.spec.md +1 -1
- package/src/tokens/motion/motion.stories.tsx +142 -0
- package/src/tokens/opacity/opacity.spec.md +1 -1
- package/src/tokens/opacity/opacity.stories.tsx +7 -11
- package/src/tokens/radius/radius.spec.md +1 -1
- package/src/tokens/typography/typography.css +1 -1
- package/src/tokens/typography/typography.spec.md +1 -1
- package/src/tokens/uiSize/icon-size.ts +2 -2
- package/src/tokens/uiSize/uiSize.spec.md +12 -11
- package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
- package/src/tokens/utility-registry.json +5 -4
- package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
|
@@ -86,7 +86,7 @@ Filter chip 的「移除這個 filter」動作已由「再點一次 deselect」
|
|
|
86
86
|
|
|
87
87
|
### 為什麼不需要 checkmark-on-selected
|
|
88
88
|
|
|
89
|
-
Material 3 filter chip 在 selected 時會把 startIcon 換成 `Check`。我們不做,因為視覺層已經用 **primary
|
|
89
|
+
Material 3 filter chip 在 selected 時會把 startIcon 換成 `Check`。我們不做,因為視覺層已經用 **primary border + text(底色維持 surface 不變)**明確表達 selected 狀態,再加 icon 是冗餘信號。
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
@@ -108,10 +108,12 @@ Chip **只有一個 size**,對應 `h-field-sm`(md density 28px / lg density
|
|
|
108
108
|
|---|---|
|
|
109
109
|
| **Default** | `bg-surface border-border text-fg-secondary`(未選文字用 neutral-8,對齊 SegmentedControl / Tabs 未選狀態;hover 才轉 `text-foreground`)|
|
|
110
110
|
| **Hover**(未選)| `hover:border-border-hover`(對齊 Input / SegmentedControl hover 的 border 加深一階)|
|
|
111
|
-
| **Selected** | `border-primary
|
|
111
|
+
| **Selected** | `border-primary text-primary`,底色維持 `bg-surface` 不變(primary base 同時染 border + text,不染底色,跟 SegmentedControl 一致)|
|
|
112
|
+
| **Hover(選中之上)** | `border-primary-hover text-primary-hover`——選中元素的 hover = 同色相升 hover 階(2026-07-06 拍板;Button pressed hover 家族 + Ant active hover 同派),**不用** neutral 灰底 |
|
|
112
113
|
| **Disabled** | `cursor-not-allowed text-fg-disabled`,border 維持 `border-border` 不變色 |
|
|
114
|
+
| **Disabled × Selected** | disabled 全滅(primary 描邊/染字一併退場 → `border-border text-fg-disabled`)——M24 state > emphasis,Checkbox checked+disabled 全滅同族(2026-07-07 補明文,原 code 只滅字不滅框 = 半滅已修) |
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
選中色對齊系統選中 canonical(semantic.css「選中狀態」段 SSOT),跟 SegmentedControl / Tabs underline 使用同一套 **primary base** token——2026-07-06 user 拍板自 primary-hover 改 base(Ant / Atlassian / Material 三家實錘「瞬時弱、持續強」:hover 階專屬瞬時態,持續選中站 base)。**❌ 選中態不得用 primary-hover**(那是瞬時 hover 專用階)。
|
|
115
117
|
|
|
116
118
|
---
|
|
117
119
|
|
|
@@ -225,7 +227,7 @@ ColorMatrix 已建:展示 default / hover / selected / disabled 四狀態的 bg
|
|
|
225
227
|
- Tab(再按一次)— 離開整組
|
|
226
228
|
- Enter / Space — 切換選取
|
|
227
229
|
|
|
228
|
-
**Focus**:整組 chip 採 roving tabindex——Tab 進入整組、方向鍵在組內移動焦點、再按 Tab 離開整組(不是 Dialog 那種把焦點鎖在裡面、Tab 無法離開的 focus trap)。focus-visible 時顯示
|
|
230
|
+
**Focus**:整組 chip 採 roving tabindex——Tab 進入整組、方向鍵在組內移動焦點、再按 Tab 離開整組(不是 Dialog 那種把焦點鎖在裡面、Tab 無法離開的 focus trap)。focus-visible 時顯示 focus ring(`focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1`,以 box-shadow 實作並 `outline-none`)。
|
|
229
231
|
|
|
230
232
|
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
231
233
|
|
|
@@ -234,4 +236,5 @@ ColorMatrix 已建:展示 default / hover / selected / disabled 四狀態的 bg
|
|
|
234
236
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
235
237
|
|
|
236
238
|
- `badge.spec.md`
|
|
239
|
+
- `pagination.spec.md`
|
|
237
240
|
- `tag.spec.md`
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
* ── State ──
|
|
36
36
|
* default bg-surface border-border text-fg-secondary
|
|
37
37
|
* hover border-border-hover text-foreground(對齊 Input / SegmentedControl)
|
|
38
|
-
* selected bg-surface(不變) border-primary
|
|
38
|
+
* selected bg-surface(不變) border-primary text-primary
|
|
39
39
|
* disabled cursor-not-allowed text-fg-disabled
|
|
40
40
|
*
|
|
41
41
|
* ── 詳見 chip.spec.md ──
|
|
@@ -56,14 +56,26 @@ const chipVariants = cva(
|
|
|
56
56
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
|
|
57
57
|
// hover(未選):border 加深一階 + 文字轉深,對齊 Input / SegmentedControl hover
|
|
58
58
|
'hover:border-border-hover hover:text-foreground',
|
|
59
|
-
// selected: 文字 + 邊框都用 primary
|
|
60
|
-
// ── pill 風格 canonical 選中規則,跟 SegmentedControl
|
|
61
|
-
// primary
|
|
62
|
-
|
|
59
|
+
// selected: 文字 + 邊框都用 primary base,底色維持 bg-surface 不變
|
|
60
|
+
// ── pill 風格 canonical 選中規則,跟 SegmentedControl 完全一致(SSOT = semantic.css「選中狀態」段):
|
|
61
|
+
// primary base 同時染文字和線條;底色不改 (不用 primary-subtle)。
|
|
62
|
+
// 2026-07-06 user 拍板:持續選中 hover 階 → base — Ant(pagination/tabs/input focus 全 base)/
|
|
63
|
+
// Atlassian(selected = brand 同顆)/ Material 3(active tab = primary)三家實錘「瞬時弱、持續強」;
|
|
64
|
+
// hover 階專屬瞬時態(hover/預覽),持續選中站 base。
|
|
65
|
+
'data-[state=on]:border-primary data-[state=on]:text-primary',
|
|
66
|
+
// 選中之上的 hover = 同色相升 hover 階(2026-07-06 user 拍板;Button pressed hover
|
|
67
|
+
// data-[state=on]:hover:text-primary-hover 家族 + Ant active 頁 hover = colorPrimaryHover
|
|
68
|
+
// 同派)。原本靠 CSS 平手 source-order 讓選中「碰巧」贏過未選 hover(靜止無回饋)= 脆弱,
|
|
69
|
+
// 顯式宣告一併解掉。
|
|
70
|
+
'data-[state=on]:hover:border-primary-hover data-[state=on]:hover:text-primary-hover',
|
|
63
71
|
// disabled:cursor-not-allowed + 鎖 hover 不變色
|
|
64
72
|
// 不用 pointer-events-none(否則 cursor-not-allowed 不會顯示)
|
|
65
73
|
'disabled:cursor-not-allowed disabled:text-fg-disabled',
|
|
66
74
|
'disabled:hover:border-border disabled:hover:text-fg-disabled',
|
|
75
|
+
// disabled × selected 疊加(2026-07-07 user 拍板統一;M24 state > emphasis:disabled 全滅,
|
|
76
|
+
// 選中的 primary 描邊/染字一併退場——原本只滅字不滅框 = 半滅,Checkbox checked+disabled 全滅同族)
|
|
77
|
+
'data-[state=on]:disabled:border-border data-[state=on]:disabled:text-fg-disabled',
|
|
78
|
+
'data-[state=on]:disabled:hover:border-border data-[state=on]:disabled:hover:text-fg-disabled',
|
|
67
79
|
]
|
|
68
80
|
)
|
|
69
81
|
|
|
@@ -208,6 +220,29 @@ ScrollChipGroup.displayName = 'ScrollChipGroup'
|
|
|
208
220
|
//
|
|
209
221
|
// Fade mask 仍保留(reserveArrowWidth: 0),軟化內容硬邊。
|
|
210
222
|
|
|
223
|
+
// ── getElementRef(Radix Slot idiom)──────────────────────────────────────────
|
|
224
|
+
// 讀取 child element 既有 ref 而不觸發 React dev deprecation warning:
|
|
225
|
+
// React ≤18 ref 在 element.ref;React 19 起移到 element.props.ref,錯誤側讀取會 fire warning
|
|
226
|
+
// (以 getter 上的 isReactWarning 標記)。抄 Radix UI Slot 的偵測法:
|
|
227
|
+
// https://github.com/radix-ui/primitives/blob/main/packages/react/slot/src/slot.tsx(getElementRef)
|
|
228
|
+
// react-compose-refs 僅為 transitive dep(非 package.json 直接依賴),不直接 import — 最小 local 實作。
|
|
229
|
+
function getElementRef(element: React.ReactElement): React.Ref<HTMLElement> | undefined {
|
|
230
|
+
// React <=18 dev:props.ref 掛 isReactWarning getter → 真 ref 在 element.ref
|
|
231
|
+
let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get
|
|
232
|
+
let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
|
|
233
|
+
if (mayWarn) return (element as unknown as { ref?: React.Ref<HTMLElement> }).ref
|
|
234
|
+
// React 19 dev:element.ref 掛 isReactWarning getter → 真 ref 在 props.ref
|
|
235
|
+
getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get
|
|
236
|
+
mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
|
|
237
|
+
if (mayWarn) return (element.props as { ref?: React.Ref<HTMLElement> }).ref
|
|
238
|
+
// production:兩處擇一
|
|
239
|
+
return (
|
|
240
|
+
(element.props as { ref?: React.Ref<HTMLElement> }).ref ??
|
|
241
|
+
(element as unknown as { ref?: React.Ref<HTMLElement> }).ref ??
|
|
242
|
+
undefined
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
212
247
|
const MenuChipGroup = React.forwardRef<
|
|
213
248
|
HTMLDivElement,
|
|
@@ -284,12 +319,22 @@ const MenuChipGroup = React.forwardRef<
|
|
|
284
319
|
[groupType, groupValue, groupOnValueChange]
|
|
285
320
|
)
|
|
286
321
|
|
|
287
|
-
|
|
288
|
-
|
|
322
|
+
// 2026-07-05 fix(deep-audit A.1b):原 cloneElement 直接以 registerItem 覆寫 child ref,
|
|
323
|
+
// consumer 傳給 Chip 的自有 ref 在 menu layout 會被靜默取代。改 compose — 同時餵
|
|
324
|
+
// registerItem(scrollIntoView 用)與 child 原 ref(對齊 Radix compose-refs idiom)。
|
|
325
|
+
const enhancedItems = items.map((child, i) => {
|
|
326
|
+
const childRef = getElementRef(child)
|
|
327
|
+
return React.cloneElement(
|
|
289
328
|
child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,
|
|
290
|
-
{
|
|
329
|
+
{
|
|
330
|
+
ref: (el: HTMLElement | null) => {
|
|
331
|
+
registerItem(i)(el)
|
|
332
|
+
if (typeof childRef === 'function') childRef(el)
|
|
333
|
+
else if (childRef) (childRef as React.MutableRefObject<HTMLElement | null>).current = el
|
|
334
|
+
},
|
|
335
|
+
}
|
|
291
336
|
)
|
|
292
|
-
)
|
|
337
|
+
})
|
|
293
338
|
|
|
294
339
|
return (
|
|
295
340
|
<div className="flex items-center gap-2">
|
|
@@ -342,16 +387,16 @@ MenuChipGroup.displayName = 'MenuChipGroup'
|
|
|
342
387
|
export const chipMeta = {
|
|
343
388
|
component: 'Chip',
|
|
344
389
|
family: 3,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
},
|
|
390
|
+
// 2026-07-04 audit 填 Phase 2:Chip 單一視覺無 variant 軸(variants 留空為 intentional,非漏填)
|
|
391
|
+
variants: {},
|
|
348
392
|
sizes: {
|
|
349
|
-
|
|
393
|
+
// 單一尺寸:h-field-sm(28/32 依 density)、icon 16、text-body、px-3(cva 真值)
|
|
394
|
+
sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },
|
|
350
395
|
},
|
|
351
|
-
states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'], // selected = data-[state=on](primary-
|
|
396
|
+
states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'], // selected = data-[state=on](primary base,2026-07-06 拍板);cva 無 active 樣式
|
|
352
397
|
tokens: {
|
|
353
398
|
bg: ['bg-surface'],
|
|
354
|
-
fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary
|
|
399
|
+
fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'],
|
|
355
400
|
ring: ['ring-ring'],
|
|
356
401
|
},
|
|
357
402
|
} as const
|
|
@@ -170,7 +170,7 @@ export const Accessibility = {
|
|
|
170
170
|
render: () => (
|
|
171
171
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
172
172
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
173
|
-
<p className="whitespace-pre-line">{"本元件不可聚焦、無鍵盤互動(載入指示不接收鍵盤),但會依模式自動輸出對的 ARIA 給輔助技術:\n\n• 有 value(determinate)→ role=\"progressbar\",並帶 aria-valuenow / aria-valuemin=0 / aria-valuemax=100,螢幕報讀器會念出目前進度。\n• 無 value(indeterminate)+ 有傳 label 或 aria-label → role=\"status\",把 label 當作 aria-label 念出(例如「載入訂單資料中」)。\n• 無 value 且沒給任何 label → 標記 aria-hidden,交由外層容器(如 Button 的 aria-busy)負責語義。\n\n當把 CircularProgress 包進互動容器(Button / Card / Link)時,聚焦與鍵盤行為由容器決定。"}</p>
|
|
173
|
+
<p className="whitespace-pre-line">{"本元件不可聚焦、無鍵盤互動(載入指示不接收鍵盤),但會依模式自動輸出對的 ARIA 給輔助技術:\n\n• 有 value(determinate)→ role=\"progressbar\",並帶 aria-valuenow / aria-valuemin=0 / aria-valuemax=100,螢幕報讀器會念出目前進度。\n• 無 value(indeterminate)+ 有傳 label 或 aria-label → role=\"status\",把 label 當作 aria-label 念出(例如「載入訂單資料中」)。\n• 無 value 且沒給任何 label → 標記 aria-hidden,交由外層容器(如 Button 的 aria-busy)負責語義。\n\n動畫尊重 prefers-reduced-motion:偏好減少動態的使用者不會看到停止的圈(那會像卡住),indeterminate 旋轉改放慢至 3s/圈(預設 1s)——保留「進行中」訊號、降低前庭刺激(Material 流派)。\n\n當把 CircularProgress 包進互動容器(Button / Card / Link)時,聚焦與鍵盤行為由容器決定。"}</p>
|
|
174
174
|
</div>
|
|
175
175
|
),
|
|
176
176
|
}
|
|
@@ -8,6 +8,7 @@ import { Search, Check } from 'lucide-react'
|
|
|
8
8
|
import { CircularProgress } from './circular-progress'
|
|
9
9
|
import { Button } from '@/design-system/components/Button/button'
|
|
10
10
|
import { Skeleton } from '@/design-system/components/Skeleton/skeleton'
|
|
11
|
+
import { Empty } from '@/design-system/components/Empty/empty'
|
|
11
12
|
import { Input } from '@/design-system/components/Input/input'
|
|
12
13
|
|
|
13
14
|
const meta: Meta = {
|
|
@@ -214,12 +215,13 @@ export const UsageScenarioRule: Story = {
|
|
|
214
215
|
title="✅ 延遲加載浮層 / 全頁 overlay — 用 Empty compose"
|
|
215
216
|
note="切換 workspace / 載入 dashboard 等需要阻擋互動的場景,走 Empty compose:<Empty icon={<CircularProgress size={48} />} title=... description=... />。不要自己手刻 absolute overlay"
|
|
216
217
|
>
|
|
218
|
+
{/* 2026-07-04 audit:改消費 <Empty> compose(原手刻垂直堆疊與本則自教規則+spec Do 矛盾) */}
|
|
217
219
|
<div className="border border-border rounded-lg w-96 h-40 flex items-center justify-center bg-muted/40">
|
|
218
|
-
<
|
|
219
|
-
<CircularProgress size={48} aria-label="載入 dashboard" />
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
<Empty
|
|
221
|
+
icon={<CircularProgress size={48} aria-label="載入 dashboard" />}
|
|
222
|
+
title="載入 dashboard 中"
|
|
223
|
+
description="Notion workspace 切換、Stripe dashboard 初載"
|
|
224
|
+
/>
|
|
223
225
|
</div>
|
|
224
226
|
</Rule>
|
|
225
227
|
</div>
|
|
@@ -185,6 +185,8 @@ Track 色鎖 `var(--secondary)`(= neutral-3,與 ProgressBar track 一致)。
|
|
|
185
185
|
| Indeterminate + 有 aria-label / label | `status` | `aria-label={aria-label ?? label}`(顯式 aria-label 優先,label 為 fallback) |
|
|
186
186
|
| Indeterminate + 無 label | `aria-hidden=true` | 由父層 `aria-busy` 管理(Button 模式) |
|
|
187
187
|
|
|
188
|
+
**動畫 × `prefers-reduced-motion`**:indeterminate 旋轉對 reduce 使用者**不停止**(loading 回饋仍需可見,停轉會像卡住),改放慢至 3s/圈(預設 1s;`circular-progress.tsx` 的 `motion-reduce:[animation-duration:3s]`,Material 流派)——保留「進行中」訊號、降低前庭刺激。對照 Skeleton 的 `motion-reduce:animate-none`(完全停止):占位塊靜止後仍是有效的靜態內容,語意不失,故兩元件策略不同。
|
|
189
|
+
|
|
188
190
|
---
|
|
189
191
|
|
|
190
192
|
## 視覺與幾何鐵律
|
|
@@ -5,7 +5,6 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|
|
5
5
|
import { Search, Upload } from 'lucide-react'
|
|
6
6
|
import type { ColumnDef } from '@tanstack/react-table'
|
|
7
7
|
import { CircularProgress } from './circular-progress'
|
|
8
|
-
import { Button } from '@/design-system/components/Button/button'
|
|
9
8
|
import { Input } from '@/design-system/components/Input/input'
|
|
10
9
|
import { Empty } from '@/design-system/components/Empty/empty'
|
|
11
10
|
import { DataTable } from '@/design-system/components/DataTable/data-table'
|
|
@@ -43,16 +42,9 @@ export const Default: Story = {
|
|
|
43
42
|
),
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<div className="flex items-center gap-3">
|
|
50
|
-
<Button variant="primary" loading>處理付款中</Button>
|
|
51
|
-
<Button variant="secondary" loading>儲存草稿</Button>
|
|
52
|
-
<Button variant="tertiary" loading>匯出 CSV</Button>
|
|
53
|
-
</div>
|
|
54
|
-
),
|
|
55
|
-
}
|
|
45
|
+
// Retired 2026-07-04 audit Dim 24:`ButtonLoading` 全程只 demo `<Button loading>`(0 個直接 `<CircularProgress>`),
|
|
46
|
+
// 跟 button.stories.tsx「載入中」重複且較弱(那邊有 原始→loading 對照 + iconOnly + startIcon 替換)。
|
|
47
|
+
// Button loading 消費 CircularProgress 的關係由 spec「何時用」+ Size canonical 段記載,story home 在 Button。
|
|
56
48
|
|
|
57
49
|
export const InlineAction: Story = {
|
|
58
50
|
name: '行內操作',
|
|
@@ -335,7 +335,7 @@ export const StateBehavior: Story = {
|
|
|
335
335
|
<div>
|
|
336
336
|
<H3>Focus / Esc / Click-outside(繼承 Popover)</H3>
|
|
337
337
|
<Desc>
|
|
338
|
-
Coachmark 大部分行為繼承 Popover(Radix)
|
|
338
|
+
Coachmark 大部分行為繼承 Popover(Radix),但刻意抑制 CTA 自動 focus(onOpenAutoFocus preventDefault 後 focus content 容器本身)——避免使用者還在讀 body 時 CTA 被 auto-focus 偷觸發,Tab 第一下即達第一個 CTA(焦點不停 trigger,否則 non-modal focus-outside dismiss 會誤關閉浮層;2026-07-05 D4 修);關閉時 return to trigger;Esc 關閉浮層(透過 Radix onOpenChange,效果等同退出,但不觸發 onSkip callback);預設 non-modal,使用者可忽略繼續主流程。
|
|
339
339
|
</Desc>
|
|
340
340
|
</div>
|
|
341
341
|
</div>
|
|
@@ -350,7 +350,7 @@ export const Accessibility = {
|
|
|
350
350
|
render: () => (
|
|
351
351
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
352
352
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
353
|
-
<p className="whitespace-pre-line">{"詳 `coachmark.spec.md` 「A11y 預設」段。摘要:\n\n- 焦點管理
|
|
353
|
+
<p className="whitespace-pre-line">{"詳 `coachmark.spec.md` 「A11y 預設」段。摘要:\n\n- 焦點管理 :刻意抑制 CTA 自動 focus(onOpenAutoFocus preventDefault 後 focus content 容器本身)——避免 user 還在讀 body 時 CTA 被偷觸發;Tab 第一下即達第一個 CTA(焦點不停 trigger,否則 non-modal focus-outside dismiss 會誤關閉;2026-07-05 D4 修);關閉時 return to trigger\n- Esc 關閉 :預設啟用——user 按 Esc 透過 Radix onOpenChange 關閉浮層(效果等同退出),但不觸發 onSkip callback(Esc 是「關閉浮層」非「明確 skip」)\n- ARIA :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" (Radix 預設)\n- Step 計數 tabular-nums :螢幕閱讀器讀「2 of 3」語意清楚"}</p>
|
|
354
354
|
</div>
|
|
355
355
|
),
|
|
356
356
|
}
|
|
@@ -55,7 +55,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
|
|
|
55
55
|
|
|
56
56
|
const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
57
57
|
<section className="mb-12">
|
|
58
|
-
<h2 className="text-
|
|
58
|
+
<h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
|
|
59
59
|
{children}
|
|
60
60
|
</section>
|
|
61
61
|
)
|
|
@@ -166,7 +166,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
|
|
|
166
166
|
|
|
167
167
|
- **Previous 最左**——回到上一步(負方向,視覺權重低)
|
|
168
168
|
- **Skip 中間**——exit escape hatch,tertiary 不搶焦點;**僅第一步顯示**(見 CTA 語義表)
|
|
169
|
-
- **Next / Done 最右**——推進主動線(正方向,primary 視覺權重高);**Coachmark
|
|
169
|
+
- **Next / Done 最右**——推進主動線(正方向,primary 視覺權重高);**Coachmark 抑制 CTA 自動 focus**(`onOpenAutoFocus` preventDefault 後 focus content 容器本身,非第一個 CTA),避免使用者還在讀 body 時按 Enter 誤推進;想推進者按一下 Tab 即達 CTA 再 Enter(焦點已在 content 內,不觸發 non-modal focus-outside dismiss;2026-07-05 D4 修)
|
|
170
170
|
|
|
171
171
|
第 1 步通常無 Previous;最後步 Next 改 Done(由 `isLastStep` 控制)。單步驟 CTA 改 `'知道了'`。
|
|
172
172
|
|
|
@@ -178,7 +178,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
|
|
|
178
178
|
|----|-------|------|
|
|
179
179
|
| 浮層外殼 | 繼承 Popover(`bg-surface-raised` / `border-border` / `rounded-lg` / `--elevation-200`) | 改 Popover 自動跟進 |
|
|
180
180
|
| Media 背景 | `bg-muted` | illustration / 透明 PNG 底色 |
|
|
181
|
-
| Title | `text-body-lg font-medium text-foreground` |
|
|
181
|
+
| Title | `text-body-lg font-medium text-foreground` | body 主 title(16px),比 kind header 的 `PopoverTitle` 小標籤(`text-body` 14px)重一階——主教學訊息是視覺主角 |
|
|
182
182
|
| Description | `text-body text-fg-secondary` | 主說明文字 |
|
|
183
183
|
| Step 計數 | `text-body text-fg-secondary tabular-nums` | 跟 body 內文同字體;數字等寬,切換步驟不跳動 |
|
|
184
184
|
| Body padding | `px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]` | overlay-surface SSOT(PopoverBody) |
|
|
@@ -203,7 +203,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
|
|
|
203
203
|
|
|
204
204
|
## A11y 預設
|
|
205
205
|
|
|
206
|
-
- **焦點管理**:Coachmark
|
|
206
|
+
- **焦點管理**:Coachmark **刻意抑制 CTA 自動 focus**(`onOpenAutoFocus` preventDefault 後 focus content 容器本身,對齊 `dialog.tsx` 的 `?? content` fallback canonical)——避免使用者還在讀 body 時 CTA 被 auto-focus 偷觸發(焦點在容器上按 Enter 無動作);Tab 第一下即達 content 內第一個 CTA。焦點**不**停在 trigger:non-modal Popover 焦點在 content 樹外時按 Tab 會觸發 focus-outside dismiss 誤關閉浮層,CTA 鍵盤永遠到不了(2026-07-05 D4 修;dismiss 機制詳 `popover.spec.md` A11y 段)。關閉時焦點 return to trigger(Radix 預設 `onCloseAutoFocus`,未 override)
|
|
207
207
|
- **Esc 關閉**:預設啟用——user 按 Esc 透過 Radix `onOpenChange` 關閉浮層(效果等同退出),**不觸發 `onSkip` callback**(Esc 是「關閉浮層」非「明確 skip」;若需區分 Esc-退出 vs Skip-按鈕 的追蹤事件,consumer 在 Skip 按鈕的 `onSkip` 處理)
|
|
208
208
|
- **ARIA**:trigger 自動 `aria-expanded` / `aria-controls`,content `role="dialog"`(Radix 預設)
|
|
209
209
|
- **Step 計數 tabular-nums**:螢幕閱讀器讀「2 of 3」語意清楚
|
|
@@ -34,9 +34,11 @@ import { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-ge
|
|
|
34
34
|
* 3. **Multi step 中間 / 最後步**(有 `onPrev`):**Skip 不顯示**(使用者已投入進度,
|
|
35
35
|
* 再給 Skip 會讓「放棄」入口與「回上一步」衝突 — Linear / Pendo / Shepherd.js
|
|
36
36
|
* 同樣規則)。CTA = `isLastStep ? 'Done' : 'Next'`
|
|
37
|
-
* 4.
|
|
38
|
-
* focus
|
|
39
|
-
*
|
|
37
|
+
* 4. **抑制 CTA 自動 focus,焦點落在 content 容器** — `onOpenAutoFocus` preventDefault
|
|
38
|
+
* 後 focus content 容器本身(非第一個 CTA),避免使用者還在讀 body 時按 Enter 誤推進;
|
|
39
|
+
* Tab 第一下即達 content 內第一個 CTA。焦點不可停在 trigger:non-modal Popover 下
|
|
40
|
+
* 焦點在 DismissableLayer 外按 Tab 會觸發 focus-outside dismiss 誤關閉
|
|
41
|
+
* (2026-07-05 D4;對齊 dialog.tsx handleOpenAutoFocus `?? content` fallback,見 spec「A11y 預設」)。
|
|
40
42
|
*
|
|
41
43
|
* ── 為什麼 Body+Footer 消費 overlay-surface ──
|
|
42
44
|
* 避免 padding token 漂移:Dialog / Popover / Coachmark 三者共用同一套 Header/Body/Footer
|
|
@@ -150,10 +152,12 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
|
|
|
150
152
|
? KIND_TITLE[kind as 'tips' | 'new-features']
|
|
151
153
|
: kind
|
|
152
154
|
|
|
153
|
-
// 2026-05-31 #6
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
// 2026-05-31 #6 / 2026-07-05 改消費 Popover 自動接線:PopoverContent(role=dialog)accessible name 三層 —
|
|
156
|
+
// 1. headerTitle → <PopoverTitle> 掛載時由 popover.tsx PopoverTitleContext 自動接 aria-labelledby(免手動)
|
|
157
|
+
// 2. 無 header 但有 body 主 title(<h3> 非 PopoverTitle,自動接線接不到)→ 仍手動接 aria-labelledby
|
|
158
|
+
// 3. 皆無 → aria-label fallback
|
|
159
|
+
const bodyTitleId = React.useId()
|
|
160
|
+
const bodyTitleLabelledBy = !headerTitle && title ? bodyTitleId : undefined
|
|
157
161
|
|
|
158
162
|
return (
|
|
159
163
|
<Popover open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
@@ -166,10 +170,16 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
|
|
|
166
170
|
className={cn('w-80 p-0 overflow-hidden', className)}
|
|
167
171
|
// 禁止 Radix 開啟時自動 focus 第一個 focusable(預設會 focus Prev / Skip / Next),
|
|
168
172
|
// Coachmark 的 CTA 不該被 auto-focus 偷觸發(user 可能還在讀 body,按 Enter 就推進)。
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
// 2026-07-05 D4:preventDefault 後必 focus content 容器本身(FocusScope 容器自帶 tabIndex=-1)——
|
|
174
|
+
// 焦點若停在 trigger(DismissableLayer 外),non-modal Popover 按 Tab 即觸發 focus-outside
|
|
175
|
+
// dismiss 直接關閉,CTA 鍵盤永遠到不了;focus 容器後 Tab 第一下即達第一個 CTA,Enter 按在
|
|
176
|
+
// 容器上無動作(原「不誤推進」意圖保留;對齊 dialog.tsx handleOpenAutoFocus `?? content` fallback)。
|
|
177
|
+
onOpenAutoFocus={(e) => {
|
|
178
|
+
e.preventDefault()
|
|
179
|
+
;(e.currentTarget as HTMLElement).focus({ preventScroll: true })
|
|
180
|
+
}}
|
|
181
|
+
aria-labelledby={bodyTitleLabelledBy}
|
|
182
|
+
aria-label={headerTitle || title ? undefined : '提示'}
|
|
173
183
|
{...props}
|
|
174
184
|
>
|
|
175
185
|
{headerTitle && (
|
|
@@ -177,7 +187,7 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
|
|
|
177
187
|
// **不 hideClose** — 對齊 Popover / Dialog / 所有 overlay 家族 canonical:header 必有 dismiss X
|
|
178
188
|
// (user 可隨時關閉,跟 Skip / Done 是不同入口,canonical 重複不冗)
|
|
179
189
|
<PopoverHeader>
|
|
180
|
-
<PopoverTitle
|
|
190
|
+
<PopoverTitle>{headerTitle}</PopoverTitle>
|
|
181
191
|
</PopoverHeader>
|
|
182
192
|
)}
|
|
183
193
|
|
|
@@ -194,7 +204,7 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
|
|
|
194
204
|
// 世界級參考:Notion / Linear / Figma onboarding tour 皆左對齊;Intercom Messenger 亦如是。
|
|
195
205
|
<PopoverBody className="flex flex-col">
|
|
196
206
|
{title && (
|
|
197
|
-
<h3 id={!headerTitle ?
|
|
207
|
+
<h3 id={!headerTitle ? bodyTitleId : undefined} className="text-body-lg font-medium text-foreground">{title}</h3>
|
|
198
208
|
)}
|
|
199
209
|
{description && (
|
|
200
210
|
// title(body-lg 16)+ desc(body 14)→ reading-lg token(label tier 決定)
|
|
@@ -306,7 +306,7 @@ export const Overview = {
|
|
|
306
306
|
<thead><tr><Th>Prop</Th><Th>Type</Th><Th>Default</Th><Th>說明</Th></tr></thead>
|
|
307
307
|
<tbody>
|
|
308
308
|
{[
|
|
309
|
-
['mode', "'edit'|'display'|'readonly'|'disabled'", "'edit'", 'FieldMode
|
|
309
|
+
['mode', "'edit'|'display'|'readonly'|'disabled'", "'edit'", 'FieldMode 四模式;顯式傳入時永遠最優先,不被 disabled 覆蓋'],
|
|
310
310
|
['size', "'sm'|'md'|'lg'", "'md'", '尺寸,與 Button 共用 field-height token'],
|
|
311
311
|
['options', 'ComboboxOption[]', '—', '選項列表(extends SelectMenuOption:value / label / icon / avatar / description / disabled / group)'],
|
|
312
312
|
['value', 'string[]', '[]', '已選中的值陣列'],
|
|
@@ -315,7 +315,7 @@ export const Overview = {
|
|
|
315
315
|
['wrap', 'boolean', 'false', '換行模式——高度隨內容展開,Tags 自然換行'],
|
|
316
316
|
['clearable', 'boolean', 'false', '有值時顯示 X clear all 按鈕'],
|
|
317
317
|
['placeholder', 'string', '—', '無值時的提示文字;未傳時桌機 fallback 到 emptyPlaceholder(預設「選擇…」全形省略號),手機原生 select fallback「選擇...」'],
|
|
318
|
-
['disabled', 'boolean', 'false', '
|
|
318
|
+
['disabled', 'boolean', 'false', '原生屬性;未傳 mode 時 resolve 為 disabled 樣式(顯式 mode prop 恆優先,見 useResolvedFieldMode)'],
|
|
319
319
|
].map(([p, t, d, desc]) => (
|
|
320
320
|
<tr key={p}><Td mono>{p}</Td><Td mono>{t}</Td><Td mono>{d}</Td><Td>{desc}</Td></tr>
|
|
321
321
|
))}
|
|
@@ -783,8 +783,8 @@ export const StateBehavior = {
|
|
|
783
783
|
<div className="flex flex-col gap-1 text-[11px] text-fg-secondary">
|
|
784
784
|
<span>1. useOverflowCount hook 量測 container 寬度與每個 tag 的自然寬度</span>
|
|
785
785
|
<span>2. ResizeObserver 持續監聽容器變化</span>
|
|
786
|
-
<span>3. 量測在
|
|
787
|
-
<span>4. 超出的 tag
|
|
786
|
+
<span>3. 量測在 useEffect 內首次 paint 後執行(確保 nested 場景所有 ref 已 attach),double-rAF 保證 layout 完成才量測;代價是可能 1-2 frame 閃爍,由 value-equal setState guard 收斂</span>
|
|
787
|
+
<span>4. 超出的 tag 以 DOM hidden attribute 隱藏(UA 樣式即 display:none;節點保留在 DOM,量測時先全部解除隱藏再計算)</span>
|
|
788
788
|
</div>
|
|
789
789
|
</div>
|
|
790
790
|
|
|
@@ -160,7 +160,7 @@ value 軸 controlled-only;open 軸方向相反 — **uncontrolled-only**:`defaul
|
|
|
160
160
|
|
|
161
161
|
## Internal API(PeoplePicker stack wrapper 私用,end-user 勿用)
|
|
162
162
|
|
|
163
|
-
`tagWrapperClassName` / `overflowWrapperClassName` / `tagAreaGapPx` / `tagAreaPaddingLeftPx` / `visibleCountOverride` 是 Combobox overflow 量測層的內部 hook,只供 DS-internal wrapper(PeoplePicker avatar stack)用,已標 `@internal`。`tagAreaPaddingLeftPx` 目前無 active consumer(PeoplePicker 走 `!px-
|
|
163
|
+
`tagWrapperClassName` / `overflowWrapperClassName` / `tagAreaGapPx` / `tagAreaPaddingLeftPx` / `visibleCountOverride` 是 Combobox overflow 量測層的內部 hook,只供 DS-internal wrapper(PeoplePicker avatar stack)用,已標 `@internal`。`tagAreaPaddingLeftPx` 目前無 active consumer(PeoplePicker 走 `!px-[var(--field-px)]` 路徑,`people-picker.tsx:371`),保留供未來精準 padding 但新 consumer 請先評估。`overflowShape` 是 public typed enum(矩形/圓形 +N),不在此列。
|
|
164
164
|
|
|
165
165
|
---
|
|
166
166
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @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.
|
|
2
|
-
// @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer
|
|
2
|
+
// @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer(people-picker.tsx:426-444)PersonDisplay SSOT 對齊;其他 Combobox consumer 走 default `<Tag>` 純文字 backward-compat。
|
|
3
3
|
// code-quality-allow: file-size — Combobox 含 NativeCombobox/CustomCombobox/useOverflowCount/OverflowTagList/ComboboxTagStack 5 子元件 + 共用 helpers,split-into-files 會破壞 measurement closures + 重複 type definitions。
|
|
4
4
|
import * as React from 'react'
|
|
5
5
|
import { X, ChevronDown } from 'lucide-react'
|
|
@@ -10,7 +10,7 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
|
|
|
10
10
|
import { Tag } from '@/design-system/components/Tag/tag'
|
|
11
11
|
import { ItemInlineAction, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
12
12
|
import { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'
|
|
13
|
-
import { SelectMenu, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
|
|
13
|
+
import { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
|
|
14
14
|
import { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'
|
|
15
15
|
import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
|
|
16
16
|
|
|
@@ -60,7 +60,7 @@ function useOverflowCount(
|
|
|
60
60
|
const [state, setState] = React.useState({ visibleCount: totalCount, ready: !enabled })
|
|
61
61
|
// 2026-05-18 Round 6 fix(per Codex M31 Round 6 H7 verdict + Step 5 共識):
|
|
62
62
|
// `ofEl.offsetWidth` 在 expanded state(visibleCount === totalCount → overflow=0 →
|
|
63
|
-
// `OverflowIndicator`
|
|
63
|
+
// `OverflowIndicator` count≤0 早退 `return null` → ofEl wrapper empty)= 0,fallback 60;
|
|
64
64
|
// 在 collapsed state(+N rendered)= 真實寬(~28-32px)。同 `available` 在兩 state 給
|
|
65
65
|
// 不同 verdict → 臨界值區 `max(B+g+Q, B+g+O) ≤ available < B+g+F` 振盪。Cache last
|
|
66
66
|
// non-zero measurement → measurement state-independent → oscillation 收斂。
|
|
@@ -168,7 +168,7 @@ function useOverflowCount(
|
|
|
168
168
|
}
|
|
169
169
|
if (ofEl) ofEl.hidden = count >= totalCount
|
|
170
170
|
// 2026-05-18 A' fix functional setState value-equal guard(per Codex Round 3 verdict):
|
|
171
|
-
//
|
|
171
|
+
// effect 內 calc 直跑 + ResizeObserver re-fire 同時跑 → 若每次都 new object setState
|
|
172
172
|
// 觸發 re-render 即使值沒變,可能 cascade。回 prev 不更新 = avoid 抖動。
|
|
173
173
|
setState(prev => (prev.visibleCount === count && prev.ready) ? prev : { visibleCount: count, ready: true })
|
|
174
174
|
}
|
|
@@ -180,13 +180,13 @@ function useOverflowCount(
|
|
|
180
180
|
//
|
|
181
181
|
// 2026-05-16 Race close:capture rAF IDs + cancel on cleanup(原版 race I3 沒 close
|
|
182
182
|
// 完;user 抓 path A 逐個 click vs path B 取消全選再全選 same length 不同 visible)。
|
|
183
|
-
// 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)—
|
|
184
|
-
// React 18 `useLayoutEffect` 在 DOM commit 後、瀏覽器繪製前同步跑,sync calc 在 paint 前
|
|
183
|
+
// 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— effect fire 時 calc() 直跑
|
|
185
184
|
// 完成 `el.hidden` 設定 + functional setState guard(value-equal 不更新 = 避免 ResizeObserver
|
|
186
|
-
// 抖動 cascade rerender)。double-rAF
|
|
185
|
+
// 抖動 cascade rerender)。double-rAF 為 fallback path(ResizeObserver / async update path)。
|
|
187
186
|
// 解 user verbatim「tag 過長 / 過多會先全顯再變 +N 閃動」root cause(per codex Round 3 cite
|
|
188
|
-
//
|
|
189
|
-
//
|
|
187
|
+
// 「render 沒設 hidden + calc imperative 寫 DOM」)。
|
|
188
|
+
// 註:Round 3 原以 useLayoutEffect sync calc(paint 前設 hidden)實作;2026-05-18 Round 5
|
|
189
|
+
// 已改 React.useEffect after-paint(nested 場景 parent ref attach 時序,詳上方 Round 5 註解)。
|
|
190
190
|
calc()
|
|
191
191
|
let rafId1 = 0, rafId2 = 0
|
|
192
192
|
const scheduleCalc = () => {
|
|
@@ -287,8 +287,9 @@ function OverflowTagList({ containerRef, items, size, wrap, renderTag, renderHid
|
|
|
287
287
|
|
|
288
288
|
// 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— 撤掉舊的 `style={{ opacity: ready ? 1 : 0 }}`
|
|
289
289
|
// gate(掛在 `<span className="contents">` 上,但 CSS Display 3 spec 規定 `display:contents` 元素不產生 box,
|
|
290
|
-
// parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker
|
|
291
|
-
//
|
|
290
|
+
// parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker 由 `useOverflowCount` effect 內
|
|
291
|
+
// `calc()` 直跑(現 L190;2026-05-18 Round 5 改 useEffect after-paint + double-rAF guard,詳該 hook 註解)解。
|
|
292
|
+
// `ready` state 保留供未來 instrumentation / debug,不再當 visual gate。
|
|
292
293
|
// 保留 `<span className="contents">` 維持 fragment-like rendering(parent JSX 期望 single child)。
|
|
293
294
|
void ready // intentional: ready 留供 future debug,目前無 consumer
|
|
294
295
|
return (
|
|
@@ -389,6 +390,8 @@ export interface ComboboxProps {
|
|
|
389
390
|
searchAriaLabel?: string
|
|
390
391
|
/** Empty-selection placeholder text。Default: 「選擇…」 */
|
|
391
392
|
emptyPlaceholder?: string
|
|
393
|
+
/** 搜尋無結果提示(2026-07-04 Q4 拍板接線)— forward SelectMenu primitive SSOT(default「沒有符合的選項」) */
|
|
394
|
+
emptyText?: string
|
|
392
395
|
/** a11y:無 Field wrapper 時提供 role='combobox' 的 accessible name(axe aria-input-field-name) */
|
|
393
396
|
'aria-label'?: string
|
|
394
397
|
/** Initial open state(uncontrolled)— 對齊 Select.defaultOpen / Radix Popover canonical。
|
|
@@ -602,8 +605,8 @@ function NativeCombobox({
|
|
|
602
605
|
) : null
|
|
603
606
|
|
|
604
607
|
return (
|
|
605
|
-
<div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',
|
|
606
|
-
wrap && 'items-start py-1',
|
|
608
|
+
<div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }), value.length > 0 && tagPadding[size], 'relative',
|
|
609
|
+
wrap && 'items-start py-1', className)}
|
|
607
610
|
style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode="edit" data-error={error ? '' : undefined}
|
|
608
611
|
onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>
|
|
609
612
|
{/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」
|
|
@@ -644,6 +647,7 @@ function CustomCombobox({
|
|
|
644
647
|
searchPlaceholder = '搜尋…', // i18n-allow: DS default
|
|
645
648
|
searchAriaLabel = '搜尋選項', // i18n-allow: DS default
|
|
646
649
|
emptyPlaceholder = '選擇…', // i18n-allow: DS default
|
|
650
|
+
emptyText,
|
|
647
651
|
defaultOpen = false,
|
|
648
652
|
onOpenChange,
|
|
649
653
|
__triggerRef,
|
|
@@ -675,6 +679,9 @@ function CustomCombobox({
|
|
|
675
679
|
// a11y: 為 listbox 容器(SelectMenu 內 PopoverContent)建立穩定 id,讓 trigger 的
|
|
676
680
|
// aria-controls 能指向它(WAI-ARIA combobox pattern 要求)。React.useId 在 SSR/CSR 都穩定。
|
|
677
681
|
const listboxId = React.useId()
|
|
682
|
+
// a11y(2026-07-05 D4):追蹤 cmdk active item id → searchIn='trigger' 搜尋 input 綁 aria-activedescendant
|
|
683
|
+
// (機制詳 select-menu.tsx useActiveDescendant docblock;必在 early return 前呼叫 — React #310 hook 順序)。
|
|
684
|
+
const activeOptionId = useActiveDescendant(listboxId, open)
|
|
678
685
|
|
|
679
686
|
React.useEffect(() => { if (!open) setSearch('') }, [open])
|
|
680
687
|
|
|
@@ -731,11 +738,11 @@ function CustomCombobox({
|
|
|
731
738
|
aria-required={fieldCtx?.required || undefined}
|
|
732
739
|
aria-describedby={fieldCtx?.descriptionId}
|
|
733
740
|
aria-errormessage={error ? fieldCtx?.errorId : undefined}
|
|
734
|
-
className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',
|
|
741
|
+
className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',
|
|
735
742
|
wrap && 'items-start py-1',
|
|
736
743
|
// 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
|
|
737
|
-
// 統一處理 — open=灰深(data-state)/ focus
|
|
738
|
-
|
|
744
|
+
// 統一處理 — open=灰深(data-state)/ focus=藍;2026-07-04 Q1:error 亦收進 error variant。
|
|
745
|
+
className)}
|
|
739
746
|
style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }}
|
|
740
747
|
data-field-mode="edit" data-error={error ? '' : undefined}
|
|
741
748
|
// WAI-ARIA APG combobox 鍵盤開啟語意 — 對齊 sibling Select(select.tsx:593-598)。
|
|
@@ -745,6 +752,8 @@ function CustomCombobox({
|
|
|
745
752
|
// open 後不 preventDefault 讓方向鍵自由流向選單導覽)。
|
|
746
753
|
// 純 additive:此 trigger 原無 onKeyDown,不覆寫既有 handler;open 邏輯仍走 setOpen SSOT。
|
|
747
754
|
onKeyDown={(e) => {
|
|
755
|
+
// 2026-07-05 D4 P0:open 後 ArrowUp/Down/Enter 轉送 cmdk root(select-menu.tsx forwardKeyToListbox)
|
|
756
|
+
if (open && forwardKeyToListbox(listboxId, e)) return
|
|
748
757
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
749
758
|
// 2026-06-11 P0 a11y(R2,同 select.tsx 修):Combobox 恆 searchable → 原 `!searchable`
|
|
750
759
|
// guard 使 Enter/Space 永遠開不了(僅 ArrowDown 可)。正確 guard = !open。
|
|
@@ -782,6 +791,8 @@ function CustomCombobox({
|
|
|
782
791
|
// empty-state fallback to trigger placeholder canonical。
|
|
783
792
|
placeholder={items.length === 0 ? placeholder : ''} onClick={(e) => { e.stopPropagation(); setOpen(true) }}
|
|
784
793
|
aria-label={searchAriaLabel}
|
|
794
|
+
// a11y(2026-07-05 D4):cmdk active item id(useActiveDescendant)→ SR 播報方向鍵導覽中的 option
|
|
795
|
+
aria-activedescendant={activeOptionId}
|
|
785
796
|
className="flex-1 min-w-[60px] bg-transparent outline-none text-body leading-compact relative z-10" />
|
|
786
797
|
) : undefined} />
|
|
787
798
|
) : (
|
|
@@ -813,6 +824,7 @@ function CustomCombobox({
|
|
|
813
824
|
return (
|
|
814
825
|
<SelectMenu
|
|
815
826
|
loading={loading}
|
|
827
|
+
emptyText={emptyText}
|
|
816
828
|
options={menuOptions}
|
|
817
829
|
value={value}
|
|
818
830
|
onValueChange={onChange as (value: string | string[]) => void}
|
|
@@ -862,7 +874,8 @@ export const comboboxMeta = {
|
|
|
862
874
|
sizes: {
|
|
863
875
|
|
|
864
876
|
},
|
|
865
|
-
|
|
877
|
+
// 'active' 移除 — trigger 走 Field chrome(field-wrapper 無按壓態)、menu row 走 MenuItem(其 meta 無 active)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。
|
|
878
|
+
states: ['default', 'hover', 'focus-visible', 'disabled'],
|
|
866
879
|
tokens: {
|
|
867
880
|
bg: ['bg-disabled', 'bg-transparent'],
|
|
868
881
|
fg: ['text-fg-disabled', 'text-fg-muted'],
|
|
@@ -69,18 +69,16 @@ export const CompositionRules: Story = {
|
|
|
69
69
|
<li><LinkTo kind="Design System/Components/PeoplePicker/展示" name="單人"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">PeoplePicker</span></LinkTo>——人員搜尋(如指派任務負責人)</li>
|
|
70
70
|
</ul>
|
|
71
71
|
|
|
72
|
-
<h4>Pattern 2 —
|
|
73
|
-
<p>需要「全域 keyboard 觸發 + 跨頁搜尋 / 動作」→
|
|
74
|
-
<pre className="text-xs"><code>{`<
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</Command>
|
|
83
|
-
</Popover>`}</code></pre>
|
|
72
|
+
<h4>Pattern 2 — CommandDialog 組成 Command Palette(Cmd+K)</h4>
|
|
73
|
+
<p>需要「全域 keyboard 觸發 + 跨頁搜尋 / 動作」→ 用本 DS export 的 <code>CommandDialog</code>(cmdk + Radix Dialog 包裝,內部已包 Command),不自行組合浮層(對齊 Linear ⌘K / Raycast / VS Code Quick Pick):</p>
|
|
74
|
+
<pre className="text-xs"><code>{`<CommandDialog open={cmdkOpen} onOpenChange={setCmdkOpen}>
|
|
75
|
+
<CommandInput placeholder="輸入指令..." />
|
|
76
|
+
<CommandList>
|
|
77
|
+
<CommandGroup heading="動作">
|
|
78
|
+
<CommandItem>新建文件</CommandItem>
|
|
79
|
+
</CommandGroup>
|
|
80
|
+
</CommandList>
|
|
81
|
+
</CommandDialog>`}</code></pre>
|
|
84
82
|
<p className="text-fg-muted">禁止:在產品端自己刻搜尋框 + 過濾清單 — 一律重用 Command,不要重造一份。</p>
|
|
85
83
|
</div>
|
|
86
84
|
</div>
|