@qijenchen/design-system 0.1.0-beta.83 → 0.1.0-beta.85
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-group.d.ts +47 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.js +154 -0
- package/dist/components/DataTable/data-table-filter-group.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 +285 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +29 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js +187 -0
- package/dist/components/DataTable/data-table-filter-value-picker.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/Field/index.d.ts +1 -0
- package/dist/components/Field/index.d.ts.map +1 -1
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/index.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 +95 -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 +31 -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 +2 -1
- 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 +28 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +2 -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 +15 -0
- package/ds-canonical/fork/preamble.md +20 -18
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +1 -1
- 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 +2 -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_codex_brief_invariants.sh +28 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/hooks/check_datatable_invariants.sh +6 -3
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -1
- 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 +28 -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_ssot_header_declaration.sh +3 -1
- package/ds-canonical/hooks/check_story_determinism.sh +2 -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_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_opacity_token_usage.sh +39 -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/story-rules.md +2 -2
- package/ds-canonical/rules/ui-development.md +2 -2
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +3 -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 +51 -58
- 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 +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +26 -25
- 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 +1 -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 +5 -5
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
- 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 +2 -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 +13 -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-group.tsx +231 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +16 -430
- package/src/components/DataTable/data-table-filter-value-picker.tsx +237 -0
- 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 +33 -13
- package/src/components/DataTable/data-table.stories.tsx +16 -17
- package/src/components/DataTable/data-table.tsx +107 -61
- package/src/components/DataTable/filter-operators.spec.md +3 -3
- 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/Field/index.ts +1 -0
- 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 +5 -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 +29 -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 +588 -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 +4 -4
- 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.css +1 -1
- 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
package/CLAUDE.md
CHANGED
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
|
|
33
33
|
## 行數預算(Anthropic 對齊)
|
|
34
34
|
|
|
35
|
-
CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap 800。SKILL ≤ 250 / spec ≤ 300(foundational SSOT 例外 ≤ 800-1200)/ memory **per-file ≤ 100 lines** + **MEMORY.md index ≤ 20 entries**(soft 18 / hard 20,session-start hook 攔)。Hooks **26 soft / 60 hard**(SSOT = `session_start_governance_check.sh` Check 7 threshold logic,2026-05-27 升 50→55→60 per codex M31 P0 hooks + baseline + primitive-misuse 3 new hooks)。動態值見 `scripts/sync-governance-counters.mjs` 跑出為準(snapshot 2026-07-
|
|
35
|
+
CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap 800。SKILL ≤ 250 / spec ≤ 300(foundational SSOT 例外 ≤ 800-1200)/ memory **per-file ≤ 100 lines** + **MEMORY.md index ≤ 20 entries**(soft 18 / hard 20,session-start hook 攔)。Hooks **26 soft / 60 hard**(SSOT = `session_start_governance_check.sh` Check 7 threshold logic,2026-05-27 升 50→55→60 per codex M31 P0 hooks + baseline + primitive-misuse 3 new hooks)。動態值見 `scripts/sync-governance-counters.mjs` 跑出為準(snapshot 2026-07-10:**31 M-rules / 91 audit dims / 56 hooks** — 數字僅供 sanity check,真值以 script 輸出為準避 drift)。
|
|
36
36
|
|
|
37
37
|
## Anti-bloat L1-L3
|
|
38
38
|
|
|
39
39
|
- **L1 Pre-write**:`check_file_size_budget.sh`(+ governance hooks listed in `.claude/hooks/` — dynamic SSOT,不在 CLAUDE.md hardcode 個別 hook)
|
|
40
|
-
- **L2 Per-
|
|
40
|
+
- **L2 Per-edit**(PostToolUse Write|Edit|MultiEdit governance-file 寫入):`log_governance_fires.sh` → `.claude/logs/hook-fires.jsonl`
|
|
41
41
|
- **L3 Periodic**(季度 / `--deep`):`/knowledge-prune` skill,retire ≥ 5%
|
|
42
42
|
|
|
43
43
|
## 加規則前必過 3 題
|
|
@@ -65,7 +65,7 @@ CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap
|
|
|
65
65
|
| D5 視覺品質 | `/visual-audit`(Layer A mechanical + B AI) |
|
|
66
66
|
| D6 原則自檢 | `design-system-audit/references/principle-audit-protocol.md` |
|
|
67
67
|
|
|
68
|
-
**Consistency 類稽核必 Phase 0 全掃再判**(避免單元件看漏系統 drift)。
|
|
68
|
+
**Consistency 類稽核必 Phase 0 全掃再判**(避免單元件看漏系統 drift)。**Deep audit 全掃優先 + 決策 batch-at-end 鐵律**(2026-07-11 user directive;Claude + codex 雙方永遠遵守):先全 DS 掃完(全元件全 dim NO-SAMPLE)→ 跨元件去重 findings → **最後一次**列「真問題 + 只影響 SSOT-UI/UX」決策清單給 user 拍板;非-SSOT = Claude+codex 共識 autonomous 做完美;**禁**稽核途中一個元件一個元件問 user。SSOT → deep-audit-cross-codex SKILL Phase A「全掃優先」段。
|
|
69
69
|
|
|
70
70
|
**Audit-vs-execute 分權**:動 canonical substantive meaning → STOP 提議;對齊 / 表達統一 / 清 duplicate / 補 pointer → AUTO(對齊 knowledge-prune SKILL Phase 2 P0+P1 scope)。
|
|
71
71
|
|
|
@@ -73,7 +73,7 @@ CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap
|
|
|
73
73
|
|
|
74
74
|
# SSOT 消費 canonical
|
|
75
75
|
|
|
76
|
-
寫視覺 code 前必查對照 — 沒列 = 自創。**完整對照表 + 強制 checklist** → `.claude/references/ssot-consultation.md`(SSOT owner;含 9 項決策對應 SSOT + 新元件 tsx 開頭「── 消費的 SSOT ──」段強制要求)。原 `check_ssot_consultation.sh` 已 retired;2026-07-07 以 `check_ssot_header_declaration.sh` 精簡復活(新建 production tsx 必帶「── 消費的 SSOT ──」段,P0)+ mindset #2 + audit dim
|
|
76
|
+
寫視覺 code 前必查對照 — 沒列 = 自創。**完整對照表 + 強制 checklist** → `.claude/references/ssot-consultation.md`(SSOT owner;含 9 項決策對應 SSOT + 新元件 tsx 開頭「── 消費的 SSOT ──」段強制要求)。原 `check_ssot_consultation.sh` 已 retired;2026-07-07 以 `check_ssot_header_declaration.sh` 精簡復活(新建 production tsx 必帶「── 消費的 SSOT ──」段,P0)+ mindset #2 + audit dim 5 + dim 54 + check_canonical_propagation 接手。
|
|
77
77
|
|
|
78
78
|
# 任務導航表
|
|
79
79
|
|
|
@@ -142,7 +142,7 @@ CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap
|
|
|
142
142
|
|
|
143
143
|
| 動作類別 | 預設 |
|
|
144
144
|
|---------|-----|
|
|
145
|
-
| **SSOT-affecting UI/UX**(增刪改 component / token / spec 視覺結構 / 跨元件 design language) | **ASK** — 中文具體人話講選項 + tradeoff,user 拍板才動。**Enforcement scope split**:production code(`src/**/*.{tsx,ts,css}`)由 `check_substantive_edit_approval_preflight.sh` PreToolUse 攔截;`*.spec.md` 視覺結構 / canonical / SSOT 段落 edit 由 `stop_self_audit.sh` 補位 post-action 攔截(避免 spec typo false-positive BLOCKER)|
|
|
145
|
+
| **SSOT-affecting UI/UX**(增刪改 component / token / spec 視覺結構 / 跨元件 design language) | **ASK** — 中文具體人話講選項 + tradeoff,user 拍板才動。**Enforcement scope split**:production code(`src/**/*.{tsx,ts,css}`)由 `check_substantive_edit_approval_preflight.sh` PreToolUse 攔截;`*.spec.md` 視覺結構 / canonical / SSOT 段落 edit 由 `stop_self_audit.sh` 補位 post-action 攔截 — **僅覆蓋 codex-collab turn**(Mechanism 4 codex-reply branch;Claude-solo turn 靠本 canonical + M18 自律)(避免 spec typo false-positive BLOCKER)|
|
|
146
146
|
| Bug fix / clean / refactor / 命名一致 / test / audit / verify | **AUTO** — 整批做完 + 完整驗證 + 撤回機制(M33 反 defer) |
|
|
147
147
|
| Governance / hook / skill / spec **內部**(typo / pointer / 結構 — 不動 canonical meaning)| **AUTO** — audit-vs-execute 分權 |
|
|
148
148
|
| Perf / a11y / 漸進遷移(不動 SSOT)| **AUTO** — 整批 + verify |
|
|
@@ -154,8 +154,8 @@ CLAUDE.md target ≤ 200(Anthropic best-practice)/ transition ≤ 400 / hard cap
|
|
|
154
154
|
**Trigger phrase auto-pipeline**(M19 升級):「依原則自主」/「不需問」/「馬不停蹄」/「全部做完」/「自動」→ 進 autonomous mode,僅 SSOT-affecting UI/UX 停下 ASK。
|
|
155
155
|
|
|
156
156
|
**2026-05-23 永久 reinforcement(user verbatim,hook 機械強制)**:
|
|
157
|
-
- **Triple-verify before propose**(M18 Q0 universal gate):propose / 列 option / 發現「問題」(含 codex / deep audit findings)前必 inline 跑 (1) grep DS-wide (2) Read spec.md / tsx (3) 對照 canonical exception。三題全過才 propose;任一 NO → 自動撤回不煩 user。Hook `check_propose_pre_grep_verify.sh
|
|
158
|
-
- **SSOT auto-sync invariant**:M-rule count / hook count / dim count / npm scope / version / plugin name 等跨 file 數字禁 hardcode 多處;SSOT in `session_start_governance_check.sh` Check 7 / `meta-patterns.md` / `design-system-audit/SKILL.md` / `package.json` / `.claude-plugin/plugin.json`;其他 file reference 或 `scripts/sync-governance-counters.mjs` 機械對齊,drift 偵測
|
|
157
|
+
- **Triple-verify before propose**(M18 Q0 universal gate):propose / 列 option / 發現「問題」(含 codex / deep audit findings)前必 inline 跑 (1) grep DS-wide (2) Read spec.md / tsx (3) 對照 canonical exception。三題全過才 propose;任一 NO → 自動撤回不煩 user。Hook `check_propose_pre_grep_verify.sh` 僅對 planning/reports/handoff md 內 propose 文做 cite-presence P1 soft check;reply 通道靠 M18 mindset + `check_propose_without_benchmark.sh` UserPromptSubmit 提醒。Anchor:2026-05-18 Sheet/inline-action/SurfaceBody false positive、2026-05-23 Badge `text-[10px]` 誤判為 drift(badge.spec.md「字體例外」段 documented exception)
|
|
158
|
+
- **SSOT auto-sync invariant**:M-rule count / hook count / dim count / npm scope / version / plugin name 等跨 file 數字禁 hardcode 多處;SSOT in `session_start_governance_check.sh` Check 7 / `meta-patterns.md` / `design-system-audit/SKILL.md` / `package.json` / `.claude-plugin/plugin.json`;其他 file reference 或 `scripts/sync-governance-counters.mjs` 機械對齊,drift 偵測 fail-closed(release:preflight `--check` + session-start Check 10 inject 修復 directive)
|
|
159
159
|
|
|
160
160
|
# 遇不確定時的協議
|
|
161
161
|
|
|
@@ -197,4 +197,4 @@ Vite + React + TypeScript + Tailwind v4 + shadcn/ui + Storybook + 自訂 Design
|
|
|
197
197
|
- `.claude/rules/story-rules.md` — paths: `**/*.stories.tsx`(三層定位 + Title + 範例最高準則)
|
|
198
198
|
|
|
199
199
|
# 元件完成 + Exploration
|
|
200
|
-
merge 前 invoke `/component-quality-gate`(
|
|
200
|
+
merge 前 invoke `/component-quality-gate`(35 項 + visual + clean-code 三層)。正式 `packages/design-system/src/`(Phase 1 後 npm workspace 內化)vs 比稿 `src/explorations/`(hook `block_prototype_imports.py` 強制隔離);比稿 `*.v1.stories.tsx` + `notes.md`,定案升級 patterns/ 或 components/。Skills:`/prototype` / `/component-quality-gate` / `/delivery-handoff`。
|
package/README.md
CHANGED
|
@@ -115,10 +115,11 @@ Token `--font-sans` stack = `Roboto, -apple-system, BlinkMacSystemFont, "Segoe U
|
|
|
115
115
|
|
|
116
116
|
| 路徑 | 內容 |
|
|
117
117
|
|---|---|
|
|
118
|
-
| `@qijenchen/design-system` | Top barrel — components / patterns / hooks / lib utilities |
|
|
118
|
+
| `@qijenchen/design-system` | Top barrel — components / patterns / hooks / lib utilities / token JS mirrors。**策展後 front-door**(2026-07-14 API 策展):named re-exports,`*Meta`(componentMeta 建置/稽核 metadata)與 internal 單元不在此 |
|
|
119
|
+
| `@qijenchen/design-system/components/<Dir>`、`…/patterns/<dir>` | Per-component subpath — 完整 `export *`(含 `*Meta`;internal 單元「包裝後 + 自行確認」的唯一管道)|
|
|
119
120
|
| `@qijenchen/design-system/styles/tokens` | CSS aggregator(全 token + Tailwind `@theme inline`)|
|
|
120
121
|
| `@qijenchen/design-system/hooks/<name>` | 單一 hook subpath |
|
|
121
|
-
| `@qijenchen/design-system/tokens/<
|
|
122
|
+
| `@qijenchen/design-system/tokens/<path>` | Token JS 模組(編譯品;eg `tokens/motion/motion`、`tokens/uiSize/icon-size`)— 原始 `.ts` / `.spec.md` 不經此路徑曝露;CSS 透過 `styles/tokens` aggregator |
|
|
122
123
|
|
|
123
124
|
**禁** import:
|
|
124
125
|
- `@qijenchen/design-system/src/...`(internal source path,未來 SSOT 結構改會壞)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","sources":["../../../src/components/Alert/alert.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 { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport { Notice, useInverseTheme, SUBTLE_ICON_COLOR, type NoticeVariant } from '@/design-system/components/Notice/notice'\n\n/**\n * Alert — inline / fixed 通知\n *\n * ── data-theme 必須搭配 text-foreground ──\n * CSS color 從 body 繼承已解析值,data-theme 只改 --foreground 不改 color。\n * 在 theme boundary 設 text-foreground 強制 re-resolve。\n *\n * ── Appearance ──\n * subtle: 淺底色 + 四邊 1px border(色相
|
|
1
|
+
{"version":3,"file":"alert.js","sources":["../../../src/components/Alert/alert.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 { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport { Notice, useInverseTheme, SUBTLE_ICON_COLOR, type NoticeVariant } from '@/design-system/components/Notice/notice'\n\n/**\n * Alert — inline / fixed 通知\n *\n * ── data-theme 必須搭配 text-foreground ──\n * CSS color 從 body 繼承已解析值,data-theme 只改 --foreground 不改 color。\n * 在 theme boundary 設 text-foreground 強制 re-resolve。\n *\n * ── Appearance ──\n * subtle: 淺底色 + 四邊 1px border(色相 text 色 = icon 同色,見下方 SUBTLE_CONTAINER 註)。不設 data-theme,跟隨頁面。\n * solid: 跟 Toast 對齊:\n * neutral → data-theme={inverse} + bg-surface-raised(同層翻轉)\n * info/success/error → bg on outer, data-theme=\"dark\" on inner\n * warning → bg on outer, data-theme=\"light\" on inner\n *\n * ── Placement ──\n * inline: rounded-md(card-level 圓角,非 overlay — 因 Alert 在頁面流內,非 floating)\n * fixed: 無圓角,full-width,無 border\n */\n\n// border 用 `--{hue}-text`(= icon 同色,2026-06-15 user 拍板「直接共用 hue-text,不開新 token」)。\n// 原借 `--{hue}-hover`(互動狀態 token)當靜態 border 是語意 smell + 跟 icon 不同色;\n// 改共用 hue-text(SUBTLE_ICON_COLOR 用的同一顆,blue-7 等)→ 0 新 token + border/icon cohesion。\nconst SUBTLE_CONTAINER: Record<NoticeVariant, string> = {\n neutral: 'bg-muted border border-border',\n info: 'bg-info-subtle border border-[var(--info-text)]',\n success: 'bg-success-subtle border border-[var(--success-text)]',\n warning: 'bg-warning-subtle border border-[var(--warning-text)]',\n error: 'bg-error-subtle border border-[var(--error-text)]',\n}\n\nconst SOLID_HUE_BG: Record<string, string> = {\n info: 'bg-info',\n success: 'bg-success',\n warning: 'bg-warning',\n error: 'bg-error',\n}\n\nconst SOLID_HUE_THEME: Record<string, string> = {\n info: 'dark',\n success: 'dark',\n warning: 'light',\n error: 'dark',\n}\n\nconst alertVariants = cva('w-full overflow-hidden', {\n variants: {\n placement: {\n inline: 'rounded-md',\n fixed: 'rounded-none border-none',\n },\n },\n defaultVariants: { placement: 'inline' },\n})\n\nexport interface AlertProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>,\n VariantProps<typeof alertVariants> {\n variant?: NoticeVariant\n appearance?: 'subtle' | 'solid'\n title: React.ReactNode\n description?: React.ReactNode\n endContent?: React.ReactNode\n /**\n * 顯示關閉鈕(預設 true)。**需同時傳 `onDismiss` 才會渲染 X** — Alert 是 controlled-only\n * (不自我移除),無 handler 的 X 是死按鈕(2026-07-05 D4;對齊 Polaris Banner / Ant Alert)。\n * 條件判斷 SSOT 在 Notice(notice.tsx `showDismiss`),Alert 原樣 forward。\n */\n dismissible?: boolean\n onDismiss?: () => void\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n (\n {\n variant = 'neutral',\n appearance = 'subtle',\n placement,\n title,\n description,\n endContent,\n // 2026-07-05 D4:不 destructure-default(原 `= true` 會把「consumer 沒傳」materialize 成顯式\n // true,讓 Notice 的 dev-warn 無法分辨消費端矛盾)。等效預設 true 由 Notice showDismiss\n // `!== false` 承擔,行為不變;X 僅在同時傳 onDismiss 時渲染(SSOT 在 notice.tsx)。\n dismissible,\n onDismiss,\n className,\n ...props\n },\n ref,\n ) => {\n const inverseTheme = useInverseTheme()\n const isSolid = appearance === 'solid'\n const iconClassName = !isSolid ? SUBTLE_ICON_COLOR[variant] : undefined\n\n // ── Live region 由 wrapping consumer 擁有(WAI-ARIA + Atlassian / Polaris / Material 共識) ──\n // Alert 是 outer host —— 自己擁有 role + aria-live;Notice(inner layout)不再帶 role,\n // 避免 nested live region 造成 screen reader 重複朗讀。\n // - error / warning → role=\"alert\" + aria-live=\"assertive\"(中斷,使用者必須立刻知道)\n // - info / success / neutral → role=\"status\" + aria-live=\"polite\"(空閒朗讀,不打斷)\n const isCritical = variant === 'error' || variant === 'warning'\n const liveRole = isCritical ? 'alert' : 'status'\n const liveLevel = isCritical ? 'assertive' : 'polite'\n\n // placement=\"fixed\" 用 loose px(density-aware)讓 Alert 嵌在更大佈局內時跟周圍\n // loose-padding 元素(Toolbar / BulkActionBar / DataTable 等)左右對齊。\n // py 維持 py-3 fixed(notification banner family canonical,垂直不隨 density)。\n const noticeEl = (\n <Notice\n variant={variant}\n title={title}\n description={description}\n endContent={endContent}\n dismissible={dismissible}\n onDismiss={onDismiss}\n iconClassName={iconClassName}\n className={placement === 'fixed' ? 'px-[var(--layout-space-loose)]' : undefined}\n />\n )\n\n // ── Subtle ──\n if (!isSolid) {\n return (\n <div\n ref={ref}\n role={liveRole}\n aria-live={liveLevel}\n className={cn(alertVariants({ placement }), SUBTLE_CONTAINER[variant], className)}\n {...props}\n >\n {noticeEl}\n </div>\n )\n }\n\n // ── Solid neutral (inverse: bg + theme 同層) ──\n if (variant === 'neutral') {\n return (\n <div\n ref={ref}\n role={liveRole}\n aria-live={liveLevel}\n data-theme={inverseTheme}\n className={cn(alertVariants({ placement }), 'bg-surface-raised text-foreground', className)}\n {...props}\n >\n {noticeEl}\n </div>\n )\n }\n\n // ── Solid 有色相: bg outer + data-theme inner ──\n return (\n <div\n ref={ref}\n role={liveRole}\n aria-live={liveLevel}\n className={cn(alertVariants({ placement }), SOLID_HUE_BG[variant], className)}\n {...props}\n >\n <div data-theme={SOLID_HUE_THEME[variant]} className=\"text-foreground\">\n {noticeEl}\n </div>\n </div>\n )\n },\n)\nAlert.displayName = 'Alert'\n\n// Story auto-compile metadata — Phase 2 fill(2026-05-15)\n// Variants = NoticeVariant 5 hues(prop name `variant`,cva 內僅 placement,色相由 SUBTLE_CONTAINER / SOLID_HUE_BG map 控)\n// Sizes = none(Alert 視覺尺寸繼承 Notice primitive,不隨 size 變;見 spec「為何無 SizeMatrix」)\nexport const alertMeta = {\n component: 'Alert',\n family: 2, // List item(對齊 alert.spec.md frontmatter family: 2)\n variants: {\n neutral: { purpose: '中性提示(系統公告、非緊急說明);無情緒色' },\n info: { purpose: '資訊性提示(版本更新、流程說明);藍色 hue' },\n success: { purpose: '成功狀態的持久性宣告(綁定生效、付款完成需保留確認)' },\n warning: { purpose: '警告但非阻斷(方案到期、需更新付款方式);最高頻' },\n error: { purpose: '錯誤但非阻斷(系統錯誤可重試、API 失敗摘要);aria-live=assertive' },\n },\n sizes: {},\n states: ['default'], // 2026-06-11 R2:Alert 本體無互動 state(spec L183 明文無 disabled;dismiss 互動屬內部 Button),\n tokens: {\n bg: ['bg-error', 'bg-error-subtle', 'bg-info', 'bg-info-subtle', 'bg-muted', 'bg-success', 'bg-success-subtle', 'bg-surface-raised', 'bg-warning', 'bg-warning-subtle'],\n fg: ['text-foreground'],\n ring: [],\n },\n defaultVariant: 'neutral',\n} as const\n\nexport { Alert, alertVariants }\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_demo-helpers.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/_demo-helpers.tsx"],"names":[],"mappings":"AAmDA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;EAOX,CAAA;AAqBV,eAAO,MAAM,cAAc,+
|
|
1
|
+
{"version":3,"file":"_demo-helpers.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/_demo-helpers.tsx"],"names":[],"mappings":"AAmDA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;EAOX,CAAA;AAqBV,eAAO,MAAM,cAAc,+CAQ1B,CAAA;AAID,eAAO,MAAM,UAAU,+CAwBtB,CAAA;AAQD,wBAAgB,WAAW,CAAC,EAC1B,gBAAgB,EAChB,qBAA4B,EAC5B,iBAAwB,GACzB,GAAE;IACD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAA;CACvB,2CAwDL;AAWD,wBAAgB,WAAW,4CAyB1B;AAQD,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAAO,2CAY/E;AASD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,qBAA4B,GAC7B,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,2CAOA"}
|
|
@@ -15,7 +15,7 @@ type AppShellLayout = 'primary-sidebar' | 'primary-header';
|
|
|
15
15
|
export interface AppShellProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
16
|
/** primary-sidebar (Linear/Notion 派) | primary-header (GitHub/Slack 派);預設 primary-sidebar */
|
|
17
17
|
layout?: AppShellLayout;
|
|
18
|
-
/** Sidebar 元素(
|
|
18
|
+
/** Sidebar 元素(應傳 Sidebar primitive,per Consumer 紀律;型別 optional 不機械強制,對齊 spec「應」措辭)*/
|
|
19
19
|
sidebar?: React.ReactNode;
|
|
20
20
|
/**
|
|
21
21
|
* Local page header(永遠 render 在 main column 頂部,當前頁 actions / breadcrumb / page-level filter)。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/app-shell.tsx"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../../src/components/AppShell/app-shell.tsx"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,KAAK,cAAc,GAAG,iBAAiB,GAAG,gBAAgB,CAAA;AAE1D,MAAM,WAAW,aAAc,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACzE,6FAA6F;IAC7F,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,qFAAqF;IACrF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,mDAAmD;IACnD,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,iDAAiD;IACjD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,uBAAuB;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAID,UAAU,oBAAoB;IAC5B,MAAM,EAAE,cAAc,CAAA;IACtB,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,OAAO,CAAA;CAClB;AAID,iBAAS,WAAW,IAAI,oBAAoB,CAI3C;AA+DD,QAAA,MAAM,QAAQ,sFAiIb,CAAA;AAKD;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,aAAa,wFA6FlB,CAAA;AAQD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { X } from "lucide-react";
|
|
4
|
-
import { Sheet, SheetContent, SheetTitle } from "../Sheet/sheet.js";
|
|
4
|
+
import { Sheet, SheetContent, handleSheetOpenAutoFocus, SheetTitle } from "../Sheet/sheet.js";
|
|
5
5
|
import { Button } from "../Button/button.js";
|
|
6
6
|
import { ScrollArea } from "../ScrollArea/scroll-area.js";
|
|
7
7
|
import { ChromeHeader } from "../../patterns/header-canonical/chrome-header.js";
|
|
@@ -161,6 +161,7 @@ const AppShellAside = React.forwardRef(
|
|
|
161
161
|
const { asideOpen, setAsideOpen, isMobile } = useAppShell();
|
|
162
162
|
const isXl = useIsXl();
|
|
163
163
|
const resolvedWidth = resolveAsideWidth(width, isXl);
|
|
164
|
+
const asideOpenerRef = React.useRef(null);
|
|
164
165
|
const renderFrame = (titleNode) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
165
166
|
/* @__PURE__ */ jsxs(ChromeHeader, { children: [
|
|
166
167
|
titleNode,
|
|
@@ -182,8 +183,27 @@ const AppShellAside = React.forwardRef(
|
|
|
182
183
|
return /* @__PURE__ */ jsx(Sheet, { open: asideOpen, onOpenChange: setAsideOpen, children: /* @__PURE__ */ jsx(
|
|
183
184
|
SheetContent,
|
|
184
185
|
{
|
|
186
|
+
ref,
|
|
185
187
|
side: "right",
|
|
186
|
-
|
|
188
|
+
onOpenAutoFocus: (e) => {
|
|
189
|
+
asideOpenerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
190
|
+
handleSheetOpenAutoFocus(e);
|
|
191
|
+
},
|
|
192
|
+
onCloseAutoFocus: (e) => {
|
|
193
|
+
var _a;
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
const opener = asideOpenerRef.current;
|
|
196
|
+
asideOpenerRef.current = null;
|
|
197
|
+
if (opener == null ? void 0 : opener.isConnected) {
|
|
198
|
+
opener.focus({ preventScroll: true });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
(_a = document.getElementById("app-shell-main")) == null ? void 0 : _a.focus({ preventScroll: true });
|
|
202
|
+
},
|
|
203
|
+
className: cn(
|
|
204
|
+
"w-[min(90vw,var(--app-shell-aside-modal-width))] flex flex-col p-0 [&>button]:hidden",
|
|
205
|
+
className
|
|
206
|
+
),
|
|
187
207
|
style: { ["--app-shell-aside-modal-width"]: `${resolvedWidth}px` },
|
|
188
208
|
children: renderFrame(/* @__PURE__ */ jsx(SheetTitle, { className: "flex-1", children: title }))
|
|
189
209
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-shell.js","sources":["../../../src/components/AppShell/app-shell.tsx"],"sourcesContent":["// @benchmark-cited: 2026-05-19 — Mantine AppShell / Ant Layout / Material 3 Drawer / Atlassian Navigation System cite in app-shell.spec.md frontmatter.\n/**\n * AppShell — web service page-level layout primitive。\n *\n * 組合 Sidebar + ChromeHeader + Aside + main 成完整 page shell。SSOT 邊界:本 composite component only\n * own slot composition + layout mode + Aside responsive mode;不 own sidebar / header /\n * sheet 視覺(各自 spec own)。\n *\n * 對齊 Mantine AppShell compound API + Ant Layout slot 模式 + Material 3 standard/modal\n * drawer canonical(per spec.md frontmatter cite)。\n *\n * Spec SSOT:`components/AppShell/app-shell.spec.md`\n */\n\nimport * as React from 'react'\nimport { X as XIcon } from 'lucide-react'\nimport {\n Sheet,\n SheetContent,\n SheetTitle,\n} from '@/design-system/components/Sheet/sheet'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ScrollArea } from '@/design-system/components/ScrollArea/scroll-area'\nimport { ChromeHeader } from '@/design-system/patterns/header-canonical/chrome-header'\nimport { useIsNarrowViewport } from '@/design-system/hooks/use-is-narrow-viewport'\nimport { cn } from '@/lib/utils'\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\ntype AppShellLayout = 'primary-sidebar' | 'primary-header'\n\nexport interface AppShellProps extends React.HTMLAttributes<HTMLDivElement> {\n /** primary-sidebar (Linear/Notion 派) | primary-header (GitHub/Slack 派);預設 primary-sidebar */\n layout?: AppShellLayout\n /** Sidebar 元素(必傳 Sidebar primitive,per Consumer 紀律)*/\n sidebar?: React.ReactNode\n /**\n * Local page header(永遠 render 在 main column 頂部,當前頁 actions / breadcrumb / page-level filter)。\n * 兩 layout mode 都會 render 此 slot — `primary-header` mode 多了 globalHeader 在上方,\n * **不取代** local header(per 2026-05-20 user clarification「primary-header = primary-sidebar + 一條 global header」)。\n */\n header?: React.ReactNode\n /**\n * Global header(僅 `primary-header` mode render)。橫跨整 viewport 的頂部 bar:\n * account avatar / workspace switcher / global search / notifications。\n * 對齊 GitHub top nav / Slack workspace bar / Gmail logo bar 慣例。\n * `primary-sidebar` mode 傳此 prop 會被忽略。\n */\n globalHeader?: React.ReactNode\n /** Aside 元素(`<AppShellAside>` sub-component);可選 */\n aside?: React.ReactNode\n /** Aside open state(modal mode 必須)*/\n asideOpen?: boolean\n onAsideOpenChange?: (open: boolean) => void\n /** Main content;`<main>` landmark + padding=0 */\n children: React.ReactNode\n}\n\nexport interface AppShellAsideProps {\n /** Required:modal mode 走 Sheet → aria-labelledby 強制,per sheet.spec.md:98 */\n title: string\n /** Width(number 或 breakpoint-keyed object);clamp min:240 max:640 */\n width?: number | { md?: number; xl?: number }\n /** Children content */\n children: React.ReactNode\n className?: string\n}\n\n// ── Context ──────────────────────────────────────────────────────────────────\n\ninterface AppShellContextValue {\n layout: AppShellLayout\n asideOpen: boolean\n setAsideOpen: (open: boolean) => void\n isMobile: boolean\n}\n\nconst AppShellContext = React.createContext<AppShellContextValue | null>(null)\n\nfunction useAppShell(): AppShellContextValue {\n const ctx = React.useContext(AppShellContext)\n if (!ctx) throw new Error('AppShellAside must be used within <AppShell>')\n return ctx\n}\n\n// Mobile breakpoint:**消費既有 `useIsNarrowViewport`**(`hooks/use-is-narrow-viewport.ts` SSOT,\n// 768px,跟 Sidebar SSOT 同源)— 不發明 local hook,per codex Layer B D2/D4 verdict 避 drift。\n\n// xl breakpoint(對齊 Tailwind v4 xl = 1280px,DS-wide consensus)\nconst XL_BREAKPOINT_PX = 1280\n\nfunction useIsXl(): boolean {\n const [isXl, setIsXl] = React.useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(`(min-width: ${XL_BREAKPOINT_PX}px)`).matches\n })\n\n React.useEffect(() => {\n const mq = window.matchMedia(`(min-width: ${XL_BREAKPOINT_PX}px)`)\n const handler = (e: MediaQueryListEvent) => setIsXl(e.matches)\n mq.addEventListener('change', handler)\n return () => mq.removeEventListener('change', handler)\n }, [])\n\n return isXl\n}\n\n// ── Width resolve(consumer 自傳 + clamp 240-640)──────────────────────────────\n\nconst ASIDE_WIDTH_MIN = 240\nconst ASIDE_WIDTH_MAX = 640\nconst ASIDE_WIDTH_DEFAULT = 320\n\nfunction resolveAsideWidth(width: AppShellAsideProps['width'], isXl: boolean): number {\n if (typeof width === 'number') {\n return Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, width))\n }\n if (width && typeof width === 'object') {\n // breakpoint-keyed:xl viewport(≥1280px)用 xl,否則 md\n const v = (isXl ? width.xl ?? width.md : width.md) ?? ASIDE_WIDTH_DEFAULT\n return Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, v))\n }\n return ASIDE_WIDTH_DEFAULT\n}\n\n// ── Skip-to-main link(a11y WCAG 2.4.1)───────────────────────────────────────\n\nfunction SkipToMain() {\n return (\n <a\n href=\"#app-shell-main\"\n className={cn(\n 'sr-only focus:not-sr-only',\n 'focus:fixed focus:top-2 focus:left-2 focus:z-50',\n 'focus:px-3 focus:py-2 focus:rounded-md',\n 'focus:bg-surface focus:text-foreground focus:shadow-[var(--elevation-200)]',\n 'focus:outline-none focus:ring-2 focus:ring-primary'\n )}\n >\n Skip to main content\n </a>\n )\n}\n\n// ── AppShell root ────────────────────────────────────────────────────────────\n\nconst AppShell = React.forwardRef<HTMLDivElement, AppShellProps>(\n (\n {\n layout = 'primary-sidebar',\n sidebar,\n header,\n globalHeader,\n aside,\n asideOpen: asideOpenProp,\n onAsideOpenChange,\n children,\n className,\n ...props\n },\n ref\n ) => {\n const [asideOpenInternal, setAsideOpenInternal] = React.useState(false)\n const isControlled = asideOpenProp !== undefined\n const asideOpen = isControlled ? asideOpenProp : asideOpenInternal\n\n const setAsideOpen = React.useCallback(\n (open: boolean) => {\n if (!isControlled) setAsideOpenInternal(open)\n onAsideOpenChange?.(open)\n },\n [isControlled, onAsideOpenChange]\n )\n\n const isMobile = useIsNarrowViewport()\n\n // ── Keyboard: cmd+. toggle aside ──\n // ⌘B sidebar toggle by Sidebar SSOT(本 component 不重覆 register)\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === '.' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n setAsideOpen(!asideOpen)\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [asideOpen, setAsideOpen])\n\n const ctxValue = React.useMemo<AppShellContextValue>(\n () => ({ layout, asideOpen, setAsideOpen, isMobile }),\n [layout, asideOpen, setAsideOpen, isMobile]\n )\n\n // ── Layout grid(2 mode)──\n // primary-sidebar:\n // row1: [sidebar (頂天)][main col (header + main)][aside (頂天)]\n // primary-header:\n // row1: [header (橫跨整 viewport, banner role)]\n // row2: [sidebar][main][aside]\n\n // AppShellAside 自決 inline vs modal mode(via AppShellContext.isMobile)。\n // AppShell 一律只 render `{aside}` 一次,AppShellAside 內部根據 isMobile 決定 render 形式。\n\n if (layout === 'primary-header') {\n // primary-header layout(2026-05-21 v2 — user clarification「primary-header = primary-sidebar + 一條 global header」):\n // 結構:row1 globalHeader(全寬 global bar)/ row2 [sidebar][main col: localHeader + main][aside]\n // - globalHeader = 跨頁 account / workspace switcher / notifications(對齊 GitHub top nav / Slack workspace bar)\n // - header = local page header,**仍存在**(per page actions / breadcrumb,對齊 GitHub repo header / Slack channel header / Gmail email-list toolbar 2-layer 慣例)\n // Consumer 必傳 `<Sidebar viewportInsetTop=\"var(--chrome-header-height)\">` 讓 sidebar 從 globalHeader 下方起算。\n return (\n <AppShellContext.Provider value={ctxValue}>\n <div\n ref={ref}\n className={cn('flex h-svh w-full flex-col overflow-hidden bg-canvas', className)}\n {...props}\n >\n <SkipToMain />\n {/* Row 1:Global header(account / workspace switcher / notifications,橫跨整 viewport)*/}\n {globalHeader && <div className=\"flex-shrink-0\">{globalHeader}</div>}\n {/* Row 2:[sidebar][main col][aside]horizontal row */}\n <div className=\"flex flex-1 min-h-0 w-full\">\n {sidebar}\n {/* Main column:local header + main content(per user model「primary-header = primary-sidebar + global header」)*/}\n <div className=\"flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden\">\n {header && <div className=\"flex-shrink-0\">{header}</div>}\n <main\n id=\"app-shell-main\"\n tabIndex={-1}\n className=\"flex-1 min-w-0 min-h-0 overflow-y-auto focus:outline-none\"\n >\n {children}\n </main>\n </div>\n {aside}\n </div>\n </div>\n </AppShellContext.Provider>\n )\n }\n\n // primary-sidebar layout\n return (\n <AppShellContext.Provider value={ctxValue}>\n <div\n ref={ref}\n className={cn('flex h-svh w-full overflow-hidden bg-canvas', className)}\n {...props}\n >\n <SkipToMain />\n {/* Sidebar — 頂天 */}\n {sidebar}\n {/* Main column(header + main 垂直堆)*/}\n <div className=\"flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden\">\n {header && (\n // Header 在 main column 內(main col sibling,非 main descendant)。ChromeHeader 自\n // 2026-05-20 起 render `<header>`(chrome-header.tsx);依 W3C HTML-AAM,`<div>` wrapper\n // 不 scope out banner(只有 main/article/aside/nav/section 能)→ 仍得 implicit banner ✓\n // (對齊 app-shell.spec.md「W3C ARIA in HTML banner rule」段)。\n <div className=\"flex-shrink-0\">{header}</div>\n )}\n <main\n id=\"app-shell-main\"\n tabIndex={-1}\n className=\"flex-1 min-h-0 overflow-y-auto focus:outline-none\"\n >\n {children}\n </main>\n </div>\n {/* Aside slot — desktop inline OR mobile Sheet,內部自決 */}\n {aside}\n </div>\n </AppShellContext.Provider>\n )\n }\n)\nAppShell.displayName = 'AppShell'\n\n// ── AppShellAside sub-component ──────────────────────────────────────────────\n\n/**\n * AppShellAside — right panel:standard inline(desktop) vs modal overlay(mobile)。\n *\n * Desktop(viewport ≥ 768px):\n * - Render 直接放 layout grid 右側(AppShell 恆 render {aside};AppShellAside 於 !asideOpen 自行 return null — 內部自決,非 via parent)\n * - 不蓋 mask / background 可操作 / 佔 layout 寬\n * - Vertical extent:primary-sidebar → 頂天立地 / primary-header → header 下方\n *\n * Mobile(viewport < 768px):\n * - Render 走 Sheet primitive(side=\"right\",per sheet.spec.md)\n * - Mask 蓋 / background 不可操作 / 不佔 layout 寬\n * - title 強制(aria-labelledby per sheet.spec.md:98)\n */\nconst AppShellAside = React.forwardRef<HTMLElement, AppShellAsideProps>(\n ({ title, width, children, className }, ref) => {\n const { asideOpen, setAsideOpen, isMobile } = useAppShell()\n const isXl = useIsXl()\n const resolvedWidth = resolveAsideWidth(width, isXl)\n\n // Shared frame:always-on header(title + close X)+ body(ScrollArea + layoutSpace 規則 1B 父層 padding)\n // 對齊 codex Layer B 2026-05-20「container mode 可變,panel role/content 不該變」+ Notion/Figma right\n // panel 共識(modal vs inline 結構相同,host wrapper 不同)。\n // 2026-05-20 migrate 消費 ChromeHeader primitive(撤回自刻 + 撤回 bg-surface 疊加 — 對齊\n // `header-canonical.spec.md`「6. Background ownership」段「Nested chrome header 透明繼承\n // parent」:aside container 自身已 bg-surface,內 header 不該再畫 bg 避免疊加 drift)。\n // titleNode 依 mode 不同:modal(Sheet)用 SheetTitle 綁 Radix Dialog titleId — 否則\n // SheetContent 的 aria-labelledby 指向不存在的 id → SR 念「unnamed dialog」+ Radix\n // TitleWarning console.error(違 sheet.spec.md:98「Sheet content 必有可見 title」)。\n // inline(aside)用純 <h2>:aside 自身已 aria-label={title},且無 Dialog context 不可用 SheetTitle。\n const renderFrame = (titleNode: React.ReactNode) => (\n <>\n <ChromeHeader>\n {titleNode}\n <Button\n iconOnly\n dismiss\n size=\"sm\"\n startIcon={XIcon}\n aria-label=\"關閉\"\n onClick={() => setAsideOpen(false)}\n />\n </ChromeHeader>\n <ScrollArea className=\"flex-1 min-h-0\">\n {children}\n </ScrollArea>\n </>\n )\n\n // Modal mode(mobile)— Sheet from right\n if (isMobile) {\n return (\n <Sheet open={asideOpen} onOpenChange={setAsideOpen}>\n <SheetContent\n side=\"right\"\n className=\"w-[min(90vw,var(--app-shell-aside-modal-width))] flex flex-col p-0 [&>button]:hidden\"\n style={{ ['--app-shell-aside-modal-width' as string]: `${resolvedWidth}px` }}\n >\n {renderFrame(<SheetTitle className=\"flex-1\">{title}</SheetTitle>)}\n </SheetContent>\n </Sheet>\n )\n }\n\n // Standard inline mode(desktop)\n if (!asideOpen) return null\n\n return (\n <aside\n ref={ref}\n aria-label={title}\n className={cn(\n 'flex flex-col h-full min-h-0 overflow-hidden',\n 'bg-surface border-l border-divider',\n className\n )}\n style={{ width: resolvedWidth }}\n >\n {renderFrame(<h2 className=\"text-body-lg font-medium flex-1 truncate\">{title}</h2>)}\n </aside>\n )\n }\n)\nAppShellAside.displayName = 'AppShellAside'\n\n// ── Exports ──────────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export useAppShell — public compound API hook(consumer 可自拼 custom aside layout,\n// 對齊 Radix `useDialogContext` / MUI `useFormControl` 慣例)。內部 AppShellAside 已消費(L294),\n// audit script 抓「無 cross-file import」是 false positive(2026-05-21 D2 codify)。\nexport { AppShell, AppShellAside, useAppShell }\n"],"names":["XIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-shell.js","sources":["../../../src/components/AppShell/app-shell.tsx"],"sourcesContent":["// @benchmark-cited: 2026-05-19 — Mantine AppShell / Ant Layout / Material 3 Drawer / Atlassian Navigation System cite in app-shell.spec.md frontmatter.\n/**\n * AppShell — web service page-level layout primitive。\n *\n * 組合 Sidebar + ChromeHeader + Aside + main 成完整 page shell。SSOT 邊界:本 composite component only\n * own slot composition + layout mode + Aside responsive mode;不 own sidebar / header /\n * sheet 視覺(各自 spec own)。\n *\n * 對齊 Mantine AppShell compound API + Ant Layout slot 模式 + Material 3 standard/modal\n * drawer canonical(per spec.md frontmatter cite)。\n *\n * Spec SSOT:`components/AppShell/app-shell.spec.md`\n */\n\nimport * as React from 'react'\nimport { X as XIcon } from 'lucide-react'\nimport {\n Sheet,\n SheetContent,\n SheetTitle,\n handleSheetOpenAutoFocus,\n} from '@/design-system/components/Sheet/sheet'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ScrollArea } from '@/design-system/components/ScrollArea/scroll-area'\nimport { ChromeHeader } from '@/design-system/patterns/header-canonical/chrome-header'\nimport { useIsNarrowViewport } from '@/design-system/hooks/use-is-narrow-viewport'\nimport { cn } from '@/lib/utils'\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\ntype AppShellLayout = 'primary-sidebar' | 'primary-header'\n\nexport interface AppShellProps extends React.HTMLAttributes<HTMLDivElement> {\n /** primary-sidebar (Linear/Notion 派) | primary-header (GitHub/Slack 派);預設 primary-sidebar */\n layout?: AppShellLayout\n /** Sidebar 元素(應傳 Sidebar primitive,per Consumer 紀律;型別 optional 不機械強制,對齊 spec「應」措辭)*/\n sidebar?: React.ReactNode\n /**\n * Local page header(永遠 render 在 main column 頂部,當前頁 actions / breadcrumb / page-level filter)。\n * 兩 layout mode 都會 render 此 slot — `primary-header` mode 多了 globalHeader 在上方,\n * **不取代** local header(per 2026-05-20 user clarification「primary-header = primary-sidebar + 一條 global header」)。\n */\n header?: React.ReactNode\n /**\n * Global header(僅 `primary-header` mode render)。橫跨整 viewport 的頂部 bar:\n * account avatar / workspace switcher / global search / notifications。\n * 對齊 GitHub top nav / Slack workspace bar / Gmail logo bar 慣例。\n * `primary-sidebar` mode 傳此 prop 會被忽略。\n */\n globalHeader?: React.ReactNode\n /** Aside 元素(`<AppShellAside>` sub-component);可選 */\n aside?: React.ReactNode\n /** Aside open state(modal mode 必須)*/\n asideOpen?: boolean\n onAsideOpenChange?: (open: boolean) => void\n /** Main content;`<main>` landmark + padding=0 */\n children: React.ReactNode\n}\n\nexport interface AppShellAsideProps {\n /** Required:modal mode 走 Sheet → aria-labelledby 強制,per sheet.spec.md:98 */\n title: string\n /** Width(number 或 breakpoint-keyed object);clamp min:240 max:640 */\n width?: number | { md?: number; xl?: number }\n /** Children content */\n children: React.ReactNode\n className?: string\n}\n\n// ── Context ──────────────────────────────────────────────────────────────────\n\ninterface AppShellContextValue {\n layout: AppShellLayout\n asideOpen: boolean\n setAsideOpen: (open: boolean) => void\n isMobile: boolean\n}\n\nconst AppShellContext = React.createContext<AppShellContextValue | null>(null)\n\nfunction useAppShell(): AppShellContextValue {\n const ctx = React.useContext(AppShellContext)\n if (!ctx) throw new Error('AppShellAside must be used within <AppShell>')\n return ctx\n}\n\n// Mobile breakpoint:**消費既有 `useIsNarrowViewport`**(`hooks/use-is-narrow-viewport.ts` SSOT,\n// 768px,跟 Sidebar SSOT 同源)— 不發明 local hook,per codex Layer B D2/D4 verdict 避 drift。\n\n// xl breakpoint(對齊 Tailwind v4 xl = 1280px,DS-wide consensus)\nconst XL_BREAKPOINT_PX = 1280\n\nfunction useIsXl(): boolean {\n const [isXl, setIsXl] = React.useState<boolean>(() => {\n if (typeof window === 'undefined') return false\n return window.matchMedia(`(min-width: ${XL_BREAKPOINT_PX}px)`).matches\n })\n\n React.useEffect(() => {\n const mq = window.matchMedia(`(min-width: ${XL_BREAKPOINT_PX}px)`)\n const handler = (e: MediaQueryListEvent) => setIsXl(e.matches)\n mq.addEventListener('change', handler)\n return () => mq.removeEventListener('change', handler)\n }, [])\n\n return isXl\n}\n\n// ── Width resolve(consumer 自傳 + clamp 240-640)──────────────────────────────\n\nconst ASIDE_WIDTH_MIN = 240\nconst ASIDE_WIDTH_MAX = 640\nconst ASIDE_WIDTH_DEFAULT = 320\n\nfunction resolveAsideWidth(width: AppShellAsideProps['width'], isXl: boolean): number {\n if (typeof width === 'number') {\n return Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, width))\n }\n if (width && typeof width === 'object') {\n // breakpoint-keyed:xl viewport(≥1280px)用 xl,否則 md\n const v = (isXl ? width.xl ?? width.md : width.md) ?? ASIDE_WIDTH_DEFAULT\n return Math.max(ASIDE_WIDTH_MIN, Math.min(ASIDE_WIDTH_MAX, v))\n }\n return ASIDE_WIDTH_DEFAULT\n}\n\n// ── Skip-to-main link(a11y WCAG 2.4.1)───────────────────────────────────────\n\nfunction SkipToMain() {\n return (\n <a\n href=\"#app-shell-main\"\n className={cn(\n 'sr-only focus:not-sr-only',\n 'focus:fixed focus:top-2 focus:left-2 focus:z-50',\n 'focus:px-3 focus:py-2 focus:rounded-md',\n 'focus:bg-surface focus:text-foreground focus:shadow-[var(--elevation-200)]',\n 'focus:outline-none focus:ring-2 focus:ring-primary'\n )}\n >\n Skip to main content\n </a>\n )\n}\n\n// ── AppShell root ────────────────────────────────────────────────────────────\n\nconst AppShell = React.forwardRef<HTMLDivElement, AppShellProps>(\n (\n {\n layout = 'primary-sidebar',\n sidebar,\n header,\n globalHeader,\n aside,\n asideOpen: asideOpenProp,\n onAsideOpenChange,\n children,\n className,\n ...props\n },\n ref\n ) => {\n const [asideOpenInternal, setAsideOpenInternal] = React.useState(false)\n const isControlled = asideOpenProp !== undefined\n const asideOpen = isControlled ? asideOpenProp : asideOpenInternal\n\n const setAsideOpen = React.useCallback(\n (open: boolean) => {\n if (!isControlled) setAsideOpenInternal(open)\n onAsideOpenChange?.(open)\n },\n [isControlled, onAsideOpenChange]\n )\n\n const isMobile = useIsNarrowViewport()\n\n // ── Keyboard: cmd+. toggle aside ──\n // ⌘B sidebar toggle by Sidebar SSOT(本 component 不重覆 register)\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === '.' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n setAsideOpen(!asideOpen)\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [asideOpen, setAsideOpen])\n\n const ctxValue = React.useMemo<AppShellContextValue>(\n () => ({ layout, asideOpen, setAsideOpen, isMobile }),\n [layout, asideOpen, setAsideOpen, isMobile]\n )\n\n // ── Layout grid(2 mode)──\n // primary-sidebar:\n // row1: [sidebar (頂天)][main col (header + main)][aside (頂天)]\n // primary-header:\n // row1: [header (橫跨整 viewport, banner role)]\n // row2: [sidebar][main][aside]\n\n // AppShellAside 自決 inline vs modal mode(via AppShellContext.isMobile)。\n // AppShell 一律只 render `{aside}` 一次,AppShellAside 內部根據 isMobile 決定 render 形式。\n\n if (layout === 'primary-header') {\n // primary-header layout(2026-05-21 v2 — user clarification「primary-header = primary-sidebar + 一條 global header」):\n // 結構:row1 globalHeader(全寬 global bar)/ row2 [sidebar][main col: localHeader + main][aside]\n // - globalHeader = 跨頁 account / workspace switcher / notifications(對齊 GitHub top nav / Slack workspace bar)\n // - header = local page header,**仍存在**(per page actions / breadcrumb,對齊 GitHub repo header / Slack channel header / Gmail email-list toolbar 2-layer 慣例)\n // Consumer 必傳 `<Sidebar viewportInsetTop=\"var(--chrome-header-height)\">` 讓 sidebar 從 globalHeader 下方起算。\n return (\n <AppShellContext.Provider value={ctxValue}>\n <div\n ref={ref}\n className={cn('flex h-svh w-full flex-col overflow-hidden bg-canvas', className)}\n {...props}\n >\n <SkipToMain />\n {/* Row 1:Global header(account / workspace switcher / notifications,橫跨整 viewport)*/}\n {globalHeader && <div className=\"flex-shrink-0\">{globalHeader}</div>}\n {/* Row 2:[sidebar][main col][aside]horizontal row */}\n <div className=\"flex flex-1 min-h-0 w-full\">\n {sidebar}\n {/* Main column:local header + main content(per user model「primary-header = primary-sidebar + global header」)*/}\n <div className=\"flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden\">\n {header && <div className=\"flex-shrink-0\">{header}</div>}\n <main\n id=\"app-shell-main\"\n tabIndex={-1}\n className=\"flex-1 min-w-0 min-h-0 overflow-y-auto focus:outline-none\"\n >\n {children}\n </main>\n </div>\n {aside}\n </div>\n </div>\n </AppShellContext.Provider>\n )\n }\n\n // primary-sidebar layout\n return (\n <AppShellContext.Provider value={ctxValue}>\n <div\n ref={ref}\n className={cn('flex h-svh w-full overflow-hidden bg-canvas', className)}\n {...props}\n >\n <SkipToMain />\n {/* Sidebar — 頂天 */}\n {sidebar}\n {/* Main column(header + main 垂直堆)*/}\n <div className=\"flex flex-col flex-1 min-w-0 min-h-0 overflow-hidden\">\n {header && (\n // Header 在 main column 內(main col sibling,非 main descendant)。ChromeHeader 自\n // 2026-05-20 起 render `<header>`(chrome-header.tsx);依 W3C HTML-AAM,`<div>` wrapper\n // 不 scope out banner(只有 main/article/aside/nav/section 能)→ 仍得 implicit banner ✓\n // (對齊 app-shell.spec.md「W3C ARIA in HTML banner rule」段)。\n <div className=\"flex-shrink-0\">{header}</div>\n )}\n <main\n id=\"app-shell-main\"\n tabIndex={-1}\n className=\"flex-1 min-h-0 overflow-y-auto focus:outline-none\"\n >\n {children}\n </main>\n </div>\n {/* Aside slot — desktop inline OR mobile Sheet,內部自決 */}\n {aside}\n </div>\n </AppShellContext.Provider>\n )\n }\n)\nAppShell.displayName = 'AppShell'\n\n// ── AppShellAside sub-component ──────────────────────────────────────────────\n\n/**\n * AppShellAside — right panel:standard inline(desktop) vs modal overlay(mobile)。\n *\n * Desktop(viewport ≥ 768px):\n * - Render 直接放 layout grid 右側(AppShell 恆 render {aside};AppShellAside 於 !asideOpen 自行 return null — 內部自決,非 via parent)\n * - 不蓋 mask / background 可操作 / 佔 layout 寬\n * - Vertical extent:primary-sidebar → 頂天立地 / primary-header → header 下方\n *\n * Mobile(viewport < 768px):\n * - Render 走 Sheet primitive(side=\"right\",per sheet.spec.md)\n * - Mask 蓋 / background 不可操作 / 不佔 layout 寬\n * - title 強制(aria-labelledby per sheet.spec.md:98)\n */\nconst AppShellAside = React.forwardRef<HTMLElement, AppShellAsideProps>(\n ({ title, width, children, className }, ref) => {\n const { asideOpen, setAsideOpen, isMobile } = useAppShell()\n const isXl = useIsXl()\n const resolvedWidth = resolveAsideWidth(width, isXl)\n\n // Shared frame:always-on header(title + close X)+ body(ScrollArea + layoutSpace 規則 1B 父層 padding)\n // 對齊 codex Layer B 2026-05-20「container mode 可變,panel role/content 不該變」+ Notion/Figma right\n // panel 共識(modal vs inline 結構相同,host wrapper 不同)。\n // 2026-05-20 migrate 消費 ChromeHeader primitive(撤回自刻 + 撤回 bg-surface 疊加 — 對齊\n // `header-canonical.spec.md`「6. Background ownership」段「Nested chrome header 透明繼承\n // parent」:aside container 自身已 bg-surface,內 header 不該再畫 bg 避免疊加 drift)。\n // titleNode 依 mode 不同:modal(Sheet)用 SheetTitle 綁 Radix Dialog titleId — 否則\n // SheetContent 的 aria-labelledby 指向不存在的 id → SR 念「unnamed dialog」+ Radix\n // TitleWarning console.error(違 sheet.spec.md:98「Sheet content 必有可見 title」)。\n // inline(aside)用純 <h2>:aside 自身已 aria-label={title},且無 Dialog context 不可用 SheetTitle。\n // R1:mobile Sheet 關閉後焦點還原目標(open 時 snapshot)\n const asideOpenerRef = React.useRef<HTMLElement | null>(null)\n\n const renderFrame = (titleNode: React.ReactNode) => (\n <>\n <ChromeHeader>\n {titleNode}\n <Button\n iconOnly\n dismiss\n size=\"sm\"\n startIcon={XIcon}\n aria-label=\"關閉\"\n onClick={() => setAsideOpen(false)}\n />\n </ChromeHeader>\n <ScrollArea className=\"flex-1 min-h-0\">\n {children}\n </ScrollArea>\n </>\n )\n\n // Modal mode(mobile)— Sheet from right\n // 2026-07-14 dim-9 修:同步轉交 forwarded ref + 合併 consumer className —— 原 mobile 分支\n // 兩者皆遺失,同一 API(ref / className)隨 breakpoint 靜默失效(desktop 有效、mobile 無效)。\n if (isMobile) {\n return (\n <Sheet open={asideOpen} onOpenChange={setAsideOpen}>\n <SheetContent\n ref={ref as React.Ref<HTMLDivElement>}\n side=\"right\"\n // R1 WCAG 2.4.3(2026-07-14,Claude+codex A′ 共識):controlled Sheet 無 SheetTrigger →\n // Radix onCloseAutoFocus triggerRef=null + preventDefault 吃掉 fallback → 焦點掉 <body>。\n // 解:開啟時 snapshot opener(事件 dispatch 時 Radix 尚未移焦;MUI/React Aria/Ant snapshot\n // 共識 + DatePicker Range 先例 date-picker.tsx:1061),關閉手動還原;opener 已 unmount →\n // fallback skip-link main(#app-shell-main tabIndex=-1)。\n onOpenAutoFocus={(e) => {\n asideOpenerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null\n handleSheetOpenAutoFocus(e) // 接力 DS 預設 body-focus(自訂 handler 覆寫預設,故顯式呼叫)\n }}\n onCloseAutoFocus={(e) => {\n e.preventDefault()\n const opener = asideOpenerRef.current\n asideOpenerRef.current = null\n if (opener?.isConnected) { opener.focus({ preventScroll: true }); return }\n document.getElementById('app-shell-main')?.focus({ preventScroll: true })\n }}\n className={cn(\n 'w-[min(90vw,var(--app-shell-aside-modal-width))] flex flex-col p-0 [&>button]:hidden',\n className\n )}\n style={{ ['--app-shell-aside-modal-width' as string]: `${resolvedWidth}px` }}\n >\n {renderFrame(<SheetTitle className=\"flex-1\">{title}</SheetTitle>)}\n </SheetContent>\n </Sheet>\n )\n }\n\n // Standard inline mode(desktop)\n if (!asideOpen) return null\n\n return (\n <aside\n ref={ref}\n aria-label={title}\n className={cn(\n 'flex flex-col h-full min-h-0 overflow-hidden',\n 'bg-surface border-l border-divider',\n className\n )}\n style={{ width: resolvedWidth }}\n >\n {renderFrame(<h2 className=\"text-body-lg font-medium flex-1 truncate\">{title}</h2>)}\n </aside>\n )\n }\n)\nAppShellAside.displayName = 'AppShellAside'\n\n// ── Exports ──────────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export useAppShell — public compound API hook(consumer 可自拼 custom aside layout,\n// 對齊 Radix `useDialogContext` / MUI `useFormControl` 慣例)。內部 AppShellAside 已消費(L294),\n// audit script 抓「無 cross-file import」是 false positive(2026-05-21 D2 codify)。\nexport { AppShell, AppShellAside, useAppShell }\n"],"names":["XIcon"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../src/components/Avatar/avatar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAMrH;;;;;;;;;;;;;;;;GAgBG;AAQH,KAAK,QAAQ,GAAG,gBAAgB,CAAA;AA6DhC,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACvE,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0BAA0B;IAC1B,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,yGAAyG;IACzG,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAC/C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;
|
|
1
|
+
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../src/components/Avatar/avatar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAMrH;;;;;;;;;;;;;;;;GAgBG;AAQH,KAAK,QAAQ,GAAG,gBAAgB,CAAA;AA6DhC,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACvE,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,0BAA0B;IAC1B,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,yGAAyG;IACzG,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAC/C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAwND,MAAM,WAAW,UAAU;IACzB,aAAa;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,2CAA2C;IAC3C,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC5B;AAID,eAAO,MAAM,UAAU;;;;;;;;;;;CAab,CAAA;AAGV,QAAA,MAAM,MAAM,+GAA0B,CAAA;AAEtC,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
|
@@ -4,7 +4,7 @@ import { User } from "lucide-react";
|
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
import { CAT_SOLID_TOKENS, CAT_SUBTLE_TOKENS } from "../../tokens/categorical-color.js";
|
|
6
6
|
import { HoverCard, HoverCardTrigger, HoverCardContent } from "../HoverCard/hover-card.js";
|
|
7
|
-
import {
|
|
7
|
+
import { MOTION_DELAY_CLOSE_MS, MOTION_DELAY_RICH_MS } from "../../tokens/motion/motion.js";
|
|
8
8
|
import { Badge } from "../Badge/badge.js";
|
|
9
9
|
import { useTableIsScrolling, useResolvedFieldDisabled } from "../Field/field-context.js";
|
|
10
10
|
const COLOR_MAP = {
|
|
@@ -62,7 +62,7 @@ const AvatarInner = React.forwardRef(
|
|
|
62
62
|
const fontSizePx = Math.round(numSize * 0.5);
|
|
63
63
|
const variantKey = solid ? "solid" : "subtle";
|
|
64
64
|
const colors = ((_a = COLOR_MAP[variantKey]) == null ? void 0 : _a[color]) ?? COLOR_MAP.subtle.neutral;
|
|
65
|
-
const radius = shape === "circle" ? "
|
|
65
|
+
const radius = shape === "circle" ? "var(--radius-full)" : "var(--radius-md)";
|
|
66
66
|
const showImage = src && !imgError;
|
|
67
67
|
const showIcon = !showImage && (Icon || !alt);
|
|
68
68
|
const showText = !showImage && !showIcon && alt;
|
|
@@ -115,7 +115,7 @@ const AvatarInner = React.forwardRef(
|
|
|
115
115
|
const focusableProps = hoverCard ? {
|
|
116
116
|
tabIndex: 0,
|
|
117
117
|
role: "img",
|
|
118
|
-
"aria-label": alt ?? "View profile"
|
|
118
|
+
"aria-label": typeof badgeCount === "number" && badgeCount > 0 ? `${alt ?? "View profile"}, ${badgeAriaLabel ?? `${badgeCount} unread`}` : alt ?? "View profile"
|
|
119
119
|
} : {};
|
|
120
120
|
const focusableClass = hoverCard ? cn("focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", shape === "circle" ? "rounded-full" : "rounded-md") : "";
|
|
121
121
|
const baseEl = !hasOverlay ? /* @__PURE__ */ jsx("div", { ref, className: cn("inline-flex shrink-0", focusableClass, className), style, ...focusableProps, ...props, children: avatarEl }) : /* @__PURE__ */ jsxs("div", { ref, className: cn("relative inline-flex shrink-0", focusableClass, className), style, ...focusableProps, ...props, children: [
|
|
@@ -150,7 +150,7 @@ const AvatarInner = React.forwardRef(
|
|
|
150
150
|
)
|
|
151
151
|
] });
|
|
152
152
|
if (!hoverCard || isTableScrolling) return baseEl;
|
|
153
|
-
return /* @__PURE__ */ jsxs(HoverCard, { openDelay:
|
|
153
|
+
return /* @__PURE__ */ jsxs(HoverCard, { openDelay: MOTION_DELAY_RICH_MS, closeDelay: MOTION_DELAY_CLOSE_MS, children: [
|
|
154
154
|
/* @__PURE__ */ jsx(HoverCardTrigger, { asChild: true, children: baseEl }),
|
|
155
155
|
/* @__PURE__ */ jsx(
|
|
156
156
|
HoverCardContent,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar.js","sources":["../../../src/components/Avatar/avatar.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 { User } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { CAT_SUBTLE_TOKENS, CAT_SOLID_TOKENS, type CategoricalColor } from '@/design-system/tokens/categorical-color'\nimport { HoverCard, HoverCardTrigger, HoverCardContent } from '@/design-system/components/HoverCard/hover-card'\nimport { HOVER_DELAY_RICH_MS, HOVER_DELAY_CLOSE_MS } from '@/design-system/tokens/motion/motion'\nimport { Badge } from '@/design-system/components/Badge/badge'\nimport { useResolvedFieldDisabled, useTableIsScrolling } from '@/design-system/components/Field/field-context'\n\n/**\n * Avatar — 頭像元件\n *\n * 三種內容模式(按優先順序):\n * 1. src → 圖片\n * 2. icon → icon 在底色圓/方形內\n * 3. alt → 取首字作文字 fallback\n * 4. 都沒有 → 預設 User icon\n *\n * ── 尺寸 ──\n * size 接受任意 px 值,icon 自動 = round_even(size × 0.6)\n * 文字 fallback 字體 = size × 0.5\n *\n * ── 形狀 ──\n * circle(預設)→ rounded-full,用於人物\n * square → rounded-md (4px),用於實體(專案、組織、App)\n */\n\n// ── 色彩 ──\n// **消費 categorical-color SSOT**(CAT_SUBTLE_TOKENS / CAT_SOLID_TOKENS,key X 一律對 `--color-X-*`,\n// 1:1 零 offset)。subtle=step-1 底 + step-7 字;solid=step-6 全色底 + on-emphasis 字\n//(亮 hue yellow/amber/orange/lime 用 --on-emphasis-dark 深字;green 白字例外)。neutral 非色相,自處理(subtle=muted、solid=neutral-9)。\n// 2026-06-04 修:原 `red` 誤接 `--color-deep-orange-*`(red=品牌紅 hue-25 ≠ deep-orange);\n// 改消費 SSOT 後 red→`--color-red-*`,並補齊全 12 色相。\ntype ColorKey = CategoricalColor\ntype VariantKey = 'subtle' | 'solid'\n\nconst COLOR_MAP: Record<VariantKey, Record<ColorKey, { bg: string; text: string }>> = {\n subtle: {\n neutral: { bg: 'var(--muted)', text: 'var(--foreground)' },\n ...CAT_SUBTLE_TOKENS,\n },\n solid: {\n neutral: { bg: 'var(--color-neutral-9)', text: 'var(--inverse-fg)' },\n ...CAT_SOLID_TOKENS,\n },\n}\n\n// ── Icon size: round to nearest even, ≈ 60% ──\nfunction getIconSize(avatarSize: number): number {\n return Math.round((avatarSize * 0.6) / 2) * 2\n}\n\n// ── Text fallback: first character ──\nfunction getInitial(text: string): string {\n return text.trim().charAt(0).toUpperCase()\n}\n\n// Semantic presence tokens — 見 color/semantic.css\n// Module-level constant(2026-04-22 D3 perf audit):從 render body 移到 module scope,\n// 避免每次 Avatar render 都 re-declare 此 4-entry object(Low impact 但渲染大量 avatars 時累積可觀)\nconst STATUS_DOT_COLOR: Record<string, string> = {\n online: 'var(--status-online)',\n away: 'var(--status-away)',\n busy: 'var(--status-busy)',\n offline: 'var(--status-offline)',\n}\n\n// ── useDocumentTheme(2026-04-23;M3 Portal 逃脫防線,scope verified 2026-04-25)──\n// 讀 `<html data-theme>` 並 observe mutation。用於 Avatar hoverCard ProfileCard:\n// Portal 後的 HoverCardContent 會繼承 trigger subtree theme(如 OverflowIndicator\n// dark tooltip 內部),造成 ProfileCard 被污染成 dark。顯式 bind app-level theme\n// 確保 ProfileCard 永遠跟 app 本身 theme 一致(light-in-light-app / dark-in-dark-app)。\n//\n// 範圍 audit 2026-04-25:觀察對象是 `document.documentElement` 自有 DOM,非 3rd-party\n// lib 內部(不屬 M2 scope);attributeFilter 限定 `data-theme` 單一 attr,re-render 成本\n// 為每次全站 theme 切換 × Avatar 數量,可接受。\nfunction useDocumentTheme(): string | null {\n const [theme, setTheme] = React.useState<string | null>(() =>\n typeof document !== 'undefined' ? document.documentElement.getAttribute('data-theme') : null,\n )\n React.useEffect(() => {\n if (typeof document === 'undefined') return\n const root = document.documentElement\n const update = () => setTheme(root.getAttribute('data-theme'))\n update()\n const obs = new MutationObserver(update)\n obs.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n return () => obs.disconnect()\n }, [])\n return theme\n}\n\n// ── Component ──\n\nexport interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 尺寸:number (px) 或 'fill'(填滿父容器,由父層決定大小)。預設 32 */\n size?: number | 'fill'\n /** 形狀:circle(人物)或 square(實體),預設 circle */\n shape?: 'circle' | 'square'\n /** 圖片 URL */\n src?: string\n /** 替代文字(圖片失敗時取首字作 fallback) */\n alt?: string\n /** Icon 模式(LucideIcon) */\n icon?: LucideIcon\n /** Icon / text fallback 的背景色,預設 neutral */\n color?: ColorKey\n /** 深底模式(step-6 背景 + on-emphasis 配對前景;亮色 hue yellow/amber/orange/lime 用深字 --on-emphasis-dark),預設 false */\n solid?: boolean\n /**\n * 在線狀態指示器(presence),顯示在 avatar **右下角**。\n * 世界級對照:Slack / Teams / Discord — `online` 是最廣泛被理解的術語。\n * 位置語義:右下 = \"此人的 presence\"(使用者聚焦於「這個人是誰 + 現在 在不在」)。\n */\n status?: 'online' | 'away' | 'busy' | 'offline'\n /**\n * 未讀 / 通知計數 badge,顯示在 avatar **右上角**。\n * 世界級對照:chat app(iMessage / Slack thread / LINE / WhatsApp)一律右上角。\n * 位置語義:右上 = \"關於此對話的新事件數量\"(使用者聚焦於「有多少未處理」);\n * 與右下的 presence 共存不衝突(不同角、不同語義)。\n * `> 99` 自動顯示 \"99+\"(交給內部 Badge 的 `max` 行為)。\n */\n badgeCount?: number\n /**\n * badgeCount overlay 的 aria-label override(i18n)。預設英文 `${badgeCount} unread`;\n * consumer 傳此 prop 本地化(對齊 `Notice` dismissAriaLabel pattern,a11y/i18n)。\n */\n badgeAriaLabel?: string\n /**\n * 傳入 HoverCard 內容(如 ProfileCard),hover avatar 時自動顯示。\n * 只有人員 avatar 需要傳;實體 avatar(專案、組織)不傳。\n */\n hoverCard?: React.ReactNode\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\n// 2026-05-13 (a) perf fix part-2(per codex Layer C Roadmap rich-cell dominant + user 拍 Path (a)):\n// `React.memo` wrap forwardRef Avatar — Roadmap 13 columns 含 person/multiPerson,每 row 多 avatar\n// × HoverCard subtree + useDocumentTheme observer = 重渲染 hotspot。memo shallow-equal props,\n// HoverCard / themeRef stable across scroll 時 skip re-render。對齊 codex Profile Plan step 5\n// (filter Avatar/PeoplePicker/FieldSurfaceProvider remounts)。\n// code-quality-allow: long-function — size × shape × color × solid × status × badgeCount × hoverCard × img-fallback 多軸 prop 組合,拆 sub-fn 會跨 fn 傳 imgError state + isTableScrolling observer 結果\nconst AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(\n ({ size = 32, shape = 'circle', src, alt, icon: Icon, color = 'neutral', solid = false, status, badgeCount, badgeAriaLabel, hoverCard, className, style, ...props }, ref) => {\n const [imgError, setImgError] = React.useState(false)\n // 2026-07-05 D4:imgError 隨 src 變更 reset — 原 state 首次 onError 後永久 true,consumer 之後\n // 換有效新 src(重傳頭像 / signed URL 刷新)時 showImage 永遠 false,<img> 不再掛載 → 新圖\n // 永不嘗試載入、終身卡 initials fallback。React 官方 adjust-state-on-prop-change 寫法\n //(render-time derived reset,較 useEffect 少一輪 render);對齊 Radix useImageLoadingStatus /\n // MUI useLoaded 皆隨 src reset 的 canonical。\n const [prevSrc, setPrevSrc] = React.useState(src)\n if (prevSrc !== src) {\n setPrevSrc(src)\n setImgError(false)\n }\n const documentTheme = useDocumentTheme()\n const isTableScrolling = useTableIsScrolling()\n // 2026-05-13 R3.5(per codex Q3 verdict + user 拍「想盡辦法 auto-handle prereq」):\n // Avatar self-dim when in disabled Field wrapper context(取代既有 wrapper opacity-disabled blanket\n // 逃生艙 — color.spec.md:729 specific-disabled-color canonical)。\n // Scope:`useResolvedFieldDisabled()`(= fieldCtx.disabled,涵蓋 <Field disabled> 與 <Field mode=\"disabled\">),標準 Field\n // 家族 wrapper disabled 時才 dim;**沒包在 Field wrapper 內的 standalone Avatar**(ProfileCard / FileItem /\n // HoverCard / Dialog 等 display 場景)**backward compat 不變**。對齊 avatar.spec.md「Avatar 在 disabled\n // 元件內 host-controlled opacity」canonical — 升級成「Avatar self-managed via fieldCtx」。\n // 2026-06-08 SSOT:欄位內 Avatar 跟隨 <Field disabled>/<Field mode=\"disabled\"> 變淡(fieldCtx-scoped,cell 無 fieldCtx → 不影響)\n const isDisabledInField = useResolvedFieldDisabled()\n const isFill = size === 'fill'\n // Fill 模式下 icon 用 60% 寬高、text 用 50cqi(container query inline-size);\n // 數字模式下用既有 px 計算\n const numSize = isFill ? 32 : (size as number)\n const iconPx = getIconSize(numSize)\n const fontSizePx = Math.round(numSize * 0.5)\n const variantKey: VariantKey = solid ? 'solid' : 'subtle'\n const colors = COLOR_MAP[variantKey]?.[color] ?? COLOR_MAP.subtle.neutral\n const radius = shape === 'circle' ? '9999px' : '4px'\n\n // 決定內容\n const showImage = src && !imgError\n const showIcon = !showImage && (Icon || (!alt))\n const showText = !showImage && !showIcon && alt\n\n const FallbackIcon = Icon ?? User\n\n // Status dot 尺寸:avatar 的 28%(Slack / Teams / Discord 世界級平均),\n // clamp [8, 16] — floor 8 保小 avatar 仍可辨識但不喧賓奪主(10 floor 會讓 24px\n // avatar 的 dot 占 42% 太大);ceiling 16 防大 avatar dot 過度放大\n const dotSize = isFill ? 10 : Math.max(8, Math.min(16, Math.round(numSize * 0.28)))\n // Border ring 在 surface 上分離 dot 與 avatar,dotSize ≥ 12 時升階到 3px 保持視覺比例\n const dotBorder = dotSize >= 12 ? 3 : 2\n\n const avatarEl = (\n <div\n className={cn(\n 'inline-flex items-center justify-center shrink-0 overflow-hidden select-none',\n isFill && 'w-full h-full',\n // 2026-05-13 R3.5 self-dim:Avatar 在 disabled Field wrapper context 內自 dim\n // (取代 field-wrapper.tsx default/bare/naked disabled blanket opacity-disabled 逃生艙)\n isDisabledInField && 'opacity-disabled',\n )}\n style={{\n ...(isFill\n ? { containerType: 'inline-size' as React.CSSProperties['containerType'] }\n : { width: numSize, height: numSize }),\n borderRadius: radius,\n backgroundColor: showImage ? undefined : colors.bg,\n color: showImage ? undefined : colors.text,\n }}\n data-avatar-size={isFill ? 'fill' : numSize}\n role={!showImage && alt && !hoverCard ? 'img' : undefined}\n aria-label={!showImage && alt && !hoverCard ? alt : undefined}\n >\n {showImage && (\n <img\n src={src}\n alt={alt ?? ''}\n className=\"w-full h-full object-cover\"\n onError={() => setImgError(true)}\n />\n )}\n {showIcon && (\n isFill\n ? <FallbackIcon className=\"w-[60%] h-[60%]\" aria-hidden />\n : <FallbackIcon size={iconPx} aria-hidden />\n )}\n {showText && (\n <span\n className=\"font-medium leading-none\"\n style={{ fontSize: isFill ? '50cqi' : fontSizePx }}\n aria-hidden\n >\n {getInitial(alt!)}\n </span>\n )}\n </div>\n )\n\n const hasOverlay = status || typeof badgeCount === 'number'\n // Keyboard access canonical(D4 2026-04-22 finding;2026-07-06 user 拍板修正 ARIA 空承諾):\n // Avatar with `hoverCard` 需 keyboard 可達 — Radix `HoverCardTrigger asChild` 不自動加\n // tabIndex,non-focusable `<div>` 會讓 keyboard-only user 無法 reach popover(WCAG 2.1.1)。\n // 解:`tabIndex=0` + `role=\"img\"` + `aria-label` + focus ring(focus 時 Radix 自動開卡)。\n // **不掛 `role=\"button\"` / `aria-haspopup`**(2026-07-06 拆除):原本宣告了卻沒有\n // Enter/Space 行為、沒有 aria-expanded = 對 AT 的空承諾;對齊 Radix hover-card 官方\n // (刻意零 popup ARIA,hover card 定位 sighted-user 輔助)+ GitHub hovercard(trigger\n // 純連結無 popup ARIA)。若無 hoverCard 則維持純展示 `<div>`。\n // **`role=\"img\"`(2026-07-07 CI axe 補修)**:aria-label 在無 role 的 generic `<div>` 上\n // 是 ARIA 禁用組合(axe aria-prohibited-attr,serious;2026-07-06 拆 role=\"button\" 後裸露)。\n // Avatar 語義本質 = 身份圖像 → `role=\"img\"` 讓 aria-label 合法、子孫自動 presentational、\n // 且不對 AT 承諾任何互動行為 — 與「拆假承諾」拍板意圖一致(emoji span / MUI Avatar img alt 同款語義)。\n const focusableProps = hoverCard\n ? {\n tabIndex: 0,\n role: 'img',\n 'aria-label': alt ?? 'View profile',\n }\n : {}\n // 2026-05-31:focus ring 圓角跟隨 shape(circle→rounded-full / square→rounded-md,對齊 body 的 `radius` 變數 '9999px'/'4px'),\n // 原寫死 rounded-full 會讓方形 avatar(實體)配 hoverCard 時出現圓形 ring。hoverCard 為通用行為(任意內容),\n // 方形 avatar 合法可配(內容非 ProfileCard 而已),故 ring 必跟形狀。\n const focusableClass = hoverCard\n ? cn('focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', shape === 'circle' ? 'rounded-full' : 'rounded-md')\n : ''\n const baseEl = !hasOverlay\n ? <div ref={ref} className={cn('inline-flex shrink-0', focusableClass, className)} style={style} {...focusableProps} {...props}>{avatarEl}</div>\n : (\n <div ref={ref} className={cn('relative inline-flex shrink-0', focusableClass, className)} style={style} {...focusableProps} {...props}>\n {avatarEl}\n {/* Status dot:bottom-right(presence — 世界級對照 Slack / Teams / Discord),\n 落在 circle avatar 圓周 45° 位置 / square avatar 右下直角;\n border ring 用 surface 色讓 dot 從 avatar 邊界視覺分離。\n a11y:`aria-hidden` — presence 資訊整合到 parent avatar 的 aria-label\n (world-class Slack 做法),避免多 `role=\"status\"` 造成 screen reader 洪水 */}\n {status && (\n <span\n className=\"absolute block rounded-full\"\n style={{\n width: dotSize,\n height: dotSize,\n bottom: 0,\n right: 0,\n backgroundColor: STATUS_DOT_COLOR[status],\n boxShadow: `0 0 0 ${dotBorder}px var(--surface-raised, var(--canvas))`,\n }}\n aria-hidden\n />\n )}\n {/* Count badge:top-right(chat 未讀 / 通知計數 — 世界級對照 iMessage /\n Slack thread / LINE / WhatsApp)。消費 DS Badge(critical variant),\n 再加 ring 與 avatar 分離 */}\n {typeof badgeCount === 'number' && badgeCount > 0 && (\n <Badge\n variant=\"critical\"\n count={badgeCount}\n max={99}\n className=\"absolute -top-1 -right-1\"\n style={{\n boxShadow: `0 0 0 2px var(--surface-raised, var(--canvas))`,\n }}\n aria-label={badgeAriaLabel ?? `${badgeCount} unread`} // i18n-allow: DS default 英文(consumer 可傳 badgeAriaLabel i18n)\n />\n )}\n </div>\n )\n\n // 2026-05-13 (c) scroll-defer perf(per user 拍 Path (c) + codex Q3 verdict):\n // DataTable scrolling 期間跳 HoverCard wrapper(Portal + useDocumentTheme observer 是\n // Roadmap 重渲 hotspot,per codex Layer C 分析)。scroll 結束 → context flips false →\n // re-render 接回完整 HoverCard tree(ProfileCard 仍可 hover 顯示)。\n // 對齊 AG Grid `deferRender` for slow React cell components / MUI X DataGrid scroll-defer。\n if (!hoverCard || isTableScrolling) return baseEl\n\n return (\n <HoverCard openDelay={HOVER_DELAY_RICH_MS} closeDelay={HOVER_DELAY_CLOSE_MS}>\n <HoverCardTrigger asChild>\n {baseEl}\n </HoverCardTrigger>\n {/* HoverCardContent canonical(2026-04-23):\n - 無 inner padding(consumer ProfileCard 自帶 `px-4 py-3` chrome)\n - `overflow-hidden` + `rounded-lg` → child(ProfileCard)圓角裁切\n - **不設 max-height**:ProfileCard 自己消費 `--radix-hover-card-content-available-height`\n 自約束高度 + 內部 ScrollArea 處理捲動\n - `data-theme={documentTheme}`:ProfileCard 永遠跟隨 **app-level theme**(從 `<html data-theme>`\n 動態讀),不受 trigger subtree theme 污染。範例:Avatar 位於 OverflowIndicator 的 dark\n tooltip 內,其 Portal 會繼承該 subtree dark theme → ProfileCard 變全黑。顯式設回 app theme\n 確保 ProfileCard 永遠 light-in-light-app / dark-in-dark-app。 */}\n <HoverCardContent\n data-theme={documentTheme ?? undefined}\n className=\"bg-surface-raised rounded-lg border border-border overflow-hidden\"\n style={{ boxShadow: 'var(--elevation-200)' }}\n >\n {hoverCard}\n </HoverCardContent>\n </HoverCard>\n )\n }\n)\n\n// ── AvatarData ─────────────────────────────────────────────────────────────\n// 資料型別,讓 consumer 傳資料而非 ReactNode。\n// 接收端內部用 Avatar 元件渲染,統一控制尺寸與 fallback。\n\nexport interface AvatarData {\n /** 圖片 URL */\n src?: string\n /** 替代文字(圖片失敗時取首字作 fallback) */\n alt: string\n /** Icon / text fallback 的背景色,預設 neutral */\n color?: ColorKey\n /**\n * Person avatar hover ProfileCard(DS-wide canonical,person avatar 預設必有,見 avatar.spec.md)。\n * Entity avatar(專案 / 組織 logo)不帶 → consumer 不傳 hoverCard 即豁免。\n * 消費 AvatarData 的 row primitive(MenuItem menu-item.tsx / SelectionItem selection-item.tsx,\n * DropdownMenu / SelectMenu 經 MenuItem)需 forward 此 prop 到內部 <Avatar hoverCard={...} />。\n * 例外:ProfileCard 內部 Avatar 不 forward(卡片本身就是 hover 目的地,nested hover-in-hover 不合理)。\n */\n hoverCard?: React.ReactNode\n}\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 avatarMeta = {\n component: 'Avatar',\n family: null, // non-family composite / overlay / layout\n // 2026-07-04 audit 標記:Avatar 無 variant 軸、size 為任意 number(px,由 consumer context 決定\n // — item-anatomy AVATAR_SIZE 查表 / ProfileCard 40 等),無 discrete tier → 兩欄留空為 intentional 非漏填\n variants: {},\n sizes: {},\n states: ['default', 'focus-visible', 'disabled'], // 2026-06-11 R2:本身無自有 hover/active(spec L279;hoverCard 互動屬 HoverCard),\n tokens: {\n bg: ['bg-surface-raised'],\n fg: ['--foreground', '--on-emphasis'],\n ring: ['ring-ring'],\n },\n} as const\n\nAvatarInner.displayName = 'Avatar'\nconst Avatar = React.memo(AvatarInner)\n\nexport { Avatar }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"avatar.js","sources":["../../../src/components/Avatar/avatar.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 { User } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { CAT_SUBTLE_TOKENS, CAT_SOLID_TOKENS, type CategoricalColor } from '@/design-system/tokens/categorical-color'\nimport { HoverCard, HoverCardTrigger, HoverCardContent } from '@/design-system/components/HoverCard/hover-card'\nimport { MOTION_DELAY_RICH_MS, MOTION_DELAY_CLOSE_MS } from '@/design-system/tokens/motion/motion'\nimport { Badge } from '@/design-system/components/Badge/badge'\nimport { useResolvedFieldDisabled, useTableIsScrolling } from '@/design-system/components/Field/field-context'\n\n/**\n * Avatar — 頭像元件\n *\n * 三種內容模式(按優先順序):\n * 1. src → 圖片\n * 2. icon → icon 在底色圓/方形內\n * 3. alt → 取首字作文字 fallback\n * 4. 都沒有 → 預設 User icon\n *\n * ── 尺寸 ──\n * size 接受任意 px 值,icon 自動 = round_even(size × 0.6)\n * 文字 fallback 字體 = size × 0.5\n *\n * ── 形狀 ──\n * circle(預設)→ rounded-full,用於人物\n * square → rounded-md (4px),用於實體(專案、組織、App)\n */\n\n// ── 色彩 ──\n// **消費 categorical-color SSOT**(CAT_SUBTLE_TOKENS / CAT_SOLID_TOKENS,key X 一律對 `--color-X-*`,\n// 1:1 零 offset)。subtle=step-1 底 + step-7 字;solid=step-6 全色底 + on-emphasis 字\n//(亮 hue yellow/amber/orange/lime 用 --on-emphasis-dark 深字;green 白字例外)。neutral 非色相,自處理(subtle=muted、solid=neutral-9)。\n// 2026-06-04 修:原 `red` 誤接 `--color-deep-orange-*`(red=品牌紅 hue-25 ≠ deep-orange);\n// 改消費 SSOT 後 red→`--color-red-*`,並補齊全 12 色相。\ntype ColorKey = CategoricalColor\ntype VariantKey = 'subtle' | 'solid'\n\nconst COLOR_MAP: Record<VariantKey, Record<ColorKey, { bg: string; text: string }>> = {\n subtle: {\n neutral: { bg: 'var(--muted)', text: 'var(--foreground)' },\n ...CAT_SUBTLE_TOKENS,\n },\n solid: {\n neutral: { bg: 'var(--color-neutral-9)', text: 'var(--inverse-fg)' },\n ...CAT_SOLID_TOKENS,\n },\n}\n\n// ── Icon size: round to nearest even, ≈ 60% ──\nfunction getIconSize(avatarSize: number): number {\n return Math.round((avatarSize * 0.6) / 2) * 2\n}\n\n// ── Text fallback: first character ──\nfunction getInitial(text: string): string {\n return text.trim().charAt(0).toUpperCase()\n}\n\n// Semantic presence tokens — 見 color/semantic.css\n// Module-level constant(2026-04-22 D3 perf audit):從 render body 移到 module scope,\n// 避免每次 Avatar render 都 re-declare 此 4-entry object(Low impact 但渲染大量 avatars 時累積可觀)\nconst STATUS_DOT_COLOR: Record<string, string> = {\n online: 'var(--status-online)',\n away: 'var(--status-away)',\n busy: 'var(--status-busy)',\n offline: 'var(--status-offline)',\n}\n\n// ── useDocumentTheme(2026-04-23;M3 Portal 逃脫防線,scope verified 2026-04-25)──\n// 讀 `<html data-theme>` 並 observe mutation。用於 Avatar hoverCard ProfileCard:\n// Portal 後的 HoverCardContent 會繼承 trigger subtree theme(如 OverflowIndicator\n// dark tooltip 內部),造成 ProfileCard 被污染成 dark。顯式 bind app-level theme\n// 確保 ProfileCard 永遠跟 app 本身 theme 一致(light-in-light-app / dark-in-dark-app)。\n//\n// 範圍 audit 2026-04-25:觀察對象是 `document.documentElement` 自有 DOM,非 3rd-party\n// lib 內部(不屬 M2 scope);attributeFilter 限定 `data-theme` 單一 attr,re-render 成本\n// 為每次全站 theme 切換 × Avatar 數量,可接受。\nfunction useDocumentTheme(): string | null {\n const [theme, setTheme] = React.useState<string | null>(() =>\n typeof document !== 'undefined' ? document.documentElement.getAttribute('data-theme') : null,\n )\n React.useEffect(() => {\n if (typeof document === 'undefined') return\n const root = document.documentElement\n const update = () => setTheme(root.getAttribute('data-theme'))\n update()\n const obs = new MutationObserver(update)\n obs.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n return () => obs.disconnect()\n }, [])\n return theme\n}\n\n// ── Component ──\n\nexport interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 尺寸:number (px) 或 'fill'(填滿父容器,由父層決定大小)。預設 32 */\n size?: number | 'fill'\n /** 形狀:circle(人物)或 square(實體),預設 circle */\n shape?: 'circle' | 'square'\n /** 圖片 URL */\n src?: string\n /** 替代文字(圖片失敗時取首字作 fallback) */\n alt?: string\n /** Icon 模式(LucideIcon) */\n icon?: LucideIcon\n /** Icon / text fallback 的背景色,預設 neutral */\n color?: ColorKey\n /** 深底模式(step-6 背景 + on-emphasis 配對前景;亮色 hue yellow/amber/orange/lime 用深字 --on-emphasis-dark),預設 false */\n solid?: boolean\n /**\n * 在線狀態指示器(presence),顯示在 avatar **右下角**。\n * 世界級對照:Slack / Teams / Discord — `online` 是最廣泛被理解的術語。\n * 位置語義:右下 = \"此人的 presence\"(使用者聚焦於「這個人是誰 + 現在 在不在」)。\n */\n status?: 'online' | 'away' | 'busy' | 'offline'\n /**\n * 未讀 / 通知計數 badge,顯示在 avatar **右上角**。\n * 世界級對照:chat app(iMessage / Slack thread / LINE / WhatsApp)一律右上角。\n * 位置語義:右上 = \"關於此對話的新事件數量\"(使用者聚焦於「有多少未處理」);\n * 與右下的 presence 共存不衝突(不同角、不同語義)。\n * `> 99` 自動顯示 \"99+\"(交給內部 Badge 的 `max` 行為)。\n */\n badgeCount?: number\n /**\n * badgeCount overlay 的 aria-label override(i18n)。預設英文 `${badgeCount} unread`;\n * consumer 傳此 prop 本地化(對齊 `Notice` dismissAriaLabel pattern,a11y/i18n)。\n */\n badgeAriaLabel?: string\n /**\n * 傳入 HoverCard 內容(如 ProfileCard),hover avatar 時自動顯示。\n * 只有人員 avatar 需要傳;實體 avatar(專案、組織)不傳。\n */\n hoverCard?: React.ReactNode\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\n// 2026-05-13 (a) perf fix part-2(per codex Layer C Roadmap rich-cell dominant + user 拍 Path (a)):\n// `React.memo` wrap forwardRef Avatar — Roadmap 13 columns 含 person/multiPerson,每 row 多 avatar\n// × HoverCard subtree + useDocumentTheme observer = 重渲染 hotspot。memo shallow-equal props,\n// HoverCard / themeRef stable across scroll 時 skip re-render。對齊 codex Profile Plan step 5\n// (filter Avatar/PeoplePicker/FieldSurfaceProvider remounts)。\n// code-quality-allow: long-function — size × shape × color × solid × status × badgeCount × hoverCard × img-fallback 多軸 prop 組合,拆 sub-fn 會跨 fn 傳 imgError state + isTableScrolling observer 結果\nconst AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(\n ({ size = 32, shape = 'circle', src, alt, icon: Icon, color = 'neutral', solid = false, status, badgeCount, badgeAriaLabel, hoverCard, className, style, ...props }, ref) => {\n const [imgError, setImgError] = React.useState(false)\n // 2026-07-05 D4:imgError 隨 src 變更 reset — 原 state 首次 onError 後永久 true,consumer 之後\n // 換有效新 src(重傳頭像 / signed URL 刷新)時 showImage 永遠 false,<img> 不再掛載 → 新圖\n // 永不嘗試載入、終身卡 initials fallback。React 官方 adjust-state-on-prop-change 寫法\n //(render-time derived reset,較 useEffect 少一輪 render);對齊 Radix useImageLoadingStatus /\n // MUI useLoaded 皆隨 src reset 的 canonical。\n const [prevSrc, setPrevSrc] = React.useState(src)\n if (prevSrc !== src) {\n setPrevSrc(src)\n setImgError(false)\n }\n const documentTheme = useDocumentTheme()\n const isTableScrolling = useTableIsScrolling()\n // 2026-05-13 R3.5(per codex Q3 verdict + user 拍「想盡辦法 auto-handle prereq」):\n // Avatar self-dim when in disabled Field wrapper context(取代既有 wrapper opacity-disabled blanket\n // 逃生艙 — color.spec.md:729 specific-disabled-color canonical)。\n // Scope:`useResolvedFieldDisabled()`(= fieldCtx.disabled,涵蓋 <Field disabled> 與 <Field mode=\"disabled\">),標準 Field\n // 家族 wrapper disabled 時才 dim;**沒包在 Field wrapper 內的 standalone Avatar**(ProfileCard / FileItem /\n // HoverCard / Dialog 等 display 場景)**backward compat 不變**。對齊 avatar.spec.md「Avatar 在 disabled\n // 元件內 host-controlled opacity」canonical — 升級成「Avatar self-managed via fieldCtx」。\n // 2026-06-08 SSOT:欄位內 Avatar 跟隨 <Field disabled>/<Field mode=\"disabled\"> 變淡(fieldCtx-scoped,cell 無 fieldCtx → 不影響)\n const isDisabledInField = useResolvedFieldDisabled()\n const isFill = size === 'fill'\n // Fill 模式下 icon 用 60% 寬高、text 用 50cqi(container query inline-size);\n // 數字模式下用既有 px 計算\n const numSize = isFill ? 32 : (size as number)\n const iconPx = getIconSize(numSize)\n const fontSizePx = Math.round(numSize * 0.5)\n const variantKey: VariantKey = solid ? 'solid' : 'subtle'\n const colors = COLOR_MAP[variantKey]?.[color] ?? COLOR_MAP.subtle.neutral\n // 2026-07-14 dim-20 修:inline style 不能用 rounded-* class,但可消費 radius token\n // (radius.spec.md:18 canonical「所有 4px 圓角一律用 rounded-md」+ :86 禁硬寫值;\n // token 改值 Avatar 跟隨 — M17 SSOT 傳播)。\n const radius = shape === 'circle' ? 'var(--radius-full)' : 'var(--radius-md)'\n\n // 決定內容\n const showImage = src && !imgError\n const showIcon = !showImage && (Icon || (!alt))\n const showText = !showImage && !showIcon && alt\n\n const FallbackIcon = Icon ?? User\n\n // Status dot 尺寸:avatar 的 28%(Slack / Teams / Discord 世界級平均),\n // clamp [8, 16] — floor 8 保小 avatar 仍可辨識但不喧賓奪主(10 floor 會讓 24px\n // avatar 的 dot 占 42% 太大);ceiling 16 防大 avatar dot 過度放大\n const dotSize = isFill ? 10 : Math.max(8, Math.min(16, Math.round(numSize * 0.28)))\n // Border ring 在 surface 上分離 dot 與 avatar,dotSize ≥ 12 時升階到 3px 保持視覺比例\n const dotBorder = dotSize >= 12 ? 3 : 2\n\n const avatarEl = (\n <div\n className={cn(\n 'inline-flex items-center justify-center shrink-0 overflow-hidden select-none',\n isFill && 'w-full h-full',\n // 2026-05-13 R3.5 self-dim:Avatar 在 disabled Field wrapper context 內自 dim\n // (取代 field-wrapper.tsx default/bare/naked disabled blanket opacity-disabled 逃生艙)\n isDisabledInField && 'opacity-disabled',\n )}\n style={{\n ...(isFill\n ? { containerType: 'inline-size' as React.CSSProperties['containerType'] }\n : { width: numSize, height: numSize }),\n borderRadius: radius,\n backgroundColor: showImage ? undefined : colors.bg,\n color: showImage ? undefined : colors.text,\n }}\n data-avatar-size={isFill ? 'fill' : numSize}\n role={!showImage && alt && !hoverCard ? 'img' : undefined}\n aria-label={!showImage && alt && !hoverCard ? alt : undefined}\n >\n {showImage && (\n <img\n src={src}\n alt={alt ?? ''}\n className=\"w-full h-full object-cover\"\n onError={() => setImgError(true)}\n />\n )}\n {showIcon && (\n isFill\n ? <FallbackIcon className=\"w-[60%] h-[60%]\" aria-hidden />\n : <FallbackIcon size={iconPx} aria-hidden />\n )}\n {showText && (\n <span\n className=\"font-medium leading-none\"\n style={{ fontSize: isFill ? '50cqi' : fontSizePx }}\n aria-hidden\n >\n {getInitial(alt!)}\n </span>\n )}\n </div>\n )\n\n const hasOverlay = status || typeof badgeCount === 'number'\n // Keyboard access canonical(D4 2026-04-22 finding;2026-07-06 user 拍板修正 ARIA 空承諾):\n // Avatar with `hoverCard` 需 keyboard 可達 — Radix `HoverCardTrigger asChild` 不自動加\n // tabIndex,non-focusable `<div>` 會讓 keyboard-only user 無法 reach popover(WCAG 2.1.1)。\n // 解:`tabIndex=0` + `role=\"img\"` + `aria-label` + focus ring(focus 時 Radix 自動開卡)。\n // **不掛 `role=\"button\"` / `aria-haspopup`**(2026-07-06 拆除):原本宣告了卻沒有\n // Enter/Space 行為、沒有 aria-expanded = 對 AT 的空承諾;對齊 Radix hover-card 官方\n // (刻意零 popup ARIA,hover card 定位 sighted-user 輔助)+ GitHub hovercard(trigger\n // 純連結無 popup ARIA)。若無 hoverCard 則維持純展示 `<div>`。\n // **`role=\"img\"`(2026-07-07 CI axe 補修)**:aria-label 在無 role 的 generic `<div>` 上\n // 是 ARIA 禁用組合(axe aria-prohibited-attr,serious;2026-07-06 拆 role=\"button\" 後裸露)。\n // Avatar 語義本質 = 身份圖像 → `role=\"img\"` 讓 aria-label 合法、子孫自動 presentational、\n // 且不對 AT 承諾任何互動行為 — 與「拆假承諾」拍板意圖一致(emoji span / MUI Avatar img alt 同款語義)。\n // a11y:role=\"img\" 讓子孫 presentational → 內層 Badge 的 count 語義被壓平,SR 讀不到。\n // 解:badgeCount 併入外層 aria-label(對齊 Slack「單一 accessible name 含未讀數」做法)。\n const focusableProps = hoverCard\n ? {\n tabIndex: 0,\n role: 'img',\n 'aria-label':\n typeof badgeCount === 'number' && badgeCount > 0\n ? `${alt ?? 'View profile'}, ${badgeAriaLabel ?? `${badgeCount} unread`}`\n : (alt ?? 'View profile'),\n }\n : {}\n // 2026-05-31:focus ring 圓角跟隨 shape(circle→rounded-full / square→rounded-md,對齊 body 的 `radius` 變數 var(--radius-full)/var(--radius-md)),\n // 原寫死 rounded-full 會讓方形 avatar(實體)配 hoverCard 時出現圓形 ring。hoverCard 為通用行為(任意內容),\n // 方形 avatar 合法可配(內容非 ProfileCard 而已),故 ring 必跟形狀。\n const focusableClass = hoverCard\n ? cn('focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', shape === 'circle' ? 'rounded-full' : 'rounded-md')\n : ''\n const baseEl = !hasOverlay\n ? <div ref={ref} className={cn('inline-flex shrink-0', focusableClass, className)} style={style} {...focusableProps} {...props}>{avatarEl}</div>\n : (\n <div ref={ref} className={cn('relative inline-flex shrink-0', focusableClass, className)} style={style} {...focusableProps} {...props}>\n {avatarEl}\n {/* Status dot:bottom-right(presence — 世界級對照 Slack / Teams / Discord),\n 落在 circle avatar 圓周 45° 位置 / square avatar 右下直角;\n border ring 用 surface 色讓 dot 從 avatar 邊界視覺分離。\n a11y:`aria-hidden` — presence 資訊整合到 parent avatar 的 aria-label\n (world-class Slack 做法),避免多 `role=\"status\"` 造成 screen reader 洪水 */}\n {status && (\n <span\n className=\"absolute block rounded-full\"\n style={{\n width: dotSize,\n height: dotSize,\n bottom: 0,\n right: 0,\n backgroundColor: STATUS_DOT_COLOR[status],\n boxShadow: `0 0 0 ${dotBorder}px var(--surface-raised, var(--canvas))`,\n }}\n aria-hidden\n />\n )}\n {/* Count badge:top-right(chat 未讀 / 通知計數 — 世界級對照 iMessage /\n Slack thread / LINE / WhatsApp)。消費 DS Badge(critical variant),\n 再加 ring 與 avatar 分離 */}\n {typeof badgeCount === 'number' && badgeCount > 0 && (\n <Badge\n variant=\"critical\"\n count={badgeCount}\n max={99}\n className=\"absolute -top-1 -right-1\"\n style={{\n boxShadow: `0 0 0 2px var(--surface-raised, var(--canvas))`,\n }}\n aria-label={badgeAriaLabel ?? `${badgeCount} unread`} // i18n-allow: DS default 英文(consumer 可傳 badgeAriaLabel i18n)\n />\n )}\n </div>\n )\n\n // 2026-05-13 (c) scroll-defer perf(per user 拍 Path (c) + codex Q3 verdict):\n // DataTable scrolling 期間跳 HoverCard wrapper(Portal + useDocumentTheme observer 是\n // Roadmap 重渲 hotspot,per codex Layer C 分析)。scroll 結束 → context flips false →\n // re-render 接回完整 HoverCard tree(ProfileCard 仍可 hover 顯示)。\n // 對齊 AG Grid `deferRender` for slow React cell components / MUI X DataGrid scroll-defer。\n if (!hoverCard || isTableScrolling) return baseEl\n\n return (\n <HoverCard openDelay={MOTION_DELAY_RICH_MS} closeDelay={MOTION_DELAY_CLOSE_MS}>\n <HoverCardTrigger asChild>\n {baseEl}\n </HoverCardTrigger>\n {/* HoverCardContent canonical(2026-04-23):\n - 無 inner padding(consumer ProfileCard 自帶 `px-4 py-3` chrome)\n - `overflow-hidden` + `rounded-lg` → child(ProfileCard)圓角裁切\n - **不設 max-height**:ProfileCard 自己消費 `--radix-hover-card-content-available-height`\n 自約束高度 + 內部 ScrollArea 處理捲動\n - `data-theme={documentTheme}`:ProfileCard 永遠跟隨 **app-level theme**(從 `<html data-theme>`\n 動態讀),不受 trigger subtree theme 污染。範例:Avatar 位於 OverflowIndicator 的 dark\n tooltip 內,其 Portal 會繼承該 subtree dark theme → ProfileCard 變全黑。顯式設回 app theme\n 確保 ProfileCard 永遠 light-in-light-app / dark-in-dark-app。 */}\n <HoverCardContent\n data-theme={documentTheme ?? undefined}\n className=\"bg-surface-raised rounded-lg border border-border overflow-hidden\"\n style={{ boxShadow: 'var(--elevation-200)' }}\n >\n {hoverCard}\n </HoverCardContent>\n </HoverCard>\n )\n }\n)\n\n// ── AvatarData ─────────────────────────────────────────────────────────────\n// 資料型別,讓 consumer 傳資料而非 ReactNode。\n// 接收端內部用 Avatar 元件渲染,統一控制尺寸與 fallback。\n\nexport interface AvatarData {\n /** 圖片 URL */\n src?: string\n /** 替代文字(圖片失敗時取首字作 fallback) */\n alt: string\n /** Icon / text fallback 的背景色,預設 neutral */\n color?: ColorKey\n /**\n * Person avatar hover ProfileCard(DS-wide canonical,person avatar 預設必有,見 avatar.spec.md)。\n * Entity avatar(專案 / 組織 logo)不帶 → consumer 不傳 hoverCard 即豁免。\n * 消費 AvatarData 的 row primitive(MenuItem menu-item.tsx / SelectionItem selection-item.tsx,\n * DropdownMenu / SelectMenu 經 MenuItem)需 forward 此 prop 到內部 <Avatar hoverCard={...} />。\n * 例外:ProfileCard 內部 Avatar 不 forward(卡片本身就是 hover 目的地,nested hover-in-hover 不合理)。\n */\n hoverCard?: React.ReactNode\n}\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 avatarMeta = {\n component: 'Avatar',\n family: null, // non-family composite / overlay / layout\n // 2026-07-04 audit 標記:Avatar 無 variant 軸、size 為任意 number(px,由 consumer context 決定\n // — item-anatomy AVATAR_SIZE 查表 / ProfileCard 40 等),無 discrete tier → 兩欄留空為 intentional 非漏填\n variants: {},\n sizes: {},\n states: ['default', 'focus-visible', 'disabled'], // 2026-06-11 R2:本身無自有 hover/active(spec L279;hoverCard 互動屬 HoverCard),\n tokens: {\n bg: ['bg-surface-raised'],\n fg: ['--foreground', '--on-emphasis'],\n ring: ['ring-ring'],\n },\n} as const\n\nAvatarInner.displayName = 'Avatar'\nconst Avatar = React.memo(AvatarInner)\n\nexport { Avatar }\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../src/components/Breadcrumb/breadcrumb.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAE5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uDAAuD,CAAA;AA0E9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,KAAK,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../../src/components/Breadcrumb/breadcrumb.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAE5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,uDAAuD,CAAA;AA0E9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,KAAK,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AA2BxC,QAAA,MAAM,UAAU,0JAKd,CAAA;AAKF;;;;GAIG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,UAAU,CAAA;CACvB;AAED,UAAU,mBAAoB,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC1F;;;;;OAKG;IACH,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAGD,QAAA,MAAM,cAAc,8FAkLnB,CAAA;AAKD;;;;;;;;;;;;GAYG;AACH,UAAU,mBAAoB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,IAAI,CAAC;IACxE,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAA;CAClD;AAED,QAAA,MAAM,cAAc,2FA4BnB,CAAA;AAKD,UAAU,mBAAoB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,GAAG,CAAC;IACvE,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,UAAU,CAAA;CACvB;AAED,QAAA,MAAM,cAAc,+FAyCnB,CAAA;AAKD,UAAU,mBAAoB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC;IAC1E,wEAAwE;IACxE,SAAS,CAAC,EAAE,UAAU,CAAA;CACvB;AAED,QAAA,MAAM,cAAc,6FAqBnB,CAAA;AAKD,UAAU,wBAAyB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,IAAI,CAAC;IAC7E,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAGD,QAAA,MAAM,mBAAmB,gGAgBxB,CAAA;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,KAAK,uBAAuB,GAAG,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,sBAAsB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;AAEnH,QAAA,MAAM,kBAAkB,mGAavB,CAAA;AAKD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;CAmBjB,CAAA;AAEV,OAAO,EACL,UAAU,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,kBAAkB,GACnB,CAAA;AACD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,CAAA"}
|
|
@@ -280,7 +280,7 @@ const breadcrumbMeta = {
|
|
|
280
280
|
// non-family composite / overlay / layout
|
|
281
281
|
variants: {},
|
|
282
282
|
sizes: {
|
|
283
|
-
// 值源 = code SSOT:
|
|
283
|
+
// 值源 = code SSOT:BREADCRUMB_TEXT_CLASS(sm/md=text-body, lg=text-body-lg)+ BREADCRUMB_ICON_SIZE(sm/md=16, lg=20)。
|
|
284
284
|
// Breadcrumb 是文字列無固定高度 token,故不列 height(對照 Tag meta 有自身 h-* 才列)。
|
|
285
285
|
sm: { iconSize: 16, typography: "body" },
|
|
286
286
|
md: { iconSize: 16, typography: "body" },
|