@qijenchen/design-system 0.1.0-beta.61 → 0.1.0-beta.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +6 -6
- package/dist/components/Accordion/accordion.d.ts +1 -1
- package/dist/components/Accordion/accordion.js +1 -1
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/Alert/alert.d.ts +2 -2
- package/dist/components/Alert/alert.js +4 -3
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +4 -3
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +1 -1
- package/dist/components/Avatar/avatar.js +2 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Badge/badge.d.ts +1 -1
- package/dist/components/Badge/badge.js +2 -1
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +5 -5
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +4 -3
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +1 -1
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +80 -69
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +5 -3
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Checkbox/checkbox-group.d.ts +2 -1
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox-group.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +9 -3
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +8 -3
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +2 -2
- package/dist/components/Chip/chip.js +3 -2
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/CircularProgress/circular-progress.d.ts +1 -1
- package/dist/components/CircularProgress/circular-progress.js +2 -1
- package/dist/components/CircularProgress/circular-progress.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +4 -4
- package/dist/components/Coachmark/coachmark.js +2 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +17 -13
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +4 -0
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +8 -4
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +2 -2
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +6 -4
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.js +1 -1
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- 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 +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +0 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Empty/empty.d.ts +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.js +1 -1
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +1 -0
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +8 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +7 -3
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +25 -14
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts +5 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +2 -1
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts +6 -2
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +5 -1
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +2 -1
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +4 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -2
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +6 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/Popover/popover.d.ts +1 -1
- package/dist/components/Popover/popover.js +2 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +9 -4
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +6 -0
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +4 -0
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +14 -4
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +4 -0
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +6 -3
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/selection-item.d.ts +5 -1
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
- package/dist/components/SelectionControl/selection-item.js +3 -1
- package/dist/components/SelectionControl/selection-item.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +6 -5
- 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.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +3 -2
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/skeleton.d.ts +1 -1
- package/dist/components/Skeleton/skeleton.js +2 -1
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +7 -3
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +9 -6
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +5 -5
- package/dist/components/Switch/switch.js +3 -3
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.js +1 -1
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Textarea/textarea.js +1 -1
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-columns.js +1 -1
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +2 -1
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts +1 -1
- package/dist/components/Tooltip/tooltip.js +2 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +4 -4
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +3 -3
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +3 -3
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.d.ts +3 -2
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +3 -2
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/ds-canonical/hooks/_log-fire.sh +3 -1
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +21 -1
- package/ds-canonical/hooks/check_audit_sample_escape.sh +6 -3
- package/ds-canonical/hooks/check_chrome_header_avatar_canonical.sh +8 -0
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +321 -0
- package/ds-canonical/hooks/check_datatable_invariants.sh +75 -12
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +15 -1
- package/ds-canonical/hooks/check_field_family_invariants.sh +12 -2
- package/ds-canonical/hooks/check_plugin_fork_health.sh +137 -0
- package/ds-canonical/hooks/check_propose_discipline.sh +147 -0
- package/ds-canonical/hooks/check_solo_workflow.sh +11 -1
- package/ds-canonical/hooks/check_story_invariants.sh +63 -9
- package/ds-canonical/hooks/check_storybook_addon_packaging.sh +151 -0
- package/ds-canonical/hooks/check_tailwind_wildcard_in_docs.sh +8 -2
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +25 -4
- package/ds-canonical/hooks/lib/_token_hygiene.sh +9 -1
- package/ds-canonical/hooks/session_start_governance_check.sh +7 -2
- package/ds-canonical/hooks/stop_self_audit.sh +2 -2
- package/ds-canonical/hooks/tests/test_check_addon_subdir_ship.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +12 -0
- package/ds-canonical/hooks/tests/test_check_consumer_app_story_title.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +6 -4
- package/ds-canonical/hooks/tests/test_check_consumer_no_ds_catalog.sh +7 -4
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +6 -4
- package/ds-canonical/hooks/tests/test_check_data_table_size_num_to_meta_width.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_fork_user_plugin_install.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +9 -0
- package/ds-canonical/hooks/tests/test_check_propose_cite_required.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +10 -0
- package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +3 -2
- package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +10 -0
- package/ds-canonical/hooks/tests/test_check_storybook_addon_preset_cjs.sh +3 -2
- package/ds-canonical/references/build-ui-canonicals.md +2 -2
- package/ds-canonical/references/composition-fidelity.md +3 -3
- package/ds-canonical/references/naming-conventions.md +1 -0
- package/ds-canonical/references/ssot-consultation.md +1 -1
- package/ds-canonical/references/ssot-index.md +7 -7
- package/ds-canonical/rules/meta-patterns.md +5 -5
- package/ds-canonical/rules/self-verify.md +1 -1
- package/ds-canonical/rules/story-rules.md +2 -0
- package/ds-canonical/rules/ui-development.md +1 -1
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +11 -2
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +15 -15
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +4 -3
- package/ds-story-manifest.json +15 -21
- package/llms-full.txt +9 -5
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
- package/src/components/Accordion/accordion.spec.md +22 -5
- package/src/components/Accordion/accordion.tsx +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
- package/src/components/Alert/alert.principles.stories.tsx +5 -5
- package/src/components/Alert/alert.spec.md +16 -11
- package/src/components/Alert/alert.stories.tsx +5 -5
- package/src/components/Alert/alert.tsx +2 -2
- package/src/components/AppShell/app-shell.principles.stories.tsx +68 -21
- package/src/components/AppShell/app-shell.spec.md +17 -24
- package/src/components/AppShell/app-shell.tsx +4 -3
- package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +3 -3
- package/src/components/AspectRatio/aspect-ratio.spec.md +15 -1
- package/src/components/Avatar/avatar.anatomy.stories.tsx +1 -1
- package/src/components/Avatar/avatar.principles.stories.tsx +8 -8
- package/src/components/Avatar/avatar.spec.md +25 -54
- package/src/components/Avatar/avatar.tsx +2 -2
- package/src/components/Badge/badge.principles.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +22 -13
- package/src/components/Badge/badge.tsx +1 -1
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +5 -2
- package/src/components/Breadcrumb/breadcrumb.spec.md +25 -25
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +7 -32
- package/src/components/Breadcrumb/breadcrumb.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +2 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +20 -6
- package/src/components/BulkActionBar/bulk-action-bar.tsx +2 -2
- package/src/components/Button/button.anatomy.stories.tsx +2 -2
- package/src/components/Button/button.spec.md +19 -17
- package/src/components/Button/button.stories.tsx +1 -1
- package/src/components/Button/button.tsx +10 -9
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Calendar/calendar.spec.md +37 -17
- package/src/components/Calendar/calendar.tsx +26 -22
- package/src/components/Carousel/carousel.anatomy.stories.tsx +6 -6
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.spec.md +23 -3
- package/src/components/Carousel/carousel.tsx +19 -4
- package/src/components/Chart/chart.anatomy.stories.tsx +1 -1
- package/src/components/Chart/chart.spec.md +20 -1
- package/src/components/Checkbox/checkbox-group.tsx +2 -1
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +4 -4
- package/src/components/Checkbox/checkbox.principles.stories.tsx +2 -2
- package/src/components/Checkbox/checkbox.spec.md +35 -15
- package/src/components/Checkbox/checkbox.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.tsx +14 -3
- package/src/components/Chip/chip.anatomy.stories.tsx +5 -5
- package/src/components/Chip/chip.principles.stories.tsx +9 -3
- package/src/components/Chip/chip.spec.md +8 -9
- package/src/components/Chip/chip.tsx +2 -2
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +6 -6
- package/src/components/CircularProgress/circular-progress.spec.md +10 -11
- package/src/components/CircularProgress/circular-progress.stories.tsx +36 -43
- package/src/components/CircularProgress/circular-progress.tsx +1 -1
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.principles.stories.tsx +14 -29
- package/src/components/Coachmark/coachmark.spec.md +21 -13
- package/src/components/Coachmark/coachmark.tsx +4 -4
- package/src/components/Combobox/combobox.anatomy.stories.tsx +28 -28
- package/src/components/Combobox/combobox.principles.stories.tsx +1 -1
- package/src/components/Combobox/combobox.spec.md +25 -7
- package/src/components/Combobox/combobox.tsx +12 -3
- package/src/components/Command/command.anatomy.stories.tsx +1 -1
- package/src/components/Command/command.principles.stories.tsx +8 -8
- package/src/components/Command/command.spec.md +18 -8
- package/src/components/Command/command.tsx +8 -3
- package/src/components/DataTable/data-table-sort-manager.tsx +1 -1
- package/src/components/DataTable/data-table.anatomy.stories.tsx +9 -7
- package/src/components/DataTable/data-table.principles.stories.tsx +3 -3
- package/src/components/DataTable/data-table.spec.md +15 -11
- package/src/components/DataTable/data-table.stories.tsx +58 -13
- package/src/components/DataTable/data-table.tsx +13 -0
- package/src/components/DataTable/filter-operators.spec.md +19 -27
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +8 -8
- package/src/components/DateGrid/date-grid.principles.stories.tsx +7 -7
- package/src/components/DateGrid/date-grid.spec.md +17 -14
- package/src/components/DateGrid/date-grid.tsx +8 -4
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +11 -15
- package/src/components/DatePicker/date-picker.principles.stories.tsx +8 -8
- package/src/components/DatePicker/date-picker.spec.md +40 -36
- package/src/components/DatePicker/date-picker.stories.tsx +23 -53
- package/src/components/DatePicker/date-picker.tsx +12 -6
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.principles.stories.tsx +4 -18
- package/src/components/DescriptionList/description-list.spec.md +9 -3
- package/src/components/DescriptionList/description-list.stories.tsx +2 -18
- package/src/components/DescriptionList/description-list.tsx +1 -1
- package/src/components/Dialog/dialog.principles.stories.tsx +1 -1
- package/src/components/Dialog/dialog.spec.md +26 -11
- package/src/components/Dialog/dialog.stories.tsx +159 -184
- package/src/components/Dialog/dialog.tsx +2 -1
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +2 -2
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +16 -17
- package/src/components/DropdownMenu/dropdown-menu.spec.md +10 -15
- package/src/components/DropdownMenu/dropdown-menu.stories.tsx +13 -15
- package/src/components/DropdownMenu/dropdown-menu.tsx +12 -2
- package/src/components/Empty/empty.spec.md +19 -18
- package/src/components/Empty/empty.tsx +1 -1
- package/src/components/Field/field-controls.spec.md +52 -41
- package/src/components/Field/field-wrapper.tsx +2 -2
- package/src/components/Field/field.anatomy.stories.tsx +6 -5
- package/src/components/Field/field.principles.stories.tsx +8 -7
- package/src/components/Field/field.spec.md +42 -20
- package/src/components/Field/field.stories.tsx +9 -7
- package/src/components/Field/field.tsx +6 -3
- package/src/components/Field/form-validation.spec.md +20 -19
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +16 -15
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +5 -1
- package/src/components/FieldControlGroup/field-control-group.spec.md +17 -1
- package/src/components/FieldControlGroup/field-control-group.stories.tsx +9 -9
- package/src/components/FieldControlGroup/field-control-group.tsx +4 -3
- package/src/components/FileItem/file-item.anatomy.stories.tsx +7 -17
- package/src/components/FileItem/file-item.principles.stories.tsx +7 -7
- package/src/components/FileItem/file-item.spec.md +24 -24
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +4 -2
- package/src/components/FileUpload/file-upload.principles.stories.tsx +10 -12
- package/src/components/FileUpload/file-upload.spec.md +37 -8
- package/src/components/FileUpload/file-upload.tsx +5 -3
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +16 -11
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +4 -4
- package/src/components/FileViewer/file-viewer.spec.md +7 -19
- package/src/components/FileViewer/file-viewer.stories.tsx +3 -3
- package/src/components/FileViewer/file-viewer.tsx +42 -16
- package/src/components/HoverCard/hover-card.spec.md +20 -10
- package/src/components/HoverCard/hover-card.stories.tsx +1 -1
- package/src/components/HoverCard/hover-card.tsx +12 -1
- package/src/components/Input/input.anatomy.stories.tsx +4 -4
- package/src/components/Input/input.principles.stories.tsx +4 -31
- package/src/components/Input/input.spec.md +16 -4
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.spec.md +14 -1
- package/src/components/Menu/menu-item.principles.stories.tsx +5 -5
- package/src/components/Menu/menu-item.spec.md +24 -12
- package/src/components/Menu/menu-item.tsx +7 -3
- package/src/components/Notice/notice.principles.stories.tsx +7 -7
- package/src/components/Notice/notice.spec.md +7 -5
- package/src/components/Notice/notice.tsx +5 -1
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.principles.stories.tsx +13 -35
- package/src/components/NumberInput/number-input.spec.md +19 -0
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +4 -3
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +2 -5
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +9 -1
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -42
- package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -3
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +11 -2
- package/src/components/PeoplePicker/people-picker.spec.md +26 -12
- package/src/components/PeoplePicker/people-picker.stories.tsx +14 -15
- package/src/components/PeoplePicker/people-picker.tsx +12 -4
- package/src/components/Popover/popover.spec.md +20 -11
- package/src/components/Popover/popover.tsx +2 -2
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +29 -25
- package/src/components/ProfileCard/profile-card.spec.md +30 -10
- package/src/components/ProfileCard/profile-card.tsx +12 -7
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +7 -5
- package/src/components/ProgressBar/progress-bar.spec.md +9 -1
- package/src/components/ProgressBar/progress-bar.stories.tsx +1 -1
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +6 -3
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +6 -13
- package/src/components/RadioGroup/radio-group.spec.md +7 -1
- package/src/components/RadioGroup/radio-group.tsx +10 -0
- package/src/components/Rating/rating.anatomy.stories.tsx +1 -0
- package/src/components/Rating/rating.principles.stories.tsx +9 -6
- package/src/components/Rating/rating.spec.md +13 -7
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +44 -24
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +51 -57
- package/src/components/ScrollArea/scroll-area.spec.md +5 -7
- package/src/components/ScrollArea/scroll-area.stories.tsx +110 -60
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.spec.md +15 -14
- package/src/components/SegmentedControl/segmented-control.stories.tsx +1 -1
- package/src/components/Select/select.anatomy.stories.tsx +12 -11
- package/src/components/Select/select.principles.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +34 -8
- package/src/components/Select/select.stories.tsx +2 -2
- package/src/components/Select/select.tsx +18 -3
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +2 -2
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +5 -5
- package/src/components/SelectMenu/select-menu.spec.md +19 -12
- package/src/components/SelectMenu/select-menu.stories.tsx +34 -27
- package/src/components/SelectMenu/select-menu.tsx +14 -3
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.spec.md +13 -2
- package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.tsx +8 -2
- package/src/components/Separator/separator.principles.stories.tsx +2 -2
- package/src/components/Separator/separator.spec.md +11 -5
- package/src/components/Sheet/sheet.anatomy.stories.tsx +7 -6
- package/src/components/Sheet/sheet.principles.stories.tsx +18 -38
- package/src/components/Sheet/sheet.spec.md +9 -11
- package/src/components/Sheet/sheet.stories.tsx +2 -2
- package/src/components/Sheet/sheet.tsx +6 -5
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +42 -33
- package/src/components/Sidebar/sidebar.principles.stories.tsx +4 -4
- package/src/components/Sidebar/sidebar.spec.md +22 -17
- package/src/components/Sidebar/sidebar.stories.tsx +16 -0
- package/src/components/Sidebar/sidebar.tsx +15 -7
- package/src/components/Skeleton/skeleton.anatomy.stories.tsx +2 -2
- package/src/components/Skeleton/skeleton.principles.stories.tsx +20 -7
- package/src/components/Skeleton/skeleton.spec.md +6 -4
- package/src/components/Skeleton/skeleton.stories.tsx +34 -26
- package/src/components/Skeleton/skeleton.tsx +1 -1
- package/src/components/Slider/slider.principles.stories.tsx +3 -3
- package/src/components/Slider/slider.spec.md +16 -12
- package/src/components/Slider/slider.stories.tsx +2 -18
- package/src/components/Slider/slider.tsx +6 -2
- package/src/components/Steps/steps.anatomy.stories.tsx +3 -3
- package/src/components/Steps/steps.principles.stories.tsx +9 -12
- package/src/components/Steps/steps.spec.md +31 -25
- package/src/components/Steps/steps.stories.tsx +5 -40
- package/src/components/Steps/steps.tsx +17 -12
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Switch/switch.spec.md +13 -0
- package/src/components/Switch/switch.tsx +3 -3
- package/src/components/Tabs/tabs.anatomy.stories.tsx +4 -4
- package/src/components/Tabs/tabs.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.spec.md +13 -6
- package/src/components/Tabs/tabs.stories.tsx +3 -3
- package/src/components/Tabs/tabs.tsx +2 -2
- package/src/components/Tag/tag.anatomy.stories.tsx +15 -15
- package/src/components/Tag/tag.principles.stories.tsx +1 -1
- package/src/components/Tag/tag.spec.md +25 -5
- package/src/components/Textarea/textarea.anatomy.stories.tsx +3 -3
- package/src/components/Textarea/textarea.principles.stories.tsx +7 -7
- package/src/components/Textarea/textarea.spec.md +15 -3
- package/src/components/Textarea/textarea.tsx +1 -1
- package/src/components/TimePicker/time-columns.tsx +1 -1
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +8 -2
- package/src/components/TimePicker/time-picker.spec.md +23 -3
- package/src/components/TimePicker/time-picker.tsx +5 -3
- package/src/components/Toast/toast.anatomy.stories.tsx +1 -1
- package/src/components/Toast/toast.spec.md +7 -4
- package/src/components/Toast/toast.stories.tsx +12 -6
- package/src/components/Toast/toast.tsx +1 -1
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +1 -1
- package/src/components/Tooltip/tooltip.spec.md +11 -8
- package/src/components/Tooltip/tooltip.tsx +1 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +25 -9
- package/src/components/TreeView/tree-view.principles.stories.tsx +5 -5
- package/src/components/TreeView/tree-view.spec.md +11 -9
- package/src/components/TreeView/tree-view.stories.tsx +5 -3
- package/src/components/TreeView/tree-view.tsx +8 -5
- package/src/patterns/action-bar/action-bar.spec.md +11 -4
- package/src/patterns/element-anatomy/element-anatomy.spec.md +8 -6
- package/src/patterns/element-anatomy/inline-action.spec.md +18 -18
- package/src/patterns/element-anatomy/item-anatomy.spec.md +47 -42
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -6
- package/src/patterns/element-anatomy/item-anatomy.tsx +3 -3
- package/src/patterns/header-canonical/chrome-header.tsx +3 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +24 -11
- package/src/patterns/header-canonical/header-canonical.stories.tsx +9 -9
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +22 -12
- package/src/patterns/overlay-surface/overlay-surface.spec.md +43 -39
- package/src/patterns/overlay-surface/overlay-surface.tsx +3 -2
- package/src/patterns/resize-handle/resize-handle.spec.md +10 -1
- package/src/patterns/resize-handle/resize-handle.stories.tsx +7 -7
- package/src/tokens/README.md +5 -2
- package/src/tokens/color/color.spec.md +17 -10
- package/src/tokens/color/semantic.css +2 -0
- package/src/tokens/density/density.spec.md +4 -14
- package/src/tokens/elevation/elevation.spec.md +7 -8
- package/src/tokens/layoutSpace/layoutSpace.spec.md +20 -27
- package/src/tokens/motion/motion.spec.md +15 -7
- package/src/tokens/opacity/opacity.spec.md +15 -26
- package/src/tokens/orphan-tokens.spec.md +14 -3
- package/src/tokens/radius/radius.spec.md +13 -15
- package/src/tokens/token-system.spec.md +10 -13
- package/src/tokens/typography/typography.spec.md +8 -12
- package/src/tokens/uiSize/uiSize.spec.md +28 -16
- /package/ds-canonical/hooks/{check_addon_subdir_ship.sh → retired/2026-06-11-prune-merge/check_addon_subdir_ship.sh} +0 -0
- /package/ds-canonical/hooks/{check_consumer_app_story_title.sh → retired/2026-06-11-prune-merge/check_consumer_app_story_title.sh} +0 -0
- /package/ds-canonical/hooks/{check_consumer_ds_primitive_misuse.sh → retired/2026-06-11-prune-merge/check_consumer_ds_primitive_misuse.sh} +0 -0
- /package/ds-canonical/hooks/{check_consumer_no_ds_catalog.sh → retired/2026-06-11-prune-merge/check_consumer_no_ds_catalog.sh} +0 -0
- /package/ds-canonical/hooks/{check_consumer_story_baseline.sh → retired/2026-06-11-prune-merge/check_consumer_story_baseline.sh} +0 -0
- /package/ds-canonical/hooks/{check_data_table_size_num_to_meta_width.sh → retired/2026-06-11-prune-merge/check_data_table_size_num_to_meta_width.sh} +0 -0
- /package/ds-canonical/hooks/{check_fork_user_plugin_install.sh → retired/2026-06-11-prune-merge/check_fork_user_plugin_install.sh} +0 -0
- /package/ds-canonical/hooks/{check_plugin_freshness.sh → retired/2026-06-11-prune-merge/check_plugin_freshness.sh} +0 -0
- /package/ds-canonical/hooks/{check_propose_cite_required.sh → retired/2026-06-11-prune-merge/check_propose_cite_required.sh} +0 -0
- /package/ds-canonical/hooks/{check_propose_plain_chinese.sh → retired/2026-06-11-prune-merge/check_propose_plain_chinese.sh} +0 -0
- /package/ds-canonical/hooks/{check_storybook_addon_preset_cjs.sh → retired/2026-06-11-prune-merge/check_storybook_addon_preset_cjs.sh} +0 -0
|
@@ -49,11 +49,10 @@ export const UsageGuidance: Story = {
|
|
|
49
49
|
<div className="prose prose-sm max-w-prose mb-8">
|
|
50
50
|
<p>適合 Field 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
51
51
|
<ul className="space-y-1">
|
|
52
|
-
<li><LinkTo kind="Design System/Components/Field/展示" name="
|
|
53
|
-
<li><LinkTo kind="Design System/Components/Field/展示" name="
|
|
54
|
-
<li><LinkTo kind="Design System/Components/Field/展示" name="
|
|
55
|
-
<li><LinkTo kind="Design System/Components/Field/展示" name="
|
|
56
|
-
<li><LinkTo kind="Design System/Components/Field/展示" name="SegmentedControl 作為 Field 控制元件"><span className="text-primary hover:underline font-medium cursor-pointer">SegmentedControl 作為 Field 控制元件</span></LinkTo></li>
|
|
52
|
+
<li><LinkTo kind="Design System/Components/Field/展示" name="垂直"><span className="text-primary hover:underline font-medium cursor-pointer">註冊 / 建立專案表單 — 引導式逐欄輸入(垂直佈局)</span></LinkTo></li>
|
|
53
|
+
<li><LinkTo kind="Design System/Components/Field/展示" name="水平"><span className="text-primary hover:underline font-medium cursor-pointer">帳號設定 / 偏好設定頁 — 修改已知屬性(水平佈局)</span></LinkTo></li>
|
|
54
|
+
<li><LinkTo kind="Design System/Components/Field/展示" name="混合控制元件的高度對齊"><span className="text-primary hover:underline font-medium cursor-pointer">同一表單混用 Input / Select / DatePicker 等控制元件</span></LinkTo></li>
|
|
55
|
+
<li><LinkTo kind="Design System/Components/Field/展示" name="SegmentedControl 作為 Field 控制元件"><span className="text-primary hover:underline font-medium cursor-pointer">少量互斥選項(計費週期、版面密度)用 SegmentedControl 當控制元件</span></LinkTo></li>
|
|
57
56
|
</ul>
|
|
58
57
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親 段)。</p>
|
|
59
58
|
</div>
|
|
@@ -116,7 +115,8 @@ export const UsageGuidance: Story = {
|
|
|
116
115
|
note="「查看使用者資料」「訂單明細」這類純展示場景不該用 Field + readonly mode——DescriptionList 的 `dl / dt / dd` HTML 語義更適合唯讀屬性列表,a11y 也更清楚"
|
|
117
116
|
>
|
|
118
117
|
<div className="max-w-sm">
|
|
119
|
-
|
|
118
|
+
{/* horizontalLabelWidth:同表單 horizontal Field 必統一 label 寬(field.spec.md「FieldGroup horizontalLabelWidth cascade」)*/}
|
|
119
|
+
<FieldGroup horizontalLabelWidth="120px">
|
|
120
120
|
<Field orientation="horizontal">
|
|
121
121
|
<FieldLabel>Email</FieldLabel>
|
|
122
122
|
<Input mode="readonly" defaultValue="user@example.com" />
|
|
@@ -197,7 +197,8 @@ export const OrientationRule: Story = {
|
|
|
197
197
|
note="已知屬性的修改介面(account settings、使用者偏好)。label + control 並排節省垂直空間,讓多個設定一目了然。label 固定寬度對齊縱向軸"
|
|
198
198
|
>
|
|
199
199
|
<div className="max-w-xl">
|
|
200
|
-
|
|
200
|
+
{/* horizontalLabelWidth:同表單 label 等寬 → 控件起點對齊(2026-06-10 user 抓本 story 違反自家 canonical)*/}
|
|
201
|
+
<FieldGroup horizontalLabelWidth="120px">
|
|
201
202
|
<Field orientation="horizontal" required>
|
|
202
203
|
<FieldLabel>電子郵件</FieldLabel>
|
|
203
204
|
<Input type="email" defaultValue="user@example.com" />
|
|
@@ -8,7 +8,7 @@ variants: {}
|
|
|
8
8
|
sizes: {}
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline
|
|
11
|
+
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline(M22(d) 顯式撤回;本檔 frontmatter 無 benchmark list,來源 URL 未補)。 -->
|
|
12
12
|
|
|
13
13
|
# Field 設計原則
|
|
14
14
|
|
|
@@ -23,7 +23,7 @@ Field 是**表單欄位的佈局容器**。只負責排版(label / control / d
|
|
|
23
23
|
|
|
24
24
|
**實作基礎**:自建——本 DS 的 form layout 設計。shadcn 的 `Form` 元件走 react-hook-form + Zod + 自己的 Field primitive(含 Controller),本 DS 不採用這套耦合設計:Field 只做 layout + context,驗證由 consumer 自選(本 DS 建議 zod,見 `form-validation.spec.md`),保持更輕量、更獨立的定位。
|
|
25
25
|
|
|
26
|
-
**Layout Family**:Field **不屬於** 4-Family Model 的 element layout families——它是 form composition pattern(包 Family 4 control + label + description)。見 CLAUDE.md
|
|
26
|
+
**Layout Family**:Field **不屬於** 4-Family Model 的 element layout families——它是 form composition pattern(包 Family 4 control + label + description)。見 CLAUDE.md「4-Family Layout Model」段 + `patterns/element-anatomy/element-anatomy.spec.md`「Field Composition(不在 family 但相關)」段。
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -32,7 +32,7 @@ Field 是**表單欄位的佈局容器**。只負責排版(label / control / d
|
|
|
32
32
|
Field 和 Field Controls(Input / NumberInput / DatePicker / Select / Combobox / LinkInput / PeoplePicker / Textarea)是兩件事:
|
|
33
33
|
|
|
34
34
|
- **Field**(本元件):只管佈局 + 狀態 context
|
|
35
|
-
- **Field Controls / Checkbox / Switch / RadioGroup**:管自己的資料型別 + edit/readonly/disabled 三態 +
|
|
35
|
+
- **Field Controls / Checkbox / Switch / RadioGroup**:管自己的資料型別 + edit/readonly/disabled 三態 + 格式化(`mode="display"` 渲染供 DataTable cell 共用)
|
|
36
36
|
|
|
37
37
|
這麼拆的理由:**Checkbox 在 table cell、form field、settings row 應該是同一個 primitive**,不該為了進到 form 就被包一層 CheckboxField;form 的高度對齊由 Field 的 control area 負責,不由 primitive 本身負責。Field Controls 的詳細共用規則見 `Field/field-controls.spec.md`。
|
|
38
38
|
|
|
@@ -77,7 +77,9 @@ Field 和 Field Controls(Input / NumberInput / DatePicker / Select / Combobox
|
|
|
77
77
|
| Horizontal 模式 label 與 control 的 gap | `gap-x-3`(12px) |
|
|
78
78
|
| Control area(任何 size) | `min-h-field-{size}` + `flex items-center` |
|
|
79
79
|
|
|
80
|
-
**Label / Description / Error 字體固定 `text-body`(14px),不隨 field size
|
|
80
|
+
**Label / Description / Error 字體固定 `text-body`(14px),不隨 field size 變**——Field size 只影響 input 高度,不影響表單佈局元素的 typography。
|
|
81
|
+
|
|
82
|
+
**為什麼固定**:世界級系統(Material、Ant Design、Atlassian、Carbon、Polaris)都是固定 label/helper text size。 <!-- @benchmark-unverified -->
|
|
81
83
|
|
|
82
84
|
---
|
|
83
85
|
|
|
@@ -110,6 +112,8 @@ Label 在左、control + description + error 在右欄垂直堆疊。Label 與 c
|
|
|
110
112
|
|
|
111
113
|
Horizontal 模式下 label 的欄寬由 `labelWidth` prop 控制(任何 CSS length 值),預設 `auto` 由 label 內容撐開。內部用 CSS variable `--field-label-width` 傳給 grid template column。
|
|
112
114
|
|
|
115
|
+
**硬規則(2026-06-10 codify,user 確認)**:同一表單的多個 horizontal Field **必經 `FieldGroup horizontalLabelWidth` 統一 label 寬**(單行 `labelWidth` 覆寫)——禁各自 `auto` 造成控件起點不對齊(錨例:field.principles 原違反自家 idiom,圖示控件參差)。機制詳下方「FieldGroup horizontalLabelWidth cascade」段。
|
|
116
|
+
|
|
113
117
|
---
|
|
114
118
|
|
|
115
119
|
## Horizontal 模式 label 垂直對齊
|
|
@@ -125,7 +129,7 @@ Horizontal 模式下 label 的欄寬由 `labelWidth` prop 控制(任何 CSS le
|
|
|
125
129
|
- label 第一行永遠對齊第一個 item 第一行
|
|
126
130
|
- 實作:`padding-top: calc((field-height - 1lh)/2)`
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
**為什麼分兩套**:inline / block 是不同的對齊語意模型(固定高度可談整體中線 vs 多行群組只有第一行錨點),必分兩套。純 CSS 跨 size/density/字體自動連動,無 JS 測量。對齊 Atlassian DSP / Salesforce Lightning(Polaris 用 baseline 修正,類似)。(附註・技術陷阱:曾試 `min-h + flex-center` 統一,出 block + 長 label regression——justify-center 失效 + 跟 SelectionItem 公式錯位,勿再嘗試) <!-- @benchmark-unverified -->
|
|
129
133
|
|
|
130
134
|
---
|
|
131
135
|
|
|
@@ -136,7 +140,7 @@ Field 的 control area 有兩種佈局模型,涵蓋所有 control 類型。**核
|
|
|
136
140
|
| Layout | Control area 樣式 | 適用 control |
|
|
137
141
|
|---|---|---|
|
|
138
142
|
| **inline**(預設) | `min-h-field-{size}` + `flex items-center` | Input / NumberInput / DatePicker / Select / Combobox / LinkInput / Textarea(單行使用)、Checkbox / Switch / 單一 Button(如 upload picker) |
|
|
139
|
-
| **block** | `flex flex-col items-start
|
|
143
|
+
| **block** | `flex flex-col items-start`,**不設 min-h、不加 padding-top**(第一行對齊由 block primitive 自帶 py 保證,見「如何宣告 block primitive」) | RadioGroup / CheckboxGroup / FileDropzone / RichTextEditor / inline DataTable 等多行/任意高度區塊 |
|
|
140
144
|
|
|
141
145
|
### 兩種模式的對齊幾何
|
|
142
146
|
|
|
@@ -157,11 +161,11 @@ Inline Block
|
|
|
157
161
|
1. 第一個 Radio 往下掉,跟 label 第一行中線錯位
|
|
158
162
|
2. FieldGroup 裡 inline / block field 並排時節奏斷掉
|
|
159
163
|
|
|
160
|
-
正確做法是「**第一行對齊**」——後續 item 從第一行往下流,label 公式錨在第一行中線。Atlassian DSP / Polaris / Material 都是這個模型。 <!-- @benchmark-unverified
|
|
164
|
+
正確做法是「**第一行對齊**」——後續 item 從第一行往下流,label 公式錨在第一行中線。Atlassian DSP / Polaris / Material 都是這個模型。 <!-- @benchmark-unverified -->
|
|
161
165
|
|
|
162
166
|
### 為什麼 primitive 不自己變高
|
|
163
167
|
|
|
164
|
-
**Checkbox / Switch / RadioGroupItem 的 primitive 保持原生尺寸**(16-20px),不為了 form 而被拉高。世界級系統(shadcn、Radix、Material、Atlassian)全部這樣做。理由: <!-- @benchmark-unverified
|
|
168
|
+
**Checkbox / Switch / RadioGroupItem 的 primitive 保持原生尺寸**(16-20px),不為了 form 而被拉高。世界級系統(shadcn、Radix、Material、Atlassian)全部這樣做。理由: <!-- @benchmark-unverified -->
|
|
165
169
|
|
|
166
170
|
1. **Primitive 保持單一職責**——Checkbox 在 table cell、toolbar、menu 裡仍然是 16px,不受 form 高度污染
|
|
167
171
|
2. **高度節奏由 Field 容器提供**——一次設定,所有 primitive 在任何 size / density 都自動對齊
|
|
@@ -243,7 +247,15 @@ Field 透過 Context 暴露以下狀態給子元件(Primitive 可以透過 `us
|
|
|
243
247
|
- Disabled 時星號改為 `text-fg-disabled`(neutral-6),與 label 同步降色
|
|
244
248
|
- 個別 FieldLabel 可用 `required` prop 覆寫 context 值
|
|
245
249
|
|
|
246
|
-
**為什麼貼齊無 gap
|
|
250
|
+
**為什麼貼齊無 gap**:星號是 label 語意的一部分,不是獨立視覺元素,所以不需要間距(星號本身 `aria-hidden`,SR 的 required 資訊由控件 `aria-required` 承擔——見「A11y 預設」段)。
|
|
251
|
+
|
|
252
|
+
## FieldLabel `info` icon(2026-06-10 文件化,對齊 field.tsx 既有實作)
|
|
253
|
+
|
|
254
|
+
label 文字後可帶 info icon(ℹ)hover 出 tooltip 補充說明:`<FieldLabel info="說明文字">`。
|
|
255
|
+
|
|
256
|
+
- **與 label 間距 `gap-1`(4px)**、InfoIcon **16px 固定**、色 `fg-muted` hover `fg-secondary`(field.tsx L388-408)
|
|
257
|
+
- **disabled 時整顆不渲染**(L400)— info 是 action affordance(hover 互動),非類型身份 indicator,non-editable 隱藏
|
|
258
|
+
- 設計定位:inline action pattern(補充工具,視覺退後)— label 的 primary interaction 是 input,info 是輔助
|
|
247
259
|
|
|
248
260
|
---
|
|
249
261
|
|
|
@@ -286,7 +298,7 @@ gap 三個語意層級(具體 gap class map 見 `field.tsx` FieldGroup,`compact`
|
|
|
286
298
|
|
|
287
299
|
同一畫面 / 同一 FieldGroup 內多個 horizontal Field **必須共用 label 欄寬度**。若每個 Field 各自傳 `labelWidth`(或省略 → 內容撐開),label 寬度會參差不齊,Switch / Input 的左邊緣不對齊,視覺上每一行「歪七扭八」。
|
|
288
300
|
|
|
289
|
-
**世界級 idiom**:macOS System Settings / iOS Settings / GitHub Settings / Notion preferences / Figma 偏好設定——setting list 的 label 全部固定寬、control 全部右對齊,列與列對齊成可掃描的欄位格網。 <!-- @benchmark-unverified
|
|
301
|
+
**世界級 idiom**:macOS System Settings / iOS Settings / GitHub Settings / Notion preferences / Figma 偏好設定——setting list 的 label 全部固定寬、control 全部右對齊,列與列對齊成可掃描的欄位格網。 <!-- @benchmark-unverified -->
|
|
290
302
|
|
|
291
303
|
`FieldGroup horizontalLabelWidth` 透過 React Context cascade 到所有子 Field(含 `vertical` 模式的 Field 不受影響——vertical 無 label 欄概念)。單一 Field 仍可用自己的 `labelWidth` prop 覆寫 group 預設(罕見 — 通常 group 預設就是 canonical)。
|
|
292
304
|
|
|
@@ -320,6 +332,13 @@ Button 的 height 與 `field-height` 共用同一組 token,放進 inline control
|
|
|
320
332
|
|
|
321
333
|
---
|
|
322
334
|
|
|
335
|
+
## 邊界案例
|
|
336
|
+
|
|
337
|
+
- **空 Field(無 control children)**:code 不擋——control area 渲染成空容器。Field 是佈局容器,無 control 即無意義,屬 consumer 誤用
|
|
338
|
+
- **RTL**:未實作方向鏡像(horizontal label 欄固定在左、Switch `ml-auto` 推右皆以 LTR 設計);RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
323
342
|
## 禁止事項
|
|
324
343
|
|
|
325
344
|
- ❌ 不得在 Field 內再包 Field——Field 不支援巢狀
|
|
@@ -353,24 +372,21 @@ Field 內的資料輸入控件(Input / NumberInput / DatePicker / Select / Com
|
|
|
353
372
|
| 唯讀資訊展示 | `DescriptionList` | Field 是表單容器,純展示用 `dl/dt/dd` 語義 |
|
|
354
373
|
| 單一 Checkbox(勾選同意)without label 結構 | 直接 Checkbox + label prop | 單個 Checkbox 是 inline primitive,不需要 Field 佈局 |
|
|
355
374
|
| DataTable cell 編輯(inline editable)| 直接放 Field Control | Field 是頁面表單佈局,table cell 空間受限 |
|
|
375
|
+
| 把 Field 當輸入元件本身(常見誤解:「Field = form 輸入框」)| `Input` 等 Field Control | Field 只管佈局 + context,輸入行為住在 control(見「與 Field Controls 的職責切分」)|
|
|
356
376
|
| 純 action 按鈕(submit / cancel / 頁面導覽)| 頁面 footer / toolbar | Field 是資料輸入容器,不放頁面級 action |
|
|
357
377
|
| 巢狀 Field | ❌ 不支援 | Field 不支援巢狀,多欄位用 FieldGroup |
|
|
358
378
|
|
|
359
379
|
---
|
|
360
380
|
|
|
361
|
-
##
|
|
381
|
+
## Anatomy stories
|
|
362
382
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
Inspector 對 container 類元件沒有對應教學價值——Field 本身不產生互動 affordance(互動由 Field Control 例如 Input / Select 處理),該 Inspect 的是 Field Control layer(已有各自元件的 Inspector)。重寫 Inspector = 複製 Input / Select 的 Inspector。
|
|
366
|
-
|
|
367
|
-
對應 anatomy story:保留 `Overview` + `OrientationMatrix` + `SizeMatrix` + `StateBehavior` + `ColorMatrix` + 元件特有 `FieldGroupBehavior`。
|
|
383
|
+
對應 anatomy story:`Overview` + `Inspector`(元件檢閱器——切 orientation / size / mode / required / invalid 即時 render)+ `OrientationMatrix` + `SizeMatrix` + `StateBehavior` + `ColorMatrix` + 元件特有 `FieldGroupBehavior` + `Accessibility`。
|
|
368
384
|
|
|
369
385
|
---
|
|
370
386
|
|
|
371
387
|
## Field state machine SSOT(v13.3)
|
|
372
388
|
|
|
373
|
-
**Canonical**:**focus dominates everything**(M11 延伸:focus 勝 hover/open/error-rest)。Cursor in input = user 編輯中 = 永遠藍。對齊 Material 3 / Polaris / Ant Design 5 共識。SSOT 在 `field-wrapper.tsx` 三 compoundVariant — 改一處全 control + cell + 各 variant
|
|
389
|
+
**Canonical**:**focus dominates everything**(M11 延伸:focus 勝 hover/open/error-rest)。Cursor in input = user 編輯中 = 永遠藍。對齊 Material 3 / Polaris / Ant Design 5 共識。SSOT 在 `field-wrapper.tsx` 三 compoundVariant — 改一處全 control + cell + 各 variant 跟動(**例外:error state CSS 目前逐 control 實作**——input.tsx / select.tsx / number-input.tsx 各自帶 `border-error` 系列,不在 field-wrapper compoundVariants,改 error 需逐 control 同步)。 <!-- @benchmark-unverified -->
|
|
374
390
|
|
|
375
391
|
| State | Token | CSS |
|
|
376
392
|
|---|---|---|
|
|
@@ -383,7 +399,7 @@ Inspector 對 container 類元件沒有對應教學價值——Field 本身不
|
|
|
383
399
|
|
|
384
400
|
副作用(自動達成 Ant「選後藍 / 取消灰」):選 option → Radix `onCloseAutoFocus` return focus → focus-within fires → 藍 / 點外取消 → focus 移外 → 灰。純 focus 機制無需 transient class。
|
|
385
401
|
|
|
386
|
-
**反模式 ❌**:per-control `open && 'border-primary'`(Combobox/Select/PeoplePicker)/ `data-[state=open]:border-primary` — 已 v13.3 全 retire
|
|
402
|
+
**反模式 ❌**:per-control `open && 'border-primary'`(Combobox/Select/PeoplePicker)/ `data-[state=open]:border-primary` — 已 v13.3 全 retire。hook `check_field_family_invariants.sh` A.3(原 check_field_state_token_consume)write-time 攔。
|
|
387
403
|
|
|
388
404
|
---
|
|
389
405
|
|
|
@@ -406,7 +422,7 @@ Inspector 對 container 類元件沒有對應教學價值——Field 本身不
|
|
|
406
422
|
|
|
407
423
|
**反模式 ❌**:naked 自寫 `outline-*` / `box-shadow inset` state ring(用 L3) / hardcode `<span h-[1lh]>` slot(用 L1)/ hardcode `items-center`(用 L2)/ per-control `open && 'border-primary'`(用 L3 SSOT)。
|
|
408
424
|
|
|
409
|
-
**機械防漂移**:hooks `
|
|
425
|
+
**機械防漂移**:hooks `check_field_family_invariants.sh`(A.1 naked row-mode propagation / A.3 Field state ring SSOT,原 standalone hooks 已 folded)+ `check_pattern_invariants.sh`(C.2 inline-action canonical gap / C.4 row slot handcraft)write-time 攔 + `/design-system-audit` 對應 audit dim。
|
|
410
426
|
|
|
411
427
|
## 相關
|
|
412
428
|
|
|
@@ -414,7 +430,6 @@ Inspector 對 container 類元件沒有對應教學價值——Field 本身不
|
|
|
414
430
|
- `./form-validation.spec.md` — 表單驗證標準(blur 驗證、zod schema、error 顯示)
|
|
415
431
|
- `../DescriptionList/description-list.spec.md` — 唯讀資訊展示(非表單)
|
|
416
432
|
- `../../patterns/element-anatomy/item-anatomy.spec.md` — SelectionItem 佈局(Checkbox / Radio 放進 Field 時 block 模式的參照)
|
|
417
|
-
- CLAUDE.md「元件 Props 命名原則」— Field 的 orientation / block control 宣告規則
|
|
418
433
|
|
|
419
434
|
## A11y 預設
|
|
420
435
|
|
|
@@ -427,12 +442,19 @@ Inspector 對 container 類元件沒有對應教學價值——Field 本身不
|
|
|
427
442
|
|
|
428
443
|
**Focus**:focus-visible ring 對齊 DS canonical(`outline: 2px solid var(--ring)`);Field 不搶焦點,focus 由內部 control 自管。
|
|
429
444
|
|
|
445
|
+
**SR 識別路徑**(由既有 aria 接線達成,見「FieldContext」與「驗證與 aria 屬性」段):focus 進 control 時 SR 可得完整資訊——label(`htmlFor`/`id` 連結)、required(control 的 `aria-required`)、invalid(`aria-invalid`)、description(`aria-describedby`)、error(`aria-errormessage`;`<FieldError role="alert">` 另在錯誤出現時即時播報)。required 星號本身 `aria-hidden`,SR 資訊來自 `aria-required` 非 `*` 字元。
|
|
446
|
+
|
|
430
447
|
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
431
448
|
|
|
432
449
|
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
433
450
|
|
|
434
451
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
435
452
|
|
|
453
|
+
- `description-list.spec.md`
|
|
436
454
|
- `element-anatomy.spec.md`
|
|
455
|
+
- `field-control-group.spec.md`
|
|
456
|
+
- `field-controls.spec.md`
|
|
457
|
+
- `form-validation.spec.md`
|
|
458
|
+
- `input.spec.md`
|
|
437
459
|
- `radio-group.spec.md`
|
|
438
460
|
- `switch.spec.md`
|
|
@@ -73,7 +73,7 @@ export const StateCascade: Story = {
|
|
|
73
73
|
render: () => (
|
|
74
74
|
<div className="flex flex-col gap-8">
|
|
75
75
|
<div>
|
|
76
|
-
<p className="text-body-sm font-medium text-fg-
|
|
76
|
+
<p className="text-body-sm font-medium text-fg-secondary mb-3">{'<Field disabled> — 所有控件自動停用(含 Switch / Rating / Slider / Select / DatePicker)'}</p>
|
|
77
77
|
<div className="flex flex-wrap gap-x-8 gap-y-4 max-w-3xl">
|
|
78
78
|
<Field disabled className="w-44"><FieldLabel>負責人</FieldLabel><Input value="王小明" onChange={() => {}} /></Field>
|
|
79
79
|
<Field disabled className="w-44"><FieldLabel>優先級</FieldLabel><Select options={CASCADE_PRIORITY} value="high" onChange={() => {}} aria-label="優先級" /></Field>
|
|
@@ -86,7 +86,7 @@ export const StateCascade: Story = {
|
|
|
86
86
|
</div>
|
|
87
87
|
</div>
|
|
88
88
|
<div>
|
|
89
|
-
<p className="text-body-sm font-medium text-fg-
|
|
89
|
+
<p className="text-body-sm font-medium text-fg-secondary mb-3">{'<Field mode="display"> — 有展示態的控件自動切純展示(Select / DatePicker / Checkbox / Switch 修復後生效)'}</p>
|
|
90
90
|
<div className="flex flex-wrap gap-x-8 gap-y-4 max-w-3xl">
|
|
91
91
|
<Field mode="display" className="w-44"><FieldLabel>負責人</FieldLabel><Input value="王小明" onChange={() => {}} /></Field>
|
|
92
92
|
<Field mode="display" className="w-44"><FieldLabel>優先級</FieldLabel><Select options={CASCADE_PRIORITY} value="high" aria-label="優先級" /></Field>
|
|
@@ -131,7 +131,7 @@ export const Horizontal: Story = {
|
|
|
131
131
|
// ── Horizontal 垂直對齊公式驗證 ─────────────────────────────────────────
|
|
132
132
|
|
|
133
133
|
export const HorizontalLabelAlignment: Story = {
|
|
134
|
-
name: '
|
|
134
|
+
name: '水平:標籤垂直對齊公式驗證',
|
|
135
135
|
render: () => (
|
|
136
136
|
<div className="max-w-3xl flex flex-col gap-8">
|
|
137
137
|
<div>
|
|
@@ -186,7 +186,7 @@ export const HorizontalLabelAlignment: Story = {
|
|
|
186
186
|
// ── Checkbox / Switch 在 Field 內的高度對齊 ─────────────────────────────
|
|
187
187
|
|
|
188
188
|
export const MixedControlAlignment: Story = {
|
|
189
|
-
name: '
|
|
189
|
+
name: '混合控制元件的高度對齊',
|
|
190
190
|
render: () => (
|
|
191
191
|
<div className="flex flex-col gap-8 max-w-3xl">
|
|
192
192
|
<div>
|
|
@@ -315,7 +315,9 @@ export const SegmentedControlInField: Story = {
|
|
|
315
315
|
</Field>
|
|
316
316
|
<Field orientation="horizontal" labelWidth="120px">
|
|
317
317
|
<FieldLabel>訂閱通知</FieldLabel>
|
|
318
|
-
|
|
318
|
+
{/* 混合表單(Input/SegmentedControl 同列)= Form-edit 情境 → Switch 跟其他控件靠左
|
|
319
|
+
(switch.spec.md「兩種對齊慣例」判準;ml-0 覆寫 Field 內預設 ml-auto 齊右 —— 齊右屬純 settings list)*/}
|
|
320
|
+
<Switch className="ml-0" />
|
|
319
321
|
</Field>
|
|
320
322
|
</FieldGroup>
|
|
321
323
|
</div>
|
|
@@ -418,7 +420,7 @@ export const BlockControlRadioGroup: Story = {
|
|
|
418
420
|
// ── Button as Data Input Affordance ─────────────────────────────────────
|
|
419
421
|
|
|
420
422
|
export const ButtonAsControl: Story = {
|
|
421
|
-
name: 'Button
|
|
423
|
+
name: 'Button 作為控制元件',
|
|
422
424
|
render: () => (
|
|
423
425
|
<div className="flex flex-col gap-8 max-w-3xl">
|
|
424
426
|
<div>
|
|
@@ -460,7 +462,7 @@ export const ButtonAsControl: Story = {
|
|
|
460
462
|
// ── Label Width 變化 ────────────────────────────────────────────────────
|
|
461
463
|
|
|
462
464
|
export const LabelWidth: Story = {
|
|
463
|
-
name: '
|
|
465
|
+
name: '標籤寬度',
|
|
464
466
|
render: () => (
|
|
465
467
|
<div className="max-w-2xl flex flex-col gap-4">
|
|
466
468
|
<p className="text-caption text-fg-muted">labelWidth 支援任何 CSS length 值</p>
|
|
@@ -223,9 +223,9 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
|
223
223
|
// - inline: min-h-field-{size} + items-center
|
|
224
224
|
// 單行 control(Input、Button 等)中線置中於 min-h box。
|
|
225
225
|
//
|
|
226
|
-
// - block: flex-col + items-start
|
|
227
|
-
// 多行 control(RadioGroup 等)
|
|
228
|
-
//
|
|
226
|
+
// - block: flex-col + items-start(不設 min-h、不加 padding-top,內容自己決定高度)
|
|
227
|
+
// 多行 control(RadioGroup 等),第一行中線由 block primitive 自帶 py 推到
|
|
228
|
+
// field-height/2,後續 item 自然往下流。
|
|
229
229
|
// Block control area 不加額外 paddingTop——block primitive(RadioGroup 等)
|
|
230
230
|
// 的子元件(SelectionItem)已自帶 py = calc((field-height - 1lh) / 2),
|
|
231
231
|
// 第一個 item 的文字自然落在 field-height/2。額外加 paddingTop 會 double padding。
|
|
@@ -383,6 +383,9 @@ const FieldLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(
|
|
|
383
383
|
style={{ ...horizontalBlockStyle, ...style }}
|
|
384
384
|
data-field-slot="label"
|
|
385
385
|
data-field-disabled={disabled ? '' : undefined}
|
|
386
|
+
// 2026-06-10 a11y:styled-disabled label 必明告 inactive(WCAG 1.4.3 inactive-UI 豁免需可機判;
|
|
387
|
+
// axe 對無 aria-disabled 的 fg-disabled 文字誤報 color-contrast — deep-audit 抓 8 筆)
|
|
388
|
+
aria-disabled={disabled || undefined}
|
|
386
389
|
{...props}
|
|
387
390
|
>
|
|
388
391
|
<span className="inline-flex items-center gap-1">
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline
|
|
1
|
+
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline(M22(d) 顯式撤回;本檔 frontmatter 無 benchmark list,來源 URL 未補)。 -->
|
|
2
2
|
|
|
3
3
|
# Form Validation 設計原則
|
|
4
4
|
|
|
5
|
-
> **本 spec = 跨表單的 validation 方法論 rules
|
|
5
|
+
> **本 spec = 跨表單的 validation 方法論 rules**(表單層級行為規範,適用於所有含 Field 元件的表單)。非 UI 元件 spec,不適用 Layout Family 分類(Dim 16 豁免)。
|
|
6
6
|
> 元件級 validation 視覺規格住在 `Field/field.spec.md`(Field wrapper chrome)+ 各 form control spec。
|
|
7
7
|
|
|
8
|
-
表單層級的驗證行為規範。適用於所有包含 Field 元件的表單。
|
|
9
|
-
|
|
10
8
|
---
|
|
11
9
|
|
|
12
10
|
## 表單驗證原則
|
|
@@ -43,7 +41,7 @@
|
|
|
43
41
|
### Submit 驗證
|
|
44
42
|
|
|
45
43
|
7. **Submit 驗證全部**——點擊 submit 時對所有欄位執行驗證(不依賴個別 field 的 blur 狀態)
|
|
46
|
-
8. **Anchor 到第一個錯誤**——若有任何欄位出錯,scroll 並 focus
|
|
44
|
+
8. **Anchor 到第一個錯誤**——若有任何欄位出錯,scroll 並 focus 到第一個錯誤欄位。多次 submit 重試時,每次都重新驗證全部欄位並重新計算「第一個錯誤」(rule 7 的自然結果),不保持上次 anchor 位置
|
|
47
45
|
9. **Async / cross-field 驗證 defer 到 submit**——某些驗證無法在 blur 當下完成(如「名稱是否重複」需要 API 查詢、跨欄位邏輯如「結束日不得早於開始日」),這些在 submit 時統一判斷。若有錯誤,同樣 anchor 到第一個出錯欄位。
|
|
48
46
|
|
|
49
47
|
### 驗證分層
|
|
@@ -91,29 +89,29 @@
|
|
|
91
89
|
|
|
92
90
|
四個關鍵決策,各自有世界級先例支撐:
|
|
93
91
|
|
|
94
|
-
**(1) Blur-only validation(non-onChange)— 對齊 Polaris / Carbon「don't validate while typing」** <!-- @benchmark-unverified
|
|
92
|
+
**(1) Blur-only validation(non-onChange)— 對齊 Polaris / Carbon「don't validate while typing」** <!-- @benchmark-unverified -->
|
|
95
93
|
|
|
96
94
|
Ant Design default `validateTrigger=['onChange', 'onBlur']` 對使用者 aggressive — 才打「user@」就跳「invalid email」碎念,reader 思路被打斷。Polaris / Carbon / iOS / Atlassian 共識 onBlur + submit,讓使用者「先表達完意圖再評斷」。
|
|
97
95
|
|
|
98
|
-
捨棄 onChange 即時驗證的代價是「打錯看不到反饋」(打到第 3 位才發現密碼太短)
|
|
96
|
+
捨棄 onChange 即時驗證的代價是「打錯看不到反饋」(打到第 3 位才發現密碼太短)——本 spec 規範 default 行為(blur + submit);DS 目前無 onChange hint API。
|
|
99
97
|
|
|
100
|
-
**(2) Edit 清 error + blur 重驗(已出錯後),非 onChange 重驗 — 對齊 Carbon / Atlassian 兩階段哲學** <!-- @benchmark-unverified
|
|
98
|
+
**(2) Edit 清 error + blur 重驗(已出錯後),非 onChange 重驗 — 對齊 Carbon / Atlassian 兩階段哲學** <!-- @benchmark-unverified -->
|
|
101
99
|
|
|
102
100
|
Material/Polaris/Ant 已出錯後 onChange re-validate(改第 1 字 error 又跳回)— 給使用者壓力。Carbon / Atlassian「edit 清 + blur 重驗」哲學:給使用者完整修正空間,離開時才再判決。
|
|
103
101
|
|
|
104
102
|
對應使用者心智:「修改」是過程,「離開 field」是動作完成的 boundary,在 boundary 評斷比每字評斷尊重 user agency。
|
|
105
103
|
|
|
106
|
-
**(3) Create always-enabled / Update disabled-until-dirty 不對稱 — 對齊 Stripe / Notion / Linear 現代慣例** <!-- @benchmark-unverified
|
|
104
|
+
**(3) Create always-enabled / Update disabled-until-dirty 不對稱 — 對齊 Stripe / Notion / Linear 現代慣例** <!-- @benchmark-unverified -->
|
|
107
105
|
|
|
108
106
|
Ant 對「Create」也 disabled-until-dirty(填了所有 required 才亮)— 但這讓使用者第一次進 form 看到 disabled button 困惑「為什麼按不了」。Stripe / Notion / Material 共識:Create 永遠 enabled — 點擊後若 invalid,顯示 error 並 scroll,使用者明確知道為什麼。
|
|
109
107
|
|
|
110
|
-
Update 場景反向:沒改的 Update 沒提交意義(對齊「intent 才 commit」),disabled 表達「等你做動作」比 enabled 後點擊判斷「沒變化」更直接。對齊 Notion 設定頁 / Figma file rename 慣例。 <!-- @benchmark-unverified
|
|
108
|
+
Update 場景反向:沒改的 Update 沒提交意義(對齊「intent 才 commit」),disabled 表達「等你做動作」比 enabled 後點擊判斷「沒變化」更直接。對齊 Notion 設定頁 / Figma file rename 慣例。 <!-- @benchmark-unverified -->
|
|
111
109
|
|
|
112
|
-
**(4) 格式驗證 vs 業務驗證分層(blur vs submit)— 對齊 Material/Carbon「local vs cross-cutting validation」哲學** <!-- @benchmark-unverified
|
|
110
|
+
**(4) 格式驗證 vs 業務驗證分層(blur vs submit)— 對齊 Material/Carbon「local vs cross-cutting validation」哲學** <!-- @benchmark-unverified -->
|
|
113
111
|
|
|
114
112
|
Email 格式 / URL 格式 / 必填等「single-field 純 syntax」blur 即可判斷;名稱重複(API 查)/ 結束日 ≥ 開始日(跨欄位)等「business / async」必須 submit 才能判 — 強行 blur 觸發 API 對使用者體驗差(每換 field 一次 API call)。
|
|
115
113
|
|
|
116
|
-
對齊 Material `<TextField error>` + Form layer error 分層 / Carbon「format vs business」雙軌。視覺一致(都紅框 + error message)避免 reader 區分「為什麼這個 error 是 blur 出來那個是 submit 出來」。 <!-- @benchmark-unverified
|
|
114
|
+
對齊 Material `<TextField error>` + Form layer error 分層 / Carbon「format vs business」雙軌。視覺一致(都紅框 + error message)避免 reader 區分「為什麼這個 error 是 blur 出來那個是 submit 出來」。 <!-- @benchmark-unverified -->
|
|
117
115
|
|
|
118
116
|
## 禁止事項
|
|
119
117
|
|
|
@@ -129,22 +127,25 @@ Email 格式 / URL 格式 / 必填等「single-field 純 syntax」blur 即可判
|
|
|
129
127
|
Form validation 的 ARIA / 鍵盤行為(對齊 WCAG 3.3.1 Error Identification + 3.3.3 Error Suggestion):
|
|
130
128
|
|
|
131
129
|
- **Error message ARIA**:Field error 容器 `id="field-{name}-error"`,Input 設 `aria-describedby="field-{name}-error"` + `aria-invalid="true"`;SR 在 focus field 時自動讀「{label}, {error message}」
|
|
132
|
-
- **Submit error scroll**:submit 失敗後,focus 自動 jump 到第一個 invalid field(`field.focus()` + `scrollIntoView({block: 'center'})`);對齊 Material / Atlassian 慣例 <!-- @benchmark-unverified
|
|
130
|
+
- **Submit error scroll**:submit 失敗後,focus 自動 jump 到第一個 invalid field(`field.focus()` + `scrollIntoView({block: 'center'})`);對齊 Material / Atlassian 慣例 <!-- @benchmark-unverified -->
|
|
133
131
|
- **Error live region**:跨欄位 / async error 用 `aria-live="polite"` 容器宣告 — SR 在空閒時讀出,不中斷使用者打字
|
|
134
132
|
- **Required indicator**:label 的 `*` 為純視覺、對讀屏隱藏(`aria-hidden="true"`,field.tsx:392);required 語意由內部輸入控件的 `aria-required`(input.tsx:192)承擔,避免讀屏讀出「asterisk」語義不清
|
|
135
|
-
- **Color-only error 警告**:error border
|
|
136
|
-
|
|
137
|
-
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
133
|
+
- **Color-only error 警告**:error border 紅色之外必有文字訊息(WCAG 1.4.1 不僅靠顏色)— 由 `<FieldError>` 文字承擔;DS **不**在 input 內放 error 狀態 icon(見 `field-controls.spec.md`「禁止事項」)
|
|
138
134
|
|
|
139
|
-
|
|
135
|
+
## 相關
|
|
140
136
|
|
|
141
|
-
- `
|
|
142
|
-
- `
|
|
137
|
+
- `field.spec.md` — Field wrapper 的 error 視覺 chrome(紅框 + error message slot)
|
|
138
|
+
- `field-controls.spec.md` — form control 共用 state(disabled / readonly / invalid)
|
|
139
|
+
- `../Input/input.spec.md` — `aria-required` / `aria-invalid` 實作端(input.tsx)
|
|
143
140
|
|
|
144
141
|
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
145
142
|
|
|
146
143
|
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
147
144
|
|
|
145
|
+
- `combobox.spec.md`
|
|
146
|
+
- `date-picker.spec.md`
|
|
147
|
+
- `field-controls.spec.md`
|
|
148
|
+
- `field.spec.md`
|
|
148
149
|
- `link-input.spec.md`
|
|
149
150
|
- `textarea.spec.md`
|
|
150
151
|
- `time-picker.spec.md`
|
|
@@ -7,6 +7,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|
|
7
7
|
import { FieldControlGroup } from './field-control-group'
|
|
8
8
|
import { Select } from '@/design-system/components/Select/select'
|
|
9
9
|
import { Input } from '@/design-system/components/Input/input'
|
|
10
|
+
import { OPERATOR_REGISTRY } from '@/design-system/components/DataTable/filter-operators'
|
|
10
11
|
|
|
11
12
|
const meta: Meta<typeof FieldControlGroup> = {
|
|
12
13
|
title: 'Design System/Components/FieldControlGroup/設計規格',
|
|
@@ -20,10 +21,10 @@ const FIELDS = [
|
|
|
20
21
|
{ value: 'sku', label: 'SKU' },
|
|
21
22
|
{ value: 'name', label: '名稱' },
|
|
22
23
|
]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// op 選項消費 OPERATOR_REGISTRY SSOT(audit dim 32 — 禁 hardcode op 字串)
|
|
25
|
+
const OPS = OPERATOR_REGISTRY.string
|
|
26
|
+
.filter((o) => ['is', 'contains'].includes(o.op))
|
|
27
|
+
.map((o) => ({ value: o.op, label: o.label }))
|
|
27
28
|
|
|
28
29
|
/* Overview */
|
|
29
30
|
export const Overview: Story = {
|
|
@@ -42,9 +43,9 @@ export const Overview: Story = {
|
|
|
42
43
|
<section>
|
|
43
44
|
<h3 className="text-body font-bold mb-2">範例</h3>
|
|
44
45
|
<FieldControlGroup block>
|
|
45
|
-
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} />
|
|
46
|
-
<Select className="w-[100px]" options={OPS} value="contains" onChange={() => {}} />
|
|
47
|
-
<Input className="flex-1" defaultValue="無線滑鼠" />
|
|
46
|
+
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
|
|
47
|
+
<Select className="w-[100px]" options={OPS} value="contains" onChange={() => {}} aria-label="篩選運算子" />
|
|
48
|
+
<Input className="flex-1" defaultValue="無線滑鼠" aria-label="篩選值" />
|
|
48
49
|
</FieldControlGroup>
|
|
49
50
|
</section>
|
|
50
51
|
</div>
|
|
@@ -60,8 +61,8 @@ export const SizeMatrix: Story = {
|
|
|
60
61
|
<div key={size}>
|
|
61
62
|
<p className="text-caption text-fg-muted mb-2">size="{size}"</p>
|
|
62
63
|
<FieldControlGroup block>
|
|
63
|
-
<Select size={size} className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} />
|
|
64
|
-
<Input size={size} className="flex-1" defaultValue="無線滑鼠" />
|
|
64
|
+
<Select size={size} className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
|
|
65
|
+
<Input size={size} className="flex-1" defaultValue="無線滑鼠" aria-label="篩選值" />
|
|
65
66
|
</FieldControlGroup>
|
|
66
67
|
</div>
|
|
67
68
|
))}
|
|
@@ -77,22 +78,22 @@ export const StateBehavior: Story = {
|
|
|
77
78
|
<div>
|
|
78
79
|
<p className="text-caption text-fg-muted mb-2">default</p>
|
|
79
80
|
<FieldControlGroup block>
|
|
80
|
-
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} />
|
|
81
|
-
<Input className="flex-1" defaultValue="無線滑鼠" />
|
|
81
|
+
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
|
|
82
|
+
<Input className="flex-1" defaultValue="無線滑鼠" aria-label="篩選值" />
|
|
82
83
|
</FieldControlGroup>
|
|
83
84
|
</div>
|
|
84
85
|
<div>
|
|
85
86
|
<p className="text-caption text-fg-muted mb-2">disabled(整 group children 各自 disabled)</p>
|
|
86
87
|
<FieldControlGroup block>
|
|
87
|
-
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} disabled />
|
|
88
|
-
<Input className="flex-1" defaultValue="無線滑鼠" disabled />
|
|
88
|
+
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} disabled aria-label="篩選欄位" />
|
|
89
|
+
<Input className="flex-1" defaultValue="無線滑鼠" disabled aria-label="篩選值" />
|
|
89
90
|
</FieldControlGroup>
|
|
90
91
|
</div>
|
|
91
92
|
<div>
|
|
92
93
|
<p className="text-caption text-fg-muted mb-2">cell error(其中一 child invalid → border-error;聚焦該 child 時才提升到 z-3 蓋過鄰接 border)</p>
|
|
93
94
|
<FieldControlGroup block>
|
|
94
|
-
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} />
|
|
95
|
-
<Input className="flex-1" defaultValue="無線滑鼠" error />
|
|
95
|
+
<Select className="w-[120px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
|
|
96
|
+
<Input className="flex-1" defaultValue="無線滑鼠" error aria-label="篩選值" />
|
|
96
97
|
</FieldControlGroup>
|
|
97
98
|
</div>
|
|
98
99
|
</div>
|
|
@@ -6,6 +6,7 @@ import { Field, FieldLabel, FieldGroup } from '@/design-system/components/Field/
|
|
|
6
6
|
import { Select } from '@/design-system/components/Select/select'
|
|
7
7
|
import { Input } from '@/design-system/components/Input/input'
|
|
8
8
|
import { Button } from '@/design-system/components/Button/button'
|
|
9
|
+
import { OPERATOR_REGISTRY } from '@/design-system/components/DataTable/filter-operators'
|
|
9
10
|
|
|
10
11
|
const meta: Meta<typeof FieldControlGroup> = {
|
|
11
12
|
title: 'Design System/Components/FieldControlGroup/設計原則',
|
|
@@ -17,7 +18,10 @@ type Story = StoryObj<typeof FieldControlGroup>
|
|
|
17
18
|
|
|
18
19
|
const CODES = [{ value: '+886', label: '+886' }, { value: '+1', label: '+1' }]
|
|
19
20
|
const FIELDS = [{ value: 'sku', label: 'SKU' }, { value: 'name', label: '名稱' }]
|
|
20
|
-
|
|
21
|
+
// op 選項消費 OPERATOR_REGISTRY SSOT(audit dim 32 — 禁 hardcode op 字串)
|
|
22
|
+
const OPS = OPERATOR_REGISTRY.string
|
|
23
|
+
.filter((o) => ['is', 'contains'].includes(o.op))
|
|
24
|
+
.map((o) => ({ value: o.op, label: o.label }))
|
|
21
25
|
|
|
22
26
|
/* WhenToUse + WhenNotToUse + VsRule + ContentGuidelines = single UsageGuidance */
|
|
23
27
|
export const UsageGuidance: Story = {
|
|
@@ -139,6 +139,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
139
139
|
| hover(子)| 該 child border-hover | 3 |
|
|
140
140
|
| focus / focus-within(子)| 該 child focus ring | 3 |
|
|
141
141
|
| disabled(子)| 該 child disabled style + **FCG-local override `border-[var(--border-opaque)]`**(K12,2026-05-04) | 0 |
|
|
142
|
+
| error(子)| 該 child border-error | 2(預設層;聚焦該 child 時才升 3 蓋過鄰接 border)|
|
|
142
143
|
|
|
143
144
|
**Disabled border integrity canonical(K12,2026-05-04)**:全域 disabled = `border-transparent`(讓 standalone field 視覺輕量),但**FCG context 下,disabled child 強制 `border-[var(--border-opaque)]`** — 確保:(a) FCG 整體外圈 border 健在,(b) inner divider 健在(不會因兩相鄰 disabled cells 都 transparent 而消失)。bg-disabled 仍區分狀態,border 維護群組視覺整合性。對齊 [Bootstrap input-group](https://getbootstrap.com/docs/5.3/forms/input-group/) / [Ant Space.Compact](https://ant.design/components/space#spacecompact) disabled idiom。
|
|
144
145
|
|
|
@@ -153,7 +154,6 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
153
154
|
**Token 系統設計**:`--border-opaque` 在 `semantic.css` 新增(grep `--border-opaque:` 查定義行,不寫死行號避免漂移),語意「視覺等同 `--border` 但 alpha-immune」。對齊 [Ant Design `colorBorderSecondary`](https://ant.design/docs/react/customize-theme#seedtoken) solid idiom — Ant 用此 token 在 table 外框 + row divider(non-white bg 場景),跟 input alpha border 視覺層級分。
|
|
154
155
|
|
|
155
156
|
**為什麼不 override bg**:user 明確要求 disabled cells 有底色(辨識 state)。bg 灰底是 disabled state 的主要視覺載體,FCG context 不應抹除。
|
|
156
|
-
| error(子)| 該 child border-error | 2(預設層;聚焦該 child 時才升 3 蓋過鄰接 border)|
|
|
157
157
|
|
|
158
158
|
**整 row error**:目前 v1 不支援 row-level error(走 cell-level)。未來若需可走 outer border-error wrapper,但 v1 follow Ant 不做。
|
|
159
159
|
|
|
@@ -162,6 +162,15 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
162
162
|
- Loading:子 control 各自處理(Input loading state / Select loading)
|
|
163
163
|
- Empty:N/A(layout primitive,無資料概念)
|
|
164
164
|
- 驗證:子 control 自管(form library 透過 Field 處理)
|
|
165
|
+
- 「子必 direct child」規則**無 runtime 偵測**——違反(多包 wrapper div)時的症狀是圓角破圖(CSS `[&>*]` 命中 wrapper),見禁止事項 2026-05-04 #2;靠 review + Storybook 目視抓
|
|
166
|
+
- a11y 驗證:Storybook a11y addon panel 0 critical violation;Tab 順序 = children DOM 順序
|
|
167
|
+
|
|
168
|
+
## 邊界案例
|
|
169
|
+
|
|
170
|
+
- **子高度不一**:容器 `items-stretch`,但 field controls 自帶固定 `h-field-*`,不會被拉齊——混 size 即視覺高低差(故禁止,見禁止事項)
|
|
171
|
+
- **某子 disabled、某子 edit**:機制上可行(disabled 子降 z-0 + K12 border 維持 divider),但語意一體應一致(見禁止事項);**全組 disabled** 走 K12 canonical(見 States)
|
|
172
|
+
- **極窄容器**:無特化處理——固定寬 children(`w-[Xpx]`)溢出容器、`flex-1` children 壓縮;子自管 width 的對價,consumer 自行配置
|
|
173
|
+
- **單一 child**:radius 選擇器 `:first-child:not(:last-child)` 不命中,圓角完整保留(等同未包 group,但此時應直接用 Field)
|
|
165
174
|
|
|
166
175
|
## 世界級對照
|
|
167
176
|
|
|
@@ -174,6 +183,13 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
174
183
|
|
|
175
184
|
3/4 共識 → 我們的實作對齊 Ant + Bootstrap 主軸。
|
|
176
185
|
|
|
186
|
+
## 相關
|
|
187
|
+
|
|
188
|
+
- `../Field/field.spec.md` — FieldGroup(多 Field 垂直堆疊近親,gap 分離)的 home
|
|
189
|
+
- `../Button/button-group.tsx` — 同 border-collapse mechanism 的 Button 版(無獨立 spec,機制同源)
|
|
190
|
+
- `../Field/field-controls.spec.md` — children(Input / Select / DatePicker 等)共用 Field control 規則
|
|
191
|
+
- `../Checkbox/checkbox.spec.md` / `../RadioGroup/radio-group.spec.md` — semantic group 近親(1 question 多 options,非本元件 scope)
|
|
192
|
+
|
|
177
193
|
## 變更紀錄
|
|
178
194
|
|
|
179
195
|
- 2026-05-04 v1:Initial。基於 Ant compact-item.ts source verify 訂機制。Filter / Sort 對齊 Ant W-A 子自管 width。
|