@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
|
@@ -40,6 +40,7 @@ import { overlayMotion } from "@/design-system/tokens/motion/overlay-motion"
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
// ── Floating layer 共用樣式 ──
|
|
43
|
+
/** @internal — menu 形浮層共用 class(DropdownMenu/SelectMenu/Command 內部);consumer 不 hand-craft menu surface,用元件本身。root barrel 排除(subpath 仍可 wrap 後用)。 */
|
|
43
44
|
const floatingLayerClass = [
|
|
44
45
|
'z-50 overflow-hidden rounded-lg border border-border bg-surface-raised',
|
|
45
46
|
overlayMotion,
|
|
@@ -121,11 +122,17 @@ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
|
121
122
|
|
|
122
123
|
// ── Content ──
|
|
123
124
|
interface DropdownMenuContentProps
|
|
124
|
-
|
|
125
|
+
// asChild Omit(2026-07-18 決策2):Content 恆注入固定 <RowSizeProvider> + <ScrollArea>
|
|
126
|
+
// wrapper 於 Portal 內 → <Content asChild> 會把 Content props slot-merge 到非-DOM Provider 上而壞。
|
|
127
|
+
// children 保留(合法渲染於固定 wrapper 內)。
|
|
128
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>, 'asChild'> {
|
|
125
129
|
size?: SizeKey
|
|
126
130
|
/** 最小寬度(px),預設 `max(180px, 觸發元件寬度)`——窄 trigger 時吃 180px 地板 */
|
|
127
131
|
minWidth?: number
|
|
128
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* 最大高度(px)——**可選更低上限**。預設已 viewport-adaptive(夾到 trigger→視窗碰撞邊界的剩餘高度、
|
|
134
|
+
* 離窗 ≥8px、超過即捲動);傳 `maxHeight` 只在「想比視窗剩餘更矮」時用(取 `min(視窗剩餘, maxHeight)`)。
|
|
135
|
+
*/
|
|
129
136
|
maxHeight?: number
|
|
130
137
|
}
|
|
131
138
|
|
|
@@ -154,24 +161,26 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
154
161
|
// close 後 programmatic refocus 在 Chromium 仍可 match `:focus-visible`(UA heuristic),
|
|
155
162
|
// 但與 shadcn 官方 live demo 同流程 DOM 比對 IDENTICAL — Radix 生態一致接受此
|
|
156
163
|
// tradeoff:APG keyboard focus-return 優先於 cosmetic ring。
|
|
157
|
-
className={cn(floatingLayerClass,
|
|
164
|
+
className={cn(floatingLayerClass, 'flex flex-col min-h-0', className)}
|
|
158
165
|
style={{
|
|
159
166
|
boxShadow: 'var(--elevation-200)',
|
|
160
167
|
minWidth: minWidth ?? 'max(180px, var(--radix-dropdown-menu-trigger-width))',
|
|
161
|
-
|
|
168
|
+
// 2026-07-18 viewport-adaptive max-h(對齊 HoverCard/Popover 既有 canonical):夾到「trigger 到
|
|
169
|
+
// 碰撞邊界(已內縮 collisionPadding=8px)的剩餘高度」→ 高選單不溢出視窗、離視窗邊 ≥8px、
|
|
170
|
+
// 短選單仍貼內容;consumer `maxHeight` 為**可選更低上限**(min 取小者)。inline style 非 class,
|
|
171
|
+
// 故動態 maxHeight 不觸 Tailwind 掃描陷阱。
|
|
172
|
+
maxHeight: maxHeight
|
|
173
|
+
? `min(var(--radix-dropdown-menu-content-available-height, 100vh), ${maxHeight}px)`
|
|
174
|
+
: 'var(--radix-dropdown-menu-content-available-height, 100vh)',
|
|
162
175
|
}}
|
|
163
176
|
{...props}
|
|
164
177
|
>
|
|
165
178
|
<RowSizeProvider value={size}>
|
|
166
|
-
{
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
</ScrollArea>
|
|
172
|
-
) : (
|
|
173
|
-
children
|
|
174
|
-
)}
|
|
179
|
+
{/* body 恆在 flex-1 ScrollArea 內:超過 clamped 高度即跨-OS 一致捲動(不吃寬度);
|
|
180
|
+
短選單 flex-1 貼內容不塌(同 HoverCard/Popover/DataTable panel 已驗)。py-2 在內層,整 padded 區可捲。 */}
|
|
181
|
+
<ScrollArea className="flex-1 min-h-0">
|
|
182
|
+
<div className="py-2">{children}</div>
|
|
183
|
+
</ScrollArea>
|
|
175
184
|
</RowSizeProvider>
|
|
176
185
|
</DropdownMenuPrimitive.Content>
|
|
177
186
|
</DropdownMenuPrimitive.Portal>
|
|
@@ -430,7 +439,11 @@ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
|
430
439
|
// Radix handles checked state;checked 底色套在外層 Radix RadioItem 本身(parent-bg
|
|
431
440
|
// pattern,詳下方 2026-05-31 #10 註解),內層 MenuItem 恆 !bg-transparent 讓它透出。
|
|
432
441
|
interface DropdownMenuRadioItemProps
|
|
433
|
-
|
|
442
|
+
// children Omit + redeclare required(2026-07-18 決策2 一致性):對齊 sibling Item/SubTrigger/
|
|
443
|
+
// CheckboxItem — menu item 必有 label,children 為 required 非 Radix optional。RadioItem 恆渲染
|
|
444
|
+
// 固定 <MenuItem>{children}</MenuItem>,children 是合法 label(非 lie),此為 required 化一致性修。
|
|
445
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>, 'children'> {
|
|
446
|
+
children: React.ReactNode
|
|
434
447
|
/** Prefix icon(LucideIcon) */
|
|
435
448
|
startIcon?: LucideIcon
|
|
436
449
|
/** 次要說明文字 */
|
|
@@ -17,8 +17,14 @@ export type FieldControlLayout = 'inline' | 'block'
|
|
|
17
17
|
* - `'toolbar'`— Toolbar / Action bar inline Field(future,目前無 consumer)
|
|
18
18
|
* - `'table-cell'`— DataTable cell-as-input substrate(取代 variant='naked' cell-detection
|
|
19
19
|
* heuristic;canonical metrics 由 surface 推導,不再 hardcode in Field consumer)
|
|
20
|
+
* - `'inline-edit'`(2026-07-17)— InlineEdit view 委派 surface。**唯一語義 = 委派控件 view 態
|
|
21
|
+
* 左 px 統一 `--field-px`(不繼承 tagPadding / avatar inset / bare-span 0px)**,讓 InlineEdit
|
|
22
|
+
* 固定 `-mx-[--field-px]` 對齊盒精準抵銷 → 值/tag/avatar 左緣落 label 左緣(x=0)。edit 態**不套**
|
|
23
|
+
* 本 surface(edit 完全繼承原 field,含 tagPadding — Jira 式)。對齊 Atlassian inline-edit
|
|
24
|
+
* read-view「fixed negative margin + read=edit geometry by construction」。只 InlineEdit display
|
|
25
|
+
* box 注入;DataTable cell(table-cell)/ 純表單(form)的 tagPadding + bare-span 完全不受影響。
|
|
20
26
|
*/
|
|
21
|
-
type FieldSurface = 'form' | 'toolbar' | 'table-cell'
|
|
27
|
+
type FieldSurface = 'form' | 'toolbar' | 'table-cell' | 'inline-edit'
|
|
22
28
|
|
|
23
29
|
// ── Context ──
|
|
24
30
|
export interface FieldContextValue {
|
|
@@ -30,7 +36,7 @@ export interface FieldContextValue {
|
|
|
30
36
|
descriptionId: string
|
|
31
37
|
errorId: string
|
|
32
38
|
mode: FieldMode
|
|
33
|
-
/** 視覺外殼透傳(2026-05-05)。default = 含 border+bg;naked = cell-as-input(edit×naked 自畫 border-based state machine;
|
|
39
|
+
/** 視覺外殼透傳(2026-05-05)。default = 含 border+bg;naked = cell-as-input(edit×naked 自畫 border-based state machine;view×naked 用 transparent border 由 host cell 供邊框);`bare` 2026-07-09 退役;naked 2026-07-14 型別收窄至
|
|
34
40
|
* FieldVariantInternal(@internal)— 公開 `<Field variant>` 只收 default,故本欄位維持 FieldVariant。
|
|
35
41
|
* child Field control 自動繼承,per-control prop override 可覆寫。詳 field-types.ts。 */
|
|
36
42
|
variant: FieldVariant
|
|
@@ -175,13 +181,13 @@ export function useResolvedFieldDisabled(disabledProp?: boolean | null): boolean
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
/**
|
|
178
|
-
* Resolve Field control 的 **mode**(
|
|
184
|
+
* Resolve Field control 的 **mode**(view / readonly / disabled / edit)— 2026-06-08 SSOT,統一兩派散落:
|
|
179
185
|
* 舊 Input 派 `modeProp ?? fieldCtx?.mode ?? (...)` → `<Field disabled>` 時 ctx.mode 仍 'edit',漏 disabled chrome。
|
|
180
|
-
* 舊 picker 派 `disabled ? 'disabled' : mode`(mode 預設 'edit')→ 完全不讀 fieldCtx.mode,`<Field mode="
|
|
186
|
+
* 舊 picker 派 `disabled ? 'disabled' : mode`(mode 預設 'edit')→ 完全不讀 fieldCtx.mode,`<Field mode="view">` 失效。
|
|
181
187
|
* 統一優先序(world-class:MUI FormControl disabled 完整 cascade + 顯式 prop 永遠最優先):
|
|
182
|
-
* 1. 顯式 mode prop(caller / DataTable cell
|
|
188
|
+
* 1. 顯式 mode prop(caller / DataTable cell 傳 mode="view")→ **永遠最優先**,故表格等顯式場景 inert
|
|
183
189
|
* 2. 有效 disabled(prop 或 `<Field disabled>`)→ 'disabled'(完整 disabled chrome)
|
|
184
|
-
* 3. FieldContext.mode(`<Field mode="
|
|
190
|
+
* 3. FieldContext.mode(`<Field mode="view"/"readonly">`)→ 讓 mode cascade 真正生效
|
|
185
191
|
* 4. 本地 readOnly → 'readonly'
|
|
186
192
|
* 5. 'edit'
|
|
187
193
|
* cell:mode prop 必有 → step 1 命中、fieldCtx=null → 完全 inert(Δ=0)。`disabled` 傳已 resolve 的 boolean 或未預設 prop。
|
|
@@ -240,7 +246,7 @@ export function FieldSurfaceSizeProvider({
|
|
|
240
246
|
/**
|
|
241
247
|
* Table-cell 可編輯訊號(2026-07-08 user 拍板)— 獨立於 FieldSurfaceContext / FieldSurfaceSizeContext
|
|
242
248
|
* 的純 boolean context,由 host(DataTable cell registry)注入該 cell 是否可編輯,讓 useFieldEmptyDisplay
|
|
243
|
-
* 分流「可編輯 cell 空
|
|
249
|
+
* 分流「可編輯 cell 空 view → 空白」vs「不可編輯 cell 空 view → '-'」。
|
|
244
250
|
* value = boolean primitive(stable when unchanged),不破壞 cell memo identity(同 TableScrollContext /
|
|
245
251
|
* FieldSurfaceSizeContext L119-121 canonical);絕不污染 FieldContext(useFieldContext() 在 cell 內仍 null)。
|
|
246
252
|
*/
|
|
@@ -261,16 +267,16 @@ export function FieldSurfaceEditableProvider({
|
|
|
261
267
|
*
|
|
262
268
|
* | 情境 | 空值顯示 |
|
|
263
269
|
* |------------------------------------------------|----------|
|
|
264
|
-
* | table-cell **可編輯** 的
|
|
270
|
+
* | table-cell **可編輯** 的 view 靜止態 | **空白 `''`**(不佔位,affordance = hover outline)|
|
|
265
271
|
* | table-cell **不可編輯**(readonly cell) | **半形 `-`** |
|
|
266
|
-
* | standalone
|
|
272
|
+
* | standalone view / readonly / form / toolbar | **半形 `-`** |
|
|
267
273
|
*
|
|
268
274
|
* 收斂式:`surface==='table-cell' && isEditable ? '' : EMPTY_DISPLAY`。
|
|
269
275
|
* 可編輯 form / edit 輸入框走 native placeholder(不經此 hook)。boolean → unchecked / disabled →
|
|
270
276
|
* 同上文字 + text-fg-disabled(M24),各控件自理。
|
|
271
277
|
* 世界級對照:table-cell blank = MUI X / AG Grid / Ant core / Notion / Airtable grid 域共識;
|
|
272
278
|
* 非 table `-` = Ant ProTable `columnEmptyText`(見 field-wrapper.tsx EMPTY_DISPLAY 註)。
|
|
273
|
-
* SSOT 條文 → field-controls.spec.md「null / undefined 值」;全 Field family
|
|
279
|
+
* SSOT 條文 → field-controls.spec.md「null / undefined 值」;全 Field family view/readonly/disabled
|
|
274
280
|
* 空值渲染必經此 hook,禁直接引 EMPTY_DISPLAY 常數(genre 分流會漏)。
|
|
275
281
|
*/
|
|
276
282
|
export function useFieldEmptyDisplay(): string {
|
|
@@ -288,7 +294,7 @@ export function useFieldEmptyDisplay(): string {
|
|
|
288
294
|
* 提示」的裝飾語意;空值符號不是提示、是被檢視的值狀態)。
|
|
289
295
|
* disabled 態維持 `text-fg-disabled`(M24 disabled 顯著性 > foreground/muted,不可被蓋)。
|
|
290
296
|
*
|
|
291
|
-
* SSOT:全 Field family
|
|
297
|
+
* SSOT:全 Field family view/readonly/disabled 空值 span 消費此 helper,傳入已 resolve 的
|
|
292
298
|
* resolvedMode。純函式(非 hook)—— resolvedMode 已由 useResolvedFieldMode 解析(含控件自身
|
|
293
299
|
* disabled prop,context hook 讀不到);故以 resolvedMode 為入參,可條件呼叫、不受 Rules of Hooks 限制。
|
|
294
300
|
* 世界級對照:Ant read-only / Carbon read-only value = 正常前景色;placeholder 才 muted。
|
|
@@ -12,7 +12,7 @@ traits:
|
|
|
12
12
|
# Field Controls 設計原則
|
|
13
13
|
|
|
14
14
|
> **Foundational SSOT rationale**(cap 800,2026-04-25 approved):
|
|
15
|
-
> Family 4 (Field Control Layout) SSOT owner。Input / NumberInput / DatePicker / Select / Combobox / LinkInput / TimePicker / Textarea / PeoplePicker 等皆消費 `fieldWrapperStyles` / edit-readonly-disabled 三態 mode architecture / endAction 處理 / `mode="
|
|
15
|
+
> Family 4 (Field Control Layout) SSOT owner。Input / NumberInput / DatePicker / Select / Combobox / LinkInput / TimePicker / Textarea / PeoplePicker 等皆消費 `fieldWrapperStyles` / edit-readonly-disabled 三態 mode architecture / endAction 處理 / `mode="view"` 渲染 pattern / Inline Action canonical(後者也 cascade 到 Sidebar / TreeView / DropdownMenu)。scope 本質 > 單一元件。
|
|
16
16
|
|
|
17
17
|
> **注意**:此文件是 Field Controls(Input / NumberInput / DatePicker / Select / Combobox / LinkInput / PeoplePicker 等)**共用**的設計原則,與 `Field/field.spec.md`(表單 Layout 容器)**不是同一個東西**。
|
|
18
18
|
>
|
|
@@ -26,7 +26,7 @@ traits:
|
|
|
26
26
|
Field Controls 是資料輸入與顯示的基礎元件。每種資料類型(text、number、date、select...)對應一個元件,同時服務 Form 和 DataTable:
|
|
27
27
|
|
|
28
28
|
- **Form**:用 Field Controls 的 edit / readonly / disabled 三態(在 Field 容器內)
|
|
29
|
-
- **DataTable**:以 Field Controls 的 `mode="
|
|
29
|
+
- **DataTable**:以 Field Controls 的 `mode="view"` 渲染 cell
|
|
30
30
|
|
|
31
31
|
每個元件擁有該類型的格式化邏輯(唯一真實來源),Form 和 DataTable 消費同一份 code。
|
|
32
32
|
|
|
@@ -42,19 +42,19 @@ components/
|
|
|
42
42
|
│ ├── field-controls.spec.md ← 本文件
|
|
43
43
|
│ ├── field-types.ts ← FieldMode / FieldVariant 共用型別 + getMenuListMinHeight(InlineActionConfig 住 patterns/element-anatomy/item-anatomy.tsx)
|
|
44
44
|
│ └── field-wrapper.tsx ← 共用 wrapper 樣式、bareInputStyles、EMPTY_DISPLAY
|
|
45
|
-
├── Input/ ← Input(含 mode="
|
|
46
|
-
├── NumberInput/ ← NumberInput(含 mode="
|
|
47
|
-
├── DatePicker/ ← DatePicker(含 mode="
|
|
48
|
-
├── Select/ ← Select(含 mode="
|
|
49
|
-
├── Combobox/ ← Combobox(含 mode="
|
|
50
|
-
├── LinkInput/ ← LinkInput(含 mode="
|
|
45
|
+
├── Input/ ← Input(含 mode="view";與 Field 平行的兄弟目錄,以下同)
|
|
46
|
+
├── NumberInput/ ← NumberInput(含 mode="view" + formatNumber)
|
|
47
|
+
├── DatePicker/ ← DatePicker(含 mode="view" + formatDate)
|
|
48
|
+
├── Select/ ← Select(含 mode="view")
|
|
49
|
+
├── Combobox/ ← Combobox(含 mode="view")
|
|
50
|
+
├── LinkInput/ ← LinkInput(含 mode="view")
|
|
51
51
|
├── PeoplePicker/ ← PeoplePicker + PersonDisplay(cross-component primitive)
|
|
52
52
|
└── Textarea/ ← Textarea(多行)
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
每個元件統一以 `mode` prop 切換樣態:
|
|
56
56
|
1. **edit / readonly / disabled** — Form 用,可編輯 / 鎖定 / 不可用
|
|
57
|
-
2. **`mode="
|
|
57
|
+
2. **`mode="view"`** — DataTable cell 用,純格式化顯示(取代過往的 `XxxDisplay` 子元件)
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
@@ -62,19 +62,23 @@ components/
|
|
|
62
62
|
|
|
63
63
|
Field 家族 = **一個 `fieldWrapperStyles` cva + 兩條正交軸**;`InlineEdit` 是**疊在其上的組合 primitive**,不屬於任一軸。此段為「框架為何長這樣」的單一住所。
|
|
64
64
|
|
|
65
|
-
**軸一 `mode`(4)× 軸二 `variant`(2)
|
|
65
|
+
**軸一 `mode`(4)× 軸二 `variant`(2)**(2026-07-16 round16 `display`→`view` 更名 + Model A):`variant` 只有 `default`(公開,完整 chrome)與 `naked`(`@internal`,cell-as-input:edit×naked 自畫 border-based state machine,view×naked 用 transparent border 由 host cell 供視覺邊框;只 DataTable `cell-registry.tsx` 消費)。`bare`(透明外殼)**2026-07-09 退役**;SSOT `field-types.ts` `FieldVariant`。有效組合:default × {edit/view/readonly/disabled};naked × {edit/view}。**`readonly×naked` / `disabled×naked` 死格 2026-07-16 移除**——DataTable cell「disabled」態廢除 + readonly 從不入 naked,全庫 0 消費;軸對稱靠 cva 省略天然覆蓋。readonly 2026-07-09 補鍵盤 focus ring(WCAG 2.4.7)。**型別層 public/internal union(2026-07-14 API 策展 E)**:公開 `FieldVariant = 'default'`;`naked` 收 `FieldVariantInternal`(`@internal`),cell-registry 經 `WithFieldVariantInternal` 消費。
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
**軸一 `view` mode = Model A(2026-07-16,user GO)**:`view`(原 `display`)= 非表單的值呈現(cell / InlineEdit / 詳情)。**`view×default` = edit 幾何減 chrome**——保留 `px-[--field-px]` + `py` + `h-field`,只拔 border/bg(透明);推翻 2026-05-13 Path Ⅰ 的 `!px-0 !py-0`。理由:view 對齊「edit 值位置」(非 label 左緣)→ 水平垂直都留 → view 與 edit **同一顆控件、只差 chrome** → read↔edit 零跳。**`view×naked` = bare**(host TD 給 padding),與 view×default **不同用途、不 collapse**。世界級對照 [Atlassian inline-edit](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx)(read=edit 幾何)+ Bootstrap [`.form-control-plaintext`](https://github.com/twbs/bootstrap/blob/main/scss/forms/_form-control.scss)(留 padding)。geometry 抽 `fieldViewGeometry(size, multiline)` helper(view×default 幾何 SSOT)。
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
**軸二 就地編輯 host(InlineEdit = DataTable cell,同一份語義,2026-07-16)**:只有 **`view ↔ edit` 二態 + `editable` 判準閘**(布林/callback,預設 true),**無 disabled 態**——世界級就地編輯 detail-pane / grid 皆無 disabled cell:[MUI X isCellEditable](https://mui.com/x/react-data-grid/editing/) / [AG Grid editable](https://www.ag-grid.com/react-data-grid/cell-editing/)。`editable=false` → view 無 hover 入口、無藍框、**不灰化**。永久唯讀資料 → 用 `<Control mode="view">`,不用 disabled。
|
|
70
70
|
|
|
71
|
-
**InlineEdit
|
|
71
|
+
**InlineEdit ≠ mode ≠ variant**:它是 **view↔edit 二態切換**——靜止 = 純值/格式化(內容 + 隱形 Pressable),點擊/Enter 才生一個 **edit-mode Field 控件**(預設 `<Input mode="edit">`,可 `renderEdit` 換 Textarea/Select)。站在 mode 軸**之上**。**Model A:InlineEdit 不自帶 geometry cva**——**兩條 view 路徑都用 `fieldViewGeometry`(field-px)**(2026-07-17 對齊 root cause 修):(a) 值-格式化路徑用 `fieldViewGeometry` **包住** `<Control mode="view">`(委派控件 bare view 皆 0px → 包住統一 field-px + 內部格式化間距仍由控件提供);(b) 純值/標題 `<Tag>` 直接套 `fieldViewGeometry`。本體只給:**orientation-aware `-mx`(vertical 含 standalone 貼 label / horizontal 對齊 sibling;不依 fieldCtx)**+ exit 保證(pointerdown-outside)+ hover bg + focus 藍框 + 隱形 Pressable。edit 態完全繼承原 field(Jira 式)。
|
|
72
|
+
|
|
73
|
+
**`naked` vs `InlineEdit`(不同層級,非冗餘)**:`naked` = 視覺外殼變體(chrome 層),靜止態**仍是控件**(裸 input),對應 in-cell 即時編輯;`InlineEdit` = 互動組合(behavior 層),靜止態**不是控件**(純值),對應 click-to-edit。各自獨立。
|
|
74
|
+
|
|
75
|
+
**InlineEdit 多行 / padding / 零位移契約(Model A)**:read↔edit 零跳 = view 與 edit **同一顆控件、只差 chrome**。**多行(`multiline`)**:edit 自動用 `<Textarea mode="edit">`,view 用 `fieldViewGeometry(size, true)` = `items-start py-2`(**= Textarea edit `py-2`**)。**多 tag / avatar / prefix 換行的 wrap py 由委派控件 view mode 提供**(= 它 edit 的 py)—— 故 view×default **不可** collapse 成 `!py-0`。水平:InlineEdit `-mx-[--field-px]` 拉整塊到欄左緣、view(`fieldViewGeometry` 統一 `px-[--field-px]`,含委派控件 bare view 被包住)被 -mx 抵消 → 值/tag 落 label x=0。edit 態繼承原 field(不統一 px)→ 純文字零跳、tag ~6px 微移(Jira 式)。pixel 鎖 `scripts/probe-inline-edit-align.mjs`。
|
|
72
76
|
|
|
73
77
|
---
|
|
74
78
|
|
|
75
|
-
## Mode — 表單三態 (
|
|
79
|
+
## Mode — 表單三態 (view 見下方 View 段)
|
|
76
80
|
|
|
77
|
-
下表涵蓋 Form-context 三態(edit / readonly / disabled);完整 `FieldMode` 為四值(`'edit' | '
|
|
81
|
+
下表涵蓋 Form-context 三態(edit / readonly / disabled);完整 `FieldMode` 為四值(`'edit' | 'view' | 'readonly' | 'disabled'`,2026-07-16 `display`→`view` 更名),`view`(非表單值呈現)於下方 `## View` 段記載。
|
|
78
82
|
|
|
79
83
|
| Mode | 底色 | 邊框 | 文字色 | 用途 |
|
|
80
84
|
|------|------|------|--------|------|
|
|
@@ -84,7 +88,7 @@ Field 家族 = **一個 `fieldWrapperStyles` cva + 兩條正交軸**;`InlineEdit
|
|
|
84
88
|
|
|
85
89
|
三種模式共用同一個 wrapper 結構(`fieldWrapperStyles`),只有底色、邊框、文字色不同。
|
|
86
90
|
|
|
87
|
-
**Boolean / 單選控件的 readonly(2026-06-12 user 拍板)**:Field 內 readonly 的 Checkbox / Switch = 同一 `fieldWrapperStyles` readonly 灰框 + ✓/—(
|
|
91
|
+
**Boolean / 單選控件的 readonly(2026-06-12 user 拍板)**:Field 內 readonly 的 Checkbox / Switch = 同一 `fieldWrapperStyles` readonly 灰框 + ✓/—(view 同款值語言);RadioGroup = 灰框 + 選中項 label(= Select readonly 同款呈現)。理由:同一張 readonly 表單中,文字控件有灰框鎖定訊號、boolean 保留全彩控件會誤導「仍可操作」(世界級 0/4 採原樣鎖互動:Salesforce = ✓ 無框靜態 glyph / SAP = 靜態文字 / Atlassian = readView / Ant Pro = 文字)。standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動。**邊界**:Rating readonly = 星星本身(星星即值語言,role=img,全業界 review-stars canonical,不包灰框);Slider 在 `<Field mode="readonly">` 內 = 鎖互動保留正常視覺(value 可讀不降色,pointer-events-none + thumb tabIndex=-1)。
|
|
88
92
|
|
|
89
93
|
### Loading state(async 驗證 / debounce fetch 中)
|
|
90
94
|
|
|
@@ -175,11 +179,11 @@ Field wrapper 透過 context 注入的 key 是 `invalid`(**非 `error`**;field.t
|
|
|
175
179
|
|
|
176
180
|
判準 = **「這個元件是不是承載/編輯一個欄位值的互動控件?」**(對齊 MUI FormControl 對 form control 的 cascade、Ant `Form disabled` 排除非表單控件如 Segmented/Tabs):
|
|
177
181
|
|
|
178
|
-
- **承載欄位值的互動控件** → 完整 cascade(disabled + 有
|
|
182
|
+
- **承載欄位值的互動控件** → 完整 cascade(disabled + 有 view 態者含 mode):Input / NumberInput / Textarea / LinkInput / Select / Combobox / DatePicker / TimePicker / PeoplePicker / Switch / Checkbox / RadioGroup / Slider / SegmentedControl / Rating。
|
|
179
183
|
- **欄位內的展示元素**(Avatar)→ 跟隨 `<Field disabled>` / `<Field mode="disabled">` **變淡**(視覺一致),用 fieldCtx 存在性 scope(DataTable cell 無 fieldCtx → 不影響)。
|
|
180
184
|
- **獨立 action 元件**(Button)→ **不**自動 cascade;由 consumer 自控 `disabled`(對齊 MUI Button 無 FormControl 整合 + Ant 排除 custom/非表單控件)。
|
|
181
185
|
|
|
182
|
-
注:有
|
|
186
|
+
注:有 view 渲染分支者(Input 家族 / Select / Combobox / DatePicker / TimePicker / PeoplePicker / **Checkbox** / **Switch**,後二者 view = ✓/—)完整響應 `<Field mode="view"/"readonly">` + `<Field disabled>`;**Slider / Rating 無 view 態但有 readonly cascade**(2026-06-12 補:Slider readonly = 鎖互動保留視覺;Rating readonly = 星星鎖定 role=img)+ 響應 `<Field disabled>`;**SegmentedControl 無 view/readonly 態**(僅 enabled/disabled)→ 只響應 `<Field disabled>`。**group 控件(Checkbox/RadioGroup/Switch/SegmentedControl)雖非 fieldWrapperStyles 消費者,仍一律經 resolver hook 解析**(gate Check 1b/2 強制)。
|
|
183
187
|
|
|
184
188
|
**機械強制**:`scripts/check-field-cascade-resolve.mjs`(ci + release:preflight)—— 消費 `fieldWrapperStyles` 的控件若散落手刻 `fieldCtx?.{disabled,mode}` 解析(而非走 resolver hook)= fail,防新控件重演 cascade 漏接。
|
|
185
189
|
|
|
@@ -265,10 +269,10 @@ Select / Combobox 的 ChevronDown、DatePicker 的 Calendar、TimePicker 的 Clo
|
|
|
265
269
|
|
|
266
270
|
- edit:`fg-muted`;**readonly:不顯示 indicator**;**disabled:`fg-disabled`**(對齊上方 Icon 色彩原則)
|
|
267
271
|
- 不可互動(`pointer-events-none`)——下拉由 select 元素本身觸發
|
|
268
|
-
- **Cell(naked variant)**:**
|
|
272
|
+
- **Cell(naked variant)**:**view 態零恆顯 indicator(2026-07-08 user 拍板 A 案,推翻 2026-05-10「indicator = editable affordance」)** —— editable affordance 統一 = hover outline(field.spec.md L4)。Benchmark 6/6 product-table 域(Ant editable-cells / MUI X singleSelect / AG Grid / Atlaskit inline-edit(v2.0.0 移除 hover-pencil 後 9 版未回歸)/ Notion / Airtable)view 態皆純值零 icon;恆顯派僅 Google Sheets Chip/Arrow(spreadsheet 域可選檔位)。`showDisplayEndIcon` prop 保留為 opt-in 逃生門(spreadsheet-flavored 消費端);cell-registry 6 個 picker 站不再傳、url 站傳 `isEditable === true`(LinkInput 例外 = wrapper-only 無 icon,取 display↔edit 像素對齊)。edit 態 indicator 照舊(本節上方 form 規則);opt-in 時 (opt-in 時保留)(同表單邏輯)
|
|
269
273
|
- locked(readonly/disabled)wrapper 並設 `aria-disabled`(disabled 時)——styled-disabled 非原生元素需明告 AT inactive,亦使 axe 正確套用 WCAG 1.4.3 inactive-UI 豁免
|
|
270
274
|
- clearable 有值時:clear X 在左,ChevronDown 在右
|
|
271
|
-
- **右側元素(clear / chevron / calendar / clock)右緣水平內距 = `--field-px`(12px,SSOT `tokens/uiSize/uiSize.css`),edit / readonly / disabled /
|
|
275
|
+
- **右側元素(clear / chevron / calendar / clock)右緣水平內距 = `--field-px`(12px,SSOT `tokens/uiSize/uiSize.css`),edit / readonly / disabled / view 全 mode 一致**(跟 Input 一致)。**tag 模式特例**:左側 `tagPadding` 用對稱 px-calc(≈8px)貼齊 tags、會吃掉右緣,故 tag 容器(含 readonly/disabled)**必 re-assert `paddingRight: var(--field-px)`** 對齊 edit;漏接 = chevron 右緣偏移 bug(2026-06-27 修 Select:354 / Combobox ReadonlyMultiSelect)
|
|
272
276
|
- **多行(Combobox tag wrap)垂直對齊**:tags 換行、容器動態變高時,右側 chevron **鎖第一行 tag 中線**(非整體置中)——容器 `items-start` + `ItemSuffix self-start` + `style={{ height: tagHeight }}`(sm 20 / md+lg 24)。對齊 item-anatomy「suffix 永遠 `h-[1lh]` 對齊第一行」canonical;edit / readonly / disabled 全 mode 一致(2026-06-27 補 readonly/disabled wrap 漏接)
|
|
273
277
|
|
|
274
278
|
## Select 顯示模式
|
|
@@ -282,7 +286,7 @@ Select 支援兩種顯示模式(`display` prop):
|
|
|
282
286
|
|
|
283
287
|
`plain` 模式可搭配 `startIcon`(代表 value 的圖示,如狀態 icon;2026-05-01 由 `text` 改名 `plain`,rationale 見 `select.spec.md`)。
|
|
284
288
|
|
|
285
|
-
`selectedItemRenderer` 設定時優先於 plain / tag 預設呈現,且 **4 mode(edit/
|
|
289
|
+
`selectedItemRenderer` 設定時優先於 plain / tag 預設呈現,且 **4 mode(edit/view/readonly/disabled)共享**(共享 contract (a),見下方)— view 態渲染 renderer 輸出(值內容),無 chrome 無 chevron。
|
|
286
290
|
|
|
287
291
|
`tag` 模式的 edit 用 hidden select overlay(跟 Combobox 同模式),Tag 用 `pointer-events-none`,點擊穿透到 select。右側元素右緣 = `--field-px`(見上方「右側元素」canonical;tag 模式 readonly/disabled 必 re-assert)。
|
|
288
292
|
|
|
@@ -318,33 +322,36 @@ Icon 色彩遵循 Inline Action 統一規則:預設 `fg-muted`,hover 時 `foregr
|
|
|
318
322
|
|
|
319
323
|
---
|
|
320
324
|
|
|
321
|
-
##
|
|
325
|
+
## View — 值呈現(mode="view",Model A;2026-07-16 原 `display` 更名)
|
|
322
326
|
|
|
323
|
-
每個 Field 元件以 `mode="
|
|
327
|
+
每個 Field 元件以 `mode="view"` 渲染分支把 raw value 格式化為純展示輸出(取代過往 `XxxDisplay` 子元件;唯一現存 cross-component view primitive 是 PeoplePicker 的 `PersonDisplay`)。**Model A 幾何**:`view×default` = edit 幾何減 chrome(留 `px`/`py`/`h-field`,只拔 border/bg)——見上方「軸一 view mode」。
|
|
324
328
|
|
|
325
|
-
|
|
326
|
-
- **DataTable cell**:cell-registry 根據 `meta.type` 選對應 Field 元件並傳 `mode="
|
|
329
|
+
View 的消費者:
|
|
330
|
+
- **DataTable cell**:cell-registry 根據 `meta.type` 選對應 Field 元件並傳 `mode="view"`(variant="naked";2026-07-16 cell「disabled」態廢除,鎖定用 `editable:(row)=>false`)
|
|
331
|
+
- **InlineEdit view 態**:委派 `<Control mode="view">` 取格式化 + 幾何(read=edit 同一顆控件)
|
|
327
332
|
- **Field readonly 模式**:內部使用相同的格式化邏輯
|
|
328
333
|
|
|
334
|
+
**例外 — LinkInput / PeoplePicker 預設 view 路徑不包 wrapper**(code 為準,2026-07-16 明文):LinkInput `mode="view"` 預設(`showDisplayEndIcon=false`)= 裸 span/anchor(`fieldDisplayTextClass` + truncate),**不消費** `fieldWrapperStyles` / `fieldViewGeometry` —— view 值是可點擊連結,inline 嵌入取 flush 呈現(backward compat);`showDisplayEndIcon=true` opt-in 才包 `fieldWrapperStyles(view × resolvedVariant)` 取 cell view↔edit 像素對齊。PeoplePicker 預設 view 同模式(裸 `PersonDisplay`/`MultiPersonDisplay`)。詳 `link-input.tsx` / `people-picker.tsx` docblock。
|
|
335
|
+
|
|
329
336
|
### null / undefined 值(2026-07-08 user 拍板 — 半形 hyphen + editable × surface 分流)
|
|
330
337
|
|
|
331
338
|
> **user verbatim**:「table cell 不可編輯的空值,單獨不可編輯的 display 的空值,單獨的 readonly 的空值都用"-";單獨可以編輯的 edit 輸入框的空值則是 placeholder;table cell 內可編輯的 display 的空值就是為空」+「我從頭到尾哪裡有說要用全形的」。
|
|
332
339
|
|
|
333
|
-
空值符號 = **半形 hyphen `-`(U+002D)**,**非**全形 em dash `—`(U+2014)。分流看 **surface × 是否可編輯 × mode**(SSOT 機械層 = `field-context.ts` `useFieldEmptyDisplay()` + `EMPTY_DISPLAY` 常數 `field-wrapper.tsx`;全 Field family
|
|
340
|
+
空值符號 = **半形 hyphen `-`(U+002D)**,**非**全形 em dash `—`(U+2014)。分流看 **surface × 是否可編輯 × mode**(SSOT 機械層 = `field-context.ts` `useFieldEmptyDisplay()` + `EMPTY_DISPLAY` 常數 `field-wrapper.tsx`;全 Field family view/readonly/disabled 空值渲染必經此 hook,**禁**直接引 `EMPTY_DISPLAY` 常數):
|
|
334
341
|
|
|
335
342
|
| 情境 | 判準 | 空值顯示 | 依據 |
|
|
336
343
|
|---|---|---|---|
|
|
337
|
-
| 不可編輯 — standalone
|
|
344
|
+
| 不可編輯 — standalone view / readonly | mode ∈ {view, readonly} 且非可編輯 table cell | **半形 `-` + `text-foreground`** | 唯讀資料「此欄無值」明示;Ant ProTable `columnEmptyText` 預設 `'-'`(https://github.com/ant-design/pro-components/blob/master/src/table/Table.tsx)|
|
|
338
345
|
| 不可編輯 — table cell(readonly cell)| `surface==='table-cell'` 且 `isEditable===false` | **半形 `-`** | 同上;非可編欄位視同唯讀資料 |
|
|
339
|
-
| **可編輯** — table cell
|
|
346
|
+
| **可編輯** — table cell view 靜止態 | `surface==='table-cell'` 且 `isEditable===true` 且 mode='view' | **全空白 `''`** | grid 域壓倒性共識(MUI X `valueToRender?.toString()` null 短路 / AG Grid `_toString(null)`→不設 textContent / Ant core rc-table 裸值 / Notion / Airtable);表格密集時「-」海 = 視覺噪音。空 editable cell 的 affordance = hover outline(field.spec.md L4),非佔位符 |
|
|
340
347
|
| **可編輯** — form / panel edit 輸入框 | mode='edit' 且非 table cell | **native placeholder** | 標準表單輸入提示;「hover 才顯 placeholder」十路查證零家採用,**禁** |
|
|
341
348
|
| **可編輯** — table cell edit / focus | `surface==='table-cell'` 且 mode='edit' | **全空白**(DataTable cell 不接 placeholder)| 對齊 Notion / Airtable cell 編輯裸輸入 |
|
|
342
349
|
|
|
343
350
|
收斂式:`surface==='table-cell' && isEditable ? '' : '-'`(可編輯 form edit 走 native placeholder,不經此 hook)。`isEditable` 由 DataTable cell registry 經 `FieldSurfaceEditableProvider` boolean context 注入(standalone / form 無此 context → 預設 false → `-`)。
|
|
344
351
|
|
|
345
|
-
**readonly native input 兩派統一**(2026-07-08):Input / Textarea 的 readonly **空值** 改走
|
|
352
|
+
**readonly native input 兩派統一**(2026-07-08):Input / Textarea 的 readonly **空值** 改走 view-span 顯 `-`(原走 native `<input readOnly>` 吐 native placeholder / 空白);readonly **有值** 仍走 native input 保留選取/複製語意。NumberInput 早已 `resolvedMode !== 'edit'` 統一走 span,無需改。「有值」判定(2026-07-14 補註,dual-model consensus;不改空值顯 `-` 規則本身):uncontrolled(只傳 `defaultValue`)同樣算有值 —— 機械層 = `useControllable` 內部 resolved value(defaultValue 初始 + 打字寫回,native element 由 `value={resolved}` 內部驅動)+ form.reset() bridge(HTML reset 不發 input event → uncontrolled 掛 form `reset` listener 把 resolved 歸位 defaultValue),切 view / readonly(native element unmount/remount)後判定與 DOM 真值一致、不顯 stale 值;controlled 純 passthrough 行為不變。
|
|
346
353
|
|
|
347
|
-
**空值符號顏色**(2026-07-09 user 拍板 verbatim「「-」代表的是不可編輯只拿來供檢視的值所以應該跟readonly 的value同樣顏色吧」):不可編輯「-」= `text-foreground`(同 readonly value 色,非 placeholder 提示的裝飾語意);disabled → `text-fg-disabled`(M24)。SSOT helper = `field-context.ts` `fieldEmptyColorClass(resolvedMode)`,全 Field family
|
|
354
|
+
**空值符號顏色**(2026-07-09 user 拍板 verbatim「「-」代表的是不可編輯只拿來供檢視的值所以應該跟readonly 的value同樣顏色吧」):不可編輯「-」= `text-foreground`(同 readonly value 色,非 placeholder 提示的裝飾語意);disabled → `text-fg-disabled`(M24)。SSOT helper = `field-context.ts` `fieldEmptyColorClass(resolvedMode)`,全 Field family view/readonly/disabled 空值 span 消費(禁散寫 `text-fg-muted`)。
|
|
348
355
|
|
|
349
356
|
**例外表**(唯一去處,禁散落各元件 spec):
|
|
350
357
|
- **boolean** → 顯示 unchecked 狀態(非空白非 dash)
|
|
@@ -353,7 +360,7 @@ Display 的消費者:
|
|
|
353
360
|
|
|
354
361
|
### DataTable 整合
|
|
355
362
|
|
|
356
|
-
DataTable 根據 column 的 `meta.type` 自動選擇 Field 元件(以 `mode="
|
|
363
|
+
DataTable 根據 column 的 `meta.type` 自動選擇 Field 元件(以 `mode="view"` 渲染):
|
|
357
364
|
|
|
358
365
|
```tsx
|
|
359
366
|
// 自動渲染——不需要手寫 cell
|
|
@@ -375,7 +382,7 @@ col.accessor('status', {
|
|
|
375
382
|
|
|
376
383
|
## 共享 contract(2026-05-12 Stream C — Selected renderer / Placeholder vocabulary / Cell surface)
|
|
377
384
|
|
|
378
|
-
**(a) Selected value renderer**:rich display(avatar+name/icon+label)元件**必**提供 consumer renderer slot,**
|
|
385
|
+
**(a) Selected value renderer**:rich display(avatar+name/icon+label)元件**必**提供 consumer renderer slot,**view/readonly/disabled/edit** 4 mode 共享同一 renderer(禁 edit-only)。`Select.selectedItemRenderer`(4 mode 已全接 — 2026-07-08 A 案回歸修正,view/readonly/disabled 的 ReadonlyDisplay 消費 renderer 輸出;renderer 輸出屬「值內容」,view 態照常渲染,與 affordance 分層見 `field.spec.md` L6)/ `Combobox.tagRenderer`(edit 已接;view path 走 ComboboxTagStack 預設 Tag,consumer tagRenderer unify 仍 deferred,見 `combobox.tsx` 檔頭 `@renderer-symmetry-allow`;現行唯一 tagRenderer consumer = PeoplePicker,其 view 走 MultiPersonDisplay 不經此 path,無實際丟失)/ PeoplePicker 走 `PersonDisplay`+`MultiPersonDisplay`+`Combobox.tagRenderer`。對齊 MUI Autocomplete `renderValue` / Ant Select `tagRender`+`labelRender`+`optionRender` / MUI DataGrid `renderCell`+`renderEditCell` 共享 params。 <!-- @benchmark-unverified -->
|
|
379
386
|
|
|
380
387
|
**(b) Placeholder vocabulary**(3 props 對 3 UI state,**不可混用**):
|
|
381
388
|
- `placeholder` — trigger empty(沒選值,例「請選擇人員」)— Ant/Polaris/Carbon canonical
|
|
@@ -386,15 +393,15 @@ col.accessor('status', {
|
|
|
386
393
|
|
|
387
394
|
**(c) Cell surface metrics**:Field family 在 cell 內**禁** hardcode padding(`tagAreaPaddingLeftPx={isEmpty ? undefined : 8}` 反 pattern)。改 **`FieldSurface` context**(`'form' | 'toolbar' | 'table-cell'`):`useFieldSurface()` 取值,`<FieldSurfaceProvider surface="table-cell">` 自動套於 `cell-registry.resolveCellComponent`。Consumer 用 `surface === 'table-cell'` 顯式 query(取代 `variant === 'naked'` heuristic)。**risk mitigation**:`avatar.left = cell.left + computed(--table-cell-px)`,禁再加 magic 8px(double-count)。**Token scope**:`--table-cell-px/py` 是 DataTable-scoped metric(CSS 定義在 `data-table.css`,Field naked variant 是 DataTable cell substrate sub-component 故 cross-path reference 不算真 cross-component),per 2026-05-13 codex Q2 verdict + AG Grid `cellHorizontalPadding`(grid theme param)/ MUI X `cellClassName`(per-cell)/ Carbon spacing scale primitive(不升 cell padding 全域 token)idiom — **不**升 `tokens/layoutSpace/` canonical。對齊 AG Grid cellRendererSelector / Material X DataGrid 共享 params / Notion property type registry。Hook `check_field_controls_contracts.sh` (contract c) 機械強制。 <!-- @benchmark-unverified -->
|
|
388
395
|
|
|
389
|
-
**(d)
|
|
396
|
+
**(d) View×default = edit 幾何減 chrome(Model A)SSOT**(2026-07-16 round16 user GO;推翻 2026-05-13 Path Ⅰ「zero chrome + `!px-0 !py-0`」拍板):default `mode='view'` = **edit 幾何減 chrome** —— 保留 size 軸 `px-[var(--field-px)]` + `h-field-*`(多行留 py),只拔 border/bg(透明)→ view 與 edit 同一顆控件、只差 chrome → read↔edit 零跳(與上方「軸一 view mode」段同一 canonical)。真要包 chrome 走 `readonly` 或 `showDisplayEndIcon=true` opt-in。**Impl**:`field-wrapper.tsx` view×default compound(`bg-transparent border-transparent`,幾何由 base + size 軸天然保留)+ `fieldViewGeometry(size, multiline)` helper(view×default 幾何 SSOT,供 InlineEdit 純值/標題路徑消費)。世界級對照:[Atlassian inline-edit read-view](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx)(read=edit 幾何)+ Bootstrap [`.form-control-plaintext`](https://github.com/twbs/bootstrap/blob/main/scss/forms/_form-control.scss)(留 padding)。機械鎖:`scripts/inline-edit-view-geometry-invariant.mjs`(multiline py-2 = Textarea edit py-2 契約;注:hook `check_field_controls_contracts.sh` 的 contract (d) 為 field-px token 檢查,非本條)。
|
|
390
397
|
|
|
391
|
-
**(e)
|
|
398
|
+
**(e) View typography canonical**(2026-05-14 user I2 + codex M31 verdict):Field family view path **必** consume `fieldWrapperStyles` size variants typography token — `sm/md → text-body`(14px line-height 1.5)/ `lg → text-body-lg`(16px)。**禁**:LinkInput / Select / Combobox 非 D-path 的 bare-span 直接 render 無 font-size class(瀏覽器 default 字體);**必**包 `text-body` (sm/md) / `text-body-lg` (lg) class。對齊跨 Field family view 視覺尺寸統一(歷史 anchor:user 抓 LinkInput view 字體跟其他 Field 不一致 = SSOT 違反 漏接 typography token)。**Impl**:LinkInput / Select / Combobox / DatePicker / TimePicker non-D-path bare-span 加 size-aware text class(helper = `fieldDisplayTextClass`)。world-class cite:MUI X DataGrid `Typography` consistent / Atlassian @atlaskit/textfield size-prop typography token / Polaris TextField typographyToken size-aware。 <!-- @benchmark-unverified -->
|
|
392
399
|
|
|
393
400
|
---
|
|
394
401
|
|
|
395
402
|
## 邊界案例(家族共用 pointer)
|
|
396
403
|
|
|
397
|
-
- **極長輸入溢出**:由各元件 spec own — Input 超寬走原生水平捲動(`input.spec.md`「邊界(內容超寬)」)、Textarea 內容超出時 native 內部捲動 /
|
|
404
|
+
- **極長輸入溢出**:由各元件 spec own — Input 超寬走原生水平捲動(`input.spec.md`「邊界(內容超寬)」)、Textarea 內容超出時 native 內部捲動 / view 態隨內容增高(`textarea.spec.md`「極長文字」)。
|
|
398
405
|
- **常見誤解 — disabled 時 label 該隱藏?**:不隱藏 — label 保留但變灰(`FieldLabel` disabled 灰化、required 星號同步 `text-fg-disabled`,SSOT `Field/field.spec.md`);停用原因由外部 Tooltip / help text 承擔(見「disabled 的停用原因」)。
|
|
399
406
|
|
|
400
407
|
## 表單驗證原則
|
|
@@ -407,8 +414,8 @@ col.accessor('status', {
|
|
|
407
414
|
- ❌ 不在 input 尾部放 error 狀態 icon——邊框顏色已傳達 error
|
|
408
415
|
- ❌ endAction 不可傳入 ReactNode——使用 InlineActionConfig 宣告式 API
|
|
409
416
|
- ❌ endAction 的 inline action 不可省略 `aria-label`(即 `label` 欄位)
|
|
410
|
-
- ❌ 不可編輯
|
|
411
|
-
- ❌ Field 的 readonly 模式不可用於 DataTable cell——readonly 有底色和 wrapper 開銷,table cell 用
|
|
417
|
+
- ❌ 不可編輯 view / readonly 空值用全形 em dash `—` 或 `text-fg-muted`——半形 `-` + `text-foreground`(「-」是供檢視的值,同 readonly value 色;disabled → fg-disabled);可編輯 table cell view 空值為空白(見「null / undefined 值」分流表)
|
|
418
|
+
- ❌ Field 的 readonly 模式不可用於 DataTable cell——readonly 有底色和 wrapper 開銷,table cell 用 `mode="view"`
|
|
412
419
|
|
|
413
420
|
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
414
421
|
|
|
@@ -417,11 +424,13 @@ col.accessor('status', {
|
|
|
417
424
|
- `checkbox.spec.md`
|
|
418
425
|
- `circular-progress.spec.md`
|
|
419
426
|
- `combobox.spec.md`
|
|
427
|
+
- `data-table.spec.md`
|
|
420
428
|
- `date-picker.spec.md`
|
|
421
429
|
- `element-anatomy.spec.md`
|
|
422
430
|
- `field-control-group.spec.md`
|
|
423
431
|
- `field.spec.md`
|
|
424
432
|
- `form-validation.spec.md`
|
|
433
|
+
- `inline-edit.spec.md`
|
|
425
434
|
- `input.spec.md`
|
|
426
435
|
- `item-anatomy.spec.md`
|
|
427
436
|
- `link-input.spec.md`
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
// ── Field Mode ───────────────────────────────────────────────────────────────
|
|
2
2
|
//
|
|
3
|
-
// 4 模式 canonical(2026-05-05 expand
|
|
3
|
+
// 4 模式 canonical(2026-05-05 expand;2026-07-16 round16 `display`→`view` 更名 + Model A 幾何):
|
|
4
4
|
// edit — 一般可編輯 input(預設 variant:border + bg)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
5
|
+
// view — **純展示值(非表單)**;語意「要呈現給人看的值本體」(cell / InlineEdit / 詳情)。
|
|
6
|
+
// **Model A**:view = edit 幾何減 chrome(透明 bg/border,**保留 px 內距 + 高度**)→ read↔edit
|
|
7
|
+
// 零跳(view 與 edit 同一顆控件、只差 chrome)。對齊 Atlassian inline-edit(read=edit 幾何)
|
|
8
|
+
// + Bootstrap `.form-control-plaintext`(留 padding)。詳 field-controls.spec.md「軸一 view mode」。
|
|
9
|
+
// readonly — input chrome + non-editable(bg-readonly + a11y ring「這是 input 但鎖了」);表單語境、值仍算數可選取
|
|
10
|
+
// disabled — input chrome + disabled state(灰底,不可互動,不送出,語意「目前不適用」);表單專屬
|
|
10
11
|
//
|
|
11
|
-
// `
|
|
12
|
-
// -
|
|
13
|
-
// -
|
|
12
|
+
// `view` vs `readonly` 判別:
|
|
13
|
+
// - 「純展示值、非表單」(DataTable cell / InlineEdit view / ProfileCard meta) → `view`
|
|
14
|
+
// - 「表單欄位但目前不可改、值仍送出」(form 鎖部分欄位) → `readonly`
|
|
14
15
|
//
|
|
16
|
+
// 命名(命名 3-test 全過):MUI X cellMode=view / Atlassian readVIEW 字根 / 與 edit 成對。
|
|
15
17
|
// World-class refs(M22 verified):
|
|
16
18
|
// Carbon: https://carbondesignsystem.com/patterns/read-only-states-pattern/
|
|
17
19
|
// PatternFly: https://www.patternfly.org/components/inline-edit/design-guidelines/
|
|
18
20
|
// Cloudscape: https://cloudscape.design/patterns/general/disabled-and-read-only-states/
|
|
19
|
-
export type FieldMode = 'edit' | '
|
|
21
|
+
export type FieldMode = 'edit' | 'view' | 'readonly' | 'disabled'
|
|
20
22
|
|
|
21
23
|
// ── Field Variant ────────────────────────────────────────────────────────────
|
|
22
24
|
//
|
|
@@ -25,7 +27,7 @@ export type FieldMode = 'edit' | 'display' | 'readonly' | 'disabled'
|
|
|
25
27
|
// default — 含 border + bg(一般 form input,= Ant outlined / MUI outlined)。公開 FieldVariant 唯一值
|
|
26
28
|
// naked — @internal(FieldVariantInternal)。cell-as-input(host cell substrate)。edit×naked 自畫
|
|
27
29
|
// border-based state machine(rest → hover → focus-within:border-primary → error 紅框);
|
|
28
|
-
//
|
|
30
|
+
// view×naked 用 transparent border,由 host cell 提供視覺邊框。
|
|
29
31
|
// 對齊 Airtable / Notion / Excel cell editing。唯一合法消費者 = DataTable cell-registry
|
|
30
32
|
// (field-controls.spec.md「軸二 variant」明文)— consumer 直傳 `<Input variant="naked">`
|
|
31
33
|
// 會繞過 canonical chrome / focus,故公開型別排除
|
|
@@ -93,7 +95,7 @@ const FIELD_HEIGHT_TOKEN: Record<string, string> = {
|
|
|
93
95
|
lg: 'var(--field-height-lg)',
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
/** CommandList 最小高度 = field-height × rows + 16px(CommandGroup py-2 上下 padding
|
|
98
|
+
/** @internal — CommandList 最小高度 = field-height × rows + 16px(CommandGroup py-2 上下 padding);Command/Select 內部 helper,consumer 不直接 import。root barrel 排除(subpath 仍可用)。 */
|
|
97
99
|
export function getMenuListMinHeight(size: string, rows: number = 3): string {
|
|
98
100
|
const token = FIELD_HEIGHT_TOKEN[size] ?? FIELD_HEIGHT_TOKEN.md
|
|
99
101
|
return `calc(${token} * ${rows} + 16px)`
|