@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// code-quality-allow: file-size — foundational composite(Field + FieldLabel + FieldDescription + FieldError + context + 8 layout variants),拆檔會讓 Field 家族互相 import 循環
|
|
2
2
|
import * as React from 'react'
|
|
3
|
+
import { cva } from 'class-variance-authority'
|
|
3
4
|
import { Info as InfoIcon } from 'lucide-react'
|
|
4
5
|
import { cn } from '@/lib/utils'
|
|
5
6
|
import { Tooltip, TooltipTrigger, TooltipContent } from '@/design-system/components/Tooltip/tooltip'
|
|
@@ -102,6 +103,20 @@ function detectControlLayout(controlNodes: React.ReactNode[]): FieldControlLayou
|
|
|
102
103
|
return 'inline'
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/**
|
|
107
|
+
* 偵測 control child 的偏好尺寸——control primitive 在自己的檔案掛 static `fieldPreferredSize`
|
|
108
|
+
* (如 InlineEdit = 'sm',view 態無邊框,md 過大會讓版面鬆散),Field 未顯式指定 size 時收此偏好。
|
|
109
|
+
* 對齊 detectControlLayout 讀 `type.fieldLayout` 的機制(first-declared-wins);全部缺宣告 → null。
|
|
110
|
+
*/
|
|
111
|
+
function detectPreferredSize(controlNodes: React.ReactNode[]): FieldSize | null {
|
|
112
|
+
for (const node of controlNodes) {
|
|
113
|
+
if (!React.isValidElement(node)) continue
|
|
114
|
+
const pref = (node.type as { fieldPreferredSize?: FieldSize } | null | undefined)?.fieldPreferredSize
|
|
115
|
+
if (pref) return pref
|
|
116
|
+
}
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
// ── Field ───────────────────────────────────────────────────────────────────
|
|
106
121
|
|
|
107
122
|
export interface FieldProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'id'> {
|
|
@@ -111,7 +126,7 @@ export interface FieldProps extends Omit<React.HTMLAttributes<HTMLDivElement>, '
|
|
|
111
126
|
* 視覺外殼(2026-05-05)。
|
|
112
127
|
* - `default`(預設)— 含 border + bg(一般 form input)
|
|
113
128
|
* - `naked` — cell-as-input(@internal;不在公開 FieldVariant 型別 — 2026-07-14 收窄至 FieldVariantInternal;2026-07-09 `bare` 退役)。edit×naked 自畫 border-based state
|
|
114
|
-
* machine(rest/hover/focus/error);
|
|
129
|
+
* machine(rest/hover/focus/error);view×naked 用 transparent border,host cell 提供視覺邊框
|
|
115
130
|
*
|
|
116
131
|
* 透傳機制:Field 一次宣告,所有 child Field control 自動繼承(per-control prop override 可覆寫)。
|
|
117
132
|
*/
|
|
@@ -155,7 +170,7 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
|
155
170
|
mode = 'edit',
|
|
156
171
|
variant = 'default',
|
|
157
172
|
orientation = 'vertical',
|
|
158
|
-
size
|
|
173
|
+
size: sizeProp,
|
|
159
174
|
required = false,
|
|
160
175
|
disabled: disabledProp = false,
|
|
161
176
|
invalid = false,
|
|
@@ -199,6 +214,10 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
|
199
214
|
const controlLayout: FieldControlLayout =
|
|
200
215
|
controlLayoutProp ?? detectControlLayout(controlNodes)
|
|
201
216
|
|
|
217
|
+
// 解析 size:consumer 顯式 > control primitive 偏好(如 InlineEdit='sm')> 預設 md
|
|
218
|
+
// (對齊 controlLayout 三層優先序;InlineEdit 進 Field 不指定 size 時自動收 sm)
|
|
219
|
+
const size: FieldSize = sizeProp ?? detectPreferredSize(controlNodes) ?? 'md'
|
|
220
|
+
|
|
202
221
|
const contextValue = React.useMemo<FieldContextValue>(
|
|
203
222
|
() => ({
|
|
204
223
|
id,
|
|
@@ -496,9 +515,22 @@ export interface FieldGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
496
515
|
horizontalLabelWidth?: string
|
|
497
516
|
}
|
|
498
517
|
|
|
518
|
+
// gap variant 收進 cva(className-only 差異 → cva,per ui-development.md「cva 適用」);值仍走 Tailwind gap utility。
|
|
519
|
+
const fieldGroupVariants = cva('flex flex-col min-w-0', {
|
|
520
|
+
variants: {
|
|
521
|
+
gap: {
|
|
522
|
+
compact: 'gap-3',
|
|
523
|
+
normal: 'gap-4',
|
|
524
|
+
loose: 'gap-6',
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
defaultVariants: {
|
|
528
|
+
gap: 'normal',
|
|
529
|
+
},
|
|
530
|
+
})
|
|
531
|
+
|
|
499
532
|
const FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(
|
|
500
533
|
({ className, gap = 'normal', horizontalLabelWidth, ...props }, ref) => {
|
|
501
|
-
const gapClass = gap === 'compact' ? 'gap-3' : gap === 'loose' ? 'gap-6' : 'gap-4'
|
|
502
534
|
const groupCtxValue = React.useMemo(
|
|
503
535
|
() => ({ horizontalLabelWidth }),
|
|
504
536
|
[horizontalLabelWidth],
|
|
@@ -507,7 +539,7 @@ const FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(
|
|
|
507
539
|
<FieldGroupContext.Provider value={groupCtxValue}>
|
|
508
540
|
<div
|
|
509
541
|
ref={ref}
|
|
510
|
-
className={cn(
|
|
542
|
+
className={cn(fieldGroupVariants({ gap }), className)}
|
|
511
543
|
data-field-group=""
|
|
512
544
|
{...props}
|
|
513
545
|
/>
|
|
@@ -156,6 +156,8 @@ Form validation 的 ARIA / 鍵盤行為(對齊 WCAG 3.3.1 Error Identification +
|
|
|
156
156
|
- **Required indicator**:label 的 `*` 為純視覺、對讀屏隱藏(`aria-hidden="true"`,field.tsx:395);required 語意由內部輸入控件的 `aria-required`(input.tsx:188)承擔,避免讀屏讀出「asterisk」語義不清
|
|
157
157
|
- **Color-only error 警告**:error border 紅色之外必有文字訊息(WCAG 1.4.1 不僅靠顏色)— 由 `<FieldError>` 文字承擔;DS **不**在 input 內放 error 狀態 icon(見 `field-controls.spec.md`「禁止事項」)
|
|
158
158
|
|
|
159
|
+
**空值 / Loading(明文 N/A)**:本 spec 是跨 Field 控件的驗證時機原則(非 UI 元件)——空值渲染與 loading 狀態分屬各控件 spec(SSOT → `field-controls.spec.md`「null / undefined 值」+「Loading state」段)。
|
|
160
|
+
|
|
159
161
|
## 相關
|
|
160
162
|
|
|
161
163
|
- `field.spec.md` — Field wrapper 的 error 視覺 chrome(紅框 + error message slot)
|
|
@@ -166,10 +168,12 @@ Form validation 的 ARIA / 鍵盤行為(對齊 WCAG 3.3.1 Error Identification +
|
|
|
166
168
|
|
|
167
169
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
168
170
|
|
|
171
|
+
- `checkbox.spec.md`
|
|
169
172
|
- `combobox.spec.md`
|
|
170
173
|
- `date-picker.spec.md`
|
|
171
174
|
- `field-controls.spec.md`
|
|
172
175
|
- `field.spec.md`
|
|
173
176
|
- `link-input.spec.md`
|
|
177
|
+
- `select.spec.md`
|
|
174
178
|
- `textarea.spec.md`
|
|
175
179
|
- `time-picker.spec.md`
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* errors store / resetField;驗證「時機」由本 hook own(RHF 的 mode/reValidateMode 不外露)。
|
|
13
13
|
*
|
|
14
14
|
* ── 與 Field 家族的關係(engine-agnostic 分層,field.spec.md「定位」段)──
|
|
15
|
-
* Field 保持純 layout + context(MUI FormControl 派,可用於 cell /
|
|
15
|
+
* Field 保持純 layout + context(MUI FormControl 派,可用於 cell / view / 無引擎場景);
|
|
16
16
|
* 本 hook 住 form 層,錯誤經 consumer 一行 `<Field invalid={!!form.errors.x}>` 接入——
|
|
17
17
|
* Field 層零耦合。這是「A 派的自由 + B 派的 DX」混合位置。
|
|
18
18
|
*
|
|
@@ -91,13 +91,13 @@ export const UsageGuidance: Story = {
|
|
|
91
91
|
<ul className="text-body text-fg-secondary list-disc pl-5 space-y-1">
|
|
92
92
|
<li>Children 同 size — 不混用 sm/md/lg(視覺高度需一致)</li>
|
|
93
93
|
<li>Children 自管 width — fixed selects + flex input 是典型 pattern</li>
|
|
94
|
-
<li
|
|
94
|
+
<li>繼承 Field 尺寸(包進 Field):size 從 Field context 自動繼承,children 不傳 size</li>
|
|
95
95
|
<li>Children scope:Input / NumberInput / Select / Combobox / DatePicker / Button(對齊 Ant 限定 list)</li>
|
|
96
96
|
</ul>
|
|
97
97
|
</section>
|
|
98
98
|
|
|
99
99
|
<section>
|
|
100
|
-
<h2 className="text-body-lg font-bold mb-3"
|
|
100
|
+
<h2 className="text-body-lg font-bold mb-3">繼承 Field 尺寸 — 包進 Field 當 control slot</h2>
|
|
101
101
|
<Field>
|
|
102
102
|
<FieldLabel>聯絡電話</FieldLabel>
|
|
103
103
|
<FieldControlGroup block>
|
|
@@ -109,7 +109,7 @@ export const UsageGuidance: Story = {
|
|
|
109
109
|
</section>
|
|
110
110
|
|
|
111
111
|
<section>
|
|
112
|
-
<h2 className="text-body-lg font-bold mb-3">
|
|
112
|
+
<h2 className="text-body-lg font-bold mb-3">Standalone 逐一設尺寸 — Filter row 場景</h2>
|
|
113
113
|
<FieldControlGroup block>
|
|
114
114
|
<Select className="!w-[140px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
|
|
115
115
|
<Select className="!w-[120px]" options={OPS} value="contains" onChange={() => {}} aria-label="篩選運算子" />
|
|
@@ -89,7 +89,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
89
89
|
|
|
90
90
|
**FCG `size` prop 目前為 no-op**:本元件只負責 border-collapse 接合(border / radius / z-index),不傳遞 size 給 children(無 Context Provider / 無 cloneElement)。Children 的尺寸完全由 child 自己決定 — 以下三種來源:
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
**繼承 Field 尺寸**(包進 Field 當 control slot):children 各自透過 `useFieldContext().size` 讀外層 `<Field>` 的 size(是 Field 的 context,不是 FCG 的)。
|
|
93
93
|
```tsx
|
|
94
94
|
<Field size="lg">
|
|
95
95
|
<FieldLabel>電話</FieldLabel>
|
|
@@ -100,7 +100,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
100
100
|
</Field>
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
**
|
|
103
|
+
**Standalone 逐一設尺寸**:**逐一給每個 child 設 `size`**(設 `<FieldControlGroup size>` 無效)。
|
|
104
104
|
```tsx
|
|
105
105
|
<FieldControlGroup>
|
|
106
106
|
<Select size="md" className="!w-[120px]" options={fields} />
|
|
@@ -108,7 +108,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
108
108
|
</FieldControlGroup>
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
**
|
|
111
|
+
**Popover 鎖 md**(進 Popover,density 鎖 md):children 自然吃 md(Popover density canonical)。
|
|
112
112
|
|
|
113
113
|
## A11y 預設
|
|
114
114
|
|
|
@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils'
|
|
|
5
5
|
import { Avatar } from '@/design-system/components/Avatar/avatar'
|
|
6
6
|
import { Button } from '@/design-system/components/Button/button'
|
|
7
7
|
import { ProgressBar } from '@/design-system/components/ProgressBar/progress-bar'
|
|
8
|
-
import { ItemContent, ItemPrefix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
8
|
+
import { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* FileItem — 檔案顯示 / 上傳進度
|
|
@@ -134,9 +134,8 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
|
|
|
134
134
|
) : null
|
|
135
135
|
|
|
136
136
|
// suffix 對齊 label 第一行(item-anatomy「24px 閾值對齊規則」小 suffix canonical):
|
|
137
|
-
// icons 16 ≤ 24 屬小 suffix,統一
|
|
138
|
-
//
|
|
139
|
-
const suffixAlign = 'h-[1lh]'
|
|
137
|
+
// icons 16 ≤ 24 屬小 suffix,統一 h-[1lh] inline(由 ItemSuffix primitive 提供),
|
|
138
|
+
// 不因 desc wrap 改公式。兩 mode 同公式,跟 item-anatomy 一致。
|
|
140
139
|
|
|
141
140
|
// Status slot 幾何(2026-04-23 user 統一):rich + compact 都用 `var(--field-height-xs)`(24)
|
|
142
141
|
// 容器,裡面 Button xs iconOnly variant="text"(auto data-unbounded)。
|
|
@@ -181,11 +180,14 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
|
|
|
181
180
|
</span>
|
|
182
181
|
) : null
|
|
183
182
|
|
|
183
|
+
// 2026-07-16 dim 39/DA3:消費 ItemSuffix primitive(照 Notice 2026-06-15 已遷模式)。
|
|
184
|
+
// ItemSuffix base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)= 原手刻
|
|
185
|
+
// div 的 flex items-center gap-2 shrink-0 h-[1lh];多出的 ml-auto 為 no-op(左鄰
|
|
186
|
+
// ItemContent 是 flex-1,已把 suffix 推到右緣)→ 像素相等。hoverReveal 預設 false,
|
|
187
|
+
// 不干擾本檔自有的 statusSlot hover-swap 機制(group-hover/row 系)。
|
|
184
188
|
const suffix = (
|
|
185
|
-
<
|
|
189
|
+
<ItemSuffix
|
|
186
190
|
className={cn(
|
|
187
|
-
'flex items-center gap-2 shrink-0',
|
|
188
|
-
suffixAlign,
|
|
189
191
|
// data-unbounded chrome-canonical trick:let Button xs (24) live inside h-[1lh]
|
|
190
192
|
// wrapper(compact ~18.2 / rich ~18.2 scanning)without pushing row height。
|
|
191
193
|
// 視覺/hit area 仍 24,layout footprint 收斂到 1lh。同 overlay-surface
|
|
@@ -201,7 +203,7 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
|
|
|
201
203
|
)}
|
|
202
204
|
{statusSlot}
|
|
203
205
|
{actions}
|
|
204
|
-
</
|
|
206
|
+
</ItemSuffix>
|
|
205
207
|
)
|
|
206
208
|
|
|
207
209
|
// content row — 消費 ItemContent primitive(封裝 label + desc + mt-gap token SSOT)。
|
|
@@ -166,10 +166,10 @@ export const MultipleRule: Story = {
|
|
|
166
166
|
note="每個欄位只收一份檔案時用預設 multiple=false。世界級對照:LinkedIn 履歷上傳、Instagram 大頭貼、Notion page cover——使用者心智模型是「這個位置放一個檔案」。"
|
|
167
167
|
>
|
|
168
168
|
<FileUpload
|
|
169
|
-
accept="
|
|
170
|
-
maxSize={
|
|
171
|
-
title="
|
|
172
|
-
description="
|
|
169
|
+
accept="image/png,image/jpeg"
|
|
170
|
+
maxSize={2_000_000}
|
|
171
|
+
title="上傳大頭貼"
|
|
172
|
+
description="PNG / JPG,最大 2 MB"
|
|
173
173
|
onUpload={noop}
|
|
174
174
|
/>
|
|
175
175
|
<Label>↑ 多拖幾個只取第一個,符合 Ant Upload.Dragger / Polaris DropZone 慣例</Label>
|
|
@@ -212,7 +212,7 @@ export const DivisionOfLaborRule: Story = {
|
|
|
212
212
|
<div>
|
|
213
213
|
<Rule
|
|
214
214
|
title="✅ FileUpload 觸發 + FileItem 顯示"
|
|
215
|
-
note="FileUpload
|
|
215
|
+
note="本範例走 consumer 自組清單:FileUpload 負責拖放偵測 + 觸發選檔,已上傳清單由 consumer 用 FileItem map 渲染。DS 同時支援第二條路——把清單交給 FileUpload 的 files prop 內建渲染(見下一則規則);兩條路都合法,差別只在觸發與清單狀態由誰持有,重點是都不重畫 FileItem 內部視覺。這正是 Notion / Slack / Figma 的附件 flow 做法。"
|
|
216
216
|
>
|
|
217
217
|
<FileUpload
|
|
218
218
|
multiple
|
|
@@ -253,9 +253,9 @@ export const DivisionOfLaborRule: Story = {
|
|
|
253
253
|
|
|
254
254
|
<Rule
|
|
255
255
|
title="❌ 不在 FileUpload 裡自己重刻 FileItem primitives"
|
|
256
|
-
note="
|
|
256
|
+
note="DS 提供兩條清單管理路徑:一是把清單交給 FileUpload 的 files prop,由它內部組合 FileItem 渲染;二是 consumer 自己用 files.map 逐項渲染 FileItem。兩條路都合法,真正禁止的是「自己重畫 FileItem 的內部視覺規格」——狀態圖示、縮圖比例、進度條、列排版都屬 FileItem 的 SSOT。世界級(Material / Ant / Carbon)也都是自己擁有清單、組合自家的 FileItem primitive,不另刻一套。"
|
|
257
257
|
>
|
|
258
|
-
<Label warn>⚠️ 要顯示清單 → 傳 `files` prop(
|
|
258
|
+
<Label warn>⚠️ 要顯示清單 → 傳 `files` prop(內建)或自組 FileItem map;但不要自己重畫 FileItem 內部結構</Label>
|
|
259
259
|
</Rule>
|
|
260
260
|
</div>
|
|
261
261
|
)
|
|
@@ -22,7 +22,7 @@ benchmark:
|
|
|
22
22
|
|
|
23
23
|
## 定位
|
|
24
24
|
|
|
25
|
-
FileUpload 是**拖放 /
|
|
25
|
+
FileUpload 是**拖放 / 點擊上傳區塊**——可拖曳檔案進入或點擊觸發檔案選取浮窗。核心職責是「上傳觸發 + 拖放偵測」;它**不 own 單筆檔案列的視覺規格**(狀態圖示 / 縮圖 / 進度條 / 列排版屬 `FileItem` 的 SSOT),但**可 orchestrate 內建清單**——透過 `files` prop 內部組合 `FileItem` 渲染已上傳清單(dual-path,見「Props 行為」與「禁止事項」);consumer 自組 `FileItem` 清單亦合法。
|
|
26
26
|
|
|
27
27
|
**實作基礎**:自建 — native HTML5 `<input type="file">` + drag-and-drop event(dragenter/dragover/dragleave/drop)+ DOM-level state 切換。無 external primitive,因為 Radix 沒 File Upload 對應 primitive,其他 UI kit 的 file upload 多數也是自建這層。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
28
28
|
|
|
@@ -146,7 +146,8 @@ FileUpload 是**拖放 / 點擊上傳區塊**——可拖曳檔案進入或點
|
|
|
146
146
|
- `role="button"` + `tabIndex=0`(disabled 或 loading 時 `-1`)
|
|
147
147
|
- Enter / Space 鍵觸發檔案選取浮窗(模擬 click)
|
|
148
148
|
- `aria-disabled={true}` 當 disabled
|
|
149
|
-
- `<input type="file">` 以 `className="hidden"`(`display:none`)隱藏 — 移出無障礙樹、不可聚焦;互動由外層 `role="button"` wrapper
|
|
149
|
+
- `<input type="file">` 以 `className="hidden"`(`display:none`)隱藏 — 移出無障礙樹、不可聚焦;互動由外層 `role="button"` wrapper 承載
|
|
150
|
+
- **Accessible name(name-from-content + custom children 例外)**:預設(未傳 children)時 name 來自 wrapper 內 `<Empty>` 的 title + description 文字,非 input 本身。**但 `children` 會整個覆寫預設結構**——若 consumer 傳入**只有 icon / branding 圖像、無可見文字**的 children,`role="button"` wrapper 就失去 accessible name(SR 讀不出用途)。此情況 consumer **必須**在 `<FileUpload>` root 傳 `aria-label`(經 `{...props}` spread 到 wrapper div),或確保 children 內含可見文字。dropzone 與 button 兩 variant 同理
|
|
150
151
|
|
|
151
152
|
---
|
|
152
153
|
|
|
@@ -222,7 +222,11 @@ const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
|
|
|
222
222
|
<div ref={ref} className={cn('w-full', hasFiles && 'flex flex-col')}>
|
|
223
223
|
{variant === 'button' ? (
|
|
224
224
|
// ── button variant:緊湊觸發(form-friendly,省空間),click-only(無拖放區)──
|
|
225
|
-
|
|
225
|
+
// consumer onClick 在此 wrapper div 承載(type = MouseEvent<HTMLDivElement>,對齊 FileUploadProps
|
|
226
|
+
// extends HTMLAttributes<HTMLDivElement>)。Button 觸發開檔會冒泡至此 → 同時觸發 consumer handler。
|
|
227
|
+
// 2026-07-17 修:onClick 於 L128 destructure 後未再 compose,button variant 靜默丟失公開 DOM prop
|
|
228
|
+
// (dropzone variant 經 handleClick 的 onClick?.(e) 正確 compose,button variant 漏接)。
|
|
229
|
+
<div className={className} onClick={onClick} {...props}>
|
|
226
230
|
<input
|
|
227
231
|
ref={inputRef}
|
|
228
232
|
type="file"
|
|
@@ -710,7 +710,7 @@ export const SizeMatrix: Story = {
|
|
|
710
710
|
<tr>
|
|
711
711
|
<Td>Body 垂直 padding</Td>
|
|
712
712
|
<Td mono>pt-[var(--layout-space-tight)] pb-[var(--layout-space-loose)]</Td>
|
|
713
|
-
<Td>非對稱:top tight / bottom loose(
|
|
713
|
+
<Td>非對稱:top tight / bottom loose(bounded region 容器底部留白較寬鬆,內容不貼底、捲動時收尾更透氣)</Td>
|
|
714
714
|
</tr>
|
|
715
715
|
<tr>
|
|
716
716
|
<Td>Section 間 gap</Td>
|
|
@@ -423,7 +423,7 @@ export const ReadOnlyRule: Story = {
|
|
|
423
423
|
|
|
424
424
|
<Rule
|
|
425
425
|
title="✅ readOnly — 其他人上傳的 / 公開公告 / 只讀 link 分享"
|
|
426
|
-
note="readOnly=true 時說明區依 DS Textarea readonly canonical:有值 → 可聚焦選取但不可改的 native readonly textarea;空值 →
|
|
426
|
+
note="readOnly=true 時說明區依 DS Textarea readonly canonical:有值 → 可聚焦選取但不可改的 native readonly textarea;空值 → view div 顯 emptyDisplay(`-`,非 placeholder);onDescriptionChange 不觸發。故 readonly 無 description 時顯示 `-`,不會出現「為這個檔案加上說明...」的編輯誘導 placeholder"
|
|
427
427
|
>
|
|
428
428
|
<Button variant="tertiary" startIcon={FolderOpen} onClick={() => setRoOpen(true)}>
|
|
429
429
|
開啟公司公告(readOnly)
|
|
@@ -140,7 +140,7 @@ FileViewer Toolbar 消費 `patterns/action-bar/action-bar.spec.md` 的**左中
|
|
|
140
140
|
- **zoom** — 同一張檔案的微調(影響範圍:當前 renderer 的 transform state),影響最小
|
|
141
141
|
- **info** — 切換附加資訊面板(影響範圍:layout,不破壞現有內容)
|
|
142
142
|
- **download** — 把內容搬出 viewer(影響範圍:外部檔案系統,不可逆副作用)
|
|
143
|
-
- **close** — 結束整個 viewing session(影響範圍:最大)。註:close = `setOpen(false)`,**僅卸載 `DialogContent` 子樹**;外層 FileViewer 元件恆掛載,shell state(zoom / infoOpen
|
|
143
|
+
- **close** — 結束整個 viewing session(影響範圍:最大)。註:close = `setOpen(false)`,**僅卸載 `DialogContent` 子樹**;外層 FileViewer 元件恆掛載,shell state(zoom / infoOpen 等 `useState`)**保留** —— 重開同一檔沿用上次 zoom / infoOpen,非 fresh state(**index 例外**:uncontrolled 時開啟即重置為 `initialIndex`,`file-viewer.tsx` 開啟 effect)
|
|
144
144
|
|
|
145
145
|
### Dismiss canonical(Close X)
|
|
146
146
|
|
|
@@ -203,7 +203,7 @@ const figmaDesignFile: FileInfo[] = [
|
|
|
203
203
|
mimeType: 'image/png',
|
|
204
204
|
size: 2_840_000,
|
|
205
205
|
description:
|
|
206
|
-
'
|
|
206
|
+
'營運儀表板 v2 最終設計稿 — 凍結欄與列操作已定案,可 zoom 檢視 spacing 與色彩細節。',
|
|
207
207
|
metadata: {
|
|
208
208
|
'設計師': 'Claire Wu',
|
|
209
209
|
'Figma link': 'figma.com/file/xyz/Dashboard',
|
|
@@ -530,13 +530,13 @@ const InfoPanel = React.memo(function InfoPanel({
|
|
|
530
530
|
{/* 檔案資訊 — 用 DS DescriptionList horizontal + divided(Google Drive /
|
|
531
531
|
Notion file info panel 模式):
|
|
532
532
|
- section header 用 FieldLabel 同款 typography 保視覺一致
|
|
533
|
-
- DescriptionList
|
|
533
|
+
- DescriptionList orientation="horizontal" divided 提供 row 下底線
|
|
534
534
|
對齊格線,key 長度不一也易讀
|
|
535
535
|
- 不再手刻 dl/dt/dd — canonical 由 DS primitive own */}
|
|
536
536
|
{/* heading → first-item gap = item → item gap(Gestalt proximity,見 description-list.spec.md) */}
|
|
537
537
|
<div className="flex flex-col gap-[var(--layout-space-tight)]">
|
|
538
538
|
<span className="text-body font-normal text-foreground">{labels.fileInfoHeading}</span>
|
|
539
|
-
<DescriptionList
|
|
539
|
+
<DescriptionList orientation="horizontal" divided>
|
|
540
540
|
<DescriptionItem label="檔名">{file.name}</DescriptionItem>
|
|
541
541
|
<DescriptionItem label="類型">{file.mimeType || '-'}</DescriptionItem>
|
|
542
542
|
{sizeText && (
|
|
@@ -775,6 +775,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
775
775
|
allowDownload = true,
|
|
776
776
|
onDownload,
|
|
777
777
|
labels: labelsOverride,
|
|
778
|
+
className,
|
|
778
779
|
...props
|
|
779
780
|
}, ref) {
|
|
780
781
|
const labels = React.useMemo(
|
|
@@ -89,7 +89,7 @@ export const VisualVariants: Story = {
|
|
|
89
89
|
<div className="flex items-center gap-6">
|
|
90
90
|
<HoverCard>
|
|
91
91
|
<HoverCardTrigger asChild>
|
|
92
|
-
<Button variant="tertiary" size="sm">
|
|
92
|
+
<Button variant="tertiary" size="sm">Ada Chen</Button>
|
|
93
93
|
</HoverCardTrigger>
|
|
94
94
|
<HoverCardContent className="bg-surface-raised border border-border rounded-lg p-4 shadow-[var(--elevation-200)]">
|
|
95
95
|
<div className="w-56">
|
|
@@ -108,11 +108,13 @@ export const VisualVariants: Story = {
|
|
|
108
108
|
<div className="flex items-center gap-6">
|
|
109
109
|
<HoverCard>
|
|
110
110
|
<HoverCardTrigger asChild>
|
|
111
|
-
<Button variant="tertiary" size="sm"
|
|
111
|
+
<Button variant="tertiary" size="sm">+3 位協作者</Button>
|
|
112
112
|
</HoverCardTrigger>
|
|
113
113
|
<HoverCardContent className="bg-tooltip rounded-md px-3 py-2" data-theme="dark">
|
|
114
|
-
<div className="text-caption w-56">
|
|
115
|
-
|
|
114
|
+
<div className="text-caption w-56 flex flex-col gap-1">
|
|
115
|
+
<span>林思妤</span>
|
|
116
|
+
<span>陳柏宇</span>
|
|
117
|
+
<span>Aisha Khan</span>
|
|
116
118
|
</div>
|
|
117
119
|
</HoverCardContent>
|
|
118
120
|
</HoverCard>
|
|
@@ -97,7 +97,12 @@ export const UsageGuidance: Story = {
|
|
|
97
97
|
>
|
|
98
98
|
<HoverCard>
|
|
99
99
|
<HoverCardTrigger asChild>
|
|
100
|
-
<a
|
|
100
|
+
<a
|
|
101
|
+
href="https://help.acme.com/user-manual"
|
|
102
|
+
target="_blank"
|
|
103
|
+
rel="noreferrer"
|
|
104
|
+
className="text-primary underline cursor-pointer inline-flex items-center gap-1"
|
|
105
|
+
>
|
|
101
106
|
使用者手冊 <ExternalLink size={12} />
|
|
102
107
|
</a>
|
|
103
108
|
</HoverCardTrigger>
|
|
@@ -22,7 +22,7 @@ Hover 觸發的**可互動浮層**,基於 Radix HoverCard。
|
|
|
22
22
|
**分類**:Internal primitive——不進 root barrel front-door、不裸用;DS 內由 Avatar(`hoverCard`)/ OverflowIndicator / ProfileCard 場景組合消費;consumer 需要時可經 per-component subpath 包裝並自行確認後使用(`.claude/rules/ui-development.md`「Root barrel front-door 排除」SSOT,internal ≠ 禁用)(frontmatter `isInternal`:root barrel 排除,storybook `Design System/Internal/HoverCard`)。
|
|
23
23
|
|
|
24
24
|
- **是**:hover 顯示可互動內容(按鈕、連結、可選取文字——**滑鼠互動**;鍵盤進不了卡片,見「A11y 預設」)的浮層容器,定位為滑鼠 / focus 使用者的補充閱讀 surface
|
|
25
|
-
- **不是**:Tooltip
|
|
25
|
+
- **不是**:Tooltip(純文字提示、**內容不可互動**;指標可短暫移到浮層屬 WCAG 1.4.13 hoverable grace,非「立即消失」);也不是鍵盤可操作的互動浮層(那是 Popover)
|
|
26
26
|
|
|
27
27
|
**Layout Family**:非上述 family — composite / multi-section(多區塊組合,自 own layout)。
|
|
28
28
|
|
|
@@ -54,7 +54,7 @@ Hover 觸發的**可互動浮層**,基於 Radix HoverCard。
|
|
|
54
54
|
|---|---|---|
|
|
55
55
|
| 觸發 | hover | hover |
|
|
56
56
|
| **內容可互動** | 是——限滑鼠(Radix 設 content 內 tabbable node 為 `tabindex="-1"`,鍵盤不可達,見「A11y 預設」) | 否(純文字) |
|
|
57
|
-
| 停留行為 |
|
|
57
|
+
| 停留行為 | 滑鼠可移到浮層互動不消失 | 滑鼠可短暫移到浮層(WCAG 1.4.13 grace,`tooltip.tsx` 未設 `disableHoverableContent`),但內容不可互動 |
|
|
58
58
|
| 視覺樣式 | 由 consumer 決定(亮色 card 或深色 tooltip 風格皆可) | 統一深色背景 |
|
|
59
59
|
| 典型用例 | ProfileCard、內容預覽、溢出清單 | icon 用途、shortcut、截斷文字補全 |
|
|
60
60
|
|
|
@@ -85,8 +85,10 @@ export const hoverCardMeta = {
|
|
|
85
85
|
},
|
|
86
86
|
states: ['default'],
|
|
87
87
|
tokens: {
|
|
88
|
-
|
|
89
|
-
fg
|
|
88
|
+
// 2026-07-16 deep-audit A.1b:清 Phase-1 佔位 — HoverCard 為純行為 primitive,
|
|
89
|
+
// Content 不套任何 bg/fg(spec「純行為 primitive」段);bg-surface-raised 屬 consumer(ProfileCard)。
|
|
90
|
+
bg: [],
|
|
91
|
+
fg: [],
|
|
90
92
|
ring: [],
|
|
91
93
|
},
|
|
92
94
|
} as const
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import { InlineEdit } from './inline-edit'
|
|
4
|
+
import { Select } from '@/design-system/components/Select/select'
|
|
5
|
+
import { Field, FieldLabel } from '@/design-system/components/Field/field'
|
|
6
|
+
|
|
7
|
+
// 就地編輯無 disabled 態的世界級對照(cite,per M22):
|
|
8
|
+
// MUI X isCellEditable/cellMode https://mui.com/x/react-data-grid/editing/
|
|
9
|
+
// AG Grid editable https://www.ag-grid.com/react-data-grid/cell-editing/
|
|
10
|
+
// Atlassian inline-edit readView/editView https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx
|
|
11
|
+
//
|
|
12
|
+
// InlineEdit 設計規格(anatomy)—— view ↔ edit 二態就地編輯 primitive。
|
|
13
|
+
// Model A(2026-07-16):view 不自帶幾何 cva,委派控件 view mode / fieldViewGeometry helper;read↔edit 零跳。
|
|
14
|
+
const meta: Meta<typeof InlineEdit> = {
|
|
15
|
+
title: 'Design System/Components/InlineEdit/設計規格',
|
|
16
|
+
component: InlineEdit,
|
|
17
|
+
parameters: { layout: 'padded' },
|
|
18
|
+
}
|
|
19
|
+
export default meta
|
|
20
|
+
type Story = StoryObj<typeof InlineEdit>
|
|
21
|
+
|
|
22
|
+
// ── shared UI ────────────────────────────────────────────────────────────────
|
|
23
|
+
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
24
|
+
return (
|
|
25
|
+
<section className="mb-8">
|
|
26
|
+
<h3 className="mb-3 border-b border-divider pb-2 text-body-lg font-semibold text-foreground">{title}</h3>
|
|
27
|
+
{children}
|
|
28
|
+
</section>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
function PropRow({ name, type, def, desc }: { name: string; type: string; def: string; desc: string }) {
|
|
32
|
+
return (
|
|
33
|
+
<tr className="border-b border-divider align-top">
|
|
34
|
+
<td className="py-2 pr-4 font-mono text-caption text-foreground">{name}</td>
|
|
35
|
+
<td className="py-2 pr-4 font-mono text-caption text-fg-secondary">{type}</td>
|
|
36
|
+
<td className="py-2 pr-4 font-mono text-caption text-fg-muted">{def}</td>
|
|
37
|
+
<td className="py-2 text-caption text-fg-secondary">{desc}</td>
|
|
38
|
+
</tr>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
function Stateful({ value: initial, ...props }: Omit<React.ComponentProps<typeof InlineEdit>, 'onCommit'>) {
|
|
42
|
+
const [value, setValue] = React.useState(initial)
|
|
43
|
+
return <InlineEdit {...props} value={value} onCommit={setValue} />
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Overview: Story = {
|
|
47
|
+
name: '元件總覽',
|
|
48
|
+
render: () => (
|
|
49
|
+
<div className="max-w-[720px]">
|
|
50
|
+
<Section title="定位">
|
|
51
|
+
<p className="text-body text-fg-secondary">
|
|
52
|
+
就地編輯 primitive:值平常以 <b>view 態</b>(純內容)呈現,滑鼠移入顯示灰底提示、點擊(或鍵盤 Enter)切成
|
|
53
|
+
<b> edit 態</b>(真正的 Field 控件)。與 DataTable cell 同一份就地編輯語義 —— 只有 <code>view ↔ edit</code> 兩態
|
|
54
|
+
+ <code>editable</code> 判準閘,<b>無 disabled 態</b>(世界級就地編輯共識,cite 見檔頭)。
|
|
55
|
+
</p>
|
|
56
|
+
</Section>
|
|
57
|
+
<Section title="Model A —— view 幾何來源">
|
|
58
|
+
<p className="mb-2 text-body text-fg-secondary">
|
|
59
|
+
InlineEdit <b>不自帶</b> geometry cva(消 M17 重複)。view 幾何來源分兩路,read↔edit 零跳因兩態同一顆控件、只差 chrome:
|
|
60
|
+
</p>
|
|
61
|
+
<ul className="ml-5 list-disc text-body text-fg-secondary">
|
|
62
|
+
<li><b>值-格式化</b>(Select→Tag / 日期 / avatar):<code>renderRead</code> 委派 <code>{'<Control mode="view">'}</code>,
|
|
63
|
+
幾何 + 內部間距(多 tag gap / avatar↔label)皆由控件 view×default 提供。</li>
|
|
64
|
+
<li><b>純值 / 標題</b>(<code>as="h1"</code>):套 <code>fieldViewGeometry(size, multiline)</code> helper
|
|
65
|
+
(= view×default 幾何 SSOT:<code>px-field-px</code> + <code>min-h-field</code> + 多行 <code>py-2</code>)。</li>
|
|
66
|
+
</ul>
|
|
67
|
+
</Section>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const Inspector: Story = {
|
|
73
|
+
name: '屬性一覽',
|
|
74
|
+
render: () => (
|
|
75
|
+
<div className="max-w-[860px]">
|
|
76
|
+
<table className="w-full">
|
|
77
|
+
<thead>
|
|
78
|
+
<tr className="border-b-2 border-border text-left">
|
|
79
|
+
<th className="py-2 pr-4 text-caption font-semibold text-fg-muted">Prop</th>
|
|
80
|
+
<th className="py-2 pr-4 text-caption font-semibold text-fg-muted">型別</th>
|
|
81
|
+
<th className="py-2 pr-4 text-caption font-semibold text-fg-muted">預設</th>
|
|
82
|
+
<th className="py-2 text-caption font-semibold text-fg-muted">說明</th>
|
|
83
|
+
</tr>
|
|
84
|
+
</thead>
|
|
85
|
+
<tbody>
|
|
86
|
+
<PropRow name="value" type="T" def="—" desc="已提交的欄位值(view 顯示 / edit 初值)。泛型:string / number / string[] / PersonValue" />
|
|
87
|
+
<PropRow name="onCommit" type="(next: T) => void" def="—" desc="blur / Enter 且草稿有變更時提交" />
|
|
88
|
+
<PropRow name="editable" type="boolean" def="true" desc="false → view 無 hover 入口、無藍框、不可進 edit、不灰化(鎖定 = 純 view)" />
|
|
89
|
+
<PropRow name="renderRead" type="(v: T) => ReactNode" def="String(value)" desc="格式化 view;鐵律:渲染對應控件 mode='view',不可自刻" />
|
|
90
|
+
<PropRow name="renderEdit" type="(p) => ReactNode" def="Input/Textarea" desc="edit 控件(必 mode='edit');非 string 值必傳" />
|
|
91
|
+
<PropRow name="multiline" type="boolean" def="false" desc="Jira description 類:view 換行 + edit 用 Textarea(py-2 = Textarea)" />
|
|
92
|
+
<PropRow name="as" type="'span'|'h1'..'h6'|'p'|'div'" def="'span'" desc="view 語意標籤;標題保留文件大綱 a11y" />
|
|
93
|
+
<PropRow name="size" type="'sm'|'md'|'lg'" def="sm(fieldCtx cascade)" desc="接 fieldCtx.size;standalone fallback sm。靜態 fieldPreferredSize='sm' 讓外層 Field 自動收 sm" />
|
|
94
|
+
<PropRow name="label" type="string" def="—" desc="view 按鈕 aria-label(「編輯 {label}」)+ 控件 aria-label" />
|
|
95
|
+
</tbody>
|
|
96
|
+
</table>
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const StateBehavior: Story = {
|
|
102
|
+
name: '狀態行為',
|
|
103
|
+
render: () => (
|
|
104
|
+
<div className="grid max-w-[720px] grid-cols-[140px_1fr] gap-x-6 gap-y-5">
|
|
105
|
+
<span className="text-body font-medium text-foreground">view(可編)</span>
|
|
106
|
+
<div><Stateful value="Fix flaky e2e test" label="標題" /></div>
|
|
107
|
+
<span className="text-body font-medium text-foreground">view(鎖定)</span>
|
|
108
|
+
<div><Stateful value="PROJ-482(系統產生)" label="編號" editable={false} /></div>
|
|
109
|
+
<span className="text-body font-medium text-foreground">多行</span>
|
|
110
|
+
<div><Stateful value={'第一行說明\n第二行說明(換行保留上下 padding)'} label="說明" multiline /></div>
|
|
111
|
+
<span className="text-body font-medium text-foreground">格式化(Tag)</span>
|
|
112
|
+
<div>
|
|
113
|
+
<Stateful
|
|
114
|
+
value="in-progress"
|
|
115
|
+
label="狀態"
|
|
116
|
+
renderRead={(v) => (
|
|
117
|
+
<Select mode="view" display="tag" size="sm" value={v as string} options={[
|
|
118
|
+
{ value: 'in-progress', label: '進行中', tagVariant: 'blue' },
|
|
119
|
+
{ value: 'done', label: '完成', tagVariant: 'green' },
|
|
120
|
+
]} />
|
|
121
|
+
)}
|
|
122
|
+
renderEdit={(p) => (
|
|
123
|
+
<Select
|
|
124
|
+
autoFocus
|
|
125
|
+
display="tag"
|
|
126
|
+
size="sm"
|
|
127
|
+
defaultOpen
|
|
128
|
+
value={p.value as string}
|
|
129
|
+
aria-label={p['aria-label']}
|
|
130
|
+
options={[
|
|
131
|
+
{ value: 'in-progress', label: '進行中', tagVariant: 'blue' },
|
|
132
|
+
{ value: 'done', label: '完成', tagVariant: 'green' },
|
|
133
|
+
]}
|
|
134
|
+
onChange={(next) => { p.onChange(next as string); p.commit() }}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
<span className="text-body font-medium text-foreground">空值</span>
|
|
140
|
+
<div><Stateful value="" label="負責人" placeholder="指派負責人…" /></div>
|
|
141
|
+
</div>
|
|
142
|
+
),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// auto-sm 靠 Field 偵測 child 靜態 `fieldPreferredSize`(對齊 detectControlLayout 讀 fieldLayout)——
|
|
146
|
+
// **必 InlineEdit 為 Field 直接子**(包一層 wrapper 元件會遮蔽靜態,Field 偵測不到 → 退回 md)。
|
|
147
|
+
function CascadeDemo() {
|
|
148
|
+
const [title, setTitle] = React.useState('Fix flaky checkout e2e test')
|
|
149
|
+
const [owner, setOwner] = React.useState('')
|
|
150
|
+
return (
|
|
151
|
+
<div className="w-[420px] rounded-lg border border-border bg-surface p-[var(--layout-space-loose)]">
|
|
152
|
+
{/* Field 偵測 InlineEdit 靜態 fieldPreferredSize='sm' → 未指定 size 自動收 sm;
|
|
153
|
+
vertical orientation → InlineEdit -mx 讓 view 值落 FieldLabel 同左緣 */}
|
|
154
|
+
<Field className="mb-4">
|
|
155
|
+
<FieldLabel>任務標題</FieldLabel>
|
|
156
|
+
<InlineEdit value={title} onCommit={setTitle} label="任務標題" />
|
|
157
|
+
</Field>
|
|
158
|
+
<Field>
|
|
159
|
+
<FieldLabel>負責人</FieldLabel>
|
|
160
|
+
<InlineEdit value={owner} onCommit={setOwner} label="負責人" placeholder="指派…" />
|
|
161
|
+
</Field>
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
export const InFieldCascade: Story = {
|
|
166
|
+
name: '包進表單欄位:尺寸自動收斂與左緣對齊',
|
|
167
|
+
render: () => <CascadeDemo />,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const Accessibility: Story = {
|
|
171
|
+
name: '無障礙',
|
|
172
|
+
render: () => (
|
|
173
|
+
<div className="max-w-[640px] text-body text-fg-secondary">
|
|
174
|
+
<ul className="ml-5 list-disc space-y-1">
|
|
175
|
+
<li>view 疊隱形 <code>{'<button aria-label="編輯 {label}">'}</code> —— Tab 可聚焦、Enter/Space 進 edit;
|
|
176
|
+
與 <code>as="h1"</code> 分離(HTML 不允許 heading 巢狀於 button),標題大綱不破壞。</li>
|
|
177
|
+
<li>鍵盤 focus 顯 <code>border-primary</code> 藍框(<code>focus-visible</code>,僅鍵盤);滑鼠點擊不顯藍框。</li>
|
|
178
|
+
<li>結算焦點路徑分流:鍵盤 Enter/Esc → 焦點回 view 按鈕;滑鼠 blur → 純 view 不搶焦。</li>
|
|
179
|
+
<li><code>editable=false</code>:不渲染 Pressable,鍵盤跳過,語意 = 唯讀值(非 disabled)。</li>
|
|
180
|
+
<li>edit 態 = 真正的 Field 控件,繼承其 a11y(Input/Textarea/Select 各自 ARIA)。</li>
|
|
181
|
+
</ul>
|
|
182
|
+
</div>
|
|
183
|
+
),
|
|
184
|
+
}
|