@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
|
@@ -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}
|
|
@@ -453,7 +462,8 @@ export const peoplePickerMeta = {
|
|
|
453
462
|
family: 4,
|
|
454
463
|
variants: {},
|
|
455
464
|
sizes: {},
|
|
456
|
-
|
|
465
|
+
// 'active' 移除 — trigger 走 Field chrome、menu 走 MenuItem,皆無自有按壓視覺(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。
|
|
466
|
+
states: ['default', 'hover', 'focus-visible', 'disabled'],
|
|
457
467
|
tokens: {
|
|
458
468
|
bg: [],
|
|
459
469
|
fg: ['text-fg-disabled', 'text-fg-muted'],
|
|
@@ -43,6 +43,10 @@ export interface PersonData {
|
|
|
43
43
|
fields?: { label: string; value: string }[]
|
|
44
44
|
/** 跳至完整 profile 頁的 handler(hover ProfileCard 必含,不傳時 fallback noop placeholder) */
|
|
45
45
|
onViewProfile?: () => void
|
|
46
|
+
/** ProfileCard 預設 Chat 按鈕 handler(2026-07-06 user 拍板加接線;未傳 dev-warn,見 ProfileCardDefaultActions) */
|
|
47
|
+
onChat?: () => void
|
|
48
|
+
/** ProfileCard 預設 Audio call 按鈕 handler(ChevronDown 下拉行為留 v2) */
|
|
49
|
+
onCall?: () => void
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export type PersonValue = string | PersonData
|
|
@@ -71,7 +75,7 @@ function buildPersonProfileCard(person: PersonData): React.ReactNode {
|
|
|
71
75
|
employeeNumber: person.employeeNumber,
|
|
72
76
|
}}
|
|
73
77
|
fields={person.fields}
|
|
74
|
-
actions={<ProfileCardDefaultActions />}
|
|
78
|
+
actions={<ProfileCardDefaultActions onChat={person.onChat} onCall={person.onCall} />}
|
|
75
79
|
// onViewMore hover context 必含(avatar.spec.md canonical)。consumer 傳
|
|
76
80
|
// `onViewProfile` 則用真 handler,否則 noop placeholder(UI 仍渲染 View more
|
|
77
81
|
// footer,避免 preview 變死路)。
|
|
@@ -179,8 +183,8 @@ function MultiPersonDisplay({
|
|
|
179
183
|
/** 傳入時啟用 dismiss(edit mode),callback 接收被移除的 person */
|
|
180
184
|
onRemove?: (person: PersonValue) => void
|
|
181
185
|
}) {
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
// 2026-07-05 D3 P1 修(React #310 家族):3 hooks 原在 empty early return 之後 — value 非空↔空
|
|
187
|
+
// 切換時 hook 數 0↔3 變動必 crash(現靠 consumer 三元切換 element type 偶然避開)。hoist 到 return 前。
|
|
184
188
|
// 2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus):消費 shared `avatar-stack-overflow`
|
|
185
189
|
// primitive。原 inline canvas-based formula 是 dual-implementation 違反 user SSOT「同 cell width 同
|
|
186
190
|
// overflow 判斷」(edit path 用 Combobox useOverflowCount DOM offsetWidth / display path 用 inline
|
|
@@ -189,7 +193,7 @@ function MultiPersonDisplay({
|
|
|
189
193
|
const containerRef = React.useRef<HTMLSpanElement>(null)
|
|
190
194
|
const [measuredCount, setMeasuredCount] = React.useState<number | null>(null)
|
|
191
195
|
React.useLayoutEffect(() => {
|
|
192
|
-
if (!measured) return
|
|
196
|
+
if (!measured || !value || value.length === 0) return
|
|
193
197
|
const el = containerRef.current
|
|
194
198
|
if (!el) return
|
|
195
199
|
const calc = () => {
|
|
@@ -207,6 +211,8 @@ function MultiPersonDisplay({
|
|
|
207
211
|
return () => ro.disconnect()
|
|
208
212
|
}, [measured, size, value])
|
|
209
213
|
|
|
214
|
+
if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
|
|
215
|
+
|
|
210
216
|
const resolvedMax = measured && measuredCount !== null ? measuredCount : (max ?? 3)
|
|
211
217
|
const people = value.map(resolvePerson)
|
|
212
218
|
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>
|
|
@@ -273,7 +273,7 @@ export const Accessibility = {
|
|
|
273
273
|
render: () => (
|
|
274
274
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
275
275
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
276
|
-
<p className="whitespace-pre-line">{"焦點 / 鍵盤 / ARIA 行為(部分為 DS 覆寫,部分為 Radix 內建):\n\n- 開啟焦點(DS 覆寫) :DS 以 onOpenAutoFocus 覆寫 Radix 預設,開啟時把焦點落在 body 第一個可互動元素(避免 Radix 預設先 focus 右上 close X,觸發 tooltip leak)\n- 關閉返回(Radix 內建) :關閉時 focus return to trigger\n- Esc 關閉(Radix 內建) :按 Esc 自動關閉並返回焦點\n- Focus trap(Radix 內建,僅 modal) : modal={true} 時焦點鎖在 content 內\n- 點外 / 焦點離開即關(Radix 內建,non-modal) :預設 non-modal 不鎖焦點,焦點或指標離開 content 樹即自動關閉(dismiss,非 focus trap)\n- ARIA(Radix 內建) :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" \n\
|
|
276
|
+
<p className="whitespace-pre-line">{"焦點 / 鍵盤 / ARIA 行為(部分為 DS 覆寫,部分為 Radix 內建):\n\n- 開啟焦點(DS 覆寫) :DS 以 onOpenAutoFocus 覆寫 Radix 預設,開啟時把焦點落在 body 第一個可互動元素(避免 Radix 預設先 focus 右上 close X,觸發 tooltip leak)\n- 關閉返回(Radix 內建) :關閉時 focus return to trigger\n- Esc 關閉(Radix 內建) :按 Esc 自動關閉並返回焦點\n- Focus trap(Radix 內建,僅 modal) : modal={true} 時焦點鎖在 content 內\n- 點外 / 焦點離開即關(Radix 內建,non-modal) :預設 non-modal 不鎖焦點,焦點或指標離開 content 樹即自動關閉(dismiss,非 focus trap)\n- ARIA(Radix 內建) :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" \n- Accessible name(DS 覆寫) :content role=\"dialog\" 預設無 accessible name, PopoverTitle 掛載時自動接上 content 的 aria-labelledby (consumer 自傳 aria-label / aria-labelledby 優先)\n\n保留 Radix data-state 屬性即可;無 PopoverTitle 的 naked popover 需 consumer 自傳 aria-label ,否則 role=\"dialog\" 無 accessible name。"}</p>
|
|
277
277
|
</div>
|
|
278
278
|
),
|
|
279
279
|
}
|
|
@@ -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
|
|
|
@@ -170,18 +170,17 @@ canonical 判斷:「使用者 click 單項是否立即改變系統狀態?」是
|
|
|
170
170
|
- **Focus trap(Radix 內建,僅 modal)**:`modal={true}` 時焦點鎖在 content 內
|
|
171
171
|
- **點外 / 焦點離開即關(Radix 內建,non-modal)**:預設 non-modal **不鎖焦點**(無 focus trap),焦點或指標離開 content 樹時由 DismissableLayer 觸發 dismiss 自動關閉——這是 dismiss-on-focus-out 機制,**不是** focus trap
|
|
172
172
|
- **ARIA(Radix 內建)**:trigger 自動 `aria-expanded` / `aria-controls`,content `role="dialog"`
|
|
173
|
+
- **Accessible name(DS 覆寫)**:Radix Popover 沒有 Radix Dialog Title 式的自動接線,content `role="dialog"` 預設**無 accessible name**。DS 以 context 補同機制(見 `popover.tsx` `PopoverTitleContext`):`<PopoverTitle>` 掛載時自動把 id 接上 content 的 `aria-labelledby`;consumer 自傳 `aria-label` / `aria-labelledby` 一律優先
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
焦點與 accessible name 的 DS 覆寫 default 已 wire 在 `PopoverContent`(consumer 可傳自己的 `onOpenAutoFocus` override),保留 Radix `data-state` 屬性即可。**無 `<PopoverTitle>` 的 naked popover 需 consumer 自傳 `aria-label`**,否則 `role="dialog"` 無 accessible name。
|
|
175
176
|
|
|
176
177
|
---
|
|
177
178
|
|
|
178
|
-
##
|
|
179
|
+
## Inspector
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
`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
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
對應 anatomy story:保留 `Overview` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation)。
|
|
183
|
+
對應 anatomy story:保留 `Overview` + `Inspector` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation) + `Accessibility`。
|
|
185
184
|
|
|
186
185
|
---
|
|
187
186
|
|
|
@@ -33,49 +33,84 @@ const PopoverAnchor = PopoverPrimitive.Anchor
|
|
|
33
33
|
const PopoverClose = PopoverPrimitive.Close
|
|
34
34
|
|
|
35
35
|
// AutoFocus canonical(對齊 Dialog / Sheet / Material / Polaris)—
|
|
36
|
-
// 開啟時 focus 落在 body 第一個有意義互動元素,避免 focus 到 close X 觸發 tooltip leak
|
|
36
|
+
// 開啟時 focus 落在 body 第一個有意義互動元素,避免 focus 到 close X 觸發 tooltip leak。
|
|
37
|
+
// 兩段查詢對齊 dialog.tsx handleOpenAutoFocus(2026-07-05 修):舊版單一混合清單按 DOM 序取
|
|
38
|
+
// 第一 match,header 內互動元素(非 data-dismiss)會搶走 body 首元素焦點,且 bare 段漏 select。
|
|
39
|
+
// 改為:先 body-scoped 查詢,查無才退 bare 全域清單(保留 naked popover — consumer 不用
|
|
40
|
+
// PopoverBody 自管結構 — 的 fallback 意圖),最後 footer → content 容器。
|
|
37
41
|
const handlePopoverOpenAutoFocus = (e: Event) => {
|
|
38
42
|
e.preventDefault()
|
|
39
43
|
const content = e.currentTarget as HTMLElement
|
|
40
44
|
const firstBodyTarget = content.querySelector<HTMLElement>(
|
|
41
|
-
'[data-popover-body] input:not([disabled]),[data-popover-body] textarea:not([disabled]),[data-popover-body] select:not([disabled]),[data-popover-body] button:not([disabled]):not([data-dismiss])
|
|
45
|
+
'[data-popover-body] input:not([disabled]),[data-popover-body] textarea:not([disabled]),[data-popover-body] select:not([disabled]),[data-popover-body] button:not([disabled]):not([data-dismiss])'
|
|
46
|
+
)
|
|
47
|
+
const firstBareTarget = content.querySelector<HTMLElement>(
|
|
48
|
+
'input:not([disabled]),textarea:not([disabled]),select:not([disabled]),button:not([disabled]):not([data-dismiss])'
|
|
42
49
|
)
|
|
43
50
|
const firstFooterButton = content.querySelector<HTMLElement>(
|
|
44
51
|
'[data-popover-footer] button:not([disabled]):not([data-dismiss])'
|
|
45
52
|
)
|
|
46
|
-
;(firstBodyTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })
|
|
53
|
+
;(firstBodyTarget ?? firstBareTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── PopoverTitle → aria-labelledby 自動接線(2026-07-05,照 Radix Dialog Title 機制補 DS 層)──
|
|
57
|
+
// Radix Popover 的 content 有 role="dialog",但沒有 Radix Dialog Title 式的自動 accessible name
|
|
58
|
+
// 接線 → 一般 Popover 預設無 accessible name。DS 層以 context 補同機制:PopoverContent 以 useId
|
|
59
|
+
// 建 titleId 供給 children;PopoverTitle 掛載時註冊,PopoverContent 偵測到有 title 註冊才設
|
|
60
|
+
// aria-labelledby。Consumer 自傳 aria-label / aria-labelledby 一律優先。PopoverTitle 亦被
|
|
61
|
+
// DataTable sort / filter panel 等非 PopoverContent 場景裸用(raw SurfaceHeader chrome)—
|
|
62
|
+
// context 為 null 時全部 no-op,行為與舊版一致。
|
|
63
|
+
interface PopoverTitleContextValue {
|
|
64
|
+
titleId: string
|
|
65
|
+
onTitleMount: () => void
|
|
66
|
+
onTitleUnmount: () => void
|
|
47
67
|
}
|
|
48
68
|
|
|
69
|
+
const PopoverTitleContext = React.createContext<PopoverTitleContextValue | null>(null)
|
|
70
|
+
|
|
49
71
|
const PopoverContent = React.forwardRef<
|
|
50
72
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
51
73
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
52
|
-
>(({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, onOpenAutoFocus, ...props }, ref) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
74
|
+
>(({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, onOpenAutoFocus, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }, ref) => {
|
|
75
|
+
const titleId = React.useId()
|
|
76
|
+
const [hasTitle, setHasTitle] = React.useState(false)
|
|
77
|
+
const titleContext = React.useMemo<PopoverTitleContextValue>(
|
|
78
|
+
() => ({ titleId, onTitleMount: () => setHasTitle(true), onTitleUnmount: () => setHasTitle(false) }),
|
|
79
|
+
[titleId],
|
|
80
|
+
)
|
|
81
|
+
return (
|
|
82
|
+
<PopoverPrimitive.Portal>
|
|
83
|
+
<PopoverTitleContext.Provider value={titleContext}>
|
|
84
|
+
<PopoverPrimitive.Content
|
|
85
|
+
ref={ref}
|
|
86
|
+
align={align}
|
|
87
|
+
sideOffset={sideOffset}
|
|
88
|
+
collisionPadding={collisionPadding}
|
|
89
|
+
// Layout-space lock(2026-06-15 canonical,原 data-density="md" 改為只鎖 layout-space):Popover 是
|
|
90
|
+
// 輕量浮層,header/footer 用 py-tight(layout-space)→ 鎖 layout-space=md 保持精簡 padding;但
|
|
91
|
+
// **ui-size 不鎖、繼承 page** → 內部 field 控件 / dismiss 按鈕隨 page 放大,跟觸發點一致(decouple)。
|
|
92
|
+
// [data-layout-space="md"] 有 reset selector(layoutSpace.css L31)→ lg page 上正確 reset 回 md。
|
|
93
|
+
data-layout-space="md"
|
|
94
|
+
onOpenAutoFocus={onOpenAutoFocus ?? handlePopoverOpenAutoFocus}
|
|
95
|
+
aria-label={ariaLabel}
|
|
96
|
+
// accessible name 自動接線:consumer 自傳優先;無 aria-label 且有 PopoverTitle 註冊才掛 titleId
|
|
97
|
+
aria-labelledby={ariaLabelledBy ?? (ariaLabel == null && hasTitle ? titleId : undefined)}
|
|
98
|
+
className={cn(
|
|
99
|
+
"z-50 w-72 rounded-lg border border-border bg-surface-raised text-foreground shadow-[var(--elevation-200)] outline-none",
|
|
100
|
+
// 2026-05-04 viewport-aware max-h SSOT(從 ProfileCard 升 DS-wide):header/footer 永遠 in-viewport,body 壓縮 scroll
|
|
101
|
+
// 2026-05-05 audit dim 35 補:加 `min-h-0` 完成 M25 chain invariant(flex item default min-h: auto 阻 shrink)
|
|
102
|
+
"max-h-[var(--radix-popover-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0",
|
|
103
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 motion-reduce:animate-none",
|
|
104
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
105
|
+
"origin-[var(--radix-popover-content-transform-origin)]",
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
</PopoverTitleContext.Provider>
|
|
111
|
+
</PopoverPrimitive.Portal>
|
|
112
|
+
)
|
|
113
|
+
})
|
|
79
114
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
80
115
|
|
|
81
116
|
// PopoverHeader: SurfaceHeader + Close X(對齊 Dialog 的 canonical,見 docblock)
|
|
@@ -134,8 +169,8 @@ PopoverFooter.displayName = "PopoverFooter"
|
|
|
134
169
|
// PopoverTitle(2026-04-22 v4 non-modal canonical):`text-body font-medium`(14px)
|
|
135
170
|
// Rationale:Popover / Coachmark 屬 **non-modal 輕量浮層**,跟 density 鎖 md 同源的視覺語言 —
|
|
136
171
|
// chrome 全體輕量:
|
|
137
|
-
// -
|
|
138
|
-
// - dismiss X
|
|
172
|
+
// - layout-space 鎖 md(ui-size 繼承 page,2026-06-15)
|
|
173
|
+
// - dismiss X 走 COMPACT_HEADER_SLOT 佔位 21(= title text-body line-box;見 PopoverHeader docblock)
|
|
139
174
|
// - **title `text-body`(14px)跟 Dialog / Sheet modal 的 body-lg(16px)形成重量級 vs 輕量級視覺區分**
|
|
140
175
|
// 世界級對照:Figma / Notion / Linear / Material 的 popover / filter panel / inline settings
|
|
141
176
|
// 的 header title 多半比 modal dialog title 小一級(16→14 或同比),視覺宣告「此浮層可忽略」
|
|
@@ -145,13 +180,27 @@ PopoverFooter.displayName = "PopoverFooter"
|
|
|
145
180
|
const PopoverTitle = React.forwardRef<
|
|
146
181
|
HTMLHeadingElement,
|
|
147
182
|
React.HTMLAttributes<HTMLHeadingElement>
|
|
148
|
-
>(({ className, ...props }, ref) =>
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
183
|
+
>(({ className, id, ...props }, ref) => {
|
|
184
|
+
const titleCtx = React.useContext(PopoverTitleContext)
|
|
185
|
+
// 掛載時向 PopoverContent 註冊 → content 自動設 aria-labelledby(見 PopoverTitleContext docblock)。
|
|
186
|
+
// consumer 自傳 id 時不註冊:auto 接線指向 context titleId,自訂 id 下引用會斷 → aria 由 consumer 自管。
|
|
187
|
+
const shouldRegister = id == null && titleCtx != null
|
|
188
|
+
const onTitleMount = titleCtx?.onTitleMount
|
|
189
|
+
const onTitleUnmount = titleCtx?.onTitleUnmount
|
|
190
|
+
React.useLayoutEffect(() => {
|
|
191
|
+
if (!shouldRegister) return
|
|
192
|
+
onTitleMount?.()
|
|
193
|
+
return onTitleUnmount
|
|
194
|
+
}, [shouldRegister, onTitleMount, onTitleUnmount])
|
|
195
|
+
return (
|
|
196
|
+
<h2
|
|
197
|
+
ref={ref}
|
|
198
|
+
id={id ?? titleCtx?.titleId}
|
|
199
|
+
className={cn("text-body font-medium truncate", className)}
|
|
200
|
+
{...props}
|
|
201
|
+
/>
|
|
202
|
+
)
|
|
203
|
+
})
|
|
155
204
|
PopoverTitle.displayName = "PopoverTitle"
|
|
156
205
|
|
|
157
206
|
// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
|
|
@@ -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
|