@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-input.js","sources":["../../../src/components/LinkInput/link-input.tsx"],"sourcesContent":["import * as React from 'react'\nimport { Pencil } from 'lucide-react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n// ── URL Validation ──────────────────────────────────────────────────────────\n\nfunction isValidUrl(value: string): boolean {\n if (!value) return true\n try {\n const url = new URL(value)\n return url.protocol === 'http:' || url.protocol === 'https:'\n } catch {\n return false\n }\n}\n\nfunction formatHostname(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, '')\n } catch {\n return url\n }\n}\n\n// ── Display rendering(inline,2026-05-05 Phase B3 retire LinkInputDisplay)──\n// 取代 LinkInputDisplay sub-component:純展示 a tag,無 input chrome、無 hover affordance。\n// edit mode 內 link state(showLink branch)也共用此 helper,確保「編輯態的 link 顯示」與\n// display mode 的視覺完全一致(SSOT)。\nfunction renderLinkAnchor(value: string, label?: string) {\n const displayText = label || formatHostname(value)\n return (\n <a\n href={value}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block truncate min-w-0 text-primary hover:text-primary-hover hover:underline transition-colors\"\n >\n {displayText}\n </a>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nexport interface LinkInputProps\n // `defaultValue` 一併 Omit(2026-07-05 deep-audit A.1b):spec.md「controlled-only,不支援\n // defaultValue」宣稱的型別面機械封鎖 — 原本仍在型別 surface 且經 {...props} spread 到已\n // controlled 的 input(React dev warning)。\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {\n mode?: FieldMode\n /**\n * Visual chrome(2026-05-05 Phase B3)。對齊 FieldContext.variant 透傳。\n * - `'default'`(預設)— Field wrapper 完整 chrome(form / Field 內嵌)。\n * - `'bare'` — 透明 variant,hover/focus 才現 border(Toolbar inline edit / DataTable cell-as-input)。\n *\n * mode='display' 時 chrome 無視覺意義(display 完全無 wrapper);chrome 僅作用於 edit / readonly / disabled。\n */\n variant?: FieldVariant\n error?: boolean\n value?: string | null\n onChange?: (value: string) => void\n placeholder?: string\n className?: string\n disabled?: boolean\n /** 自訂顯示文字(非編輯時) */\n label?: string\n /**\n * Display 是否包 Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 anchor,backward compat)。\n * 設 true 時 display 走 fieldWrapperStyles(naked variant)包覆 anchor,\n * 與 cell edit (`<Input naked>`) 同 DOM 結構,消除 Layer-B padding mismatch。\n * **本元件 edit 無 endIcon(UrlCell 用 plain Input edit)→ display 也無 ItemSuffix**(僅 wrapper)。\n */\n showDisplayEndIcon?: boolean\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n value,\n onChange,\n placeholder = 'https://',\n className,\n disabled: disabledProp,\n label,\n showDisplayEndIcon = false,\n readOnly,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n const fieldCtx = useFieldContext()\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // spec field-controls.spec.md L125「readOnly 原生屬性自動覆蓋 mode」契約落地。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const isEditable = resolvedMode === 'edit'\n // chrome resolution:per-prop > context > 'default'\n const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)\n\n // 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md\n // resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、\n // aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框\n // (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。\n const fieldInvalid = useResolvedFieldInvalid(errorProp)\n\n // ── Local state / refs ──────────────────────────────────────────────────\n // 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,\n // 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash\n // (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。\n const [editing, setEditing] = React.useState(false)\n const [localValue, setLocalValue] = React.useState(value ?? '')\n const [localError, setLocalError] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n\n // Sync external value → local\n React.useEffect(() => {\n if (!editing) setLocalValue(value ?? '')\n }, [value, editing])\n\n // Merge refs\n const setRef = React.useCallback((el: HTMLInputElement | null) => {\n inputRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el\n }, [ref])\n\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const focusRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])\n\n // ── mode='display' ─────────────────────────────────────────────────────\n // 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。\n // 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。\n // Default(showDisplayEndIcon=false):無 wrapper 裸 anchor — backward compat。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper 包覆 anchor,\n // 與 cell edit (`<Input naked>`) 同 DOM 結構消除像素偏移(無 ItemSuffix,因 edit 也無 endIcon)。\n if (resolvedMode === 'display') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) display typography canonical):非 D-path bare\n // anchor / span 必套 `fieldDisplayTextClass(size)`(sm/md→text-body,lg→text-body-lg)\n // — 對齊跨 Field family display 視覺尺寸統一。原無 font-size class → 用 browser default\n // 字體 → 跟其他 Field display 不一致(user 抓 I2)。truncate 同需,長 URL ellipsis(I1)。\n if (!value) return <span className={cn(fieldDisplayTextClass(size), 'text-fg-muted block truncate')}>{EMPTY_DISPLAY}</span>\n return <span className={cn(fieldDisplayTextClass(size), 'block truncate')}>{renderLinkAnchor(value, label)}</span>\n }\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'display', variant: resolvedVariant, size }), className)}\n data-field-mode=\"display\"\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {value\n ? renderLinkAnchor(value, label)\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n }\n </span>\n </div>\n )\n }\n\n const hasValidValue = !!value && isValidUrl(value)\n const showLink = isEditable && hasValidValue && !editing && !localError\n const error = fieldInvalid || localError\n\n const handleEdit = () => {\n setEditing(true)\n if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current)\n focusRafIdRef.current = requestAnimationFrame(() => {\n focusRafIdRef.current = 0\n inputRef.current?.focus()\n })\n }\n\n const handleBlur = () => {\n setEditing(false)\n const trimmed = localValue.trim()\n if (!trimmed) {\n // Empty is OK — clear value\n setLocalError(false)\n onChange?.('')\n return\n }\n if (isValidUrl(trimmed)) {\n setLocalError(false)\n onChange?.(trimmed)\n } else {\n setLocalError(true)\n }\n }\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setLocalValue(e.target.value)\n // Clear error on edit (blur validation)\n if (localError) setLocalError(false)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') inputRef.current?.blur()\n if (e.key === 'Escape') {\n setLocalValue(value ?? '')\n setLocalError(false)\n setEditing(false)\n }\n }\n\n // readonly — 顯示藍色連結(可點擊)\n // disabled — 顯示純文字 fg-disabled(不可點擊)\n if (!isEditable) {\n const displayText = value ? (label || formatHostname(value)) : null\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, size }), className)}\n data-field-mode={resolvedMode}\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {resolvedMode === 'disabled'\n ? (displayText\n ? <span className=\"text-fg-disabled\">{displayText}</span>\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>)\n : (value\n ? renderLinkAnchor(value, label)\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>)\n }\n </span>\n </div>\n )\n }\n\n // edit — link display mode(有合法 URL 且未在編輯中)\n if (showLink) {\n return (\n <div\n // 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,\n // 否則 <Field invalid> 下 link state 完全無 error 呈現\n className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <span className=\"flex-1 min-w-0\">\n {value && renderLinkAnchor(value, label)}\n </span>\n <ItemInlineAction\n size={size ?? 'md'}\n action={{ icon: Pencil, label: '編輯連結', onClick: handleEdit }} // i18n-allow: DS default inline-action label\n />\n </div>\n )\n }\n\n // edit — text input mode(正在編輯、無值、或格式錯誤)\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),\n className,\n )}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <input\n ref={setRef}\n type=\"url\"\n id={idProp ?? fieldCtx?.id}\n value={localValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={bareInputStyles}\n {...props}\n />\n </div>\n )\n }\n)\nLinkInput.displayName = 'LinkInput'\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 linkInputMeta = {\n component: 'LinkInput',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);\n // readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。\n states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],\n tokens: {\n // bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。\n bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],\n ring: [],\n },\n} as const\n\nexport { LinkInput }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"link-input.js","sources":["../../../src/components/LinkInput/link-input.tsx"],"sourcesContent":["import * as React from 'react'\nimport { Pencil } from 'lucide-react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n// ── URL Validation ──────────────────────────────────────────────────────────\n\nfunction isValidUrl(value: string): boolean {\n if (!value) return true\n try {\n const url = new URL(value)\n return url.protocol === 'http:' || url.protocol === 'https:'\n } catch {\n return false\n }\n}\n\nfunction formatHostname(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, '')\n } catch {\n return url\n }\n}\n\n// ── Display rendering(inline,2026-05-05 Phase B3 retire LinkInputDisplay)──\n// 取代 LinkInputDisplay sub-component:純展示 a tag,無 input chrome、無 hover affordance。\n// edit mode 內 link state(showLink branch)也共用此 helper,確保「編輯態的 link 顯示」與\n// display mode 的視覺完全一致(SSOT)。\nfunction renderLinkAnchor(value: string, label?: string) {\n const displayText = label || formatHostname(value)\n return (\n <a\n href={value}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block truncate min-w-0 text-primary hover:text-primary-hover hover:underline transition-colors\"\n >\n {displayText}\n </a>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nexport interface LinkInputProps\n // `defaultValue` 一併 Omit(2026-07-05 deep-audit A.1b):spec.md「controlled-only,不支援\n // defaultValue」宣稱的型別面機械封鎖 — 原本仍在型別 surface 且經 {...props} spread 到已\n // controlled 的 input(React dev warning)。\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant' | 'width'> {\n mode?: FieldMode\n /**\n * Visual chrome(2026-05-05 Phase B3)。對齊 FieldContext.variant 透傳。\n * - `'default'`(預設)— Field wrapper 完整 chrome(form / Field 內嵌)。\n * (2026-07-09 `bare` variant 退役;naked = cell-as-input substrate,@internal)\n *\n * mode='display' 時 chrome 無視覺意義(display 完全無 wrapper);chrome 僅作用於 edit / readonly / disabled。\n */\n variant?: FieldVariant\n error?: boolean\n value?: string | null\n onChange?: (value: string) => void\n placeholder?: string\n className?: string\n disabled?: boolean\n /** 自訂顯示文字(非編輯時) */\n label?: string\n /**\n * Display 是否包 Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 anchor,backward compat)。\n * 設 true 時 display 走 fieldWrapperStyles(naked variant)包覆 anchor,\n * 與 cell edit (`<Input naked>`) 同 DOM 結構,消除 Layer-B padding mismatch。\n * **本元件 edit 無 endIcon(UrlCell 用 plain Input edit)→ display 也無 ItemSuffix**(僅 wrapper)。\n */\n showDisplayEndIcon?: boolean\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n value,\n onChange,\n placeholder = 'https://',\n className,\n disabled: disabledProp,\n label,\n showDisplayEndIcon = false,\n readOnly,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n const fieldCtx = useFieldContext()\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // spec field-controls.spec.md L125「readOnly 原生屬性自動覆蓋 mode」契約落地。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const emptyDisplay = useFieldEmptyDisplay()\n const isEditable = resolvedMode === 'edit'\n // chrome resolution:per-prop > context > 'default'\n const resolvedVariant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n\n // 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md\n // resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、\n // aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框\n // (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。\n const fieldInvalid = useResolvedFieldInvalid(errorProp)\n\n // ── Local state / refs ──────────────────────────────────────────────────\n // 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,\n // 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash\n // (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。\n const [editing, setEditing] = React.useState(false)\n const [localValue, setLocalValue] = React.useState(value ?? '')\n const [localError, setLocalError] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n\n // Sync external value → local\n React.useEffect(() => {\n if (!editing) setLocalValue(value ?? '')\n }, [value, editing])\n\n // Merge refs\n const setRef = React.useCallback((el: HTMLInputElement | null) => {\n inputRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el\n }, [ref])\n\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const focusRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])\n\n // ── mode='display' ─────────────────────────────────────────────────────\n // 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。\n // 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。\n // Default(showDisplayEndIcon=false):無 wrapper 裸 anchor — backward compat。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper 包覆 anchor,\n // 與 cell edit (`<Input naked>`) 同 DOM 結構消除像素偏移(無 ItemSuffix,因 edit 也無 endIcon)。\n if (resolvedMode === 'display') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) display typography canonical):非 D-path bare\n // anchor / span 必套 `fieldDisplayTextClass(size)`(sm/md→text-body,lg→text-body-lg)\n // — 對齊跨 Field family display 視覺尺寸統一。原無 font-size class → 用 browser default\n // 字體 → 跟其他 Field display 不一致(user 抓 I2)。truncate 同需,長 URL ellipsis(I1)。\n if (!value) return <span className={cn(fieldDisplayTextClass(size), fieldEmptyColorClass(resolvedMode), 'block truncate')}>{emptyDisplay}</span>\n return <span className={cn(fieldDisplayTextClass(size), 'block truncate')}>{renderLinkAnchor(value, label)}</span>\n }\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'display', variant: resolvedVariant, size }), className)}\n data-field-mode=\"display\"\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {value\n ? renderLinkAnchor(value, label)\n : <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>\n }\n </span>\n </div>\n )\n }\n\n const hasValidValue = !!value && isValidUrl(value)\n const showLink = isEditable && hasValidValue && !editing && !localError\n const error = fieldInvalid || localError\n\n const handleEdit = () => {\n setEditing(true)\n if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current)\n focusRafIdRef.current = requestAnimationFrame(() => {\n focusRafIdRef.current = 0\n inputRef.current?.focus()\n })\n }\n\n const handleBlur = () => {\n setEditing(false)\n const trimmed = localValue.trim()\n if (!trimmed) {\n // Empty is OK — clear value\n setLocalError(false)\n onChange?.('')\n return\n }\n if (isValidUrl(trimmed)) {\n setLocalError(false)\n onChange?.(trimmed)\n } else {\n setLocalError(true)\n }\n }\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setLocalValue(e.target.value)\n // Clear error on edit (blur validation)\n if (localError) setLocalError(false)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') inputRef.current?.blur()\n if (e.key === 'Escape') {\n setLocalValue(value ?? '')\n setLocalError(false)\n setEditing(false)\n }\n }\n\n // readonly — 顯示藍色連結(可點擊)\n // disabled — 顯示純文字 fg-disabled(不可點擊)\n if (!isEditable) {\n const displayText = value ? (label || formatHostname(value)) : null\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, size }), className)}\n data-field-mode={resolvedMode}\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {resolvedMode === 'disabled'\n ? (displayText\n ? <span className=\"text-fg-disabled\">{displayText}</span>\n : <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>)\n : (value\n ? renderLinkAnchor(value, label)\n : <span className={fieldEmptyColorClass(resolvedMode)}>{emptyDisplay}</span>)\n }\n </span>\n </div>\n )\n }\n\n // edit — link display mode(有合法 URL 且未在編輯中)\n if (showLink) {\n return (\n <div\n // 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,\n // 否則 <Field invalid> 下 link state 完全無 error 呈現\n className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <span className=\"flex-1 min-w-0\">\n {value && renderLinkAnchor(value, label)}\n </span>\n <ItemInlineAction\n size={size ?? 'md'}\n action={{ icon: Pencil, label: '編輯連結', onClick: handleEdit }} // i18n-allow: DS default inline-action label\n />\n </div>\n )\n }\n\n // edit — text input mode(正在編輯、無值、或格式錯誤)\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),\n className,\n )}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <input\n ref={setRef}\n type=\"url\"\n id={idProp ?? fieldCtx?.id}\n value={localValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={bareInputStyles}\n {...props}\n />\n </div>\n )\n }\n)\nLinkInput.displayName = 'LinkInput'\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 linkInputMeta = {\n component: 'LinkInput',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);\n // readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。\n states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],\n tokens: {\n // bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。\n bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],\n ring: [],\n },\n} as const\n\nexport { LinkInput }\n"],"names":[],"mappings":"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"}
|
|
@@ -14,10 +14,22 @@ export interface MenuItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
14
14
|
children: React.ReactNode;
|
|
15
15
|
/** 次要說明文字,顯示在 label 下方 */
|
|
16
16
|
description?: React.ReactNode;
|
|
17
|
-
/** 左側 icon(LucideIcon),與 avatar 互斥 */
|
|
17
|
+
/** 左側 icon(LucideIcon),與 avatar / startContent 互斥 */
|
|
18
18
|
startIcon?: LucideIcon;
|
|
19
|
-
/**
|
|
19
|
+
/** startIcon 的 className 透傳(2026-07-08 WM 戰役:status 類彩色 icon 需求;disabled 色仍勝出) */
|
|
20
|
+
startIconClassName?: string;
|
|
21
|
+
/** 左側頭像資料(AvatarData),元件內部渲染 Avatar。與 startIcon / startContent 互斥 */
|
|
20
22
|
avatar?: AvatarData;
|
|
23
|
+
/**
|
|
24
|
+
* 左側自訂內容(ReactNode),與 startIcon / avatar 互斥。與 `endContent` 對稱。
|
|
25
|
+
*
|
|
26
|
+
* **佔位與排列邏輯跟 startIcon 一模一樣**(2026-07-10 WM TypeIcon 彩色徽章需求):
|
|
27
|
+
* 渲染在同一個 prefix 對齊容器(`itemPrefixAlignVariants` SSOT)內 —— gap / 對齊 /
|
|
28
|
+
* shrink 全繼承,零平行 layout 邏輯,不可能與 startIcon 漂移。Consumer 唯一責任
|
|
29
|
+
* 是節點尺寸對齊 `ICON_SIZE[size]`(sm/md=16px、lg=20px;如 `size-4` 徽章)。
|
|
30
|
+
* 典型場景:類型徽章(彩底 + glyph)、status 圓點組合等 LucideIcon 表達不了的 leading 視覺。
|
|
31
|
+
*/
|
|
32
|
+
startContent?: React.ReactNode;
|
|
21
33
|
/** 顯示 checkbox(多選模式由父層控制) */
|
|
22
34
|
checkbox?: boolean;
|
|
23
35
|
/** Checkbox 選中狀態 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/menu-item.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAA;AA0BlF,QAAA,MAAM,gBAAgB;;8EAkBrB,CAAA;AASD,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC5D,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC,eAAe;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,
|
|
1
|
+
{"version":3,"file":"menu-item.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/menu-item.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAA;AA0BlF,QAAA,MAAM,gBAAgB;;8EAkBrB,CAAA;AASD,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC5D,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC,eAAe;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,qDAAqD;IACrD,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,qEAAqE;IACrE,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe,CAAA;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,SAAS;IACT,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/B;AAeD,QAAA,MAAM,QAAQ,sFA6Kb,CAAA;AAeD,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,QAAA,MAAM,SAAS,uFAWd,CAAA;AAKD,MAAM,WAAW,eAAgB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC3E,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,QAAA,MAAM,UAAU,wFAUf,CAAA;AAID,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;CAef,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -40,7 +40,9 @@ const MenuItem = React.forwardRef(
|
|
|
40
40
|
children,
|
|
41
41
|
description,
|
|
42
42
|
startIcon: StartIcon,
|
|
43
|
+
startIconClassName,
|
|
43
44
|
avatar,
|
|
45
|
+
startContent,
|
|
44
46
|
checkbox,
|
|
45
47
|
checked,
|
|
46
48
|
selected,
|
|
@@ -61,7 +63,7 @@ const MenuItem = React.forwardRef(
|
|
|
61
63
|
const avatarPx = avatar ? description ? AVATAR_SIZE.block[sizeKey] : AVATAR_SIZE.inline[sizeKey] : 0;
|
|
62
64
|
const isBlockAlign = avatarPx > 24 && !!description;
|
|
63
65
|
const prefixAlign = isBlockAlign ? `block-${sizeKey}` : "inline";
|
|
64
|
-
const hasPrefix = !!StartIcon || !!avatar || checkbox;
|
|
66
|
+
const hasPrefix = !!StartIcon || !!avatar || !!startContent || checkbox;
|
|
65
67
|
if (header) {
|
|
66
68
|
return /* @__PURE__ */ jsx(
|
|
67
69
|
"div",
|
|
@@ -102,13 +104,23 @@ const MenuItem = React.forwardRef(
|
|
|
102
104
|
onKeyDown: disabled ? void 0 : props.onKeyDown,
|
|
103
105
|
children: [
|
|
104
106
|
hasPrefix && /* @__PURE__ */ jsxs("div", { className: cn(itemPrefixAlignVariants({ align: prefixAlign })), children: [
|
|
105
|
-
checkbox &&
|
|
107
|
+
checkbox && // 2026-07-14 a11y(axe nested-interactive):此 checkbox 是純視覺 indicator —— checked
|
|
108
|
+
// ARIA state 由外層 row 單一 owner 宣告(Radix menuitemcheckbox / cmdk option /
|
|
109
|
+
// select-all row role="checkbox",見上方「ARIA 單一 owner gate」)。Radix Checkbox 渲
|
|
110
|
+
// focusable <button role="checkbox">,巢狀在互動 row 內 = axe nested-interactive
|
|
111
|
+
// violation;tabIndex=-1 不夠(axe-core no-focusable-content 對 tabindex<0 仍回
|
|
112
|
+
// violation,僅 messageKey 換 notHidden)。aria-hidden 移出 AT tree(消 SR 內外雙重
|
|
113
|
+
// checkbox 宣告)+ inert 移除 focusability(axe focusDisabled 認 inert → clean pass)。
|
|
114
|
+
// inert:React 18 無 typing(React 19 才內建),以 spread cast 傳字串 attr。
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
106
116
|
Checkbox,
|
|
107
117
|
{
|
|
108
118
|
size: CHECKBOX_SIZE[sizeKey],
|
|
109
119
|
checked: checked === true ? true : checked === "indeterminate" ? "indeterminate" : false,
|
|
110
120
|
disabled,
|
|
111
121
|
tabIndex: -1,
|
|
122
|
+
"aria-hidden": true,
|
|
123
|
+
...{ inert: "" },
|
|
112
124
|
className: "pointer-events-none"
|
|
113
125
|
}
|
|
114
126
|
),
|
|
@@ -117,7 +129,7 @@ const MenuItem = React.forwardRef(
|
|
|
117
129
|
{
|
|
118
130
|
"data-prefix-type": "icon",
|
|
119
131
|
size: iconPx,
|
|
120
|
-
className: cn("shrink-0", disabled && "text-fg-disabled"),
|
|
132
|
+
className: cn("shrink-0", startIconClassName, disabled && "text-fg-disabled"),
|
|
121
133
|
"aria-hidden": true
|
|
122
134
|
}
|
|
123
135
|
),
|
|
@@ -131,6 +143,16 @@ const MenuItem = React.forwardRef(
|
|
|
131
143
|
hoverCard: avatar.hoverCard,
|
|
132
144
|
size: avatarPx
|
|
133
145
|
}
|
|
146
|
+
),
|
|
147
|
+
startContent && // 自訂 leading 節點:同容器 = 佔位/排列與 startIcon 一模一樣(見 props JSDoc)。
|
|
148
|
+
// disabled 用 opacity-disabled(對齊本元件 suffix slot 對自訂 ReactNode 的同款處理)。
|
|
149
|
+
/* @__PURE__ */ jsx(
|
|
150
|
+
"span",
|
|
151
|
+
{
|
|
152
|
+
"data-prefix-type": "content",
|
|
153
|
+
className: cn("flex shrink-0 items-center", disabled && "opacity-disabled"),
|
|
154
|
+
children: startContent
|
|
155
|
+
}
|
|
134
156
|
)
|
|
135
157
|
] }),
|
|
136
158
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.js","sources":["../../../src/components/Menu/menu-item.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 DropdownMenu / SelectMenu 等 menu 類 DS 元件 wrap 消費,end-user app 請用 wrapper 元件。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\n// Row primitive 共用常數——統一從 item-layout pattern module 引入\nimport { ICON_SIZE, AVATAR_SIZE, ItemContent, itemPrefixAlignVariants, ROW_PADDING_BY_SIZE } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * MenuItem — 所有 menu 類元件的共用視覺佈局層\n *\n * SelectMenu、DropdownMenu、未來的 ContextMenu 等都消費這個元件。\n * 它只負責 layout(padding、gap、prefix alignment、typography),\n * 互動行為由各 menu 的 Radix primitive 外層控制。\n *\n * ── 結構 ──\n * [checkbox?] [startIcon? | avatar?] [label + description?]\n *\n * ── Prefix 對齊規則(24px 閾值)──\n * prefix ≤ 24px → h-[1lh],對齊第一行 label\n * prefix > 24px → h-[calc(1lh+var(--item-gap-label-desc-scanning)+desc_1lh)](size-aware -scanning[-lg] 變體),對齊文字區塊\n * 無 description → prefix 上限 24px\n *\n * ── Size ──\n * sm / md / lg — 單行高度對齊 field-height token\n */\n\nconst CHECKBOX_SIZE: Record<string, 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'md', lg: 'lg' }\n\n// ── Item variants ──\nconst menuItemVariants = cva(\n [\n 'flex items-start gap-2 px-3 w-full',\n 'cursor-pointer select-none',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:bg-neutral-hover',\n ],\n {\n variants: {\n // 消費 ROW_PADDING_BY_SIZE SSOT(item-anatomy.tsx 導出)\n // 改一處 → 全 row primitive 自動同步(消除前 SidebarMenuButton 獨立實作 risk)\n size: ROW_PADDING_BY_SIZE,\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Prefix alignment container ──\n// 消費 `itemPrefixAlignVariants`(SSOT from patterns/element-anatomy/item-anatomy.tsx)\n// 原本 MenuItem 自管 cva 造成 drift 風險(block formula 改動需同步);改為共用 SSOT\n// 讓公式只有一份,改 item-anatomy 一處 → MenuItem 自動同步。\n\n// ── Component ──\n\nexport interface MenuItemProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof menuItemVariants> {\n /** Label 文字 */\n children: React.ReactNode\n /** 次要說明文字,顯示在 label 下方 */\n description?: React.ReactNode\n /** 左側 icon(LucideIcon),與 avatar 互斥 */\n startIcon?: LucideIcon\n /** 左側頭像資料(AvatarData),元件內部渲染 Avatar。與 startIcon 互斥 */\n avatar?: AvatarData\n /** 顯示 checkbox(多選模式由父層控制) */\n checkbox?: boolean\n /** Checkbox 選中狀態 */\n checked?: boolean | 'indeterminate'\n /** 單選選中(bg-neutral-selected 背景高亮,持續選中狀態) */\n selected?: boolean\n /** 後綴 Tag(ReactNode),靠右對齊 */\n tag?: React.ReactNode\n /** 後綴自訂內容(ReactNode),用於 DropdownMenu 的 badge/endIcon/shortcut 等 */\n endContent?: React.ReactNode\n /** 停用 */\n disabled?: boolean\n /** 作為群組標題(不可選,font-medium,fg-muted) */\n header?: boolean\n /**\n * Label 最大行數(line-clamp 截斷,超過顯示 ellipsis)。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(單行截斷,符合選單快速掃視需求)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → **明確**不截斷,自然 wrap 任意行數\n *\n * 為什麼用 `'none'` 而不是 `undefined` 表達不截斷?React props 的 destructure default\n * 在 `undefined` 時會接管,所以 `<MenuItem labelMaxLines={undefined}>` 等同沒傳,\n * 會 fallback 到預設 `1`。要明確覆寫成「不截斷」,必須用一個非 undefined 的 sentinel。\n */\n labelMaxLines?: number | 'none'\n /**\n * Description 最大行數。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(跟 label 對稱,維持掃視節奏)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → 明確不截斷\n *\n * 為什麼預設 1?Menu 的設計目的是「快速掃視多個選項挑一個」,垂直空間是\n * 寶貴的——一個過高的 item 會破壞 row rhythm,讓使用者眼睛重新校準。description\n * 跟 label 對稱地截到 1 行,確保所有 item 高度一致(無 desc / 有 desc 兩種高度)。\n * Consumer 若有合理理由要 2 行 description,可顯式 override。\n */\n descMaxLines?: number | 'none'\n}\n\n/** 把 maxLines 轉成 line-clamp class;'none' / 0 → 空字串 */\nfunction lineClampClass(maxLines: number | 'none'): string {\n if (maxLines === 'none' || !maxLines) return ''\n if (maxLines === 1) return 'line-clamp-1'\n if (maxLines === 2) return 'line-clamp-2'\n if (maxLines === 3) return 'line-clamp-3'\n if (maxLines === 4) return 'line-clamp-4'\n if (maxLines === 5) return 'line-clamp-5'\n if (maxLines === 6) return 'line-clamp-6'\n return ''\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(\n (\n {\n children,\n description,\n startIcon: StartIcon,\n avatar,\n checkbox,\n checked,\n selected,\n tag,\n endContent,\n disabled,\n header,\n size,\n labelMaxLines = 1,\n descMaxLines = 1,\n className,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const labelClampClass = lineClampClass(labelMaxLines)\n const descClampClass = lineClampClass(descMaxLines)\n const iconPx = ICON_SIZE[sizeKey]\n\n // ── 決定 avatar 容器尺寸 + 對齊模式 ──\n // 有 description → 使用 block 尺寸(32/40px),block 對齊\n // 無 description → 使用 inline 尺寸(20/24px),inline 對齊\n const avatarPx = avatar\n ? (description ? AVATAR_SIZE.block[sizeKey] : AVATAR_SIZE.inline[sizeKey])\n : 0\n const isBlockAlign = avatarPx > 24 && !!description\n\n const prefixAlign = isBlockAlign\n ? (`block-${sizeKey}` as const)\n : 'inline'\n\n const hasPrefix = !!StartIcon || !!avatar || checkbox\n\n // ── Header variant ──\n if (header) {\n return (\n <div\n ref={ref}\n className={cn(\n menuItemVariants({ size }),\n 'font-medium text-fg-muted cursor-default pointer-events-none',\n className,\n )}\n role=\"presentation\"\n {...props}\n >\n {children}\n </div>\n )\n }\n\n // ── ARIA 單一 owner gate(2026-07-05)──\n // consumer(DropdownMenu 各 item / SelectMenu option row)把 MenuItem 巢狀在 Radix / cmdk\n // 互動節點內時傳 role=\"presentation\":外層 primitive 是唯一 ARIA owner(aria-checked /\n // aria-disabled 由外層宣告)。presentation 節點不得掛 ARIA state(axe aria-prohibited-attr\n // + 內外雙重宣告)→ 同步抑制 role=\"option\" 與 aria-selected / aria-disabled 預設;\n // data-selected / data-disabled 保留(純樣式 hook,非 ARIA)。正規 listbox option 用法\n //(不傳 role)與 select-all row(role=\"checkbox\")不受影響。\n const isPresentation = props.role === 'presentation'\n\n return (\n <div\n ref={ref}\n role={isPresentation ? 'presentation' : 'option'}\n aria-selected={isPresentation ? undefined : selected || (checked === true) || undefined}\n aria-disabled={isPresentation ? undefined : disabled || undefined}\n data-selected={selected ? '' : undefined}\n data-disabled={disabled ? '' : undefined}\n className={cn(\n menuItemVariants({ size }),\n !disabled && !selected && 'hover:bg-neutral-hover',\n !disabled && selected && 'bg-neutral-selected',\n // disabled 用 cursor-not-allowed(對齊 Button + Material/Polaris/Atlassian);\n // pointer-events-none 會讓 cursor 失效,改用 aria-disabled + onClick guard\n disabled && 'text-fg-disabled cursor-not-allowed',\n className,\n )}\n // 2026-07-04 robustness:先 spread 其餘 props、後放 guarded handlers — 消除\n // 「handler 置前 + 後方 runtime filter 同名 key」對屬性順序的隱性依賴,行為不變\n {...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}\n onClick={disabled ? undefined : props.onClick}\n onKeyDown={disabled ? undefined : props.onKeyDown}\n >\n {/* Prefix 對齊容器 */}\n {hasPrefix && (\n <div className={cn(itemPrefixAlignVariants({ align: prefixAlign }))}>\n {checkbox && (\n <Checkbox\n size={CHECKBOX_SIZE[sizeKey]}\n checked={checked === true ? true : checked === 'indeterminate' ? 'indeterminate' : false}\n disabled={disabled}\n tabIndex={-1}\n className=\"pointer-events-none\"\n />\n )}\n {StartIcon && (\n <StartIcon\n data-prefix-type=\"icon\"\n size={iconPx}\n className={cn('shrink-0', disabled && 'text-fg-disabled')}\n aria-hidden\n />\n )}\n {avatar && (\n <Avatar\n data-prefix-type=\"avatar\"\n src={avatar.src}\n alt={avatar.alt}\n color={avatar.color}\n hoverCard={avatar.hoverCard}\n size={avatarPx}\n />\n )}\n </div>\n )}\n\n {/* Content — 消費 ItemContent primitive(SSOT)。\n scanning mode 跟隨 size:sm/md = caption,lg = body(desc 比 label lg 16 小 1 tier)。\n labelClampClass / descClampClass 透過 className escape hatch 傳入(MenuItem 特化 labelMaxLines / descMaxLines 語意)。 */}\n <ItemContent\n label={children}\n description={description}\n mode=\"scanning\"\n size={sizeKey === 'lg' ? 'lg' : 'md'}\n descriptionTone={disabled ? 'disabled' : 'secondary'}\n labelTruncate={false}\n labelClassName={cn(labelClampClass || 'break-words', disabled && 'text-fg-disabled')}\n descriptionClassName={cn(descClampClass || 'break-words')}\n />\n\n {(tag || endContent) && (\n // @row-slot-handcraft-allow: MenuItem 自身即 row primitive(item-anatomy Family 1 owner),此 tag/endContent 容器是 MenuItem 自有 suffix slot 實作,非外部 consumer hand-craft → 不再 wrap ItemSuffix\n <div className={cn(\n 'flex items-center gap-2 shrink-0 h-[1lh] ml-auto',\n disabled && 'opacity-disabled',\n )}>\n {tag}\n {endContent}\n </div>\n )}\n </div>\n )\n }\n)\nMenuItem.displayName = 'MenuItem'\n\n// ── Group ──\n// MenuGroup — Menu-like group primitive(跨元件設計語言統一,SSOT 見\n// `patterns/element-anatomy/item-anatomy.spec.md`「Group auto-separation」)\n//\n// 設計語言:每個 group 上下 8px + 相鄰 group 間 border-divider + 視覺 gap 16px\n//\n// 本元件用 Pattern A:`py-2 [&+&]:border-t [&+&]:border-divider`\n// (適用外層容器無 py-2 的情境,例 Command.List——Group 自帶邊界 padding)\n//\n// **視覺必須跟 DropdownMenuGroup 等價**(見 dropdown-menu.tsx Pattern B 對照)。\n// 改動前先讀 item-anatomy.spec.md「Group auto-separation」確認視覺不漂移。\n\nexport interface MenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuGroup = React.forwardRef<HTMLDivElement, MenuGroupProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n className={cn('py-2 [&+&]:border-t [&+&]:border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuGroup.displayName = 'MenuGroup'\n\n// ── Footer ──\n\nexport interface MenuFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuFooter = React.forwardRef<HTMLDivElement, MenuFooterProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('py-2 border-t border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuFooter.displayName = 'MenuFooter'\n\n// Story auto-compile metadata — Phase 1+2 migration\nexport const menuItemMeta = {\n component: 'MenuItem',\n family: 1,\n variants: {},\n sizes: {\n sm: {},\n md: {},\n lg: {},\n },\n defaultSize: 'md',\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected'],\n fg: ['text-fg-muted', 'text-fg-disabled'],\n },\n} as const\n\nexport { MenuItem, MenuGroup, MenuFooter, menuItemVariants }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu-item.js","sources":["../../../src/components/Menu/menu-item.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 DropdownMenu / SelectMenu 等 menu 類 DS 元件 wrap 消費,end-user app 請用 wrapper 元件。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\n// Row primitive 共用常數——統一從 item-layout pattern module 引入\nimport { ICON_SIZE, AVATAR_SIZE, ItemContent, itemPrefixAlignVariants, ROW_PADDING_BY_SIZE } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * MenuItem — 所有 menu 類元件的共用視覺佈局層\n *\n * SelectMenu、DropdownMenu、未來的 ContextMenu 等都消費這個元件。\n * 它只負責 layout(padding、gap、prefix alignment、typography),\n * 互動行為由各 menu 的 Radix primitive 外層控制。\n *\n * ── 結構 ──\n * [checkbox?] [startIcon? | avatar?] [label + description?]\n *\n * ── Prefix 對齊規則(24px 閾值)──\n * prefix ≤ 24px → h-[1lh],對齊第一行 label\n * prefix > 24px → h-[calc(1lh+var(--item-gap-label-desc-scanning)+desc_1lh)](size-aware -scanning[-lg] 變體),對齊文字區塊\n * 無 description → prefix 上限 24px\n *\n * ── Size ──\n * sm / md / lg — 單行高度對齊 field-height token\n */\n\nconst CHECKBOX_SIZE: Record<string, 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'md', lg: 'lg' }\n\n// ── Item variants ──\nconst menuItemVariants = cva(\n [\n 'flex items-start gap-2 px-3 w-full',\n 'cursor-pointer select-none',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:bg-neutral-hover',\n ],\n {\n variants: {\n // 消費 ROW_PADDING_BY_SIZE SSOT(item-anatomy.tsx 導出)\n // 改一處 → 全 row primitive 自動同步(消除前 SidebarMenuButton 獨立實作 risk)\n size: ROW_PADDING_BY_SIZE,\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Prefix alignment container ──\n// 消費 `itemPrefixAlignVariants`(SSOT from patterns/element-anatomy/item-anatomy.tsx)\n// 原本 MenuItem 自管 cva 造成 drift 風險(block formula 改動需同步);改為共用 SSOT\n// 讓公式只有一份,改 item-anatomy 一處 → MenuItem 自動同步。\n\n// ── Component ──\n\nexport interface MenuItemProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof menuItemVariants> {\n /** Label 文字 */\n children: React.ReactNode\n /** 次要說明文字,顯示在 label 下方 */\n description?: React.ReactNode\n /** 左側 icon(LucideIcon),與 avatar / startContent 互斥 */\n startIcon?: LucideIcon\n /** startIcon 的 className 透傳(2026-07-08 WM 戰役:status 類彩色 icon 需求;disabled 色仍勝出) */\n startIconClassName?: string\n /** 左側頭像資料(AvatarData),元件內部渲染 Avatar。與 startIcon / startContent 互斥 */\n avatar?: AvatarData\n /**\n * 左側自訂內容(ReactNode),與 startIcon / avatar 互斥。與 `endContent` 對稱。\n *\n * **佔位與排列邏輯跟 startIcon 一模一樣**(2026-07-10 WM TypeIcon 彩色徽章需求):\n * 渲染在同一個 prefix 對齊容器(`itemPrefixAlignVariants` SSOT)內 —— gap / 對齊 /\n * shrink 全繼承,零平行 layout 邏輯,不可能與 startIcon 漂移。Consumer 唯一責任\n * 是節點尺寸對齊 `ICON_SIZE[size]`(sm/md=16px、lg=20px;如 `size-4` 徽章)。\n * 典型場景:類型徽章(彩底 + glyph)、status 圓點組合等 LucideIcon 表達不了的 leading 視覺。\n */\n startContent?: React.ReactNode\n /** 顯示 checkbox(多選模式由父層控制) */\n checkbox?: boolean\n /** Checkbox 選中狀態 */\n checked?: boolean | 'indeterminate'\n /** 單選選中(bg-neutral-selected 背景高亮,持續選中狀態) */\n selected?: boolean\n /** 後綴 Tag(ReactNode),靠右對齊 */\n tag?: React.ReactNode\n /** 後綴自訂內容(ReactNode),用於 DropdownMenu 的 badge/endIcon/shortcut 等 */\n endContent?: React.ReactNode\n /** 停用 */\n disabled?: boolean\n /** 作為群組標題(不可選,font-medium,fg-muted) */\n header?: boolean\n /**\n * Label 最大行數(line-clamp 截斷,超過顯示 ellipsis)。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(單行截斷,符合選單快速掃視需求)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → **明確**不截斷,自然 wrap 任意行數\n *\n * 為什麼用 `'none'` 而不是 `undefined` 表達不截斷?React props 的 destructure default\n * 在 `undefined` 時會接管,所以 `<MenuItem labelMaxLines={undefined}>` 等同沒傳,\n * 會 fallback 到預設 `1`。要明確覆寫成「不截斷」,必須用一個非 undefined 的 sentinel。\n */\n labelMaxLines?: number | 'none'\n /**\n * Description 最大行數。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(跟 label 對稱,維持掃視節奏)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → 明確不截斷\n *\n * 為什麼預設 1?Menu 的設計目的是「快速掃視多個選項挑一個」,垂直空間是\n * 寶貴的——一個過高的 item 會破壞 row rhythm,讓使用者眼睛重新校準。description\n * 跟 label 對稱地截到 1 行,確保所有 item 高度一致(無 desc / 有 desc 兩種高度)。\n * Consumer 若有合理理由要 2 行 description,可顯式 override。\n */\n descMaxLines?: number | 'none'\n}\n\n/** 把 maxLines 轉成 line-clamp class;'none' / 0 → 空字串 */\nfunction lineClampClass(maxLines: number | 'none'): string {\n if (maxLines === 'none' || !maxLines) return ''\n if (maxLines === 1) return 'line-clamp-1'\n if (maxLines === 2) return 'line-clamp-2'\n if (maxLines === 3) return 'line-clamp-3'\n if (maxLines === 4) return 'line-clamp-4'\n if (maxLines === 5) return 'line-clamp-5'\n if (maxLines === 6) return 'line-clamp-6'\n return ''\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(\n (\n {\n children,\n description,\n startIcon: StartIcon,\n startIconClassName,\n avatar,\n startContent,\n checkbox,\n checked,\n selected,\n tag,\n endContent,\n disabled,\n header,\n size,\n labelMaxLines = 1,\n descMaxLines = 1,\n className,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const labelClampClass = lineClampClass(labelMaxLines)\n const descClampClass = lineClampClass(descMaxLines)\n const iconPx = ICON_SIZE[sizeKey]\n\n // ── 決定 avatar 容器尺寸 + 對齊模式 ──\n // 有 description → 使用 block 尺寸(32/40px),block 對齊\n // 無 description → 使用 inline 尺寸(20/24px),inline 對齊\n const avatarPx = avatar\n ? (description ? AVATAR_SIZE.block[sizeKey] : AVATAR_SIZE.inline[sizeKey])\n : 0\n const isBlockAlign = avatarPx > 24 && !!description\n\n const prefixAlign = isBlockAlign\n ? (`block-${sizeKey}` as const)\n : 'inline'\n\n const hasPrefix = !!StartIcon || !!avatar || !!startContent || checkbox\n\n // ── Header variant ──\n if (header) {\n return (\n <div\n ref={ref}\n className={cn(\n menuItemVariants({ size }),\n 'font-medium text-fg-muted cursor-default pointer-events-none',\n className,\n )}\n role=\"presentation\"\n {...props}\n >\n {children}\n </div>\n )\n }\n\n // ── ARIA 單一 owner gate(2026-07-05)──\n // consumer(DropdownMenu 各 item / SelectMenu option row)把 MenuItem 巢狀在 Radix / cmdk\n // 互動節點內時傳 role=\"presentation\":外層 primitive 是唯一 ARIA owner(aria-checked /\n // aria-disabled 由外層宣告)。presentation 節點不得掛 ARIA state(axe aria-prohibited-attr\n // + 內外雙重宣告)→ 同步抑制 role=\"option\" 與 aria-selected / aria-disabled 預設;\n // data-selected / data-disabled 保留(純樣式 hook,非 ARIA)。正規 listbox option 用法\n //(不傳 role)與 select-all row(role=\"checkbox\")不受影響。\n const isPresentation = props.role === 'presentation'\n\n return (\n <div\n ref={ref}\n role={isPresentation ? 'presentation' : 'option'}\n aria-selected={isPresentation ? undefined : selected || (checked === true) || undefined}\n aria-disabled={isPresentation ? undefined : disabled || undefined}\n data-selected={selected ? '' : undefined}\n data-disabled={disabled ? '' : undefined}\n className={cn(\n menuItemVariants({ size }),\n !disabled && !selected && 'hover:bg-neutral-hover',\n !disabled && selected && 'bg-neutral-selected',\n // disabled 用 cursor-not-allowed(對齊 Button + Material/Polaris/Atlassian);\n // pointer-events-none 會讓 cursor 失效,改用 aria-disabled + onClick guard\n disabled && 'text-fg-disabled cursor-not-allowed',\n className,\n )}\n // 2026-07-04 robustness:先 spread 其餘 props、後放 guarded handlers — 消除\n // 「handler 置前 + 後方 runtime filter 同名 key」對屬性順序的隱性依賴,行為不變\n {...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}\n onClick={disabled ? undefined : props.onClick}\n onKeyDown={disabled ? undefined : props.onKeyDown}\n >\n {/* Prefix 對齊容器 */}\n {hasPrefix && (\n <div className={cn(itemPrefixAlignVariants({ align: prefixAlign }))}>\n {checkbox && (\n // 2026-07-14 a11y(axe nested-interactive):此 checkbox 是純視覺 indicator —— checked\n // ARIA state 由外層 row 單一 owner 宣告(Radix menuitemcheckbox / cmdk option /\n // select-all row role=\"checkbox\",見上方「ARIA 單一 owner gate」)。Radix Checkbox 渲\n // focusable <button role=\"checkbox\">,巢狀在互動 row 內 = axe nested-interactive\n // violation;tabIndex=-1 不夠(axe-core no-focusable-content 對 tabindex<0 仍回\n // violation,僅 messageKey 換 notHidden)。aria-hidden 移出 AT tree(消 SR 內外雙重\n // checkbox 宣告)+ inert 移除 focusability(axe focusDisabled 認 inert → clean pass)。\n // inert:React 18 無 typing(React 19 才內建),以 spread cast 傳字串 attr。\n <Checkbox\n size={CHECKBOX_SIZE[sizeKey]}\n checked={checked === true ? true : checked === 'indeterminate' ? 'indeterminate' : false}\n disabled={disabled}\n tabIndex={-1}\n aria-hidden\n {...({ inert: '' } as object)}\n className=\"pointer-events-none\"\n />\n )}\n {StartIcon && (\n <StartIcon\n data-prefix-type=\"icon\"\n size={iconPx}\n className={cn('shrink-0', startIconClassName, disabled && 'text-fg-disabled')}\n aria-hidden\n />\n )}\n {avatar && (\n <Avatar\n data-prefix-type=\"avatar\"\n src={avatar.src}\n alt={avatar.alt}\n color={avatar.color}\n hoverCard={avatar.hoverCard}\n size={avatarPx}\n />\n )}\n {startContent && (\n // 自訂 leading 節點:同容器 = 佔位/排列與 startIcon 一模一樣(見 props JSDoc)。\n // disabled 用 opacity-disabled(對齊本元件 suffix slot 對自訂 ReactNode 的同款處理)。\n <span\n data-prefix-type=\"content\"\n className={cn('flex shrink-0 items-center', disabled && 'opacity-disabled')}\n >\n {startContent}\n </span>\n )}\n </div>\n )}\n\n {/* Content — 消費 ItemContent primitive(SSOT)。\n scanning mode 跟隨 size:sm/md = caption,lg = body(desc 比 label lg 16 小 1 tier)。\n labelClampClass / descClampClass 透過 className escape hatch 傳入(MenuItem 特化 labelMaxLines / descMaxLines 語意)。 */}\n <ItemContent\n label={children}\n description={description}\n mode=\"scanning\"\n size={sizeKey === 'lg' ? 'lg' : 'md'}\n descriptionTone={disabled ? 'disabled' : 'secondary'}\n labelTruncate={false}\n labelClassName={cn(labelClampClass || 'break-words', disabled && 'text-fg-disabled')}\n descriptionClassName={cn(descClampClass || 'break-words')}\n />\n\n {(tag || endContent) && (\n // @row-slot-handcraft-allow: MenuItem 自身即 row primitive(item-anatomy Family 1 owner),此 tag/endContent 容器是 MenuItem 自有 suffix slot 實作,非外部 consumer hand-craft → 不再 wrap ItemSuffix\n <div className={cn(\n 'flex items-center gap-2 shrink-0 h-[1lh] ml-auto',\n disabled && 'opacity-disabled',\n )}>\n {tag}\n {endContent}\n </div>\n )}\n </div>\n )\n }\n)\nMenuItem.displayName = 'MenuItem'\n\n// ── Group ──\n// MenuGroup — Menu-like group primitive(跨元件設計語言統一,SSOT 見\n// `patterns/element-anatomy/item-anatomy.spec.md`「Group auto-separation」)\n//\n// 設計語言:每個 group 上下 8px + 相鄰 group 間 border-divider + 視覺 gap 16px\n//\n// 本元件用 Pattern A:`py-2 [&+&]:border-t [&+&]:border-divider`\n// (適用外層容器無 py-2 的情境,例 Command.List——Group 自帶邊界 padding)\n//\n// **視覺必須跟 DropdownMenuGroup 等價**(見 dropdown-menu.tsx Pattern B 對照)。\n// 改動前先讀 item-anatomy.spec.md「Group auto-separation」確認視覺不漂移。\n\nexport interface MenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuGroup = React.forwardRef<HTMLDivElement, MenuGroupProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n className={cn('py-2 [&+&]:border-t [&+&]:border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuGroup.displayName = 'MenuGroup'\n\n// ── Footer ──\n\nexport interface MenuFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuFooter = React.forwardRef<HTMLDivElement, MenuFooterProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('py-2 border-t border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuFooter.displayName = 'MenuFooter'\n\n// Story auto-compile metadata — Phase 1+2 migration\nexport const menuItemMeta = {\n component: 'MenuItem',\n family: 1,\n variants: {},\n sizes: {\n sm: {},\n md: {},\n lg: {},\n },\n defaultSize: 'md',\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected'],\n fg: ['text-fg-muted', 'text-fg-disabled'],\n },\n} as const\n\nexport { MenuItem, MenuGroup, MenuFooter, menuItemVariants }\n"],"names":[],"mappings":"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"}
|
|
@@ -14,15 +14,15 @@ export interface NumberFormatOptions {
|
|
|
14
14
|
locale?: string;
|
|
15
15
|
}
|
|
16
16
|
declare function formatNumber(value: number | null | undefined, options?: NumberFormatOptions): string;
|
|
17
|
-
export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange' | 'type'>, Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'>, NumberFormatOptions {
|
|
17
|
+
export interface NumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange' | 'type'>, Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant' | 'width'>, NumberFormatOptions {
|
|
18
18
|
/** Field display mode */
|
|
19
19
|
mode?: FieldMode;
|
|
20
20
|
/**
|
|
21
21
|
* Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。
|
|
22
22
|
* - `'default'`(預設)— 完整 Field wrapper chrome。
|
|
23
|
-
* - `
|
|
23
|
+
* (2026-07-09 `bare` variant 退役;naked = cell-as-input substrate,@internal)
|
|
24
24
|
*
|
|
25
|
-
* 透傳:在 `<Field variant="
|
|
25
|
+
* 透傳:在 `<Field variant="default">` 內自動繼承 context.variant;per-prop override context。
|
|
26
26
|
*/
|
|
27
27
|
variant?: FieldVariant;
|
|
28
28
|
/** Error 狀態(正交於 mode)。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-input.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/number-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"number-input.d.ts","sourceRoot":"","sources":["../../../src/components/NumberInput/number-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AACjH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uDAAuD,CAAA;AAC/F,OAAO,EAAE,kBAAkB,EAAmB,MAAM,gDAAgD,CAAA;AAOpG,MAAM,WAAW,mBAAmB;IAClC,WAAW;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,iBAAS,YAAY,CACnB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,OAAO,GAAE,mBAAwB,GAChC,MAAM,CAOR;AAOD,MAAM,WAAW,gBAIf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,UAAU,GAAG,MAAM,CAAC,EAChH,IAAI,CAAC,YAAY,CAAC,OAAO,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,EAC3E,mBAAmB;IACrB,yBAAyB;IACzB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS;IACT,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW;IACX,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACzC,qDAAqD;IACrD,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAKD,QAAA,MAAM,WAAW,2FA2HhB,CAAA;AAMD,eAAO,MAAM,eAAe;;;;;;;;;;;CAgBlB,CAAA;AAGV,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
|
-
import {
|
|
5
|
-
import { useFieldContext, useResolvedFieldInvalid, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldVariant, useResolvedFieldMode } from "../Field/field-context.js";
|
|
4
|
+
import { fieldWrapperStyles, bareInputStyles } from "../Field/field-wrapper.js";
|
|
5
|
+
import { useFieldContext, useResolvedFieldInvalid, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldVariant, useResolvedFieldMode, useFieldEmptyDisplay, fieldEmptyColorClass } from "../Field/field-context.js";
|
|
6
6
|
import { ItemInlineAction } from "../../patterns/element-anatomy/item-anatomy.js";
|
|
7
7
|
function formatNumber(value, options = {}) {
|
|
8
8
|
if (value == null) return "";
|
|
@@ -40,6 +40,7 @@ const NumberInput = React.forwardRef(
|
|
|
40
40
|
const disabled = useResolvedFieldDisabled(disabledProp);
|
|
41
41
|
const variant = useResolvedFieldVariant(variantProp);
|
|
42
42
|
const resolvedMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly });
|
|
43
|
+
const emptyDisplay = useFieldEmptyDisplay();
|
|
43
44
|
const [draft, setDraft] = React.useState(null);
|
|
44
45
|
if (resolvedMode !== "edit") {
|
|
45
46
|
return /* @__PURE__ */ jsx(
|
|
@@ -53,10 +54,10 @@ const NumberInput = React.forwardRef(
|
|
|
53
54
|
className: cn(
|
|
54
55
|
"flex-1 min-w-0",
|
|
55
56
|
resolvedMode === "disabled" && "text-fg-disabled cursor-not-allowed",
|
|
56
|
-
// 2026-
|
|
57
|
-
value == null && resolvedMode
|
|
57
|
+
// 2026-07-09 空值符號前景 + M24:fieldEmptyColorClass = disabled→fg-disabled / else→foreground(同 readonly value 色)
|
|
58
|
+
value == null && fieldEmptyColorClass(resolvedMode)
|
|
58
59
|
),
|
|
59
|
-
children: value == null ?
|
|
60
|
+
children: value == null ? emptyDisplay : formatNumber(value, { precision, prefix, suffix, locale })
|
|
60
61
|
}
|
|
61
62
|
)
|
|
62
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-input.js","sources":["../../../src/components/NumberInput/number-input.tsx"],"sourcesContent":["import * as React from 'react'\nimport { type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport type { InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n// ── Format ──────────────────────────────────────────────────────────────────\n\nexport interface NumberFormatOptions {\n /** 小數位數 */\n precision?: number\n /** 前綴(如 '$'、'NT$') */\n prefix?: string\n /** 後綴(如 '%'、'元') */\n suffix?: string\n /** locale(預設 'en-US') */\n locale?: string\n}\n\nfunction formatNumber(\n value: number | null | undefined,\n options: NumberFormatOptions = {},\n): string {\n if (value == null) return ''\n const { precision, prefix = '', suffix = '', locale = 'en-US' } = options\n const formatted = precision != null\n ? value.toLocaleString(locale, { minimumFractionDigits: precision, maximumFractionDigits: precision })\n : value.toLocaleString(locale)\n return `${prefix}${formatted}${suffix}`\n}\n\n// Phase B1(2026-05-05):NumberInputDisplay 退場。\n// 改用 `<NumberInput mode=\"display\" value={...} prefix={...} ... />`,format 邏輯在 mode='display' 分支重用。\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface NumberInputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange' | 'type'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'>,\n NumberFormatOptions {\n /** Field display mode */\n mode?: FieldMode\n /**\n * Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。\n * - `'default'`(預設)— 完整 Field wrapper chrome。\n * - `'bare'` — 透明 variant,hover/focus 才 reveal(Toolbar inline / DataTable cell)。\n *\n * 透傳:在 `<Field variant=\"bare\">` 內自動繼承 context.variant;per-prop override context。\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。 */\n error?: boolean\n /** 數值 */\n value?: number | null\n /** 數值變更 */\n onChange?: (value: number | null) => void\n /** 右側 inline action — 宣告式 API,Field 根據 size 自動渲染。 */\n endAction?: InlineActionConfig\n /**\n * 右側 slot(ReactNode)— escape hatch 供 consumer 放自訂元素(如 stepper button group / 自訂 popover trigger)。\n * 跟 `endAction` 互斥(同時傳 endSlot 會優先,endAction 被忽略)。\n * 規則對齊 Input.endSlot:90% case 用 endAction 宣告式 API,10% config 表達不出時走 endSlot。\n */\n endSlot?: React.ReactNode\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n value,\n onChange,\n precision,\n prefix,\n suffix,\n locale,\n endAction,\n endSlot,\n className,\n disabled: disabledProp,\n readOnly,\n id: idProp,\n onBlur: onBlurProp,\n onKeyDown: onKeyDownProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // chrome 透傳:per-prop override context;context 沒值則 'default'\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n\n // 2026-07-05 D4 draft state:controlled value 回寫會吃掉輸入中間態('-'、'1.'、'0.0' 等\n // parse 後 lossy 的 raw 字串)— 例:已有值 5 全選打「-」→ onChange(null) 回寫把 DOM 重設\n // 為空,負號被吃、無法打出 -5。修法對齊 Ant InputNumber / Adobe Spectrum NumberField 的\n // internal draft string canonical:輸入期間 DOM 顯示以 local draft 優先,parse 成功即時同步\n // onChange(parsed);blur / Enter commit 清 draft 回 value 顯示;Escape 一併棄 draft(對齊\n // form-validation 規則 4 回復原值)。宣告於 non-edit early return 之前(Rules-of-Hooks,\n // 同 LinkInput 2026-07-04 修法)。\n const [draft, setDraft] = React.useState<string | null>(null)\n\n // display / readonly / disabled 都顯示格式化值(span 取代 input)\n if (resolvedMode !== 'edit') {\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: variant, size }), className)}\n data-field-mode={resolvedMode}\n >\n <span\n className={cn(\n 'flex-1 min-w-0',\n resolvedMode === 'disabled' && 'text-fg-disabled cursor-not-allowed',\n // 2026-05-31 M24:disabled > muted。disabled 時不可再套 muted(否則 neutral-7 蓋過 disabled neutral-6)\n value == null && resolvedMode !== 'disabled' && 'text-fg-muted',\n )}\n >\n {value == null ? EMPTY_DISPLAY : formatNumber(value, { precision, prefix, suffix, locale })}\n </span>\n </div>\n )\n }\n\n // edit 模式:raw 數值輸入(draft 承載中間態;value 面語意不變 — ''/'-' 視為空、NaN 忽略)\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const raw = e.target.value\n setDraft(raw)\n if (!onChange) return\n if (raw === '' || raw === '-') {\n onChange(null)\n return\n }\n const parsed = Number(raw)\n if (!Number.isNaN(parsed)) {\n onChange(parsed)\n }\n }\n\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),\n className,\n )}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <input\n ref={ref}\n type=\"text\"\n inputMode=\"decimal\"\n id={idProp ?? fieldCtx?.id}\n // 輸入期間 draft(raw 字串)優先;非輸入期間才從 value prop 同步顯示(2026-07-05 D4)\n value={draft ?? (value != null ? String(value) : '')}\n onChange={handleChange}\n // blur / Enter = commit(清 draft,顯示回 committed value);Escape 棄 draft\n onBlur={(e) => { setDraft(null); onBlurProp?.(e) }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === 'Escape') setDraft(null)\n onKeyDownProp?.(e)\n }}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={bareInputStyles}\n {...props}\n />\n {endSlot ? (\n // endSlot escape hatch:consumer 自控右側 slot(對齊 Input.endSlot canonical)\n endSlot\n ) : endAction ? (\n <ItemInlineAction action={endAction} size={size ?? 'md'} />\n ) : null}\n </div>\n )\n }\n)\nNumberInput.displayName = 'NumberInput'\n\n// code-quality-allow: dead-export — public API surface — consumer-exposed for future use\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 numberInputMeta = {\n component: 'NumberInput',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — Field chrome 無按壓態;stepper 按壓屬內嵌 ItemInlineActionButton(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\n// code-quality-allow: dead-export — public API surface — consumer-exposed for future use\nexport { NumberInput, formatNumber }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"number-input.js","sources":["../../../src/components/NumberInput/number-input.tsx"],"sourcesContent":["import * as React from 'react'\nimport { type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'\nimport type { InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { fieldWrapperStyles, bareInputStyles } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n// ── Format ──────────────────────────────────────────────────────────────────\n\nexport interface NumberFormatOptions {\n /** 小數位數 */\n precision?: number\n /** 前綴(如 '$'、'NT$') */\n prefix?: string\n /** 後綴(如 '%'、'元') */\n suffix?: string\n /** locale(預設 'en-US') */\n locale?: string\n}\n\nfunction formatNumber(\n value: number | null | undefined,\n options: NumberFormatOptions = {},\n): string {\n if (value == null) return ''\n const { precision, prefix = '', suffix = '', locale = 'en-US' } = options\n const formatted = precision != null\n ? value.toLocaleString(locale, { minimumFractionDigits: precision, maximumFractionDigits: precision })\n : value.toLocaleString(locale)\n return `${prefix}${formatted}${suffix}`\n}\n\n// Phase B1(2026-05-05):NumberInputDisplay 退場。\n// 改用 `<NumberInput mode=\"display\" value={...} prefix={...} ... />`,format 邏輯在 mode='display' 分支重用。\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface NumberInputProps\n // `defaultValue` 一併 Omit(2026-07-14 dim-26):number-input.spec.md「controlled-only,不支援\n // defaultValue」宣稱的型別面機械封鎖 — 原本仍在型別 surface 且經 {...props} spread 到已\n // controlled 的 input(React value/defaultValue dev warning)。與 LinkInput(link-input.tsx)同款。\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange' | 'type'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant' | 'width'>,\n NumberFormatOptions {\n /** Field display mode */\n mode?: FieldMode\n /**\n * Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。\n * - `'default'`(預設)— 完整 Field wrapper chrome。\n * (2026-07-09 `bare` variant 退役;naked = cell-as-input substrate,@internal)\n *\n * 透傳:在 `<Field variant=\"default\">` 內自動繼承 context.variant;per-prop override context。\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。 */\n error?: boolean\n /** 數值 */\n value?: number | null\n /** 數值變更 */\n onChange?: (value: number | null) => void\n /** 右側 inline action — 宣告式 API,Field 根據 size 自動渲染。 */\n endAction?: InlineActionConfig\n /**\n * 右側 slot(ReactNode)— escape hatch 供 consumer 放自訂元素(如 stepper button group / 自訂 popover trigger)。\n * 跟 `endAction` 互斥(同時傳 endSlot 會優先,endAction 被忽略)。\n * 規則對齊 Input.endSlot:90% case 用 endAction 宣告式 API,10% config 表達不出時走 endSlot。\n */\n endSlot?: React.ReactNode\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n value,\n onChange,\n precision,\n prefix,\n suffix,\n locale,\n endAction,\n endSlot,\n className,\n disabled: disabledProp,\n readOnly,\n id: idProp,\n onBlur: onBlurProp,\n onKeyDown: onKeyDownProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n const fieldCtx = useFieldContext()\n const error = useResolvedFieldInvalid(errorProp)\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // chrome 透傳:per-prop override context;context 沒值則 'default'\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const emptyDisplay = useFieldEmptyDisplay()\n\n // 2026-07-05 D4 draft state:controlled value 回寫會吃掉輸入中間態('-'、'1.'、'0.0' 等\n // parse 後 lossy 的 raw 字串)— 例:已有值 5 全選打「-」→ onChange(null) 回寫把 DOM 重設\n // 為空,負號被吃、無法打出 -5。修法對齊 Ant InputNumber / Adobe Spectrum NumberField 的\n // internal draft string canonical:輸入期間 DOM 顯示以 local draft 優先,parse 成功即時同步\n // onChange(parsed);blur / Enter commit 清 draft、顯示回 committed value;Escape 棄未提交\n // raw draft 字串、顯示回 committed value(NumberInput 為 live-commit:每次合法 parse 即\n // onChange,故非回復 pre-edit 原值 — 真 revert 需另存 pre-edit snapshot,屬 API 擴充)。\n // 宣告於 non-edit early return 之前(Rules-of-Hooks,\n // 同 LinkInput 2026-07-04 修法)。\n const [draft, setDraft] = React.useState<string | null>(null)\n\n // display / readonly / disabled 都顯示格式化值(span 取代 input)\n if (resolvedMode !== 'edit') {\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: variant, size }), className)}\n data-field-mode={resolvedMode}\n >\n <span\n className={cn(\n 'flex-1 min-w-0',\n resolvedMode === 'disabled' && 'text-fg-disabled cursor-not-allowed',\n // 2026-07-09 空值符號前景 + M24:fieldEmptyColorClass = disabled→fg-disabled / else→foreground(同 readonly value 色)\n value == null && fieldEmptyColorClass(resolvedMode),\n )}\n >\n {value == null ? emptyDisplay : formatNumber(value, { precision, prefix, suffix, locale })}\n </span>\n </div>\n )\n }\n\n // edit 模式:raw 數值輸入(draft 承載中間態;value 面語意不變 — ''/'-' 視為空、NaN 忽略)\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const raw = e.target.value\n setDraft(raw)\n if (!onChange) return\n if (raw === '' || raw === '-') {\n onChange(null)\n return\n }\n const parsed = Number(raw)\n if (!Number.isNaN(parsed)) {\n onChange(parsed)\n }\n }\n\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),\n className,\n )}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <input\n ref={ref}\n type=\"text\"\n inputMode=\"decimal\"\n id={idProp ?? fieldCtx?.id}\n // 輸入期間 draft(raw 字串)優先;非輸入期間才從 value prop 同步顯示(2026-07-05 D4)\n value={draft ?? (value != null ? String(value) : '')}\n onChange={handleChange}\n // blur / Enter = commit(清 draft,顯示回 committed value);Escape 棄 draft\n onBlur={(e) => { setDraft(null); onBlurProp?.(e) }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === 'Escape') setDraft(null)\n onKeyDownProp?.(e)\n }}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={bareInputStyles}\n {...props}\n />\n {endSlot ? (\n // endSlot escape hatch:consumer 自控右側 slot(對齊 Input.endSlot canonical)\n endSlot\n ) : endAction ? (\n <ItemInlineAction action={endAction} size={size ?? 'md'} />\n ) : null}\n </div>\n )\n }\n)\nNumberInput.displayName = 'NumberInput'\n\n// code-quality-allow: dead-export — public API surface — consumer-exposed for future use\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 numberInputMeta = {\n component: 'NumberInput',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — Field chrome 無按壓態;stepper 按壓屬內嵌 ItemInlineActionButton(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\n// code-quality-allow: dead-export — public API surface — consumer-exposed for future use\nexport { NumberInput, formatNumber }\n"],"names":[],"mappings":";;;;;;AAuBA,SAAS,aACP,OACA,UAA+B,IACvB;AACR,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,EAAE,WAAW,SAAS,IAAI,SAAS,IAAI,SAAS,YAAY;AAClE,QAAM,YAAY,aAAa,OAC3B,MAAM,eAAe,QAAQ,EAAE,uBAAuB,WAAW,uBAAuB,UAAA,CAAW,IACnG,MAAM,eAAe,MAAM;AAC/B,SAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM;AACvC;AA2CA,MAAM,cAAc,MAAM;AAAA,EACxB,CACE;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO,YAAY;AAAA,IACnB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,WAAW,gBAAA;AACjB,UAAM,QAAQ,wBAAwB,SAAS;AAC/C,UAAM,OAAO,qBAAqB,QAAQ;AAC1C,UAAM,WAAW,yBAAyB,YAAY;AAEtD,UAAM,UAAgC,wBAAwB,WAAW;AAEzE,UAAM,eAA0B,qBAAqB,EAAE,MAAM,UAAU,UAAU,UAAU;AAC3F,UAAM,eAAe,qBAAA;AAWrB,UAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAG5D,QAAI,iBAAiB,QAAQ;AAC3B,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW,GAAG,mBAAmB,EAAE,MAAM,cAAc,SAAkB,MAAM,GAAG,SAAS;AAAA,UAC3F,mBAAiB;AAAA,UAEjB,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,iBAAiB,cAAc;AAAA;AAAA,gBAE/B,SAAS,QAAQ,qBAAqB,YAAY;AAAA,cAAA;AAAA,cAGnD,UAAA,SAAS,OAAO,eAAe,aAAa,OAAO,EAAE,WAAW,QAAQ,QAAQ,OAAA,CAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,QAC3F;AAAA,MAAA;AAAA,IAGN;AAGA,UAAM,eAAe,CAAC,MAA2C;AAC/D,YAAM,MAAM,EAAE,OAAO;AACrB,eAAS,GAAG;AACZ,UAAI,CAAC,SAAU;AACf,UAAI,QAAQ,MAAM,QAAQ,KAAK;AAC7B,iBAAS,IAAI;AACb;AAAA,MACF;AACA,YAAM,SAAS,OAAO,GAAG;AACzB,UAAI,CAAC,OAAO,MAAM,MAAM,GAAG;AACzB,iBAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT,mBAAmB,EAAE,MAAM,QAAQ,SAAkB,MAAM,OAAO;AAAA,UAClE;AAAA,QAAA;AAAA,QAEF,mBAAgB;AAAA,QAChB,cAAY,QAAQ,KAAK;AAAA,QAEzB,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC;AAAA,cACA,MAAK;AAAA,cACL,WAAU;AAAA,cACV,IAAI,WAAU,qCAAU;AAAA,cAExB,OAAO,UAAU,SAAS,OAAO,OAAO,KAAK,IAAI;AAAA,cACjD,UAAU;AAAA,cAEV,QAAQ,CAAC,MAAM;AAAE,yBAAS,IAAI;AAAG,yDAAa;AAAA,cAAG;AAAA,cACjD,WAAW,CAAC,MAAM;AAChB,oBAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,mBAAmB,IAAI;AAC1D,+DAAgB;AAAA,cAClB;AAAA,cACA,gBAAc,SAAS;AAAA,cACvB,kBAAe,qCAAU,aAAY;AAAA,cACrC,oBAAkB,wBAAuB,qCAAU;AAAA,cACnD,qBAAmB,yBAAyB,QAAQ,qCAAU,UAAU;AAAA,cACxE,WAAW;AAAA,cACV,GAAG;AAAA,YAAA;AAAA,UAAA;AAAA,UAEL;AAAA;AAAA,YAEC;AAAA,cACE,gCACD,kBAAA,EAAiB,QAAQ,WAAW,MAAM,QAAQ,MAAM,IACvD;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF;AACA,YAAY,cAAc;AAKnB,MAAM,kBAAkB;AAAA,EAC7B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO,CAAA;AAAA;AAAA,EAIP,QAAQ,CAAC,WAAW,SAAS,iBAAiB,UAAU;AAAA,EACxD,QAAQ;AAAA,IACN,IAAI,CAAA;AAAA,IACJ,IAAI,CAAC,oBAAoB,eAAe;AAAA,IACxC,MAAM,CAAA;AAAA,EAAC;AAEX;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overflow-indicator.d.ts","sourceRoot":"","sources":["../../../src/components/OverflowIndicator/overflow-indicator.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA6B9B,MAAM,WAAW,sBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACxB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmDD,QAAA,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"overflow-indicator.d.ts","sourceRoot":"","sources":["../../../src/components/OverflowIndicator/overflow-indicator.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA6B9B,MAAM,WAAW,sBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACxB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmDD,QAAA,MAAM,iBAAiB,gGAwDtB,CAAA;AAKD,eAAO,MAAM,qBAAqB;;;;;;;;;;;CAgBxB,CAAA;AAEV,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
4
|
import { HoverCard, HoverCardTrigger, HoverCardContent } from "../HoverCard/hover-card.js";
|
|
5
5
|
import { tagVariants } from "../Tag/tag.js";
|
|
6
|
-
import {
|
|
6
|
+
import { MOTION_DELAY_CLOSE_MS, MOTION_DELAY_PLAIN_MS } from "../../tokens/motion/motion.js";
|
|
7
7
|
const triggerSize = {
|
|
8
8
|
sm: "h-5 min-w-5",
|
|
9
9
|
md: "h-6 min-w-6",
|
|
@@ -59,8 +59,6 @@ const OverflowIndicator = React.forwardRef(
|
|
|
59
59
|
ref,
|
|
60
60
|
"data-overflow-indicator": "",
|
|
61
61
|
tabIndex: 0,
|
|
62
|
-
role: "button",
|
|
63
|
-
"aria-haspopup": "dialog",
|
|
64
62
|
className: cn(tagVariants({ color: "neutral", size }), "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", className),
|
|
65
63
|
...props,
|
|
66
64
|
children: /* @__PURE__ */ jsxs("span", { className: "px-1", children: [
|
|
@@ -74,8 +72,6 @@ const OverflowIndicator = React.forwardRef(
|
|
|
74
72
|
ref,
|
|
75
73
|
"data-overflow-indicator": "",
|
|
76
74
|
tabIndex: 0,
|
|
77
|
-
role: "button",
|
|
78
|
-
"aria-haspopup": "dialog",
|
|
79
75
|
className: cn(
|
|
80
76
|
"shrink-0 rounded-full inline-grid place-content-center",
|
|
81
77
|
"bg-muted text-foreground font-medium leading-none cursor-pointer",
|
|
@@ -91,7 +87,7 @@ const OverflowIndicator = React.forwardRef(
|
|
|
91
87
|
]
|
|
92
88
|
}
|
|
93
89
|
);
|
|
94
|
-
return /* @__PURE__ */ jsxs(HoverCard, { openDelay:
|
|
90
|
+
return /* @__PURE__ */ jsxs(HoverCard, { openDelay: MOTION_DELAY_PLAIN_MS, closeDelay: MOTION_DELAY_CLOSE_MS, children: [
|
|
95
91
|
/* @__PURE__ */ jsx(HoverCardTrigger, { asChild: true, children: trigger }),
|
|
96
92
|
/* @__PURE__ */ jsx(HoverCardContent, { className: "bg-tooltip rounded-lg", "data-theme": "dark", children: /* @__PURE__ */ jsx(ShrinkWrapList, { children }) })
|
|
97
93
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overflow-indicator.js","sources":["../../../src/components/OverflowIndicator/overflow-indicator.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 Combobox / PeoplePicker 等 DS 元件 wrap 消費,end-user app 請用 wrapper 元件。\n */\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { HoverCard, HoverCardTrigger, HoverCardContent } from '@/design-system/components/HoverCard/hover-card'\nimport { tagVariants } from '@/design-system/components/Tag/tag'\nimport {
|
|
1
|
+
{"version":3,"file":"overflow-indicator.js","sources":["../../../src/components/OverflowIndicator/overflow-indicator.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 Combobox / PeoplePicker 等 DS 元件 wrap 消費,end-user app 請用 wrapper 元件。\n */\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { HoverCard, HoverCardTrigger, HoverCardContent } from '@/design-system/components/HoverCard/hover-card'\nimport { tagVariants } from '@/design-system/components/Tag/tag'\nimport { MOTION_DELAY_PLAIN_MS, MOTION_DELAY_CLOSE_MS } from '@/design-system/tokens/motion/motion'\n\n/**\n * OverflowIndicator — +N 觸發器 + HoverCard 顯示溢出內容\n *\n * 統一用 HoverCard(不用 Tooltip)——溢出內容可能需要互動:\n * - 人員 +N:tag dismiss + hover name card\n * - 一般 +N:穩定顯示溢出項目\n *\n * trigger 不用 Tag 元件(Tag 有內建 truncation Tooltip 會跟 HoverCard 衝突),\n * 改用 tagVariants 直接套樣式。\n */\n\nconst triggerSize: Record<string, string> = {\n sm: 'h-5 min-w-5',\n md: 'h-6 min-w-6',\n lg: 'h-6 min-w-6',\n}\n\nconst triggerText: Record<string, string> = {\n sm: 'text-[10px]',\n md: 'text-caption',\n lg: 'text-caption',\n}\n\nexport interface OverflowIndicatorProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {\n count: number\n shape?: 'circle' | 'tag'\n size?: 'sm' | 'md' | 'lg'\n children: React.ReactNode\n className?: string\n}\n\nfunction ShrinkWrapList({ children }: { children: React.ReactNode }) {\n const containerRef = React.useRef<HTMLDivElement | null>(null)\n\n // 2026-05-16 audit codex Round 6:rAF capture + cancel on unmount/re-run(defensive hygiene)。\n // 原 callback ref `requestAnimationFrame(() => ...)` 沒 cancel,unmount-during-rAF 可能 fire 後\n // mutate detached element.style — no-op but pattern hygiene 應對齊 DS-wide rAF cancel canonical。\n React.useLayoutEffect(() => {\n const container = containerRef.current\n if (!container) return\n let rafId = 0\n rafId = requestAnimationFrame(() => {\n rafId = 0\n const cs = getComputedStyle(container)\n const padL = parseFloat(cs.paddingLeft) || 0\n const padR = parseFloat(cs.paddingRight) || 0\n const gap = parseFloat(cs.gap) || parseFloat(cs.columnGap) || 0\n const available = container.offsetWidth - padL - padR\n\n const items = Array.from(container.children) as HTMLElement[]\n if (items.length === 0) return\n\n let currentRow = 0\n let maxRow = 0\n\n items.forEach(item => {\n const w = item.offsetWidth\n const needed = currentRow > 0 ? currentRow + gap + w : w\n\n if (needed > available && currentRow > 0) {\n maxRow = Math.max(maxRow, currentRow)\n currentRow = w\n } else {\n currentRow = needed\n }\n })\n maxRow = Math.max(maxRow, currentRow)\n\n container.style.maxWidth = `${Math.ceil(maxRow) + padL + padR + 1}px`\n })\n return () => { if (rafId) cancelAnimationFrame(rafId) }\n }, [children])\n\n return (\n <div ref={containerRef} className=\"flex flex-wrap gap-1 p-2 max-w-[280px]\">\n {children}\n </div>\n )\n}\n\nconst OverflowIndicator = React.forwardRef<HTMLSpanElement, OverflowIndicatorProps>(\n function OverflowIndicator(\n { count, shape = 'circle', size = 'md', children, className, ...props },\n ref,\n ) {\n if (count <= 0) return null\n\n // a11y(2026-07-14 dim-6):拆 role=\"button\" + aria-haspopup=\"dialog\" —— trigger 無\n // activation 行為(無 Enter/Space handler,HoverCard 靠 hover/focus 自動開),宣告了\n // button/haspopup 卻按 Enter 無反應 = 對 AT 空承諾。對齊 Avatar hoverCard 同款拆除\n // (avatar.tsx 2026-07-06 + hover-card.spec.md「A11y 預設」:Radix 官方零 role/aria 定位)。\n // trigger 有可見 \"+N\" 文字,無需 aria-label / role;保留 tabIndex=0 + focus ring(WCAG 2.1.1)。\n const trigger = shape === 'tag' ? (\n <span\n ref={ref}\n data-overflow-indicator=\"\"\n tabIndex={0}\n className={cn(tagVariants({ color: 'neutral', size }), 'cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', className)}\n {...props}\n >\n <span className=\"px-1\">+{count}</span>\n </span>\n ) : (\n <span\n ref={ref}\n data-overflow-indicator=\"\"\n tabIndex={0}\n className={cn(\n 'shrink-0 rounded-full inline-grid place-content-center',\n 'bg-muted text-foreground font-medium leading-none cursor-pointer',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n triggerSize[size],\n triggerText[size],\n className,\n )}\n {...props}\n >\n +{count}\n </span>\n )\n\n // 2026-05-18 fix(per user audit「所有 hovercard 應消費 hover delay token」+ motion.spec.md SSOT):\n // plain tier(純列表展開、無 fetch)= MOTION_DELAY_PLAIN_MS,per motion.spec.md 對照表 row;\n // close = MOTION_DELAY_CLOSE_MS。2026-06-11 修:2026-05-18 token 遷移誤挑 rich(原 hardcode 200/300\n // 兩 tier 皆非,遷移未對照 spec 表)— popup 可互動性由 close 緩衝保障,與 open tier 無關。\n return (\n <HoverCard openDelay={MOTION_DELAY_PLAIN_MS} closeDelay={MOTION_DELAY_CLOSE_MS}>\n <HoverCardTrigger asChild>\n {trigger}\n </HoverCardTrigger>\n <HoverCardContent className=\"bg-tooltip rounded-lg\" data-theme=\"dark\">\n <ShrinkWrapList>{children}</ShrinkWrapList>\n </HoverCardContent>\n </HoverCard>\n )\n },\n)\nOverflowIndicator.displayName = 'OverflowIndicator'\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 overflowIndicatorMeta = {\n component: 'OverflowIndicator',\n family: 'self-contained', // 對齊 overflow-indicator.spec.md frontmatter family: self-contained(SSOT)\n variants: {\n\n },\n sizes: {\n\n },\n // 2026-07-04 audit 對齊(spec:95「本身無 hover / active / disabled / selected 變化」;code 有 focus-visible ring tsx:106/121)\n states: ['default', 'focus-visible'],\n tokens: {\n bg: ['bg-muted'],\n fg: ['text-foreground'],\n ring: [],\n },\n} as const\n\nexport { OverflowIndicator }\n"],"names":["OverflowIndicator"],"mappings":"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"}
|