@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
|
@@ -22,7 +22,8 @@ import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'
|
|
|
22
22
|
* Consumer 傳 `ratio={n/m}` 數字計算(如 16/9 = 1.7777)。
|
|
23
23
|
*
|
|
24
24
|
* ── 常見消費者 ──
|
|
25
|
-
* Coachmark media / Carousel item image
|
|
25
|
+
* Coachmark media(coachmark.tsx)/ FileViewer 圖片預覽(file-viewer.tsx)/ Carousel item image、Card thumbnail(未來)
|
|
26
|
+
*(Chart 現用 raw aspect-video 非消費者 — 2026-07-04 grep 驗證)
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
export type AspectRatioProps = React.ComponentPropsWithoutRef<typeof AspectRatioPrimitive.Root>
|
|
@@ -47,7 +48,7 @@ export const aspectRatioMeta = {
|
|
|
47
48
|
sizes: {
|
|
48
49
|
|
|
49
50
|
},
|
|
50
|
-
// 2026-06-05 fix(deep-audit):純 structural container,無互動狀態(spec
|
|
51
|
+
// 2026-06-05 fix(deep-audit):純 structural container,無互動狀態(spec「為何無 ColorMatrix / SizeMatrix / StateBehavior」段;段名指法免行號漂移)。
|
|
51
52
|
// 原 ['default','hover','active','focus-visible','disabled'] 是 Phase 1 mechanical placeholder,與 spec 矛盾。
|
|
52
53
|
states: ['default'],
|
|
53
54
|
tokens: {
|
|
@@ -32,7 +32,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
|
|
|
32
32
|
|
|
33
33
|
const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
34
34
|
<section className="mb-12">
|
|
35
|
-
<h2 className="text-
|
|
35
|
+
<h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
|
|
36
36
|
{children}
|
|
37
37
|
</section>
|
|
38
38
|
)
|
|
@@ -71,51 +71,31 @@ Avatar 是視覺身份標識——代表一個人、一個實體(專案、組
|
|
|
71
71
|
hover ProfileCard 是 preview,所有 preview 必提供「看完整資料」的路徑。
|
|
72
72
|
缺 `onViewMore` 使用者點不到完整 profile → hover 成單向死路。對齊 Slack / Linear / Notion / Figma / Gmail hover-profile popover 必有 "View profile" link 的世界級 pattern。詳 `../ProfileCard/profile-card.spec.md`「View more」canonical。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
73
73
|
|
|
74
|
-
**適用範圍(ALL person avatars,無例外)**:
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
- FileItem rich 的 author avatar
|
|
78
|
-
- Menu / Dropdown 的 assignee / owner
|
|
79
|
-
- Chat / thread / notification 頭像
|
|
80
|
-
|
|
81
|
-
**世界級對照**(hover → profile 是 default,不是 opt-in):
|
|
82
|
-
- Slack:hover user avatar → profile popover(default)
|
|
83
|
-
- Figma:hover member avatar → profile card(default)
|
|
84
|
-
- Linear:hover assignee avatar → user card(default)
|
|
85
|
-
- Notion:hover person tag → user card(default)
|
|
86
|
-
- GitHub:hover username + avatar → user hover card(default)
|
|
74
|
+
**適用範圍(ALL person avatars,無例外)**:PeoplePicker tag / table cell 頭像 / sidebar user / comment author / avatar 堆疊 / Dialog·Sheet member list / FileItem rich author / Menu·Dropdown assignee·owner / chat·thread·notification 頭像。
|
|
75
|
+
|
|
76
|
+
**世界級對照**(hover → profile 是 default,不是 opt-in):Slack / Figma / Linear / Notion / GitHub —— hover person avatar(或 username)→ profile popover / user card,全部 default 行為。
|
|
87
77
|
|
|
88
78
|
**例外(ProfileCard 不適用,但 hoverCard 通用行為仍可用)**:
|
|
89
79
|
- Entity avatar(專案 / 組織 logo / app icon)— 不代表人員,**不自動套 ProfileCard**(ProfileCard 是人專用內容模板:64px 圓形 + presence 狀態 + Chat/通話 動作,實體沒有)。但 `hoverCard` prop 本身是**通用行為**(型別 `React.ReactNode`,avatar.tsx `AvatarProps.hoverCard`),entity / square avatar **仍可配 hoverCard 顯示其他內容**(如實體資訊卡);此時 focus ring 自動跟隨 square 形狀(rounded-md)。未來若需實體資訊卡,做成**平行的內容元件**(對齊 Atlassian `ProfileCard` / `TeamProfileCard` 分離 + GitHub `data-hovercard-type` 各 type 獨立內容),**不泛化 ProfileCard**。
|
|
90
80
|
- Bot / automated account avatar(Atlas / Octocat 等)— 本 DS 後續視需求決
|
|
91
81
|
|
|
92
|
-
**為什麼必 default**:
|
|
93
|
-
- Person avatar 單獨無 context(只有照片 + 首字),user 不知是誰(世界級對照詳上方清單)
|
|
94
|
-
- Opt-in 模式會讓一半的 consumer 忘記加,DS 品質不可控
|
|
95
|
-
|
|
96
|
-
**違規檢測(M10 proactive scan)**:
|
|
97
|
-
`grep '<Avatar.*alt=\{'` 全 src,檢查有無 person name(非 entity)alt 且沒 `hoverCard` prop。
|
|
98
|
-
未來會加 hook `check_avatar_hovercard.sh` 自動攔截。
|
|
82
|
+
**為什麼必 default**:Person avatar 單獨無 context(只有照片 + 首字),user 不知是誰;opt-in 模式會讓一半 consumer 忘記加,DS 品質不可控。**違規檢測(M10 proactive scan)**:`grep '<Avatar.*alt=\{'` 全 src,檢查 person name(非 entity)alt 且沒 `hoverCard` prop 者;目前無專用 hook,靠 grep 掃描。
|
|
99
83
|
|
|
100
84
|
**Keyboard 可達 canonical(2026-04-22 D4 UX audit 補齊)**:
|
|
101
85
|
當 Avatar 有 `hoverCard` prop,Avatar wrapper **必 keyboard focusable**,讓 keyboard-only user 也能 reach ProfileCard popover。Avatar 元件內部自動套:
|
|
102
|
-
- `tabIndex=0`(可 Tab
|
|
103
|
-
- `role="
|
|
86
|
+
- `tabIndex=0`(可 Tab 到;focus 時 Radix hover-card 自動開卡)
|
|
87
|
+
- `role="img"` + `aria-label`(取 `alt`)——2026-07-07 CI axe 補修:aria-label 掛在無 role 的 generic `<div>` 是 ARIA 禁用組合(axe `aria-prohibited-attr`,serious);Avatar 語義本質 = 身份圖像,`role="img"` 讓命名合法、子孫自動 presentational、不對 AT 承諾任何互動行為
|
|
104
88
|
- `focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1`(keyboard focus 視覺 indicator)
|
|
89
|
+
- **不掛 `role="button"` / `aria-haspopup`**(2026-07-06 user 拍板拆除):原本宣告了卻無 Enter/Space 行為、無 `aria-expanded` = 對 AT 的空承諾(D4 finding);對齊 Radix hover-card 官方(刻意零 popup ARIA,定位 sighted-user 輔助)+ GitHub hovercard(trigger 無 popup ARIA)
|
|
105
90
|
- 若無 `hoverCard` → 純展示 `<div>` 不 focusable(避免 tabTrap 噪音)
|
|
106
91
|
|
|
107
|
-
Rationale:Radix `HoverCardTrigger asChild` 不自動加 tabIndex 給 non-focusable child。若 Avatar wrapper 是 `<div>` 沒 tabIndex,keyboard user 無法 reach ProfileCard content,違反 WCAG 2.1.1(Keyboard)
|
|
92
|
+
Rationale:Radix `HoverCardTrigger asChild` 不自動加 tabIndex 給 non-focusable child。若 Avatar wrapper 是 `<div>` 沒 tabIndex,keyboard user 無法 reach ProfileCard content,違反 WCAG 2.1.1(Keyboard)。此 canonical 確保 DS-wide「person avatar → ProfileCard」keyboard 可達,且 ARIA 只宣告真的有的行為。
|
|
108
93
|
|
|
109
94
|
**Status dot a11y(2026-04-22 D4 UX audit 調整)**:Avatar status dot 內部 span 從原 `role="status"` + `aria-label="presence: online"` 改為 `aria-hidden`。Rationale:`role="status"` 是 live region,多 Avatars(member list / mention picker)會造成 SR 洪水噪音;presence 資訊應整合進 parent avatar 的 `aria-label`(world-class Slack / Teams / Discord 共通)— consumer 傳 `alt` 時自帶語意(e.g. `alt="Alan Chen (online)"`)。
|
|
110
95
|
|
|
111
96
|
### 溢出人員列表(+N)
|
|
112
97
|
|
|
113
|
-
Avatar 堆疊的「+N」hover 也出 **HoverCard**(不是 Tooltip
|
|
114
|
-
- 列表內的 person tag 需要 dismiss 功能(互動)
|
|
115
|
-
- 每個 person tag hover 需要再出 name card(嵌套 HoverCard)
|
|
116
|
-
- Tooltip 是非互動的,不支援上述需求
|
|
117
|
-
|
|
118
|
-
架構:`+N → HoverCard(人員列表)→ 每個人名 → HoverCard(name card)`
|
|
98
|
+
Avatar 堆疊的「+N」hover 也出 **HoverCard**(不是 Tooltip)——列表內 person tag 需要 dismiss 互動、每個 person tag hover 需再出 name card(嵌套 HoverCard),Tooltip 非互動不支援。架構:`+N → HoverCard(人員列表)→ 每個人名 → HoverCard(name card)`
|
|
119
99
|
|
|
120
100
|
### 純文字截斷 vs 人員頭像
|
|
121
101
|
|
|
@@ -264,7 +244,7 @@ Avatar `badgeCount` 內部消費 DS `<Badge variant="critical" max={99}>`,加上
|
|
|
264
244
|
- **無 `alt` 時 fallback**:image 模式自動降級 initials / icon;一律不靜默渲染無 SR 標的元素。
|
|
265
245
|
- **Status dot SR 處理**:status dot 內部 span `aria-hidden`(presence 訊號整合進 parent `alt`),避免 `role="status"` live region 在 member list 造成 SR 洪水(詳「Status dot a11y」段)。
|
|
266
246
|
- **`badgeCount` 語意**:內部消費 `<Badge variant="critical">`,由 Avatar 傳入 `aria-label="N unread"` 提供計數語義(Badge 本身只有 `role="status"`,不自產 aria-label);`badgeCount <= 0` 不渲染避免空 announce。
|
|
267
|
-
- **HoverCard 整合**:Avatar 帶 `hoverCard` prop 時自動 `tabIndex=0` + `role="
|
|
247
|
+
- **HoverCard 整合**:Avatar 帶 `hoverCard` prop 時自動 `tabIndex=0` + `role="img"` + `aria-label`(取 `alt`)+ `focus-visible:ring-2`,確保 keyboard user 能 Tab 進入觸發 ProfileCard popover;**不掛 `role="button"` / `aria-haspopup`**(2026-07-06 拆除;role="img" 為 2026-07-07 axe 補修,詳「Keyboard 可達 canonical」段)。
|
|
268
248
|
- **Image alt 語意**:meaningful image(person photo / brand logo)用實質 `alt`;decorative-only(極少)走 `alt=""`,但 Avatar 本質是身份識別,decorative 用法應改用 Icon 元件。
|
|
269
249
|
|
|
270
250
|
---
|
|
@@ -41,7 +41,7 @@ export const Modes = {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/* @story-trait-rationale: Shapes / Colors / AllSizes retired 2026-05-17 per audit Dim 24 —
|
|
44
|
-
* anatomy.stories.tsx SizeMatrix(含 circle/square shape axis)+ ColorMatrix(
|
|
44
|
+
* anatomy.stories.tsx SizeMatrix(含 circle/square shape axis)+ ColorMatrix(12 hue + neutral × subtle/solid)
|
|
45
45
|
* 已 cover trait grids。展示層保留 typical real-product 情境(Modes / Fallback / Status / Count badge 等),
|
|
46
46
|
* 避免跟 anatomy trait grid 重複。 */
|
|
47
47
|
|
|
@@ -145,6 +145,16 @@ export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
145
145
|
const AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|
146
146
|
({ size = 32, shape = 'circle', src, alt, icon: Icon, color = 'neutral', solid = false, status, badgeCount, badgeAriaLabel, hoverCard, className, style, ...props }, ref) => {
|
|
147
147
|
const [imgError, setImgError] = React.useState(false)
|
|
148
|
+
// 2026-07-05 D4:imgError 隨 src 變更 reset — 原 state 首次 onError 後永久 true,consumer 之後
|
|
149
|
+
// 換有效新 src(重傳頭像 / signed URL 刷新)時 showImage 永遠 false,<img> 不再掛載 → 新圖
|
|
150
|
+
// 永不嘗試載入、終身卡 initials fallback。React 官方 adjust-state-on-prop-change 寫法
|
|
151
|
+
//(render-time derived reset,較 useEffect 少一輪 render);對齊 Radix useImageLoadingStatus /
|
|
152
|
+
// MUI useLoaded 皆隨 src reset 的 canonical。
|
|
153
|
+
const [prevSrc, setPrevSrc] = React.useState(src)
|
|
154
|
+
if (prevSrc !== src) {
|
|
155
|
+
setPrevSrc(src)
|
|
156
|
+
setImgError(false)
|
|
157
|
+
}
|
|
148
158
|
const documentTheme = useDocumentTheme()
|
|
149
159
|
const isTableScrolling = useTableIsScrolling()
|
|
150
160
|
// 2026-05-13 R3.5(per codex Q3 verdict + user 拍「想盡辦法 auto-handle prereq」):
|
|
@@ -227,16 +237,22 @@ const AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|
|
227
237
|
)
|
|
228
238
|
|
|
229
239
|
const hasOverlay = status || typeof badgeCount === 'number'
|
|
230
|
-
// Keyboard access canonical(D4
|
|
231
|
-
// 需 keyboard 可達 — Radix `HoverCardTrigger asChild` 不自動加
|
|
232
|
-
// `<div>` 會讓 keyboard-only user 無法 reach
|
|
233
|
-
//
|
|
234
|
-
// `
|
|
240
|
+
// Keyboard access canonical(D4 2026-04-22 finding;2026-07-06 user 拍板修正 ARIA 空承諾):
|
|
241
|
+
// Avatar with `hoverCard` 需 keyboard 可達 — Radix `HoverCardTrigger asChild` 不自動加
|
|
242
|
+
// tabIndex,non-focusable `<div>` 會讓 keyboard-only user 無法 reach popover(WCAG 2.1.1)。
|
|
243
|
+
// 解:`tabIndex=0` + `role="img"` + `aria-label` + focus ring(focus 時 Radix 自動開卡)。
|
|
244
|
+
// **不掛 `role="button"` / `aria-haspopup`**(2026-07-06 拆除):原本宣告了卻沒有
|
|
245
|
+
// Enter/Space 行為、沒有 aria-expanded = 對 AT 的空承諾;對齊 Radix hover-card 官方
|
|
246
|
+
// (刻意零 popup ARIA,hover card 定位 sighted-user 輔助)+ GitHub hovercard(trigger
|
|
247
|
+
// 純連結無 popup ARIA)。若無 hoverCard 則維持純展示 `<div>`。
|
|
248
|
+
// **`role="img"`(2026-07-07 CI axe 補修)**:aria-label 在無 role 的 generic `<div>` 上
|
|
249
|
+
// 是 ARIA 禁用組合(axe aria-prohibited-attr,serious;2026-07-06 拆 role="button" 後裸露)。
|
|
250
|
+
// Avatar 語義本質 = 身份圖像 → `role="img"` 讓 aria-label 合法、子孫自動 presentational、
|
|
251
|
+
// 且不對 AT 承諾任何互動行為 — 與「拆假承諾」拍板意圖一致(emoji span / MUI Avatar img alt 同款語義)。
|
|
235
252
|
const focusableProps = hoverCard
|
|
236
253
|
? {
|
|
237
254
|
tabIndex: 0,
|
|
238
|
-
role: '
|
|
239
|
-
'aria-haspopup': 'dialog' as const,
|
|
255
|
+
role: 'img',
|
|
240
256
|
'aria-label': alt ?? 'View profile',
|
|
241
257
|
}
|
|
242
258
|
: {}
|
|
@@ -320,7 +336,6 @@ const AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|
|
320
336
|
)
|
|
321
337
|
}
|
|
322
338
|
)
|
|
323
|
-
AvatarInner.displayName = 'AvatarInner'
|
|
324
339
|
|
|
325
340
|
// ── AvatarData ─────────────────────────────────────────────────────────────
|
|
326
341
|
// 資料型別,讓 consumer 傳資料而非 ReactNode。
|
|
@@ -336,8 +351,9 @@ export interface AvatarData {
|
|
|
336
351
|
/**
|
|
337
352
|
* Person avatar hover ProfileCard(DS-wide canonical,person avatar 預設必有,見 avatar.spec.md)。
|
|
338
353
|
* Entity avatar(專案 / 組織 logo)不帶 → consumer 不傳 hoverCard 即豁免。
|
|
339
|
-
*
|
|
340
|
-
* 需 forward 此 prop 到內部 <Avatar hoverCard={
|
|
354
|
+
* 消費 AvatarData 的 row primitive(MenuItem menu-item.tsx / SelectionItem selection-item.tsx,
|
|
355
|
+
* DropdownMenu / SelectMenu 經 MenuItem)需 forward 此 prop 到內部 <Avatar hoverCard={...} />。
|
|
356
|
+
* 例外:ProfileCard 內部 Avatar 不 forward(卡片本身就是 hover 目的地,nested hover-in-hover 不合理)。
|
|
341
357
|
*/
|
|
342
358
|
hoverCard?: React.ReactNode
|
|
343
359
|
}
|
|
@@ -347,12 +363,10 @@ export interface AvatarData {
|
|
|
347
363
|
export const avatarMeta = {
|
|
348
364
|
component: 'Avatar',
|
|
349
365
|
family: null, // non-family composite / overlay / layout
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
},
|
|
353
|
-
sizes: {
|
|
354
|
-
|
|
355
|
-
},
|
|
366
|
+
// 2026-07-04 audit 標記:Avatar 無 variant 軸、size 為任意 number(px,由 consumer context 決定
|
|
367
|
+
// — item-anatomy AVATAR_SIZE 查表 / ProfileCard 40 等),無 discrete tier → 兩欄留空為 intentional 非漏填
|
|
368
|
+
variants: {},
|
|
369
|
+
sizes: {},
|
|
356
370
|
states: ['default', 'focus-visible', 'disabled'], // 2026-06-11 R2:本身無自有 hover/active(spec L279;hoverCard 互動屬 HoverCard),
|
|
357
371
|
tokens: {
|
|
358
372
|
bg: ['bg-surface-raised'],
|
|
@@ -25,8 +25,8 @@ const VARIANTS: VariantKey[] = ['critical', 'high', 'medium', 'low']
|
|
|
25
25
|
type ColorSpec = { bg: string; text: string }
|
|
26
26
|
|
|
27
27
|
const TOKEN_MAP: Record<VariantKey, ColorSpec> = {
|
|
28
|
-
critical: { bg: '--notification', text: '
|
|
29
|
-
high: { bg: '--info', text: '
|
|
28
|
+
critical: { bg: '--notification', text: '--on-emphasis' },
|
|
29
|
+
high: { bg: '--info', text: '--on-emphasis' },
|
|
30
30
|
medium: { bg: '--info-subtle', text: '--info-text' },
|
|
31
31
|
low: { bg: '--secondary', text: '--fg-muted' },
|
|
32
32
|
}
|
|
@@ -167,13 +167,13 @@ Contrast floor **是下限不是上限**——不限制 semantic 繼續往上。
|
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
170
|
-
## 字體例外:`text-[10px]`(documented
|
|
170
|
+
## 字體例外:`text-[10px]`(documented micro-indicator 例外)
|
|
171
171
|
|
|
172
|
-
Badge count 模式的數字使用 `text-[10px]
|
|
172
|
+
Badge count 模式的數字使用 `text-[10px]`(10px,與 typography 系統最小的 `--font-footnote` 同值;12px 是 `--font-caption`)。不消費 `text-footnote` utility——該 utility 綁 line-height 1.3 且語意為法律文字/來源標注,Badge count 需要 `leading-none`(circle 內數字置中 canonical)。這是 **Badge 獨有的合法例外**:
|
|
173
173
|
|
|
174
174
|
- **為什麼不加新 typography token**: 10px 是「count indicator 專用尺寸」,全系統**只有 Badge / OverflowIndicator 兩處**使用;創一個 `--font-micro` token 只為這兩個消費者會過度 generalization
|
|
175
|
-
- **為什麼合法**: Badge count 是輔助 indicator(次要訊號),12px
|
|
176
|
-
- **其他元件不得比照**: 若新元件覺得需要 `text-[10px]`,先問「真的不是
|
|
175
|
+
- **為什麼合法**: Badge count 是輔助 indicator(次要訊號),caption(12px)字寬在 16×16 的圓形 Badge 內缺乏 padding 呼吸(字徑/圓徑比例過密)。對照世界級:Material Badge 10sp、Polaris Badge 字級小於 body、GitHub Counter 10-11px 都小於 body <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
176
|
+
- **其他元件不得比照**: 若新元件覺得需要 `text-[10px]`,先問「真的不是 caption 12px 可以解決嗎」,99% 情境 caption 夠用;真有需求跟 Badge / OverflowIndicator 歸同一 micro-indicator family 討論
|
|
177
177
|
|
|
178
178
|
---
|
|
179
179
|
|
|
@@ -395,4 +395,5 @@ Badge 是**計數 / 狀態視覺指示器**,本身**非互動元件**:
|
|
|
395
395
|
|
|
396
396
|
- `avatar.spec.md`
|
|
397
397
|
- `button.spec.md`
|
|
398
|
+
- `overflow-indicator.spec.md`
|
|
398
399
|
- `tag.spec.md`
|
|
@@ -77,8 +77,11 @@ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
|
77
77
|
({ variant, dot = false, count, max, className, role, ...props }, ref) => {
|
|
78
78
|
// dot 預設 attention 色 = critical(非 cva 的 count default 'low');count 沿用 cva default 'low'。
|
|
79
79
|
const effectiveVariant = variant ?? (dot ? 'critical' : 'low')
|
|
80
|
+
// 2026-07-05 D4 修(續 07-04 guard):count 模式 count == null → 整顆 return null 不渲染 —
|
|
81
|
+
// 原 guard 只擋文字,仍渲染 16px 空 pill + 空 role=status live region(Ant/Material 皆不渲染)。
|
|
82
|
+
if (!dot && count == null) return null
|
|
80
83
|
const display = dot ? null : (
|
|
81
|
-
max != null && count
|
|
84
|
+
max != null && count! > max ? `${max}+` : `${count}`
|
|
82
85
|
)
|
|
83
86
|
|
|
84
87
|
// a11y(2026-04-25 axe aria-prohibited-attr fix):
|
|
@@ -36,7 +36,7 @@ export const Overview: Story = {
|
|
|
36
36
|
<div className="flex flex-col gap-10">
|
|
37
37
|
<div>
|
|
38
38
|
<H3>Anatomy</H3>
|
|
39
|
-
<Desc>Breadcrumb
|
|
39
|
+
<Desc>Breadcrumb 基於 shadcn/ui Breadcrumb 結構橋接 DS token,基礎結構為原生 HTML + Tailwind(asChild 用 Radix Slot;truncate Tooltip 與收合 DropdownMenu 消費 DS 內部元件)。使用 `<nav aria-label="Breadcrumb">` 確保正確 a11y。</Desc>
|
|
40
40
|
<Breadcrumb>
|
|
41
41
|
<BreadcrumbList>
|
|
42
42
|
<BreadcrumbItem>
|
|
@@ -439,7 +439,7 @@ export const CollapseMatrix: Story = {
|
|
|
439
439
|
</BreadcrumbItem>
|
|
440
440
|
</BreadcrumbList>
|
|
441
441
|
</Breadcrumb>
|
|
442
|
-
<p className="text-footnote text-fg-muted mt-3"
|
|
442
|
+
<p className="text-footnote text-fg-muted mt-3">本 story 為靜態結構展示:手動 `<BreadcrumbItem><BreadcrumbEllipsis />` 只渲染 ⋯ 按鈕本身(消費 ItemInlineActionButton,帶 aria-label / focus / hover bg),未接 DropdownMenu — 此處點擊無展開行為。完整互動接線(Ellipsis 包在 DropdownMenuTrigger 內展開中間層)見展示層「可互動省略」story;declarative `items` 與 compositional 超過 `maxItems` 的自動收合路徑則由元件自動接上 DropdownMenu。</p>
|
|
443
443
|
</div>
|
|
444
444
|
</div>
|
|
445
445
|
),
|
|
@@ -244,16 +244,21 @@ export const LongPathRule: Story = {
|
|
|
244
244
|
<BreadcrumbLink href="#">組織</BreadcrumbLink>
|
|
245
245
|
</BreadcrumbItem>
|
|
246
246
|
<BreadcrumbSeparator />
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
247
|
+
{/* 2026-07-04 修 broken demo:裸 DropdownMenu 會被 BreadcrumbList children filter 丟棄
|
|
248
|
+
(breadcrumb.tsx compositionalContent 只認 BreadcrumbItem)→ 包 BreadcrumbItem
|
|
249
|
+
(對齊 declarative 模式內部 ellipsis 作法) */}
|
|
250
|
+
<BreadcrumbItem>
|
|
251
|
+
<DropdownMenu>
|
|
252
|
+
<DropdownMenuTrigger asChild>
|
|
253
|
+
<BreadcrumbEllipsis />
|
|
254
|
+
</DropdownMenuTrigger>
|
|
255
|
+
<DropdownMenuContent>
|
|
256
|
+
<DropdownMenuItem>部門</DropdownMenuItem>
|
|
257
|
+
<DropdownMenuItem>設計團隊</DropdownMenuItem>
|
|
258
|
+
<DropdownMenuItem>成員管理</DropdownMenuItem>
|
|
259
|
+
</DropdownMenuContent>
|
|
260
|
+
</DropdownMenu>
|
|
261
|
+
</BreadcrumbItem>
|
|
257
262
|
<BreadcrumbSeparator />
|
|
258
263
|
<BreadcrumbItem>
|
|
259
264
|
<BreadcrumbLink href="#">權限</BreadcrumbLink>
|
|
@@ -30,7 +30,7 @@ benchmark:
|
|
|
30
30
|
## 定位
|
|
31
31
|
|
|
32
32
|
Breadcrumb 顯示「當前頁面在資訊階層中的位置」,同時提供快速回到上層的路徑導覽。
|
|
33
|
-
基於 shadcn/ui Breadcrumb
|
|
33
|
+
基於 shadcn/ui Breadcrumb 結構(原生 nav / ol / li 語意 + Tailwind;`asChild` 多型渲染用 Radix Slot,truncate Tooltip 與 collapse DropdownMenu 消費 DS 內部 Radix-based 元件),橋接設計系統 token。
|
|
34
34
|
|
|
35
35
|
**Breadcrumb 是「你在哪裡」的指示器,不是「你可以去哪裡」的選單**——反映當前路徑,不是全部可能路徑。
|
|
36
36
|
|
|
@@ -113,7 +113,7 @@ Breadcrumb 顯示「當前頁面在資訊階層中的位置」,同時提供快
|
|
|
113
113
|
## 對齊既有設計語言
|
|
114
114
|
|
|
115
115
|
- **色彩三階層**(fg-secondary → foreground / primary-hover → fg-muted)跟 MenuItem / Tabs trigger / SegmentedControl 的三階層結構一致:**次要 → 主要(或 hover 強調) → 弱化**
|
|
116
|
-
- **`primary-hover` 在 Breadcrumb
|
|
116
|
+
- **`primary-hover` 在 Breadcrumb 的用法是「互動時文字變藍」**——屬於全系統「瞬時互動高亮」色彩語言(連結 hover 換色 canonical 同族,`primary-hover` = 瞬時態專用階)。注意與「持續選中」分軌:選中規則(Tabs underline / Chip / SegmentedControl selected)自 2026-07-06 拍板起用 primary **base**(semantic.css「選中狀態」段 SSOT)——瞬時弱、持續強
|
|
117
117
|
- **分隔 icon 用 `ChevronRight`**(不用 `/` 字元)對齊 Button `endIcon` 的「方向指示」icon 選擇
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -177,6 +177,8 @@ Breadcrumb 顯示「當前頁面在資訊階層中的位置」,同時提供快
|
|
|
177
177
|
| `ellipsis`(BreadcrumbEllipsis 包裝)| **0** | 永遠完整顯示 ⋯ |
|
|
178
178
|
| `BreadcrumbSeparator` | **0** | 永遠完整(否則 path 視覺斷裂)|
|
|
179
179
|
|
|
180
|
+
**Compositional 手動 ellipsis 注意**:compositional path 按位置自動分派 role(first=root / last=current / 其餘=middle,`breadcrumb.tsx` `cloneWithRole` 尊重顯式 role),手動 `<BreadcrumbItem><BreadcrumbEllipsis/></BreadcrumbItem>` 需顯式傳 `role="ellipsis"` 才拿到 shrink:0(否則按位置拿 middle / shrink:2);declarative `items` 與自動收合路徑由元件自動標 `role="ellipsis"`。
|
|
181
|
+
|
|
180
182
|
**為何 flex-shrink hierarchy 不用 fixed max-width**:
|
|
181
183
|
- 容器寬 / items 少 → 各 item 自然寬度,**不浪費剩餘空間**
|
|
182
184
|
- 容器窄 / items 多 → 按 priority 縮(root 先 → middle → current 最後)
|
|
@@ -247,7 +249,7 @@ ColorMatrix 已建:展示 BreadcrumbLink / Page / Separator / Ellipsis 四種節
|
|
|
247
249
|
|
|
248
250
|
## A11y 預設
|
|
249
251
|
|
|
250
|
-
**ARIA / Pattern
|
|
252
|
+
**ARIA / Pattern**:結構為原生 HTML(nav + ol + li + a/span),a11y 來自原生 HTML 語意(`asChild` 用 Radix Slot 只合併 prop、不管理焦點 / ARIA / 鍵盤;truncate Tooltip 與 collapse DropdownMenu 各自帶自身 a11y)— 外層 `<nav aria-label="Breadcrumb">`、當前頁 `<span role="link" aria-disabled="true" aria-current="page">`、分隔符 `aria-hidden`(不進無障礙樹)。對齊 [WAI-ARIA Breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/)。
|
|
251
253
|
|
|
252
254
|
**為何當前頁加 `role="link" aria-disabled="true"`**:BreadcrumbPage 是「被 disable 的連結」— 把它標記成連結家族(`role="link"`)讓螢幕閱讀器把整條 breadcrumb 念成同一個連結序列,再以 `aria-disabled="true"` 告知不可操作(對齊 shadcn/ui Breadcrumb canonical `BreadcrumbPage`)。WAI-ARIA APG 允許 current 用 link + `aria-current`,shadcn 加 `aria-disabled` 是合理增強。
|
|
253
255
|
|
|
@@ -205,8 +205,10 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, BreadcrumbListProps>(
|
|
|
205
205
|
// Declarative mode(items prop provided):自動 render + auto-collapse
|
|
206
206
|
const declarativeContent = React.useMemo(() => {
|
|
207
207
|
if (!items) return null
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
// 2026-07-04 修:非字串 label 的 key 原用 Math.random() → 每次 render 新 key 造成
|
|
209
|
+
// BreadcrumbItem unmount/remount(重置 TruncatedLabel ResizeObserver 與 state)→ 改 stable idx key
|
|
210
|
+
const renderItem = (spec: BreadcrumbItemSpec, role: 'root' | 'middle' | 'current', idx: number) => (
|
|
211
|
+
<BreadcrumbItem key={`${role}-${typeof spec.label === 'string' ? spec.label : idx}`} role={role}>
|
|
210
212
|
{role === 'current'
|
|
211
213
|
? <BreadcrumbPage startIcon={spec.startIcon}>
|
|
212
214
|
<TruncatedLabel fullText={typeof spec.label === 'string' ? spec.label : undefined}>{spec.label}</TruncatedLabel>
|
|
@@ -264,7 +266,7 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, BreadcrumbListProps>(
|
|
|
264
266
|
</BreadcrumbItem>
|
|
265
267
|
)
|
|
266
268
|
} else {
|
|
267
|
-
rendered.push(renderItem(entry.spec, entry.role))
|
|
269
|
+
rendered.push(renderItem(entry.spec, entry.role, i))
|
|
268
270
|
}
|
|
269
271
|
})
|
|
270
272
|
return rendered
|
|
@@ -596,7 +598,11 @@ export const breadcrumbMeta = {
|
|
|
596
598
|
|
|
597
599
|
},
|
|
598
600
|
sizes: {
|
|
599
|
-
|
|
601
|
+
// 值源 = code SSOT:BREADCRUMB_SIZE_TEXT(sm/md=text-body, lg=text-body-lg)+ BREADCRUMB_ICON_SIZE(sm/md=16, lg=20)。
|
|
602
|
+
// Breadcrumb 是文字列無固定高度 token,故不列 height(對照 Tag meta 有自身 h-* 才列)。
|
|
603
|
+
sm: { iconSize: 16, typography: 'body' },
|
|
604
|
+
md: { iconSize: 16, typography: 'body' },
|
|
605
|
+
lg: { iconSize: 20, typography: 'body-lg' },
|
|
600
606
|
},
|
|
601
607
|
states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
|
|
602
608
|
tokens: {
|
|
@@ -54,7 +54,8 @@ export const Overview: Story = {
|
|
|
54
54
|
<section>
|
|
55
55
|
<H3>Selection state-driven 渲染(0 / N)</H3>
|
|
56
56
|
<Desc>
|
|
57
|
-
<code>selection.length === 0</code>
|
|
57
|
+
<code>selection.length === 0</code> 且無 <code>totalSelected</code> → null(不佔 layout);
|
|
58
|
+
有選取(含反向選取 <code>totalSelected {'>'} 0</code>,見 spec「Extend dataset pattern」)→ 主 bar 浮現。
|
|
58
59
|
擴 dataset 的 hint banner 由 consumer 用 <code>Alert</code> 元件包在 BulkActionBar 上方/下方,
|
|
59
60
|
不在 BulkActionBar 內部 hardcode(對齊 ref 圖)。
|
|
60
61
|
</Desc>
|
|
@@ -185,7 +186,7 @@ export const Accessibility: Story = {
|
|
|
185
186
|
<section>
|
|
186
187
|
<H3>ARIA roles</H3>
|
|
187
188
|
<ul className="list-disc list-inside text-caption text-fg-secondary space-y-1">
|
|
188
|
-
<li><code>role="
|
|
189
|
+
<li><code>role="group"</code> on root + <code>aria-label="批次操作"</code>(可 i18n override;2026-07-06 自 toolbar 降級——未實作 APG toolbar 方向鍵契約,group 語意誠實,Gmail / Linear 同款)</li>
|
|
189
190
|
<li>Clear icon:<code>aria-label="清除選取"</code></li>
|
|
190
191
|
<li>actions slot 內各 button 由 consumer 提供 aria-label / 文字</li>
|
|
191
192
|
<li>Hint banner 用 Alert(自帶 <code>role="status"</code> + <code>aria-live="polite"</code>)</li>
|
|
@@ -17,9 +17,9 @@ benchmark:
|
|
|
17
17
|
|
|
18
18
|
## 定位
|
|
19
19
|
|
|
20
|
-
選取多項 item(table row / list item)
|
|
20
|
+
選取多項 item(table row / list item)後浮現的批次操作列。**不獨立於選取狀態存在**——無選取時隱藏,有選取時浮現(完整判準含 `totalSelected` 反向選取例外,見「Extend dataset pattern」)。
|
|
21
21
|
|
|
22
|
-
**Layout Family**:非 1-4 family — composite / multi-section(自 own layout
|
|
22
|
+
**Layout Family**:非 1-4 family — composite / multi-section(自 own layout:clear 區 + count 區 + batch actions 區;**不含** page-level primary / hint banner,見下方同名段)。
|
|
23
23
|
|
|
24
24
|
**世界級對照**:Linear bulk action toolbar / Polaris IndexTable bulk actions / Material DataGrid `<GridToolbar>` selection mode / Notion database row selection bar / Gmail / GitHub Issues 多選 toolbar。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
25
25
|
|
|
@@ -66,10 +66,10 @@ benchmark:
|
|
|
66
66
|
## 結構
|
|
67
67
|
|
|
68
68
|
```
|
|
69
|
-
|
|
70
|
-
│ [✕] [{N}
|
|
71
|
-
│ ↑ clear ↑ count + filter inline
|
|
72
|
-
|
|
69
|
+
┌────────────────────────────────────────────────────────────────────┐
|
|
70
|
+
│ [✕] [已選 {N} 項 · {M} 個被 filter 隱藏] │ [Action 1] [Action 2] │
|
|
71
|
+
│ ↑ clear ↑ count + filter inline ↑ batch actions(consumer)│
|
|
72
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
- 全 md Buttons(`same-row consistency`,close X 同 size;rationale 見下方「Size canonical」)
|
|
@@ -82,7 +82,7 @@ benchmark:
|
|
|
82
82
|
### Slot
|
|
83
83
|
|
|
84
84
|
- **`actions`**:consumer 提供 **md** Buttons(size rationale 見「Size canonical」);`variant=tertiary`(主)/ `tertiary danger`(destructive)— **不用 primary**(留 dialog 確認最終 action)
|
|
85
|
-
- **count
|
|
85
|
+
- **count 區**:`已選 {N} 項`(內建)+ inline filter hidden status `· {M} 個被 filter 隱藏`(`hiddenByFilter` prop 傳入時)
|
|
86
86
|
- **clear**:`<Button iconOnly size=md variant=text dismiss />`(內建,觸發 `onClear`)
|
|
87
87
|
|
|
88
88
|
#### Size canonical(2026-05-04 升 SSOT)
|
|
@@ -120,7 +120,7 @@ BulkActionBar 是 plain block(無 positioning 邏輯),consumer 用 flex column
|
|
|
120
120
|
</div>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
**為什麼撤 top-replace**:Polaris IndexTable / Material DataGrid / GitHub / Gmail 等「替代 toolbar」做法在 selection 期間**喪失 filter / sort / search 功能**,user workflow 斷裂(「我選了 50 個再 filter 出 status=error 子集 batch action」這種常見 workflow 卡關)。本 DS 採 Linear / Notion / Apple Mail / iOS Files /
|
|
123
|
+
**為什麼撤 top-replace**:Polaris IndexTable / Material DataGrid / GitHub / Gmail 等「替代 toolbar」做法在 selection 期間**喪失 filter / sort / search 功能**,user workflow 斷裂(「我選了 50 個再 filter 出 status=error 子集 batch action」這種常見 workflow 卡關)。本 DS 採 Linear / Notion / Apple Mail / iOS Files / Atlassian additive 派 — toolbar 永遠保留。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
124
124
|
|
|
125
125
|
### Layout 行為(4 use case 全 covered by inline composition)
|
|
126
126
|
|
|
@@ -138,18 +138,19 @@ case 4 比較少見,consumer 知道自己 layout 時自加 wrapper 即可,DS pri
|
|
|
138
138
|
## API
|
|
139
139
|
|
|
140
140
|
```ts
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// extends HTMLAttributes:其餘 div 屬性(id / data-* / event handlers)spread 到 root
|
|
142
|
+
interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
143
|
+
/** 已選 ID;length === 0 且無 totalSelected 時自動隱藏(回 null,完整判準見「Extend dataset pattern」) */
|
|
143
144
|
selection: readonly string[]
|
|
144
145
|
/** Clear 觸發,user 點 X icon(consumer 在 page-level 監聽 Esc 觸發) */
|
|
145
146
|
onClear?: () => void
|
|
146
147
|
/** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger,不用 primary) */
|
|
147
148
|
actions?: React.ReactNode
|
|
148
|
-
/** Filter 模式:hidden 數量,顯示在 count 區 inline
|
|
149
|
+
/** Filter 模式:hidden 數量,顯示在 count 區 inline 「已選 {N} 項 · {M} 個被 filter 隱藏」 */
|
|
149
150
|
hiddenByFilter?: number
|
|
150
151
|
/** 擴選整個 dataset 後的真總數;number 時 count 顯示此值,否則 fallback selection.length(見「Extend dataset pattern」) */
|
|
151
152
|
totalSelected?: number | null
|
|
152
|
-
/** i18n labels(Partial,merge with default;對齊 Material localeText / Polaris i18n 慣例) */
|
|
153
|
+
/** i18n labels(Partial,merge with default;對齊 Material localeText / Polaris i18n 慣例 — @benchmark-unverified) */
|
|
153
154
|
labels?: Partial<BulkActionBarLabels>
|
|
154
155
|
className?: string
|
|
155
156
|
}
|
|
@@ -168,7 +169,7 @@ interface BulkActionBarLabels {
|
|
|
168
169
|
|
|
169
170
|
### Extend dataset pattern(totalSelected)
|
|
170
171
|
|
|
171
|
-
「本頁全選 → hint 點擊 → 擴選整個 dataset」2-step 後,consumer 把 `totalSelected` 設為 dataset 真總數,count 區改顯示該值(否則 fallback `selection.length`)——避免 Alert 顯「已選 5370」但 bar 仍顯「已選 50」的不同步(2026-05-13 ship)。對齊 Gmail / Linear / Notion 全選 dataset hint pattern。
|
|
172
|
+
「本頁全選 → hint 點擊 → 擴選整個 dataset」2-step 後,consumer 把 `totalSelected` 設為 dataset 真總數,count 區改顯示該值(否則 fallback `selection.length`)——避免 Alert 顯「已選 5370」但 bar 仍顯「已選 50」的不同步(2026-05-13 ship)。對齊 Gmail / Linear / Notion 全選 dataset hint pattern。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
172
173
|
|
|
173
174
|
**可見性與反向選取(DataTable all 模式,2026-06-22)**:顯示判準 = `selection.length > 0 || (totalSelected ?? 0) > 0`。反向選取(`{ mode:'all', excluded }`)下若可見列全被 excluded、`selection`(可見代表)為空但 `totalSelected > 0`(全集仍有選取),bar **仍顯示**(否則「已選 N 個但 bar 消失」矛盾)。對應 `data-table.spec.md`「L2 選取」inverted 模型。
|
|
174
175
|
|
|
@@ -177,7 +178,7 @@ interface BulkActionBarLabels {
|
|
|
177
178
|
## a11y 預設
|
|
178
179
|
|
|
179
180
|
- BulkActionBar 整體用 `role="toolbar"` + `aria-label`(default `"批次操作"`,可 override)
|
|
180
|
-
- count 文字用 `aria-live="polite"`
|
|
181
|
+
- count 文字用 `aria-live="polite"` + `aria-atomic="true"`(selection 變更時 SR 整句重讀「已選 3 項」)
|
|
181
182
|
- Clear button:`aria-label="清除選取"`
|
|
182
183
|
- Hint banner 用 `role="status"` + `aria-live="polite"`(state 切換時通知)
|
|
183
184
|
- 鍵盤:Esc → `onClear()`(consumer 應監聽 page-level keydown 觸發);Tab 序按 DOM 順序 = clear(X)→ actions(count 是純文字 span,非互動元素 → 不參與 Tab)
|
|
@@ -21,7 +21,10 @@ export interface BulkActionBarLabels {
|
|
|
21
21
|
count: (n: number) => string
|
|
22
22
|
clear: string
|
|
23
23
|
hiddenSuffix: (hidden: number) => string
|
|
24
|
+
/** @deprecated 2026-07-06 role 已降級 "group"(user 拍板),改用 `ariaLabel`;此 key 保留 backward-compat(值仍作 fallback)。 */
|
|
24
25
|
toolbarAriaLabel: string
|
|
26
|
+
/** root `role="group"` 的 aria-label(2026-07-06 新名;未傳 fallback toolbarAriaLabel)。 */
|
|
27
|
+
ariaLabel?: string
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
// code-quality-allow: dead-export — public API per spec.md(consumer spread + override)
|
|
@@ -41,7 +44,7 @@ export interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
41
44
|
onClear?: () => void
|
|
42
45
|
/** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger;不用 primary) */
|
|
43
46
|
actions?: React.ReactNode
|
|
44
|
-
/** Filter 模式:hidden 數量,顯示在 count 區 inline
|
|
47
|
+
/** Filter 模式:hidden 數量,顯示在 count 區 inline「已選 {N} 項 · {M} 個被 filter 隱藏」 */
|
|
45
48
|
hiddenByFilter?: number
|
|
46
49
|
/**
|
|
47
50
|
* 「擴選整個 dataset」狀態(2026-05-13 ship,per user 抓 Alert「已選 5370」但 BulkActionBar
|
|
@@ -89,8 +92,11 @@ const BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(
|
|
|
89
92
|
return (
|
|
90
93
|
<div
|
|
91
94
|
ref={ref}
|
|
92
|
-
role
|
|
93
|
-
|
|
95
|
+
// 2026-07-06 user 拍板:role "toolbar" → "group" — 原宣告 toolbar 但未實作 APG toolbar
|
|
96
|
+
// 方向鍵 roving 契約(AT 告知「工具列」但方向鍵無反應 = 空承諾);group 語意誠實,
|
|
97
|
+
// Tab 序照 DOM(spec 本有 canonical),Gmail / Linear bulk bar 實務同款。
|
|
98
|
+
role="group"
|
|
99
|
+
aria-label={labels.ariaLabel ?? labels.toolbarAriaLabel}
|
|
94
100
|
className={cn(
|
|
95
101
|
'flex items-center gap-2',
|
|
96
102
|
'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',
|
|
@@ -120,7 +126,7 @@ const BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(
|
|
|
120
126
|
理由:count 是 state-bearing 主資訊(「你在 selection mode + N items」),非裝飾
|
|
121
127
|
World-class 共識:Linear/Notion/Carbon/Polaris 均用 primary foreground;muted 化弱化 state signal
|
|
122
128
|
hiddenByFilter suffix 維持 muted(這是次資訊,視覺層次正確) */}
|
|
123
|
-
{/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec
|
|
129
|
+
{/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec「Count text color canonical」表 + 上方 comment(state-bearing 主資訊) */}
|
|
124
130
|
<span className="text-body text-foreground font-medium tabular-nums" aria-live="polite" aria-atomic="true">
|
|
125
131
|
{/* 2026-05-13:totalSelected override 走 dataset 擴選後真總數,否則 fallback page-level selection.length */}
|
|
126
132
|
{labels.count(typeof totalSelected === 'number' ? totalSelected : selection.length)}
|
|
@@ -26,9 +26,9 @@ const STATES: StateKey[] = ['default', 'hover', 'active', 'disabled']
|
|
|
26
26
|
|
|
27
27
|
const TOKEN_MAP: Record<VariantKey, Record<StateKey, ColorSpec>> = {
|
|
28
28
|
primary: {
|
|
29
|
-
default: { bg: '--primary', text: '
|
|
30
|
-
hover: { bg: '--primary-hover', text: '
|
|
31
|
-
active: { bg: '--primary-active', text: '
|
|
29
|
+
default: { bg: '--primary', text: '--on-emphasis', border: 'transparent' },
|
|
30
|
+
hover: { bg: '--primary-hover', text: '--on-emphasis', border: 'transparent' },
|
|
31
|
+
active: { bg: '--primary-active', text: '--on-emphasis', border: 'transparent' },
|
|
32
32
|
disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent' },
|
|
33
33
|
},
|
|
34
34
|
secondary: {
|
|
@@ -59,9 +59,9 @@ const TOKEN_MAP: Record<VariantKey, Record<StateKey, ColorSpec>> = {
|
|
|
59
59
|
|
|
60
60
|
const DANGER_MAP: Partial<Record<VariantKey, Record<StateKey, ColorSpec>>> = {
|
|
61
61
|
primary: {
|
|
62
|
-
default: { bg: '--error', text: '
|
|
63
|
-
hover: { bg: '--error-hover', text: '
|
|
64
|
-
active: { bg: '--error-active', text: '
|
|
62
|
+
default: { bg: '--error', text: '--on-emphasis', border: 'transparent' },
|
|
63
|
+
hover: { bg: '--error-hover', text: '--on-emphasis', border: 'transparent' },
|
|
64
|
+
active: { bg: '--error-active', text: '--on-emphasis', border: 'transparent' },
|
|
65
65
|
disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent' },
|
|
66
66
|
},
|
|
67
67
|
secondary: {
|
|
@@ -168,7 +168,7 @@ Tag md 和 Button xs 同 24px,但:
|
|
|
168
168
|
|
|
169
169
|
- `startIcon`:最多一個,放在最左側
|
|
170
170
|
- `badge` 和 `endIcon` 可同時出現在右側
|
|
171
|
-
- `badge` 傳入 Badge 元件(通知計數指示器)。Badge
|
|
171
|
+
- `badge` 傳入 Badge 元件(通知計數指示器)。Badge 層級受按鈕底色的 contrast floor 約束——深色底按鈕(primary、secondary+danger)floor 為 `high`(`high` / `critical` 可直接用;`low` / `medium` 被迫 bump 是設計錯配訊號),詳見 `badge.spec.md`「各容器的 contrast floor 對照」
|
|
172
172
|
|
|
173
173
|
---
|
|
174
174
|
|
|
@@ -362,7 +362,7 @@ const ICON_ONLY_BASE = 'aspect-square p-0 min-w-0 gap-0'
|
|
|
362
362
|
- FileItem / DataTable **row delete(Trash icon)**→ 一般 Button xs 或 Inline Action(看 row size),**不套 dismiss**
|
|
363
363
|
- Tag dismiss X / Input clear X → Inline Action(host chrome padding 內,不是 Button)
|
|
364
364
|
|
|
365
|
-
完整 predicate 見 `patterns/element-anatomy/
|
|
365
|
+
完整 predicate 見 `patterns/element-anatomy/inline-action.spec.md`「Predicate:Inline Action vs Button iconOnly」+「Dismiss canonical — X close only」(2026-04-24 自 item-anatomy.spec.md 抽出)。
|
|
366
366
|
|
|
367
367
|
#### `data-unbounded` attribute(2026-04-22 v5 canonical)
|
|
368
368
|
|
|
@@ -456,6 +456,7 @@ N/A(action trigger,無資料層)。
|
|
|
456
456
|
- `header-canonical.spec.md`
|
|
457
457
|
- `item-anatomy.spec.md`
|
|
458
458
|
- `overlay-surface.spec.md`
|
|
459
|
+
- `pagination.spec.md`
|
|
459
460
|
- `segmented-control.spec.md`
|
|
460
461
|
- `switch.spec.md`
|
|
461
462
|
- `tabs.spec.md`
|