@qijenchen/design-system 0.1.0-beta.85 → 0.1.0-beta.87
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/AGENTS.md +210 -0
- package/CLAUDE.md +17 -191
- package/cli-init.mjs +30 -2
- package/dist/components/Accordion/accordion.d.ts +2 -2
- package/dist/components/Accordion/accordion.d.ts.map +1 -1
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/Avatar/avatar.js +1 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +3 -3
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button-group.d.ts +2 -2
- package/dist/components/Button/button-group.d.ts.map +1 -1
- package/dist/components/Button/button-group.js +2 -2
- package/dist/components/Button/button-group.js.map +1 -1
- package/dist/components/Button/button.d.ts +2 -1
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +1 -1
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +14 -8
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +64 -37
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox-group.js +21 -14
- package/dist/components/Checkbox/checkbox-group.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +2 -2
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +49 -10
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +15 -6
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +13 -7
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +11 -2
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts +5 -5
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +41 -38
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +1 -17
- 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.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +71 -64
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.d.ts +0 -2
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +3 -4
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -2
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +2 -2
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +1 -1
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +44 -32
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +3 -3
- package/dist/components/DatePicker/date-picker.js +4 -4
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts +3 -3
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +6 -6
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +2 -2
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +8 -3
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +7 -7
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +16 -10
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-types.d.ts +2 -2
- 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 +20 -4
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +30 -31
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +25 -3
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/use-form-validation.d.ts +1 -1
- package/dist/components/Field/use-form-validation.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +2 -5
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +5 -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 +2 -1
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts +2 -2
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +4 -2
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/InlineEdit/inline-edit.d.ts +23 -16
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
- package/dist/components/InlineEdit/inline-edit.js +104 -62
- package/dist/components/InlineEdit/inline-edit.js.map +1 -1
- package/dist/components/Input/input.d.ts +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +6 -6
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +5 -5
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +3 -3
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +17 -1
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +7 -1
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +12 -1
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +5 -5
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +7 -5
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +1 -1
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +3 -3
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +1 -1
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.d.ts.map +1 -1
- package/dist/components/Rating/rating.js +4 -0
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +3 -2
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +4 -3
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +16 -6
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +17 -5
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +17 -1
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +17 -3
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +1 -1
- package/dist/components/SelectionControl/boolean-value.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +2 -2
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.js +2 -2
- 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 +6 -3
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts +6 -4
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +19 -5
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +4 -4
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +3 -1
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +73 -23
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts +3 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +2 -2
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +4 -4
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +15 -15
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +4 -4
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +8 -5
- 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 +27 -23
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +16 -0
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/drag-visual.d.ts +2 -2
- package/dist/lib/drag-visual.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +21 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +2 -2
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +3 -3
- package/dist/tokens/categorical-color.d.ts.map +1 -1
- package/dist/tokens/categorical-color.js +2 -2
- package/dist/tokens/categorical-color.js.map +1 -1
- package/ds-canonical/fork/AGENTS.md +213 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +33 -0
- package/ds-canonical/fork/codex/hooks.json +16 -0
- package/ds-canonical/fork/governance.lock +22 -7
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +22 -1
- package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +14 -2
- package/ds-canonical/fork/manifest.json +10 -1
- package/ds-canonical/fork/preamble.md +3 -2
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +7 -7
- package/ds-canonical/fork/skills/ux-audit/SKILL.md +1 -1
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +0 -1
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +9 -3
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +22 -1
- package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +19 -2
- package/ds-canonical/hooks/check_plugin_fork_health.sh +17 -1
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +21 -4
- package/ds-canonical/hooks/inject_deploy_url_after_push.sh +14 -2
- package/ds-canonical/hooks/session_start_governance_check.sh +10 -2
- package/ds-canonical/references/certified-surfaces.md +20 -0
- package/ds-canonical/references/preflight-gate-baseline.json +5 -0
- package/ds-canonical/references/principle-dim-map.json +20 -2
- package/ds-canonical/references/props-naming.md +2 -1
- package/ds-canonical/references/ssot-index.md +2 -2
- package/ds-canonical/references/story-baseline-registry.json +2 -2
- package/ds-canonical/rules/story-rules.md +1 -1
- package/ds-canonical/rules/ui-development.md +2 -1
- package/ds-canonical/skills/codex-collab/SKILL.md +7 -7
- package/ds-canonical/skills/codex-collab/references/brief-template.md +5 -1
- package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +5 -5
- package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
- package/ds-canonical/skills/product-ui-audit/SKILL.md +7 -7
- package/ds-canonical/skills/ux-audit/SKILL.md +1 -1
- package/ds-canonical/skills/visual-audit/SKILL.md +0 -1
- package/ds-story-manifest.json +21 -21
- package/llms-full.txt +3 -3
- package/llms.txt +1 -1
- package/package.json +2 -1
- package/src/components/Accordion/accordion.anatomy.stories.tsx +11 -11
- package/src/components/Accordion/accordion.principles.stories.tsx +21 -6
- package/src/components/Accordion/accordion.spec.md +10 -1
- package/src/components/Accordion/accordion.stories.tsx +10 -5
- package/src/components/Accordion/accordion.tsx +7 -2
- package/src/components/Alert/alert.principles.stories.tsx +2 -8
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +19 -13
- package/src/components/AppShell/app-shell.principles.stories.tsx +21 -21
- package/src/components/AppShell/app-shell.spec.md +4 -0
- package/src/components/AppShell/app-shell.stories.tsx +4 -5
- package/src/components/Avatar/avatar.tsx +1 -1
- package/src/components/Badge/badge.spec.md +2 -2
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +7 -3
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +4 -1
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +6 -6
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +18 -6
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -5
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +8 -24
- package/src/components/BulkActionBar/bulk-action-bar.tsx +4 -4
- package/src/components/Button/button-group.tsx +4 -4
- package/src/components/Button/button.anatomy.stories.tsx +26 -7
- package/src/components/Button/button.principles.stories.tsx +1 -1
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.stories.tsx +22 -28
- package/src/components/Button/button.tsx +2 -1
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Calendar/calendar.spec.md +1 -1
- package/src/components/Carousel/carousel.spec.md +17 -1
- package/src/components/Carousel/carousel.tsx +93 -42
- package/src/components/Checkbox/checkbox-group.tsx +26 -15
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +3 -3
- package/src/components/Checkbox/checkbox.spec.md +12 -3
- package/src/components/Checkbox/checkbox.stories.tsx +4 -4
- package/src/components/Checkbox/checkbox.tsx +79 -19
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +5 -5
- package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
- package/src/components/Combobox/combobox.anatomy.stories.tsx +11 -4
- package/src/components/Combobox/combobox.principles.stories.tsx +10 -11
- package/src/components/Combobox/combobox.spec.md +6 -2
- package/src/components/Combobox/combobox.stories.tsx +9 -36
- package/src/components/Combobox/combobox.tsx +42 -27
- package/src/components/Command/command.spec.md +3 -3
- package/src/components/Command/command.tsx +9 -2
- package/src/components/DataTable/cell-registry.tsx +75 -76
- package/src/components/DataTable/column-types.ts +4 -17
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +20 -7
- package/src/components/DataTable/data-table-filter-panel.tsx +6 -6
- package/src/components/DataTable/data-table-interaction-layer.tsx +3 -3
- package/src/components/DataTable/data-table-sort-manager.tsx +2 -2
- package/src/components/DataTable/data-table.anatomy.stories.tsx +25 -24
- package/src/components/DataTable/data-table.css +2 -3
- package/src/components/DataTable/data-table.principles.stories.tsx +17 -11
- package/src/components/DataTable/data-table.spec.md +8 -7
- package/src/components/DataTable/data-table.stories.tsx +39 -29
- package/src/components/DataTable/data-table.tsx +90 -67
- package/src/components/DataTable/filter-operators.spec.md +2 -0
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -9
- package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
- package/src/components/DatePicker/date-picker.spec.md +10 -9
- package/src/components/DatePicker/date-picker.stories.tsx +9 -9
- package/src/components/DatePicker/date-picker.tsx +13 -13
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +5 -5
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.spec.md +12 -12
- package/src/components/DescriptionList/description-list.stories.tsx +3 -3
- package/src/components/DescriptionList/description-list.tsx +10 -10
- package/src/components/Dialog/dialog.tsx +4 -1
- package/src/components/DropdownMenu/dropdown-menu.tsx +27 -14
- package/src/components/Empty/empty.spec.md +0 -1
- package/src/components/Field/field-context.ts +17 -11
- package/src/components/Field/field-controls.spec.md +46 -37
- package/src/components/Field/field-types.ts +14 -12
- package/src/components/Field/field-wrapper.tsx +59 -43
- package/src/components/Field/field.anatomy.stories.tsx +6 -6
- package/src/components/Field/field.principles.stories.tsx +5 -28
- package/src/components/Field/field.spec.md +5 -4
- package/src/components/Field/field.stories.tsx +103 -72
- package/src/components/Field/field.tsx +36 -4
- package/src/components/Field/form-validation.spec.md +4 -0
- package/src/components/Field/use-form-validation.ts +1 -1
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
- package/src/components/FileItem/file-item.tsx +10 -8
- package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
- package/src/components/FileUpload/file-upload.spec.md +3 -2
- package/src/components/FileUpload/file-upload.tsx +5 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.spec.md +1 -1
- package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.tsx +3 -2
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
- package/src/components/HoverCard/hover-card.spec.md +2 -2
- package/src/components/HoverCard/hover-card.tsx +4 -2
- package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
- package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
- package/src/components/InlineEdit/inline-edit.spec.md +71 -46
- package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
- package/src/components/InlineEdit/inline-edit.tsx +211 -125
- package/src/components/Input/input.anatomy.stories.tsx +15 -15
- package/src/components/Input/input.principles.stories.tsx +3 -3
- package/src/components/Input/input.spec.md +4 -4
- package/src/components/Input/input.stories.tsx +3 -3
- package/src/components/Input/input.tsx +13 -13
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.spec.md +6 -2
- package/src/components/LinkInput/link-input.stories.tsx +8 -8
- package/src/components/LinkInput/link-input.tsx +21 -19
- package/src/components/Notice/notice.principles.stories.tsx +1 -1
- package/src/components/Notice/notice.tsx +8 -2
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
- package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.spec.md +6 -6
- package/src/components/NumberInput/number-input.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.tsx +17 -5
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
- package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
- package/src/components/PeoplePicker/people-picker.tsx +39 -20
- package/src/components/PeoplePicker/person-display.tsx +3 -3
- package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
- package/src/components/Popover/popover.principles.stories.tsx +49 -28
- package/src/components/Popover/popover.spec.md +4 -2
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
- package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
- package/src/components/README.md +1 -0
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
- package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
- package/src/components/RadioGroup/radio-group.tsx +19 -14
- package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
- package/src/components/Rating/rating.principles.stories.tsx +3 -3
- package/src/components/Rating/rating.spec.md +2 -2
- package/src/components/Rating/rating.stories.tsx +4 -18
- package/src/components/Rating/rating.tsx +5 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +1 -1
- package/src/components/ScrollArea/scroll-area.tsx +5 -2
- package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
- package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
- package/src/components/SegmentedControl/segmented-control.tsx +6 -3
- package/src/components/Select/select.anatomy.stories.tsx +13 -7
- package/src/components/Select/select.principles.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +10 -1
- package/src/components/Select/select.stories.tsx +41 -27
- package/src/components/Select/select.tsx +44 -26
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
- package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
- package/src/components/SelectMenu/select-menu.tsx +38 -5
- package/src/components/SelectionControl/boolean-value.tsx +2 -2
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
- package/src/components/Separator/separator.principles.stories.tsx +1 -1
- package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
- package/src/components/Sheet/sheet.tsx +4 -1
- package/src/components/Sidebar/sidebar.tsx +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
- package/src/components/Slider/slider.principles.stories.tsx +6 -6
- package/src/components/Slider/slider.spec.md +13 -1
- package/src/components/Slider/slider.stories.tsx +44 -32
- package/src/components/Slider/slider.tsx +13 -4
- package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
- package/src/components/Steps/steps.principles.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +4 -2
- package/src/components/Steps/steps.stories.tsx +4 -56
- package/src/components/Steps/steps.tsx +31 -7
- package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Switch/switch.spec.md +4 -4
- package/src/components/Switch/switch.stories.tsx +3 -3
- package/src/components/Switch/switch.tsx +16 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
- package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
- package/src/components/Tabs/tabs.spec.md +5 -2
- package/src/components/Tabs/tabs.stories.tsx +36 -44
- package/src/components/Tabs/tabs.tsx +101 -36
- package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
- package/src/components/Tag/tag.principles.stories.tsx +4 -4
- package/src/components/Tag/tag.spec.md +4 -1
- package/src/components/Tag/tag.tsx +5 -3
- package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
- package/src/components/Textarea/textarea.spec.md +5 -5
- package/src/components/Textarea/textarea.tsx +24 -24
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
- package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
- package/src/components/TimePicker/time-picker.spec.md +6 -5
- package/src/components/TimePicker/time-picker.stories.tsx +3 -3
- package/src/components/TimePicker/time-picker.tsx +24 -14
- package/src/components/Toast/toast.principles.stories.tsx +1 -1
- package/src/components/Toast/toast.stories.tsx +1 -1
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
- package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
- package/src/components/Tooltip/tooltip.spec.md +9 -3
- package/src/components/Tooltip/tooltip.stories.tsx +1 -1
- package/src/components/Tooltip/tooltip.tsx +12 -2
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
- package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
- package/src/components/TreeView/tree-view.spec.md +8 -6
- package/src/components/TreeView/tree-view.stories.tsx +9 -43
- package/src/components/TreeView/tree-view.tsx +36 -0
- package/src/index.ts +13 -10
- package/src/lib/drag-visual.ts +2 -2
- package/src/lib/utils.ts +19 -0
- package/src/patterns/action-bar/action-bar.spec.md +1 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
- package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
- package/src/tokens/categorical-color.ts +5 -5
- package/src/tokens/color/color.spec.md +4 -4
- package/src/tokens/color/primitives.css +9 -2
- package/src/tokens/motion/motion.spec.md +6 -7
- package/src/tokens/opacity/opacity.spec.md +1 -1
- package/src/tokens/orphan-tokens.spec.md +1 -1
- package/src/tokens/typography/typography.spec.md +2 -0
- package/src/tokens/uiSize/uiSize.css +5 -0
- package/src/tokens/uiSize/uiSize.spec.md +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Avatar } from "./components/Avatar/avatar.js";
|
|
|
6
6
|
import { Badge, badgeVariants } from "./components/Badge/badge.js";
|
|
7
7
|
import { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "./components/Breadcrumb/breadcrumb.js";
|
|
8
8
|
import { BULK_ACTION_BAR_DEFAULT_LABELS, BulkActionBar } from "./components/BulkActionBar/bulk-action-bar.js";
|
|
9
|
-
import { Button,
|
|
9
|
+
import { Button, buttonVariants } from "./components/Button/button.js";
|
|
10
10
|
import { ButtonDivider, ButtonGroup } from "./components/Button/button-group.js";
|
|
11
11
|
import { Calendar } from "./components/Calendar/calendar.js";
|
|
12
12
|
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "./components/Carousel/carousel.js";
|
|
@@ -24,10 +24,9 @@ import { DataTableSortManager } from "./components/DataTable/data-table-sort-man
|
|
|
24
24
|
import { DatePicker, DatePickerRange, formatDate } from "./components/DatePicker/date-picker.js";
|
|
25
25
|
import { DescriptionItem, DescriptionList } from "./components/DescriptionList/description-list.js";
|
|
26
26
|
import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "./components/Dialog/dialog.js";
|
|
27
|
-
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger
|
|
27
|
+
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "./components/DropdownMenu/dropdown-menu.js";
|
|
28
28
|
import { Empty } from "./components/Empty/empty.js";
|
|
29
29
|
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "./components/Field/field.js";
|
|
30
|
-
import { getMenuListMinHeight } from "./components/Field/field-types.js";
|
|
31
30
|
import { FieldControlGroup } from "./components/FieldControlGroup/field-control-group.js";
|
|
32
31
|
import { FileItem } from "./components/FileItem/file-item.js";
|
|
33
32
|
import { FileUpload } from "./components/FileUpload/file-upload.js";
|
|
@@ -47,7 +46,7 @@ import { ScrollArea, ScrollBar } from "./components/ScrollArea/scroll-area.js";
|
|
|
47
46
|
import { SegmentedControl, SegmentedControlItem, segmentedControlItemVariants, segmentedControlVariants } from "./components/SegmentedControl/segmented-control.js";
|
|
48
47
|
import { Select } from "./components/Select/select.js";
|
|
49
48
|
import { Separator } from "./components/Separator/separator.js";
|
|
50
|
-
import { Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger,
|
|
49
|
+
import { Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, sheetVariants } from "./components/Sheet/sheet.js";
|
|
51
50
|
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarProvider, SidebarSeparator, SidebarTrigger, useSidebar } from "./components/Sidebar/sidebar.js";
|
|
52
51
|
import { Skeleton } from "./components/Skeleton/skeleton.js";
|
|
53
52
|
import { Slider, sliderRootVariants } from "./components/Slider/slider.js";
|
|
@@ -60,7 +59,7 @@ import { TimePicker } from "./components/TimePicker/time-picker.js";
|
|
|
60
59
|
import { Toaster, toast } from "./components/Toast/toast.js";
|
|
61
60
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./components/Tooltip/tooltip.js";
|
|
62
61
|
import { TreeItem, TreeView, treeItemVariants } from "./components/TreeView/tree-view.js";
|
|
63
|
-
import { AVATAR_SIZE, ICON_SIZE, INLINE_ACTION_HOVER_BG_SIZE, ItemAvatar, ItemContent, ItemIcon,
|
|
62
|
+
import { AVATAR_SIZE, ICON_SIZE, INLINE_ACTION_HOVER_BG_SIZE, ItemAvatar, ItemContent, ItemIcon, ItemLabel, ItemPrefix, ItemSuffix, ROW_PADDING_BY_SIZE, getUniformPrefixSlotStyle, itemPrefixAlignVariants, useRowSize } from "./patterns/element-anatomy/item-anatomy.js";
|
|
64
63
|
import { ChromeHeader, HEADER_TABS_SLOT_WRAPPER_CLASS } from "./patterns/header-canonical/chrome-header.js";
|
|
65
64
|
import { ResizeHandle } from "./patterns/resize-handle/resize-handle.js";
|
|
66
65
|
import { CATEGORICAL_HUES, CAT_ACCENT, CAT_EVENT, CAT_INTERACT, CAT_SOLID, CAT_SOLID_TOKENS, CAT_SUBTLE, CAT_SUBTLE_TOKENS } from "./tokens/categorical-color.js";
|
|
@@ -73,7 +72,7 @@ import { useIsTouchDevice } from "./hooks/use-is-touch-device.js";
|
|
|
73
72
|
import { useOverflowIndices, useScrollEdges } from "./hooks/use-overflow-items.js";
|
|
74
73
|
import { dragActiveCursor, dragHandleCursorClass, dragSourceClass, dragSourceStyle, dropIndicatorColumn, dropIndicatorInside, dropIndicatorRow, isReorderNoop, reconstructFullRowGhost, snapToCursorModifier } from "./lib/drag-visual.js";
|
|
75
74
|
import { applySelectAll, clearSelection } from "./lib/multi-select-ordering.js";
|
|
76
|
-
import { cn } from "./lib/utils.js";
|
|
75
|
+
import { cn, lineClampClass } from "./lib/utils.js";
|
|
77
76
|
import { MultiPersonDisplay, PersonAvatarTag, PersonDisplay, buildPersonProfileCard, resolvePerson } from "./components/PeoplePicker/person-display.js";
|
|
78
77
|
import { PEOPLE_PICKER_LENGTH1_WRAPPER_CLASS, getPeoplePickerTagWrapperClass } from "./components/PeoplePicker/people-picker-helpers.js";
|
|
79
78
|
import { createEmptyFilterTree, dataTableFilterMatch, evaluateTree, isFilterTreeActive } from "./components/DataTable/filter-tree.js";
|
|
@@ -102,7 +101,6 @@ export {
|
|
|
102
101
|
Button,
|
|
103
102
|
ButtonDivider,
|
|
104
103
|
ButtonGroup,
|
|
105
|
-
ButtonGroupContext,
|
|
106
104
|
CATEGORICAL_HUES,
|
|
107
105
|
CAT_ACCENT,
|
|
108
106
|
CAT_EVENT,
|
|
@@ -185,8 +183,6 @@ export {
|
|
|
185
183
|
ItemAvatar,
|
|
186
184
|
ItemContent,
|
|
187
185
|
ItemIcon,
|
|
188
|
-
ItemInlineAction,
|
|
189
|
-
ItemInlineActionButton,
|
|
190
186
|
ItemLabel,
|
|
191
187
|
ItemPrefix,
|
|
192
188
|
ItemSuffix,
|
|
@@ -222,7 +218,6 @@ export {
|
|
|
222
218
|
RadioGroupItem,
|
|
223
219
|
Rating,
|
|
224
220
|
ResizeHandle,
|
|
225
|
-
RowSizeProvider,
|
|
226
221
|
ScrollArea,
|
|
227
222
|
ScrollBar,
|
|
228
223
|
SegmentedControl,
|
|
@@ -300,16 +295,14 @@ export {
|
|
|
300
295
|
dropIndicatorInside,
|
|
301
296
|
dropIndicatorRow,
|
|
302
297
|
evaluateTree,
|
|
303
|
-
floatingLayerClass,
|
|
304
298
|
formatDate,
|
|
305
299
|
formatNumber,
|
|
306
|
-
getMenuListMinHeight,
|
|
307
300
|
getPeoplePickerTagWrapperClass,
|
|
308
301
|
getUniformPrefixSlotStyle,
|
|
309
|
-
handleSheetOpenAutoFocus,
|
|
310
302
|
isFilterTreeActive,
|
|
311
303
|
isReorderNoop,
|
|
312
304
|
itemPrefixAlignVariants,
|
|
305
|
+
lineClampClass,
|
|
313
306
|
overlayMotion,
|
|
314
307
|
radioItemVariants,
|
|
315
308
|
reconstructFullRowGhost,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* ## Pattern
|
|
8
8
|
*
|
|
9
|
-
* - **Source element**(被拖中的 row/column):`opacity-
|
|
9
|
+
* - **Source element**(被拖中的 row/column):`opacity-disabled`(= var(--opacity-disabled) 0.45 半透,user 仍看得到原位;2026-05-06 v14.5.2 由 opacity-30 改)
|
|
10
10
|
* — 不是 `opacity:0` 全隱形,因 user 拖太遠看不到「源頭」會迷失方向。對齊 TreeView 最早 v1 + Notion / Atlassian / Linear。
|
|
11
11
|
* - **Ghost preview**(浮動 follow cursor):透過 dnd-kit `<DragOverlay>` portal,clone source 的 outerHTML
|
|
12
12
|
* strip transform/transition/opacity → render 在 portal 層,visually 跟 cursor 走。
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* - `bg-primary`:semantic state token(`--primary`)— 跟 focus state ring color 同 source(M23 一致)
|
|
27
27
|
* - `bg-primary-subtle`:semantic primary subtle(`--primary-subtle`)— inside-drop dim background
|
|
28
28
|
* - `h-0.5` / `w-0.5`:Tailwind size token = 2px(對齊 hairline divider thickness 概念)
|
|
29
|
-
* - `opacity-
|
|
29
|
+
* - `opacity-disabled`:DS token var(--opacity-disabled)= 0.45(半透但仍清楚看到 outline;見下方 v14.5.2 note)
|
|
30
30
|
*/
|
|
31
31
|
import type * as React from 'react';
|
|
32
32
|
import type { Modifier } from '@dnd-kit/core';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drag-visual.js","sources":["../../src/lib/drag-visual.ts"],"sourcesContent":["/**\n * Drag visual SSOT(2026-05-06 v14.5)\n *\n * DS canonical drag visual,3 處 consumer(TreeView / DataTable row drag / DataTable column reorder)\n * 共用同一組 styling constant — 對齊 TreeView 最早 codified 的 pattern。\n *\n * ## Pattern\n *\n * - **Source element**(被拖中的 row/column):`opacity-30`(半透,user 仍看得到原位)\n * — 不是 `opacity:0` 全隱形,因 user 拖太遠看不到「源頭」會迷失方向。對齊 TreeView 最早 v1 + Notion / Atlassian / Linear。\n * - **Ghost preview**(浮動 follow cursor):透過 dnd-kit `<DragOverlay>` portal,clone source 的 outerHTML\n * strip transform/transition/opacity → render 在 portal 層,visually 跟 cursor 走。\n * - **Drop indicator**(目標位置藍細線):2px(`h-0.5` / `w-0.5`)`bg-primary` absolute line:\n * - **Row context**(TreeView / table row drag):水平線 在 target row 的 top(before)/ bottom(after)\n * - **Column context**(table column reorder):垂直線 在 target column 的 left(before)/ right(after)\n * - **Inside-drop highlight**(nested target,如拖到 TreeView 子層 / Table nested row 內):整 row `bg-primary-subtle`\n *\n * ## Why centralize\n *\n * 三處 drag 之前各自實作不一致(TreeView opacity:30 + indicator vs DataTable row opacity:0 no indicator\n * vs Column reorder opacity:0 + indicator)— M23 / M27 violation(DS 內 canonical 沒對齊)。\n * 抽到此 module 後 3 處 import 同 constants,未來改 1 處全 sync。\n *\n * ## Token usage\n *\n * - `bg-primary`:semantic state token(`--primary`)— 跟 focus state ring color 同 source(M23 一致)\n * - `bg-primary-subtle`:semantic primary subtle(`--primary-subtle`)— inside-drop dim background\n * - `h-0.5` / `w-0.5`:Tailwind size token = 2px(對齊 hairline divider thickness 概念)\n * - `opacity-30`:Tailwind 30% opacity(半透但仍清楚看到 outline)\n */\n\nimport type * as React from 'react'\nimport type { Modifier } from '@dnd-kit/core'\n\n// ── Source element styling(被拖中的 source row/column)─────────────────────\n\n/**\n * Source element 拖中的 opacity className。\n *\n * **Reuse `opacity-disabled` token**(2026-05-06 v14.5.2 對齊 Atlassian Pragmatic):\n * Atlassian dnd guidelines 也用 `opacity.disabled` token 給 drag source dim(他們值 0.40,\n * 我們值 0.45,role 一致)。不另開 drag-source 專屬 token — single-role-per-value 哲學\n * + DS internal SSOT。\n */\nexport const dragSourceClass = 'opacity-disabled' as const\n\n/** Source element 拖中的 inline style(consume DS token `--opacity-disabled`)*/\nexport function dragSourceStyle(isDragging: boolean): React.CSSProperties {\n return isDragging ? { opacity: 'var(--opacity-disabled)' } : {}\n}\n\n/**\n * DragOverlay ghost 視覺 canonical(2026-05-06 v14.5.2 對齊 dnd-kit / Atlassian / Material):\n * **不 dim**(opacity:1)+ elevation shadow `shadow-[var(--elevation-200)]` + 顯式 bg\n * (`bg-surface` 或 `bg-surface-raised`)+ border。Lifted preview pattern,跟 surface 拉開\n * 視覺距離靠 shadow 不靠 opacity。\n *\n * Consumer 自己組 className(因 ghost 結構各自不同 — TreeView 是 icon+label compact pill,\n * DataTable 是 cloned row HTML wrapper),這裡只 codify 規則 不導出 class string。\n */\n\n// ── Drop indicator className(target row/column edge 顯藍細線)──────────────\n\n/**\n * Row drop indicator(水平線,跨 row 全寬)\n *\n * - `before`:在 target row top edge,提示「放開後會插入這 row 之前」\n * - `after`:在 target row bottom edge,提示「放開後會插入這 row 之後」\n *\n * **Indent option**:nested context(TreeView / table nested rows)可加 `style={{ left: indentPx }}`\n * 讓 indicator 隨 depth 縮排,視覺對齊 row content 起始位置。\n */\nexport const dropIndicatorRow = {\n before: 'absolute top-0 left-0 right-0 h-0.5 bg-primary z-10 pointer-events-none',\n after: 'absolute bottom-0 left-0 right-0 h-0.5 bg-primary z-10 pointer-events-none',\n} as const\n\n/**\n * Column drop indicator(垂直線,跨 column header height)\n *\n * - `before`:在 target column left edge(-1px outset 對齊 grid line)\n * - `after`:在 target column right edge(-1px outset 對齊 grid line)\n *\n * 兩種變體,視覺一致(2px primary line),只有 DOM mechanism 不同 — consumer 視 use case 選:\n *\n * - **`absoluteDiv`**(TreeView pattern):render absolute `<div>` child;\n * consumer 必須有 `position: relative` parent。\n * - **`pseudoBefore` / `pseudoAfter`**:用 Tailwind `before:` / `after:` pseudo-element;\n * consumer 不需 child element(適合 cloneElement 等不能加 child 的場景)。\n *\n * 兩變體 thickness / color / z-index 完全一致,差別純 implementation 機制。\n */\nexport const dropIndicatorColumn = {\n // Absolute div 變體(consumer render <div>)\n before: 'absolute top-0 bottom-0 left-[-1px] w-0.5 bg-primary z-10 pointer-events-none',\n after: 'absolute top-0 bottom-0 right-[-1px] w-0.5 bg-primary z-10 pointer-events-none',\n // Pseudo-element 變體(用 Tailwind before:/after:)\n pseudoBefore: 'before:absolute before:top-0 before:bottom-0 before:left-[-1px] before:w-0.5 before:bg-primary before:z-10 before:pointer-events-none before:content-[\"\"]',\n pseudoAfter: 'after:absolute after:top-0 after:bottom-0 after:right-[-1px] after:w-0.5 after:bg-primary after:z-10 after:pointer-events-none after:content-[\"\"]',\n} as const\n\n// ── Inside-drop highlight(nested context,target row 整列亮藍 subtle)─────\n\n/**\n * Nested 拖入 highlight(TreeView / nested rows 拖到子層)。整 row 加 background。\n */\nexport const dropIndicatorInside = 'bg-primary-subtle' as const\n\n// ── Cursor classes ────────────────────────────────────────────────────────\n\n/** Draggable element 拖中時的 cursor(grabbing)*/\nexport const dragActiveCursor = 'cursor-grabbing' as const\n/**\n * Combined drag handle cursor canonical(2026-05-07 v15.7 user directive):\n * **只 cursor-grab,不變 grabbing**。對齊 Material / Carbon / Polaris canonical —\n * drag affordance 由 visible button 提供,cursor 變化反而干擾 indicator+ghost 視覺焦點。\n * Consumer 直接 cn(...)。\n */\nexport const dragHandleCursorClass = 'cursor-grab' as const\n\n// ── Modifier:Ghost 跟 cursor 維持初始相對位置 SSOT(v15.7 user directive) ─\n\n/**\n * Ghost 跟 cursor 維持「cursor 在 ghost 點下時相對位置」SSOT(對齊 user directive\n * 「ghost 和 cursor 要確保相對位置是合理的而且有 ssot」)。\n *\n * **Why need this**:dnd-kit DragOverlay 預設 ghost.top-left = source rect (`setNodeRef`)\n * 的 top-left。當 source ≠ activator 位置不同(e.g. activator = portal'd Button 在\n * table outer 邊緣;source = primary row 在 table 內 column region),cursor 點下\n * activator → cursor 落在 source rect 的 negative offset → ghost 永遠相對 source rect\n * 起算 → cursor 跟 ghost 起始就有大 offset(e.g. 100+ px),drag 期間維持錯位。\n *\n * **本 modifier**:把 (cursor.initial - source.left, cursor.initial - source.top)\n * 加進 transform → ghost.top-left 永遠對齊 cursor 位置(cursor 在 ghost 左前角)。\n * Consumer 套進 DndContext.modifiers array,所有 drag scenario 套用 = SSOT。\n *\n * **對齊世界級**:Linear / Notion / Jira / AG Grid 的 row drag ghost 都跟 cursor\n * 維持初始相對位置(整列拖時 cursor 在 click 點;button 拖時 cursor 在 button 位置)—\n * 這個 modifier 把「整列拖 + 整列 ghost」UX 在 button-only / 多 region 場景下也達到。\n */\nexport const snapToCursorModifier: Modifier = ({ transform, activatorEvent, draggingNodeRect }) => {\n if (!activatorEvent || !draggingNodeRect) return transform\n // **codex P1 fix(2026-05-07 v15.12)**:KeyboardSensor 走 KeyboardEvent,沒\n // `clientX/clientY` → `offsetX/offsetY = NaN` → ghost transform 變 NaN/NaN →\n // overlay 定位+collision 全 corrupt → keyboard-initiated drag 直接壞。Guard 條件:\n // 只 pointer/mouse event 才平移,keyboard 走 dnd-kit 預設(無 modifier 偏移)。\n if (!('clientX' in activatorEvent) || !('clientY' in activatorEvent)) return transform\n const ev = activatorEvent as PointerEvent | MouseEvent\n if (typeof ev.clientX !== 'number' || typeof ev.clientY !== 'number') return transform\n const offsetX = ev.clientX - draggingNodeRect.left\n const offsetY = ev.clientY - draggingNodeRect.top\n return {\n ...transform,\n x: transform.x + offsetX,\n y: transform.y + offsetY,\n }\n}\n\n// ── Reorder noop helper(SSOT 對齊 row + column reorder canonical) ────────\n\n/**\n * Drop position 等同 source 原位 → noop。對齊 Linear / Notion / Jira / TreeView SSOT:\n * user 拉到 source 鄰近(視覺等同原位)應 cancel,不 commit reorder 也不顯 indicator。\n *\n * Consumer 在 onDragOver 用此判斷是否畫 indicator;onDragEnd 用同一函數判斷是否\n * 觸發 reorder callback —— 兩處共享一個 invariant,避免「indicator 顯示但 commit 不 fire」\n * 或反向 drift。\n *\n * 規則:\n * - source idx N,drop on idx N (self) → noop\n * - source idx N,drop on idx N-1 with side='after' = N(原位)→ noop\n * - source idx N,drop on idx N+1 with side='before' = N(原位)→ noop\n */\nexport function isReorderNoop(activeIdx: number, overIdx: number, side: 'before' | 'after'): boolean {\n if (activeIdx === overIdx) return true\n if (side === 'after' && overIdx + 1 === activeIdx) return true\n if (side === 'before' && overIdx - 1 === activeIdx) return true\n return false\n}\n\n// ── Ghost reconstruction(跨 region table row → 完整橫跨 ghost) ───────────\n\n/**\n * 為 dnd-kit DragOverlay 重建跨 region 的完整 row ghost(2026-05-07 v15.9 Bug A+B 修)。\n *\n * **Why cross-region**:Pinned column DataTable 結構 = 三 region(left / center / right)\n * 各 mount 一個 row div(同 `row.id`),listeners 只在 primary(center)。之前 ghost 只\n * 抓 primary region row → cells 只有 center 欄(missing pinned SKU / Updated)→ user 看\n * 到 ghost「跟 source 不一樣」(Bug B)。virtualized + 無 pinning 下亦可能因 region 多\n * 個 mount 衍生不可預期(Bug A)。\n *\n * **Algorithm**:\n * 1. 收集 `[role=\"row\"][data-sortable-row-id=\"${id}\"]` 所有 match(可能 1-3 個 region)\n * 2. DOM 文檔順序剛好是 left → center → right(因 body region 排列順序)\n * 3. 從每個 region row 的 children(role=cell)抽出 outerHTML,串成單一 flex row\n * 4. 總寬 = sum(region offsetWidth)\n * 5. Wrap 用 primary region 的 className(視覺一致)+ inline `display:flex; width:total`\n *\n * **Single-region case**(無 pinning,常見):退化成 single row clone + sanitize,行為跟舊版同。\n *\n * **Returns** `{ html, width }` 或 `null`(找不到 row)。\n */\nexport function reconstructFullRowGhost(\n rowId: string,\n tableRoot: HTMLElement | null,\n): { html: string; width: number } | null {\n // **codex P1 fix(2026-05-07 v15.13)**:tableRoot 強制 required(non-optional)。\n // 之前 fallback document 仍允許多 DataTable 同頁 row.id reuse(default index-based)→\n // cross-instance ghost 污染。strict-scope canonical:caller 必傳 tableRef.current\n // (`[data-data-table-outer]`),null 直接 return null(caller 該守 ref 已 mount)。\n if (!tableRoot) return null\n const escaped = rowId.replace(/([\"\\\\])/g, '\\\\$1')\n const allRows = Array.from(\n tableRoot.querySelectorAll<HTMLElement>(\n `[role=\"row\"][data-sortable-row-id=\"${escaped}\"]`,\n ),\n )\n if (allRows.length === 0) return null\n\n // Single-region:直接 clone 整 row + sanitize(無 pinning 場景,跟舊版一致)\n if (allRows.length === 1) {\n const sourceEl = allRows[0]\n const clone = sourceEl.cloneNode(true) as HTMLElement\n sanitizeGhostClone(clone, sourceEl.offsetWidth)\n return { html: clone.outerHTML, width: sourceEl.offsetWidth }\n }\n\n // Multi-region:抽 cells 串接。Primary region(`data-row-drag-source=\"true\"`)的\n // className + role 給 outer wrapper,確保 hover bg / border / row-height token 一致。\n const primaryRow =\n allRows.find((r) => r.dataset.rowDragSource === 'true') ?? allRows[0]\n const cellsHtml: string[] = []\n let totalWidth = 0\n for (const regionRow of allRows) {\n totalWidth += regionRow.offsetWidth\n for (const cell of Array.from(\n regionRow.querySelectorAll<HTMLElement>('[role=\"cell\"],[role=\"gridcell\"]'),\n )) {\n const cellClone = cell.cloneNode(true) as HTMLElement\n // Strip drag handle portal(只在 primary region)+ inline transform 殘餘\n cellClone.querySelectorAll('[data-drag-handle-portal]').forEach((n) => n.remove())\n cellClone.style.transform = 'none'\n cellsHtml.push(cellClone.outerHTML)\n }\n }\n // 套 primary row 的 class(cn 結果含 hover/border/sizing token)+ display:flex 顯式\n // 容納 cells,inline style override transform/position/opacity 避免 stale 殘餘\n const inlineStyle = `display:flex; width:${totalWidth}px; transform:none; position:static; opacity:1; transition:none;`\n const html = `<div role=\"row\" class=\"${primaryRow.className}\" style=\"${inlineStyle}\">${cellsHtml.join('')}</div>`\n return { html, width: totalWidth }\n}\n\n/**\n * Reset ghost clone inline styles + strip 干擾 attributes(transform/opacity/data-row-index 等)。\n * Internal helper, not exported.\n */\nfunction sanitizeGhostClone(el: HTMLElement, width: number): void {\n el.style.position = 'static'\n el.style.transform = 'none'\n el.style.transition = 'none'\n el.style.opacity = '1'\n el.style.zIndex = ''\n el.style.width = `${width}px`\n el.removeAttribute('data-row-index')\n el.removeAttribute('aria-rowindex')\n el.removeAttribute('data-hovered')\n el.querySelectorAll('[data-drag-handle-portal]').forEach((n) => n.remove())\n}\n\n// ── Type exports for consumer ─────────────────────────────────────────────\n\nexport type DropPosition = 'before' | 'after' | 'inside'\n"],"names":[],"mappings":"AA4CO,MAAM,kBAAkB;AAGxB,SAAS,gBAAgB,YAA0C;AACxE,SAAO,aAAa,EAAE,SAAS,0BAAA,IAA8B,CAAA;AAC/D;AAuBO,MAAM,mBAAmB;AAAA,EAC9B,QAAQ;AAAA,EACR,OAAO;AACT;AAiBO,MAAM,sBAAsB;AAAA;AAAA,EAEjC,QAAQ;AAAA,EACR,OAAO;AAAA;AAAA,EAEP,cAAc;AAAA,EACd,aAAa;AACf;AAOO,MAAM,sBAAsB;AAK5B,MAAM,mBAAmB;AAOzB,MAAM,wBAAwB;AAsB9B,MAAM,uBAAiC,CAAC,EAAE,WAAW,gBAAgB,uBAAuB;AACjG,MAAI,CAAC,kBAAkB,CAAC,iBAAkB,QAAO;AAKjD,MAAI,EAAE,aAAa,mBAAmB,EAAE,aAAa,gBAAiB,QAAO;AAC7E,QAAM,KAAK;AACX,MAAI,OAAO,GAAG,YAAY,YAAY,OAAO,GAAG,YAAY,SAAU,QAAO;AAC7E,QAAM,UAAU,GAAG,UAAU,iBAAiB;AAC9C,QAAM,UAAU,GAAG,UAAU,iBAAiB;AAC9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,UAAU,IAAI;AAAA,IACjB,GAAG,UAAU,IAAI;AAAA,EAAA;AAErB;AAiBO,SAAS,cAAc,WAAmB,SAAiB,MAAmC;AACnG,MAAI,cAAc,QAAS,QAAO;AAClC,MAAI,SAAS,WAAW,UAAU,MAAM,UAAW,QAAO;AAC1D,MAAI,SAAS,YAAY,UAAU,MAAM,UAAW,QAAO;AAC3D,SAAO;AACT;AAwBO,SAAS,wBACd,OACA,WACwC;AAKxC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,UAAU,MAAM,QAAQ,YAAY,MAAM;AAChD,QAAM,UAAU,MAAM;AAAA,IACpB,UAAU;AAAA,MACR,sCAAsC,OAAO;AAAA,IAAA;AAAA,EAC/C;AAEF,MAAI,QAAQ,WAAW,EAAG,QAAO;AAGjC,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,WAAW,QAAQ,CAAC;AAC1B,UAAM,QAAQ,SAAS,UAAU,IAAI;AACrC,uBAAmB,OAAO,SAAS,WAAW;AAC9C,WAAO,EAAE,MAAM,MAAM,WAAW,OAAO,SAAS,YAAA;AAAA,EAClD;AAIA,QAAM,aACJ,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,kBAAkB,MAAM,KAAK,QAAQ,CAAC;AACtE,QAAM,YAAsB,CAAA;AAC5B,MAAI,aAAa;AACjB,aAAW,aAAa,SAAS;AAC/B,kBAAc,UAAU;AACxB,eAAW,QAAQ,MAAM;AAAA,MACvB,UAAU,iBAA8B,iCAAiC;AAAA,IAAA,GACxE;AACD,YAAM,YAAY,KAAK,UAAU,IAAI;AAErC,gBAAU,iBAAiB,2BAA2B,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ;AACjF,gBAAU,MAAM,YAAY;AAC5B,gBAAU,KAAK,UAAU,SAAS;AAAA,IACpC;AAAA,EACF;AAGA,QAAM,cAAc,uBAAuB,UAAU;AACrD,QAAM,OAAO,0BAA0B,WAAW,SAAS,YAAY,WAAW,KAAK,UAAU,KAAK,EAAE,CAAC;AACzG,SAAO,EAAE,MAAM,OAAO,WAAA;AACxB;AAMA,SAAS,mBAAmB,IAAiB,OAAqB;AAChE,KAAG,MAAM,WAAW;AACpB,KAAG,MAAM,YAAY;AACrB,KAAG,MAAM,aAAa;AACtB,KAAG,MAAM,UAAU;AACnB,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,QAAQ,GAAG,KAAK;AACzB,KAAG,gBAAgB,gBAAgB;AACnC,KAAG,gBAAgB,eAAe;AAClC,KAAG,gBAAgB,cAAc;AACjC,KAAG,iBAAiB,2BAA2B,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ;AAC5E;"}
|
|
1
|
+
{"version":3,"file":"drag-visual.js","sources":["../../src/lib/drag-visual.ts"],"sourcesContent":["/**\n * Drag visual SSOT(2026-05-06 v14.5)\n *\n * DS canonical drag visual,3 處 consumer(TreeView / DataTable row drag / DataTable column reorder)\n * 共用同一組 styling constant — 對齊 TreeView 最早 codified 的 pattern。\n *\n * ## Pattern\n *\n * - **Source element**(被拖中的 row/column):`opacity-disabled`(= var(--opacity-disabled) 0.45 半透,user 仍看得到原位;2026-05-06 v14.5.2 由 opacity-30 改)\n * — 不是 `opacity:0` 全隱形,因 user 拖太遠看不到「源頭」會迷失方向。對齊 TreeView 最早 v1 + Notion / Atlassian / Linear。\n * - **Ghost preview**(浮動 follow cursor):透過 dnd-kit `<DragOverlay>` portal,clone source 的 outerHTML\n * strip transform/transition/opacity → render 在 portal 層,visually 跟 cursor 走。\n * - **Drop indicator**(目標位置藍細線):2px(`h-0.5` / `w-0.5`)`bg-primary` absolute line:\n * - **Row context**(TreeView / table row drag):水平線 在 target row 的 top(before)/ bottom(after)\n * - **Column context**(table column reorder):垂直線 在 target column 的 left(before)/ right(after)\n * - **Inside-drop highlight**(nested target,如拖到 TreeView 子層 / Table nested row 內):整 row `bg-primary-subtle`\n *\n * ## Why centralize\n *\n * 三處 drag 之前各自實作不一致(TreeView opacity:30 + indicator vs DataTable row opacity:0 no indicator\n * vs Column reorder opacity:0 + indicator)— M23 / M27 violation(DS 內 canonical 沒對齊)。\n * 抽到此 module 後 3 處 import 同 constants,未來改 1 處全 sync。\n *\n * ## Token usage\n *\n * - `bg-primary`:semantic state token(`--primary`)— 跟 focus state ring color 同 source(M23 一致)\n * - `bg-primary-subtle`:semantic primary subtle(`--primary-subtle`)— inside-drop dim background\n * - `h-0.5` / `w-0.5`:Tailwind size token = 2px(對齊 hairline divider thickness 概念)\n * - `opacity-disabled`:DS token var(--opacity-disabled)= 0.45(半透但仍清楚看到 outline;見下方 v14.5.2 note)\n */\n\nimport type * as React from 'react'\nimport type { Modifier } from '@dnd-kit/core'\n\n// ── Source element styling(被拖中的 source row/column)─────────────────────\n\n/**\n * Source element 拖中的 opacity className。\n *\n * **Reuse `opacity-disabled` token**(2026-05-06 v14.5.2 對齊 Atlassian Pragmatic):\n * Atlassian dnd guidelines 也用 `opacity.disabled` token 給 drag source dim(他們值 0.40,\n * 我們值 0.45,role 一致)。不另開 drag-source 專屬 token — single-role-per-value 哲學\n * + DS internal SSOT。\n */\nexport const dragSourceClass = 'opacity-disabled' as const\n\n/** Source element 拖中的 inline style(consume DS token `--opacity-disabled`)*/\nexport function dragSourceStyle(isDragging: boolean): React.CSSProperties {\n return isDragging ? { opacity: 'var(--opacity-disabled)' } : {}\n}\n\n/**\n * DragOverlay ghost 視覺 canonical(2026-05-06 v14.5.2 對齊 dnd-kit / Atlassian / Material):\n * **不 dim**(opacity:1)+ elevation shadow `shadow-[var(--elevation-200)]` + 顯式 bg\n * (`bg-surface` 或 `bg-surface-raised`)+ border。Lifted preview pattern,跟 surface 拉開\n * 視覺距離靠 shadow 不靠 opacity。\n *\n * Consumer 自己組 className(因 ghost 結構各自不同 — TreeView 是 icon+label compact pill,\n * DataTable 是 cloned row HTML wrapper),這裡只 codify 規則 不導出 class string。\n */\n\n// ── Drop indicator className(target row/column edge 顯藍細線)──────────────\n\n/**\n * Row drop indicator(水平線,跨 row 全寬)\n *\n * - `before`:在 target row top edge,提示「放開後會插入這 row 之前」\n * - `after`:在 target row bottom edge,提示「放開後會插入這 row 之後」\n *\n * **Indent option**:nested context(TreeView / table nested rows)可加 `style={{ left: indentPx }}`\n * 讓 indicator 隨 depth 縮排,視覺對齊 row content 起始位置。\n */\nexport const dropIndicatorRow = {\n before: 'absolute top-0 left-0 right-0 h-0.5 bg-primary z-10 pointer-events-none',\n after: 'absolute bottom-0 left-0 right-0 h-0.5 bg-primary z-10 pointer-events-none',\n} as const\n\n/**\n * Column drop indicator(垂直線,跨 column header height)\n *\n * - `before`:在 target column left edge(-1px outset 對齊 grid line)\n * - `after`:在 target column right edge(-1px outset 對齊 grid line)\n *\n * 兩種變體,視覺一致(2px primary line),只有 DOM mechanism 不同 — consumer 視 use case 選:\n *\n * - **`absoluteDiv`**(TreeView pattern):render absolute `<div>` child;\n * consumer 必須有 `position: relative` parent。\n * - **`pseudoBefore` / `pseudoAfter`**:用 Tailwind `before:` / `after:` pseudo-element;\n * consumer 不需 child element(適合 cloneElement 等不能加 child 的場景)。\n *\n * 兩變體 thickness / color / z-index 完全一致,差別純 implementation 機制。\n */\nexport const dropIndicatorColumn = {\n // Absolute div 變體(consumer render <div>)\n before: 'absolute top-0 bottom-0 left-[-1px] w-0.5 bg-primary z-10 pointer-events-none',\n after: 'absolute top-0 bottom-0 right-[-1px] w-0.5 bg-primary z-10 pointer-events-none',\n // Pseudo-element 變體(用 Tailwind before:/after:)\n pseudoBefore: 'before:absolute before:top-0 before:bottom-0 before:left-[-1px] before:w-0.5 before:bg-primary before:z-10 before:pointer-events-none before:content-[\"\"]',\n pseudoAfter: 'after:absolute after:top-0 after:bottom-0 after:right-[-1px] after:w-0.5 after:bg-primary after:z-10 after:pointer-events-none after:content-[\"\"]',\n} as const\n\n// ── Inside-drop highlight(nested context,target row 整列亮藍 subtle)─────\n\n/**\n * Nested 拖入 highlight(TreeView / nested rows 拖到子層)。整 row 加 background。\n */\nexport const dropIndicatorInside = 'bg-primary-subtle' as const\n\n// ── Cursor classes ────────────────────────────────────────────────────────\n\n/** Draggable element 拖中時的 cursor(grabbing)*/\nexport const dragActiveCursor = 'cursor-grabbing' as const\n/**\n * Combined drag handle cursor canonical(2026-05-07 v15.7 user directive):\n * **只 cursor-grab,不變 grabbing**。對齊 Material / Carbon / Polaris canonical —\n * drag affordance 由 visible button 提供,cursor 變化反而干擾 indicator+ghost 視覺焦點。\n * Consumer 直接 cn(...)。\n */\nexport const dragHandleCursorClass = 'cursor-grab' as const\n\n// ── Modifier:Ghost 跟 cursor 維持初始相對位置 SSOT(v15.7 user directive) ─\n\n/**\n * Ghost 跟 cursor 維持「cursor 在 ghost 點下時相對位置」SSOT(對齊 user directive\n * 「ghost 和 cursor 要確保相對位置是合理的而且有 ssot」)。\n *\n * **Why need this**:dnd-kit DragOverlay 預設 ghost.top-left = source rect (`setNodeRef`)\n * 的 top-left。當 source ≠ activator 位置不同(e.g. activator = portal'd Button 在\n * table outer 邊緣;source = primary row 在 table 內 column region),cursor 點下\n * activator → cursor 落在 source rect 的 negative offset → ghost 永遠相對 source rect\n * 起算 → cursor 跟 ghost 起始就有大 offset(e.g. 100+ px),drag 期間維持錯位。\n *\n * **本 modifier**:把 (cursor.initial - source.left, cursor.initial - source.top)\n * 加進 transform → ghost.top-left 永遠對齊 cursor 位置(cursor 在 ghost 左前角)。\n * Consumer 套進 DndContext.modifiers array,所有 drag scenario 套用 = SSOT。\n *\n * **對齊世界級**:Linear / Notion / Jira / AG Grid 的 row drag ghost 都跟 cursor\n * 維持初始相對位置(整列拖時 cursor 在 click 點;button 拖時 cursor 在 button 位置)—\n * 這個 modifier 把「整列拖 + 整列 ghost」UX 在 button-only / 多 region 場景下也達到。\n */\nexport const snapToCursorModifier: Modifier = ({ transform, activatorEvent, draggingNodeRect }) => {\n if (!activatorEvent || !draggingNodeRect) return transform\n // **codex P1 fix(2026-05-07 v15.12)**:KeyboardSensor 走 KeyboardEvent,沒\n // `clientX/clientY` → `offsetX/offsetY = NaN` → ghost transform 變 NaN/NaN →\n // overlay 定位+collision 全 corrupt → keyboard-initiated drag 直接壞。Guard 條件:\n // 只 pointer/mouse event 才平移,keyboard 走 dnd-kit 預設(無 modifier 偏移)。\n if (!('clientX' in activatorEvent) || !('clientY' in activatorEvent)) return transform\n const ev = activatorEvent as PointerEvent | MouseEvent\n if (typeof ev.clientX !== 'number' || typeof ev.clientY !== 'number') return transform\n const offsetX = ev.clientX - draggingNodeRect.left\n const offsetY = ev.clientY - draggingNodeRect.top\n return {\n ...transform,\n x: transform.x + offsetX,\n y: transform.y + offsetY,\n }\n}\n\n// ── Reorder noop helper(SSOT 對齊 row + column reorder canonical) ────────\n\n/**\n * Drop position 等同 source 原位 → noop。對齊 Linear / Notion / Jira / TreeView SSOT:\n * user 拉到 source 鄰近(視覺等同原位)應 cancel,不 commit reorder 也不顯 indicator。\n *\n * Consumer 在 onDragOver 用此判斷是否畫 indicator;onDragEnd 用同一函數判斷是否\n * 觸發 reorder callback —— 兩處共享一個 invariant,避免「indicator 顯示但 commit 不 fire」\n * 或反向 drift。\n *\n * 規則:\n * - source idx N,drop on idx N (self) → noop\n * - source idx N,drop on idx N-1 with side='after' = N(原位)→ noop\n * - source idx N,drop on idx N+1 with side='before' = N(原位)→ noop\n */\nexport function isReorderNoop(activeIdx: number, overIdx: number, side: 'before' | 'after'): boolean {\n if (activeIdx === overIdx) return true\n if (side === 'after' && overIdx + 1 === activeIdx) return true\n if (side === 'before' && overIdx - 1 === activeIdx) return true\n return false\n}\n\n// ── Ghost reconstruction(跨 region table row → 完整橫跨 ghost) ───────────\n\n/**\n * 為 dnd-kit DragOverlay 重建跨 region 的完整 row ghost(2026-05-07 v15.9 Bug A+B 修)。\n *\n * **Why cross-region**:Pinned column DataTable 結構 = 三 region(left / center / right)\n * 各 mount 一個 row div(同 `row.id`),listeners 只在 primary(center)。之前 ghost 只\n * 抓 primary region row → cells 只有 center 欄(missing pinned SKU / Updated)→ user 看\n * 到 ghost「跟 source 不一樣」(Bug B)。virtualized + 無 pinning 下亦可能因 region 多\n * 個 mount 衍生不可預期(Bug A)。\n *\n * **Algorithm**:\n * 1. 收集 `[role=\"row\"][data-sortable-row-id=\"${id}\"]` 所有 match(可能 1-3 個 region)\n * 2. DOM 文檔順序剛好是 left → center → right(因 body region 排列順序)\n * 3. 從每個 region row 的 children(role=cell)抽出 outerHTML,串成單一 flex row\n * 4. 總寬 = sum(region offsetWidth)\n * 5. Wrap 用 primary region 的 className(視覺一致)+ inline `display:flex; width:total`\n *\n * **Single-region case**(無 pinning,常見):退化成 single row clone + sanitize,行為跟舊版同。\n *\n * **Returns** `{ html, width }` 或 `null`(找不到 row)。\n */\nexport function reconstructFullRowGhost(\n rowId: string,\n tableRoot: HTMLElement | null,\n): { html: string; width: number } | null {\n // **codex P1 fix(2026-05-07 v15.13)**:tableRoot 強制 required(non-optional)。\n // 之前 fallback document 仍允許多 DataTable 同頁 row.id reuse(default index-based)→\n // cross-instance ghost 污染。strict-scope canonical:caller 必傳 tableRef.current\n // (`[data-data-table-outer]`),null 直接 return null(caller 該守 ref 已 mount)。\n if (!tableRoot) return null\n const escaped = rowId.replace(/([\"\\\\])/g, '\\\\$1')\n const allRows = Array.from(\n tableRoot.querySelectorAll<HTMLElement>(\n `[role=\"row\"][data-sortable-row-id=\"${escaped}\"]`,\n ),\n )\n if (allRows.length === 0) return null\n\n // Single-region:直接 clone 整 row + sanitize(無 pinning 場景,跟舊版一致)\n if (allRows.length === 1) {\n const sourceEl = allRows[0]\n const clone = sourceEl.cloneNode(true) as HTMLElement\n sanitizeGhostClone(clone, sourceEl.offsetWidth)\n return { html: clone.outerHTML, width: sourceEl.offsetWidth }\n }\n\n // Multi-region:抽 cells 串接。Primary region(`data-row-drag-source=\"true\"`)的\n // className + role 給 outer wrapper,確保 hover bg / border / row-height token 一致。\n const primaryRow =\n allRows.find((r) => r.dataset.rowDragSource === 'true') ?? allRows[0]\n const cellsHtml: string[] = []\n let totalWidth = 0\n for (const regionRow of allRows) {\n totalWidth += regionRow.offsetWidth\n for (const cell of Array.from(\n regionRow.querySelectorAll<HTMLElement>('[role=\"cell\"],[role=\"gridcell\"]'),\n )) {\n const cellClone = cell.cloneNode(true) as HTMLElement\n // Strip drag handle portal(只在 primary region)+ inline transform 殘餘\n cellClone.querySelectorAll('[data-drag-handle-portal]').forEach((n) => n.remove())\n cellClone.style.transform = 'none'\n cellsHtml.push(cellClone.outerHTML)\n }\n }\n // 套 primary row 的 class(cn 結果含 hover/border/sizing token)+ display:flex 顯式\n // 容納 cells,inline style override transform/position/opacity 避免 stale 殘餘\n const inlineStyle = `display:flex; width:${totalWidth}px; transform:none; position:static; opacity:1; transition:none;`\n const html = `<div role=\"row\" class=\"${primaryRow.className}\" style=\"${inlineStyle}\">${cellsHtml.join('')}</div>`\n return { html, width: totalWidth }\n}\n\n/**\n * Reset ghost clone inline styles + strip 干擾 attributes(transform/opacity/data-row-index 等)。\n * Internal helper, not exported.\n */\nfunction sanitizeGhostClone(el: HTMLElement, width: number): void {\n el.style.position = 'static'\n el.style.transform = 'none'\n el.style.transition = 'none'\n el.style.opacity = '1'\n el.style.zIndex = ''\n el.style.width = `${width}px`\n el.removeAttribute('data-row-index')\n el.removeAttribute('aria-rowindex')\n el.removeAttribute('data-hovered')\n el.querySelectorAll('[data-drag-handle-portal]').forEach((n) => n.remove())\n}\n\n// ── Type exports for consumer ─────────────────────────────────────────────\n\nexport type DropPosition = 'before' | 'after' | 'inside'\n"],"names":[],"mappings":"AA4CO,MAAM,kBAAkB;AAGxB,SAAS,gBAAgB,YAA0C;AACxE,SAAO,aAAa,EAAE,SAAS,0BAAA,IAA8B,CAAA;AAC/D;AAuBO,MAAM,mBAAmB;AAAA,EAC9B,QAAQ;AAAA,EACR,OAAO;AACT;AAiBO,MAAM,sBAAsB;AAAA;AAAA,EAEjC,QAAQ;AAAA,EACR,OAAO;AAAA;AAAA,EAEP,cAAc;AAAA,EACd,aAAa;AACf;AAOO,MAAM,sBAAsB;AAK5B,MAAM,mBAAmB;AAOzB,MAAM,wBAAwB;AAsB9B,MAAM,uBAAiC,CAAC,EAAE,WAAW,gBAAgB,uBAAuB;AACjG,MAAI,CAAC,kBAAkB,CAAC,iBAAkB,QAAO;AAKjD,MAAI,EAAE,aAAa,mBAAmB,EAAE,aAAa,gBAAiB,QAAO;AAC7E,QAAM,KAAK;AACX,MAAI,OAAO,GAAG,YAAY,YAAY,OAAO,GAAG,YAAY,SAAU,QAAO;AAC7E,QAAM,UAAU,GAAG,UAAU,iBAAiB;AAC9C,QAAM,UAAU,GAAG,UAAU,iBAAiB;AAC9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,UAAU,IAAI;AAAA,IACjB,GAAG,UAAU,IAAI;AAAA,EAAA;AAErB;AAiBO,SAAS,cAAc,WAAmB,SAAiB,MAAmC;AACnG,MAAI,cAAc,QAAS,QAAO;AAClC,MAAI,SAAS,WAAW,UAAU,MAAM,UAAW,QAAO;AAC1D,MAAI,SAAS,YAAY,UAAU,MAAM,UAAW,QAAO;AAC3D,SAAO;AACT;AAwBO,SAAS,wBACd,OACA,WACwC;AAKxC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,UAAU,MAAM,QAAQ,YAAY,MAAM;AAChD,QAAM,UAAU,MAAM;AAAA,IACpB,UAAU;AAAA,MACR,sCAAsC,OAAO;AAAA,IAAA;AAAA,EAC/C;AAEF,MAAI,QAAQ,WAAW,EAAG,QAAO;AAGjC,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,WAAW,QAAQ,CAAC;AAC1B,UAAM,QAAQ,SAAS,UAAU,IAAI;AACrC,uBAAmB,OAAO,SAAS,WAAW;AAC9C,WAAO,EAAE,MAAM,MAAM,WAAW,OAAO,SAAS,YAAA;AAAA,EAClD;AAIA,QAAM,aACJ,QAAQ,KAAK,CAAC,MAAM,EAAE,QAAQ,kBAAkB,MAAM,KAAK,QAAQ,CAAC;AACtE,QAAM,YAAsB,CAAA;AAC5B,MAAI,aAAa;AACjB,aAAW,aAAa,SAAS;AAC/B,kBAAc,UAAU;AACxB,eAAW,QAAQ,MAAM;AAAA,MACvB,UAAU,iBAA8B,iCAAiC;AAAA,IAAA,GACxE;AACD,YAAM,YAAY,KAAK,UAAU,IAAI;AAErC,gBAAU,iBAAiB,2BAA2B,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ;AACjF,gBAAU,MAAM,YAAY;AAC5B,gBAAU,KAAK,UAAU,SAAS;AAAA,IACpC;AAAA,EACF;AAGA,QAAM,cAAc,uBAAuB,UAAU;AACrD,QAAM,OAAO,0BAA0B,WAAW,SAAS,YAAY,WAAW,KAAK,UAAU,KAAK,EAAE,CAAC;AACzG,SAAO,EAAE,MAAM,OAAO,WAAA;AACxB;AAMA,SAAS,mBAAmB,IAAiB,OAAqB;AAChE,KAAG,MAAM,WAAW;AACpB,KAAG,MAAM,YAAY;AACrB,KAAG,MAAM,aAAa;AACtB,KAAG,MAAM,UAAU;AACnB,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,QAAQ,GAAG,KAAK;AACzB,KAAG,gBAAgB,gBAAgB;AACnC,KAAG,gBAAgB,eAAe;AAClC,KAAG,gBAAgB,cAAc;AACjC,KAAG,iBAAiB,2BAA2B,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ;AAC5E;"}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -9,4 +9,5 @@ import { type ClassValue } from 'clsx';
|
|
|
9
9
|
* 例如 cn("px-4", "px-2") → "px-2"(後者優先)
|
|
10
10
|
*/
|
|
11
11
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
12
|
+
export declare function lineClampClass(n: number | undefined | null): string | undefined;
|
|
12
13
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/lib/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAkF5C;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAkF5C;;;;;;;;GAQG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAUD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAS/E"}
|
package/dist/lib/utils.js
CHANGED
|
@@ -75,7 +75,27 @@ const twMerge = extendTailwindMerge({
|
|
|
75
75
|
function cn(...inputs) {
|
|
76
76
|
return twMerge(clsx(inputs));
|
|
77
77
|
}
|
|
78
|
+
const LINE_CLAMP = {
|
|
79
|
+
1: "line-clamp-1",
|
|
80
|
+
2: "line-clamp-2",
|
|
81
|
+
3: "line-clamp-3",
|
|
82
|
+
4: "line-clamp-4",
|
|
83
|
+
5: "line-clamp-5",
|
|
84
|
+
6: "line-clamp-6"
|
|
85
|
+
};
|
|
86
|
+
function lineClampClass(n) {
|
|
87
|
+
var _a;
|
|
88
|
+
if (n == null || n <= 0) return void 0;
|
|
89
|
+
const rounded = Math.round(n);
|
|
90
|
+
if (rounded !== n || n > 6) {
|
|
91
|
+
if (typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "production") {
|
|
92
|
+
console.warn(`[DS] line-clamp 值 ${n} 超出 canonical 範圍 1-6(或非整數)→ clamp 回 6。請傳 1-6 整數。`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return LINE_CLAMP[Math.min(6, Math.max(1, rounded))];
|
|
96
|
+
}
|
|
78
97
|
export {
|
|
79
|
-
cn
|
|
98
|
+
cn,
|
|
99
|
+
lineClampClass
|
|
80
100
|
};
|
|
81
101
|
//# sourceMappingURL=utils.js.map
|
package/dist/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/lib/utils.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n/**\n * 擴充 tailwind-merge,讓它認識設計系統的自訂 typography 與 text-color utilities。\n *\n * 預設 tailwind-merge 看到 `text-{xxx}` 自訂 class 會用 heuristic 猜它是 font-size\n * 還是 color——猜錯就會把不該放在同一組的 class 誤判為衝突,然後 strip 掉其中一個。\n *\n * 實際發生過的 bug:`text-body`(font-size 14px)和 `text-fg-secondary`(color)\n * 被同時放進 font-size group,tailwind-merge 把 `text-body` 吃掉,導致元件\n * inherit 父層 16px,description 永遠跟 label 同字級。\n *\n * 修法:**font-size group 和 text-color group 都明確列舉**,不留猜測空間。\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n 'text-h1', 'text-h2', 'text-h3', 'text-h4', 'text-h5', 'text-h6',\n 'text-body-lg', 'text-body', 'text-caption', 'text-footnote',\n ],\n // 自訂 text-color utilities(對應 semantic.css 的 `@theme inline --color-*` bridge)。\n //\n // 任何新增的 text-{semantic-name} color utility 都必須在這裡登記,否則\n // tailwind-merge 會誤判成 font-size 與 typography 衝突(歷史 bug:\n // text-body + text-fg-secondary 被 strip)。\n //\n // 完整家族對齊 semantic.css: 5 狀態色(primary/error/success/warning/info)\n // × 3 互動階(base/hover/active)+ text(on-subtle-bg)+ subtle(含 primary)\n // + notification(紅 badge)+ neutral foreground 四階。\n 'text-color': [\n // Neutral foreground 家族\n 'text-foreground',\n 'text-fg-secondary',\n 'text-fg-muted',\n 'text-fg-disabled',\n 'text-inverse-fg',\n 'text-on-emphasis', // 白字於夠深的飽和色底(Avatar / Tag solid / Steps filled indicator)\n 'text-on-emphasis-dark', // 深字於太亮的飽和色底(yellow/amber/orange/lime solid;原 warning-foreground)\n\n // Status 基色(base)\n 'text-primary',\n 'text-error',\n 'text-success',\n 'text-warning',\n 'text-info',\n 'text-notification',\n\n // Status 互動階(hover / active)\n 'text-primary-hover',\n 'text-primary-active',\n 'text-error-hover',\n 'text-error-active',\n 'text-success-hover',\n 'text-success-active',\n 'text-warning-hover',\n 'text-warning-active',\n 'text-info-hover',\n 'text-info-active',\n\n // on-subtle-bg 版本(深色文字配淺色底)\n 'text-primary-text',\n 'text-error-text',\n 'text-success-text',\n 'text-warning-text',\n 'text-info-text',\n\n // Subtle 文字(少用,保持完整家族一致)\n 'text-primary-subtle',\n 'text-error-subtle',\n 'text-success-subtle',\n 'text-warning-subtle',\n 'text-info-subtle',\n ],\n // Custom opacity utility(對應 tokens/opacity/opacity.css `@utility opacity-disabled`)。\n // 不註冊 group → tailwind-merge 用 default opacity-N heuristic,可能誤判 class 衝突。\n 'opacity': ['opacity-disabled'],\n },\n },\n})\n\n/**\n * cn() — Tailwind class 合併工具\n *\n * 用法:\n * cn(\"px-4 py-2\", isActive && \"bg-primary\", className)\n *\n * 原理:clsx 處理條件式 class,twMerge 解決 Tailwind class 衝突\n * 例如 cn(\"px-4\", \"px-2\") → \"px-2\"(後者優先)\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"],"names":[],"mappings":";;AAeA,MAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,aAAa;AAAA,MACX,aAAa;AAAA,QACX;AAAA,QAAW;AAAA,QAAW;AAAA,QAAW;AAAA,QAAW;AAAA,QAAW;AAAA,QACvD;AAAA,QAAgB;AAAA,QAAa;AAAA,QAAgB;AAAA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAW/C,cAAc;AAAA;AAAA,QAEZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QACA;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA;AAAA;AAAA,MAIF,WAAW,CAAC,kBAAkB;AAAA,IAAA;AAAA,EAChC;AAEJ,CAAC;AAWM,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/lib/utils.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n/**\n * 擴充 tailwind-merge,讓它認識設計系統的自訂 typography 與 text-color utilities。\n *\n * 預設 tailwind-merge 看到 `text-{xxx}` 自訂 class 會用 heuristic 猜它是 font-size\n * 還是 color——猜錯就會把不該放在同一組的 class 誤判為衝突,然後 strip 掉其中一個。\n *\n * 實際發生過的 bug:`text-body`(font-size 14px)和 `text-fg-secondary`(color)\n * 被同時放進 font-size group,tailwind-merge 把 `text-body` 吃掉,導致元件\n * inherit 父層 16px,description 永遠跟 label 同字級。\n *\n * 修法:**font-size group 和 text-color group 都明確列舉**,不留猜測空間。\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [\n 'text-h1', 'text-h2', 'text-h3', 'text-h4', 'text-h5', 'text-h6',\n 'text-body-lg', 'text-body', 'text-caption', 'text-footnote',\n ],\n // 自訂 text-color utilities(對應 semantic.css 的 `@theme inline --color-*` bridge)。\n //\n // 任何新增的 text-{semantic-name} color utility 都必須在這裡登記,否則\n // tailwind-merge 會誤判成 font-size 與 typography 衝突(歷史 bug:\n // text-body + text-fg-secondary 被 strip)。\n //\n // 完整家族對齊 semantic.css: 5 狀態色(primary/error/success/warning/info)\n // × 3 互動階(base/hover/active)+ text(on-subtle-bg)+ subtle(含 primary)\n // + notification(紅 badge)+ neutral foreground 四階。\n 'text-color': [\n // Neutral foreground 家族\n 'text-foreground',\n 'text-fg-secondary',\n 'text-fg-muted',\n 'text-fg-disabled',\n 'text-inverse-fg',\n 'text-on-emphasis', // 白字於夠深的飽和色底(Avatar / Tag solid / Steps filled indicator)\n 'text-on-emphasis-dark', // 深字於太亮的飽和色底(yellow/amber/orange/lime solid;原 warning-foreground)\n\n // Status 基色(base)\n 'text-primary',\n 'text-error',\n 'text-success',\n 'text-warning',\n 'text-info',\n 'text-notification',\n\n // Status 互動階(hover / active)\n 'text-primary-hover',\n 'text-primary-active',\n 'text-error-hover',\n 'text-error-active',\n 'text-success-hover',\n 'text-success-active',\n 'text-warning-hover',\n 'text-warning-active',\n 'text-info-hover',\n 'text-info-active',\n\n // on-subtle-bg 版本(深色文字配淺色底)\n 'text-primary-text',\n 'text-error-text',\n 'text-success-text',\n 'text-warning-text',\n 'text-info-text',\n\n // Subtle 文字(少用,保持完整家族一致)\n 'text-primary-subtle',\n 'text-error-subtle',\n 'text-success-subtle',\n 'text-warning-subtle',\n 'text-info-subtle',\n ],\n // Custom opacity utility(對應 tokens/opacity/opacity.css `@utility opacity-disabled`)。\n // 不註冊 group → tailwind-merge 用 default opacity-N heuristic,可能誤判 class 衝突。\n 'opacity': ['opacity-disabled'],\n },\n },\n})\n\n/**\n * cn() — Tailwind class 合併工具\n *\n * 用法:\n * cn(\"px-4 py-2\", isActive && \"bg-primary\", className)\n *\n * 原理:clsx 處理條件式 class,twMerge 解決 Tailwind class 衝突\n * 例如 cn(\"px-4\", \"px-2\") → \"px-2\"(後者優先)\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\n// ── line-clamp 安全對照(2026-07-18 決策4)────────────────────────────────────\n// 動態 `line-clamp-${n}` / `line-clamp-[${n}]` = Tailwind 掃不到具體值 → silent 失效(失敗記憶索引:\n// 「Tailwind v4 動態 class」陷阱)。此 map 全靜態字串 → Tailwind 可掃。canonical 上限 1-6(spec maxLines/\n// descriptionClamp ≤6);超界值 dev-warn + clamp 回 6(不 crash、不靜默失效)。null/0/undefined → 不 clamp。\nconst LINE_CLAMP: Record<number, string> = {\n 1: 'line-clamp-1', 2: 'line-clamp-2', 3: 'line-clamp-3',\n 4: 'line-clamp-4', 5: 'line-clamp-5', 6: 'line-clamp-6',\n}\nexport function lineClampClass(n: number | undefined | null): string | undefined {\n if (n == null || n <= 0) return undefined\n const rounded = Math.round(n)\n if (rounded !== n || n > 6) {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n console.warn(`[DS] line-clamp 值 ${n} 超出 canonical 範圍 1-6(或非整數)→ clamp 回 6。請傳 1-6 整數。`)\n }\n }\n return LINE_CLAMP[Math.min(6, Math.max(1, rounded))]\n}\n"],"names":[],"mappings":";;AAeA,MAAM,UAAU,oBAAoB;AAAA,EAClC,QAAQ;AAAA,IACN,aAAa;AAAA,MACX,aAAa;AAAA,QACX;AAAA,QAAW;AAAA,QAAW;AAAA,QAAW;AAAA,QAAW;AAAA,QAAW;AAAA,QACvD;AAAA,QAAgB;AAAA,QAAa;AAAA,QAAgB;AAAA,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAW/C,cAAc;AAAA;AAAA,QAEZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QACA;AAAA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,QAGA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA;AAAA;AAAA,MAIF,WAAW,CAAC,kBAAkB;AAAA,IAAA;AAAA,EAChC;AAEJ,CAAC;AAWM,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;AAMA,MAAM,aAAqC;AAAA,EACzC,GAAG;AAAA,EAAgB,GAAG;AAAA,EAAgB,GAAG;AAAA,EACzC,GAAG;AAAA,EAAgB,GAAG;AAAA,EAAgB,GAAG;AAC3C;AACO,SAAS,eAAe,GAAkD;;AAC/E,MAAI,KAAK,QAAQ,KAAK,EAAG,QAAO;AAChC,QAAM,UAAU,KAAK,MAAM,CAAC;AAC5B,MAAI,YAAY,KAAK,IAAI,GAAG;AAC1B,QAAI,OAAO,YAAY,iBAAe,aAAQ,QAAR,mBAAa,cAAa,cAAc;AAC5E,cAAQ,KAAK,qBAAqB,CAAC,kDAAkD;AAAA,IACvF;AAAA,EACF;AACA,SAAO,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AACrD;"}
|
|
@@ -294,6 +294,7 @@ export interface ItemAvatarProps extends Omit<AvatarProps, "size"> {
|
|
|
294
294
|
* 不可能硬寫錯誤尺寸。
|
|
295
295
|
*/
|
|
296
296
|
export declare const ItemAvatar: React.ForwardRefExoticComponent<ItemAvatarProps & React.RefAttributes<HTMLDivElement>>;
|
|
297
|
+
/** @internal — internal ItemInlineActionButton 的 props;隨其元件同走 subpath,不進 root barrel front-door。 */
|
|
297
298
|
export interface ItemInlineActionButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
298
299
|
/** Lucide icon 元件 */
|
|
299
300
|
icon: LucideIcon;
|
|
@@ -338,6 +339,7 @@ export interface ItemInlineActionButtonProps extends Omit<React.ButtonHTMLAttrib
|
|
|
338
339
|
}
|
|
339
340
|
/** @internal — L3 inline-action primitive;app code 禁直接 import,經 MenuItem/Row endActions 消費(inline-action.spec.md)。 */
|
|
340
341
|
export declare const ItemInlineActionButton: React.ForwardRefExoticComponent<ItemInlineActionButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
342
|
+
/** @internal — internal ItemInlineAction 的 props;隨其元件同走 subpath,不進 root barrel front-door。 */
|
|
341
343
|
export interface ItemInlineActionProps {
|
|
342
344
|
action: InlineActionConfig;
|
|
343
345
|
/** Size 覆寫(見 `ItemInlineActionButtonProps.size`)——Field Controls / Tag 必傳 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-anatomy.d.ts","sourceRoot":"","sources":["../../../src/patterns/element-anatomy/item-anatomy.tsx"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"item-anatomy.d.ts","sourceRoot":"","sources":["../../../src/patterns/element-anatomy/item-anatomy.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { cn, lineClampClass } from "../../lib/utils.js";
|
|
5
5
|
import { Avatar } from "../../components/Avatar/avatar.js";
|
|
6
6
|
import { Tooltip, TooltipTrigger, TooltipContent } from "../../components/Tooltip/tooltip.js";
|
|
7
7
|
const ICON_SIZE = {
|
|
@@ -98,7 +98,7 @@ const ItemContent = React.forwardRef(
|
|
|
98
98
|
}[descriptionTone];
|
|
99
99
|
const isLg = size === "lg";
|
|
100
100
|
const modeClass = mode === "scanning" ? isLg ? "text-[length:var(--font-body-size)] leading-compact" : "text-[length:var(--font-caption-size)] leading-compact" : isLg ? "text-[length:var(--font-body-size)]" : "";
|
|
101
|
-
const clampClass = descriptionClamp
|
|
101
|
+
const clampClass = lineClampClass(descriptionClamp) ?? "";
|
|
102
102
|
return /* @__PURE__ */ jsxs(
|
|
103
103
|
"div",
|
|
104
104
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-anatomy.js","sources":["../../../src/patterns/element-anatomy/item-anatomy.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — foundational SSOT for Family 1+2 row primitives + all item-anatomy helpers(ItemContent / ItemIcon / ItemPrefix / ItemSuffix / ItemInlineAction 等),拆檔會讓 primitive 跨檔 import 滿天飛\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { cva } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, type AvatarProps } from \"@/design-system/components/Avatar/avatar\"\nimport {\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n} from \"@/design-system/components/Tooltip/tooltip\"\n\n// ── InlineActionConfig ─────────────────────────────────────────────────────\n// 宣告式 API:consumer 只宣告 intent,host 根據 size tier 自動渲染。\n// 跨 Family 消費(Field family + Row family),SSOT 住 patterns/element-anatomy/\n// 對齊 inline-action.spec.md「API 設計」節。\n//\n// Canonical 實作:`ItemInlineAction`(本檔內)。\nexport interface InlineActionConfig {\n icon: LucideIcon\n /** aria-label,同時作為 tooltip 來源 */\n label: string\n /**\n * 點擊 handler。可選接收 React 事件物件——有需要時可呼叫 `stopPropagation()` 避免\n * 事件冒泡到父層(例如 Select 清除按鈕在 popover trigger 內,不想觸發 popover open)。\n */\n onClick: (e?: React.MouseEvent<HTMLButtonElement>) => void\n}\n\n/**\n * Item Layout — 共用工具與常數\n *\n * 這個檔案不是元件,是 `item-anatomy.spec.md` 的 code-level 實作。\n * 所有 row primitives(`MenuItem` / `TreeItem` / `SidebarMenuButton` /\n * `SelectionItem`)**必須 import 本 module 的 ICON_SIZE 和 helpers**,\n * 不要各自複製常數或 wrapper className——單一 source of truth,避免不一致。\n *\n * 詳細規則見 `item-anatomy.spec.md`。\n */\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\n/** Row primitive 的 size 變體,對齊 `--field-height-*` token family */\nexport type RowSize = \"sm\" | \"md\" | \"lg\"\n\n// ── Constants ──────────────────────────────────────────────────────────────\n\n/**\n * Icon 尺寸(px)per row size。\n *\n * - sm / md: 16px(標準 row icon)\n * - lg: 20px(對齊 text-body-lg 的視覺重量)\n *\n * **用法**:透過 Lucide icon 的 `size` prop 直接傳入,不要用 CSS `[&>svg]:size-*`\n * selector——當 icon 被包在 `<ItemPrefix>` 的 `h-[1lh]` wrapper 內時,\n * `>` 直接子選擇器會失效,Lucide icon 會 fallback 到預設 24px。\n *\n * ```tsx\n * import { ICON_SIZE } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n *\n * const iconPx = ICON_SIZE[size] // size: RowSize\n * <MyLucideIcon size={iconPx} />\n * ```\n */\nexport const ICON_SIZE: Record<RowSize, number> = {\n sm: 16,\n md: 16,\n lg: 20,\n}\n\n/**\n * Avatar 尺寸(px)per row size,分 inline / block 兩種模式。\n *\n * - **inline**(無 description,大宗情境)→ 對齊第一行 label,用 `h-[1lh]` 容器\n * sm: 20, md: 24, lg: 24\n * - **block**(有 description,avatar 跨越 label + desc 兩行)→ 對齊文字塊中心\n * sm: 32, md: 32, lg: 40\n *\n * **用法**:row context 的 consumer(包含 `asChild` pattern 的 Sidebar / Tree\n * consumer)**必須**用 `<ItemAvatar>` / `<ItemIcon>` helper 元件(自動查\n * RowSizeContext 對表),**禁止** `import { AVATAR_SIZE }` 手動查表、**不可硬寫\n * `size={24}`** — 硬寫會讓 sm 變體的 avatar 尺寸錯誤、跟 ICON_SIZE 對齊規則脫鉤。\n * 詳 item-anatomy.spec.md「Helper 程式化封裝 + review 防線」段。\n *\n * ```tsx\n * import { ItemAvatar } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n *\n * <ItemAvatar alt=\"Alan\" color=\"blue\" /> // 自動查 AVATAR_SIZE.inline[rowSize]\n * ```\n *\n * Canonical 消費者:MenuItem 直接 import 本檔 `AVATAR_SIZE`(menu-item.tsx,消費 .block/.inline;re-export 已完成,MenuItem 無內部查表)。\n */\nexport const AVATAR_SIZE = {\n inline: { sm: 20, md: 24, lg: 24 },\n block: { sm: 32, md: 32, lg: 40 },\n} as const satisfies Record<\"inline\" | \"block\", Record<RowSize, number>>\n\n/**\n * 回傳 uniform prefix slot 的 inline style(CSS variable form)。\n *\n * 用法:Row-primitive 頂層容器(例如 `<SidebarProvider uniformPrefix>` 的 wrapper)\n * 把這個 style 套在 `style` prop 上(預先放在 `--mixed-prefix-slot` 候選位置,\n * 由外層 CSS `:has()` 條件化套到 `--item-prefix-slot`),子樹內所有 `<ItemPrefix>` /\n * `<ItemIcon>` / `<ItemAvatar>` 會自動用固定寬槽對齊。\n *\n * 槽寬 = `AVATAR_SIZE.inline[size]`(20/24/24 @ sm/md/lg)——以最大的 inline\n * prefix(avatar)為基準,icon 在槽內 `justify-center`。\n *\n * ```tsx\n * <ul style={getUniformPrefixSlotStyle(size)}>\n * <ItemIcon icon={Folder} /> ← 16px icon 在 24px 槽內置中\n * <ItemAvatar alt=\"GitHub\" /> ← 24px logo 填滿槽\n * </ul>\n * ```\n */\nexport function getUniformPrefixSlotStyle(size: RowSize): React.CSSProperties {\n return { \"--item-prefix-slot\": `${AVATAR_SIZE.inline[size]}px` } as React.CSSProperties\n}\n\n/**\n * Inline action(suffix 裡的 hover-action icon button)hover 背景尺寸(px)。\n * 規則:icon size + 2px(每邊 +1px,用 absolute positioning 溢出不影響排版)。\n * 對齊 `inline-action.spec.md`(canonical SSOT,2026-04-24 自 item-anatomy.spec 抽出)。\n */\n// code-quality-allow: dead-export — public constant — DS API surface,consumer 可引(即使當前 internal-only)\nexport const INLINE_ACTION_HOVER_BG_SIZE: Record<RowSize, number> = {\n sm: 18,\n md: 18,\n lg: 22,\n}\n\n/**\n * `ROW_PADDING_BY_SIZE` — Family 1+2 row primitive 的 size → padding + typography 公式 SSOT\n *\n * Formula(canonical):`py = (field-height - 1lh) / 2`,對齊 1em text 到 field-height center。\n *\n * **消費者**:menuItemVariants / sidebarMenuButtonVariants / treeItemVariants / 未來 row primitive\n * **Rationale**:前先 3 cva 各自寫同一公式 → drift risk(item-anatomy.spec 有明文「SidebarMenuButton\n * 獨立實作風險」)。抽本 export 後改**一處全同步**,risk 消失(對齊 Meta-Pattern M17「SSOT 必可傳播」)。\n *\n * 2026-04-24 D Phase 1+2 consolidation 建立。\n */\nexport const ROW_PADDING_BY_SIZE: Record<RowSize, string> = {\n sm: 'text-body leading-compact py-[calc((var(--field-height-sm)-1lh)/2)]',\n md: 'text-body leading-compact py-[calc((var(--field-height-md)-1lh)/2)]',\n lg: 'text-body-lg leading-compact py-[calc((var(--field-height-lg)-1lh)/2)]',\n}\n\n// ── Helper components ──────────────────────────────────────────────────────\n\n/**\n * `<ItemPrefix>` — Row primitive 的 prefix slot wrapper。\n *\n * 把 prefix(icon / avatar / checkbox / indicator)包進 `h-[1lh]` 容器,\n * 強制對齊第一行文字中線。這是 item-layout 的底層規則(詳見 spec「Prefix 垂直對齊」)。\n *\n * **為什麼**:\n * - Row primitive 的 outer flex 是 `items-start`(多行 label 時 prefix 不飄移)\n * - 單行時 prefix 透過這個 wrapper 強制對齊第一行中線,視覺效果 = `items-center`\n * - 不同尺寸的 prefix(icon 16、Avatar 24、Checkbox 20)都能穩定置中\n *\n * 用法:\n * ```tsx\n * <div className=\"flex items-start gap-2\">\n * <ItemPrefix>\n * <Avatar size={24} />\n * </ItemPrefix>\n * <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n * </div>\n * ```\n *\n * asChild 的 consumer(例如 `<SidebarMenuButton asChild>`)**必須自己**用這個 wrapper\n * 包 prefix,不能省略。\n */\nexport const ItemPrefix = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n \"h-[1lh] shrink-0 flex items-center\",\n // Uniform prefix slot:讀取 `--item-prefix-slot` CSS variable,\n // fallback `auto`(= 預設行為,不佔額外寬度、不 center)。\n // Row-primitive 頂層容器(例如 `<SidebarProvider uniformPrefix>` 的 wrapper)\n // 若設定這個 var,小尺寸 prefix(icon 16px)會在固定寬槽(24px @ md)內置中,\n // 與大尺寸 prefix(app logo 24px)對齊,達成 label 齊左。\n // 詳見 item-anatomy.spec.md「Uniform prefix slot」節。\n \"min-w-[var(--item-prefix-slot,auto)] justify-center\",\n className\n )}\n {...props}\n />\n))\nItemPrefix.displayName = \"ItemPrefix\"\n\n/**\n * `itemPrefixAlignVariants` — Prefix 對齊高度的 cva SSOT(for 複雜 prefix 結構)。\n *\n * ── 存在的理由 ──\n * `<ItemPrefix>` primitive 預設單一 child(icon / avatar 任一)+ inline 對齊。\n * 但**複雜 prefix 結構**(e.g. MenuItem 可同時含 checkbox + icon/avatar 多 child)\n * 或**block mode**(大 prefix + description 對齊塊中心)需要自建 wrapper cva。\n * 本 cva 把 block formula SSOT 化,讓 MenuItem 等 consumer 共用,\n * 改值一處(token or cva)→ 所有 block-prefix consumer 同步。\n *\n * ── Align 變體(對齊 item-anatomy.spec.md「24px 閾值對齊規則」) ──\n * - `\"inline\"`:`h-[1lh]`,小 prefix(≤ 24px)對齊 label 第一行\n * - `\"block-sm\"` / `\"block-md\"`:大 prefix + scanning desc(sm/md menu),\n * 高度 = label(1lh) + gap(token) + desc(caption × 1.3)\n * - `\"block-lg\"`:大 prefix + scanning desc(lg menu),desc 改為 body × 1.3\n *\n * ── Consumer ──\n * MenuItem(inline / block-sm / block-md / block-lg)。其他 row primitive 簡單情境\n * 直接用 `<ItemPrefix>` 就好,不需此 cva。\n *\n * ── SSOT 傳播 ──\n * gap 用 `var(--item-gap-label-desc-scanning[-lg])` token(bare 變體已不存在,4 個 mode×size 變體);font-size 用 token-awareness\n * (`var(--font-caption-size)` / `var(--font-body-size)`)。改 token → 公式同步。\n */\n// code-quality-allow: long-function — cva variant/styles table — 拆 fn 會失去 type inference + 跨 fn 傳 config 反而難讀\nexport const itemPrefixAlignVariants = cva(\n \"flex items-center gap-2 shrink-0\",\n {\n variants: {\n align: {\n inline: \"h-[1lh]\",\n // sm/md desc = text-caption (12px × 1.3 = 15.6px)\n // block-sm / block-md:scanning mode(body + caption 組合)→ 用 scanning mode 專屬 gap token\n \"block-sm\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning)+var(--font-caption-size)*1.3)]\",\n \"block-md\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning)+var(--font-caption-size)*1.3)]\",\n // block-lg:scanning-lg mode(body-lg + body compact 組合)→ 用 scanning-lg mode 專屬 gap token\n \"block-lg\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning-lg)+var(--font-body-size)*1.3)]\",\n },\n },\n defaultVariants: {\n align: \"inline\",\n },\n }\n)\n\n/**\n * `<ItemLabel>` — Row primitive 的 label span。\n *\n * 預設 `min-w-0 flex-1 truncate`——單行截斷,佔滿剩餘 flex 空間。\n * 加 `data-sidebar=\"menu-label\"` attribute 讓 sidebar icon 模式的 CSS selector 能命中。\n *\n * 用法:\n * ```tsx\n * <ItemLabel>{label}</ItemLabel>\n * ```\n */\nexport const ItemLabel = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n data-sidebar=\"menu-label\"\n className={cn(\"min-w-0 flex-1 truncate\", className)}\n {...props}\n />\n))\nItemLabel.displayName = \"ItemLabel\"\n\n/**\n * `<ItemContent>` — Row primitive 的 label + optional description 內容區(SSOT)。\n *\n * ── 存在的唯一理由 ──\n * 封裝「flex-col + label + description + `mt-[var(--item-gap-label-desc-<mode>[-lg])]` gap」結構,\n * 避免 13+ 消費者各自 hard-code `mt-0.5`。改 token 一處,全 DS 同步。\n *\n * ── Consumer 偏離 canonical ──\n * 消費端若有**確切合理理由**不用 `<ItemContent>` / 需自訂 label/desc 行為,必在\n * 該元件 `spec.md` 明文寫下 rationale(對齊 item-anatomy canonical「偏離必明文」)。\n * 合法偏離範例:\n * - MenuItem 的 `leading-compact + text-caption` scanning-mode typography\n * → MenuItem spec 明文 rationale + 消費 `--item-gap-label-desc-scanning[-lg]` token 直接用\n * - SelectionItem 的 control slot 跟 block formula 綁定\n * → SelectionItem spec 明文 rationale\n *\n * ── Props ──\n * - `label`:label 內容(ReactNode,必填)\n * - `description`:description 內容(ReactNode,optional)\n * - `mode`:typography tier(scanning vs reading,世界級對齊 Material `dense` / Carbon `size` / Ant `size`)\n * - `\"reading\"`(預設):繼承 parent `text-body`(14px / 1.5 leading),舒適閱讀(Family 2 / Material body-large 派)\n * - `\"scanning\"`:desc 縮為 `text-caption`(12px)+ `leading-compact`(1.3),緊湊掃視\n * (Family 1 Menu item / Material body-medium dense 派)\n * - `descriptionTone`:desc 顏色語意\n * - `\"secondary\"`(預設):`text-fg-secondary`\n * - `\"error\"`:`text-error-text`\n * - `\"muted\"`:`text-fg-muted`\n * - `\"disabled\"`:`text-fg-disabled`(對齊 M24 state precedence:disabled > muted > emphasis)\n * - `descriptionWrap`:desc 多行 wrap(預設 true)/ false = truncate\n * - `labelClassName` / `descriptionClassName`:escape hatches(明文 rationale 才用)\n *\n * ── World-class benchmark ──\n * 6 家 DS(Material / Polaris / Atlassian / Apple HIG / Carbon / Ant)一致用\n * 14/20 vs 16/24 兩擋 body 表達 scanning vs reading。API 兩派:(A)密度 prop\n * Material `dense` / Carbon `size` / Ant `size`(本 DS 採 A 派);(B)typography\n * token 手選 Polaris / Atlassian / Apple。\n */\n\nexport interface ItemContentProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n label: React.ReactNode\n description?: React.ReactNode\n /**\n * Typography mode — **leading 行為**(跟 size 正交的另一維):\n * - `\"reading\"`(預設):default leading(1.5)— Family 2 List item 舒適閱讀\n * - `\"scanning\"`:`leading-compact`(1.3)+ desc 降一 tier —— Menu item / Step 掃視\n */\n mode?: \"reading\" | \"scanning\"\n /**\n * Size tier — **label 字級**(跟 mode 正交的另一維):\n * - `\"md\"`(預設):label `text-body`(14)\n * - `\"lg\"`:label `text-body-lg`(16)— desc 永遠比 label 小一 tier(body)\n *\n * 2 × 2 = 4 typography 組合,對應 4 tokens:\n * - md + reading → body(14/1.5) + body(14/1.5)\n * - md + scanning → body(14/1.3) + caption(12/1.3)\n * - lg + reading → body-lg(16/1.5) + body(14/1.5)\n * - lg + scanning → body-lg(16/1.3) + body(14/1.3)\n */\n size?: \"md\" | \"lg\"\n descriptionTone?: \"secondary\" | \"error\" | \"muted\" | \"disabled\"\n descriptionWrap?: boolean\n /**\n * Description 多行截斷(line-clamp-N)。undefined = 不 clamp(自由 wrap)。\n * Tailwind line-clamp utilities 支援 1-6。\n */\n descriptionClamp?: number\n /**\n * Description `break-words`(default `false`,僅在 consumer 需要時 opt-in)。\n * 使用情境:SelectionItem / Steps 這類 description 可能塞長英文 token 需 break。\n */\n descriptionBreakWords?: boolean\n /**\n * Label truncate (default `true`, 對齊 row-item 大宗 idiom)。\n * Opt-out(`false`)for card-like consumers(e.g. ProfileCard label 允許 wrap)。\n * World-class 對照:Material `ListItemText primaryTypographyProps.noWrap` default true。\n */\n labelTruncate?: boolean\n labelClassName?: string\n descriptionClassName?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nexport const ItemContent = React.forwardRef<HTMLDivElement, ItemContentProps>(\n (\n {\n label,\n description,\n mode = \"reading\",\n size = \"md\",\n descriptionTone = \"secondary\",\n descriptionWrap = true,\n descriptionClamp,\n descriptionBreakWords = false,\n labelTruncate = true,\n labelClassName,\n descriptionClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const toneClass = {\n secondary: \"text-fg-secondary\",\n error: \"text-error-text\",\n muted: \"text-fg-muted\",\n disabled: \"text-fg-disabled\",\n }[descriptionTone]\n\n // Typography desc class — 2 維 cross-product (mode × size):\n // ┌───────────┬────────────────────────────┬────────────────────────────┐\n // │ │ size=\"md\"(body label 14) │ size=\"lg\"(body-lg label 16)│\n // ├───────────┼────────────────────────────┼────────────────────────────┤\n // │ reading │ 繼承 body(14/1.5) │ body(14)default leading │\n // │ scanning │ caption(12)+ compact │ body(14)+ compact │\n // └───────────┴────────────────────────────┴────────────────────────────┘\n const isLg = size === \"lg\"\n const modeClass =\n mode === \"scanning\"\n ? isLg\n ? \"text-[length:var(--font-body-size)] leading-compact\"\n : \"text-[length:var(--font-caption-size)] leading-compact\"\n : isLg\n ? \"text-[length:var(--font-body-size)]\"\n : \"\"\n\n const clampClass = descriptionClamp ? `line-clamp-${descriptionClamp}` : \"\"\n\n return (\n <div\n ref={ref}\n className={cn(\"flex flex-col min-w-0 flex-1\", className)}\n {...props}\n >\n <span className={cn(labelTruncate && \"truncate\", labelClassName)}>{label}</span>\n {description && (\n <span\n className={cn(\n // Typography-mode-aware gap token(2026-04-23):\n // (mode, size)2 維正交 → 4 token 對應 4 typography 組合\n mode === \"scanning\"\n ? isLg\n ? \"mt-[var(--item-gap-label-desc-scanning-lg)]\"\n : \"mt-[var(--item-gap-label-desc-scanning)]\"\n : isLg\n ? \"mt-[var(--item-gap-label-desc-reading-lg)]\"\n : \"mt-[var(--item-gap-label-desc-reading)]\",\n modeClass,\n toneClass,\n clampClass,\n descriptionBreakWords && \"break-words\",\n !descriptionWrap && \"truncate\",\n descriptionClassName,\n )}\n >\n {description}\n </span>\n )}\n </div>\n )\n },\n)\nItemContent.displayName = \"ItemContent\"\n\n// ── Row size context ──────────────────────────────────────────────────────\n//\n// 作用:讓 row primitive(Sidebar / SelectMenu / Tree / DropdownMenu)只 propagate\n// 一次 size,整個子樹內的 <ItemIcon> / <ItemAvatar> 都自動查到正確尺寸。\n//\n// **存在的唯一理由**:消除 asChild pattern 下 consumer 硬寫 `size={24}` 的可能性。\n// 過去 consumer 必須 `useSidebar().size` 再手動傳 `AVATAR_SIZE.inline[size]`,\n// 每個 consumer 都是一次漂移機會(曾發生:sm 欄 avatar 顯示 24 而非 20,破壞規格)。\n// 現在 consumer 只寫 `<ItemAvatar alt=\"...\" />`,尺寸由 context 自動決定。\n//\n// Row primitive 實作者的責任:在元件內部用 `<RowSizeProvider value={size}>` 包裹\n// children(不論 asChild 或非 asChild 路徑),這樣任何 descendant 的 ItemIcon /\n// ItemAvatar 都會拿到正確尺寸。\n\nconst RowSizeContext = React.createContext<RowSize | null>(null)\n\n/** @internal — L3 row-size primitive;app code 不直接 import,由 MenuItem/Row/Field 等內部消費(inline-action.spec.md)。 */\nexport const RowSizeProvider = RowSizeContext.Provider\n\n/**\n * 讀取當前 row size。在 row primitive 子樹外呼叫會回 fallback(預設 \"md\")。\n * Row primitive 實作者不該呼叫這個——你們已經知道自己的 size。\n * 這個 hook 是給 `<ItemIcon>` / `<ItemAvatar>` 和 asChild consumer 的逃生艙用。\n */\nexport function useRowSize(fallback: RowSize = \"md\"): RowSize {\n return React.useContext(RowSizeContext) ?? fallback\n}\n\n// ── Prefab prefix elements ────────────────────────────────────────────────\n//\n// `<ItemIcon>` 和 `<ItemAvatar>` 是「prefix slot 的完成品元件」——自帶 ItemPrefix\n// wrapper + 自動查 ICON_SIZE / AVATAR_SIZE。\n//\n// **asChild consumer 必須用這兩個元件,禁止手動寫 `<Avatar size={N} />`**。\n// 禁令理由見 item-anatomy.spec.md「Avatar 尺寸選擇」節。\n\nexport interface ItemIconProps\n extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n /** Lucide icon 元件(不是 JSX element) */\n icon: LucideIcon\n}\n\n/**\n * `<ItemIcon icon={Folder} />` — 自動查 `ICON_SIZE[rowSize]`,包在 `ItemPrefix` 內。\n * 永遠對齊第一行 label(item-layout 的 icon 永遠 inline)。\n */\nexport const ItemIcon = React.forwardRef<SVGSVGElement, ItemIconProps>(\n ({ icon: Icon, className, ...props }, ref) => {\n const size = useRowSize()\n return (\n // data-prefix-type=\"icon\" 讓 row-primitive 容器的 `:has()` selector 能偵測類型,\n // 用於 `<SidebarProvider uniformPrefix>` opt-in 後的混用 auto-detection\n <ItemPrefix data-prefix-type=\"icon\">\n <Icon\n ref={ref}\n size={ICON_SIZE[size]}\n className={cn(\"shrink-0\", className)}\n aria-hidden\n {...props}\n />\n </ItemPrefix>\n )\n }\n)\nItemIcon.displayName = \"ItemIcon\"\n\nexport interface ItemAvatarProps extends Omit<AvatarProps, \"size\"> {\n /**\n * 對齊模式(選填,預設 `\"inline\"`):\n * - `\"inline\"`:視覺重量輕的 avatar,對齊第一行 label(20/24/24 @ sm/md/lg)\n * - `\"block\"`:視覺重量重的 avatar,對齊 label + description 文字塊(32/32/40 @ sm/md/lg)\n *\n * 預設 inline——大宗情境(扁平 row、footer user、主清單)都用這個。\n * 只有 avatar 是 item 主體(人物卡、顯著身份辨識)才用 block。\n *\n * 詳見 `item-anatomy.spec.md`「Avatar 尺寸選擇」節,含 24px 對齊模式閾值規則。\n */\n mode?: \"inline\" | \"block\"\n}\n\n/**\n * `<ItemAvatar alt=\"Alan\" color=\"blue\" />` — 自動查 `AVATAR_SIZE[mode][rowSize]`,\n * 包在 `ItemPrefix` 內。\n *\n * asChild consumer 的標準用法——不需知道 size,不需 import AVATAR_SIZE,\n * 不可能硬寫錯誤尺寸。\n */\n// code-quality-allow: long-function — forwardRef body 含 size×color×hoverCard 三正交 prop 渲染分流,拆出 sub-fn 反增 prop drilling\nexport const ItemAvatar = React.forwardRef<HTMLDivElement, ItemAvatarProps>(\n ({ mode = \"inline\", ...props }, ref) => {\n const size = useRowSize()\n return (\n // data-prefix-type=\"avatar\" 讓 row-primitive 容器的 `:has()` selector 能偵測類型,\n // 用於 `<SidebarProvider uniformPrefix>` opt-in 後的混用 auto-detection\n <ItemPrefix data-prefix-type=\"avatar\">\n <Avatar ref={ref} size={AVATAR_SIZE[mode][size]} {...props} />\n </ItemPrefix>\n )\n }\n)\nItemAvatar.displayName = \"ItemAvatar\"\n\n// ── Inline action (suffix slot) ───────────────────────────────────────────\n//\n// 共用 row-primitive 的 suffix inline action 渲染——消除過去 Input / NumberInput /\n// Tag / LinkInput / Combobox 各自複製 18 行 JSX 的狀況(見 `inline-action.spec.md`)。\n//\n// 規格(完全對齊 inline-action.spec.md — canonical SSOT,2026-04-24 自 item-anatomy.spec 抽出):\n// - Icon 尺寸 = ICON_SIZE[rowSize] (16/16/20)\n// - Hover bg 尺寸 = icon + 2 (18/18/22,INLINE_ACTION_HOVER_BG_SIZE)\n// - Hover bg 圓角 = rounded-md (sm/md)/ rounded-md (lg)\n// - Icon 顏色:fg-muted → foreground on hover/active\n// - 必須 <button type=\"button\">,aria-label = label,Tooltip(同 label)\n// - cursor-pointer\n\n// ── Low-level:ItemInlineActionButton ──\n//\n// Root 是 `<button>`(不是 Tooltip wrapper),可直接塞進 Radix `asChild` pattern\n// 例如 `<Collapsible.Trigger asChild>`, `<Popover.Trigger asChild>` 等。\n//\n// 自動從 `RowSizeContext` 查:\n// - 寬高 = ICON_SIZE[size]\n// - Hover bg 尺寸 = INLINE_ACTION_HOVER_BG_SIZE[size]\n// - Hover bg 圓角 = rounded-md (sm/md) / rounded-md (lg)\n// - Icon color = fg-muted → foreground on hover/active\n//\n// 接受任何 button props 並 spread(讓 Radix Slot 可以 merge onClick / data-state 等)\n\nexport interface ItemInlineActionButtonProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n /** Lucide icon 元件 */\n icon: LucideIcon\n /** 可選:額外加在 icon 上的 className(例如 rotate transition) */\n iconClassName?: string\n /**\n * Size 明確覆寫(用於 fields / Tag 等**不在 `RowSizeProvider` 樹內**的 host):\n * 傳了就以此為準,否則從 `RowSizeContext` 讀(fallback \"md\")。\n *\n * 使用時機:\n * - Row primitive 子樹(Sidebar / TreeView / SelectMenu / DropdownMenu)→ **不傳**,吃 context\n * - Field / Tag(自有 size 系統,沒包 RowSizeProvider)→ **必傳** `size={hostSize}`\n */\n size?: RowSize\n /**\n * Hover / active bg className override(chromatic host 專用,如 Tag solid variant)。\n *\n * 預設 `group-hover/action:bg-neutral-hover group-active/action:bg-neutral-active`。\n * 提供後**完全取代** hover + active 雙態 className,但**不影響** rest 態(永遠 bg-transparent)\n * 與 overlay-trigger 態(`group-data-[state=open]/action:bg-neutral-hover` — 維持 host hover bg)。\n *\n * Consumer 須**同時**包含 hover + active 兩態 selector,例:\n * `'group-hover/action:bg-[var(--my-hover)] group-active/action:bg-[var(--my-active)]'`\n *\n * Use case:Tag solid(blue/green/red 等)需 hover bg 跟 host 色相一致(非 neutral)。\n */\n hoverBgClassName?: string\n /**\n * 標記本 button 是 **overlay trigger**(DropdownMenu / Popover / Tooltip 透過 `asChild` 包覆)。\n *\n * `true` 時:Radix overlay open 期間(`data-state=\"open\"`)button 維持 host hover 樣式\n * — 視覺鎖,讓 user 追溯 floating panel 來源(對齊 shadcn / Radix Themes / Material)。\n *\n * `false` 時(default):無 data-state=open 樣式 — 適用 in-place 互動如 `Collapsible.Trigger`\n * (展開內容接在 trigger 下方,不需追溯)、drag handle、dismiss X 等。\n *\n * Canonical 詳 `inline-action.spec.md`「Overlay trigger canonical」段。\n *\n * @default false\n */\n overlayTrigger?: boolean\n}\n\n/** @internal — L3 inline-action primitive;app code 禁直接 import,經 MenuItem/Row endActions 消費(inline-action.spec.md)。 */\nexport const ItemInlineActionButton = React.forwardRef<\n HTMLButtonElement,\n ItemInlineActionButtonProps\n>(({ icon: Icon, className, iconClassName, style, type = \"button\", size: sizeProp, hoverBgClassName, overlayTrigger = false, ...rest }, ref) => {\n const contextSize = useRowSize()\n const size = sizeProp ?? contextSize\n const iconPx = ICON_SIZE[size]\n const hoverBgPx = INLINE_ACTION_HOVER_BG_SIZE[size]\n return (\n <button\n ref={ref}\n type={type}\n className={cn(\n \"group/action relative grid place-content-center shrink-0 cursor-pointer\",\n \"text-fg-muted hover:text-foreground active:text-foreground transition-colors\",\n // Overlay trigger active state — 只在 consumer 顯式宣告 overlayTrigger=true 時生效\n // (canonical 2026-05-05:Collapsible / drag / dismiss 等 in-place 互動 ≠ overlay,\n // 不應 leak 視覺 lock。詳 inline-action.spec.md「Overlay trigger canonical」)\n overlayTrigger && \"data-[state=open]:text-foreground\",\n \"focus-visible:outline-2 focus-visible:outline-ring\",\n className\n )}\n style={{ width: iconPx, height: iconPx, ...style }}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(\n \"absolute pointer-events-none\",\n \"rounded-md\",\n \"bg-transparent\",\n hoverBgClassName ?? \"group-hover/action:bg-neutral-hover group-active/action:bg-neutral-active\",\n // Overlay 開啟 = 維持 host hover bg(只在 overlayTrigger=true 時生效)\n overlayTrigger && \"group-data-[state=open]/action:bg-neutral-hover\",\n \"transition-colors\"\n )}\n style={{\n width: hoverBgPx,\n height: hoverBgPx,\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n }}\n />\n <Icon size={iconPx} className={cn(\"relative\", iconClassName)} aria-hidden />\n </button>\n )\n})\nItemInlineActionButton.displayName = \"ItemInlineActionButton\"\n\n// ── High-level:ItemInlineAction ──\n//\n// 宣告式 API:consumer 給 `{ icon, label, onClick }`,元件自動處理 Tooltip +\n// aria-label。適用於 row primitive 的 suffix slot(SidebarMenuButton inlineActions 等)。\n//\n// 內部用 `ItemInlineActionButton`——兩層共用同一套視覺規格、同一個 RowSizeContext 查表。\n// 需要接 Radix asChild(例如 collapsible trigger)時改用 `ItemInlineActionButton`,\n// 那層沒有 Tooltip 包裝,Root 就是 button,可直接塞進 Radix Slot。\n\nexport interface ItemInlineActionProps {\n action: InlineActionConfig\n /** Size 覆寫(見 `ItemInlineActionButtonProps.size`)——Field Controls / Tag 必傳 */\n size?: RowSize\n}\n\n/** @internal — L3 inline-action primitive;app code 禁直接 import,經 row 元件消費(inline-action.spec.md)。 */\nexport const ItemInlineAction = React.forwardRef<\n HTMLButtonElement,\n ItemInlineActionProps\n>(({ action, size }, ref) => (\n <Tooltip>\n <TooltipTrigger asChild>\n <ItemInlineActionButton\n ref={ref}\n icon={action.icon}\n aria-label={action.label}\n onClick={action.onClick}\n size={size}\n />\n </TooltipTrigger>\n <TooltipContent>{action.label}</TooltipContent>\n </Tooltip>\n))\nItemInlineAction.displayName = \"ItemInlineAction\"\n\n// ── Item suffix slot ──────────────────────────────────────────────────────\n//\n// `<ItemSuffix>` — row primitive 的 suffix 容器。對齊 item-layout spec:\n// - `h-[1lh]`:suffix 永遠對齊第一行 label(跟 prefix 解耦)\n// - `ml-auto`:靠右\n// - `gap-2`:多個 inline action 之間的標準間距(inline-action.spec.md canonical SSOT)\n// - `hoverReveal` opt-in:opacity 0→1 on 父層 row hover/focus-visible\n//\n// `hoverGroup`(2026-05-05 v8 SSOT 升級):row primitive group selector 參數化。\n// 先前 `/menu-item` hardcode → TreeView(`/tree-item`)/ FileItem(`/row`)/\n// DataTable(`/row`)無法消費,被迫自刻 suffix slot(M1 違反)。\n// 參數化後 4 個 row primitive 都走同一條 SSOT。Tailwind JIT 需 static class,\n// 故用 lookup table 而非動態字串(避免 JIT scan 失敗)。\n\nconst SUFFIX_HOVER_REVEAL_BY_GROUP = {\n \"menu-item\":\n \"opacity-0 group-hover/menu-item:opacity-100 group-has-[:focus-visible]/menu-item:opacity-100 transition-opacity duration-150\",\n \"tree-item\":\n \"opacity-0 group-hover/tree-item:opacity-100 group-has-[:focus-visible]/tree-item:opacity-100 transition-opacity duration-150\",\n row: \"opacity-0 group-hover/row:opacity-100 group-has-[:focus-visible]/row:opacity-100 transition-opacity duration-150\",\n} as const\n\nexport type ItemSuffixHoverGroup = keyof typeof SUFFIX_HOVER_REVEAL_BY_GROUP\n\nexport interface ItemSuffixProps extends React.HTMLAttributes<HTMLSpanElement> {\n /**\n * Hover-reveal:預設隱藏,父層 row hover / keyboard focus-visible 時才淡入。\n * 對齊 TreeView / SidebarMenuButton inline action 行為。預設 false(永遠顯示,如 Badge)。\n *\n * 用 `group-has-[:focus-visible]` 而非 `group-focus-within`——後者會被 mouse click 觸發,\n * 導致 click 後 suffix 永久顯示直到焦點移走。focus-visible 只在鍵盤 tab 時啟動。\n */\n hoverReveal?: boolean\n /**\n * 父層 row 的 group selector(consumer 必加 `group/<name>` 到 row wrapper)。\n * 預設 `'menu-item'`(對齊 MenuItem / Sidebar)。其他 row primitive:\n * `'tree-item'`(TreeView)/ `'row'`(DataTable / FileItem)。\n *\n * 加新 row primitive(且需 hover-reveal)時:在 `SUFFIX_HOVER_REVEAL_BY_GROUP`\n * 加 entry,Tailwind JIT 才能 scan 到 static class。\n */\n hoverGroup?: ItemSuffixHoverGroup\n}\n\nexport const ItemSuffix = React.forwardRef<HTMLSpanElement, ItemSuffixProps>(\n ({ className, hoverReveal = false, hoverGroup = \"menu-item\", ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n \"h-[1lh] shrink-0 ml-auto flex items-center gap-2\",\n hoverReveal && SUFFIX_HOVER_REVEAL_BY_GROUP[hoverGroup],\n className\n )}\n {...props}\n />\n )\n)\nItemSuffix.displayName = \"ItemSuffix\"\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"item-anatomy.js","sources":["../../../src/patterns/element-anatomy/item-anatomy.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — foundational SSOT for Family 1+2 row primitives + all item-anatomy helpers(ItemContent / ItemIcon / ItemPrefix / ItemSuffix / ItemInlineAction 等),拆檔會讓 primitive 跨檔 import 滿天飛\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { cva } from \"class-variance-authority\"\nimport { cn, lineClampClass } from \"@/lib/utils\"\nimport { Avatar, type AvatarProps } from \"@/design-system/components/Avatar/avatar\"\nimport {\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n} from \"@/design-system/components/Tooltip/tooltip\"\n\n// ── InlineActionConfig ─────────────────────────────────────────────────────\n// 宣告式 API:consumer 只宣告 intent,host 根據 size tier 自動渲染。\n// 跨 Family 消費(Field family + Row family),SSOT 住 patterns/element-anatomy/\n// 對齊 inline-action.spec.md「API 設計」節。\n//\n// Canonical 實作:`ItemInlineAction`(本檔內)。\nexport interface InlineActionConfig {\n icon: LucideIcon\n /** aria-label,同時作為 tooltip 來源 */\n label: string\n /**\n * 點擊 handler。可選接收 React 事件物件——有需要時可呼叫 `stopPropagation()` 避免\n * 事件冒泡到父層(例如 Select 清除按鈕在 popover trigger 內,不想觸發 popover open)。\n */\n onClick: (e?: React.MouseEvent<HTMLButtonElement>) => void\n}\n\n/**\n * Item Layout — 共用工具與常數\n *\n * 這個檔案不是元件,是 `item-anatomy.spec.md` 的 code-level 實作。\n * 所有 row primitives(`MenuItem` / `TreeItem` / `SidebarMenuButton` /\n * `SelectionItem`)**必須 import 本 module 的 ICON_SIZE 和 helpers**,\n * 不要各自複製常數或 wrapper className——單一 source of truth,避免不一致。\n *\n * 詳細規則見 `item-anatomy.spec.md`。\n */\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\n/** Row primitive 的 size 變體,對齊 `--field-height-*` token family */\nexport type RowSize = \"sm\" | \"md\" | \"lg\"\n\n// ── Constants ──────────────────────────────────────────────────────────────\n\n/**\n * Icon 尺寸(px)per row size。\n *\n * - sm / md: 16px(標準 row icon)\n * - lg: 20px(對齊 text-body-lg 的視覺重量)\n *\n * **用法**:透過 Lucide icon 的 `size` prop 直接傳入,不要用 CSS `[&>svg]:size-*`\n * selector——當 icon 被包在 `<ItemPrefix>` 的 `h-[1lh]` wrapper 內時,\n * `>` 直接子選擇器會失效,Lucide icon 會 fallback 到預設 24px。\n *\n * ```tsx\n * import { ICON_SIZE } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n *\n * const iconPx = ICON_SIZE[size] // size: RowSize\n * <MyLucideIcon size={iconPx} />\n * ```\n */\nexport const ICON_SIZE: Record<RowSize, number> = {\n sm: 16,\n md: 16,\n lg: 20,\n}\n\n/**\n * Avatar 尺寸(px)per row size,分 inline / block 兩種模式。\n *\n * - **inline**(無 description,大宗情境)→ 對齊第一行 label,用 `h-[1lh]` 容器\n * sm: 20, md: 24, lg: 24\n * - **block**(有 description,avatar 跨越 label + desc 兩行)→ 對齊文字塊中心\n * sm: 32, md: 32, lg: 40\n *\n * **用法**:row context 的 consumer(包含 `asChild` pattern 的 Sidebar / Tree\n * consumer)**必須**用 `<ItemAvatar>` / `<ItemIcon>` helper 元件(自動查\n * RowSizeContext 對表),**禁止** `import { AVATAR_SIZE }` 手動查表、**不可硬寫\n * `size={24}`** — 硬寫會讓 sm 變體的 avatar 尺寸錯誤、跟 ICON_SIZE 對齊規則脫鉤。\n * 詳 item-anatomy.spec.md「Helper 程式化封裝 + review 防線」段。\n *\n * ```tsx\n * import { ItemAvatar } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n *\n * <ItemAvatar alt=\"Alan\" color=\"blue\" /> // 自動查 AVATAR_SIZE.inline[rowSize]\n * ```\n *\n * Canonical 消費者:MenuItem 直接 import 本檔 `AVATAR_SIZE`(menu-item.tsx,消費 .block/.inline;re-export 已完成,MenuItem 無內部查表)。\n */\nexport const AVATAR_SIZE = {\n inline: { sm: 20, md: 24, lg: 24 },\n block: { sm: 32, md: 32, lg: 40 },\n} as const satisfies Record<\"inline\" | \"block\", Record<RowSize, number>>\n\n/**\n * 回傳 uniform prefix slot 的 inline style(CSS variable form)。\n *\n * 用法:Row-primitive 頂層容器(例如 `<SidebarProvider uniformPrefix>` 的 wrapper)\n * 把這個 style 套在 `style` prop 上(預先放在 `--mixed-prefix-slot` 候選位置,\n * 由外層 CSS `:has()` 條件化套到 `--item-prefix-slot`),子樹內所有 `<ItemPrefix>` /\n * `<ItemIcon>` / `<ItemAvatar>` 會自動用固定寬槽對齊。\n *\n * 槽寬 = `AVATAR_SIZE.inline[size]`(20/24/24 @ sm/md/lg)——以最大的 inline\n * prefix(avatar)為基準,icon 在槽內 `justify-center`。\n *\n * ```tsx\n * <ul style={getUniformPrefixSlotStyle(size)}>\n * <ItemIcon icon={Folder} /> ← 16px icon 在 24px 槽內置中\n * <ItemAvatar alt=\"GitHub\" /> ← 24px logo 填滿槽\n * </ul>\n * ```\n */\nexport function getUniformPrefixSlotStyle(size: RowSize): React.CSSProperties {\n return { \"--item-prefix-slot\": `${AVATAR_SIZE.inline[size]}px` } as React.CSSProperties\n}\n\n/**\n * Inline action(suffix 裡的 hover-action icon button)hover 背景尺寸(px)。\n * 規則:icon size + 2px(每邊 +1px,用 absolute positioning 溢出不影響排版)。\n * 對齊 `inline-action.spec.md`(canonical SSOT,2026-04-24 自 item-anatomy.spec 抽出)。\n */\n// code-quality-allow: dead-export — public constant — DS API surface,consumer 可引(即使當前 internal-only)\nexport const INLINE_ACTION_HOVER_BG_SIZE: Record<RowSize, number> = {\n sm: 18,\n md: 18,\n lg: 22,\n}\n\n/**\n * `ROW_PADDING_BY_SIZE` — Family 1+2 row primitive 的 size → padding + typography 公式 SSOT\n *\n * Formula(canonical):`py = (field-height - 1lh) / 2`,對齊 1em text 到 field-height center。\n *\n * **消費者**:menuItemVariants / sidebarMenuButtonVariants / treeItemVariants / 未來 row primitive\n * **Rationale**:前先 3 cva 各自寫同一公式 → drift risk(item-anatomy.spec 有明文「SidebarMenuButton\n * 獨立實作風險」)。抽本 export 後改**一處全同步**,risk 消失(對齊 Meta-Pattern M17「SSOT 必可傳播」)。\n *\n * 2026-04-24 D Phase 1+2 consolidation 建立。\n */\nexport const ROW_PADDING_BY_SIZE: Record<RowSize, string> = {\n sm: 'text-body leading-compact py-[calc((var(--field-height-sm)-1lh)/2)]',\n md: 'text-body leading-compact py-[calc((var(--field-height-md)-1lh)/2)]',\n lg: 'text-body-lg leading-compact py-[calc((var(--field-height-lg)-1lh)/2)]',\n}\n\n// ── Helper components ──────────────────────────────────────────────────────\n\n/**\n * `<ItemPrefix>` — Row primitive 的 prefix slot wrapper。\n *\n * 把 prefix(icon / avatar / checkbox / indicator)包進 `h-[1lh]` 容器,\n * 強制對齊第一行文字中線。這是 item-layout 的底層規則(詳見 spec「Prefix 垂直對齊」)。\n *\n * **為什麼**:\n * - Row primitive 的 outer flex 是 `items-start`(多行 label 時 prefix 不飄移)\n * - 單行時 prefix 透過這個 wrapper 強制對齊第一行中線,視覺效果 = `items-center`\n * - 不同尺寸的 prefix(icon 16、Avatar 24、Checkbox 20)都能穩定置中\n *\n * 用法:\n * ```tsx\n * <div className=\"flex items-start gap-2\">\n * <ItemPrefix>\n * <Avatar size={24} />\n * </ItemPrefix>\n * <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n * </div>\n * ```\n *\n * asChild 的 consumer(例如 `<SidebarMenuButton asChild>`)**必須自己**用這個 wrapper\n * 包 prefix,不能省略。\n */\nexport const ItemPrefix = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n \"h-[1lh] shrink-0 flex items-center\",\n // Uniform prefix slot:讀取 `--item-prefix-slot` CSS variable,\n // fallback `auto`(= 預設行為,不佔額外寬度、不 center)。\n // Row-primitive 頂層容器(例如 `<SidebarProvider uniformPrefix>` 的 wrapper)\n // 若設定這個 var,小尺寸 prefix(icon 16px)會在固定寬槽(24px @ md)內置中,\n // 與大尺寸 prefix(app logo 24px)對齊,達成 label 齊左。\n // 詳見 item-anatomy.spec.md「Uniform prefix slot」節。\n \"min-w-[var(--item-prefix-slot,auto)] justify-center\",\n className\n )}\n {...props}\n />\n))\nItemPrefix.displayName = \"ItemPrefix\"\n\n/**\n * `itemPrefixAlignVariants` — Prefix 對齊高度的 cva SSOT(for 複雜 prefix 結構)。\n *\n * ── 存在的理由 ──\n * `<ItemPrefix>` primitive 預設單一 child(icon / avatar 任一)+ inline 對齊。\n * 但**複雜 prefix 結構**(e.g. MenuItem 可同時含 checkbox + icon/avatar 多 child)\n * 或**block mode**(大 prefix + description 對齊塊中心)需要自建 wrapper cva。\n * 本 cva 把 block formula SSOT 化,讓 MenuItem 等 consumer 共用,\n * 改值一處(token or cva)→ 所有 block-prefix consumer 同步。\n *\n * ── Align 變體(對齊 item-anatomy.spec.md「24px 閾值對齊規則」) ──\n * - `\"inline\"`:`h-[1lh]`,小 prefix(≤ 24px)對齊 label 第一行\n * - `\"block-sm\"` / `\"block-md\"`:大 prefix + scanning desc(sm/md menu),\n * 高度 = label(1lh) + gap(token) + desc(caption × 1.3)\n * - `\"block-lg\"`:大 prefix + scanning desc(lg menu),desc 改為 body × 1.3\n *\n * ── Consumer ──\n * MenuItem(inline / block-sm / block-md / block-lg)。其他 row primitive 簡單情境\n * 直接用 `<ItemPrefix>` 就好,不需此 cva。\n *\n * ── SSOT 傳播 ──\n * gap 用 `var(--item-gap-label-desc-scanning[-lg])` token(bare 變體已不存在,4 個 mode×size 變體);font-size 用 token-awareness\n * (`var(--font-caption-size)` / `var(--font-body-size)`)。改 token → 公式同步。\n */\n// code-quality-allow: long-function — cva variant/styles table — 拆 fn 會失去 type inference + 跨 fn 傳 config 反而難讀\nexport const itemPrefixAlignVariants = cva(\n \"flex items-center gap-2 shrink-0\",\n {\n variants: {\n align: {\n inline: \"h-[1lh]\",\n // sm/md desc = text-caption (12px × 1.3 = 15.6px)\n // block-sm / block-md:scanning mode(body + caption 組合)→ 用 scanning mode 專屬 gap token\n \"block-sm\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning)+var(--font-caption-size)*1.3)]\",\n \"block-md\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning)+var(--font-caption-size)*1.3)]\",\n // block-lg:scanning-lg mode(body-lg + body compact 組合)→ 用 scanning-lg mode 專屬 gap token\n \"block-lg\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning-lg)+var(--font-body-size)*1.3)]\",\n },\n },\n defaultVariants: {\n align: \"inline\",\n },\n }\n)\n\n/**\n * `<ItemLabel>` — Row primitive 的 label span。\n *\n * 預設 `min-w-0 flex-1 truncate`——單行截斷,佔滿剩餘 flex 空間。\n * 加 `data-sidebar=\"menu-label\"` attribute 讓 sidebar icon 模式的 CSS selector 能命中。\n *\n * 用法:\n * ```tsx\n * <ItemLabel>{label}</ItemLabel>\n * ```\n */\nexport const ItemLabel = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n data-sidebar=\"menu-label\"\n className={cn(\"min-w-0 flex-1 truncate\", className)}\n {...props}\n />\n))\nItemLabel.displayName = \"ItemLabel\"\n\n/**\n * `<ItemContent>` — Row primitive 的 label + optional description 內容區(SSOT)。\n *\n * ── 存在的唯一理由 ──\n * 封裝「flex-col + label + description + `mt-[var(--item-gap-label-desc-<mode>[-lg])]` gap」結構,\n * 避免 13+ 消費者各自 hard-code `mt-0.5`。改 token 一處,全 DS 同步。\n *\n * ── Consumer 偏離 canonical ──\n * 消費端若有**確切合理理由**不用 `<ItemContent>` / 需自訂 label/desc 行為,必在\n * 該元件 `spec.md` 明文寫下 rationale(對齊 item-anatomy canonical「偏離必明文」)。\n * 合法偏離範例:\n * - MenuItem 的 `leading-compact + text-caption` scanning-mode typography\n * → MenuItem spec 明文 rationale + 消費 `--item-gap-label-desc-scanning[-lg]` token 直接用\n * - SelectionItem 的 control slot 跟 block formula 綁定\n * → SelectionItem spec 明文 rationale\n *\n * ── Props ──\n * - `label`:label 內容(ReactNode,必填)\n * - `description`:description 內容(ReactNode,optional)\n * - `mode`:typography tier(scanning vs reading,世界級對齊 Material `dense` / Carbon `size` / Ant `size`)\n * - `\"reading\"`(預設):繼承 parent `text-body`(14px / 1.5 leading),舒適閱讀(Family 2 / Material body-large 派)\n * - `\"scanning\"`:desc 縮為 `text-caption`(12px)+ `leading-compact`(1.3),緊湊掃視\n * (Family 1 Menu item / Material body-medium dense 派)\n * - `descriptionTone`:desc 顏色語意\n * - `\"secondary\"`(預設):`text-fg-secondary`\n * - `\"error\"`:`text-error-text`\n * - `\"muted\"`:`text-fg-muted`\n * - `\"disabled\"`:`text-fg-disabled`(對齊 M24 state precedence:disabled > muted > emphasis)\n * - `descriptionWrap`:desc 多行 wrap(預設 true)/ false = truncate\n * - `labelClassName` / `descriptionClassName`:escape hatches(明文 rationale 才用)\n *\n * ── World-class benchmark ──\n * 6 家 DS(Material / Polaris / Atlassian / Apple HIG / Carbon / Ant)一致用\n * 14/20 vs 16/24 兩擋 body 表達 scanning vs reading。API 兩派:(A)密度 prop\n * Material `dense` / Carbon `size` / Ant `size`(本 DS 採 A 派);(B)typography\n * token 手選 Polaris / Atlassian / Apple。\n */\n\nexport interface ItemContentProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n label: React.ReactNode\n description?: React.ReactNode\n /**\n * Typography mode — **leading 行為**(跟 size 正交的另一維):\n * - `\"reading\"`(預設):default leading(1.5)— Family 2 List item 舒適閱讀\n * - `\"scanning\"`:`leading-compact`(1.3)+ desc 降一 tier —— Menu item / Step 掃視\n */\n mode?: \"reading\" | \"scanning\"\n /**\n * Size tier — **label 字級**(跟 mode 正交的另一維):\n * - `\"md\"`(預設):label `text-body`(14)\n * - `\"lg\"`:label `text-body-lg`(16)— desc 永遠比 label 小一 tier(body)\n *\n * 2 × 2 = 4 typography 組合,對應 4 tokens:\n * - md + reading → body(14/1.5) + body(14/1.5)\n * - md + scanning → body(14/1.3) + caption(12/1.3)\n * - lg + reading → body-lg(16/1.5) + body(14/1.5)\n * - lg + scanning → body-lg(16/1.3) + body(14/1.3)\n */\n size?: \"md\" | \"lg\"\n descriptionTone?: \"secondary\" | \"error\" | \"muted\" | \"disabled\"\n descriptionWrap?: boolean\n /**\n * Description 多行截斷(line-clamp-N)。undefined = 不 clamp(自由 wrap)。\n * Tailwind line-clamp utilities 支援 1-6。\n */\n descriptionClamp?: number\n /**\n * Description `break-words`(default `false`,僅在 consumer 需要時 opt-in)。\n * 使用情境:SelectionItem / Steps 這類 description 可能塞長英文 token 需 break。\n */\n descriptionBreakWords?: boolean\n /**\n * Label truncate (default `true`, 對齊 row-item 大宗 idiom)。\n * Opt-out(`false`)for card-like consumers(e.g. ProfileCard label 允許 wrap)。\n * World-class 對照:Material `ListItemText primaryTypographyProps.noWrap` default true。\n */\n labelTruncate?: boolean\n labelClassName?: string\n descriptionClassName?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nexport const ItemContent = React.forwardRef<HTMLDivElement, ItemContentProps>(\n (\n {\n label,\n description,\n mode = \"reading\",\n size = \"md\",\n descriptionTone = \"secondary\",\n descriptionWrap = true,\n descriptionClamp,\n descriptionBreakWords = false,\n labelTruncate = true,\n labelClassName,\n descriptionClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const toneClass = {\n secondary: \"text-fg-secondary\",\n error: \"text-error-text\",\n muted: \"text-fg-muted\",\n disabled: \"text-fg-disabled\",\n }[descriptionTone]\n\n // Typography desc class — 2 維 cross-product (mode × size):\n // ┌───────────┬────────────────────────────┬────────────────────────────┐\n // │ │ size=\"md\"(body label 14) │ size=\"lg\"(body-lg label 16)│\n // ├───────────┼────────────────────────────┼────────────────────────────┤\n // │ reading │ 繼承 body(14/1.5) │ body(14)default leading │\n // │ scanning │ caption(12)+ compact │ body(14)+ compact │\n // └───────────┴────────────────────────────┴────────────────────────────┘\n const isLg = size === \"lg\"\n const modeClass =\n mode === \"scanning\"\n ? isLg\n ? \"text-[length:var(--font-body-size)] leading-compact\"\n : \"text-[length:var(--font-caption-size)] leading-compact\"\n : isLg\n ? \"text-[length:var(--font-body-size)]\"\n : \"\"\n\n const clampClass = lineClampClass(descriptionClamp) ?? \"\"\n\n return (\n <div\n ref={ref}\n className={cn(\"flex flex-col min-w-0 flex-1\", className)}\n {...props}\n >\n <span className={cn(labelTruncate && \"truncate\", labelClassName)}>{label}</span>\n {description && (\n <span\n className={cn(\n // Typography-mode-aware gap token(2026-04-23):\n // (mode, size)2 維正交 → 4 token 對應 4 typography 組合\n mode === \"scanning\"\n ? isLg\n ? \"mt-[var(--item-gap-label-desc-scanning-lg)]\"\n : \"mt-[var(--item-gap-label-desc-scanning)]\"\n : isLg\n ? \"mt-[var(--item-gap-label-desc-reading-lg)]\"\n : \"mt-[var(--item-gap-label-desc-reading)]\",\n modeClass,\n toneClass,\n clampClass,\n descriptionBreakWords && \"break-words\",\n !descriptionWrap && \"truncate\",\n descriptionClassName,\n )}\n >\n {description}\n </span>\n )}\n </div>\n )\n },\n)\nItemContent.displayName = \"ItemContent\"\n\n// ── Row size context ──────────────────────────────────────────────────────\n//\n// 作用:讓 row primitive(Sidebar / SelectMenu / Tree / DropdownMenu)只 propagate\n// 一次 size,整個子樹內的 <ItemIcon> / <ItemAvatar> 都自動查到正確尺寸。\n//\n// **存在的唯一理由**:消除 asChild pattern 下 consumer 硬寫 `size={24}` 的可能性。\n// 過去 consumer 必須 `useSidebar().size` 再手動傳 `AVATAR_SIZE.inline[size]`,\n// 每個 consumer 都是一次漂移機會(曾發生:sm 欄 avatar 顯示 24 而非 20,破壞規格)。\n// 現在 consumer 只寫 `<ItemAvatar alt=\"...\" />`,尺寸由 context 自動決定。\n//\n// Row primitive 實作者的責任:在元件內部用 `<RowSizeProvider value={size}>` 包裹\n// children(不論 asChild 或非 asChild 路徑),這樣任何 descendant 的 ItemIcon /\n// ItemAvatar 都會拿到正確尺寸。\n\nconst RowSizeContext = React.createContext<RowSize | null>(null)\n\n/** @internal — L3 row-size primitive;app code 不直接 import,由 MenuItem/Row/Field 等內部消費(inline-action.spec.md)。 */\nexport const RowSizeProvider = RowSizeContext.Provider\n\n/**\n * 讀取當前 row size。在 row primitive 子樹外呼叫會回 fallback(預設 \"md\")。\n * Row primitive 實作者不該呼叫這個——你們已經知道自己的 size。\n * 這個 hook 是給 `<ItemIcon>` / `<ItemAvatar>` 和 asChild consumer 的逃生艙用。\n */\nexport function useRowSize(fallback: RowSize = \"md\"): RowSize {\n return React.useContext(RowSizeContext) ?? fallback\n}\n\n// ── Prefab prefix elements ────────────────────────────────────────────────\n//\n// `<ItemIcon>` 和 `<ItemAvatar>` 是「prefix slot 的完成品元件」——自帶 ItemPrefix\n// wrapper + 自動查 ICON_SIZE / AVATAR_SIZE。\n//\n// **asChild consumer 必須用這兩個元件,禁止手動寫 `<Avatar size={N} />`**。\n// 禁令理由見 item-anatomy.spec.md「Avatar 尺寸選擇」節。\n\nexport interface ItemIconProps\n extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n /** Lucide icon 元件(不是 JSX element) */\n icon: LucideIcon\n}\n\n/**\n * `<ItemIcon icon={Folder} />` — 自動查 `ICON_SIZE[rowSize]`,包在 `ItemPrefix` 內。\n * 永遠對齊第一行 label(item-layout 的 icon 永遠 inline)。\n */\nexport const ItemIcon = React.forwardRef<SVGSVGElement, ItemIconProps>(\n ({ icon: Icon, className, ...props }, ref) => {\n const size = useRowSize()\n return (\n // data-prefix-type=\"icon\" 讓 row-primitive 容器的 `:has()` selector 能偵測類型,\n // 用於 `<SidebarProvider uniformPrefix>` opt-in 後的混用 auto-detection\n <ItemPrefix data-prefix-type=\"icon\">\n <Icon\n ref={ref}\n size={ICON_SIZE[size]}\n className={cn(\"shrink-0\", className)}\n aria-hidden\n {...props}\n />\n </ItemPrefix>\n )\n }\n)\nItemIcon.displayName = \"ItemIcon\"\n\nexport interface ItemAvatarProps extends Omit<AvatarProps, \"size\"> {\n /**\n * 對齊模式(選填,預設 `\"inline\"`):\n * - `\"inline\"`:視覺重量輕的 avatar,對齊第一行 label(20/24/24 @ sm/md/lg)\n * - `\"block\"`:視覺重量重的 avatar,對齊 label + description 文字塊(32/32/40 @ sm/md/lg)\n *\n * 預設 inline——大宗情境(扁平 row、footer user、主清單)都用這個。\n * 只有 avatar 是 item 主體(人物卡、顯著身份辨識)才用 block。\n *\n * 詳見 `item-anatomy.spec.md`「Avatar 尺寸選擇」節,含 24px 對齊模式閾值規則。\n */\n mode?: \"inline\" | \"block\"\n}\n\n/**\n * `<ItemAvatar alt=\"Alan\" color=\"blue\" />` — 自動查 `AVATAR_SIZE[mode][rowSize]`,\n * 包在 `ItemPrefix` 內。\n *\n * asChild consumer 的標準用法——不需知道 size,不需 import AVATAR_SIZE,\n * 不可能硬寫錯誤尺寸。\n */\n// code-quality-allow: long-function — forwardRef body 含 size×color×hoverCard 三正交 prop 渲染分流,拆出 sub-fn 反增 prop drilling\nexport const ItemAvatar = React.forwardRef<HTMLDivElement, ItemAvatarProps>(\n ({ mode = \"inline\", ...props }, ref) => {\n const size = useRowSize()\n return (\n // data-prefix-type=\"avatar\" 讓 row-primitive 容器的 `:has()` selector 能偵測類型,\n // 用於 `<SidebarProvider uniformPrefix>` opt-in 後的混用 auto-detection\n <ItemPrefix data-prefix-type=\"avatar\">\n <Avatar ref={ref} size={AVATAR_SIZE[mode][size]} {...props} />\n </ItemPrefix>\n )\n }\n)\nItemAvatar.displayName = \"ItemAvatar\"\n\n// ── Inline action (suffix slot) ───────────────────────────────────────────\n//\n// 共用 row-primitive 的 suffix inline action 渲染——消除過去 Input / NumberInput /\n// Tag / LinkInput / Combobox 各自複製 18 行 JSX 的狀況(見 `inline-action.spec.md`)。\n//\n// 規格(完全對齊 inline-action.spec.md — canonical SSOT,2026-04-24 自 item-anatomy.spec 抽出):\n// - Icon 尺寸 = ICON_SIZE[rowSize] (16/16/20)\n// - Hover bg 尺寸 = icon + 2 (18/18/22,INLINE_ACTION_HOVER_BG_SIZE)\n// - Hover bg 圓角 = rounded-md (sm/md)/ rounded-md (lg)\n// - Icon 顏色:fg-muted → foreground on hover/active\n// - 必須 <button type=\"button\">,aria-label = label,Tooltip(同 label)\n// - cursor-pointer\n\n// ── Low-level:ItemInlineActionButton ──\n//\n// Root 是 `<button>`(不是 Tooltip wrapper),可直接塞進 Radix `asChild` pattern\n// 例如 `<Collapsible.Trigger asChild>`, `<Popover.Trigger asChild>` 等。\n//\n// 自動從 `RowSizeContext` 查:\n// - 寬高 = ICON_SIZE[size]\n// - Hover bg 尺寸 = INLINE_ACTION_HOVER_BG_SIZE[size]\n// - Hover bg 圓角 = rounded-md (sm/md) / rounded-md (lg)\n// - Icon color = fg-muted → foreground on hover/active\n//\n// 接受任何 button props 並 spread(讓 Radix Slot 可以 merge onClick / data-state 等)\n\n/** @internal — internal ItemInlineActionButton 的 props;隨其元件同走 subpath,不進 root barrel front-door。 */\nexport interface ItemInlineActionButtonProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n /** Lucide icon 元件 */\n icon: LucideIcon\n /** 可選:額外加在 icon 上的 className(例如 rotate transition) */\n iconClassName?: string\n /**\n * Size 明確覆寫(用於 fields / Tag 等**不在 `RowSizeProvider` 樹內**的 host):\n * 傳了就以此為準,否則從 `RowSizeContext` 讀(fallback \"md\")。\n *\n * 使用時機:\n * - Row primitive 子樹(Sidebar / TreeView / SelectMenu / DropdownMenu)→ **不傳**,吃 context\n * - Field / Tag(自有 size 系統,沒包 RowSizeProvider)→ **必傳** `size={hostSize}`\n */\n size?: RowSize\n /**\n * Hover / active bg className override(chromatic host 專用,如 Tag solid variant)。\n *\n * 預設 `group-hover/action:bg-neutral-hover group-active/action:bg-neutral-active`。\n * 提供後**完全取代** hover + active 雙態 className,但**不影響** rest 態(永遠 bg-transparent)\n * 與 overlay-trigger 態(`group-data-[state=open]/action:bg-neutral-hover` — 維持 host hover bg)。\n *\n * Consumer 須**同時**包含 hover + active 兩態 selector,例:\n * `'group-hover/action:bg-[var(--my-hover)] group-active/action:bg-[var(--my-active)]'`\n *\n * Use case:Tag solid(blue/green/red 等)需 hover bg 跟 host 色相一致(非 neutral)。\n */\n hoverBgClassName?: string\n /**\n * 標記本 button 是 **overlay trigger**(DropdownMenu / Popover / Tooltip 透過 `asChild` 包覆)。\n *\n * `true` 時:Radix overlay open 期間(`data-state=\"open\"`)button 維持 host hover 樣式\n * — 視覺鎖,讓 user 追溯 floating panel 來源(對齊 shadcn / Radix Themes / Material)。\n *\n * `false` 時(default):無 data-state=open 樣式 — 適用 in-place 互動如 `Collapsible.Trigger`\n * (展開內容接在 trigger 下方,不需追溯)、drag handle、dismiss X 等。\n *\n * Canonical 詳 `inline-action.spec.md`「Overlay trigger canonical」段。\n *\n * @default false\n */\n overlayTrigger?: boolean\n}\n\n/** @internal — L3 inline-action primitive;app code 禁直接 import,經 MenuItem/Row endActions 消費(inline-action.spec.md)。 */\nexport const ItemInlineActionButton = React.forwardRef<\n HTMLButtonElement,\n ItemInlineActionButtonProps\n>(({ icon: Icon, className, iconClassName, style, type = \"button\", size: sizeProp, hoverBgClassName, overlayTrigger = false, ...rest }, ref) => {\n const contextSize = useRowSize()\n const size = sizeProp ?? contextSize\n const iconPx = ICON_SIZE[size]\n const hoverBgPx = INLINE_ACTION_HOVER_BG_SIZE[size]\n return (\n <button\n ref={ref}\n type={type}\n className={cn(\n \"group/action relative grid place-content-center shrink-0 cursor-pointer\",\n \"text-fg-muted hover:text-foreground active:text-foreground transition-colors\",\n // Overlay trigger active state — 只在 consumer 顯式宣告 overlayTrigger=true 時生效\n // (canonical 2026-05-05:Collapsible / drag / dismiss 等 in-place 互動 ≠ overlay,\n // 不應 leak 視覺 lock。詳 inline-action.spec.md「Overlay trigger canonical」)\n overlayTrigger && \"data-[state=open]:text-foreground\",\n \"focus-visible:outline-2 focus-visible:outline-ring\",\n className\n )}\n style={{ width: iconPx, height: iconPx, ...style }}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(\n \"absolute pointer-events-none\",\n \"rounded-md\",\n \"bg-transparent\",\n hoverBgClassName ?? \"group-hover/action:bg-neutral-hover group-active/action:bg-neutral-active\",\n // Overlay 開啟 = 維持 host hover bg(只在 overlayTrigger=true 時生效)\n overlayTrigger && \"group-data-[state=open]/action:bg-neutral-hover\",\n \"transition-colors\"\n )}\n style={{\n width: hoverBgPx,\n height: hoverBgPx,\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n }}\n />\n <Icon size={iconPx} className={cn(\"relative\", iconClassName)} aria-hidden />\n </button>\n )\n})\nItemInlineActionButton.displayName = \"ItemInlineActionButton\"\n\n// ── High-level:ItemInlineAction ──\n//\n// 宣告式 API:consumer 給 `{ icon, label, onClick }`,元件自動處理 Tooltip +\n// aria-label。適用於 row primitive 的 suffix slot(SidebarMenuButton inlineActions 等)。\n//\n// 內部用 `ItemInlineActionButton`——兩層共用同一套視覺規格、同一個 RowSizeContext 查表。\n// 需要接 Radix asChild(例如 collapsible trigger)時改用 `ItemInlineActionButton`,\n// 那層沒有 Tooltip 包裝,Root 就是 button,可直接塞進 Radix Slot。\n\n/** @internal — internal ItemInlineAction 的 props;隨其元件同走 subpath,不進 root barrel front-door。 */\nexport interface ItemInlineActionProps {\n action: InlineActionConfig\n /** Size 覆寫(見 `ItemInlineActionButtonProps.size`)——Field Controls / Tag 必傳 */\n size?: RowSize\n}\n\n/** @internal — L3 inline-action primitive;app code 禁直接 import,經 row 元件消費(inline-action.spec.md)。 */\nexport const ItemInlineAction = React.forwardRef<\n HTMLButtonElement,\n ItemInlineActionProps\n>(({ action, size }, ref) => (\n <Tooltip>\n <TooltipTrigger asChild>\n <ItemInlineActionButton\n ref={ref}\n icon={action.icon}\n aria-label={action.label}\n onClick={action.onClick}\n size={size}\n />\n </TooltipTrigger>\n <TooltipContent>{action.label}</TooltipContent>\n </Tooltip>\n))\nItemInlineAction.displayName = \"ItemInlineAction\"\n\n// ── Item suffix slot ──────────────────────────────────────────────────────\n//\n// `<ItemSuffix>` — row primitive 的 suffix 容器。對齊 item-layout spec:\n// - `h-[1lh]`:suffix 永遠對齊第一行 label(跟 prefix 解耦)\n// - `ml-auto`:靠右\n// - `gap-2`:多個 inline action 之間的標準間距(inline-action.spec.md canonical SSOT)\n// - `hoverReveal` opt-in:opacity 0→1 on 父層 row hover/focus-visible\n//\n// `hoverGroup`(2026-05-05 v8 SSOT 升級):row primitive group selector 參數化。\n// 先前 `/menu-item` hardcode → TreeView(`/tree-item`)/ FileItem(`/row`)/\n// DataTable(`/row`)無法消費,被迫自刻 suffix slot(M1 違反)。\n// 參數化後 4 個 row primitive 都走同一條 SSOT。Tailwind JIT 需 static class,\n// 故用 lookup table 而非動態字串(避免 JIT scan 失敗)。\n\nconst SUFFIX_HOVER_REVEAL_BY_GROUP = {\n \"menu-item\":\n \"opacity-0 group-hover/menu-item:opacity-100 group-has-[:focus-visible]/menu-item:opacity-100 transition-opacity duration-150\",\n \"tree-item\":\n \"opacity-0 group-hover/tree-item:opacity-100 group-has-[:focus-visible]/tree-item:opacity-100 transition-opacity duration-150\",\n row: \"opacity-0 group-hover/row:opacity-100 group-has-[:focus-visible]/row:opacity-100 transition-opacity duration-150\",\n} as const\n\nexport type ItemSuffixHoverGroup = keyof typeof SUFFIX_HOVER_REVEAL_BY_GROUP\n\nexport interface ItemSuffixProps extends React.HTMLAttributes<HTMLSpanElement> {\n /**\n * Hover-reveal:預設隱藏,父層 row hover / keyboard focus-visible 時才淡入。\n * 對齊 TreeView / SidebarMenuButton inline action 行為。預設 false(永遠顯示,如 Badge)。\n *\n * 用 `group-has-[:focus-visible]` 而非 `group-focus-within`——後者會被 mouse click 觸發,\n * 導致 click 後 suffix 永久顯示直到焦點移走。focus-visible 只在鍵盤 tab 時啟動。\n */\n hoverReveal?: boolean\n /**\n * 父層 row 的 group selector(consumer 必加 `group/<name>` 到 row wrapper)。\n * 預設 `'menu-item'`(對齊 MenuItem / Sidebar)。其他 row primitive:\n * `'tree-item'`(TreeView)/ `'row'`(DataTable / FileItem)。\n *\n * 加新 row primitive(且需 hover-reveal)時:在 `SUFFIX_HOVER_REVEAL_BY_GROUP`\n * 加 entry,Tailwind JIT 才能 scan 到 static class。\n */\n hoverGroup?: ItemSuffixHoverGroup\n}\n\nexport const ItemSuffix = React.forwardRef<HTMLSpanElement, ItemSuffixProps>(\n ({ className, hoverReveal = false, hoverGroup = \"menu-item\", ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n \"h-[1lh] shrink-0 ml-auto flex items-center gap-2\",\n hoverReveal && SUFFIX_HOVER_REVEAL_BY_GROUP[hoverGroup],\n className\n )}\n {...props}\n />\n )\n)\nItemSuffix.displayName = \"ItemSuffix\"\n"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* **加色相**:加進 `CATEGORICAL_HUES` + 下列 5 個 map 各補一行字面值(key X 用 `--color-X-*`,1:1)。
|
|
24
24
|
* **機械防線**:`scripts/categorical-color-invariants.mjs` I1(release:preflight 已 chain)強制 map 的 key X 的值含 `--color-X-`(名實一致,禁 offset;原引 hook 名不存在,2026-07-04 修)。
|
|
25
25
|
*/
|
|
26
|
-
/** 12 個 categorical
|
|
26
|
+
/** 12 個 categorical 色相。⚠️ 順序 = **categorical 指派序**(圖表系列鄰色最大區辨:blue→green→deep-orange→yellow→red…),**非** primitives.css 檔內宣告序 — 兩者本就不同,map 以名為 key 故功能無涉;**禁**為「對齊」重排(重排 = 圖表系列換色 = 視覺 breaking)。 */
|
|
27
27
|
export declare const CATEGORICAL_HUES: readonly ["blue", "green", "deep-orange", "yellow", "red", "orange", "amber", "lime", "turquoise", "indigo", "purple", "magenta"];
|
|
28
28
|
export type CategoricalHue = (typeof CATEGORICAL_HUES)[number];
|
|
29
29
|
/** neutral 不是色相(無 hue),用 secondary/foreground + neutral-9 solid,各 consumer 自處理(不在下列 map)。 */
|
|
@@ -33,8 +33,8 @@ export declare const CAT_SUBTLE: Record<CategoricalHue, string>;
|
|
|
33
33
|
/**
|
|
34
34
|
* solid 模式 class:step-6 底 + on-emphasis 配對文字。
|
|
35
35
|
* 文字色依「step-6 底亮度」分桶(WCAG ≥3:1 大粗字門檻,2026-06-04 user「以最低為原則」):
|
|
36
|
-
* - 夠深的色底 → `text-on-emphasis`(白):blue / deep-orange / red /
|
|
37
|
-
* - 太亮的色底 → `text-on-emphasis-dark`(深):yellow / amber / orange / lime(白字連 3:1
|
|
36
|
+
* - 夠深的色底 → `text-on-emphasis`(白):blue / deep-orange / red / indigo / purple / magenta
|
|
37
|
+
* - 太亮的色底 → `text-on-emphasis-dark`(深):yellow / amber / orange / lime / turquoise(白字連 3:1 都不過;turquoise 2026-07-16 dark 色相修正後 L=0.68 白字 2.77 → 切深字,兩 mode 5.65/6.47 全過)
|
|
38
38
|
* - ★ green 例外:白字 2.47 連 3:1 都不過,但 user 拍板維持白字(綠底白字慣見觀感)= documented exception。
|
|
39
39
|
* 對比為 oklch→相對亮度實測(scripts/categorical-color-invariants.mjs 機械驗,green 列 exempt)。
|
|
40
40
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"categorical-color.d.ts","sourceRoot":"","sources":["../../src/tokens/categorical-color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,
|
|
1
|
+
{"version":3,"file":"categorical-color.d.ts","sourceRoot":"","sources":["../../src/tokens/categorical-color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,oMAAoM;AACpM,eAAO,MAAM,gBAAgB,mIAanB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9D,6FAA6F;AAC7F,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAA;AAEzD,wEAAwE;AACxE,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAarD,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAapD,CAAA;AAED,sEAAsE;AACtE,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,cAAc,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAalF,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,cAAc,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAalF,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,cAAc,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAajF,CAAA;AAED,wDAAwD;AACxD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAapD,CAAA;AAED,6CAA6C;AAC7C,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAarD,CAAA"}
|
|
@@ -36,7 +36,7 @@ const CAT_SOLID = {
|
|
|
36
36
|
orange: "bg-[var(--color-orange-6)] text-on-emphasis-dark",
|
|
37
37
|
amber: "bg-[var(--color-amber-6)] text-on-emphasis-dark",
|
|
38
38
|
lime: "bg-[var(--color-lime-6)] text-on-emphasis-dark",
|
|
39
|
-
turquoise: "bg-[var(--color-turquoise-6)] text-on-emphasis",
|
|
39
|
+
turquoise: "bg-[var(--color-turquoise-6)] text-on-emphasis-dark",
|
|
40
40
|
indigo: "bg-[var(--color-indigo-6)] text-on-emphasis",
|
|
41
41
|
purple: "bg-[var(--color-purple-6)] text-on-emphasis",
|
|
42
42
|
magenta: "bg-[var(--color-magenta-6)] text-on-emphasis"
|
|
@@ -79,7 +79,7 @@ const CAT_SOLID_TOKENS = {
|
|
|
79
79
|
orange: { bg: "var(--color-orange-6)", text: "var(--on-emphasis-dark)" },
|
|
80
80
|
amber: { bg: "var(--color-amber-6)", text: "var(--on-emphasis-dark)" },
|
|
81
81
|
lime: { bg: "var(--color-lime-6)", text: "var(--on-emphasis-dark)" },
|
|
82
|
-
turquoise: { bg: "var(--color-turquoise-6)", text: "var(--on-emphasis)" },
|
|
82
|
+
turquoise: { bg: "var(--color-turquoise-6)", text: "var(--on-emphasis-dark)" },
|
|
83
83
|
indigo: { bg: "var(--color-indigo-6)", text: "var(--on-emphasis)" },
|
|
84
84
|
purple: { bg: "var(--color-purple-6)", text: "var(--on-emphasis)" },
|
|
85
85
|
magenta: { bg: "var(--color-magenta-6)", text: "var(--on-emphasis)" }
|