@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,6 +1,7 @@
|
|
|
1
1
|
// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.
|
|
2
|
-
// code-quality-allow: file-size — foundational composite(Tabs + overflow scroll mode + dropdown switcher + inline action slot)在單一 wrapper SSOT 內,拆分會破壞 a11y / focus management chain。當前
|
|
2
|
+
// code-quality-allow: file-size — foundational composite(Tabs + overflow scroll mode + dropdown switcher + inline action slot)在單一 wrapper SSOT 內,拆分會破壞 a11y / focus management chain。當前 ~578 < cap 800。
|
|
3
3
|
import * as React from 'react'
|
|
4
|
+
import { createPortal } from 'react-dom'
|
|
4
5
|
import * as TabsPrimitive from '@radix-ui/react-tabs'
|
|
5
6
|
import { cva } from 'class-variance-authority'
|
|
6
7
|
import type { LucideIcon } from 'lucide-react'
|
|
@@ -55,6 +56,15 @@ interface TabsContextValue {
|
|
|
55
56
|
}
|
|
56
57
|
const TabsContext = React.createContext<TabsContextValue>({ size: 'md' })
|
|
57
58
|
|
|
59
|
+
// ── Inline-action overlay context(2026-07-18 決策1,axe aria-required-children 正規修)──
|
|
60
|
+
// role=tablist 只能擁有 role=tab;per-tab inlineAction(如「更多 ▾」dropdown)若 DOM-在 tablist 內
|
|
61
|
+
// = critical 違規(axe 實測確認)。Radix 要求 trigger 為 TabsList 的 React 子代(roving focus
|
|
62
|
+
// context),故 trigger 必 DOM-在 tablist 內;唯一正規解 = 把 action **portal 出 tablist** 到一個
|
|
63
|
+
// sibling overlay 層,以 trigger 的量測座標定位(涵蓋 3 種 overflow 模式,viewport-relative)。
|
|
64
|
+
// aria-owns 不可行:axe required-children 計 DOM-owned 子代,不因 aria-owns 排除。
|
|
65
|
+
// 代價(user 2026-07-18 接受):鍵盤 Tab 序 = 全部 tab 後才到 action(action 現為 overlay 內獨立 tab stop)。
|
|
66
|
+
const TabsActionOverlayContext = React.createContext<HTMLDivElement | null>(null)
|
|
67
|
+
|
|
58
68
|
// ── Root ──
|
|
59
69
|
// Wrap Radix Tabs 以支援 value/onValueChange 的 context pass-through
|
|
60
70
|
// (menu 模式的 overflow items 需要能 proxy click 觸發同一個 onValueChange)
|
|
@@ -170,27 +180,26 @@ const TabsList = React.forwardRef<
|
|
|
170
180
|
TabsListProps
|
|
171
181
|
>(({ className, size = 'sm', overflow = 'none', children, ...props }, ref) => {
|
|
172
182
|
const tabsSizeContext = React.useMemo(() => ({ size }), [size])
|
|
183
|
+
// Overlay 層(決策1):per-tab inlineAction 由 TabsTrigger portal 到此,DOM-在 tablist 外,
|
|
184
|
+
// 以量測座標定位在對應 tab 之上。pointer-events-none(action span 自帶 auto);z 疊在 tab 之上。
|
|
185
|
+
const [overlayEl, setOverlayEl] = React.useState<HTMLDivElement | null>(null)
|
|
186
|
+
|
|
187
|
+
let listNode: React.ReactNode
|
|
173
188
|
if (overflow === 'scroll') {
|
|
174
|
-
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
</ScrollTabsList>
|
|
179
|
-
</TabsContext.Provider>
|
|
189
|
+
listNode = (
|
|
190
|
+
<ScrollTabsList ref={ref} className={className} {...props}>
|
|
191
|
+
{children}
|
|
192
|
+
</ScrollTabsList>
|
|
180
193
|
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
{children}
|
|
187
|
-
</MenuTabsList>
|
|
188
|
-
</TabsContext.Provider>
|
|
194
|
+
} else if (overflow === 'menu') {
|
|
195
|
+
listNode = (
|
|
196
|
+
<MenuTabsList ref={ref} className={className} {...props}>
|
|
197
|
+
{children}
|
|
198
|
+
</MenuTabsList>
|
|
189
199
|
)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
<TabsContext.Provider value={tabsSizeContext}>
|
|
200
|
+
} else {
|
|
201
|
+
// none(預設)
|
|
202
|
+
listNode = (
|
|
194
203
|
<TabsPrimitive.List
|
|
195
204
|
ref={ref}
|
|
196
205
|
className={cn(TABS_LIST_BASE, 'w-full', className)}
|
|
@@ -198,6 +207,18 @@ const TabsList = React.forwardRef<
|
|
|
198
207
|
>
|
|
199
208
|
{children}
|
|
200
209
|
</TabsPrimitive.List>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<TabsContext.Provider value={tabsSizeContext}>
|
|
215
|
+
<TabsActionOverlayContext.Provider value={overlayEl}>
|
|
216
|
+
<div className="relative">
|
|
217
|
+
{listNode}
|
|
218
|
+
{/* inline-action overlay:sibling of tablist(非其 DOM 子代)→ 修 axe aria-required-children */}
|
|
219
|
+
<div ref={setOverlayEl} className="pointer-events-none absolute inset-0 z-10" />
|
|
220
|
+
</div>
|
|
221
|
+
</TabsActionOverlayContext.Provider>
|
|
201
222
|
</TabsContext.Provider>
|
|
202
223
|
)
|
|
203
224
|
})
|
|
@@ -449,7 +470,10 @@ const tabsTriggerVariants = cva(
|
|
|
449
470
|
)
|
|
450
471
|
|
|
451
472
|
interface TabsTriggerProps
|
|
452
|
-
|
|
473
|
+
// asChild Omit(2026-07-18 決策2):Tab trigger 固定渲染 [startIcon?, <span>{children}</span>,
|
|
474
|
+
// suffix?] 多節點陣列 → Radix Slot 的 React.Children.only 恆 crash(每個 tab 都會)。
|
|
475
|
+
// children(label)保留合法;TabsRoot / TabsList 為真容器不收窄。
|
|
476
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>, 'asChild'> {
|
|
453
477
|
/** 左側 icon(LucideIcon) */
|
|
454
478
|
startIcon?: LucideIcon
|
|
455
479
|
/** 右側 badge(通常是計數指示器) */
|
|
@@ -481,23 +505,56 @@ const TabsTrigger = React.forwardRef<
|
|
|
481
505
|
TabsTriggerProps
|
|
482
506
|
>(({ className, startIcon: StartIcon, badge, endIcon: EndIcon, inlineAction, children, ...props }, ref) => {
|
|
483
507
|
const { size } = React.useContext(TabsContext)
|
|
508
|
+
const overlayEl = React.useContext(TabsActionOverlayContext)
|
|
484
509
|
// 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)
|
|
485
510
|
const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']
|
|
486
511
|
const hasSuffix = badge != null || EndIcon !== undefined
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
// paddingRight 預留 action 空間 — icon 寬 + 原 gap(有 badge/endIcon 時 4px = suffix 內
|
|
493
|
-
// gap-1;僅 action 時 8px = slot 間 gap-2),幾何與巢狀版一致。click 天然分流(action 不在
|
|
494
|
-
// trigger 子樹,不冒泡),原 stopPropagation 五連發不再需要。
|
|
512
|
+
// inlineAction 幾何:trigger 以 paddingRight 預留 action 空間 — icon 寬 + 原 gap(有 badge/endIcon
|
|
513
|
+
// 時 4px = suffix 內 gap-1;僅 action 時 8px = slot 間 gap-2)。2026-07-05 修 button-巢-button
|
|
514
|
+
// (sibling 佈局);2026-07-18 決策1 再進化:action 不再是 tablist DOM 內的 sibling(axe
|
|
515
|
+
// aria-required-children critical),改 portal 到 TabsList 的 overlay 層(tablist 外),以量測座標
|
|
516
|
+
// 定位在 trigger 右緣。trigger 仍在 tablist 內(Radix roving focus 要求)。
|
|
495
517
|
const hasAction = inlineAction != null
|
|
496
518
|
const actionPaddingRight = hasAction ? iconSize + (hasSuffix ? 4 : 8) : undefined
|
|
497
519
|
|
|
520
|
+
// 量測 trigger 位置(viewport-relative,涵蓋 none/scroll/menu 三模式),同步 overlay 內 action 座標。
|
|
521
|
+
const triggerElRef = React.useRef<HTMLButtonElement | null>(null)
|
|
522
|
+
const setTriggerRef = React.useCallback(
|
|
523
|
+
(el: HTMLButtonElement | null) => {
|
|
524
|
+
triggerElRef.current = el
|
|
525
|
+
if (typeof ref === 'function') ref(el)
|
|
526
|
+
else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = el
|
|
527
|
+
},
|
|
528
|
+
[ref],
|
|
529
|
+
)
|
|
530
|
+
const [actionPos, setActionPos] = React.useState<{ left: number; top: number; height: number } | null>(null)
|
|
531
|
+
|
|
532
|
+
React.useLayoutEffect(() => {
|
|
533
|
+
if (!hasAction || !overlayEl) return
|
|
534
|
+
const el = triggerElRef.current
|
|
535
|
+
if (!el) return
|
|
536
|
+
const compute = () => {
|
|
537
|
+
const t = triggerElRef.current?.getBoundingClientRect()
|
|
538
|
+
const o = overlayEl.getBoundingClientRect()
|
|
539
|
+
if (!t) return
|
|
540
|
+
setActionPos({ left: t.right - o.left, top: t.top - o.top, height: t.height })
|
|
541
|
+
}
|
|
542
|
+
compute()
|
|
543
|
+
const ro = new ResizeObserver(compute)
|
|
544
|
+
ro.observe(el)
|
|
545
|
+
ro.observe(overlayEl)
|
|
546
|
+
window.addEventListener('resize', compute)
|
|
547
|
+
window.addEventListener('scroll', compute, true) // capture:含 scroll-mode 捲動容器內部捲動
|
|
548
|
+
return () => {
|
|
549
|
+
ro.disconnect()
|
|
550
|
+
window.removeEventListener('resize', compute)
|
|
551
|
+
window.removeEventListener('scroll', compute, true)
|
|
552
|
+
}
|
|
553
|
+
}, [hasAction, overlayEl])
|
|
554
|
+
|
|
498
555
|
const trigger = (
|
|
499
556
|
<TabsPrimitive.Trigger
|
|
500
|
-
ref={
|
|
557
|
+
ref={setTriggerRef}
|
|
501
558
|
className={cn(tabsTriggerVariants({ size }), className)}
|
|
502
559
|
style={hasAction ? { paddingRight: actionPaddingRight } : undefined}
|
|
503
560
|
{...props}
|
|
@@ -516,14 +573,22 @@ const TabsTrigger = React.forwardRef<
|
|
|
516
573
|
if (!hasAction) return trigger
|
|
517
574
|
|
|
518
575
|
return (
|
|
519
|
-
|
|
520
|
-
// Radix roving focus 用 collection context 收 trigger,不要求 trigger 是 List 直接子層。
|
|
521
|
-
<span className="relative inline-flex">
|
|
576
|
+
<>
|
|
522
577
|
{trigger}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
578
|
+
{overlayEl &&
|
|
579
|
+
actionPos &&
|
|
580
|
+
createPortal(
|
|
581
|
+
// action portal 到 overlay(tablist 外);left=trigger.right + `-translate-x-full` → action
|
|
582
|
+
// 右緣對齊 trigger 右緣;top/height + items-center → 垂直置中(同原 right-0 + top-1/2 幾何)。
|
|
583
|
+
<span
|
|
584
|
+
className="pointer-events-auto absolute inline-flex -translate-x-full items-center"
|
|
585
|
+
style={{ left: actionPos.left, top: actionPos.top, height: actionPos.height }}
|
|
586
|
+
>
|
|
587
|
+
{inlineAction}
|
|
588
|
+
</span>,
|
|
589
|
+
overlayEl,
|
|
590
|
+
)}
|
|
591
|
+
</>
|
|
527
592
|
)
|
|
528
593
|
})
|
|
529
594
|
TabsTrigger.displayName = 'TabsTrigger'
|
|
@@ -31,7 +31,7 @@ type SizeKey = 'sm' | 'md' | 'lg'
|
|
|
31
31
|
type ColorSpec = { bg: string; text: string; border: string }
|
|
32
32
|
type SolidSpec = 'subtle' | 'solid'
|
|
33
33
|
|
|
34
|
-
// neutral + 全 12 色相(順序對齊 SSOT CATEGORICAL_HUES
|
|
34
|
+
// neutral + 全 12 色相(順序對齊 SSOT CATEGORICAL_HUES = categorical 指派序;與 primitives.css 檔內宣告序本就不同,見 categorical-color.ts 注)
|
|
35
35
|
const VARIANTS: VariantKey[] = ['neutral', ...CATEGORICAL_HUES]
|
|
36
36
|
const SIZES: SizeKey[] = ['sm', 'md', 'lg']
|
|
37
37
|
|
|
@@ -326,7 +326,7 @@ const InspectorInner = () => {
|
|
|
326
326
|
onRemove={withDismiss ? () => {} : undefined}
|
|
327
327
|
solid={solid}
|
|
328
328
|
>
|
|
329
|
-
|
|
329
|
+
設計
|
|
330
330
|
</Tag>
|
|
331
331
|
</div>
|
|
332
332
|
|
|
@@ -237,7 +237,7 @@ export const IconRule: Story = {
|
|
|
237
237
|
<div>
|
|
238
238
|
<Rule
|
|
239
239
|
title="icon — 類別圖示(tag label 的視覺強化)"
|
|
240
|
-
note="用 LucideIcon,Tag
|
|
240
|
+
note="用 LucideIcon,Tag 一律固定 16px、不隨 Tag 尺寸縮放——讓所有 Tag 的 icon 前綴維持相同節奏,一整排並排掃視時對齊不跳動。icon 顏色繼承 Tag 文字色(有色 variant 就跟該色),而不是自成一色:避免 icon 變成 Tag 內第二個視覺焦點,搶走 label 的主體性。"
|
|
241
241
|
>
|
|
242
242
|
<div className="flex items-center gap-2 flex-wrap">
|
|
243
243
|
<Tag color="blue" icon={TagIcon}>Feature</Tag>
|
|
@@ -247,13 +247,13 @@ export const IconRule: Story = {
|
|
|
247
247
|
|
|
248
248
|
<Rule
|
|
249
249
|
title="avatar — 人員 / 組織類 Tag 前綴"
|
|
250
|
-
note="多選 Combobox 的已選成員用 avatar + name
|
|
250
|
+
note="多選 Combobox 的已選成員用 avatar + name 組合。avatar 只提供視覺辨識(一眼看到「是個人」),真正承載身份的是名字;兩者並用才能避免相似頭像、或圖片載入失敗時的誤認——所以 avatar Tag 一定要保留可見姓名,不能只放頭像。人員 / 組織標籤同理。"
|
|
251
251
|
>
|
|
252
252
|
<div className="flex items-center gap-2 flex-wrap">
|
|
253
|
-
<Tag color="neutral" avatar={<img src="https://i.pravatar.cc/40?img=1" className="w-4 h-4 rounded-full" />}>
|
|
253
|
+
<Tag color="neutral" avatar={<img src="https://i.pravatar.cc/40?img=1" alt="" className="w-4 h-4 rounded-full" />}>
|
|
254
254
|
Ada Chen
|
|
255
255
|
</Tag>
|
|
256
|
-
<Tag color="neutral" avatar={<img src="https://i.pravatar.cc/40?img=2" className="w-4 h-4 rounded-full" />}>
|
|
256
|
+
<Tag color="neutral" avatar={<img src="https://i.pravatar.cc/40?img=2" alt="" className="w-4 h-4 rounded-full" />}>
|
|
257
257
|
Alice
|
|
258
258
|
</Tag>
|
|
259
259
|
</div>
|
|
@@ -220,6 +220,8 @@ Inline Action 的其他規則(尺寸、hover 背景 pattern)不變。
|
|
|
220
220
|
| `icon` | `LucideIcon` | 左側 icon,Tag 統一 16px。與 avatar 互斥 |
|
|
221
221
|
| `avatar` | `ReactNode` | 左側 avatar。與 icon 互斥 |
|
|
222
222
|
| `onRemove` | `() => void` | 可移除——Tag 自動渲染 remove 按鈕並控制尺寸與互動樣式(從集合移除 item) |
|
|
223
|
+
| `removeAriaLabel` | `string` | remove 按鈕 aria-label 的目標名(對齊 FileUpload `removeAriaLabel` 同場景 + `<動作>AriaLabel` 命名慣例)。**children 為非字串 ReactNode 時必傳**,否則 SR 只讀得出泛稱「移除」、辨識不出移除哪個 Tag;children 為字串時預設取 children |
|
|
224
|
+
| ~~`dismissLabel`~~ | `string` | **@deprecated 2026-07-17 → `removeAriaLabel`**(dismiss 保留給 Alert/Notice/Toast「通知被忽略」;過渡一版仍可用) |
|
|
223
225
|
| `solid` | `boolean` | 深底配對前景模式(step-6 背景;yellow/amber/orange/lime 用深字,其餘白字),預設 false |
|
|
224
226
|
| `unbounded` | `boolean` | 預設 false = 寬度 cap 160px;true = 寬度交給 parent constrain(cell-as-input narrow cell 場景),詳「邊界案例」 |
|
|
225
227
|
|
|
@@ -271,7 +273,7 @@ Tag 是**純顯示 indicator**(Family 3 indicator variant,見「與 Button 的
|
|
|
271
273
|
Tag 是**純視覺 indicator**(非互動 control,互動版本是 Chip),預設 ARIA 行為:
|
|
272
274
|
|
|
273
275
|
- **靜態 Tag**(無 `onRemove`):render 為 `<div data-tag-root>`(無 role、`inline-flex`,SR 等價於無語意 generic container,視覺上為 inline-level 跟隨 inline flow)。若 Tag 表達狀態語意(「進行中」「已逾期」),consumer 應在父 region 用 `aria-label` 或視覺 prefix(icon)讓 SR 理解 context
|
|
274
|
-
- **Removable Tag**(`onRemove` 存在):inline X close 走 `<ItemInlineActionButton icon={X} size="md" aria-label="移除 {
|
|
276
|
+
- **Removable Tag**(`onRemove` 存在):inline X close 走 `<ItemInlineActionButton icon={X} size="md" aria-label="移除 {name}" />`(item-anatomy SSOT 的原生 `<button>`,非 `Button` 元件)— 對齊 inline-action canonical(`patterns/element-anatomy/inline-action.spec.md`)。**名稱來源**:`removeAriaLabel` prop 優先(舊名 `dismissLabel` deprecated 仍支援),否則取字串 children;**若 children 是非字串 ReactNode(如 avatar + 排版節點)且未傳 `removeAriaLabel`,aria-label 會退化成泛稱「移除」**——多個 Tag 並排時 SR 無法區分移除哪一個,故非字串 children 必傳 `removeAriaLabel`
|
|
275
277
|
- **Keyboard**:靜態 Tag 不取得 focus(無互動);**例外:截斷時條件性 `tabIndex=0`**(2026-07-06 拍板)——全文 tooltip 需鍵盤可達,APG Tooltip pattern 原文「popup 在元素收到鍵盤焦點或 hover 時顯示」= trigger 可 focus 是 W3C 官方前提;Radix Tooltip focus-open 自動生效,未截斷不入 tab 序。dismissible 的 X 走原生 button 鍵盤(Tab → focus → Enter/Space → dismiss)
|
|
276
278
|
- **Color-only 語意警告**:Tag 用色相區分狀態時必有文字 label 或 prefix icon(色盲 user 友好);對齊 WCAG 1.4.1「不僅靠顏色傳達」
|
|
277
279
|
- **驗證**:Storybook a11y addon panel 0 critical violation;WCAG AA contrast ≥ 4.5:1(text)/ 3:1(icon)
|
|
@@ -289,4 +291,5 @@ Tag 是**純視覺 indicator**(非互動 control,互動版本是 Chip),預設 AR
|
|
|
289
291
|
|
|
290
292
|
- `avatar.spec.md`
|
|
291
293
|
- `badge.spec.md`
|
|
294
|
+
- `inline-action.spec.md`
|
|
292
295
|
- `overflow-indicator.spec.md`
|
|
@@ -68,7 +68,9 @@ export interface TagProps
|
|
|
68
68
|
avatar?: React.ReactNode
|
|
69
69
|
/** 可移除——Tag 自動渲染 remove 按鈕並控制尺寸與互動樣式(從集合移除 item) */
|
|
70
70
|
onRemove?: () => void
|
|
71
|
-
/** remove 按鈕的 aria-label 目標名(a11y)。children 為非字串 ReactNode 時建議傳,否則 SR 讀不出移除哪個 tag。預設取 string children。 */
|
|
71
|
+
/** remove 按鈕的 aria-label 目標名(a11y)。children 為非字串 ReactNode 時建議傳,否則 SR 讀不出移除哪個 tag。預設取 string children。對齊 FileUpload `removeAriaLabel`(同「移除集合項目」場景 + DS `<動作>AriaLabel` 命名慣例)。 */
|
|
72
|
+
removeAriaLabel?: string
|
|
73
|
+
/** @deprecated 2026-07-17 改名為 `removeAriaLabel`(對齊 FileUpload + onRemove callback 語意;dismiss 保留給 Alert/Notice/Toast「通知被忽略」)。舊名過渡一版後移除。 */
|
|
72
74
|
dismissLabel?: string
|
|
73
75
|
/** 深底模式(step-6 背景 + on-emphasis 配對前景;亮色 hue yellow/amber/orange/lime 用深字 --on-emphasis-dark,green 白字例外) */
|
|
74
76
|
solid?: boolean
|
|
@@ -123,7 +125,7 @@ function TagDismiss({ onRemove, label, solid, color }: { onRemove: () => void; l
|
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
function TagInner(
|
|
126
|
-
{ className, color, size, icon: Icon, avatar, onRemove, dismissLabel, solid, unbounded = false, children, style, ...props }: TagProps,
|
|
128
|
+
{ className, color, size, icon: Icon, avatar, onRemove, removeAriaLabel, dismissLabel, solid, unbounded = false, children, style, ...props }: TagProps,
|
|
127
129
|
forwardedRef: React.ForwardedRef<HTMLDivElement>,
|
|
128
130
|
) {
|
|
129
131
|
const solidClass = solid ? SOLID_CLASSES[color ?? 'neutral'] : undefined
|
|
@@ -202,7 +204,7 @@ function TagInner(
|
|
|
202
204
|
{Icon && <Icon size={16} aria-hidden />}
|
|
203
205
|
{avatar && <span className="shrink-0 w-4 h-4 rounded-full overflow-hidden inline-grid place-content-center [&>*]:w-full [&>*]:h-full">{avatar}</span>}
|
|
204
206
|
<span data-tag-text="" className="px-1 truncate min-w-0">{children}</span>
|
|
205
|
-
{onRemove && <TagDismiss onRemove={onRemove} label={dismissLabel
|
|
207
|
+
{onRemove && <TagDismiss onRemove={onRemove} label={removeAriaLabel ?? dismissLabel ?? label} solid={solid} color={color ?? 'neutral'} />}
|
|
206
208
|
</div>
|
|
207
209
|
)
|
|
208
210
|
|
|
@@ -34,7 +34,7 @@ export const Overview: Story = {
|
|
|
34
34
|
<thead><tr><Th>Prop</Th><Th>Type</Th><Th>Default</Th><Th>說明</Th></tr></thead>
|
|
35
35
|
<tbody>
|
|
36
36
|
{[
|
|
37
|
-
['mode', "'edit' | '
|
|
37
|
+
['mode', "'edit' | 'view' | 'readonly' | 'disabled'", "'edit'", 'Field mode(view 渲染純文字 div;readonly 用底色 + padding 標示閱讀區)'],
|
|
38
38
|
['size', "'sm' | 'md' | 'lg'", "'md'", 'sm/md text-body,lg text-body-lg'],
|
|
39
39
|
['rows', 'number', '3', '預設可見行數'],
|
|
40
40
|
['placeholder', 'string', '—', '空值提示'],
|
|
@@ -82,7 +82,7 @@ export const Inspector: Story = {
|
|
|
82
82
|
defaultValue: '',
|
|
83
83
|
},
|
|
84
84
|
argTypes: {
|
|
85
|
-
mode: { control: 'radio', options: ['edit', '
|
|
85
|
+
mode: { control: 'radio', options: ['edit', 'view', 'readonly', 'disabled'] },
|
|
86
86
|
size: { control: 'radio', options: ['sm', 'md', 'lg'] },
|
|
87
87
|
rows: { control: { type: 'number', min: 1, max: 20 } },
|
|
88
88
|
error: { control: 'boolean', description: 'error 視覺(border-error + aria-invalid)' },
|
|
@@ -126,10 +126,10 @@ export const ModeMatrix: Story = {
|
|
|
126
126
|
/>
|
|
127
127
|
</div>
|
|
128
128
|
<div>
|
|
129
|
-
<H3>
|
|
129
|
+
<H3>view — 純展示(渲 div 取代 textarea,whitespace-pre-wrap 保留多行)</H3>
|
|
130
130
|
<Desc>對齊 Carbon read-only / Cloudscape display mode。常見於檢視已提交的留言 / 評論。</Desc>
|
|
131
131
|
<Textarea
|
|
132
|
-
mode="
|
|
132
|
+
mode="view"
|
|
133
133
|
rows={3}
|
|
134
134
|
value="我覺得這個專案非常有潛力,建議加強 onboarding 的引導流程..."
|
|
135
135
|
/>
|
|
@@ -201,11 +201,11 @@ export const ColorMatrix: Story = {
|
|
|
201
201
|
<Td><TokenCell token="--fg-muted" display="fg-muted" /></Td>
|
|
202
202
|
</tr>
|
|
203
203
|
<tr>
|
|
204
|
-
<Td mono>
|
|
204
|
+
<Td mono>view</Td>
|
|
205
205
|
<Td>透明(zero-chrome,host context 提供邊界)</Td>
|
|
206
206
|
<Td>透明(無邊框)</Td>
|
|
207
207
|
<Td><TokenCell token="--foreground" display="foreground" /></Td>
|
|
208
|
-
<Td>—(
|
|
208
|
+
<Td>—(view 無 placeholder;空值顯半形 -(hyphen)foreground)</Td>
|
|
209
209
|
</tr>
|
|
210
210
|
<tr>
|
|
211
211
|
<Td mono>readonly</Td>
|
|
@@ -27,7 +27,7 @@ benchmark:
|
|
|
27
27
|
|
|
28
28
|
Textarea 是**多行文字**的輸入與顯示元件——Input 的多行版本。格式化邏輯為 identity(value → value)。
|
|
29
29
|
|
|
30
|
-
**Layout Family**:Family 4(Field control)— multi-line variant。**繼承** Family 4 的 token(字體 / padding / border / state 色彩),但**結構簡化為單一 native `<textarea>`**(edit 態裸 `<textarea>`、
|
|
30
|
+
**Layout Family**:Family 4(Field control)— multi-line variant。**繼承** Family 4 的 token(字體 / padding / border / state 色彩),但**結構簡化為單一 native `<textarea>`**(edit 態裸 `<textarea>`、view 態裸 `<div>`)——無 fieldWrapper composite、無 startIcon / content / endAction 多 slot anatomy(見下方「與 Input 的差異」表與「禁止事項」)。高度可隨 rows / resize-y 擴展、不受 `--field-height-*` 約束。
|
|
31
31
|
|
|
32
32
|
**實作基礎**:native `<textarea>` + 橋接 DS token,無 external primitive base。shadcn 同類做法。
|
|
33
33
|
|
|
@@ -71,7 +71,7 @@ Textarea 是**多行文字**的輸入與顯示元件——Input 的多行版本
|
|
|
71
71
|
|
|
72
72
|
- **`rows` prop**:預設 `3`,控制初始可見行數
|
|
73
73
|
- **`resize-y`**:使用者可手動拖拉下邊緣垂直 resize
|
|
74
|
-
- **`variant="naked"` 例外**:cell-as-input substrate(DataTable cell
|
|
74
|
+
- **`variant="naked"` 例外**:cell-as-input substrate(DataTable cell)naked 僅 edit/view 兩 mode(readonly/disabled×naked 死格 2026-07-16 移除),一律 `!resize-none` 覆蓋 `resize-y`,並以 `!h-full` 撐滿 cell——cell 內手動 resize 會破壞表格佈局(`textarea.tsx` naked compoundVariants,intentional divergence)
|
|
75
75
|
- **`min-h-*` className**:消費者可透過 Tailwind utility 覆寫最小高度
|
|
76
76
|
- **禁止 `resize: horizontal` 或 `both`**:水平 resize 破壞 form 佈局
|
|
77
77
|
|
|
@@ -104,12 +104,12 @@ native `<textarea>` 自帶 `value` / `defaultValue` / `onChange` triplet — Tex
|
|
|
104
104
|
|
|
105
105
|
### Readonly 特例
|
|
106
106
|
|
|
107
|
-
不同於 Input 的 readonly(同高度、緊湊底色),`variant="default"` 的 Textarea readonly **保留 padding,並填上 `bg-readonly` 底色標示閱讀區**——多行內容需要明確的閱讀區域邊界訊號,沒有這塊填色區會無法與周圍純文字內容區分。`variant="naked"`(cell-as-input substrate)
|
|
107
|
+
不同於 Input 的 readonly(同高度、緊湊底色),`variant="default"` 的 Textarea readonly **保留 padding,並填上 `bg-readonly` 底色標示閱讀區**——多行內容需要明確的閱讀區域邊界訊號,沒有這塊填色區會無法與周圍純文字內容區分。`variant="naked"`(cell-as-input substrate)view 態透明且 `!px-0 !py-0`——邊界訊號由 host context(table cell)提供(`textarea.tsx` compoundVariants,intentional divergence;readonly/disabled×naked 死格 2026-07-16 移除;`bare` variant 2026-07-09 退役)。
|
|
108
108
|
|
|
109
109
|
### 邊界案例
|
|
110
110
|
|
|
111
|
-
- **空值**:edit 態顯示 placeholder;
|
|
112
|
-
- **極長文字**:edit 態高度固定於 rows / resize 結果,內容超出時 native 內部捲動;
|
|
111
|
+
- **空值**:edit 態顯示 placeholder;view / readonly 態 value 為空(`null` / `''`)時渲染半形 `-`(`EMPTY_DISPLAY`)+ `text-foreground`(disabled → fg-disabled)
|
|
112
|
+
- **極長文字**:edit 態高度固定於 rows / resize 結果,內容超出時 native 內部捲動;view 態 `<div>` 隨內容增高(`whitespace-pre-wrap break-words`),無截斷
|
|
113
113
|
- **resize 上限**:`resize-y` 預設無上限;需限制時 consumer 傳 `max-h-*` className(native resize 受 max-height 約束)
|
|
114
114
|
|
|
115
115
|
---
|
|
@@ -11,12 +11,12 @@ import { useControllable } from '@/design-system/hooks/use-controllable'
|
|
|
11
11
|
* Textarea — 多行文字輸入
|
|
12
12
|
*
|
|
13
13
|
* ── 定位 ────────────────────────────────────────────────────────────────
|
|
14
|
-
* 多行版本的 Input,edit /
|
|
14
|
+
* 多行版本的 Input,edit / view / readonly / disabled 四態與 Input 邏輯一致(Phase B1 2026-05-05)。
|
|
15
15
|
* 不同於 Input:
|
|
16
16
|
* - 沒有固定 field-height(高度由 rows 或 min-h 決定)
|
|
17
17
|
* - 沒有 startIcon / endAction(textarea 慣例不放 icon)
|
|
18
18
|
* - readonly 呈現保留邊框與 padding,只改底色,讓多行文字有合理閱讀區
|
|
19
|
-
* -
|
|
19
|
+
* - view 渲染 <div> + white-space:pre-wrap 保留多行文本
|
|
20
20
|
*
|
|
21
21
|
* ── Padding 規則 ───────────────────────────────────────────────────────
|
|
22
22
|
* 多行內容必須有上下內距才能閱讀舒適。不沿用 Input 的 items-center,
|
|
@@ -48,7 +48,7 @@ const textareaVariants = cva(
|
|
|
48
48
|
variants: {
|
|
49
49
|
mode: {
|
|
50
50
|
edit: '',
|
|
51
|
-
|
|
51
|
+
view: '',
|
|
52
52
|
readonly: '',
|
|
53
53
|
disabled: '',
|
|
54
54
|
},
|
|
@@ -80,11 +80,13 @@ const textareaVariants = cva(
|
|
|
80
80
|
},
|
|
81
81
|
{ mode: 'edit', variant: 'default', error: false, className: 'focus-within:!border-primary focus-within:hover:!border-primary' },
|
|
82
82
|
{
|
|
83
|
-
|
|
83
|
+
// 2026-07-16 round16 Model A(user GO,推翻 2026-05-13 Path Ⅰ 的 `!px-0 !py-0`):
|
|
84
|
+
// Textarea view×default = **edit 幾何減 chrome** — 保留 base `px-[var(--field-px)] py-2`(= edit 內距),
|
|
85
|
+
// 只拔 border/bg。多行 read↔edit 零跳(view 與 edit 同一顆 Textarea、py-2 天生一致)。跟 Field wrapper
|
|
86
|
+
// view×default 同 SSOT(field-wrapper.tsx)。
|
|
87
|
+
mode: 'view',
|
|
84
88
|
variant: 'default',
|
|
85
|
-
|
|
86
|
-
// (跟 Input 同 SSOT,per field-controls.spec.md (d))
|
|
87
|
-
className: 'bg-transparent border border-transparent !px-0 !py-0',
|
|
89
|
+
className: 'bg-transparent border border-transparent',
|
|
88
90
|
},
|
|
89
91
|
{
|
|
90
92
|
mode: 'readonly',
|
|
@@ -118,7 +120,7 @@ const textareaVariants = cva(
|
|
|
118
120
|
'!px-[var(--table-cell-px)] !py-[var(--table-cell-py)]',
|
|
119
121
|
'border border-border',
|
|
120
122
|
'hover:border-border-hover',
|
|
121
|
-
// textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟
|
|
123
|
+
// textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟 view
|
|
122
124
|
// `<div>` text-body line-height: 1.5(21px @ 14px)不一致 → cell 進 edit 後 height
|
|
123
125
|
// shift。顯式 leading 對齊 div 行為。
|
|
124
126
|
'!leading-[1.5]',
|
|
@@ -126,16 +128,14 @@ const textareaVariants = cva(
|
|
|
126
128
|
},
|
|
127
129
|
{ mode: 'edit', variant: 'naked', error: false, className: 'focus-visible:!border-primary focus-visible:hover:!border-primary' },
|
|
128
130
|
// 2026-05-13 Q1 R4 verify(per codex Q1 verdict 補 Textarea nuance):
|
|
129
|
-
// Textarea
|
|
131
|
+
// Textarea view×naked 用 `!h-full`,**不**對齊 Field wrapper 的 `!h-auto`。
|
|
130
132
|
// Why divergence:textarea 是 native form element 帶 intrinsic rows-based height,且 cell 內
|
|
131
133
|
// multi-line text 需要撐滿 cell 而非依 line-height intrinsic。`!h-full` 讓 textarea 填滿 cell
|
|
132
134
|
// 高度,文字 anchored to cell.top + cell padding(同視覺結果 Field wrapper autoRow !h-auto)。
|
|
133
135
|
// 此 divergence intentional + documented;非 SSOT violation。
|
|
134
|
-
{ mode: '
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
// color.spec.md:729 逃生艙 rule)。Textarea 已用具體 `text-fg-disabled` token,不需要 wrapper opacity。
|
|
138
|
-
{ mode: 'disabled', variant: 'naked', className: 'bg-transparent !rounded-none cursor-not-allowed text-fg-disabled !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]' },
|
|
136
|
+
{ mode: 'view', variant: 'naked', className: 'bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]' },
|
|
137
|
+
// 2026-07-16 round16:`readonly×naked` / `disabled×naked` 死格移除(鏡射 field-wrapper.tsx;
|
|
138
|
+
// DataTable cell 廢 disabled 態 + readonly 從不入 naked,全庫 0 消費)。
|
|
139
139
|
],
|
|
140
140
|
defaultVariants: {
|
|
141
141
|
mode: 'edit',
|
|
@@ -149,7 +149,7 @@ const textareaVariants = cva(
|
|
|
149
149
|
export interface TextareaProps
|
|
150
150
|
extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>,
|
|
151
151
|
Omit<VariantProps<typeof textareaVariants>, 'mode' | 'variant'> {
|
|
152
|
-
/** Field
|
|
152
|
+
/** Field view mode */
|
|
153
153
|
mode?: FieldMode
|
|
154
154
|
/**
|
|
155
155
|
* Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。透傳自 FieldContext.variant,per-prop override。
|
|
@@ -193,22 +193,22 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
193
193
|
const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })
|
|
194
194
|
const emptyDisplay = useFieldEmptyDisplay()
|
|
195
195
|
const isEditable = resolvedMode === 'edit'
|
|
196
|
-
const
|
|
196
|
+
const isView = resolvedMode === 'view'
|
|
197
197
|
const inputId = idProp ?? fieldCtx?.id
|
|
198
198
|
const ariaDescribedBy = ariaDescribedByProp ?? fieldCtx?.descriptionId
|
|
199
199
|
const ariaErrorMessage = ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)
|
|
200
200
|
|
|
201
|
-
// ──
|
|
201
|
+
// ── view mode(+ readonly 空值)純展示,渲染 <div> 取代 <textarea>(white-space:pre-wrap 保留多行) ──
|
|
202
202
|
// 對齊 Carbon read-only / Cloudscape display-mode
|
|
203
|
-
// 2026-07-08 user 拍板:readonly 空值也走
|
|
203
|
+
// 2026-07-08 user 拍板:readonly 空值也走 view-div 顯 '-'(readonly **有值** 仍走下方
|
|
204
204
|
// native <textarea readOnly> 保留選取/複製語意 — field-controls.spec.md「null / undefined 值」)。
|
|
205
|
-
// 2026-07-13 fix:空值判斷需認得 uncontrolled defaultValue — readonly /
|
|
205
|
+
// 2026-07-13 fix:空值判斷需認得 uncontrolled defaultValue — readonly / view 也要顯示內容,
|
|
206
206
|
// 不可誤判為空值渲 '-'(原為靜態 value ?? defaultValue)。
|
|
207
207
|
// 2026-07-14 R2(dual-model consensus,同 Input input.tsx):resolved value 改走 useControllable
|
|
208
208
|
// 內部 SSOT(Radix idiom)。原靜態版有 typing-stale gap:uncontrolled 打字後外部切
|
|
209
|
-
//
|
|
209
|
+
// view / readonly 仍顯示 stale defaultValue。uncontrolled 時 native textarea 由
|
|
210
210
|
// value={resolved} 內部驅動(defaultValue 只作初始值,不落 DOM attribute),同時修
|
|
211
|
-
// remount-stale(切
|
|
211
|
+
// remount-stale(切 view 再回 edit 從 defaultValue attribute 重掛)+ form.reset() stale
|
|
212
212
|
// (HTML 標準 reset 不發 input event,下方 reset bridge 修)。controlled(傳 value)時
|
|
213
213
|
// useControllable 純 passthrough,行為與原本完全一致。consumer onChange 不接進 hook
|
|
214
214
|
// (保留 native event signature),於 <textarea> onChange 另行轉發。
|
|
@@ -218,10 +218,10 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
218
218
|
defaultValue: defaultValue ?? '',
|
|
219
219
|
})
|
|
220
220
|
const displayValue = resolved != null && resolved !== '' ? String(resolved) : null
|
|
221
|
-
const showDisplaySpan =
|
|
221
|
+
const showDisplaySpan = isView || (resolvedMode === 'readonly' && displayValue == null)
|
|
222
222
|
|
|
223
223
|
// form.reset() bridge(uncontrolled only):reset 恢復 defaultValue 但不發 input event →
|
|
224
|
-
// 手動把 resolved 歸位 defaultValue。keyed on showDisplaySpan:
|
|
224
|
+
// 手動把 resolved 歸位 defaultValue。keyed on showDisplaySpan:view 分支不掛 native
|
|
225
225
|
// textarea,回 edit 重掛時 effect 重跑補掛 listener。
|
|
226
226
|
const innerRef = React.useRef<HTMLTextAreaElement | null>(null)
|
|
227
227
|
React.useEffect(() => {
|
|
@@ -240,7 +240,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
240
240
|
}, [ref])
|
|
241
241
|
|
|
242
242
|
if (showDisplaySpan) {
|
|
243
|
-
const spanMode =
|
|
243
|
+
const spanMode = isView ? 'view' : 'readonly'
|
|
244
244
|
return (
|
|
245
245
|
<div
|
|
246
246
|
id={inputId}
|
|
@@ -28,7 +28,7 @@ type Story = StoryObj<typeof TimePicker>
|
|
|
28
28
|
|
|
29
29
|
export const Overview: Story = {
|
|
30
30
|
name: '元件總覽',
|
|
31
|
-
render: () => <TimePicker value="09:00" onChange={() => {}} />,
|
|
31
|
+
render: () => <TimePicker value="09:00" onChange={() => {}} aria-label="時間選擇器總覽" />,
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -105,9 +105,9 @@ export const ColorMatrix: Story = {
|
|
|
105
105
|
</table>
|
|
106
106
|
</div>
|
|
107
107
|
<div className="flex gap-3 mt-3">
|
|
108
|
-
<TimePicker value="09:00" onChange={() => {}} />
|
|
109
|
-
<TimePicker value="09:00" onChange={() => {}} error />
|
|
110
|
-
<TimePicker value="09:00" onChange={() => {}} disabled />
|
|
108
|
+
<TimePicker value="09:00" onChange={() => {}} aria-label="時間欄位配色(預設)" />
|
|
109
|
+
<TimePicker value="09:00" onChange={() => {}} error aria-label="時間欄位配色(錯誤狀態)" />
|
|
110
|
+
<TimePicker value="09:00" onChange={() => {}} disabled aria-label="時間欄位配色(停用)" />
|
|
111
111
|
</div>
|
|
112
112
|
</div>
|
|
113
113
|
|
|
@@ -169,15 +169,15 @@ export const SizeMatrix: Story = {
|
|
|
169
169
|
<div className="flex flex-col gap-3">
|
|
170
170
|
<div className="flex items-center gap-3">
|
|
171
171
|
<span className="w-12 text-caption text-fg-muted">sm</span>
|
|
172
|
-
<TimePicker size="sm" value="09:00" onChange={() => {}} />
|
|
172
|
+
<TimePicker size="sm" value="09:00" onChange={() => {}} aria-label="小尺寸時間選擇器" />
|
|
173
173
|
</div>
|
|
174
174
|
<div className="flex items-center gap-3">
|
|
175
175
|
<span className="w-12 text-caption text-fg-muted">md ★default</span>
|
|
176
|
-
<TimePicker size="md" value="09:00" onChange={() => {}} />
|
|
176
|
+
<TimePicker size="md" value="09:00" onChange={() => {}} aria-label="中尺寸時間選擇器" />
|
|
177
177
|
</div>
|
|
178
178
|
<div className="flex items-center gap-3">
|
|
179
179
|
<span className="w-12 text-caption text-fg-muted">lg</span>
|
|
180
|
-
<TimePicker size="lg" value="09:00" onChange={() => {}} />
|
|
180
|
+
<TimePicker size="lg" value="09:00" onChange={() => {}} aria-label="大尺寸時間選擇器" />
|
|
181
181
|
</div>
|
|
182
182
|
</div>
|
|
183
183
|
),
|
|
@@ -205,15 +205,15 @@ export const PrecisionMatrix: Story = {
|
|
|
205
205
|
<div className="flex flex-col gap-3 w-80">
|
|
206
206
|
<div>
|
|
207
207
|
<div className="text-caption text-fg-muted mb-1">兩欄(showSeconds=false,預設)</div>
|
|
208
|
-
<TimePicker value="09:00" onChange={() => {}} />
|
|
208
|
+
<TimePicker value="09:00" onChange={() => {}} aria-label="時分兩欄時間選擇器" />
|
|
209
209
|
</div>
|
|
210
210
|
<div>
|
|
211
211
|
<div className="text-caption text-fg-muted mb-1">三欄(showSeconds=true)</div>
|
|
212
|
-
<TimePicker value="09:00:00" onChange={() => {}} showSeconds />
|
|
212
|
+
<TimePicker value="09:00:00" onChange={() => {}} showSeconds aria-label="時分秒三欄時間選擇器" />
|
|
213
213
|
</div>
|
|
214
214
|
<div>
|
|
215
215
|
<div className="text-caption text-fg-muted mb-1">minuteStep=15(會議常用)</div>
|
|
216
|
-
<TimePicker value="09:00" onChange={() => {}} minuteStep={15} />
|
|
216
|
+
<TimePicker value="09:00" onChange={() => {}} minuteStep={15} aria-label="15 分鐘間隔時間選擇器" />
|
|
217
217
|
</div>
|
|
218
218
|
<div>
|
|
219
219
|
<div className="text-caption text-fg-muted mb-1">disabledTime:0-5 點不可選</div>
|
|
@@ -221,6 +221,7 @@ export const PrecisionMatrix: Story = {
|
|
|
221
221
|
value="09:00"
|
|
222
222
|
onChange={() => {}}
|
|
223
223
|
disabledTime={() => ({ disabledHours: [0, 1, 2, 3, 4, 5] })}
|
|
224
|
+
aria-label="限制可選時段的時間選擇器"
|
|
224
225
|
/>
|
|
225
226
|
</div>
|
|
226
227
|
</div>
|
|
@@ -246,7 +247,7 @@ export const Inspector: Story = {
|
|
|
246
247
|
},
|
|
247
248
|
render: (args) => {
|
|
248
249
|
const [v, setV] = React.useState(args.value ?? '')
|
|
249
|
-
return <TimePicker {...args} value={v} onChange={setV} />
|
|
250
|
+
return <TimePicker {...args} value={v} onChange={setV} aria-label="時間選擇器檢閱器" />
|
|
250
251
|
},
|
|
251
252
|
}
|
|
252
253
|
|