@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.81
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 -0
- 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 +7 -1
- 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 +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +3 -3
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +68 -42
- 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/Carousel/carousel.d.ts.map +1 -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 +5 -2
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +24 -3
- 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 +7 -1
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +5 -1
- 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 +4 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +2 -0
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +7 -6
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.js +2 -1
- 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 +9 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +142 -50
- 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 +1 -1
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +6 -4
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +124 -106
- package/dist/components/DatePicker/date-picker.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 +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +11 -4
- package/dist/components/DropdownMenu/dropdown-menu.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/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 +26 -14
- 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.map +1 -1
- package/dist/components/FileUpload/file-upload.js +12 -6
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +4 -3
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +1 -5
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +2 -2
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +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 +1 -1
- 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.map +1 -1
- package/dist/components/NumberInput/number-input.js +15 -7
- 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/PeoplePicker/people-picker.d.ts +2 -2
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +36 -35
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +2 -2
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +5 -2
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +27 -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 +10 -5
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +9 -3
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +45 -13
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +2 -1
- package/dist/components/SelectMenu/select-menu.d.ts +17 -4
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +39 -6
- 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 +2 -1
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +23 -8
- 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/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 +38 -8
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +5 -2
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +16 -24
- 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 +7 -3
- 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 +20 -12
- 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 +5 -7
- 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 +3 -2
- 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.map +1 -1
- package/dist/components/TreeView/tree-view.js +3 -2
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +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 +16 -16
- package/llms-full.txt +6 -1
- package/llms.txt +2 -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 +1 -1
- package/src/components/Avatar/avatar.stories.tsx +1 -1
- package/src/components/Avatar/avatar.tsx +17 -9
- package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +4 -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 +4 -2
- package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +2 -1
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
- package/src/components/BulkActionBar/bulk-action-bar.tsx +2 -2
- package/src/components/Button/button.anatomy.stories.tsx +6 -6
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.tsx +63 -26
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +2 -2
- package/src/components/Calendar/calendar.spec.md +7 -1
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.tsx +1 -0
- 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 +39 -6
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +1 -1
- package/src/components/Chip/chip.tsx +4 -4
- 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 +13 -5
- 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 +23 -16
- 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 +1 -1
- 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 +25 -25
- package/src/components/DataTable/data-table.stories.tsx +4 -3
- package/src/components/DataTable/data-table.tsx +203 -52
- package/src/components/DataTable/filter-operators.spec.md +4 -4
- package/src/components/DataTable/filter-tree.ts +32 -9
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +6 -4
- package/src/components/DateGrid/date-grid.tsx +7 -5
- 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 +33 -12
- package/src/components/DescriptionList/description-list.spec.md +1 -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 +4 -2
- package/src/components/DropdownMenu/dropdown-menu.tsx +11 -3
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -1
- package/src/components/Empty/empty.stories.tsx +4 -15
- package/src/components/Field/field-controls.spec.md +1 -0
- 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/form-validation.spec.md +5 -2
- package/src/components/Field/use-form-validation.ts +38 -14
- 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 +3 -0
- package/src/components/FileUpload/file-upload.tsx +21 -6
- 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 +4 -3
- 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 +1 -5
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
- package/src/components/LinkInput/link-input.tsx +45 -33
- package/src/components/Menu/menu-item.spec.md +2 -1
- package/src/components/Menu/menu-item.tsx +3 -1
- 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 +23 -8
- 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/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 +63 -54
- package/src/components/PeoplePicker/person-display.tsx +5 -3
- package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
- package/src/components/Popover/popover.spec.md +11 -13
- package/src/components/Popover/popover.tsx +2 -2
- 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 +5 -2
- 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 +36 -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 +2 -2
- package/src/components/SegmentedControl/segmented-control.spec.md +1 -1
- package/src/components/SegmentedControl/segmented-control.tsx +16 -6
- package/src/components/Select/select.anatomy.stories.tsx +2 -2
- package/src/components/Select/select.spec.md +2 -1
- package/src/components/Select/select.tsx +37 -16
- 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 +11 -8
- package/src/components/SelectMenu/select-menu.tsx +92 -10
- 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 +1 -1
- package/src/components/Sheet/sheet.tsx +2 -1
- 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 +36 -11
- 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.spec.md +4 -4
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +5 -4
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Switch/switch.spec.md +3 -3
- package/src/components/Switch/switch.tsx +51 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +3 -2
- package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
- package/src/components/Tabs/tabs.spec.md +2 -1
- package/src/components/Tabs/tabs.tsx +32 -25
- package/src/components/Tag/tag.principles.stories.tsx +1 -1
- package/src/components/Tag/tag.tsx +7 -3
- package/src/components/Textarea/textarea.spec.md +2 -1
- package/src/components/Textarea/textarea.tsx +16 -9
- 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 +6 -8
- 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 +9 -6
- 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 +1 -1
- package/src/components/TreeView/tree-view.tsx +17 -2
- package/src/index.ts +1 -1
- package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
- package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -12
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +1 -1
- 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/overlay-surface/overlay-surface.spec.md +8 -7
- 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 +6 -6
- package/src/tokens/color/color.stories.tsx +1 -1
- package/src/tokens/color/primitives.css +7 -5
- 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 +1 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
- package/src/tokens/motion/motion.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/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
|
@@ -63,7 +63,7 @@ sm / md 跟 Tag 同階(20/24px),lg 對齊 md(尺寸需求一致,不需
|
|
|
63
63
|
|
|
64
64
|
**字級依 shape 分流**:`circle` shape 走元件內建的 `triggerText` map(sm 10px / md·lg 12px);`tag` shape 直接套 `tagVariants({ size })`(sm 12px / md·lg 14px,見 `tag.spec.md`),刻意對齊 Tag 視覺一致(見上方「trigger 不用 Tag 元件」段)。下方「sm 用 `text-[10px]`」的理由僅適用於 circle shape。
|
|
65
65
|
|
|
66
|
-
**sm 用 `text-[10px]` 的理由**:
|
|
66
|
+
**sm 用 `text-[10px]` 的理由**:micro-indicator 特殊例外(10px,與 `--font-footnote` 同值但不消費其 utility——lh 1.3 vs 需要的緊湊行高),與 Badge 共享「micro-indicator typography」tier——trigger 數字是次要輔助 indicator(非主訊息),在 20px 小容器內 caption 12px 會擠。完整理由與 pattern-family 說明詳見 `badge.spec.md`「字體例外:`text-[10px]`」。若未來 OverflowIndicator / Badge 以外的元件也需要這個 10px micro-indicator 尺寸,先依 `badge.spec.md`「其他元件不得比照」歸同一 family 討論,確立第三個消費者才升 typography token,而非直接抄 arbitrary value。
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
@@ -158,7 +158,8 @@ export const overflowIndicatorMeta = {
|
|
|
158
158
|
sizes: {
|
|
159
159
|
|
|
160
160
|
},
|
|
161
|
-
|
|
161
|
+
// 2026-07-04 audit 對齊(spec:95「本身無 hover / active / disabled / selected 變化」;code 有 focus-visible ring tsx:106/121)
|
|
162
|
+
states: ['default', 'focus-visible'],
|
|
162
163
|
tokens: {
|
|
163
164
|
bg: ['bg-muted'],
|
|
164
165
|
fg: ['text-foreground'],
|
|
@@ -252,7 +252,7 @@ export const ColorMatrix: Story = {
|
|
|
252
252
|
<tr>
|
|
253
253
|
<Td mono>disabled</Td>
|
|
254
254
|
<Td><TokenCell token="--bg-disabled" display="bg-disabled" /></Td>
|
|
255
|
-
<Td
|
|
255
|
+
<Td>—(border transparent)</Td>
|
|
256
256
|
<Td><TokenCell token="--fg-disabled" display="fg-disabled" /></Td>
|
|
257
257
|
</tr>
|
|
258
258
|
<tr>
|
|
@@ -88,7 +88,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
|
|
|
88
88
|
| 資料類型 | **人員**(含 Avatar) | 任意(純文字)| 任意(純文字)|
|
|
89
89
|
| 單選 / 多選 | 兩者皆支援(value 類型決定)| 單選 | 多選 |
|
|
90
90
|
| 視覺 prefix | Avatar(必備) | 可選 startIcon | 無 |
|
|
91
|
-
| 底層實作 | Popover + Command + SelectMenu | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu,`select.tsx:
|
|
91
|
+
| 底層實作 | Popover + Command + SelectMenu | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu,`select.tsx:582`)/ 觸控 native `<select>`(`select.tsx` NativeSelect) | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu + Tag overlay,`combobox.tsx:728`)/ 觸控 native `<select>`(`combobox.tsx` NativeCombobox) |
|
|
92
92
|
| 搜尋 | 永遠啟用(人名本質);single = inline-trigger(wrap `<Select searchable>`),multi 預設 panel-top(`searchIn='menu'`,`searchIn='trigger'` opt-in)— 對齊本 spec L91-92 SSOT 表 | 可選 `searchable`(單選短列表 → inline-trigger only)| 可選 `searchable` + `searchIn='menu' \| 'trigger'`(多選 → 兩種模式)|
|
|
93
93
|
|
|
94
94
|
**搜尋型態 SSOT canonical**(A1-A5 spec,2026-05-11;2026-05-12 補 PeoplePicker multi `searchIn` opt-in):
|
|
@@ -147,7 +147,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
|
|
|
147
147
|
|
|
148
148
|
| 共享項 | SSOT owner |
|
|
149
149
|
|---|---|
|
|
150
|
-
| Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec
|
|
150
|
+
| Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec L175「Avatar 左 inset SSOT」段 + `people-picker.tsx:371` `!px-[var(--field-px)]` inject(form context)/ `!px-[var(--table-cell-px)]`(naked variant table cell)|
|
|
151
151
|
| Avatar+人名視覺 | `person-display.tsx:137-152` `PersonDisplay`(共享 renderer)|
|
|
152
152
|
| 人名按空間 ellipsis | `person-display.tsx:150` `truncate flex-1 min-w-0` |
|
|
153
153
|
| Placeholder ellipsis(inline-search active 時)| `field-wrapper.tsx:244` `bareInputStyles` 含 `truncate` |
|
|
@@ -237,7 +237,7 @@ PeoplePicker 是 **composite Field Control**(Field shell + Avatar + SelectMenu),
|
|
|
237
237
|
|
|
238
238
|
## shadcn passthrough 例外說明
|
|
239
239
|
|
|
240
|
-
PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox>`(multi)),其主 API 為 declarative(value / onChange / size / mode / options...)。**有套 `forwardRef` + HTML root props spread**(codex P2 fix 2026-05-07 v15.10,`people-picker.tsx:
|
|
240
|
+
PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox>`(multi)),其主 API 為 declarative(value / onChange / size / mode / options...)。**有套 `forwardRef` + HTML root props spread**(codex P2 fix 2026-05-07 v15.10,`people-picker.tsx:111` / L53 / L131 `...rest`):
|
|
241
241
|
|
|
242
242
|
- **forwardRef + 單一 root**:`PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>`,ref 與 `...rest`(`id` / `data-testid` / `onBlur` / `onFocus` / `aria-*` 等 HTML root props)forward 到內部 trigger 容器(readonly/disabled root div、或 wrapped Select / Combobox 的 root),對齊 DS 既有 Combobox / Select 慣例
|
|
243
243
|
- **`onChange` 走 Omit**:本元件 onChange 用 `(value: PersonValue[]) => void` custom signature,跟 HTMLAttributes onChange 衝突,故 Omit
|
|
@@ -251,7 +251,7 @@ PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox
|
|
|
251
251
|
|
|
252
252
|
- **Disabled**:`disabled` → `resolvedMode='disabled'`(`useResolvedFieldMode`),走獨立 static-display 分支(`people-picker.tsx`「readonly / disabled」段)——渲染靜態 `<div>` 包 `MultiPersonDisplay` / `PersonDisplay` + `ItemSuffix` ChevronDown 類型身份 indicator(2026-06-26:disabled 保留 chevron、readonly 不顯示;naked variant 依 `showDisplayEndIcon`),**不** wrap Select / Combobox、無 dismiss X、無 inline-search input。token 走 M24 state precedence(`text-fg-disabled`,含 chevron)。
|
|
253
253
|
- **Loading(async people fetch)**:目前 PeoplePicker **未暴露 `loading` prop**(`PeoplePickerProps` 無此欄位,內部也未 forward 給 wrapped Select / Combobox)。consumer 若需 loading 態,在 fetch 完成前自行控制 `people=[]`(走 emptyText 空態)。底層 Select / Combobox / SelectMenu 各有自己的 loading 機制,但尚未經 PeoplePicker API 層轉發。
|
|
254
|
-
- **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec
|
|
254
|
+
- **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec L80 已 codify);無 creatable mode(人員不可建立)。
|
|
255
255
|
- **Empty(no value selected)**:single mode → trigger 顯 placeholder「請選擇人員」;multi mode `value=[]` → trigger 同 placeholder(無 avatar stack 渲);詳「Trigger display SSOT canonical table」B-D 段。
|
|
256
256
|
- **`people` 清單變動(async fetch 後更新)**:已選 value 顯示不依賴 `people` 查找 — display 路徑直接渲染 value 自帶資料;edit 路徑以人名回查 `people`,查不到降級為純名字 string(initials fallback),不報錯(`people-picker-helpers.ts:74-75`)。選單選項即時跟隨 props 重渲。
|
|
257
257
|
- **RTL**:未實作方向鏡像(avatar stack overlap `-ml-0.5` / 12px inset 皆以 LTR physical 方向設計);RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)。
|
|
@@ -150,7 +150,7 @@ const AsyncDirectoryPicker = () => {
|
|
|
150
150
|
<div className="flex flex-col gap-2 max-w-xs">
|
|
151
151
|
<PeoplePicker value={val} people={people} onChange={setVal} aria-label="任務協作者" />
|
|
152
152
|
<p className="text-caption text-fg-secondary">
|
|
153
|
-
Jira 任務「協作者」欄位場景:已選成員隨任務資料先抵達,組織人員名錄約 1.5 秒後才從 API
|
|
153
|
+
Jira 任務「協作者」欄位場景:已選成員隨任務資料先抵達,組織人員名錄約 1.5 秒後才從 API 回來——已選成員立即顯示、不報錯;名錄未到前 avatar 先以姓名縮寫 fallback 呈現,名錄載入後自動補上頭像。
|
|
154
154
|
</p>
|
|
155
155
|
</div>
|
|
156
156
|
)
|
|
@@ -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
|
-
// @placeholder-vocabulary-allow:
|
|
2
|
+
// @placeholder-vocabulary-allow: 2026-07-04 Q4 完成 field-controls.spec.md 共享 contract b — emptyPlaceholder forward 已移除,emptyText 直達 Combobox → SelectMenu emptyText(search-empty 真住所);placeholder 為 trigger empty SSOT。
|
|
3
3
|
// @cell-metric-escape-allow: comment describes RETIRED `tagAreaPaddingLeftPx={8}` magic — current code is surface-guarded (`surface === 'form'` only injects `!px-[var(--field-px)]`; table-cell context untouched, lets naked `!px-[var(--table-cell-px)]` SSOT take over). Hook regex grep'd the comment word, not the live code path. Per (a) fix 2026-05-13 user-approved Path a.
|
|
4
4
|
import * as React from 'react'
|
|
5
5
|
import { ChevronDown } from 'lucide-react'
|
|
@@ -67,8 +67,8 @@ export interface PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElem
|
|
|
67
67
|
placeholder?: string
|
|
68
68
|
/** 搜尋框 placeholder */
|
|
69
69
|
searchPlaceholder?: string
|
|
70
|
-
/** 搜尋無結果訊息(filtered menu empty)。**僅**用於 SelectMenu
|
|
71
|
-
*
|
|
70
|
+
/** 搜尋無結果訊息(filtered menu empty)。**僅**用於 SelectMenu `emptyText`(菜單空狀態,
|
|
71
|
+
* 2026-07-04 Q4 接線完成),不轉 trigger placeholder(2026-05-12 Issue 4 semantic fix)。 */
|
|
72
72
|
emptyText?: string
|
|
73
73
|
className?: string
|
|
74
74
|
disabled?: boolean
|
|
@@ -139,6 +139,56 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
139
139
|
const isMulti = Array.isArray(value)
|
|
140
140
|
const isEmpty = !value || (isMulti && value.length === 0)
|
|
141
141
|
|
|
142
|
+
// 2026-07-05 D3 P0 修:以下派生 + 4 hooks 原宣告在 display/readonly/single/pill 四個 early return
|
|
143
|
+
// 之後 — 同一 mounted instance 的 resolvedMode 於 edit↔display/disabled 切換(<Field mode>/<Field
|
|
144
|
+
// disabled> cascade 正是 runtime 翻轉機制)時 hook 數 7→11 變動 → React #310 crash(與 Combobox
|
|
145
|
+
// beta.76 / LinkInput 2026-07-04 同家族)。全部 hoist 到任何 early return 之前(對齊 select.tsx
|
|
146
|
+
// 「所有 hooks 必在 early return 前 call」canonical);effect 加 mode/stack guard,邏輯不變。
|
|
147
|
+
const selectedNames: string[] = !value
|
|
148
|
+
? []
|
|
149
|
+
: Array.isArray(value)
|
|
150
|
+
? value.map(v => resolvePerson(v).name)
|
|
151
|
+
: [resolvePerson(value).name]
|
|
152
|
+
|
|
153
|
+
// SSOT visible count compute via formula primitive + ResizeObserver(multi stack mode 專用)
|
|
154
|
+
const stackContainerRef = React.useRef<HTMLDivElement | null>(null)
|
|
155
|
+
const [stackVisibleCount, setStackVisibleCount] = React.useState<number | undefined>(undefined)
|
|
156
|
+
React.useLayoutEffect(() => {
|
|
157
|
+
if (resolvedMode !== 'edit' || !isMulti || multiDisplay !== 'stack' || selectedNames.length <= 1) {
|
|
158
|
+
setStackVisibleCount(undefined); return
|
|
159
|
+
}
|
|
160
|
+
const root = stackContainerRef.current
|
|
161
|
+
if (!root) return
|
|
162
|
+
// 2026-05-15 ROOT CAUSE FIX(user 抓「之前說的問題都還是存在」):
|
|
163
|
+
// stackContainerRef 透過 mergedStackRef 接 Combobox forwarded ref → root **就是** [role=combobox]
|
|
164
|
+
// div 自己。原 `root.querySelector('[role=combobox]')` 不找 self 永遠 null → trigger=null →
|
|
165
|
+
// tagArea=null → available=0 → setStackVisibleCount(0) → 整 stack 全 overflow → fallback 到
|
|
166
|
+
// Combobox DOM-based useOverflowCount(非 deterministic 那個算法)。修:用 root 自己當 trigger,
|
|
167
|
+
// 從 root 內找 tagArea(flex-1 min-w-0 div)。
|
|
168
|
+
const calc = () => {
|
|
169
|
+
const trigger = root.matches('[role="combobox"]') ? root : root.querySelector<HTMLElement>('[role="combobox"]')
|
|
170
|
+
const tagArea = trigger?.querySelector<HTMLElement>('div[class*="flex-1"][class*="min-w-0"]')
|
|
171
|
+
const available = tagArea?.clientWidth ?? trigger?.clientWidth ?? 0
|
|
172
|
+
const visible = getAvatarStackVisibleCount({
|
|
173
|
+
availablePx: available,
|
|
174
|
+
total: selectedNames.length,
|
|
175
|
+
avatarPx: AVATAR_STACK_AVATAR_PX[size],
|
|
176
|
+
overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],
|
|
177
|
+
})
|
|
178
|
+
setStackVisibleCount(visible)
|
|
179
|
+
}
|
|
180
|
+
calc()
|
|
181
|
+
const ro = new ResizeObserver(calc)
|
|
182
|
+
ro.observe(root)
|
|
183
|
+
return () => ro.disconnect()
|
|
184
|
+
}, [resolvedMode, isMulti, multiDisplay, selectedNames.length, size])
|
|
185
|
+
// Merge ref:forward to parent + capture for ResizeObserver
|
|
186
|
+
const mergedStackRef = React.useCallback((el: HTMLDivElement | null) => {
|
|
187
|
+
stackContainerRef.current = el
|
|
188
|
+
if (typeof ref === 'function') ref(el)
|
|
189
|
+
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el
|
|
190
|
+
}, [ref])
|
|
191
|
+
|
|
142
192
|
// ── mode='display' ────────────────────────────────────────────────────────
|
|
143
193
|
// Default(showDisplayEndIcon=false):裸 PersonDisplay / MultiPersonDisplay — backward compat。
|
|
144
194
|
// Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,
|
|
@@ -200,11 +250,7 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
200
250
|
}
|
|
201
251
|
|
|
202
252
|
// ── edit mode ─────────────────────────────────────────────────────────────
|
|
203
|
-
|
|
204
|
-
? []
|
|
205
|
-
: Array.isArray(value)
|
|
206
|
-
? value.map(v => resolvePerson(v).name)
|
|
207
|
-
: [resolvePerson(value).name]
|
|
253
|
+
// (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)
|
|
208
254
|
|
|
209
255
|
// ── single mode → wraps Select ────────────────────────────────────────────
|
|
210
256
|
if (!isMulti) {
|
|
@@ -219,8 +265,9 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
219
265
|
onChange={handleSingleChange}
|
|
220
266
|
searchable
|
|
221
267
|
placeholder={placeholder}
|
|
222
|
-
// 2026-05-12
|
|
223
|
-
//
|
|
268
|
+
// 2026-05-12 Issue 4:placeholder = trigger empty。2026-07-04 Q4:emptyText 走 Select →
|
|
269
|
+
// SelectMenu 接線(search-empty 語意,與 trigger-empty 分離)。
|
|
270
|
+
emptyText={emptyText}
|
|
224
271
|
defaultOpen={defaultOpen}
|
|
225
272
|
onOpenChange={onOpenChange}
|
|
226
273
|
className={className}
|
|
@@ -252,11 +299,10 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
252
299
|
searchable
|
|
253
300
|
searchPlaceholder={searchPlaceholder}
|
|
254
301
|
// 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty hint('請選擇人員')
|
|
255
|
-
// — semantic clean separation
|
|
256
|
-
//
|
|
257
|
-
// 1 cycle:future 移除 emptyPlaceholder forward,emptyText 改傳 SelectMenu noResultsText。
|
|
302
|
+
// — semantic clean separation。2026-07-04 Q4 拍板完成 1-cycle 承諾:emptyPlaceholder forward
|
|
303
|
+
// 移除,emptyText 改傳 Combobox → SelectMenu emptyText(search-empty 真住所,菜單空狀態)。
|
|
258
304
|
placeholder={placeholder}
|
|
259
|
-
|
|
305
|
+
emptyText={emptyText}
|
|
260
306
|
wrap={pillWrap}
|
|
261
307
|
defaultOpen={defaultOpen}
|
|
262
308
|
onOpenChange={onOpenChange}
|
|
@@ -302,45 +348,8 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
302
348
|
const handleMultiChange = (next: string[]) => {
|
|
303
349
|
onChange?.(next.map(name => findPerson(people, name)))
|
|
304
350
|
}
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
const [stackVisibleCount, setStackVisibleCount] = React.useState<number | undefined>(undefined)
|
|
308
|
-
React.useLayoutEffect(() => {
|
|
309
|
-
// 注:此 effect 只在 multi stack mode 跑(early return if not stack);length<=1 不需 override
|
|
310
|
-
if (!isMulti || selectedNames.length <= 1) {
|
|
311
|
-
setStackVisibleCount(undefined); return
|
|
312
|
-
}
|
|
313
|
-
const root = stackContainerRef.current
|
|
314
|
-
if (!root) return
|
|
315
|
-
// 2026-05-15 ROOT CAUSE FIX(user 抓「之前說的問題都還是存在」):
|
|
316
|
-
// stackContainerRef 透過 mergedStackRef 接 Combobox forwarded ref → root **就是** [role=combobox]
|
|
317
|
-
// div 自己。原 `root.querySelector('[role=combobox]')` 不找 self 永遠 null → trigger=null →
|
|
318
|
-
// tagArea=null → available=0 → setStackVisibleCount(0) → 整 stack 全 overflow → fallback 到
|
|
319
|
-
// Combobox DOM-based useOverflowCount(非 deterministic 那個算法)。修:用 root 自己當 trigger,
|
|
320
|
-
// 從 root 內找 tagArea(flex-1 min-w-0 div)。
|
|
321
|
-
const calc = () => {
|
|
322
|
-
const trigger = root.matches('[role="combobox"]') ? root : root.querySelector<HTMLElement>('[role="combobox"]')
|
|
323
|
-
const tagArea = trigger?.querySelector<HTMLElement>('div[class*="flex-1"][class*="min-w-0"]')
|
|
324
|
-
const available = tagArea?.clientWidth ?? trigger?.clientWidth ?? 0
|
|
325
|
-
const visible = getAvatarStackVisibleCount({
|
|
326
|
-
availablePx: available,
|
|
327
|
-
total: selectedNames.length,
|
|
328
|
-
avatarPx: AVATAR_STACK_AVATAR_PX[size],
|
|
329
|
-
overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],
|
|
330
|
-
})
|
|
331
|
-
setStackVisibleCount(visible)
|
|
332
|
-
}
|
|
333
|
-
calc()
|
|
334
|
-
const ro = new ResizeObserver(calc)
|
|
335
|
-
ro.observe(root)
|
|
336
|
-
return () => ro.disconnect()
|
|
337
|
-
}, [isMulti, multiDisplay, selectedNames.length, size])
|
|
338
|
-
// Merge ref:forward to parent + capture for ResizeObserver
|
|
339
|
-
const mergedStackRef = React.useCallback((el: HTMLDivElement | null) => {
|
|
340
|
-
stackContainerRef.current = el
|
|
341
|
-
if (typeof ref === 'function') ref(el)
|
|
342
|
-
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el
|
|
343
|
-
}, [ref])
|
|
351
|
+
// ── edit mode ─────────────────────────────────────────────────────────────
|
|
352
|
+
// (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)
|
|
344
353
|
|
|
345
354
|
return (
|
|
346
355
|
<Combobox
|
|
@@ -355,7 +364,7 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
355
364
|
searchPlaceholder={searchPlaceholder}
|
|
356
365
|
// 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty('請選擇人員');emptyText = search-empty(僅 backward-compat forward 1 cycle)
|
|
357
366
|
placeholder={placeholder}
|
|
358
|
-
|
|
367
|
+
emptyText={emptyText}
|
|
359
368
|
wrap={false}
|
|
360
369
|
defaultOpen={defaultOpen}
|
|
361
370
|
onOpenChange={onOpenChange}
|
|
@@ -179,8 +179,8 @@ function MultiPersonDisplay({
|
|
|
179
179
|
/** 傳入時啟用 dismiss(edit mode),callback 接收被移除的 person */
|
|
180
180
|
onRemove?: (person: PersonValue) => void
|
|
181
181
|
}) {
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
// 2026-07-05 D3 P1 修(React #310 家族):3 hooks 原在 empty early return 之後 — value 非空↔空
|
|
183
|
+
// 切換時 hook 數 0↔3 變動必 crash(現靠 consumer 三元切換 element type 偶然避開)。hoist 到 return 前。
|
|
184
184
|
// 2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus):消費 shared `avatar-stack-overflow`
|
|
185
185
|
// primitive。原 inline canvas-based formula 是 dual-implementation 違反 user SSOT「同 cell width 同
|
|
186
186
|
// overflow 判斷」(edit path 用 Combobox useOverflowCount DOM offsetWidth / display path 用 inline
|
|
@@ -189,7 +189,7 @@ function MultiPersonDisplay({
|
|
|
189
189
|
const containerRef = React.useRef<HTMLSpanElement>(null)
|
|
190
190
|
const [measuredCount, setMeasuredCount] = React.useState<number | null>(null)
|
|
191
191
|
React.useLayoutEffect(() => {
|
|
192
|
-
if (!measured) return
|
|
192
|
+
if (!measured || !value || value.length === 0) return
|
|
193
193
|
const el = containerRef.current
|
|
194
194
|
if (!el) return
|
|
195
195
|
const calc = () => {
|
|
@@ -207,6 +207,8 @@ function MultiPersonDisplay({
|
|
|
207
207
|
return () => ro.disconnect()
|
|
208
208
|
}, [measured, size, value])
|
|
209
209
|
|
|
210
|
+
if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
|
|
211
|
+
|
|
210
212
|
const resolvedMax = measured && measuredCount !== null ? measuredCount : (max ?? 3)
|
|
211
213
|
const people = value.map(resolvePerson)
|
|
212
214
|
const visible = people.slice(0, resolvedMax)
|
|
@@ -244,7 +244,7 @@ export const StateBehavior: Story = {
|
|
|
244
244
|
<div className="flex flex-col gap-8">
|
|
245
245
|
<div>
|
|
246
246
|
<H3>開啟 / 關閉</H3>
|
|
247
|
-
<Desc>Radix
|
|
247
|
+
<Desc>Radix 提供 data-state 切換 + DS Tailwind animate 驅動(popover.tsx L70-71):進場 `fade-in + zoom-in-95 + slide-from-side`,離場鏡像。所有方向都使用 side-aware slide 動畫,浮層看起來從 trigger 「長出來」。</Desc>
|
|
248
248
|
<ul className="text-caption text-fg-muted list-disc pl-5 space-y-1">
|
|
249
249
|
<li>點擊 trigger → 開啟</li>
|
|
250
250
|
<li>點 Popover 外部 → 關閉(click outside)</li>
|
|
@@ -24,14 +24,14 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
|
|
|
24
24
|
|
|
25
25
|
**Layout Family**:非上述 family — composite / multi-section(多區塊組合,自 own layout)。
|
|
26
26
|
|
|
27
|
-
**視覺對齊 Dialog**:外殼 token 與 Dialog 完全一致(`bg-surface-raised` / `border-border` / `rounded-lg` / `elevation-200`)。差別僅兩點:(1) Popover 是 non-modal 輕量浮層不阻斷背景,(2) **
|
|
27
|
+
**視覺對齊 Dialog**:外殼 token 與 Dialog 完全一致(`bg-surface-raised` / `border-border` / `rounded-lg` / `elevation-200`)。差別僅兩點:(1) Popover 是 non-modal 輕量浮層不阻斷背景,(2) **layout-space 鎖 `md`** — header / footer padding 不隨頁面放大(輕量 chrome 保持精簡);**ui-size 不鎖、繼承 page** — 內部控件隨 page 放大、對齊觸發點(2026-06-15 改,原 `data-density` master switch,見 `popover.tsx` docblock)。
|
|
28
28
|
|
|
29
|
-
**Content size canonical(2026-04-29
|
|
29
|
+
**Content size canonical(2026-04-29,2026-06-15 更新)**:ui-size 繼承 page → **fields(Input / Select / Checkbox)預設隨 page ui-size**(md page = `size="md"` field-height-md 32px;lg page 放大,與觸發點一致)。其餘細節走 SSOT pointer:
|
|
30
30
|
- Chrome corner buttons(close X / refresh / dismiss)→ `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(sm native + v5 trick)
|
|
31
31
|
- Row inline action(panel list row 內 Eye / drag / suffix toggle)→ `patterns/element-anatomy/inline-action.spec.md`「尺寸對照」table(md = 16+18 hover bg)
|
|
32
32
|
- Footer action button → `action-bar.spec.md`(輕量 chrome,sm canonical)
|
|
33
33
|
|
|
34
|
-
**觸發距離 canonical**:`sideOffset = 8`(px)—— trigger 到 Popover 邊緣的垂直/水平間距。8px 是世界級浮層 idiom(Notion / Linear / Figma / Stripe 皆約 6-8px);< 4px 會讓浮層貼死 trigger 失去「另一層」感,> 12px 會拉斷 trigger ↔ content 的視覺關聯。
|
|
34
|
+
**觸發距離 canonical**:`sideOffset = 8`(px)—— trigger 到 Popover 邊緣的垂直/水平間距。8px 是世界級浮層 idiom(Notion / Linear / Figma / Stripe 皆約 6-8px);< 4px 會讓浮層貼死 trigger 失去「另一層」感,> 12px 會拉斷 trigger ↔ content 的視覺關聯。shadcn 預設 4px 太緊(Radix 原生預設 0 更貼),本 DS 改為 8 對齊 overlay primitive 的呼吸感。Dialog 不適用(居中或 full-screen)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
35
35
|
|
|
36
36
|
**Viewport 邊距 canonical**:`collisionPadding = 8`(px)——浮層與 viewport 邊緣的最小間距。當 popover 原本要貼近 viewport 邊(trigger 在頁面最右側、或視窗很窄),Radix 會自動推開 8px 讓浮層不貼死邊界。世界級對照:macOS / iOS / Notion / Figma 的 overflow 選單永遠留視覺呼吸距。`sideOffset` 管 trigger↔popover,`collisionPadding` 管 popover↔viewport,兩者互補。Consumer 不需傳 — 預設值是 canonical。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
37
37
|
|
|
@@ -54,7 +54,7 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
|
|
|
54
54
|
其他結構化浮層 spec 若討論 align 應 pointer 指回本節。
|
|
55
55
|
|
|
56
56
|
**結構化組合(可選)**:PopoverContent 外殼無內距,consumer 可選擇:
|
|
57
|
-
- **單一 body**:包 `<PopoverBody>` 取得
|
|
57
|
+
- **單一 body**:包 `<PopoverBody>` 取得 SurfaceBody canonical padding (`px-loose py-tight`,`overlay-surface.spec.md` SSOT;Dialog body 另有 `pb-[--layout-space-bottom]` 特化)
|
|
58
58
|
- **含標題**:組合 `<PopoverHeader>` + `<PopoverBody>`(下分隔線由 Header 提供)
|
|
59
59
|
- **含操作按鈕列**:再加 `<PopoverFooter>`(上分隔線由 Footer 提供)
|
|
60
60
|
|
|
@@ -64,13 +64,13 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
|
|
|
64
64
|
|
|
65
65
|
**Close X 按鈕(2026-04-22 v5 unbounded canonical)**:所有 `PopoverHeader` 內建右上 X 按鈕,`<Button data-dismiss iconOnly dismiss size="sm" />` native sm(28 md / 32 lg)。透過 SurfaceHeader 的 v5 CSS rule 自動套負 margin 讓 layout 佔位;PopoverHeader 走 `COMPACT_HEADER_SLOT`(= calc(--font-body-size×1.5) = 21 = title line-box,衍生)→ 佔位 21(輕量 chrome,見 `popover.tsx` docblock),非 Dialog 的 24。`hideClose` prop 可讓 composition 元件(如 Coachmark)選擇隱藏。詳 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」。
|
|
66
66
|
|
|
67
|
-
**Header / Footer 高度 canonical**:padding-based(繼承 SurfaceHeader / SurfaceFooter),高度 = max(child layout) + 2×tight。header:slot 佔位 21(`COMPACT_HEADER_SLOT` = title line-box,衍生)→ max(21 title, 21 slot) + 2×12 = **45**,自然比 Dialog / Sheet 48 輕一級(`popover.tsx` PopoverHeader docblock + overlay-surface.tsx 對照);footer
|
|
67
|
+
**Header / Footer 高度 canonical**:padding-based(繼承 SurfaceHeader / SurfaceFooter),高度 = max(child layout) + 2×tight。header:slot 佔位 21(`COMPACT_HEADER_SLOT` = title line-box,衍生)→ max(21 title, 21 slot) + 2×12 = **45**,自然比 Dialog / Sheet 48 輕一級(`popover.tsx` PopoverHeader docblock + overlay-surface.tsx 對照);footer:SurfaceFooter **不**套 unbounded slot trick(僅 padding + `border-t`,見 `overlay-surface.spec.md`「為什麼 SurfaceHeader 是 padding-based」)→ 高度 = max(child layout) + 2×tight(sm action button 28 → **52** md)。詳 `tokens/uiSize/uiSize.spec.md`「Chrome header 選型 canonical」。
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
71
|
## Title typography canonical(non-modal 特化,2026-04-22 v4)
|
|
72
72
|
|
|
73
|
-
**PopoverTitle 用 `text-body font-medium`(14px),不是 Dialog / Sheet 的 `text-body-lg`(16px)**。這是針對 **non-modal 輕量浮層** 的視覺考量,跟
|
|
73
|
+
**PopoverTitle 用 `text-body font-medium`(14px),不是 Dialog / Sheet 的 `text-body-lg`(16px)**。這是針對 **non-modal 輕量浮層** 的視覺考量,跟 layout-space 鎖 md 同源的輕量 rationale。
|
|
74
74
|
|
|
75
75
|
**重量分級(world-class 共通 pattern)**:
|
|
76
76
|
|
|
@@ -152,11 +152,11 @@ canonical 判斷:「使用者 click 單項是否立即改變系統狀態?」是
|
|
|
152
152
|
|
|
153
153
|
## 邊界案例
|
|
154
154
|
|
|
155
|
-
- **極長內容**:PopoverContent 設 `max-h-[var(--radix-popover-content-available-height)]`(popover.tsx:
|
|
155
|
+
- **極長內容**:PopoverContent 設 `max-h-[var(--radix-popover-content-available-height)]`(popover.tsx:69),body 包 `<PopoverBody>`(SurfaceBody `flex-1 min-h-0 overflow-y-auto`)→ viewport 太小時 header / footer 留在視窗內、body 內捲。中間 wrapper 必 forward flex chain,詳 `../../patterns/overlay-surface/overlay-surface.spec.md`「Viewport-aware scroll chain invariant」。
|
|
156
156
|
- **Viewport 邊緣**:`collisionPadding = 8` 自動推開(見「Viewport 邊距 canonical」)。
|
|
157
157
|
- **嵌套 Popover**:禁止(見「禁止事項」)。
|
|
158
158
|
- **Dark mode**:殼走 semantic token 自動 adapt(見 `color.spec.md`)。
|
|
159
|
-
- **Density
|
|
159
|
+
- **Density**:layout-space 鎖 `md`(header / footer padding 精簡);ui-size 繼承 page(見「定位」段)。
|
|
160
160
|
|
|
161
161
|
---
|
|
162
162
|
|
|
@@ -175,13 +175,11 @@ Consumer 無需額外處理 a11y,保留 Radix `data-state` 屬性即可(開啟
|
|
|
175
175
|
|
|
176
176
|
---
|
|
177
177
|
|
|
178
|
-
##
|
|
178
|
+
## Inspector
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
`Inspector` 提供互動 Controls 即時切換 PopoverContent 六個關鍵維度:`side` / `align`(12 種定位組合)、`sideOffset`(與 trigger 距離)、`width`(w-72 / w-80 / w-96)、`showHeader` / `showFooter`(chrome 結構開關)。內容區由 consumer 決定,Inspector 聚焦容器層級的可試玩 prop(對齊 `sheet.spec.md`「Inspector」canonical)。`PlacementMatrix` 另以靜態 12-cell side × align 矩陣一眼呈現所有定位組合;Header / Body / Footer padding 屬 `overlay-surface` SSOT,由 `ColorMatrix` 的 token 表 + 該 pattern spec 覆蓋。
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
對應 anatomy story:保留 `Overview` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation)。
|
|
182
|
+
對應 anatomy story:保留 `Overview` + `Inspector` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation) + `Accessibility`。
|
|
185
183
|
|
|
186
184
|
---
|
|
187
185
|
|
|
@@ -134,8 +134,8 @@ PopoverFooter.displayName = "PopoverFooter"
|
|
|
134
134
|
// PopoverTitle(2026-04-22 v4 non-modal canonical):`text-body font-medium`(14px)
|
|
135
135
|
// Rationale:Popover / Coachmark 屬 **non-modal 輕量浮層**,跟 density 鎖 md 同源的視覺語言 —
|
|
136
136
|
// chrome 全體輕量:
|
|
137
|
-
// -
|
|
138
|
-
// - dismiss X
|
|
137
|
+
// - layout-space 鎖 md(ui-size 繼承 page,2026-06-15)
|
|
138
|
+
// - dismiss X 走 COMPACT_HEADER_SLOT 佔位 21(= title text-body line-box;見 PopoverHeader docblock)
|
|
139
139
|
// - **title `text-body`(14px)跟 Dialog / Sheet modal 的 body-lg(16px)形成重量級 vs 輕量級視覺區分**
|
|
140
140
|
// 世界級對照:Figma / Notion / Linear / Material 的 popover / filter panel / inline settings
|
|
141
141
|
// 的 header title 多半比 modal dialog title 小一級(16→14 或同比),視覺宣告「此浮層可忽略」
|
|
@@ -24,7 +24,7 @@ type InspectorArgs = {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const meta: Meta = {
|
|
27
|
-
title: 'Design System/
|
|
27
|
+
title: 'Design System/Components/ProfileCard/設計規格',
|
|
28
28
|
parameters: { layout: 'padded' },
|
|
29
29
|
}
|
|
30
30
|
export default meta
|
|
@@ -97,7 +97,7 @@ export const Overview: Story = {
|
|
|
97
97
|
{[
|
|
98
98
|
['name', 'string', '必填', '姓名(profile header)'],
|
|
99
99
|
['subtitle', 'string', '—', '職稱 / 位置 / 描述'],
|
|
100
|
-
['avatar', 'AvatarData', '—', '{ src?, alt?,
|
|
100
|
+
['avatar', 'AvatarData', '—', '{ src?, alt, color?, hoverCard? }(alt 必填;未傳 avatar 時以 name 作 fallback alt);size/status 由 ProfileCard 覆寫,hoverCard 不 forward'],
|
|
101
101
|
['status', "'online' | 'away' | 'busy' | 'offline'", '—', '觸發 Status section + Avatar 狀態點'],
|
|
102
102
|
['statusMessage', 'ReactNode', '—', '狀態說明文字(完整顯示,內容多時 Body 區捲動)'],
|
|
103
103
|
['actions', 'ReactNode', '—', 'CTA buttons(通常 2 個 sm Button)'],
|
|
@@ -122,7 +122,7 @@ export const Overview: Story = {
|
|
|
122
122
|
<tr><Td>Card 寬度</Td><Td mono>w-[320px](元件級常數,不抽 token)</Td></tr>
|
|
123
123
|
<tr><Td>Profile header padding</Td><Td mono>px-4 py-3</Td></tr>
|
|
124
124
|
<tr><Td>Profile avatar / text gap</Td><Td mono>gap-3</Td></tr>
|
|
125
|
-
<tr><Td>Avatar 大小</Td><Td mono>64px(
|
|
125
|
+
<tr><Td>Avatar 大小</Td><Td mono>64px(profile-card.tsx 元件級常數 AVATAR_SIZE;非 item-anatomy AVATAR_SIZE.block tier — block 為 32/32/40)</Td></tr>
|
|
126
126
|
<tr><Td>Actions padding</Td><Td mono>px-4 pb-3 + gap-2</Td></tr>
|
|
127
127
|
<tr><Td>Status / Info padding</Td><Td mono>px-4 py-3</Td></tr>
|
|
128
128
|
<tr><Td>View more padding</Td><Td mono>px-4 py-3</Td></tr>
|
|
@@ -376,7 +376,7 @@ export const HoverCardIntegration: Story = {
|
|
|
376
376
|
<Avatar alt={p.name} size={40} status={p.status} />
|
|
377
377
|
</span>
|
|
378
378
|
</HoverCardTrigger>
|
|
379
|
-
<HoverCardContent align="start" className="p-0" sideOffset
|
|
379
|
+
<HoverCardContent align="start" className="p-0">{/* sideOffset 不 override — spec 禁止事項(default 即 OVERLAY_SIDE_OFFSET) */}
|
|
380
380
|
<ProfileCard
|
|
381
381
|
name={p.name}
|
|
382
382
|
subtitle={p.subtitle}
|
|
@@ -8,7 +8,7 @@ import { Avatar } from '@/design-system/components/Avatar/avatar'
|
|
|
8
8
|
import { Button } from '@/design-system/components/Button/button'
|
|
9
9
|
|
|
10
10
|
const meta: Meta = {
|
|
11
|
-
title: 'Design System/
|
|
11
|
+
title: 'Design System/Components/ProfileCard/設計原則',
|
|
12
12
|
parameters: { layout: 'padded' },
|
|
13
13
|
}
|
|
14
14
|
export default meta
|
|
@@ -87,7 +87,7 @@ export const UsageGuidance: Story = {
|
|
|
87
87
|
<p>適合 ProfileCard 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
88
88
|
<ul className="space-y-1">
|
|
89
89
|
<li>
|
|
90
|
-
<LinkTo kind="Design System/
|
|
90
|
+
<LinkTo kind="Design System/Components/ProfileCard/展示" name="懸停展開 ProfileCard"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">懸停留言者 / PR reviewer / 成員列表的頭像,展開人員詳情卡</span></LinkTo>
|
|
91
91
|
</li>
|
|
92
92
|
</ul>
|
|
93
93
|
<p>判斷時對照 spec.md「何時用 / 何時不用」段落。</p>
|
|
@@ -3,7 +3,7 @@ import { ProfileCard, ProfileCardDefaultActions } from './profile-card'
|
|
|
3
3
|
import { Avatar } from '@/design-system/components/Avatar/avatar'
|
|
4
4
|
|
|
5
5
|
const meta: Meta = {
|
|
6
|
-
title: 'Design System/
|
|
6
|
+
title: 'Design System/Components/ProfileCard/展示',
|
|
7
7
|
parameters: { layout: 'padded' },
|
|
8
8
|
}
|
|
9
9
|
export default meta
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Public 元件(2026-07-04 Q8 拍板升 public,原 @internal 標記移除)。
|
|
3
|
+
* 理由:avatar.spec.md「hover 必出 ProfileCard」DS-wide canonical 要求 consumer 直接
|
|
4
|
+
* import ProfileCard + ProfileCardDefaultActions 組 HoverCard — 按 ui-development.md
|
|
5
|
+
* 唯一判準(DS 想讓 consumer 直接用 = public)internal 標記是 governance bug;
|
|
6
|
+
* 世界級對照:Atlassian ProfileCard 同為 public standalone。
|
|
4
7
|
*/
|
|
5
8
|
// @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.
|
|
6
9
|
import * as React from 'react'
|
|
@@ -29,7 +29,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
|
|
|
29
29
|
|
|
30
30
|
const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
31
31
|
<section className="mb-12">
|
|
32
|
-
<h2 className="text-
|
|
32
|
+
<h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
|
|
33
33
|
{children}
|
|
34
34
|
</section>
|
|
35
35
|
)
|
|
@@ -90,7 +90,7 @@ Item-level default / hover / checked / disabled **色彩**與 Checkbox 共用同
|
|
|
90
90
|
|
|
91
91
|
繼承 Field family,詳見 `../Field/field-controls.spec.md` + `../Field/form-validation.spec.md`。
|
|
92
92
|
|
|
93
|
-
**Field 內 readonly(2026-06-12 user 拍板)**:不渲染 radio 群組,改渲染 `fieldWrapperStyles` readonly 灰框 + 選中項 label(= Select readonly 同款,同為單選資料的鎖定呈現);standalone readonly 維持原樣鎖互動(ReadonlyContext)。
|
|
93
|
+
**Field 內 readonly(2026-06-12 user 拍板)**:不渲染 radio 群組,改渲染 `fieldWrapperStyles` readonly 灰框 + 選中項 label(= Select readonly 同款,同為單選資料的鎖定呈現);standalone readonly 維持原樣鎖互動(ReadonlyContext + item onClick preventDefault guard — 2026-07-05 D4 補:`<label htmlFor>` synthetic click 非 pointer event,`pointer-events-none` 攔不到,需 guard 才真鎖)。
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
|
|
@@ -112,7 +112,7 @@ Item-level default / hover / checked / disabled **色彩**與 Checkbox 共用同
|
|
|
112
112
|
- **極長 label / description**:預設不截斷、自然換行(`labelMaxLines` / `descMaxLines` 預設 `'none'`,Clamp 政策 SSOT 見 `checkbox.spec.md`「Clamp 政策」)。
|
|
113
113
|
- **icon / avatar prefix**:`RadioGroupItem` 一級 `icon` / `avatar` props 轉發 `SelectionItem` prefix 槽(2026-06-12 M30;規則 SSOT 見 `checkbox.spec.md` 同段 + `selection-item.spec.md`)。
|
|
114
114
|
- **RTL**:未實作方向鏡像;RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)。
|
|
115
|
-
- **Dark mode / density**:走 SelectionItem semantic token 自動 adapt
|
|
115
|
+
- **Dark mode / density**:走 SelectionItem semantic token 自動 adapt;垂直行距由 SelectionItem `py = (field-height − 1lh) / 2` 公式消費 uiSize token(`--field-height-*`)生成,隨 density 自動調整(零外部 gap 機制 SSOT 見 `checkbox.spec.md`「群組模式」)。
|
|
116
116
|
|
|
117
117
|
---
|
|
118
118
|
|
|
@@ -44,7 +44,9 @@ export const Modes: Story = {
|
|
|
44
44
|
</div>
|
|
45
45
|
<div>
|
|
46
46
|
<h3 className="text-body font-bold text-foreground mb-2">disabled</h3>
|
|
47
|
-
|
|
47
|
+
{/* mode="disabled" → Radix Root disabled 原生 propagate 全 item(radio 控件真停用);
|
|
48
|
+
SelectionItem disabled 另管 label 降色 — 雙傳對齊「停用」story 寫法 */}
|
|
49
|
+
<RadioGroup mode="disabled" value="yearly" aria-label="付款方案(disabled mode demo)">
|
|
48
50
|
<SelectionItem control={<RadioGroupItem value="monthly" id="m-dis" />} label="月付方案" htmlFor="m-dis" disabled />
|
|
49
51
|
<SelectionItem control={<RadioGroupItem value="yearly" id="y-dis" />} label="年付方案" htmlFor="y-dis" disabled />
|
|
50
52
|
<SelectionItem control={<RadioGroupItem value="lifetime" id="l-dis" />} label="終身方案" htmlFor="l-dis" disabled />
|
|
@@ -80,14 +80,31 @@ const RadioGroup = React.forwardRef<
|
|
|
80
80
|
// 對齊 Carbon read-only single-select(只顯示 selected 內容)+ Airtable / Notion read-only。
|
|
81
81
|
// 實作:walk children 找 control.value === selectedValue 的 SelectionItem,render label plain text。
|
|
82
82
|
// (不用 context dispatch 給 RadioGroupItem — SelectionItem layout wrapper 仍會渲染所有 item label)
|
|
83
|
+
// 2026-07-04 修:display / readonly-in-Field 分支的 <div> 原丟棄剩餘 props(id/data-*/aria-*)
|
|
84
|
+
// 且不轉發 forwardRef ref → 自家 radio-group.stories.tsx display demo 傳的 aria-label 都靜默失效。
|
|
85
|
+
// 抽出 Radix 專屬 non-DOM props,其餘 DOM props 於兩分支 spread 轉發(與 Checkbox/Switch 同修);
|
|
86
|
+
// edit 主路徑不受影響(Root 仍 spread 完整 props)。
|
|
87
|
+
const {
|
|
88
|
+
onValueChange: _onValueChange,
|
|
89
|
+
disabled: _disabled,
|
|
90
|
+
required: _required,
|
|
91
|
+
name: _name,
|
|
92
|
+
orientation: _orientation,
|
|
93
|
+
dir: _dir,
|
|
94
|
+
loop: _loop,
|
|
95
|
+
asChild: _asChild,
|
|
96
|
+
children: _children,
|
|
97
|
+
...restDomProps
|
|
98
|
+
} = props as RadioGroupProps & { asChild?: boolean }
|
|
99
|
+
|
|
83
100
|
if (resolvedMode === 'display') {
|
|
84
101
|
const selectedValue = (value ?? defaultValue) as string | undefined
|
|
85
102
|
if (!selectedValue) {
|
|
86
|
-
return <div role="group" className={cn('grid', className)}><span className="text-fg-muted">{EMPTY_DISPLAY}</span></div>
|
|
103
|
+
return <div {...restDomProps} ref={ref as React.Ref<HTMLDivElement>} role="group" className={cn('grid', className)}><span className="text-fg-muted">{EMPTY_DISPLAY}</span></div>
|
|
87
104
|
}
|
|
88
105
|
const selectedLabel = findSelectedRadioLabel(props.children, selectedValue)
|
|
89
106
|
return (
|
|
90
|
-
<div role="group" className={cn('grid', className)}>
|
|
107
|
+
<div {...restDomProps} ref={ref as React.Ref<HTMLDivElement>} role="group" className={cn('grid', className)}>
|
|
91
108
|
<span className="text-foreground">{selectedLabel ?? selectedValue}</span>
|
|
92
109
|
</div>
|
|
93
110
|
)
|
|
@@ -103,6 +120,8 @@ const RadioGroup = React.forwardRef<
|
|
|
103
120
|
const boxSize = resolvedBoxSize
|
|
104
121
|
return (
|
|
105
122
|
<div
|
|
123
|
+
{...restDomProps}
|
|
124
|
+
ref={ref as React.Ref<HTMLDivElement>}
|
|
106
125
|
role="radiogroup"
|
|
107
126
|
aria-readonly="true"
|
|
108
127
|
aria-labelledby={fieldCtx?.labelId}
|
|
@@ -260,6 +279,18 @@ const RadioGroupItem = React.forwardRef<
|
|
|
260
279
|
tabIndex={effectiveReadonly ? -1 : undefined}
|
|
261
280
|
className={cn(radioItemVariants({ size }), className)}
|
|
262
281
|
{...props}
|
|
282
|
+
// 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click
|
|
283
|
+
// 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍選中。
|
|
284
|
+
// preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 onCheck;
|
|
285
|
+
// 必列在 {...props} 之後才不被 consumer onClick 覆蓋(與 Checkbox/Switch 同修)。
|
|
286
|
+
// 同時攔 Radix roving-focus 的 arrow-key onFocus → click() 路徑(同為 click event)。
|
|
287
|
+
onClick={(event) => {
|
|
288
|
+
if (effectiveReadonly) {
|
|
289
|
+
event.preventDefault()
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
props.onClick?.(event)
|
|
293
|
+
}}
|
|
263
294
|
>
|
|
264
295
|
<RadioGroupPrimitive.Indicator className="grid place-content-center">
|
|
265
296
|
<Circle
|
|
@@ -306,7 +337,9 @@ export const radioGroupMeta = {
|
|
|
306
337
|
md: { fieldHeight: 32, iconSize: 8, typography: 'body' },
|
|
307
338
|
lg: { fieldHeight: 36, iconSize: 10, typography: 'body-lg' },
|
|
308
339
|
},
|
|
309
|
-
|
|
340
|
+
// 2026-07-04 修 stale meta:移除 'active' — radioItemVariants cva 無 active-state 樣式,
|
|
341
|
+
// Radix radio 也無 data-state=active(對照 checkboxMeta 正確不含 active)
|
|
342
|
+
states: ['default', 'hover', 'focus-visible', 'disabled'],
|
|
310
343
|
tokens: {
|
|
311
344
|
bg: ['bg-disabled', 'bg-surface'],
|
|
312
345
|
fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'],
|