@qijenchen/design-system 0.1.0-beta.82 → 0.1.0-beta.84
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 +8 -8
- package/README.md +3 -2
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +22 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +4 -4
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/Button/button.d.ts +2 -2
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +12 -3
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +13 -5
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +7 -1
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +8 -3
- 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 -0
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +8 -4
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +1 -1
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +10 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +5 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +30 -13
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +53 -62
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +10 -4
- package/dist/components/DataTable/column-types.d.ts.map +1 -1
- package/dist/components/DataTable/column-types.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +183 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +607 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +11 -15
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +140 -0
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +5 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +36 -15
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-operators.js +140 -0
- package/dist/components/DataTable/filter-operators.js.map +1 -0
- package/dist/components/DataTable/filter-tree.js +312 -0
- package/dist/components/DataTable/filter-tree.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +3 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +12 -1
- package/dist/components/DataTable/index.js.map +1 -1
- package/dist/components/DataTable/lib/column-meta.js +25 -0
- package/dist/components/DataTable/lib/column-meta.js.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +3 -1
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +7 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +105 -74
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +20 -3
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +38 -20
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +3 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +45 -5
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +19 -0
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +21 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
- package/dist/components/Field/field-edit-keys.js +19 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +20 -1
- package/dist/components/Field/field-types.d.ts.map +1 -1
- package/dist/components/Field/field-types.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +3 -2
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +38 -39
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +2 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +1 -1
- 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 +1 -1
- 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.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +7 -2
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +22 -1
- 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 +5 -3
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.d.ts.map +1 -0
- package/dist/components/InlineEdit/index.js +5 -0
- package/dist/components/InlineEdit/index.js.map +1 -0
- package/dist/components/InlineEdit/inline-edit.d.ts +59 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
- package/dist/components/InlineEdit/inline-edit.js +164 -0
- package/dist/components/InlineEdit/inline-edit.js.map +1 -0
- package/dist/components/Input/input.d.ts +6 -11
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +43 -13
- 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 +6 -5
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts +14 -2
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +25 -3
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +3 -3
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +6 -5
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -6
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/pagination.js +1 -1
- package/dist/components/Pagination/pagination.js.map +1 -1
- package/dist/components/PeoplePicker/index.js +7 -1
- package/dist/components/PeoplePicker/index.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +9 -2
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +23 -10
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +6 -3
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +19 -14
- 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 +3 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +2 -2
- package/dist/components/ProfileCard/profile-card.js +3 -3
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +15 -9
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +11 -1
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +6 -3
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts +46 -5
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +109 -28
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +3 -1
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +8 -1
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
- package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
- package/dist/components/SelectionControl/boolean-value.js +23 -0
- package/dist/components/SelectionControl/boolean-value.js.map +1 -0
- package/dist/components/SelectionControl/index.d.ts +1 -0
- package/dist/components/SelectionControl/index.d.ts.map +1 -1
- package/dist/components/SelectionControl/index.js +2 -0
- package/dist/components/SelectionControl/index.js.map +1 -1
- package/dist/components/Sheet/index.js +2 -1
- package/dist/components/Sheet/sheet.d.ts +7 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +5 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +9 -4
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +8 -10
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +6 -6
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +3 -2
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +15 -1
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -2
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +50 -42
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
- package/dist/components/TimePicker/time-columns.js +42 -38
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +8 -6
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +7 -5
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +56 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +211 -12
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +115 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +93 -108
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +7 -6
- 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/tokens/categorical-color.js +15 -0
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/motion/motion.d.ts +7 -6
- package/dist/tokens/motion/motion.d.ts.map +1 -1
- package/dist/tokens/motion/motion.js +6 -6
- package/dist/tokens/motion/motion.js.map +1 -1
- package/dist/tokens/motion/overlay-motion.d.ts +19 -0
- package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
- package/dist/tokens/motion/overlay-motion.js +9 -0
- package/dist/tokens/motion/overlay-motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.js +5 -0
- package/dist/tokens/uiSize/icon-size.js.map +1 -0
- package/ds-canonical/fork/governance.lock +37 -19
- package/ds-canonical/fork/hooks/_log-fire.sh +46 -0
- package/ds-canonical/fork/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +17 -5
- package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/fork/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +36 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +39 -0
- package/ds-canonical/fork/hooks/check_story_invariants.sh +119 -0
- package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/fork/manifest.json +20 -0
- package/ds-canonical/fork/preamble.md +22 -18
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +9 -3
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +7 -0
- package/ds-canonical/fork/skills/performance-audit/SKILL.md +4 -2
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +10 -2
- package/ds-canonical/hooks/README.md +1 -1
- package/ds-canonical/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +65 -7
- package/ds-canonical/hooks/check_benchmark_citation.sh +5 -3
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +28 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +17 -5
- package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +1 -1
- package/ds-canonical/hooks/check_opacity_token_usage.sh +36 -0
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +3 -2
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +6 -1
- package/ds-canonical/hooks/check_propose_without_benchmark.sh +6 -2
- package/ds-canonical/hooks/check_ssot_header_declaration.sh +55 -0
- package/ds-canonical/hooks/check_story_determinism.sh +39 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +8 -7
- package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +1 -1
- package/ds-canonical/hooks/lib/_token_hygiene.sh +10 -1
- package/ds-canonical/hooks/stop_self_audit.sh +31 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +44 -0
- package/ds-canonical/hooks/tests/test_check_benchmark_citation.sh +3 -3
- package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +43 -2
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +112 -0
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +9 -2
- package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +27 -2
- package/ds-canonical/hooks/tests/test_check_fork_user_plugin_install.sh +3 -1
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +41 -2
- package/ds-canonical/hooks/tests/test_check_ssot_header_declaration.sh +72 -0
- package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
- package/ds-canonical/references/build-ui-canonicals.md +2 -1
- package/ds-canonical/references/failure-class-registry.json +145 -0
- package/ds-canonical/references/governance-audit-coverage.md +33 -0
- package/ds-canonical/references/item-anatomy-recipe.md +2 -2
- package/ds-canonical/references/scenario-definition.md +6 -9
- package/ds-canonical/references/ssot-consultation.md +2 -2
- package/ds-canonical/references/ssot-index.md +5 -4
- package/ds-canonical/references/story-baseline-registry.json +8 -2
- package/ds-canonical/references/structural-token-retention.md +6 -9
- package/ds-canonical/rules/meta-patterns.md +8 -8
- package/ds-canonical/rules/self-verify.md +4 -3
- package/ds-canonical/rules/spec-rules.md +1 -1
- package/ds-canonical/rules/story-rules.md +2 -2
- package/ds-canonical/rules/ui-development.md +4 -2
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +9 -3
- package/ds-canonical/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/skills/codex-collab/SKILL.md +9 -7
- package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
- package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
- package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +2 -2
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +57 -68
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +29 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +29 -10
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +21 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +5 -0
- package/ds-canonical/skills/design-system-audit/SKILL.md +27 -26
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +13 -1
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +1 -1
- package/ds-canonical/skills/governance-health/SKILL.md +2 -1
- package/ds-canonical/skills/knowledge-prune/SKILL.md +3 -0
- package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +1 -0
- package/ds-canonical/skills/new-component/SKILL.md +7 -5
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
- package/ds-canonical/skills/performance-audit/SKILL.md +2 -2
- package/ds-canonical/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +3 -3
- package/ds-canonical/skills/story-writing/SKILL.md +3 -3
- package/ds-canonical/skills/visual-audit/SKILL.md +10 -2
- package/ds-story-manifest.json +29 -20
- package/llms-full.txt +22 -1
- package/llms.txt +5 -4
- package/package.json +7 -3
- package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
- package/src/components/Accordion/accordion.stories.tsx +16 -1
- package/src/components/Alert/alert.principles.stories.tsx +1 -1
- package/src/components/Alert/alert.spec.md +8 -10
- package/src/components/Alert/alert.stories.tsx +28 -28
- package/src/components/Alert/alert.tsx +1 -1
- package/src/components/AppShell/_demo-helpers.tsx +6 -3
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +28 -2
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
- package/src/components/AspectRatio/aspect-ratio.stories.tsx +7 -89
- package/src/components/Avatar/avatar.anatomy.stories.tsx +7 -3
- package/src/components/Avatar/avatar.principles.stories.tsx +19 -15
- package/src/components/Avatar/avatar.spec.md +2 -2
- package/src/components/Avatar/avatar.stories.tsx +8 -6
- package/src/components/Avatar/avatar.tsx +13 -5
- package/src/components/Badge/badge.principles.stories.tsx +1 -1
- package/src/components/Badge/badge.spec.md +2 -0
- package/src/components/Badge/badge.stories.tsx +4 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +6 -4
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +6 -5
- package/src/components/Breadcrumb/breadcrumb.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/Button/button.principles.stories.tsx +1 -1
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.tsx +18 -3
- package/src/components/Calendar/calendar.anatomy.stories.tsx +12 -10
- package/src/components/Calendar/calendar.principles.stories.tsx +14 -6
- package/src/components/Calendar/calendar.spec.md +5 -4
- package/src/components/Calendar/calendar.stories.tsx +7 -4
- package/src/components/Calendar/calendar.tsx +26 -7
- package/src/components/Carousel/carousel.anatomy.stories.tsx +5 -4
- package/src/components/Carousel/carousel.spec.md +2 -2
- package/src/components/Carousel/carousel.tsx +11 -3
- package/src/components/Chart/chart.anatomy.stories.tsx +7 -4
- package/src/components/Chart/chart.principles.stories.tsx +1 -1
- package/src/components/Chart/chart.stories.tsx +2 -8
- package/src/components/Chart/chart.tsx +3 -0
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +28 -49
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +11 -26
- package/src/components/Checkbox/checkbox.stories.tsx +11 -8
- package/src/components/Checkbox/checkbox.tsx +18 -13
- package/src/components/Chip/chip.anatomy.stories.tsx +44 -24
- package/src/components/Chip/chip.principles.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +5 -4
- package/src/components/Chip/chip.tsx +5 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -4
- package/src/components/CircularProgress/circular-progress.spec.md +3 -3
- package/src/components/CircularProgress/circular-progress.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +2 -1
- package/src/components/Coachmark/coachmark.spec.md +5 -5
- package/src/components/Coachmark/coachmark.stories.tsx +3 -1
- package/src/components/Coachmark/coachmark.tsx +10 -3
- package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
- package/src/components/Combobox/combobox.tsx +53 -19
- package/src/components/Command/command.anatomy.stories.tsx +2 -2
- package/src/components/Command/command.principles.stories.tsx +1 -1
- package/src/components/Command/command.stories.tsx +42 -10
- package/src/components/DataTable/cell-registry.tsx +86 -63
- package/src/components/DataTable/column-types.ts +10 -1
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +1 -1
- package/src/components/DataTable/data-table-filter-panel.tsx +4 -1
- package/src/components/DataTable/data-table-interaction-layer.tsx +15 -19
- package/src/components/DataTable/data-table.anatomy.stories.tsx +15 -13
- package/src/components/DataTable/data-table.principles.stories.tsx +9 -9
- package/src/components/DataTable/data-table.spec.md +32 -12
- package/src/components/DataTable/data-table.stories.tsx +16 -17
- package/src/components/DataTable/data-table.tsx +103 -60
- package/src/components/DataTable/index.ts +3 -0
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.principles.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DateGrid/date-grid.tsx +3 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +8 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +3 -3
- package/src/components/DatePicker/date-picker.spec.md +3 -3
- package/src/components/DatePicker/date-picker.stories.tsx +4 -5
- package/src/components/DatePicker/date-picker.tsx +99 -45
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +12 -10
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.spec.md +3 -3
- package/src/components/DescriptionList/description-list.stories.tsx +7 -6
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -0
- package/src/components/Dialog/dialog.spec.md +9 -0
- package/src/components/Dialog/dialog.stories.tsx +66 -5
- package/src/components/Dialog/dialog.tsx +60 -25
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +8 -6
- package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +18 -9
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Field/field-context.ts +69 -5
- package/src/components/Field/field-controls.spec.md +62 -8
- package/src/components/Field/field-edit-keys.ts +66 -0
- package/src/components/Field/field-types.ts +58 -9
- package/src/components/Field/field-wrapper.tsx +50 -42
- package/src/components/Field/field.anatomy.stories.tsx +9 -6
- package/src/components/Field/field.spec.md +1 -1
- package/src/components/Field/field.stories.tsx +1 -1
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +3 -3
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +6 -6
- package/src/components/FieldControlGroup/field-control-group.spec.md +9 -4
- package/src/components/FieldControlGroup/field-control-group.tsx +1 -1
- package/src/components/FileItem/file-item.anatomy.stories.tsx +1 -1
- package/src/components/FileItem/file-item.spec.md +14 -8
- package/src/components/FileItem/file-item.stories.tsx +36 -25
- package/src/components/FileItem/file-item.tsx +8 -6
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +6 -6
- package/src/components/FileUpload/file-upload.principles.stories.tsx +5 -5
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/FileUpload/file-upload.tsx +1 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -3
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +3 -3
- package/src/components/FileViewer/file-viewer.spec.md +30 -12
- package/src/components/FileViewer/file-viewer.stories.tsx +1 -2
- package/src/components/FileViewer/file-viewer.tsx +12 -4
- package/src/components/FileViewer/image-renderer.tsx +35 -7
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -2
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -3
- package/src/components/HoverCard/hover-card.spec.md +5 -5
- package/src/components/HoverCard/hover-card.stories.tsx +16 -16
- package/src/components/HoverCard/hover-card.tsx +5 -3
- package/src/components/InlineEdit/index.ts +5 -0
- package/src/components/InlineEdit/inline-edit.spec.md +149 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +246 -0
- package/src/components/InlineEdit/inline-edit.tsx +287 -0
- package/src/components/Input/input.anatomy.stories.tsx +8 -7
- package/src/components/Input/input.principles.stories.tsx +2 -12
- package/src/components/Input/input.spec.md +13 -18
- package/src/components/Input/input.tsx +69 -22
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.tsx +11 -10
- package/src/components/Menu/menu-item.anatomy.stories.tsx +3 -3
- package/src/components/Menu/menu-item.principles.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +22 -15
- package/src/components/Menu/menu-item.stories.tsx +39 -11
- package/src/components/Menu/menu-item.tsx +38 -4
- package/src/components/Notice/notice.principles.stories.tsx +1 -1
- package/src/components/Notice/notice.spec.md +16 -3
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +3 -3
- package/src/components/NumberInput/number-input.principles.stories.tsx +1 -1
- package/src/components/NumberInput/number-input.spec.md +4 -4
- package/src/components/NumberInput/number-input.tsx +19 -12
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +10 -8
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +4 -2
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +71 -63
- package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -8
- package/src/components/Pagination/pagination.spec.md +1 -1
- package/src/components/Pagination/pagination.stories.tsx +2 -6
- package/src/components/Pagination/pagination.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +4 -4
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +2 -2
- package/src/components/PeoplePicker/people-picker.spec.md +6 -6
- package/src/components/PeoplePicker/people-picker.tsx +56 -16
- package/src/components/PeoplePicker/person-display.tsx +32 -16
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.tsx +3 -1
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +12 -23
- package/src/components/ProfileCard/profile-card.spec.md +3 -3
- package/src/components/ProfileCard/profile-card.tsx +6 -6
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +3 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.stories.tsx +50 -42
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +10 -28
- package/src/components/RadioGroup/radio-group.stories.tsx +29 -32
- package/src/components/RadioGroup/radio-group.tsx +41 -15
- package/src/components/Rating/rating.spec.md +11 -3
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +7 -3
- package/src/components/ScrollArea/scroll-area.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.tsx +17 -4
- package/src/components/Select/select.anatomy.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +27 -17
- package/src/components/Select/select.stories.tsx +33 -0
- package/src/components/Select/select.tsx +190 -36
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +4 -4
- package/src/components/SelectMenu/select-menu.spec.md +15 -7
- package/src/components/SelectMenu/select-menu.tsx +16 -3
- package/src/components/SelectionControl/boolean-value.tsx +39 -0
- package/src/components/SelectionControl/index.ts +1 -0
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +3 -3
- package/src/components/SelectionControl/selection-item.stories.tsx +3 -3
- package/src/components/Separator/separator.spec.md +2 -2
- package/src/components/Separator/separator.stories.tsx +6 -27
- package/src/components/Sheet/sheet.anatomy.stories.tsx +1 -1
- package/src/components/Sheet/sheet.principles.stories.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -2
- package/src/components/Sheet/sheet.tsx +9 -5
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +16 -12
- package/src/components/Sidebar/sidebar.principles.stories.tsx +10 -8
- package/src/components/Sidebar/sidebar.spec.md +13 -15
- package/src/components/Sidebar/sidebar.stories.tsx +8 -7
- package/src/components/Sidebar/sidebar.tsx +37 -25
- package/src/components/Skeleton/skeleton.stories.tsx +4 -11
- package/src/components/Slider/slider.anatomy.stories.tsx +3 -3
- package/src/components/Slider/slider.principles.stories.tsx +2 -2
- package/src/components/Slider/slider.spec.md +5 -5
- package/src/components/Slider/slider.tsx +3 -1
- package/src/components/Steps/steps.anatomy.stories.tsx +4 -4
- package/src/components/Steps/steps.principles.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +1 -1
- package/src/components/Switch/switch.anatomy.stories.tsx +7 -7
- package/src/components/Switch/switch.principles.stories.tsx +4 -4
- package/src/components/Switch/switch.spec.md +8 -8
- package/src/components/Switch/switch.stories.tsx +9 -24
- package/src/components/Switch/switch.tsx +17 -13
- package/src/components/Tabs/tabs.anatomy.stories.tsx +10 -7
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tabs/tabs.tsx +21 -1
- package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
- package/src/components/Textarea/textarea.principles.stories.tsx +1 -1
- package/src/components/Textarea/textarea.spec.md +4 -4
- package/src/components/Textarea/textarea.tsx +72 -46
- package/src/components/TimePicker/time-columns.tsx +6 -2
- package/src/components/TimePicker/time-picker.principles.stories.tsx +1 -1
- package/src/components/TimePicker/time-picker.spec.md +3 -3
- package/src/components/TimePicker/time-picker.tsx +16 -10
- package/src/components/Toast/toast.stories.tsx +7 -4
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +4 -4
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/Tooltip/tooltip.spec.md +1 -1
- package/src/components/Tooltip/tooltip.stories.tsx +4 -39
- package/src/components/Tooltip/tooltip.tsx +8 -6
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -3
- package/src/components/TreeView/tree-view.spec.md +42 -11
- package/src/components/TreeView/tree-view.stories.tsx +1 -0
- package/src/components/TreeView/tree-view.tsx +322 -17
- package/src/index.ts +584 -60
- package/src/patterns/action-bar/action-bar.spec.md +9 -0
- package/src/patterns/action-bar/action-bar.stories.tsx +6 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +2 -0
- package/src/patterns/element-anatomy/item-anatomy.spec.md +14 -5
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -4
- package/src/patterns/element-anatomy/item-anatomy.tsx +7 -6
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -4
- package/src/patterns/overlay-surface/overlay-surface.spec.md +22 -1
- package/src/styles/tokens.css +3 -0
- package/src/tokens/README.md +1 -1
- package/src/tokens/color/color.spec.md +3 -3
- package/src/tokens/color/color.stories.tsx +3 -3
- package/src/tokens/density/density.stories.tsx +16 -3
- package/src/tokens/layoutSpace/layoutSpace.spec.md +3 -1
- package/src/tokens/motion/motion.css +33 -21
- package/src/tokens/motion/motion.spec.md +32 -15
- package/src/tokens/motion/motion.stories.tsx +58 -5
- package/src/tokens/motion/motion.ts +7 -6
- package/src/tokens/motion/overlay-motion.ts +26 -0
- package/src/tokens/orphan-tokens.spec.md +4 -4
- package/src/tokens/token-system.spec.md +1 -0
- package/src/tokens/uiSize/uiSize.spec.md +1 -1
- package/src/tokens/uiSize/uiSize.stories.tsx +4 -2
- package/src/tokens/utility-registry.json +160 -23
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"person-display.js","sources":["../../../src/components/PeoplePicker/person-display.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport { EMPTY_DISPLAY } from '@/design-system/components/Field/field-wrapper'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'\nimport { Avatar } from '@/design-system/components/Avatar/avatar'\nimport { ProfileCard, ProfileCardDefaultActions } from '@/design-system/components/ProfileCard/profile-card'\nimport { useTableIsScrolling } from '@/design-system/components/Field/field-context'\nimport { ItemPrefix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport {\n getAvatarStackVisibleCount,\n AVATAR_STACK_AVATAR_PX,\n AVATAR_STACK_OVERFLOW_CHIP_PX,\n} from './avatar-stack-overflow'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n// PersonData 承載 ProfileCard 所需的完整資訊。DS 全域 person avatar 的 hoverCard ProfileCard 永遠\n// 顯示同一組 sections(name + subtitle + status + 4 default fields + 自訂 fields + actions + View more)\n// — 缺資料顯 placeholder,不會 collapse。對齊 avatar.spec.md「person avatar hover → ProfileCard」\n// DS-wide canonical(2026-05-06 v11 always-render schema 升級)。\nexport interface PersonData {\n name: string\n avatarUrl?: string\n /** 角色 / 部門 / ID 等 meta 單行(ProfileCard subtitle) */\n description?: string\n /** Presence 狀態(對齊 Avatar presence canonical)。**2026-05-14 v12 update**(per user 拍板):\n * production 每 user 一定有 presence state,**undefined = loading transient(資料還沒讀到)**,\n * 不是「user 沒設定」。ProfileCard 在 undefined 期間隱藏整 status block,**禁** render「Status not set」\n * placeholder 文字。 */\n status?: 'online' | 'away' | 'busy' | 'offline'\n /** Status 訊息(ProfileCard status section)。只在 status defined 時 render,缺則顯 `—` placeholder。\n * Status undefined 整 block skip(無 statusMessage 也跟著 skip)。 */\n statusMessage?: React.ReactNode\n /** **2026-05-07 v15.7 user directive**:ProfileCard default 只 render `id` + `employeeNumber`,\n * 其他 description 一律 opt-in by consumer 透過 `fields` array prop。對齊\n * `NAMECARD_DEFAULT_FIELD_KEYS = ['id', 'employeeNumber']`。 */\n id?: string\n employeeNumber?: string\n /** 自訂額外 fields(在 default fields 之後 append)。Email / Phone / Department / Location\n * / 任何其他 description 一律走這個 prop(opt-in,consumer 自選)。 */\n fields?: { label: string; value: string }[]\n /** 跳至完整 profile 頁的 handler(hover ProfileCard 必含,不傳時 fallback noop placeholder) */\n onViewProfile?: () => void\n /** ProfileCard 預設 Chat 按鈕 handler(2026-07-06 user 拍板加接線;未傳 dev-warn,見 ProfileCardDefaultActions) */\n onChat?: () => void\n /** ProfileCard 預設 Audio call 按鈕 handler(ChevronDown 下拉行為留 v2) */\n onCall?: () => void\n}\n\nexport type PersonValue = string | PersonData\n\nfunction resolvePerson(value: PersonValue): PersonData {\n return typeof value === 'string' ? { name: value } : value\n}\n\n// buildPersonProfileCard — DS 全域 person avatar hoverCard 的 canonical ProfileCard JSX 建構器。\n// SSOT for「avatar hover ProfileCard 一致視覺」— 任何 person avatar consumer 都走這個 helper,\n// 不可繞道直接 build ProfileCard。\n//\n// **2026-05-07 v15.7 user directive**:default field values 只 `id` + `employeeNumber`,\n// 對齊 NAMECARD_DEFAULT_FIELD_KEYS。其他 description(email/phone/department/location/etc)\n// consumer 想顯式透過 `person.fields` opt-in 傳入。\nfunction buildPersonProfileCard(person: PersonData): React.ReactNode {\n return (\n <ProfileCard\n name={person.name}\n subtitle={person.description}\n avatar={{ src: person.avatarUrl, alt: person.name }}\n status={person.status}\n statusMessage={person.statusMessage}\n defaultFieldValues={{\n id: person.id,\n employeeNumber: person.employeeNumber,\n }}\n fields={person.fields}\n actions={<ProfileCardDefaultActions onChat={person.onChat} onCall={person.onCall} />}\n // onViewMore hover context 必含(avatar.spec.md canonical)。consumer 傳\n // `onViewProfile` 則用真 handler,否則 noop placeholder(UI 仍渲染 View more\n // footer,避免 preview 變死路)。\n onViewMore={person.onViewProfile ?? (() => {})}\n />\n )\n}\n\n// ── Avatar Size ─────────────────────────────────────────────────────────────\n// 與 Tag 高度對齊:sm=20px, md/lg=24px(對齊 item-anatomy AVATAR_SIZE.inline)\n\nconst AVATAR_PX: Record<'sm' | 'md' | 'lg', number> = { sm: 20, md: 24, lg: 24 }\n\n// ── PersonAvatar ────────────────────────────────────────────────────────────\n// Consume DS `Avatar` primitive(2026-04-22 refactor,M1 SSOT consumption)+ 預設 ProfileCard\n// hoverCard(avatar.spec.md DS-wide「person avatar hover → ProfileCard」canonical)。\n//\n// 之前用 local `<img>` / `<User icon />` hand-craft 繞過 DS Avatar,違反 M1。本次 refactor:\n// - 所有 person avatar 經過 DS Avatar primitive(size 對應 uiSize family,fallback / icon / badge 集中管理)\n// - 人員資訊 → ProfileCard(subtitle = description,actions = ProfileCardDefaultActions)\n\n// 2026-05-13 (a) perf fix(per codex Layer C HoverCard subtree dominant):\n// useMemo `buildPersonProfileCard` per-person stable ref。原 every render call → new JSX ref →\n// Avatar.memo bails → HoverCard subtree 重建。Stable ref → memo skip → big win on scroll。\n//\n// (c) push-up scroll-defer:當 DataTable virtualizer.isScrolling=true,**完全不 build ProfileCard**\n// (Avatar 收 undefined → 跳 HoverCard wrapper)。原 (c) v1 在 Avatar 層 skip wrapper 但 ProfileCard\n// JSX subtree 仍在此處 build → 浪費 React reconciliation work。push 到此處才真省。\nfunction PersonAvatar({\n person,\n size = 'md',\n className = '',\n style,\n}: {\n person: PersonData\n size?: 'sm' | 'md' | 'lg'\n className?: string\n style?: React.CSSProperties\n}) {\n const isTableScrolling = useTableIsScrolling()\n const nameCard = React.useMemo(\n () => (isTableScrolling ? undefined : buildPersonProfileCard(person)),\n [person, isTableScrolling]\n )\n return (\n <Avatar\n src={person.avatarUrl}\n alt={person.name}\n size={AVATAR_PX[size]}\n className={className}\n style={style}\n hoverCard={nameCard}\n />\n )\n}\n\n// ── Single Person Display ───────────────────────────────────────────────────\n\n// 2026-05-14 item-anatomy SSOT fix(per codex+Layer A 共識 path (a) + user 拍板「全部做完」):\n// outer 改 items-start + Avatar 外包 ItemPrefix primitive consumption。單行視覺 = items-center 等效;\n// 多行(autoRowHeight cell)避免 avatar+name center 整 row 不對齊 first-line text top。M1 消費既有\n// 對齊 TreeView / MenuItem / SelectionItem 共用 ItemPrefix wrap chevron/icon/avatar canonical。\nfunction PersonDisplay({ value, size = 'md' }: { value?: PersonValue | null; size?: 'sm' | 'md' | 'lg' }) {\n if (!value) return <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n\n const person = resolvePerson(value)\n\n // 2026-05-14 I1 fix(per codex addendum verdict):outer `inline-flex` → `flex w-full`\n // 完成 truncate 寬度約束鏈。原 inline-flex content-width parent constrain 不到 name span\n // → cell overflow-hidden 硬裁 → ellipsis dots 不可見。改 flex(block-level full width)\n // + inner name span `flex-1 min-w-0 truncate` 真實 truncate-with-ellipsis 顯示。\n // 對齊 GitHub Primer ActionList / Slack users_select / Atlassian UserPicker truncation canonical。\n return (\n <span className=\"flex items-start gap-2 min-w-0 w-full\">\n <ItemPrefix><PersonAvatar person={person} size={size} /></ItemPrefix>\n <span className=\"truncate flex-1 min-w-0\">{person.name}</span>\n </span>\n )\n}\nPersonDisplay.displayName = 'PersonDisplay'\n\n// ── Multi Person Display ────────────────────────────────────────────────────\n// 多人堆疊:avatar 重疊(-2px),不顯示人名。\n// 第一個 avatar z-index 最高(在最上面),依此類推。\n// 溢出時顯示 +N 指示器,hover 出 tooltip 列出溢出的人(avatar + 人名)。\n\nfunction MultiPersonDisplay({\n value,\n size = 'md',\n max,\n measured = false,\n onRemove,\n}: {\n value?: PersonValue[] | null\n size?: 'sm' | 'md' | 'lg'\n /** 最多顯示幾個 avatar(不含 +N),預設 3。`measured=true` 時忽略此 prop(改 container width 算)*/\n max?: number\n /**\n * 2026-05-15 codex Round 5 C+ SSOT fix:`measured=true` 啟動 container-width 量測(取代 hardcode `max ?? 3`)\n * → display + edit stack 同 algorithm(同 cell width → 同 overflow 判斷),不再 display 用固定 3 vs edit\n * 用 useOverflowCount。對齊 field-controls.spec.md:286 「4-mode 共享 renderer」contract + user round 3\n * verbatim「同空間兩判斷點」SSOT directive。Default false 保 backward compat(non-cell context 仍 max ?? 3)。\n */\n measured?: boolean\n /** 傳入時啟用 dismiss(edit mode),callback 接收被移除的 person */\n onRemove?: (person: PersonValue) => void\n}) {\n // 2026-07-05 D3 P1 修(React #310 家族):3 hooks 原在 empty early return 之後 — value 非空↔空\n // 切換時 hook 數 0↔3 變動必 crash(現靠 consumer 三元切換 element type 偶然避開)。hoist 到 return 前。\n // 2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus):消費 shared `avatar-stack-overflow`\n // primitive。原 inline canvas-based formula 是 dual-implementation 違反 user SSOT「同 cell width 同\n // overflow 判斷」(edit path 用 Combobox useOverflowCount DOM offsetWidth / display path 用 inline\n // canvas)。**抽 primitive 統一**:display + edit 共用 `getAvatarStackVisibleCount` formula。\n // SSOT in `./avatar-stack-overflow.ts`,M14 mechanical guard 防 future drift。\n const containerRef = React.useRef<HTMLSpanElement>(null)\n const [measuredCount, setMeasuredCount] = React.useState<number | null>(null)\n React.useLayoutEffect(() => {\n if (!measured || !value || value.length === 0) return\n const el = containerRef.current\n if (!el) return\n const calc = () => {\n const visible = getAvatarStackVisibleCount({\n availablePx: el.clientWidth,\n total: value.length,\n avatarPx: AVATAR_STACK_AVATAR_PX[size],\n overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],\n })\n setMeasuredCount(visible)\n }\n calc()\n const ro = new ResizeObserver(calc)\n ro.observe(el)\n return () => ro.disconnect()\n }, [measured, size, value])\n\n if (!value || value.length === 0) return <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n\n const resolvedMax = measured && measuredCount !== null ? measuredCount : (max ?? 3)\n const people = value.map(resolvePerson)\n const visible = people.slice(0, resolvedMax)\n const hidden = people.slice(resolvedMax)\n const overflow = hidden.length\n\n // 單人回退到 PersonDisplay(顯示名字)\n if (people.length === 1) {\n return <PersonDisplay value={value[0]} size={size} />\n }\n\n // 2026-05-14 item-anatomy SSOT fix(per codex+Layer A 共識):outer items-start + avatar stack\n // 鎖 first-line baseline(整 stack 是 prefix slot,h-[1lh] 對齊 first line)。\n return (\n <span ref={containerRef} className=\"inline-flex items-start min-w-0\">\n <ItemPrefix className=\"!justify-start\"><span className=\"inline-flex items-center min-w-0\">\n {visible.map((person, i) => {\n // **2026-05-07 v15.11 Bug D 升級 SSOT**:visible avatar 也支援 inline dismiss\n // (對齊 user directive「avatar = tag」)。Dismiss overlay 走 `AvatarDismissOverlay`\n // 共用 SSOT(下方 export),Combobox tagRenderer / 此處 / 任何 future avatar consumer\n // 都用同一視覺 — 紅圈 X 對齊 avatar 右上,hover/focus-visible 才顯。\n const handleDismiss = onRemove ? () => onRemove(value![i]) : undefined\n return (\n <span key={person.name + i} className={`relative inline-flex group/avatar ${i > 0 ? '-ml-0.5' : ''}`} style={{ zIndex: visible.length - i }}>\n <PersonAvatar\n person={person}\n size={size}\n className=\"ring-2 ring-[var(--surface)]\"\n />\n {handleDismiss && <AvatarDismissOverlay onRemove={handleDismiss} label={person.name} />}\n </span>\n )\n })}\n {overflow > 0 && (\n <OverflowIndicator\n count={overflow}\n size={size}\n className=\"ring-2 ring-[var(--surface)] -ml-0.5\"\n >\n {hidden.map((person, i) => (\n <Tag\n key={person.name + i}\n color=\"neutral\"\n size=\"sm\"\n // Tag.avatar 是 ReactNode(非 AvatarData object)——傳 <Avatar> 元素。\n // Tag 內部用 `w-4 h-4 rounded-full` 容器 slot,Avatar 填滿 object-cover。\n // **hoverCard 必帶**(avatar.spec.md DS-wide canonical:所有 person avatar 必 hover → ProfileCard)。\n // 跟 PersonAvatar 共用 `buildPersonProfileCard` helper 確保顯示資訊一致。\n avatar={\n <Avatar\n src={person.avatarUrl}\n alt={person.name}\n size={16}\n hoverCard={buildPersonProfileCard(person)}\n />\n }\n onRemove={onRemove ? () => onRemove(value![resolvedMax + i]) : undefined}\n >\n {person.name}\n </Tag>\n ))}\n </OverflowIndicator>\n )}\n </span></ItemPrefix>\n </span>\n )\n}\nMultiPersonDisplay.displayName = 'MultiPersonDisplay'\n\n// ── AvatarDismissOverlay ────────────────────────────────────────────────────\n// SSOT for「person avatar overlay dismiss」(2026-05-07 v15.12,user spec confirmed)。\n//\n// **Visual canonical**(對齊 DS new token `--surface-strong`):\n// - **12×12 圓**(固定,不隨 field size 變)\n// - **bg `--surface-strong`**(neutral-6),hover → `--surface-strong-hover`\n// (light=neutral-5 / dark=neutral-7,跨 mode 對稱)\n// - **X icon size=12 strokeWidth=3**(icon 跟底色一樣大,對齊 checkbox checkmark\n// sm/md stroke 規格;2026-06-12 同步 checkbox 2026-05-18 簡化 3.5→3,SSOT →\n// .claude/references/ui-dev-rules.md「小尺寸 icon stroke 補償」)\n// - **text-on-emphasis**(白 X,確保飽和色底對比)\n// - **位置 `absolute top-0 right-0`**(button 右上角貼齊 avatar 右上角,完全在 avatar\n// 內 — user-confirmed canonical)\n//\n// **a11y**(codex P1 fix):`opacity-0` 而非 `display:none` — element 在 DOM/tab-order,\n// keyboard tab 可達,觸控 focus-within 也顯。Hover / focus-within / focus-visible\n// 三條件之一觸發 `opacity-100`。\n//\n// **Why centralize**:Combobox tagRenderer (PeoplePicker stack mode) + MultiPersonDisplay\n// dismiss 共用 SSOT,改 1 處全 sync(M17 propagation)。\nfunction AvatarDismissOverlay({ onRemove, label }: { onRemove: () => void; label: string }) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onRemove() }}\n aria-label={`移除 ${label}`}\n className={[\n // **Position(2026-05-07 v15.15 user-confirmed)**:asymmetric `-top-px -right-1`\n // (top -1px / right -4px)— field padding-y(4px sm/md)緊 → top 只 -1px 安全;\n // padding-x 12px 寬鬆 → right 凸 4px 達 badge canonical visual。對齊 ClickUp\n // 世界級 idiom(asymmetric offset by avatar/field size constraint)。\n 'absolute -top-px -right-1 z-10',\n 'inline-flex items-center justify-center',\n // **12×12 + 2px white ring**(SSOT match stacked avatar,Slack/Material/iOS\n // notification badge 2px ring canonical)。改用 `[box-shadow:...]` 而非 `ring-2`\n // 避免跟下方 `focus-visible:ring-2` 在 tailwind-merge 衝突(同 ring family\n // override 互殺)。Box-shadow inset 0 不影響 layout,也不被 focus-visible ring\n // 蓋掉(focus 那邊另一條 outline ring 不同 layer)。\n 'w-3 h-3 rounded-full [box-shadow:0_0_0_2px_var(--surface)]',\n // bg-surface-strong = neutral-6-opaque / hover = neutral-7-opaque(both modes,\n // step-7 dark 公式自動 lighter → engaged 跨 mode 對稱)\n 'bg-surface-strong text-on-emphasis hover:bg-surface-strong-hover',\n // a11y(codex P1 fix):opacity 而非 display:none — element 在 DOM/tab-order,\n // keyboard 可達。Hover / focus-within / focus-visible 三條件之一觸發。\n 'opacity-0 group-hover/avatar:opacity-100 group-focus-within/avatar:opacity-100 focus-visible:opacity-100',\n 'transition-opacity duration-150',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n ].join(' ')}\n >\n <X size={12} strokeWidth={3} aria-hidden />\n </button>\n )\n}\n\n// ── PersonAvatarTag(Combobox tagRenderer SSOT for stack mode)─────────────\n// PeoplePicker `multiDisplay='stack'` 模式 wraps Combobox,tagRenderer 不能用 Tag pill\n// (那是 pill mode),改 render 此元件 — Avatar overlap 視覺 + AvatarDismissOverlay。\n// 對齊 user directive「avatar = tag 概念,差別只在視覺,SSOT 一致」(2026-05-07 v15.13)。\n//\n// **架構**(v15.13 重構):本元件**不自包** `group/avatar` / `-ml-0.5` overlap wrapper,\n// 因 Combobox `tagRenderer` 結果會被內部 `<div shrink-0>` 包成 measurement wrapper\n// (useOverflowCount 必要)。把 overlap + group 拉到 Combobox 的 `tagWrapperClassName`\n// 上,sibling-level overlap + group selector 才能正確 chain → AvatarDismissOverlay 的\n// `group-hover/avatar:opacity-100` 才會通。\nfunction PersonAvatarTag({\n person, size = 'md', onRemove,\n}: {\n person: PersonData\n size?: 'sm' | 'md' | 'lg'\n onRemove?: () => void\n}) {\n return (\n <>\n <PersonAvatar person={person} size={size} className=\"ring-2 ring-[var(--surface)]\" />\n {onRemove && <AvatarDismissOverlay onRemove={onRemove} label={person.name} />}\n </>\n )\n}\nPersonAvatarTag.displayName = 'PersonAvatarTag'\n\nexport { PersonDisplay, MultiPersonDisplay, PersonAvatarTag, buildPersonProfileCard, resolvePerson }\n"],"names":["visible"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"person-display.js","sources":["../../../src/components/PeoplePicker/person-display.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { useFieldEmptyDisplay } from '@/design-system/components/Field/field-context'\nimport { Tag } from '@/design-system/components/Tag/tag'\nimport { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'\nimport { Avatar } from '@/design-system/components/Avatar/avatar'\nimport { ProfileCard, ProfileCardDefaultActions } from '@/design-system/components/ProfileCard/profile-card'\nimport { useTableIsScrolling } from '@/design-system/components/Field/field-context'\nimport { ItemPrefix } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport {\n getAvatarStackVisibleCount,\n AVATAR_STACK_AVATAR_PX,\n AVATAR_STACK_OVERFLOW_CHIP_PX,\n} from './avatar-stack-overflow'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n// PersonData 承載 ProfileCard 所需的完整資訊。DS 全域 person avatar 的 hoverCard ProfileCard 顯示\n// 同一組 sections(name + subtitle + status + 2 default fields(id + employeeNumber,見下方\n// NAMECARD_DEFAULT_FIELD_KEYS 對齊註解)+ 自訂 fields + actions + View more)— 缺資料顯 placeholder;\n// **例外:status undefined 時整 status block 省略(collapse,= loading transient,見 status jsDoc)**。\n// 對齊 avatar.spec.md「person avatar hover → ProfileCard」DS-wide canonical。\nexport interface PersonData {\n name: string\n avatarUrl?: string\n /** 角色 / 部門 / ID 等 meta 單行(ProfileCard subtitle) */\n description?: string\n /** Presence 狀態(對齊 Avatar presence canonical)。**2026-05-14 v12 update**(per user 拍板):\n * production 每 user 一定有 presence state,**undefined = loading transient(資料還沒讀到)**,\n * 不是「user 沒設定」。ProfileCard 在 undefined 期間隱藏整 status block,**禁** render「Status not set」\n * placeholder 文字。 */\n status?: 'online' | 'away' | 'busy' | 'offline'\n /** Status 訊息(ProfileCard status section)。只在 status defined 時 render,缺則顯 `—` placeholder。\n * Status undefined 整 block skip(無 statusMessage 也跟著 skip)。 */\n statusMessage?: React.ReactNode\n /** **2026-05-07 v15.7 user directive**:ProfileCard default 只 render `id` + `employeeNumber`,\n * 其他 description 一律 opt-in by consumer 透過 `fields` array prop。對齊\n * `NAMECARD_DEFAULT_FIELD_KEYS = ['id', 'employeeNumber']`。 */\n id?: string\n employeeNumber?: string\n /** 自訂額外 fields(在 default fields 之後 append)。Email / Phone / Department / Location\n * / 任何其他 description 一律走這個 prop(opt-in,consumer 自選)。 */\n fields?: { label: string; value: string }[]\n /** 跳至完整 profile 頁的 handler(hover ProfileCard 必含,不傳時 fallback noop placeholder) */\n onViewProfile?: () => void\n /** ProfileCard 預設 Chat 按鈕 handler(2026-07-06 user 拍板加接線;未傳 dev-warn,見 ProfileCardDefaultActions) */\n onChat?: () => void\n /** ProfileCard 預設 Audio call 按鈕 handler(ChevronDown 下拉行為留 v2) */\n onCall?: () => void\n}\n\nexport type PersonValue = string | PersonData\n\nfunction resolvePerson(value: PersonValue): PersonData {\n return typeof value === 'string' ? { name: value } : value\n}\n\n// buildPersonProfileCard — DS 全域 person avatar hoverCard 的 canonical ProfileCard JSX 建構器。\n// SSOT for「avatar hover ProfileCard 一致視覺」— 任何 person avatar consumer 都走這個 helper,\n// 不可繞道直接 build ProfileCard。\n//\n// **2026-05-07 v15.7 user directive**:default field values 只 `id` + `employeeNumber`,\n// 對齊 NAMECARD_DEFAULT_FIELD_KEYS。其他 description(email/phone/department/location/etc)\n// consumer 想顯式透過 `person.fields` opt-in 傳入。\nfunction buildPersonProfileCard(person: PersonData): React.ReactNode {\n return (\n <ProfileCard\n name={person.name}\n subtitle={person.description}\n avatar={{ src: person.avatarUrl, alt: person.name }}\n status={person.status}\n statusMessage={person.statusMessage}\n defaultFieldValues={{\n id: person.id,\n employeeNumber: person.employeeNumber,\n }}\n fields={person.fields}\n actions={<ProfileCardDefaultActions onChat={person.onChat} onCall={person.onCall} />}\n // onViewMore hover context 必含(avatar.spec.md canonical)。consumer 傳\n // `onViewProfile` 則用真 handler,否則 noop placeholder(UI 仍渲染 View more\n // footer,避免 preview 變死路)。\n onViewMore={person.onViewProfile ?? (() => {})}\n />\n )\n}\n\n// ── Avatar Size ─────────────────────────────────────────────────────────────\n// 與 Tag 高度對齊:sm=20px, md/lg=24px(對齊 item-anatomy AVATAR_SIZE.inline)\n\nconst AVATAR_PX: Record<'sm' | 'md' | 'lg', number> = { sm: 20, md: 24, lg: 24 }\n\n// ── PersonAvatar ────────────────────────────────────────────────────────────\n// Consume DS `Avatar` primitive(2026-04-22 refactor,M1 SSOT consumption)+ 預設 ProfileCard\n// hoverCard(avatar.spec.md DS-wide「person avatar hover → ProfileCard」canonical)。\n//\n// 之前用 local `<img>` / `<User icon />` hand-craft 繞過 DS Avatar,違反 M1。本次 refactor:\n// - 所有 person avatar 經過 DS Avatar primitive(size 對應 uiSize family,fallback / icon / badge 集中管理)\n// - 人員資訊 → ProfileCard(subtitle = description,actions = ProfileCardDefaultActions)\n\n// 2026-05-13 (a) perf fix(per codex Layer C HoverCard subtree dominant):\n// useMemo `buildPersonProfileCard` per-person stable ref。原 every render call → new JSX ref →\n// Avatar.memo bails → HoverCard subtree 重建。Stable ref → memo skip → big win on scroll。\n//\n// (c) push-up scroll-defer:當 DataTable virtualizer.isScrolling=true,**完全不 build ProfileCard**\n// (Avatar 收 undefined → 跳 HoverCard wrapper)。原 (c) v1 在 Avatar 層 skip wrapper 但 ProfileCard\n// JSX subtree 仍在此處 build → 浪費 React reconciliation work。push 到此處才真省。\nfunction PersonAvatar({\n person,\n size = 'md',\n className = '',\n style,\n disabled = false,\n}: {\n person: PersonData\n size?: 'sm' | 'md' | 'lg'\n className?: string\n style?: React.CSSProperties\n /** disabled field context(2026-07-14 deep-audit 修):抑制 ProfileCard hoverCard(Avatar 收\n * undefined → 無 HoverCard wrapper / 無 tabIndex,真不可互動)+ avatar 自身 dim(host-controlled\n * opacity — avatar.spec.md「Avatar 在 disabled 元件內 host-controlled opacity」canonical;\n * PeoplePicker disabled 分支無 fieldCtx,Avatar self-dim 搆不到,host 補)。\n * 對齊 people-picker.spec.md「disabled:灰化整個 field,不可互動」。 */\n disabled?: boolean\n}) {\n const isTableScrolling = useTableIsScrolling()\n const nameCard = React.useMemo(\n () => (isTableScrolling || disabled ? undefined : buildPersonProfileCard(person)),\n [person, isTableScrolling, disabled]\n )\n return (\n <Avatar\n src={person.avatarUrl}\n alt={person.name}\n size={AVATAR_PX[size]}\n className={cn(className, disabled && 'opacity-disabled')}\n style={style}\n hoverCard={nameCard}\n />\n )\n}\n\n// ── Single Person Display ───────────────────────────────────────────────────\n\n// 2026-05-14 item-anatomy SSOT fix(per codex+Layer A 共識 path (a) + user 拍板「全部做完」):\n// outer 改 items-start + Avatar 外包 ItemPrefix primitive consumption。單行視覺 = items-center 等效;\n// 多行(autoRowHeight cell)避免 avatar+name center 整 row 不對齊 first-line text top。M1 消費既有\n// 對齊 TreeView / MenuItem / SelectionItem 共用 ItemPrefix wrap chevron/icon/avatar canonical。\nfunction PersonDisplay({ value, size = 'md', disabled = false }: { value?: PersonValue | null; size?: 'sm' | 'md' | 'lg'; /** 見 PersonAvatar.disabled jsDoc(抑制 hoverCard + dim) */ disabled?: boolean }) {\nconst emptyDisplay = useFieldEmptyDisplay()\n if (!value) return <span className=\"text-foreground\">{emptyDisplay}</span>\n\n const person = resolvePerson(value)\n\n // 2026-05-14 I1 fix(per codex addendum verdict):outer `inline-flex` → `flex w-full`\n // 完成 truncate 寬度約束鏈。原 inline-flex content-width parent constrain 不到 name span\n // → cell overflow-hidden 硬裁 → ellipsis dots 不可見。改 flex(block-level full width)\n // + inner name span `flex-1 min-w-0 truncate` 真實 truncate-with-ellipsis 顯示。\n // 對齊 GitHub Primer ActionList / Slack users_select / Atlassian UserPicker truncation canonical。\n return (\n <span className=\"flex items-start gap-2 min-w-0 w-full\">\n <ItemPrefix><PersonAvatar person={person} size={size} disabled={disabled} /></ItemPrefix>\n <span className=\"truncate flex-1 min-w-0\">{person.name}</span>\n </span>\n )\n}\nPersonDisplay.displayName = 'PersonDisplay'\n\n// ── Multi Person Display ────────────────────────────────────────────────────\n// 多人堆疊:avatar 重疊(-2px),不顯示人名。\n// 第一個 avatar z-index 最高(在最上面),依此類推。\n// 溢出時顯示 +N 指示器,hover 出 tooltip 列出溢出的人(avatar + 人名)。\n\nfunction MultiPersonDisplay({\n value,\n size = 'md',\n max,\n measured = false,\n onRemove,\n disabled = false,\n}: {\n value?: PersonValue[] | null\n size?: 'sm' | 'md' | 'lg'\n /** 最多顯示幾個 avatar(不含 +N),預設 3。`measured=true` 時忽略此 prop(改 container width 算)*/\n max?: number\n /**\n * 2026-05-15 codex Round 5 C+ SSOT fix:`measured=true` 啟動 container-width 量測(取代 hardcode `max ?? 3`)\n * → display + edit stack 同 algorithm(同 cell width → 同 overflow 判斷),不再 display 用固定 3 vs edit\n * 用 useOverflowCount。對齊 field-controls.spec.md:286 「4-mode 共享 renderer」contract + user round 3\n * verbatim「同空間兩判斷點」SSOT directive。Default false 保 backward compat(non-cell context 仍 max ?? 3)。\n */\n measured?: boolean\n /** 傳入時啟用 dismiss(edit mode),callback 接收被移除的 person */\n onRemove?: (person: PersonValue) => void\n /** 見 PersonAvatar.disabled jsDoc(抑制全 stack ProfileCard hoverCard + avatar dim) */\n disabled?: boolean\n}) {\n const emptyDisplay = useFieldEmptyDisplay()\n // 2026-07-05 D3 P1 修(React #310 家族):3 hooks 原在 empty early return 之後 — value 非空↔空\n // 切換時 hook 數 0↔3 變動必 crash(現靠 consumer 三元切換 element type 偶然避開)。hoist 到 return 前。\n // 2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus):消費 shared `avatar-stack-overflow`\n // primitive。原 inline canvas-based formula 是 dual-implementation 違反 user SSOT「同 cell width 同\n // overflow 判斷」(edit path 用 Combobox useOverflowCount DOM offsetWidth / display path 用 inline\n // canvas)。**抽 primitive 統一**:display + edit 共用 `getAvatarStackVisibleCount` formula。\n // SSOT in `./avatar-stack-overflow.ts`,M14 mechanical guard 防 future drift。\n const containerRef = React.useRef<HTMLSpanElement>(null)\n const [measuredCount, setMeasuredCount] = React.useState<number | null>(null)\n React.useLayoutEffect(() => {\n if (!measured || !value || value.length === 0) return\n const el = containerRef.current\n if (!el) return\n const calc = () => {\n const visible = getAvatarStackVisibleCount({\n availablePx: el.clientWidth,\n total: value.length,\n avatarPx: AVATAR_STACK_AVATAR_PX[size],\n overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],\n })\n setMeasuredCount(visible)\n }\n calc()\n const ro = new ResizeObserver(calc)\n ro.observe(el)\n return () => ro.disconnect()\n }, [measured, size, value])\n\n if (!value || value.length === 0) return <span className=\"text-foreground\">{emptyDisplay}</span>\n\n const resolvedMax = measured && measuredCount !== null ? measuredCount : (max ?? 3)\n const people = value.map(resolvePerson)\n const visible = people.slice(0, resolvedMax)\n const hidden = people.slice(resolvedMax)\n const overflow = hidden.length\n\n // 單人回退到 PersonDisplay(顯示名字)\n if (people.length === 1) {\n return <PersonDisplay value={value[0]} size={size} disabled={disabled} />\n }\n\n // 2026-05-14 item-anatomy SSOT fix(per codex+Layer A 共識):outer items-start + avatar stack\n // 鎖 first-line baseline(整 stack 是 prefix slot,h-[1lh] 對齊 first line)。\n return (\n <span ref={containerRef} className=\"inline-flex items-start min-w-0\">\n <ItemPrefix className=\"!justify-start\"><span className=\"inline-flex items-center min-w-0\">\n {visible.map((person, i) => {\n // **2026-05-07 v15.11 Bug D 升級 SSOT**:visible avatar 也支援 inline dismiss\n // (對齊 user directive「avatar = tag」)。Dismiss overlay 走 `AvatarDismissOverlay`\n // 共用 SSOT(下方 export),Combobox tagRenderer / 此處 / 任何 future avatar consumer\n // 都用同一視覺 — 紅圈 X 對齊 avatar 右上,hover/focus-visible 才顯。\n const handleDismiss = onRemove ? () => onRemove(value![i]) : undefined\n return (\n <span key={person.name + i} className={`relative inline-flex group/avatar ${i > 0 ? '-ml-0.5' : ''}`} style={{ zIndex: visible.length - i }}>\n <PersonAvatar\n person={person}\n size={size}\n className=\"ring-2 ring-[var(--surface)]\"\n disabled={disabled}\n />\n {handleDismiss && <AvatarDismissOverlay onRemove={handleDismiss} label={person.name} />}\n </span>\n )\n })}\n {overflow > 0 && (\n <OverflowIndicator\n count={overflow}\n size={size}\n className=\"ring-2 ring-[var(--surface)] -ml-0.5\"\n >\n {hidden.map((person, i) => (\n <Tag\n key={person.name + i}\n color=\"neutral\"\n size=\"sm\"\n // Tag.avatar 是 ReactNode(非 AvatarData object)——傳 <Avatar> 元素。\n // Tag 內部用 `w-4 h-4 rounded-full` 容器 slot,Avatar 填滿 object-cover。\n // **hoverCard 必帶**(avatar.spec.md DS-wide canonical:所有 person avatar 必 hover → ProfileCard)。\n // 跟 PersonAvatar 共用 `buildPersonProfileCard` helper 確保顯示資訊一致。\n avatar={\n <Avatar\n src={person.avatarUrl}\n alt={person.name}\n size={16}\n // disabled field:同 PersonAvatar — 抑制 ProfileCard hoverCard(不可互動)\n hoverCard={disabled ? undefined : buildPersonProfileCard(person)}\n />\n }\n onRemove={onRemove ? () => onRemove(value![resolvedMax + i]) : undefined}\n >\n {person.name}\n </Tag>\n ))}\n </OverflowIndicator>\n )}\n </span></ItemPrefix>\n </span>\n )\n}\nMultiPersonDisplay.displayName = 'MultiPersonDisplay'\n\n// ── AvatarDismissOverlay ────────────────────────────────────────────────────\n// SSOT for「person avatar overlay dismiss」(2026-05-07 v15.12,user spec confirmed)。\n//\n// **Visual canonical**(對齊 DS new token `--surface-strong`):\n// - **12×12 圓**(固定,不隨 field size 變)\n// - **bg `--surface-strong`**(neutral-6),hover → `--surface-strong-hover`\n// (light=neutral-5 / dark=neutral-7,跨 mode 對稱)\n// - **X icon size=12 strokeWidth=3**(icon 跟底色一樣大,對齊 checkbox checkmark\n// sm/md stroke 規格;2026-06-12 同步 checkbox 2026-05-18 簡化 3.5→3,SSOT →\n// .claude/references/ui-dev-rules.md「小尺寸 icon stroke 補償」)\n// - **text-on-emphasis**(白 X,確保飽和色底對比)\n// - **位置 `absolute -top-px -right-1`**(top -1px / right -4px,右緣凸出 avatar 外 4px —\n// v15.15 user-confirmed asymmetric canonical,rationale 見下方 className 內註解)\n//\n// **a11y**(codex P1 fix):`opacity-0` 而非 `display:none` — element 在 DOM/tab-order,\n// keyboard tab 可達,觸控 focus-within 也顯。Hover / focus-within / focus-visible\n// 三條件之一觸發 `opacity-100`。\n//\n// **Why centralize**:Combobox tagRenderer (PeoplePicker stack mode) + MultiPersonDisplay\n// dismiss 共用 SSOT,改 1 處全 sync(M17 propagation)。\nfunction AvatarDismissOverlay({ onRemove, label }: { onRemove: () => void; label: string }) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onRemove() }}\n aria-label={`移除 ${label}`}\n className={[\n // **Position(2026-05-07 v15.15 user-confirmed)**:asymmetric `-top-px -right-1`\n // (top -1px / right -4px)— field padding-y(4px sm/md)緊 → top 只 -1px 安全;\n // padding-x 12px 寬鬆 → right 凸 4px 達 badge canonical visual。對齊 ClickUp\n // 世界級 idiom(asymmetric offset by avatar/field size constraint)。\n 'absolute -top-px -right-1 z-10',\n 'inline-flex items-center justify-center',\n // **12×12 + 2px white ring**(SSOT match stacked avatar,Slack/Material/iOS\n // notification badge 2px ring canonical)。改用 `[box-shadow:...]` 而非 `ring-2`\n // 避免跟下方 `focus-visible:ring-2` 在 tailwind-merge 衝突(同 ring family\n // override 互殺)。Box-shadow inset 0 不影響 layout,也不被 focus-visible ring\n // 蓋掉(focus 那邊另一條 outline ring 不同 layer)。\n 'w-3 h-3 rounded-full [box-shadow:0_0_0_2px_var(--surface)]',\n // bg-surface-strong = neutral-6-opaque / hover = neutral-7-opaque(both modes,\n // step-7 dark 公式自動 lighter → engaged 跨 mode 對稱)\n 'bg-surface-strong text-on-emphasis hover:bg-surface-strong-hover',\n // a11y(codex P1 fix):opacity 而非 display:none — element 在 DOM/tab-order,\n // keyboard 可達。Hover / focus-within / focus-visible 三條件之一觸發。\n 'opacity-0 group-hover/avatar:opacity-100 group-focus-within/avatar:opacity-100 focus-visible:opacity-100',\n 'transition-opacity duration-150',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n ].join(' ')}\n >\n <X size={12} strokeWidth={3} aria-hidden />\n </button>\n )\n}\n\n// ── PersonAvatarTag(Combobox tagRenderer SSOT for stack mode)─────────────\n// PeoplePicker `multiDisplay='stack'` 模式 wraps Combobox,tagRenderer 不能用 Tag pill\n// (那是 pill mode),改 render 此元件 — Avatar overlap 視覺 + AvatarDismissOverlay。\n// 對齊 user directive「avatar = tag 概念,差別只在視覺,SSOT 一致」(2026-05-07 v15.13)。\n//\n// **架構**(v15.13 重構):本元件**不自包** `group/avatar` / `-ml-0.5` overlap wrapper,\n// 因 Combobox `tagRenderer` 結果會被內部 `<div shrink-0>` 包成 measurement wrapper\n// (useOverflowCount 必要)。把 overlap + group 拉到 Combobox 的 `tagWrapperClassName`\n// 上,sibling-level overlap + group selector 才能正確 chain → AvatarDismissOverlay 的\n// `group-hover/avatar:opacity-100` 才會通。\nfunction PersonAvatarTag({\n person, size = 'md', onRemove,\n}: {\n person: PersonData\n size?: 'sm' | 'md' | 'lg'\n onRemove?: () => void\n}) {\n return (\n <>\n <PersonAvatar person={person} size={size} className=\"ring-2 ring-[var(--surface)]\" />\n {onRemove && <AvatarDismissOverlay onRemove={onRemove} label={person.name} />}\n </>\n )\n}\nPersonAvatarTag.displayName = 'PersonAvatarTag'\n\nexport { PersonDisplay, MultiPersonDisplay, PersonAvatarTag, buildPersonProfileCard, resolvePerson }\n"],"names":["visible"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/components/Popover/popover.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"popover.d.ts","sourceRoot":"","sources":["../../../src/components/Popover/popover.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAA;AAS3D;;;;;;;;;;;;;;;;;GAiBG;AAEH,QAAA,MAAM,OAAO,yCAAwB,CAAA;AACrC,QAAA,MAAM,cAAc,gHAA2B,CAAA;AAC/C,QAAA,MAAM,aAAa,4GAA0B,CAAA;AAC7C,QAAA,MAAM,YAAY,8GAAyB,CAAA;AAsC3C,QAAA,MAAM,cAAc,gKA2ClB,CAAA;AAKF,UAAU,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACvE;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,QAAA,MAAM,aAAa,2FAqBlB,CAAA;AAYD,QAAA,MAAM,WAAW,6GAEhB,CAAA;AAGD,QAAA,MAAM,aAAa,6GAElB,CAAA;AAcD,QAAA,MAAM,YAAY,qHAuBhB,CAAA;AAKF,eAAO,MAAM,WAAW;;;;;;;;;;;CAed,CAAA;AAEV,OAAO,EACL,OAAO,EACP,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,GACb,CAAA"}
|
|
@@ -6,6 +6,7 @@ import { cn } from "../../lib/utils.js";
|
|
|
6
6
|
import { SurfaceBody, SurfaceFooter, SurfaceHeader, COMPACT_HEADER_SLOT } from "../../patterns/overlay-surface/overlay-surface.js";
|
|
7
7
|
import { Button } from "../Button/button.js";
|
|
8
8
|
import { OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET } from "../../tokens/elevation/overlay-geometry.js";
|
|
9
|
+
import { overlayMotion } from "../../tokens/motion/overlay-motion.js";
|
|
9
10
|
const Popover = PopoverPrimitive.Root;
|
|
10
11
|
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
11
12
|
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
@@ -48,7 +49,8 @@ const PopoverContent = React.forwardRef(({ className, align = "center", sideOffs
|
|
|
48
49
|
// 2026-05-04 viewport-aware max-h SSOT(從 ProfileCard 升 DS-wide):header/footer 永遠 in-viewport,body 壓縮 scroll
|
|
49
50
|
// 2026-05-05 audit dim 35 補:加 `min-h-0` 完成 M25 chain invariant(flex item default min-h: auto 阻 shrink)
|
|
50
51
|
"max-h-[var(--radix-popover-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0",
|
|
51
|
-
|
|
52
|
+
overlayMotion,
|
|
53
|
+
"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",
|
|
52
54
|
"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",
|
|
53
55
|
"origin-[var(--radix-popover-content-transform-origin)]",
|
|
54
56
|
className
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popover.js","sources":["../../../src/components/Popover/popover.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\nimport { X as XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { SurfaceHeader, SurfaceBody, SurfaceFooter, COMPACT_HEADER_SLOT } from \"@/design-system/patterns/overlay-surface/overlay-surface\"\nimport { Button } from \"@/design-system/components/Button/button\"\nimport { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from \"@/design-system/tokens/elevation/overlay-geometry\"\n\n/**\n * Popover — Radix Popover + 設計系統 token\n *\n * ── 視覺 ──\n * 與 Dialog 對齊:bg-surface-raised / rounded-lg / border-border / elevation-200。\n * layout-space 鎖 md(輕量浮層 header py-tight 保持精簡);ui-size 繼承 page(內部控件對齊觸發點,2026-06-15 改,原 data-density master switch)。\n *\n * ── 結構 ──\n * PopoverContent:外殼(bg / border / radius / shadow / density),無內距。\n * PopoverHeader / PopoverBody / PopoverFooter:消費 overlay-surface pattern\n * 共用的 SurfaceHeader / SurfaceBody / SurfaceFooter primitives(padding SSOT)。\n *\n * ── Header dismiss X(2026-04-20 決策) ──\n * 所有 PopoverHeader 一律附右上 X 按鈕(對齊 Dialog 的 canonical)。Popover 雖是\n * non-modal + click-outside-to-close,但有 header 的 Popover 通常結構化程度高\n * (title / 多區塊),明確的「關閉」入口讓使用者更易退出。無 header 的簡單 Popover\n * 不加 X(click-outside / Esc 即可)。\n */\n\nconst Popover = PopoverPrimitive.Root\nconst PopoverTrigger = PopoverPrimitive.Trigger\nconst PopoverAnchor = PopoverPrimitive.Anchor\nconst PopoverClose = PopoverPrimitive.Close\n\n// AutoFocus canonical(對齊 Dialog / Sheet / Material / Polaris)—\n// 開啟時 focus 落在 body 第一個有意義互動元素,避免 focus 到 close X 觸發 tooltip leak。\n// 兩段查詢對齊 dialog.tsx handleOpenAutoFocus(2026-07-05 修):舊版單一混合清單按 DOM 序取\n// 第一 match,header 內互動元素(非 data-dismiss)會搶走 body 首元素焦點,且 bare 段漏 select。\n// 改為:先 body-scoped 查詢,查無才退 bare 全域清單(保留 naked popover — consumer 不用\n// PopoverBody 自管結構 — 的 fallback 意圖),最後 footer → content 容器。\nconst handlePopoverOpenAutoFocus = (e: Event) => {\n e.preventDefault()\n const content = e.currentTarget as HTMLElement\n const firstBodyTarget = content.querySelector<HTMLElement>(\n '[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])'\n )\n const firstBareTarget = content.querySelector<HTMLElement>(\n 'input:not([disabled]),textarea:not([disabled]),select:not([disabled]),button:not([disabled]):not([data-dismiss])'\n )\n const firstFooterButton = content.querySelector<HTMLElement>(\n '[data-popover-footer] button:not([disabled]):not([data-dismiss])'\n )\n ;(firstBodyTarget ?? firstBareTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })\n}\n\n// ── PopoverTitle → aria-labelledby 自動接線(2026-07-05,照 Radix Dialog Title 機制補 DS 層)──\n// Radix Popover 的 content 有 role=\"dialog\",但沒有 Radix Dialog Title 式的自動 accessible name\n// 接線 → 一般 Popover 預設無 accessible name。DS 層以 context 補同機制:PopoverContent 以 useId\n// 建 titleId 供給 children;PopoverTitle 掛載時註冊,PopoverContent 偵測到有 title 註冊才設\n// aria-labelledby。Consumer 自傳 aria-label / aria-labelledby 一律優先。PopoverTitle 亦被\n// DataTable sort / filter panel 等非 PopoverContent 場景裸用(raw SurfaceHeader chrome)—\n// context 為 null 時全部 no-op,行為與舊版一致。\ninterface PopoverTitleContextValue {\n titleId: string\n onTitleMount: () => void\n onTitleUnmount: () => void\n}\n\nconst PopoverTitleContext = React.createContext<PopoverTitleContextValue | null>(null)\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, onOpenAutoFocus, \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy, ...props }, ref) => {\n const titleId = React.useId()\n const [hasTitle, setHasTitle] = React.useState(false)\n const titleContext = React.useMemo<PopoverTitleContextValue>(\n () => ({ titleId, onTitleMount: () => setHasTitle(true), onTitleUnmount: () => setHasTitle(false) }),\n [titleId],\n )\n return (\n <PopoverPrimitive.Portal>\n <PopoverTitleContext.Provider value={titleContext}>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n // Layout-space lock(2026-06-15 canonical,原 data-density=\"md\" 改為只鎖 layout-space):Popover 是\n // 輕量浮層,header/footer 用 py-tight(layout-space)→ 鎖 layout-space=md 保持精簡 padding;但\n // **ui-size 不鎖、繼承 page** → 內部 field 控件 / dismiss 按鈕隨 page 放大,跟觸發點一致(decouple)。\n // [data-layout-space=\"md\"] 有 reset selector(layoutSpace.css L31)→ lg page 上正確 reset 回 md。\n data-layout-space=\"md\"\n onOpenAutoFocus={onOpenAutoFocus ?? handlePopoverOpenAutoFocus}\n aria-label={ariaLabel}\n // accessible name 自動接線:consumer 自傳優先;無 aria-label 且有 PopoverTitle 註冊才掛 titleId\n aria-labelledby={ariaLabelledBy ?? (ariaLabel == null && hasTitle ? titleId : undefined)}\n className={cn(\n \"z-50 w-72 rounded-lg border border-border bg-surface-raised text-foreground shadow-[var(--elevation-200)] outline-none\",\n // 2026-05-04 viewport-aware max-h SSOT(從 ProfileCard 升 DS-wide):header/footer 永遠 in-viewport,body 壓縮 scroll\n // 2026-05-05 audit dim 35 補:加 `min-h-0` 完成 M25 chain invariant(flex item default min-h: auto 阻 shrink)\n \"max-h-[var(--radix-popover-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0\",\n \"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\",\n \"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\",\n \"origin-[var(--radix-popover-content-transform-origin)]\",\n className\n )}\n {...props}\n />\n </PopoverTitleContext.Provider>\n </PopoverPrimitive.Portal>\n )\n})\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\n// PopoverHeader: SurfaceHeader + Close X(對齊 Dialog 的 canonical,見 docblock)\n// justify-between 讓 children 與 Close 分左右。\ninterface PopoverHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * 隱藏右上 close X(預設 false,顯示)。\n * Coachmark / Tour 類 composition 用 Skip / Done 自管 close,不需 X。\n */\n hideClose?: boolean\n}\n\nconst PopoverHeader = React.forwardRef<HTMLDivElement, PopoverHeaderProps>(\n ({ className, children, hideClose = false, ...props }, ref) => (\n // Popover lightweight chrome canonical(2026-05-04 重思 v2;2026-06-16 slot 改衍生 + 收斂 SSOT):\n // 走 `COMPACT_HEADER_SLOT`(= calc(--font-body-size×1.5) = 14×1.5 = 21 = PopoverTitle text-body line-box,\n // 衍生非寫死,取代舊各自寫死的 1.25rem / 20 巧合值)→ slot = title 行高,title 字級改 slot 自動跟。\n // Header 維持 padding-based:max(21 title, 21 slot) + py-tight(12*2) = 45 → 自然比 Dialog/Sheet 48 輕一級。\n // Q10 穩定:title-only / title+close 都 = title + py 主導(slot=title)。無 min-h / 無 py override。\n <SurfaceHeader\n ref={ref}\n className={cn(\"justify-between\", COMPACT_HEADER_SLOT, className)}\n {...props}\n >\n <div className=\"flex-1 min-w-0\">{children}</div>\n {!hideClose && (\n // Dismiss X = native sm,SurfaceHeader 負 my trick 讓 layout 佔位縮到 slot(21 = title line-box)\n <PopoverPrimitive.Close asChild>\n <Button data-dismiss iconOnly dismiss size=\"sm\" startIcon={XIcon} aria-label=\"關閉\" />\n </PopoverPrimitive.Close>\n )}\n </SurfaceHeader>\n ),\n)\nPopoverHeader.displayName = \"PopoverHeader\"\n\n// PopoverBody / PopoverFooter: wrap SurfaceBody / SurfaceFooter with data-popover-*\n// attributes so handlePopoverOpenAutoFocus 可正確定位 body 內第一個 interactive 元素\n//\n// ── List-as-region 場景(menu / Cmd+K / nav)──\n// 不再提供 `flush` variant(2026-05-01 移除,對齊 DialogBody / SheetBody canonical)。\n// consumer 用 SurfaceBody className override 撤掉 chrome padding + 自管 list outer wrapper:\n// `<PopoverBody className=\"!px-0 !py-0\"><div className=\"py-2\">{items}</div></PopoverBody>`\n// 或乾脆不用 PopoverBody,直接 PopoverContent > 自管 list 結構(naked popover)。\n// 詳 DialogBody comment + `tokens/layoutSpace/layoutSpace.spec.md`「List-as-region in overlay body」\nconst PopoverBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ ...props }, ref) => <SurfaceBody ref={ref} data-popover-body {...props} />,\n)\nPopoverBody.displayName = \"PopoverBody\"\n\nconst PopoverFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ ...props }, ref) => <SurfaceFooter ref={ref} data-popover-footer {...props} />\n)\nPopoverFooter.displayName = \"PopoverFooter\"\n\n// PopoverTitle(2026-04-22 v4 non-modal canonical):`text-body font-medium`(14px)\n// Rationale:Popover / Coachmark 屬 **non-modal 輕量浮層**,跟 density 鎖 md 同源的視覺語言 —\n// chrome 全體輕量:\n// - layout-space 鎖 md(ui-size 繼承 page,2026-06-15)\n// - dismiss X 走 COMPACT_HEADER_SLOT 佔位 21(= title text-body line-box;見 PopoverHeader docblock)\n// - **title `text-body`(14px)跟 Dialog / Sheet modal 的 body-lg(16px)形成重量級 vs 輕量級視覺區分**\n// 世界級對照:Figma / Notion / Linear / Material 的 popover / filter panel / inline settings\n// 的 header title 多半比 modal dialog title 小一級(16→14 或同比),視覺宣告「此浮層可忽略」\n//\n// Coachmark 同樣消費 PopoverTitle 作 header 小標籤(如 \"新功能介紹\" / \"Tip 1 of 3\"),\n// CoachmarkBody 的主 title 另走 text-body-lg(見 coachmark.tsx CoachmarkBody 主 title <h3>)。\nconst PopoverTitle = React.forwardRef<\n HTMLHeadingElement,\n React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, id, ...props }, ref) => {\n const titleCtx = React.useContext(PopoverTitleContext)\n // 掛載時向 PopoverContent 註冊 → content 自動設 aria-labelledby(見 PopoverTitleContext docblock)。\n // consumer 自傳 id 時不註冊:auto 接線指向 context titleId,自訂 id 下引用會斷 → aria 由 consumer 自管。\n const shouldRegister = id == null && titleCtx != null\n const onTitleMount = titleCtx?.onTitleMount\n const onTitleUnmount = titleCtx?.onTitleUnmount\n React.useLayoutEffect(() => {\n if (!shouldRegister) return\n onTitleMount?.()\n return onTitleUnmount\n }, [shouldRegister, onTitleMount, onTitleUnmount])\n return (\n <h2\n ref={ref}\n id={id ?? titleCtx?.titleId}\n className={cn(\"text-body font-medium truncate\", className)}\n {...props}\n />\n )\n})\nPopoverTitle.displayName = \"PopoverTitle\"\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const popoverMeta = {\n component: 'Popover',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default'], // 浮層容器 primitive,無自己的 variant/size/disabled 互動 state(spec「為何無 Inspector」段;對齊 separator.tsx meta canonical)\n tokens: {\n bg: ['bg-surface-raised'],\n fg: ['text-foreground'],\n ring: [],\n },\n} as const\n\nexport {\n Popover,\n PopoverTrigger,\n PopoverAnchor,\n PopoverContent,\n PopoverClose,\n PopoverHeader,\n PopoverBody,\n PopoverFooter,\n PopoverTitle,\n}\n"],"names":["XIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"popover.js","sources":["../../../src/components/Popover/popover.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\nimport { X as XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { SurfaceHeader, SurfaceBody, SurfaceFooter, COMPACT_HEADER_SLOT } from \"@/design-system/patterns/overlay-surface/overlay-surface\"\nimport { Button } from \"@/design-system/components/Button/button\"\nimport { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from \"@/design-system/tokens/elevation/overlay-geometry\"\nimport { overlayMotion } from \"@/design-system/tokens/motion/overlay-motion\"\n\n/**\n * Popover — Radix Popover + 設計系統 token\n *\n * ── 視覺 ──\n * 與 Dialog 對齊:bg-surface-raised / rounded-lg / border-border / elevation-200。\n * layout-space 鎖 md(輕量浮層 header py-tight 保持精簡);ui-size 繼承 page(內部控件對齊觸發點,2026-06-15 改,原 data-density master switch)。\n *\n * ── 結構 ──\n * PopoverContent:外殼(bg / border / radius / shadow / density),無內距。\n * PopoverHeader / PopoverBody / PopoverFooter:消費 overlay-surface pattern\n * 共用的 SurfaceHeader / SurfaceBody / SurfaceFooter primitives(padding SSOT)。\n *\n * ── Header dismiss X(2026-04-20 決策) ──\n * 所有 PopoverHeader 一律附右上 X 按鈕(對齊 Dialog 的 canonical)。Popover 雖是\n * non-modal + click-outside-to-close,但有 header 的 Popover 通常結構化程度高\n * (title / 多區塊),明確的「關閉」入口讓使用者更易退出。無 header 的簡單 Popover\n * 不加 X(click-outside / Esc 即可)。\n */\n\nconst Popover = PopoverPrimitive.Root\nconst PopoverTrigger = PopoverPrimitive.Trigger\nconst PopoverAnchor = PopoverPrimitive.Anchor\nconst PopoverClose = PopoverPrimitive.Close\n\n// AutoFocus canonical(對齊 Dialog / Sheet / Material / Polaris)—\n// 開啟時 focus 落在 body 第一個有意義互動元素,避免 focus 到 close X 觸發 tooltip leak。\n// 兩段查詢對齊 dialog.tsx handleOpenAutoFocus(2026-07-05 修):舊版單一混合清單按 DOM 序取\n// 第一 match,header 內互動元素(非 data-dismiss)會搶走 body 首元素焦點,且 bare 段漏 select。\n// 改為:先 body-scoped 查詢,查無才退 bare 全域清單(保留 naked popover — consumer 不用\n// PopoverBody 自管結構 — 的 fallback 意圖),最後 footer → content 容器。\nconst handlePopoverOpenAutoFocus = (e: Event) => {\n e.preventDefault()\n const content = e.currentTarget as HTMLElement\n const firstBodyTarget = content.querySelector<HTMLElement>(\n '[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])'\n )\n const firstBareTarget = content.querySelector<HTMLElement>(\n 'input:not([disabled]),textarea:not([disabled]),select:not([disabled]),button:not([disabled]):not([data-dismiss])'\n )\n const firstFooterButton = content.querySelector<HTMLElement>(\n '[data-popover-footer] button:not([disabled]):not([data-dismiss])'\n )\n ;(firstBodyTarget ?? firstBareTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })\n}\n\n// ── PopoverTitle → aria-labelledby 自動接線(2026-07-05,照 Radix Dialog Title 機制補 DS 層)──\n// Radix Popover 的 content 有 role=\"dialog\",但沒有 Radix Dialog Title 式的自動 accessible name\n// 接線 → 一般 Popover 預設無 accessible name。DS 層以 context 補同機制:PopoverContent 以 useId\n// 建 titleId 供給 children;PopoverTitle 掛載時註冊,PopoverContent 偵測到有 title 註冊才設\n// aria-labelledby。Consumer 自傳 aria-label / aria-labelledby 一律優先。PopoverTitle 亦被\n// DataTable sort / filter panel 等非 PopoverContent 場景裸用(raw SurfaceHeader chrome)—\n// context 為 null 時全部 no-op,行為與舊版一致。\ninterface PopoverTitleContextValue {\n titleId: string\n onTitleMount: () => void\n onTitleUnmount: () => void\n}\n\nconst PopoverTitleContext = React.createContext<PopoverTitleContextValue | null>(null)\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, onOpenAutoFocus, \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy, ...props }, ref) => {\n const titleId = React.useId()\n const [hasTitle, setHasTitle] = React.useState(false)\n const titleContext = React.useMemo<PopoverTitleContextValue>(\n () => ({ titleId, onTitleMount: () => setHasTitle(true), onTitleUnmount: () => setHasTitle(false) }),\n [titleId],\n )\n return (\n <PopoverPrimitive.Portal>\n <PopoverTitleContext.Provider value={titleContext}>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n // Layout-space lock(2026-06-15 canonical,原 data-density=\"md\" 改為只鎖 layout-space):Popover 是\n // 輕量浮層,header/footer 用 py-tight(layout-space)→ 鎖 layout-space=md 保持精簡 padding;但\n // **ui-size 不鎖、繼承 page** → 內部 field 控件 / dismiss 按鈕隨 page 放大,跟觸發點一致(decouple)。\n // [data-layout-space=\"md\"] 有 reset selector(layoutSpace.css L31)→ lg page 上正確 reset 回 md。\n data-layout-space=\"md\"\n onOpenAutoFocus={onOpenAutoFocus ?? handlePopoverOpenAutoFocus}\n aria-label={ariaLabel}\n // accessible name 自動接線:consumer 自傳優先;無 aria-label 且有 PopoverTitle 註冊才掛 titleId\n aria-labelledby={ariaLabelledBy ?? (ariaLabel == null && hasTitle ? titleId : undefined)}\n className={cn(\n \"z-50 w-72 rounded-lg border border-border bg-surface-raised text-foreground shadow-[var(--elevation-200)] outline-none\",\n // 2026-05-04 viewport-aware max-h SSOT(從 ProfileCard 升 DS-wide):header/footer 永遠 in-viewport,body 壓縮 scroll\n // 2026-05-05 audit dim 35 補:加 `min-h-0` 完成 M25 chain invariant(flex item default min-h: auto 阻 shrink)\n \"max-h-[var(--radix-popover-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0\",\n overlayMotion,\n \"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\",\n \"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\",\n \"origin-[var(--radix-popover-content-transform-origin)]\",\n className\n )}\n {...props}\n />\n </PopoverTitleContext.Provider>\n </PopoverPrimitive.Portal>\n )\n})\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\n// PopoverHeader: SurfaceHeader + Close X(對齊 Dialog 的 canonical,見 docblock)\n// justify-between 讓 children 與 Close 分左右。\ninterface PopoverHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * 隱藏右上 close X(預設 false,顯示)。\n * Coachmark / Tour 類 composition 用 Skip / Done 自管 close,不需 X。\n */\n hideClose?: boolean\n}\n\nconst PopoverHeader = React.forwardRef<HTMLDivElement, PopoverHeaderProps>(\n ({ className, children, hideClose = false, ...props }, ref) => (\n // Popover lightweight chrome canonical(2026-05-04 重思 v2;2026-06-16 slot 改衍生 + 收斂 SSOT):\n // 走 `COMPACT_HEADER_SLOT`(= calc(--font-body-size×1.5) = 14×1.5 = 21 = PopoverTitle text-body line-box,\n // 衍生非寫死,取代舊各自寫死的 1.25rem / 20 巧合值)→ slot = title 行高,title 字級改 slot 自動跟。\n // Header 維持 padding-based:max(21 title, 21 slot) + py-tight(12*2) = 45 → 自然比 Dialog/Sheet 48 輕一級。\n // Q10 穩定:title-only / title+close 都 = title + py 主導(slot=title)。無 min-h / 無 py override。\n <SurfaceHeader\n ref={ref}\n className={cn(\"justify-between\", COMPACT_HEADER_SLOT, className)}\n {...props}\n >\n <div className=\"flex-1 min-w-0\">{children}</div>\n {!hideClose && (\n // Dismiss X = native sm,SurfaceHeader 負 my trick 讓 layout 佔位縮到 slot(21 = title line-box)\n <PopoverPrimitive.Close asChild>\n <Button data-dismiss iconOnly dismiss size=\"sm\" startIcon={XIcon} aria-label=\"關閉\" />\n </PopoverPrimitive.Close>\n )}\n </SurfaceHeader>\n ),\n)\nPopoverHeader.displayName = \"PopoverHeader\"\n\n// PopoverBody / PopoverFooter: wrap SurfaceBody / SurfaceFooter with data-popover-*\n// attributes so handlePopoverOpenAutoFocus 可正確定位 body 內第一個 interactive 元素\n//\n// ── List-as-region 場景(menu / Cmd+K / nav)──\n// 不再提供 `flush` variant(2026-05-01 移除,對齊 DialogBody / SheetBody canonical)。\n// consumer 用 SurfaceBody className override 撤掉 chrome padding + 自管 list outer wrapper:\n// `<PopoverBody className=\"!px-0 !py-0\"><div className=\"py-2\">{items}</div></PopoverBody>`\n// 或乾脆不用 PopoverBody,直接 PopoverContent > 自管 list 結構(naked popover)。\n// 詳 DialogBody comment + `tokens/layoutSpace/layoutSpace.spec.md`「List-as-region in overlay body」\nconst PopoverBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ ...props }, ref) => <SurfaceBody ref={ref} data-popover-body {...props} />,\n)\nPopoverBody.displayName = \"PopoverBody\"\n\nconst PopoverFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ ...props }, ref) => <SurfaceFooter ref={ref} data-popover-footer {...props} />\n)\nPopoverFooter.displayName = \"PopoverFooter\"\n\n// PopoverTitle(2026-04-22 v4 non-modal canonical):`text-body font-medium`(14px)\n// Rationale:Popover / Coachmark 屬 **non-modal 輕量浮層**,跟 density 鎖 md 同源的視覺語言 —\n// chrome 全體輕量:\n// - layout-space 鎖 md(ui-size 繼承 page,2026-06-15)\n// - dismiss X 走 COMPACT_HEADER_SLOT 佔位 21(= title text-body line-box;見 PopoverHeader docblock)\n// - **title `text-body`(14px)跟 Dialog / Sheet modal 的 body-lg(16px)形成重量級 vs 輕量級視覺區分**\n// 世界級對照:Figma / Notion / Linear / Material 的 popover / filter panel / inline settings\n// 的 header title 多半比 modal dialog title 小一級(16→14 或同比),視覺宣告「此浮層可忽略」\n//\n// Coachmark 同樣消費 PopoverTitle 作 header 小標籤(如 \"新功能介紹\" / \"Tip 1 of 3\"),\n// CoachmarkBody 的主 title 另走 text-body-lg(見 coachmark.tsx CoachmarkBody 主 title <h3>)。\nconst PopoverTitle = React.forwardRef<\n HTMLHeadingElement,\n React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, id, ...props }, ref) => {\n const titleCtx = React.useContext(PopoverTitleContext)\n // 掛載時向 PopoverContent 註冊 → content 自動設 aria-labelledby(見 PopoverTitleContext docblock)。\n // consumer 自傳 id 時不註冊:auto 接線指向 context titleId,自訂 id 下引用會斷 → aria 由 consumer 自管。\n const shouldRegister = id == null && titleCtx != null\n const onTitleMount = titleCtx?.onTitleMount\n const onTitleUnmount = titleCtx?.onTitleUnmount\n React.useLayoutEffect(() => {\n if (!shouldRegister) return\n onTitleMount?.()\n return onTitleUnmount\n }, [shouldRegister, onTitleMount, onTitleUnmount])\n return (\n <h2\n ref={ref}\n id={id ?? titleCtx?.titleId}\n className={cn(\"text-body font-medium truncate\", className)}\n {...props}\n />\n )\n})\nPopoverTitle.displayName = \"PopoverTitle\"\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const popoverMeta = {\n component: 'Popover',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default'], // 浮層容器 primitive,無自己的 variant/size/disabled 互動 state(spec「為何無 Inspector」段;對齊 separator.tsx meta canonical)\n tokens: {\n bg: ['bg-surface-raised'],\n fg: ['text-foreground'],\n ring: [],\n },\n} as const\n\nexport {\n Popover,\n PopoverTrigger,\n PopoverAnchor,\n PopoverContent,\n PopoverClose,\n PopoverHeader,\n PopoverBody,\n PopoverFooter,\n PopoverTitle,\n}\n"],"names":["XIcon"],"mappings":"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"}
|
|
@@ -43,7 +43,7 @@ type StatusType = 'online' | 'away' | 'busy' | 'offline';
|
|
|
43
43
|
* 各自不一致(同 person 在 DataTable / PeoplePicker / avatar.principles 看起來不同)。
|
|
44
44
|
*
|
|
45
45
|
* v11 canonical(歷史):ProfileCard 當時 always renders 4 default sections — 缺 data → 顯
|
|
46
|
-
* `EMPTY_PLACEHOLDER`("
|
|
46
|
+
* `EMPTY_PLACEHOLDER`("-")。現行:default fields 縮為 2(v15.7)+ status 條件 render(v12),
|
|
47
47
|
* 「視覺結構 SSOT 在此元件,不依賴 consumer」原則不變。
|
|
48
48
|
*
|
|
49
49
|
* ── 對齊 world-class ──
|
|
@@ -66,7 +66,7 @@ export interface ProfileCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
66
66
|
actions?: React.ReactNode;
|
|
67
67
|
/**
|
|
68
68
|
* Consumer 傳的 field 資料(partial)。預設 keys 走 `NAMECARD_DEFAULT_FIELD_KEYS` —
|
|
69
|
-
* 只有 id / employeeNumber 兩個 default field 永遠 render(缺資料顯
|
|
69
|
+
* 只有 id / employeeNumber 兩個 default field 永遠 render(缺資料顯 `-`);email / phone /
|
|
70
70
|
* department / location 等其他欄位一律 opt-in by consumer 透過本 prop 傳入。Consumer 想新增
|
|
71
71
|
* 自訂 field 直接傳入(在 default 之後 append),想 override default key value 也直接傳。
|
|
72
72
|
*/
|
|
@@ -39,7 +39,7 @@ const DEFAULT_FIELD_LABEL = {
|
|
|
39
39
|
id: "ID",
|
|
40
40
|
employeeNumber: "Employee number"
|
|
41
41
|
};
|
|
42
|
-
const FIELD_PLACEHOLDER = "
|
|
42
|
+
const FIELD_PLACEHOLDER = "-";
|
|
43
43
|
const ProfileCard = React.forwardRef(
|
|
44
44
|
({
|
|
45
45
|
name,
|
|
@@ -130,9 +130,9 @@ const ProfileCard = React.forwardRef(
|
|
|
130
130
|
),
|
|
131
131
|
/* @__PURE__ */ jsx("span", { className: "text-body", children: STATUS_LABEL[status] })
|
|
132
132
|
] }),
|
|
133
|
-
/* @__PURE__ */ jsx(DescriptionList, { children: /* @__PURE__ */ jsx(DescriptionItem, { label: "Status message", children: statusMessage ?? /* @__PURE__ */ jsx("span", { className: "text-
|
|
133
|
+
/* @__PURE__ */ jsx(DescriptionList, { children: /* @__PURE__ */ jsx(DescriptionItem, { label: "Status message", children: statusMessage ?? /* @__PURE__ */ jsx("span", { className: "text-foreground", children: FIELD_PLACEHOLDER }) }) })
|
|
134
134
|
] }),
|
|
135
|
-
/* @__PURE__ */ jsx("div", { className: cn("px-4 py-3", status && "border-t border-divider"), children: /* @__PURE__ */ jsx(DescriptionList, { cols: 2, children: allFields.map((f) => /* @__PURE__ */ jsx(DescriptionItem, { label: f.label, children: f.value === FIELD_PLACEHOLDER ? /* @__PURE__ */ jsx("span", { className: "text-
|
|
135
|
+
/* @__PURE__ */ jsx("div", { className: cn("px-4 py-3", status && "border-t border-divider"), children: /* @__PURE__ */ jsx(DescriptionList, { cols: 2, children: allFields.map((f) => /* @__PURE__ */ jsx(DescriptionItem, { label: f.label, children: f.value === FIELD_PLACEHOLDER ? /* @__PURE__ */ jsx("span", { className: "text-foreground", children: FIELD_PLACEHOLDER }) : f.value }, f.label)) }) })
|
|
136
136
|
] }),
|
|
137
137
|
onViewMore && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-divider px-4 py-3", children: /* @__PURE__ */ jsx(Button, { variant: "link", size: "sm", onClick: onViewMore, className: "w-full", children: viewMoreLabel }) })
|
|
138
138
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile-card.js","sources":["../../../src/components/ProfileCard/profile-card.tsx"],"sourcesContent":["/**\n * Public 元件(2026-07-04 Q8 拍板升 public,原 @internal 標記移除)。\n * 理由:avatar.spec.md「hover 必出 ProfileCard」DS-wide canonical 要求 consumer 直接\n * import ProfileCard + ProfileCardDefaultActions 組 HoverCard — 按 ui-development.md\n * 唯一判準(DS 想讓 consumer 直接用 = public)internal 標記是 governance bug;\n * 世界級對照:Atlassian ProfileCard 同為 public standalone。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { MessageCircle, Phone, ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\nimport { Button } from '@/design-system/components/Button/button'\nimport { DescriptionList, DescriptionItem } from '@/design-system/components/DescriptionList/description-list'\nimport { ScrollArea } from '@/design-system/components/ScrollArea/scroll-area'\nimport { ItemContent } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * ProfileCardDefaultActions — canonical 預設 action 組合\n *\n * ── 為什麼需要 export ──\n * ProfileCard 的 action 列在世界級 chat/contact app 是「關係型快速動作」的 canonical\n * (Slack / iMessage / LinkedIn / Figma 皆如此):**Chat / Message + Audio call**。\n * 跨 consumer(avatar.principles / profile-card.stories / future product code)應該用\n * 同一組預設,避免每個範例各自發明 action 組合,讓 reader 誤以為 action 會隨情境自動變。\n *\n * ── 使用方式(hover context 必含 onViewMore,見 profile-card.spec.md 「View more」節)──\n * <ProfileCard name=\"...\" actions={<ProfileCardDefaultActions />} onViewMore={...} />\n *\n * ── 何時要換成自訂 action ──\n * - Single-action 情境(只要「傳訊息」)→ consumer 傳 `<Button>傳訊息</Button>`\n * - 特定情境的 action(管理員「撤銷邀請」/ HR「離職管理」)→ consumer 自訂\n * - 非人員關係動作(「訂購此商品」)→ 根本不應該用 ProfileCard\n *\n * Chat + Audio call 是 **default**,不是 **only**——consumer 可覆寫,但需有明確理由。\n */\nexport const ProfileCardDefaultActions = ({\n onChat,\n onCall,\n}: {\n /** Chat 按鈕 handler(2026-07-06 user 拍板:預設鈕必可接線,未傳 = dev-warn;PersonData.onChat 自動 forward) */\n onChat?: () => void\n /** Audio call 按鈕 handler(ChevronDown 下拉行為留 v2,先接主 callback) */\n onCall?: () => void\n}) => {\n // Dev-mode warning(對齊本檔 defaultFieldValues dev-warn 先例):預設 actions 沒接 handler\n // = 按鈕點了永遠沒反應(2026-07-06 D4 finding);production 必傳 callback 或自組 actions。\n if (process.env.NODE_ENV !== 'production' && !onChat && !onCall) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS ProfileCard] ProfileCardDefaultActions 未接 onChat/onCall——按鈕點擊無反應。請傳入 callback(PersonData.onChat/onCall 會自動 forward)或自組 actions。'\n )\n }\n return (\n <>\n <Button variant=\"tertiary\" size=\"sm\" startIcon={MessageCircle} onClick={onChat}>Chat</Button>\n <Button variant=\"tertiary\" size=\"sm\" startIcon={Phone} endIcon={ChevronDown} onClick={onCall}>Audio call</Button>\n </>\n )\n}\n\n/**\n * ProfileCard — 人員 HoverCard 的內容元件\n *\n * ── Status 對齊 Avatar presence canonical(2026-04-20) ──\n * status type = `online | away | busy | offline`,跟 Avatar 的 presence prop 對齊\n * (世界級 Slack / Teams / Discord term);dot 色走 `--status-*` semantic token\n * (獨立於 success/error/warning,避免語義衝突)。\n *\n * ── Avatar 對齊 ──\n * 跟 FileItem rich 統一:右側 text column 用 justify-center + minHeight=avatar。\n * 短文字置中於 avatar,長文字(多行名字)自然撐高。\n *\n * ── Info fields ──\n * Status message / ID / Employee number 等唯讀屬性全部用 DescriptionList 家族\n * 承載(不手刻 dt/dd),canonical 由 DS primitive own。\n */\n\nconst AVATAR_SIZE = 64\n\ntype StatusType = 'online' | 'away' | 'busy' | 'offline'\n\n// Presence semantic tokens(見 color/semantic.css)——跟 Avatar status dot 共用\nconst STATUS_DOT_COLOR: Record<StatusType, string> = {\n online: 'var(--status-online)',\n away: 'var(--status-away)',\n busy: 'var(--status-busy)',\n offline: 'var(--status-offline)',\n}\n\nconst STATUS_LABEL: Record<StatusType, string> = {\n online: 'Online',\n away: 'Away',\n busy: 'Busy',\n offline: 'Offline',\n}\n\n/**\n * ProfileCard SSOT — 預設 field keys(現行 v15.7:default 只 `id` + `employeeNumber`;\n * v11 always-render 為歷史脈絡,已退役 — 見下方 2026-05-07 註解 + render 處 v12 註解)\n *\n * ── 為什麼 SSOT ──\n * User explicit rule:「所有 namecard 預設顯示的資訊都是要一樣完整的」。前 v10 props\n * 全 optional + body conditional render → consumer 漏傳 fields 視覺缺 section,每個範例\n * 各自不一致(同 person 在 DataTable / PeoplePicker / avatar.principles 看起來不同)。\n *\n * v11 canonical(歷史):ProfileCard 當時 always renders 4 default sections — 缺 data → 顯\n * `EMPTY_PLACEHOLDER`(\"—\")。現行:default fields 縮為 2(v15.7)+ status 條件 render(v12),\n * 「視覺結構 SSOT 在此元件,不依賴 consumer」原則不變。\n *\n * ── 對齊 world-class ──\n * Slack profile card / Linear member card / Notion person card / GitHub user card / Figma user card\n * 都是 fixed schema(role / email / location / department / department / pronouns 等),\n * 不會因為某 user 沒填 phone 整個 phone field 不見 — 缺 = 顯 `Not set` 或留白。\n *\n * ── 為什麼 placeholder 不 hide ──\n * Hide → consumer 不知道少傳 → 視覺漂移;Placeholder → 永遠看到「該欄該有」+ dev-warn 提示\n * consumer 補資料,自動防漂移(M19 ensure-canonical 對齊)。\n */\n// **2026-05-07 v15.7 user directive**:default render 只 `id` + `employeeNumber` 兩個。\n// Email / Phone / Department / Location 等其他 description 一律 opt-in by consumer 透過\n// `fields` array prop。對齊 user 明確「應該確保所有都只有這兩個,因為我並沒有要求你要選其他的」。\nexport const NAMECARD_DEFAULT_FIELD_KEYS = ['id', 'employeeNumber'] as const\ntype ProfileCardDefaultFieldKey = typeof NAMECARD_DEFAULT_FIELD_KEYS[number]\n\nconst DEFAULT_FIELD_LABEL: Record<ProfileCardDefaultFieldKey, string> = {\n id: 'ID',\n employeeNumber: 'Employee number',\n}\n\nconst FIELD_PLACEHOLDER = '—'\n\nexport interface ProfileCardProps extends React.HTMLAttributes<HTMLDivElement> {\n name: string\n avatar?: AvatarData\n subtitle?: string\n status?: StatusType\n statusMessage?: React.ReactNode\n actions?: React.ReactNode\n /**\n * Consumer 傳的 field 資料(partial)。預設 keys 走 `NAMECARD_DEFAULT_FIELD_KEYS` —\n * 只有 id / employeeNumber 兩個 default field 永遠 render(缺資料顯 `—`);email / phone /\n * department / location 等其他欄位一律 opt-in by consumer 透過本 prop 傳入。Consumer 想新增\n * 自訂 field 直接傳入(在 default 之後 append),想 override default key value 也直接傳。\n */\n fields?: { label: string; value: React.ReactNode }[]\n /**\n * Default field 的真實值。Object key = NAMECARD_DEFAULT_FIELD_KEYS 之一。\n * 缺 key → render placeholder。Dev mode 會 console.warn 提醒消費者補資料。\n */\n defaultFieldValues?: Partial<Record<ProfileCardDefaultFieldKey, React.ReactNode>>\n onViewMore?: () => void\n viewMoreLabel?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst ProfileCard = React.forwardRef<HTMLDivElement, ProfileCardProps>(\n (\n {\n name,\n avatar,\n subtitle,\n status,\n statusMessage,\n actions,\n fields,\n defaultFieldValues,\n onViewMore,\n viewMoreLabel = 'View more',\n className,\n ...props\n },\n ref,\n ) => {\n // v12 canonical:default fields 永遠 render(缺資料顯 placeholder),consumer 自訂\n // fields 在 default 之後 append。Status section 為條件 render(status undefined →\n // 整 block 隱藏,見下方 render 處)— v11「永遠 render + placeholder」已退役。\n //\n // **Dedup canonical(2026-05-07 v15.8 fix Bug E)**:consumer 的 `fields` array 若含\n // label 撞 default(eg. 「ID」「Employee number」),consumer 值 win — defaults 那一行\n // 跳過(否則 same label 連 render 兩次,如 default placeholder `—` + consumer 真值)。\n // 這是遷移期 forgiving 行為:DEV warn 提示應改用 `defaultFieldValues`,但 production\n // 不破壞既有 consumer。對齊 React `key` 唯一性 + Linear / Slack profile card 一 label\n // 一 row idiom。\n const allFields = React.useMemo(() => {\n const consumerLabels = new Set((fields ?? []).map((f) => f.label))\n const defaults = NAMECARD_DEFAULT_FIELD_KEYS\n .map((key) => ({\n label: DEFAULT_FIELD_LABEL[key],\n value: defaultFieldValues?.[key] ?? FIELD_PLACEHOLDER,\n }))\n .filter((d) => !consumerLabels.has(d.label))\n return fields && fields.length > 0 ? [...defaults, ...fields] : defaults\n }, [defaultFieldValues, fields])\n\n // Dev warn:consumer 透過 `fields` 傳 default key label(legacy pattern)→ 應改 `defaultFieldValues`\n if (process.env.NODE_ENV !== 'production' && fields) {\n const legacyEntry = fields.find((f) =>\n Object.values(DEFAULT_FIELD_LABEL).includes(f.label as string),\n )\n if (legacyEntry) {\n // eslint-disable-next-line no-console\n console.warn(\n `[ProfileCard] \"${name}\":legacy pattern — fields[].label=\"${legacyEntry.label}\" ` +\n `is a default field. Migrate to defaultFieldValues={{ id, employeeNumber }} prop ` +\n `to align with NAMECARD_DEFAULT_FIELD_KEYS canonical.`,\n )\n }\n }\n\n // Dev mode warn:consumer 沒傳 default field 任何 key → 提示補完(避免漂移成 placeholder-only)\n if (process.env.NODE_ENV !== 'production' && !defaultFieldValues) {\n // eslint-disable-next-line no-console\n console.warn(\n `[ProfileCard] \"${name}\":no defaultFieldValues passed — sections will render placeholders. ` +\n `Pass at least { id, employeeNumber } via defaultFieldValues prop. ` +\n `For other description items (email/phone/department/location etc),use \\`fields\\` prop array.`,\n )\n }\n\n // Layout canonical(2026-04-23):Header + Actions 固定上,Body(status + fields)可捲動,\n // View more 固定下。**ProfileCard 自己約束高度**,不依賴 consumer HoverCardContent 設 flex:\n // - `max-h-[var(--radix-hover-card-content-available-height,...)]`:HoverCard / Popover\n // context 自動繼承 Radix viewport-aware 變數;standalone 落到 100vh fallback\n // - 內部 `flex flex-col + overflow-hidden`:Header(shrink-0)+ Body(flex-1 min-h-0 ScrollArea)\n // + Footer(shrink-0)三層 chrome\n // 世界級對照:Slack / Linear / GitHub / Notion hover-profile popover 皆此 chrome pattern。\n return (\n <div\n ref={ref}\n className={cn(\n 'w-[320px] flex flex-col overflow-hidden',\n 'max-h-[var(--radix-hover-card-content-available-height,var(--radix-popover-content-available-height,100vh))]',\n className,\n )}\n {...props}\n >\n {/* ── HEADER(固定): profile + actions ── */}\n <div className=\"shrink-0 flex flex-col\">\n <div className=\"flex items-start gap-3 px-4 py-3\">\n <Avatar\n src={avatar?.src}\n alt={avatar?.alt ?? name}\n color={avatar?.color}\n size={AVATAR_SIZE}\n status={status}\n className=\"shrink-0\"\n />\n {/* ProfileCard typography:label body-lg(16/1.5) + desc body(14/1.5) = reading mode + size=\"lg\"。\n labelClassName escape hatch 加 font-medium(card context 語意)+ labelTruncate=false 允許 wrap。 */}\n <ItemContent\n label={name}\n description={subtitle}\n mode=\"reading\"\n size=\"lg\"\n labelTruncate={false}\n labelClassName=\"text-body-lg font-medium text-foreground\"\n className=\"justify-center\"\n style={{ minHeight: AVATAR_SIZE }}\n />\n </div>\n\n {/* Action buttons — 均分空間 + 填滿格子(canonical):多個 action 等寬瓜分容器,\n 單一 action 也撐滿容器。`grid grid-flow-col auto-cols-fr` + `[&>*]:w-full`。\n 世界級對照:iOS contact card / macOS contact / LinkedIn profile card 的 action row。 */}\n {actions && (\n <div className=\"grid grid-flow-col auto-cols-fr gap-2 px-4 pb-3 [&>*]:w-full\">\n {actions}\n </div>\n )}\n </div>\n\n {/* ── BODY(可捲動,v12 status-conditional 2026-05-14):status + fields ──\n **v12 rule**(per user 拍板「不應該顯示『狀態沒有被設定』,production 每 user 一定有\n presence state,undefined 頂多是 loading transient 還沒讀到」):status undefined →\n 隱藏整 status badge + status message block(loading 期間 skip),禁 render「Status not\n set」這種 placeholder(語義錯,user presence 不會「沒設定」)。**ProfileCard-specific 不外推\n 至 DS 其他元件**(FileItem / DescriptionList / DataTable cell 各自 placeholder 邏輯\n unrelated)。Fields section 仍 always-render(info schema 性質)。 */}\n <ScrollArea className=\"flex-1 min-h-0 border-t border-divider\">\n {/* Status section:`status` defined 才 render(v12 conditional canonical) */}\n {status && (\n <div className=\"px-4 py-3 flex flex-col gap-3\">\n <div className=\"flex items-center gap-2 px-3 py-2 bg-muted rounded-md\">\n <span\n className=\"w-2.5 h-2.5 rounded-full shrink-0\"\n style={{ backgroundColor: STATUS_DOT_COLOR[status] }}\n />\n <span className=\"text-body\">{STATUS_LABEL[status]}</span>\n </div>\n {/* Status message — 只在 status defined 才 render(語意配對:status badge + status\n message 是一組;沒 status 就沒 status message)。缺 statusMessage 顯 placeholder。 */}\n <DescriptionList>\n <DescriptionItem label=\"Status message\">\n {statusMessage ?? <span className=\"text-fg-muted\">{FIELD_PLACEHOLDER}</span>}\n </DescriptionItem>\n </DescriptionList>\n </div>\n )}\n\n {/* Fields section:status defined 才有 border-t separator;無 status section 時去除 border-t(因 ScrollArea 起點已有上方 border-t,不重疊) */}\n <div className={cn('px-4 py-3', status && 'border-t border-divider')}>\n <DescriptionList cols={2}>\n {allFields.map((f) => (\n <DescriptionItem key={f.label} label={f.label}>\n {f.value === FIELD_PLACEHOLDER\n ? <span className=\"text-fg-muted\">{FIELD_PLACEHOLDER}</span>\n : f.value}\n </DescriptionItem>\n ))}\n </DescriptionList>\n </div>\n </ScrollArea>\n\n {/* ── FOOTER(固定): View more,py-3 canonical(12px,比一般 link 按鈕多呼吸) ── */}\n {onViewMore && (\n <div className=\"shrink-0 border-t border-divider px-4 py-3\">\n <Button variant=\"link\" size=\"sm\" onClick={onViewMore} className=\"w-full\">{viewMoreLabel}</Button>\n </div>\n )}\n </div>\n )\n },\n)\nProfileCard.displayName = 'ProfileCard'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const nameCardMeta = {\n component: 'ProfileCard',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: [], // 唯讀 person-info template,無自身互動 state(hover/active/focus/disabled 屬子 Button/Avatar)\n tokens: {\n bg: ['bg-muted'],\n fg: ['text-foreground'],\n ring: [],\n },\n} as const\n\nexport { ProfileCard }\n"],"names":[],"mappings":";;;;;;;;;AAoCO,MAAM,4BAA4B,CAAC;AAAA,EACxC;AAAA,EACA;AACF,MAKM;AAGJ,MAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,UAAU,CAAC,QAAQ;AAE/D,YAAQ;AAAA,MACN;AAAA,IAAA;AAAA,EAEJ;AACA,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO,SAAQ,YAAW,MAAK,MAAK,WAAW,eAAe,SAAS,QAAQ,UAAA,OAAA,CAAI;AAAA,IACpF,oBAAC,QAAA,EAAO,SAAQ,YAAW,MAAK,MAAK,WAAW,OAAO,SAAS,aAAa,SAAS,QAAQ,UAAA,aAAA,CAAU;AAAA,EAAA,GAC1G;AAEJ;AAmBA,MAAM,cAAc;AAKpB,MAAM,mBAA+C;AAAA,EACnD,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AACX;AAEA,MAAM,eAA2C;AAAA,EAC/C,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AACX;AA2BO,MAAM,8BAA8B,CAAC,MAAM,gBAAgB;AAGlE,MAAM,sBAAkE;AAAA,EACtE,IAAI;AAAA,EACJ,gBAAgB;AAClB;AAEA,MAAM,oBAAoB;AA0B1B,MAAM,cAAc,MAAM;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AAWH,UAAM,YAAY,MAAM,QAAQ,MAAM;AACpC,YAAM,iBAAiB,IAAI,KAAK,UAAU,CAAA,GAAI,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACjE,YAAM,WAAW,4BACd,IAAI,CAAC,SAAS;AAAA,QACb,OAAO,oBAAoB,GAAG;AAAA,QAC9B,QAAO,yDAAqB,SAAQ;AAAA,MAAA,EACpC,EACD,OAAO,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,KAAK,CAAC;AAC7C,aAAO,UAAU,OAAO,SAAS,IAAI,CAAC,GAAG,UAAU,GAAG,MAAM,IAAI;AAAA,IAClE,GAAG,CAAC,oBAAoB,MAAM,CAAC;AAG/B,QAAI,QAAQ,IAAI,aAAa,gBAAgB,QAAQ;AACnD,YAAM,cAAc,OAAO;AAAA,QAAK,CAAC,MAC/B,OAAO,OAAO,mBAAmB,EAAE,SAAS,EAAE,KAAe;AAAA,MAAA;AAE/D,UAAI,aAAa;AAEf,gBAAQ;AAAA,UACN,kBAAkB,IAAI,sCAAsC,YAAY,KAAK;AAAA,QAAA;AAAA,MAIjF;AAAA,IACF;AAGA,QAAI,QAAQ,IAAI,aAAa,gBAAgB,CAAC,oBAAoB;AAEhE,cAAQ;AAAA,QACN,kBAAkB,IAAI;AAAA,MAAA;AAAA,IAI1B;AASA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAGJ,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,0BACb,UAAA;AAAA,YAAA,qBAAC,OAAA,EAAI,WAAU,oCACb,UAAA;AAAA,cAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,KAAK,iCAAQ;AAAA,kBACb,MAAK,iCAAQ,QAAO;AAAA,kBACpB,OAAO,iCAAQ;AAAA,kBACf,MAAM;AAAA,kBACN;AAAA,kBACA,WAAU;AAAA,gBAAA;AAAA,cAAA;AAAA,cAIZ;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,OAAO;AAAA,kBACP,aAAa;AAAA,kBACb,MAAK;AAAA,kBACL,MAAK;AAAA,kBACL,eAAe;AAAA,kBACf,gBAAe;AAAA,kBACf,WAAU;AAAA,kBACV,OAAO,EAAE,WAAW,YAAA;AAAA,gBAAY;AAAA,cAAA;AAAA,YAClC,GACF;AAAA,YAKC,WACC,oBAAC,OAAA,EAAI,WAAU,gEACZ,UAAA,QAAA,CACH;AAAA,UAAA,GAEJ;AAAA,UASA,qBAAC,YAAA,EAAW,WAAU,0CAEnB,UAAA;AAAA,YAAA,UACC,qBAAC,OAAA,EAAI,WAAU,iCACb,UAAA;AAAA,cAAA,qBAAC,OAAA,EAAI,WAAU,yDACb,UAAA;AAAA,gBAAA;AAAA,kBAAC;AAAA,kBAAA;AAAA,oBACC,WAAU;AAAA,oBACV,OAAO,EAAE,iBAAiB,iBAAiB,MAAM,EAAA;AAAA,kBAAE;AAAA,gBAAA;AAAA,oCAEpD,QAAA,EAAK,WAAU,aAAa,UAAA,aAAa,MAAM,EAAA,CAAE;AAAA,cAAA,GACpD;AAAA,cAGA,oBAAC,iBAAA,EACC,UAAA,oBAAC,iBAAA,EAAgB,OAAM,kBACpB,UAAA,iBAAiB,oBAAC,QAAA,EAAK,WAAU,iBAAiB,UAAA,kBAAA,CAAkB,GACvE,EAAA,CACF;AAAA,YAAA,GACF;AAAA,gCAID,OAAA,EAAI,WAAW,GAAG,aAAa,UAAU,yBAAyB,GACjE,UAAA,oBAAC,iBAAA,EAAgB,MAAM,GACpB,UAAA,UAAU,IAAI,CAAC,MACd,oBAAC,iBAAA,EAA8B,OAAO,EAAE,OACrC,UAAA,EAAE,UAAU,oBACT,oBAAC,UAAK,WAAU,iBAAiB,UAAA,mBAAkB,IACnD,EAAE,MAAA,GAHc,EAAE,KAIxB,CACD,GACH,EAAA,CACF;AAAA,UAAA,GACF;AAAA,UAGC,cACC,oBAAC,OAAA,EAAI,WAAU,8CACb,8BAAC,QAAA,EAAO,SAAQ,QAAO,MAAK,MAAK,SAAS,YAAY,WAAU,UAAU,yBAAc,EAAA,CAC1F;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AACA,YAAY,cAAc;AAInB,MAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO,CAAA;AAAA,EAGP,QAAQ,CAAA;AAAA;AAAA,EACR,QAAQ;AAAA,IACN,IAAI,CAAC,UAAU;AAAA,IACf,IAAI,CAAC,iBAAiB;AAAA,IACtB,MAAM,CAAA;AAAA,EAAC;AAEX;"}
|
|
1
|
+
{"version":3,"file":"profile-card.js","sources":["../../../src/components/ProfileCard/profile-card.tsx"],"sourcesContent":["/**\n * Public 元件(2026-07-04 Q8 拍板升 public,原 @internal 標記移除)。\n * 理由:avatar.spec.md「hover 必出 ProfileCard」DS-wide canonical 要求 consumer 直接\n * import ProfileCard + ProfileCardDefaultActions 組 HoverCard — 按 ui-development.md\n * 唯一判準(DS 想讓 consumer 直接用 = public)internal 標記是 governance bug;\n * 世界級對照:Atlassian ProfileCard 同為 public standalone。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { MessageCircle, Phone, ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\nimport { Button } from '@/design-system/components/Button/button'\nimport { DescriptionList, DescriptionItem } from '@/design-system/components/DescriptionList/description-list'\nimport { ScrollArea } from '@/design-system/components/ScrollArea/scroll-area'\nimport { ItemContent } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * ProfileCardDefaultActions — canonical 預設 action 組合\n *\n * ── 為什麼需要 export ──\n * ProfileCard 的 action 列在世界級 chat/contact app 是「關係型快速動作」的 canonical\n * (Slack / iMessage / LinkedIn / Figma 皆如此):**Chat / Message + Audio call**。\n * 跨 consumer(avatar.principles / profile-card.stories / future product code)應該用\n * 同一組預設,避免每個範例各自發明 action 組合,讓 reader 誤以為 action 會隨情境自動變。\n *\n * ── 使用方式(hover context 必含 onViewMore,見 profile-card.spec.md 「View more」節)──\n * <ProfileCard name=\"...\" actions={<ProfileCardDefaultActions />} onViewMore={...} />\n *\n * ── 何時要換成自訂 action ──\n * - Single-action 情境(只要「傳訊息」)→ consumer 傳 `<Button>傳訊息</Button>`\n * - 特定情境的 action(管理員「撤銷邀請」/ HR「離職管理」)→ consumer 自訂\n * - 非人員關係動作(「訂購此商品」)→ 根本不應該用 ProfileCard\n *\n * Chat + Audio call 是 **default**,不是 **only**——consumer 可覆寫,但需有明確理由。\n */\nexport const ProfileCardDefaultActions = ({\n onChat,\n onCall,\n}: {\n /** Chat 按鈕 handler(2026-07-06 user 拍板:預設鈕必可接線,未傳 = dev-warn;PersonData.onChat 自動 forward) */\n onChat?: () => void\n /** Audio call 按鈕 handler(ChevronDown 下拉行為留 v2,先接主 callback) */\n onCall?: () => void\n}) => {\n // Dev-mode warning(對齊本檔 defaultFieldValues dev-warn 先例):預設 actions 沒接 handler\n // = 按鈕點了永遠沒反應(2026-07-06 D4 finding);production 必傳 callback 或自組 actions。\n if (process.env.NODE_ENV !== 'production' && !onChat && !onCall) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS ProfileCard] ProfileCardDefaultActions 未接 onChat/onCall——按鈕點擊無反應。請傳入 callback(PersonData.onChat/onCall 會自動 forward)或自組 actions。'\n )\n }\n return (\n <>\n <Button variant=\"tertiary\" size=\"sm\" startIcon={MessageCircle} onClick={onChat}>Chat</Button>\n <Button variant=\"tertiary\" size=\"sm\" startIcon={Phone} endIcon={ChevronDown} onClick={onCall}>Audio call</Button>\n </>\n )\n}\n\n/**\n * ProfileCard — 人員 HoverCard 的內容元件\n *\n * ── Status 對齊 Avatar presence canonical(2026-04-20) ──\n * status type = `online | away | busy | offline`,跟 Avatar 的 presence prop 對齊\n * (世界級 Slack / Teams / Discord term);dot 色走 `--status-*` semantic token\n * (獨立於 success/error/warning,避免語義衝突)。\n *\n * ── Avatar 對齊 ──\n * 跟 FileItem rich 統一:右側 text column 用 justify-center + minHeight=avatar。\n * 短文字置中於 avatar,長文字(多行名字)自然撐高。\n *\n * ── Info fields ──\n * Status message / ID / Employee number 等唯讀屬性全部用 DescriptionList 家族\n * 承載(不手刻 dt/dd),canonical 由 DS primitive own。\n */\n\nconst AVATAR_SIZE = 64\n\ntype StatusType = 'online' | 'away' | 'busy' | 'offline'\n\n// Presence semantic tokens(見 color/semantic.css)——跟 Avatar status dot 共用\nconst STATUS_DOT_COLOR: Record<StatusType, string> = {\n online: 'var(--status-online)',\n away: 'var(--status-away)',\n busy: 'var(--status-busy)',\n offline: 'var(--status-offline)',\n}\n\nconst STATUS_LABEL: Record<StatusType, string> = {\n online: 'Online',\n away: 'Away',\n busy: 'Busy',\n offline: 'Offline',\n}\n\n/**\n * ProfileCard SSOT — 預設 field keys(現行 v15.7:default 只 `id` + `employeeNumber`;\n * v11 always-render 為歷史脈絡,已退役 — 見下方 2026-05-07 註解 + render 處 v12 註解)\n *\n * ── 為什麼 SSOT ──\n * User explicit rule:「所有 namecard 預設顯示的資訊都是要一樣完整的」。前 v10 props\n * 全 optional + body conditional render → consumer 漏傳 fields 視覺缺 section,每個範例\n * 各自不一致(同 person 在 DataTable / PeoplePicker / avatar.principles 看起來不同)。\n *\n * v11 canonical(歷史):ProfileCard 當時 always renders 4 default sections — 缺 data → 顯\n * `EMPTY_PLACEHOLDER`(\"-\")。現行:default fields 縮為 2(v15.7)+ status 條件 render(v12),\n * 「視覺結構 SSOT 在此元件,不依賴 consumer」原則不變。\n *\n * ── 對齊 world-class ──\n * Slack profile card / Linear member card / Notion person card / GitHub user card / Figma user card\n * 都是 fixed schema(role / email / location / department / department / pronouns 等),\n * 不會因為某 user 沒填 phone 整個 phone field 不見 — 缺 = 顯 `Not set` 或留白。\n *\n * ── 為什麼 placeholder 不 hide ──\n * Hide → consumer 不知道少傳 → 視覺漂移;Placeholder → 永遠看到「該欄該有」+ dev-warn 提示\n * consumer 補資料,自動防漂移(M19 ensure-canonical 對齊)。\n */\n// **2026-05-07 v15.7 user directive**:default render 只 `id` + `employeeNumber` 兩個。\n// Email / Phone / Department / Location 等其他 description 一律 opt-in by consumer 透過\n// `fields` array prop。對齊 user 明確「應該確保所有都只有這兩個,因為我並沒有要求你要選其他的」。\nexport const NAMECARD_DEFAULT_FIELD_KEYS = ['id', 'employeeNumber'] as const\ntype ProfileCardDefaultFieldKey = typeof NAMECARD_DEFAULT_FIELD_KEYS[number]\n\nconst DEFAULT_FIELD_LABEL: Record<ProfileCardDefaultFieldKey, string> = {\n id: 'ID',\n employeeNumber: 'Employee number',\n}\n\nconst FIELD_PLACEHOLDER = '-'\n\nexport interface ProfileCardProps extends React.HTMLAttributes<HTMLDivElement> {\n name: string\n avatar?: AvatarData\n subtitle?: string\n status?: StatusType\n statusMessage?: React.ReactNode\n actions?: React.ReactNode\n /**\n * Consumer 傳的 field 資料(partial)。預設 keys 走 `NAMECARD_DEFAULT_FIELD_KEYS` —\n * 只有 id / employeeNumber 兩個 default field 永遠 render(缺資料顯 `-`);email / phone /\n * department / location 等其他欄位一律 opt-in by consumer 透過本 prop 傳入。Consumer 想新增\n * 自訂 field 直接傳入(在 default 之後 append),想 override default key value 也直接傳。\n */\n fields?: { label: string; value: React.ReactNode }[]\n /**\n * Default field 的真實值。Object key = NAMECARD_DEFAULT_FIELD_KEYS 之一。\n * 缺 key → render placeholder。Dev mode 會 console.warn 提醒消費者補資料。\n */\n defaultFieldValues?: Partial<Record<ProfileCardDefaultFieldKey, React.ReactNode>>\n onViewMore?: () => void\n viewMoreLabel?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst ProfileCard = React.forwardRef<HTMLDivElement, ProfileCardProps>(\n (\n {\n name,\n avatar,\n subtitle,\n status,\n statusMessage,\n actions,\n fields,\n defaultFieldValues,\n onViewMore,\n viewMoreLabel = 'View more',\n className,\n ...props\n },\n ref,\n ) => {\n // v12 canonical:default fields 永遠 render(缺資料顯 placeholder),consumer 自訂\n // fields 在 default 之後 append。Status section 為條件 render(status undefined →\n // 整 block 隱藏,見下方 render 處)— v11「永遠 render + placeholder」已退役。\n //\n // **Dedup canonical(2026-05-07 v15.8 fix Bug E)**:consumer 的 `fields` array 若含\n // label 撞 default(eg. 「ID」「Employee number」),consumer 值 win — defaults 那一行\n // 跳過(否則 same label 連 render 兩次,如 default placeholder `-` + consumer 真值)。\n // 這是遷移期 forgiving 行為:DEV warn 提示應改用 `defaultFieldValues`,但 production\n // 不破壞既有 consumer。對齊 React `key` 唯一性 + Linear / Slack profile card 一 label\n // 一 row idiom。\n const allFields = React.useMemo(() => {\n const consumerLabels = new Set((fields ?? []).map((f) => f.label))\n const defaults = NAMECARD_DEFAULT_FIELD_KEYS\n .map((key) => ({\n label: DEFAULT_FIELD_LABEL[key],\n value: defaultFieldValues?.[key] ?? FIELD_PLACEHOLDER,\n }))\n .filter((d) => !consumerLabels.has(d.label))\n return fields && fields.length > 0 ? [...defaults, ...fields] : defaults\n }, [defaultFieldValues, fields])\n\n // Dev warn:consumer 透過 `fields` 傳 default key label(legacy pattern)→ 應改 `defaultFieldValues`\n if (process.env.NODE_ENV !== 'production' && fields) {\n const legacyEntry = fields.find((f) =>\n Object.values(DEFAULT_FIELD_LABEL).includes(f.label as string),\n )\n if (legacyEntry) {\n // eslint-disable-next-line no-console\n console.warn(\n `[ProfileCard] \"${name}\":legacy pattern — fields[].label=\"${legacyEntry.label}\" ` +\n `is a default field. Migrate to defaultFieldValues={{ id, employeeNumber }} prop ` +\n `to align with NAMECARD_DEFAULT_FIELD_KEYS canonical.`,\n )\n }\n }\n\n // Dev mode warn:consumer 沒傳 default field 任何 key → 提示補完(避免漂移成 placeholder-only)\n if (process.env.NODE_ENV !== 'production' && !defaultFieldValues) {\n // eslint-disable-next-line no-console\n console.warn(\n `[ProfileCard] \"${name}\":no defaultFieldValues passed — sections will render placeholders. ` +\n `Pass at least { id, employeeNumber } via defaultFieldValues prop. ` +\n `For other description items (email/phone/department/location etc),use \\`fields\\` prop array.`,\n )\n }\n\n // Layout canonical(2026-04-23):Header + Actions 固定上,Body(status + fields)可捲動,\n // View more 固定下。**ProfileCard 自己約束高度**,不依賴 consumer HoverCardContent 設 flex:\n // - `max-h-[var(--radix-hover-card-content-available-height,...)]`:HoverCard / Popover\n // context 自動繼承 Radix viewport-aware 變數;standalone 落到 100vh fallback\n // - 內部 `flex flex-col + overflow-hidden`:Header(shrink-0)+ Body(flex-1 min-h-0 ScrollArea)\n // + Footer(shrink-0)三層 chrome\n // 世界級對照:Slack / Linear / GitHub / Notion hover-profile popover 皆此 chrome pattern。\n return (\n <div\n ref={ref}\n className={cn(\n 'w-[320px] flex flex-col overflow-hidden',\n 'max-h-[var(--radix-hover-card-content-available-height,var(--radix-popover-content-available-height,100vh))]',\n className,\n )}\n {...props}\n >\n {/* ── HEADER(固定): profile + actions ── */}\n <div className=\"shrink-0 flex flex-col\">\n <div className=\"flex items-start gap-3 px-4 py-3\">\n <Avatar\n src={avatar?.src}\n alt={avatar?.alt ?? name}\n color={avatar?.color}\n size={AVATAR_SIZE}\n status={status}\n className=\"shrink-0\"\n />\n {/* ProfileCard typography:label body-lg(16/1.5) + desc body(14/1.5) = reading mode + size=\"lg\"。\n labelClassName escape hatch 加 font-medium(card context 語意)+ labelTruncate=false 允許 wrap。 */}\n <ItemContent\n label={name}\n description={subtitle}\n mode=\"reading\"\n size=\"lg\"\n labelTruncate={false}\n labelClassName=\"text-body-lg font-medium text-foreground\"\n className=\"justify-center\"\n style={{ minHeight: AVATAR_SIZE }}\n />\n </div>\n\n {/* Action buttons — 均分空間 + 填滿格子(canonical):多個 action 等寬瓜分容器,\n 單一 action 也撐滿容器。`grid grid-flow-col auto-cols-fr` + `[&>*]:w-full`。\n 世界級對照:iOS contact card / macOS contact / LinkedIn profile card 的 action row。 */}\n {actions && (\n <div className=\"grid grid-flow-col auto-cols-fr gap-2 px-4 pb-3 [&>*]:w-full\">\n {actions}\n </div>\n )}\n </div>\n\n {/* ── BODY(可捲動,v12 status-conditional 2026-05-14):status + fields ──\n **v12 rule**(per user 拍板「不應該顯示『狀態沒有被設定』,production 每 user 一定有\n presence state,undefined 頂多是 loading transient 還沒讀到」):status undefined →\n 隱藏整 status badge + status message block(loading 期間 skip),禁 render「Status not\n set」這種 placeholder(語義錯,user presence 不會「沒設定」)。**ProfileCard-specific 不外推\n 至 DS 其他元件**(FileItem / DescriptionList / DataTable cell 各自 placeholder 邏輯\n unrelated)。Fields section 仍 always-render(info schema 性質)。 */}\n <ScrollArea className=\"flex-1 min-h-0 border-t border-divider\">\n {/* Status section:`status` defined 才 render(v12 conditional canonical) */}\n {status && (\n <div className=\"px-4 py-3 flex flex-col gap-3\">\n <div className=\"flex items-center gap-2 px-3 py-2 bg-muted rounded-md\">\n <span\n className=\"w-2.5 h-2.5 rounded-full shrink-0\"\n style={{ backgroundColor: STATUS_DOT_COLOR[status] }}\n />\n <span className=\"text-body\">{STATUS_LABEL[status]}</span>\n </div>\n {/* Status message — 只在 status defined 才 render(語意配對:status badge + status\n message 是一組;沒 status 就沒 status message)。缺 statusMessage 顯 placeholder。 */}\n <DescriptionList>\n <DescriptionItem label=\"Status message\">\n {statusMessage ?? <span className=\"text-foreground\">{FIELD_PLACEHOLDER}</span>}\n </DescriptionItem>\n </DescriptionList>\n </div>\n )}\n\n {/* Fields section:status defined 才有 border-t separator;無 status section 時去除 border-t(因 ScrollArea 起點已有上方 border-t,不重疊) */}\n <div className={cn('px-4 py-3', status && 'border-t border-divider')}>\n <DescriptionList cols={2}>\n {allFields.map((f) => (\n <DescriptionItem key={f.label} label={f.label}>\n {f.value === FIELD_PLACEHOLDER\n ? <span className=\"text-foreground\">{FIELD_PLACEHOLDER}</span>\n : f.value}\n </DescriptionItem>\n ))}\n </DescriptionList>\n </div>\n </ScrollArea>\n\n {/* ── FOOTER(固定): View more,py-3 canonical(12px,比一般 link 按鈕多呼吸) ── */}\n {onViewMore && (\n <div className=\"shrink-0 border-t border-divider px-4 py-3\">\n <Button variant=\"link\" size=\"sm\" onClick={onViewMore} className=\"w-full\">{viewMoreLabel}</Button>\n </div>\n )}\n </div>\n )\n },\n)\nProfileCard.displayName = 'ProfileCard'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const nameCardMeta = {\n component: 'ProfileCard',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n states: [], // 唯讀 person-info template,無自身互動 state(hover/active/focus/disabled 屬子 Button/Avatar)\n tokens: {\n bg: ['bg-muted'],\n fg: ['text-foreground'],\n ring: [],\n },\n} as const\n\nexport { ProfileCard }\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../../src/components/RadioGroup/radio-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,mBAAmB,MAAM,6BAA6B,CAAA;AAElE,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAG3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;
|
|
1
|
+
{"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../../src/components/RadioGroup/radio-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,mBAAmB,MAAM,6BAA6B,CAAA;AAElE,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAG3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAa1E,MAAM,WAAW,eACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,mBAAmB,CAAC,IAAI,CAAC;IACvE;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAkCD,QAAA,MAAM,UAAU,wFAsGd,CAAA;AAWF,QAAA,MAAM,iBAAiB;;8EA2BtB,CAAA;AAOD,KAAK,uBAAuB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAA;AAE9F,MAAM,WAAW,mBACf,SAAQ,uBAAuB,EAC7B,YAAY,CAAC,OAAO,iBAAiB,CAAC;IACxC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,qHAAqH;IACrH,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAID,QAAA,MAAM,cAAc,+FA6FnB,CAAA;AAKD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwBjB,CAAA;AAEV,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -4,10 +4,11 @@ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
|
4
4
|
import { Circle } from "lucide-react";
|
|
5
5
|
import { cva } from "class-variance-authority";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
-
import { useResolvedFieldMode, useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled } from "../Field/field-context.js";
|
|
7
|
+
import { useResolvedFieldMode, useFieldEmptyDisplay, useFieldContext, useResolvedFieldSize, fieldEmptyColorClass, useResolvedFieldDisabled } from "../Field/field-context.js";
|
|
8
8
|
import { SelectionItem } from "../SelectionControl/selection-item.js";
|
|
9
|
-
import {
|
|
9
|
+
import { fieldWrapperStyles } from "../Field/field-wrapper.js";
|
|
10
10
|
const RadioGroupReadonlyContext = React.createContext(false);
|
|
11
|
+
const RadioGroupDisabledContext = React.createContext(false);
|
|
11
12
|
function findSelectedRadioLabel(children, selectedValue) {
|
|
12
13
|
let selectedLabel = null;
|
|
13
14
|
React.Children.forEach(children, (child) => {
|
|
@@ -29,6 +30,7 @@ function findSelectedRadioLabel(children, selectedValue) {
|
|
|
29
30
|
}
|
|
30
31
|
const RadioGroup = React.forwardRef(({ className, mode, variant: _chrome, value, defaultValue, ...props }, ref) => {
|
|
31
32
|
const resolvedMode = useResolvedFieldMode({ mode, disabled: props.disabled });
|
|
33
|
+
const emptyDisplay = useFieldEmptyDisplay();
|
|
32
34
|
const fieldCtx = useFieldContext();
|
|
33
35
|
const resolvedBoxSize = useResolvedFieldSize(void 0, "md");
|
|
34
36
|
const {
|
|
@@ -46,7 +48,7 @@ const RadioGroup = React.forwardRef(({ className, mode, variant: _chrome, value,
|
|
|
46
48
|
if (resolvedMode === "display") {
|
|
47
49
|
const selectedValue = value ?? defaultValue;
|
|
48
50
|
if (!selectedValue) {
|
|
49
|
-
return /* @__PURE__ */ jsx("div", { ...restDomProps, ref, role: "group", className: cn("grid", className), children: /* @__PURE__ */ jsx("span", { className:
|
|
51
|
+
return /* @__PURE__ */ jsx("div", { ...restDomProps, ref, role: "group", className: cn("grid", className), children: /* @__PURE__ */ jsx("span", { className: fieldEmptyColorClass(resolvedMode), children: emptyDisplay }) });
|
|
50
52
|
}
|
|
51
53
|
const selectedLabel = findSelectedRadioLabel(props.children, selectedValue);
|
|
52
54
|
return /* @__PURE__ */ jsx("div", { ...restDomProps, ref, role: "group", className: cn("grid", className), children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: selectedLabel ?? selectedValue }) });
|
|
@@ -71,21 +73,25 @@ const RadioGroup = React.forwardRef(({ className, mode, variant: _chrome, value,
|
|
|
71
73
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
72
74
|
className
|
|
73
75
|
),
|
|
74
|
-
children: selectedValue ? /* @__PURE__ */ jsx("span", { className: "text-foreground", children: selectedLabel ?? selectedValue }) : /* @__PURE__ */ jsx("span", { className:
|
|
76
|
+
children: selectedValue ? /* @__PURE__ */ jsx("span", { className: "text-foreground", children: selectedLabel ?? selectedValue }) : /* @__PURE__ */ jsx("span", { className: fieldEmptyColorClass(resolvedMode), children: emptyDisplay })
|
|
75
77
|
}
|
|
76
78
|
);
|
|
77
79
|
}
|
|
78
|
-
return /* @__PURE__ */ jsx(RadioGroupReadonlyContext.Provider, { value: resolvedMode === "readonly", children: /* @__PURE__ */ jsx(
|
|
80
|
+
return /* @__PURE__ */ jsx(RadioGroupReadonlyContext.Provider, { value: resolvedMode === "readonly", children: /* @__PURE__ */ jsx(RadioGroupDisabledContext.Provider, { value: resolvedMode === "disabled", children: /* @__PURE__ */ jsx(
|
|
79
81
|
RadioGroupPrimitive.Root,
|
|
80
82
|
{
|
|
81
83
|
className: cn("grid", className),
|
|
82
84
|
value,
|
|
83
85
|
defaultValue,
|
|
86
|
+
"aria-labelledby": fieldCtx == null ? void 0 : fieldCtx.labelId,
|
|
87
|
+
"aria-invalid": (fieldCtx == null ? void 0 : fieldCtx.invalid) || void 0,
|
|
88
|
+
"aria-describedby": fieldCtx == null ? void 0 : fieldCtx.descriptionId,
|
|
89
|
+
"aria-readonly": resolvedMode === "readonly" || void 0,
|
|
84
90
|
...props,
|
|
85
91
|
disabled: resolvedMode === "disabled",
|
|
86
92
|
ref
|
|
87
93
|
}
|
|
88
|
-
) });
|
|
94
|
+
) }) });
|
|
89
95
|
});
|
|
90
96
|
RadioGroup.displayName = "RadioGroup";
|
|
91
97
|
RadioGroup.fieldLayout = "block";
|
|
@@ -133,7 +139,8 @@ const RadioGroupItem = React.forwardRef(
|
|
|
133
139
|
}, ref) => {
|
|
134
140
|
const sizeKey = size ?? "md";
|
|
135
141
|
const dotPx = dotSize[sizeKey];
|
|
136
|
-
const
|
|
142
|
+
const groupDisabled = React.useContext(RadioGroupDisabledContext);
|
|
143
|
+
const resolvedDisabled = useResolvedFieldDisabled(disabled) || groupDisabled;
|
|
137
144
|
const groupReadonly = React.useContext(RadioGroupReadonlyContext);
|
|
138
145
|
const effectiveReadonly = readOnly || groupReadonly;
|
|
139
146
|
const generatedId = React.useId();
|
|
@@ -144,7 +151,6 @@ const RadioGroupItem = React.forwardRef(
|
|
|
144
151
|
id: inputId,
|
|
145
152
|
ref,
|
|
146
153
|
disabled: resolvedDisabled,
|
|
147
|
-
"aria-readonly": effectiveReadonly || void 0,
|
|
148
154
|
"data-readonly": effectiveReadonly || void 0,
|
|
149
155
|
tabIndex: effectiveReadonly ? -1 : void 0,
|
|
150
156
|
className: cn(radioItemVariants({ size }), className),
|
|
@@ -190,7 +196,7 @@ const radioGroupMeta = {
|
|
|
190
196
|
variants: {},
|
|
191
197
|
sizes: {
|
|
192
198
|
// iconSize = 渲染指示器尺寸(對齊 checkbox/switch meta 慣例:checkbox iconSize=真 Check glyph 12/12/16)。
|
|
193
|
-
// Radio 指示器是 filled dot 非 glyph,真值 = dotSize 8/8/10
|
|
199
|
+
// Radio 指示器是 filled dot 非 glyph,真值 = 上方 `const dotSize` map 8/8/10;
|
|
194
200
|
// 控件框 16/16/20 與 Checkbox 對齊但那是 box 非「指示器」,不入此鍵(避免 metadata 語意 drift)。
|
|
195
201
|
sm: { fieldHeight: 28, iconSize: 8, typography: "body" },
|
|
196
202
|
md: { fieldHeight: 32, iconSize: 8, typography: "body" },
|