@qijenchen/design-system 0.1.0-beta.10 → 0.1.0-beta.13
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 +201 -0
- package/README.md +7 -15
- package/cli-init.mjs +90 -0
- package/ds-canonical/commands/README.md +26 -0
- package/ds-canonical/commands/gov-status.md +79 -0
- package/ds-canonical/hooks/README.md +145 -0
- package/ds-canonical/hooks/_log-fire.sh +44 -0
- package/ds-canonical/hooks/block_prototype_imports.py +111 -0
- package/ds-canonical/hooks/check_app_shell_primary_header_consistency.sh +68 -0
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +88 -0
- package/ds-canonical/hooks/check_audit_sample_escape.sh +73 -0
- package/ds-canonical/hooks/check_benchmark_citation.sh +106 -0
- package/ds-canonical/hooks/check_canonical_propagation.sh +189 -0
- package/ds-canonical/hooks/check_chrome_header_handcraft.sh +70 -0
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +83 -0
- package/ds-canonical/hooks/check_codex_collab_5step.sh +108 -0
- package/ds-canonical/hooks/check_datatable_invariants.sh +117 -0
- package/ds-canonical/hooks/check_dim_count_drift.sh +72 -0
- package/ds-canonical/hooks/check_field_controls_contracts.sh +110 -0
- package/ds-canonical/hooks/check_field_family_invariants.sh +205 -0
- package/ds-canonical/hooks/check_file_size_budget.sh +60 -0
- package/ds-canonical/hooks/check_header_with_tabs_border.sh +87 -0
- package/ds-canonical/hooks/check_main_branch_workbench.sh +93 -0
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +165 -0
- package/ds-canonical/hooks/check_opacity_token_usage.sh +149 -0
- package/ds-canonical/hooks/check_pattern_invariants.sh +194 -0
- package/ds-canonical/hooks/check_peoplepicker_ssot_drift.sh +56 -0
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +53 -0
- package/ds-canonical/hooks/check_propose_plain_chinese.sh +74 -0
- package/ds-canonical/hooks/check_propose_pre_grep_verify.sh +70 -0
- package/ds-canonical/hooks/check_select_all_canonical.sh +58 -0
- package/ds-canonical/hooks/check_solo_workflow.sh +258 -0
- package/ds-canonical/hooks/check_spec_class_drift.sh +88 -0
- package/ds-canonical/hooks/check_story_invariants.sh +612 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +105 -0
- package/ds-canonical/hooks/check_tab_lg_chrome_header_equal.sh +66 -0
- package/ds-canonical/hooks/check_wrapper_primitive_schema_drift.sh +104 -0
- package/ds-canonical/hooks/enforce_home_charter.sh +44 -0
- package/ds-canonical/hooks/inject_pending_self_audit.sh +204 -0
- package/ds-canonical/hooks/lib/_approval_re.sh +33 -0
- package/ds-canonical/hooks/lib/_code_quality.sh +73 -0
- package/ds-canonical/hooks/lib/_cva_default_sync.sh +69 -0
- package/ds-canonical/hooks/lib/_governance_coverage_check.sh +49 -0
- package/ds-canonical/hooks/lib/_hardcoded_strings.sh +163 -0
- package/ds-canonical/hooks/lib/_layout_space_canonical.sh +56 -0
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +141 -0
- package/ds-canonical/hooks/lib/_person_data_richness.sh +42 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +48 -0
- package/ds-canonical/hooks/lib/_token_hygiene.sh +95 -0
- package/ds-canonical/hooks/log_governance_fires.sh +50 -0
- package/ds-canonical/hooks/log_skill_invokes.sh +41 -0
- package/ds-canonical/hooks/post_edit_dispatcher.sh +62 -0
- package/ds-canonical/hooks/retired/check_anatomy_section_numbering.sh +106 -0
- package/ds-canonical/hooks/retired/check_avatar_hovercard.sh +90 -0
- package/ds-canonical/hooks/retired/check_button_icon_literal.sh.retired-2026-04-28 +38 -0
- package/ds-canonical/hooks/retired/check_container_breathing.sh +142 -0
- package/ds-canonical/hooks/retired/check_governance_compliance.sh +61 -0
- package/ds-canonical/hooks/retired/check_icon_only_padding_formula.sh +104 -0
- package/ds-canonical/hooks/retired/check_item_content_primitive.sh +150 -0
- package/ds-canonical/hooks/retired/check_item_list_gap.sh +153 -0
- package/ds-canonical/hooks/retired/check_sideoffset_canonical.sh +65 -0
- package/ds-canonical/hooks/retired/check_spec_iteration_tag.sh +87 -0
- package/ds-canonical/hooks/retired/check_ssot_consultation.sh +88 -0
- package/ds-canonical/hooks/retired/check_sync_update.sh +20 -0
- package/ds-canonical/hooks/retired/check_third_party_dom_verified.sh +95 -0
- package/ds-canonical/hooks/retired/enforce_home_charter.sh +125 -0
- package/ds-canonical/hooks/retired/post_edit_canonical_interrogate.sh +109 -0
- package/ds-canonical/hooks/retired/pre_edit_spec_check.sh +68 -0
- package/ds-canonical/hooks/retired/pre_new_component_spec.sh +39 -0
- package/ds-canonical/hooks/retired/pre_write_subsumption_check.sh +112 -0
- package/ds-canonical/hooks/retired/stop_meta_self_audit.sh.retired-2026-05-13 +76 -0
- package/ds-canonical/hooks/retired/tests/test_check_anatomy_section_numbering.sh +14 -0
- package/ds-canonical/hooks/retired/tests/test_check_avatar_hovercard.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_container_breathing.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_governance_compliance.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_icon_only_padding_formula.sh +79 -0
- package/ds-canonical/hooks/retired/tests/test_check_item_content_primitive.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_item_list_gap.sh +163 -0
- package/ds-canonical/hooks/retired/tests/test_check_sideoffset_canonical.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_spec_iteration_tag.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_ssot_consultation.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_check_sync_update.sh +14 -0
- package/ds-canonical/hooks/retired/tests/test_check_third_party_dom_verified.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_enforce_home_charter.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_pre_edit_spec_check.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_pre_new_component_spec.sh +15 -0
- package/ds-canonical/hooks/retired/tests/test_pre_write_subsumption_check.sh +63 -0
- package/ds-canonical/hooks/session_start_governance_check.sh +263 -0
- package/ds-canonical/hooks/stop_passive_logging.sh +322 -0
- package/ds-canonical/hooks/stop_self_audit.sh +450 -0
- package/ds-canonical/hooks/tests/KNOWN-BROKEN.md +15 -0
- package/ds-canonical/hooks/tests/run-all.sh +76 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +143 -0
- package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +140 -0
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +115 -0
- package/ds-canonical/hooks/tests/test_check_audit_sample_escape.sh +93 -0
- package/ds-canonical/hooks/tests/test_check_benchmark_citation.sh +115 -0
- package/ds-canonical/hooks/tests/test_check_canonical_propagation.sh +133 -0
- package/ds-canonical/hooks/tests/test_check_chrome_header_handcraft.sh +123 -0
- package/ds-canonical/hooks/tests/test_check_code_quality.sh +15 -0
- package/ds-canonical/hooks/tests/test_check_codex_collab_5step.sh +96 -0
- package/ds-canonical/hooks/tests/test_check_cva_default_sync.sh +15 -0
- package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +122 -0
- package/ds-canonical/hooks/tests/test_check_dim_count_drift.sh +98 -0
- package/ds-canonical/hooks/tests/test_check_field_controls_contracts.sh +126 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +194 -0
- package/ds-canonical/hooks/tests/test_check_file_size_budget.sh +32 -0
- package/ds-canonical/hooks/tests/test_check_hardcoded_strings.sh +14 -0
- package/ds-canonical/hooks/tests/test_check_header_with_tabs_border.sh +110 -0
- package/ds-canonical/hooks/tests/test_check_layout_space_canonical.sh +73 -0
- package/ds-canonical/hooks/tests/test_check_main_branch_workbench.sh +147 -0
- package/ds-canonical/hooks/tests/test_check_naming_and_abstraction.sh +136 -0
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +110 -0
- package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +126 -0
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +148 -0
- package/ds-canonical/hooks/tests/test_check_peoplepicker_ssot_drift.sh +108 -0
- package/ds-canonical/hooks/tests/test_check_person_data_richness.sh +58 -0
- package/ds-canonical/hooks/tests/test_check_pixel_quantified_audit.sh +142 -0
- package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +126 -0
- package/ds-canonical/hooks/tests/test_check_propose_pre_grep_verify.sh +117 -0
- package/ds-canonical/hooks/tests/test_check_select_all_canonical.sh +125 -0
- package/ds-canonical/hooks/tests/test_check_solo_workflow.sh +201 -0
- package/ds-canonical/hooks/tests/test_check_spec_class_drift.sh +135 -0
- package/ds-canonical/hooks/tests/test_check_story_anatomy.sh.broken +197 -0
- package/ds-canonical/hooks/tests/test_check_story_category.sh.broken +187 -0
- package/ds-canonical/hooks/tests/test_check_story_compile_drift.sh +15 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +209 -0
- package/ds-canonical/hooks/tests/test_check_story_name_jargon.sh.broken +53 -0
- package/ds-canonical/hooks/tests/test_check_story_slot_split.sh +156 -0
- package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +176 -0
- package/ds-canonical/hooks/tests/test_check_tab_lg_chrome_header_equal.sh +138 -0
- package/ds-canonical/hooks/tests/test_check_token_hygiene.sh +21 -0
- package/ds-canonical/hooks/tests/test_check_wrapper_primitive_schema_drift.sh +169 -0
- package/ds-canonical/hooks/tests/test_enforce_home_charter.sh +77 -0
- package/ds-canonical/hooks/tests/test_inject_pending_self_audit.sh +125 -0
- package/ds-canonical/hooks/tests/test_log_governance_fires.sh +10 -0
- package/ds-canonical/hooks/tests/test_log_skill_invokes.sh +7 -0
- package/ds-canonical/hooks/tests/test_post_edit_dispatcher.sh +108 -0
- package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +143 -0
- package/ds-canonical/hooks/tests/test_stop_capture_metrics.sh +95 -0
- package/ds-canonical/hooks/tests/test_stop_governance_drift_check.sh.broken +125 -0
- package/ds-canonical/hooks/tests/test_stop_harvest_corrections.sh +10 -0
- package/ds-canonical/hooks/tests/test_stop_passive_logging.sh +100 -0
- package/ds-canonical/hooks/tests/test_stop_self_audit.sh +76 -0
- package/ds-canonical/hooks/tests/test_stop_tsc_sanity.sh +10 -0
- package/ds-canonical/references/README.md +43 -0
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +74 -0
- package/ds-canonical/references/build-ui-canonicals.md +69 -0
- package/ds-canonical/references/cva-patterns.md +41 -0
- package/ds-canonical/references/drag-canonical.md +331 -0
- package/ds-canonical/references/item-anatomy-recipe.md +225 -0
- package/ds-canonical/references/naming-conventions.md +56 -0
- package/ds-canonical/references/principle-dim-map.json +515 -0
- package/ds-canonical/references/props-naming.md +45 -0
- package/ds-canonical/references/spec-rules.md +58 -0
- package/ds-canonical/references/ssot-consultation.md +63 -0
- package/ds-canonical/references/ssot-index.md +40 -0
- package/ds-canonical/references/story-baseline-registry.json +79 -0
- package/ds-canonical/references/structural-token-retention.md +42 -0
- package/ds-canonical/references/tailwind-gotchas.md +87 -0
- package/ds-canonical/references/ui-dev-rules.md +60 -0
- package/ds-canonical/rules/README.md +34 -0
- package/ds-canonical/rules/meta-patterns.md +87 -0
- package/ds-canonical/rules/self-verify.md +53 -0
- package/ds-canonical/rules/spec-rules.md +25 -0
- package/ds-canonical/rules/story-rules.md +56 -0
- package/ds-canonical/rules/ui-development.md +87 -0
- package/ds-canonical/skills/README.md +88 -0
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +181 -0
- package/ds-canonical/skills/code-quality-audit/SKILL.md +63 -0
- package/ds-canonical/skills/codex-collab/SKILL.md +249 -0
- package/ds-canonical/skills/codex-collab/references/brief-template.md +48 -0
- package/ds-canonical/skills/codex-collab/references/transport.md +58 -0
- package/ds-canonical/skills/codify-corrections/SKILL.md +184 -0
- package/ds-canonical/skills/codify-principle/SKILL.md +151 -0
- package/ds-canonical/skills/component-quality-gate/SKILL.md +102 -0
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +79 -0
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +247 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +123 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +165 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +91 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +229 -0
- package/ds-canonical/skills/delivery-handoff/references/flow-diagram.md +180 -0
- package/ds-canonical/skills/delivery-handoff/references/handoff-template.md +177 -0
- package/ds-canonical/skills/delivery-handoff/references/inventory-checklist.md +196 -0
- package/ds-canonical/skills/design-system-audit/SKILL.md +343 -0
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1260 -0
- package/ds-canonical/skills/design-system-audit/references/checkpoints.md +240 -0
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +240 -0
- package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +364 -0
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +175 -0
- package/ds-canonical/skills/design-system-audit/references/spec-template.md +66 -0
- package/ds-canonical/skills/ensure-canonical/SKILL.md +196 -0
- package/ds-canonical/skills/governance-health/SKILL.md +146 -0
- package/ds-canonical/skills/knowledge-prune/SKILL.md +303 -0
- package/ds-canonical/skills/new-component/SKILL.md +170 -0
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +85 -0
- package/ds-canonical/skills/performance-audit/SKILL.md +107 -0
- package/ds-canonical/skills/product-ui-audit/SKILL.md +230 -0
- package/ds-canonical/skills/product-ui-audit/references/audit-checks.md +246 -0
- package/ds-canonical/skills/product-ui-audit/references/common-misuses.md +329 -0
- package/ds-canonical/skills/product-ui-audit/references/report-template.md +159 -0
- package/ds-canonical/skills/propose-options/SKILL.md +177 -0
- package/ds-canonical/skills/prototype/SKILL.md +244 -0
- package/ds-canonical/skills/prototype/references/audit-checks.md +37 -0
- package/ds-canonical/skills/prototype/references/benchmark-sources.md +94 -0
- package/ds-canonical/skills/prototype/references/checkpoints.md +191 -0
- package/ds-canonical/skills/prototype/references/evaluation-matrix.md +141 -0
- package/ds-canonical/skills/prototype/references/ooux-template.md +198 -0
- package/ds-canonical/skills/prototype/references/proposal-template.md +229 -0
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +198 -0
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +159 -0
- package/ds-canonical/skills/story-writing/SKILL.md +122 -0
- package/ds-canonical/skills/story-writing/references/anatomy-standard.md +217 -0
- package/ds-canonical/skills/story-writing/references/category-templates.md +174 -0
- package/ds-canonical/skills/story-writing/references/example-selection.md +70 -0
- package/ds-canonical/skills/story-writing/references/self-check.md +20 -0
- package/ds-canonical/skills/ux-audit/SKILL.md +130 -0
- package/ds-canonical/skills/visual-audit/SKILL.md +245 -0
- package/ds-canonical/skills/visual-audit/output/.gitkeep +0 -0
- package/ds-canonical/skills/visual-audit/references/audit-architecture.md +100 -0
- package/ds-canonical/skills/visual-audit/references/visual-checklist.md +297 -0
- package/ds-canonical/skills/visual-audit/references/world-class-benchmarks.md +198 -0
- package/package.json +9 -5
- package/src/components/Accordion/accordion.spec.md +114 -0
- package/src/components/Alert/alert.spec.md +197 -0
- package/src/components/AppShell/app-shell.spec.md +331 -0
- package/src/components/AspectRatio/aspect-ratio.spec.md +134 -0
- package/src/components/Avatar/avatar.spec.md +329 -0
- package/src/components/Badge/badge.spec.md +380 -0
- package/src/components/Breadcrumb/breadcrumb.spec.md +257 -0
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +210 -0
- package/src/components/Button/button.spec.md +460 -0
- package/src/components/Calendar/calendar.spec.md +242 -0
- package/src/components/Carousel/carousel.spec.md +253 -0
- package/src/components/Chart/chart.spec.md +155 -0
- package/src/components/Checkbox/checkbox.spec.md +344 -0
- package/src/components/Chip/chip.spec.md +237 -0
- package/src/components/CircularProgress/circular-progress.spec.md +268 -0
- package/src/components/Coachmark/coachmark.spec.md +230 -0
- package/src/components/Combobox/combobox.spec.md +180 -0
- package/src/components/Command/command.spec.md +171 -0
- package/src/components/DataTable/data-table.spec.md +525 -0
- package/src/components/DateGrid/date-grid.spec.md +215 -0
- package/src/components/DatePicker/date-picker.spec.md +334 -0
- package/src/components/DescriptionList/description-list.spec.md +214 -0
- package/src/components/Dialog/dialog.spec.md +202 -0
- package/src/components/DropdownMenu/dropdown-menu.spec.md +250 -0
- package/src/components/Empty/empty.spec.md +214 -0
- package/src/components/Field/field-controls.spec.md +338 -0
- package/src/components/Field/field.spec.md +438 -0
- package/src/components/Field/form-validation.spec.md +152 -0
- package/src/components/FieldControlGroup/field-control-group.spec.md +176 -0
- package/src/components/FileItem/file-item.spec.md +467 -0
- package/src/components/FileUpload/file-upload.spec.md +123 -0
- package/src/components/FileViewer/file-viewer.spec.md +373 -0
- package/src/components/HoverCard/hover-card.spec.md +157 -0
- package/src/components/Input/input.spec.md +193 -0
- package/src/components/LinkInput/link-input.spec.md +130 -0
- package/src/components/Menu/menu-item.spec.md +290 -0
- package/src/components/NameCard/name-card.spec.md +171 -0
- package/src/components/Notice/notice.spec.md +149 -0
- package/src/components/NumberInput/number-input.spec.md +126 -0
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +120 -0
- package/src/components/PeoplePicker/people-picker.spec.md +263 -0
- package/src/components/Popover/popover.spec.md +198 -0
- package/src/components/ProgressBar/progress-bar.spec.md +232 -0
- package/src/components/RadioGroup/radio-group.spec.md +141 -0
- package/src/components/Rating/rating.spec.md +208 -0
- package/src/components/ScrollArea/scroll-area.spec.md +145 -0
- package/src/components/SegmentedControl/segmented-control.spec.md +295 -0
- package/src/components/Select/select.spec.md +299 -0
- package/src/components/SelectMenu/select-menu.spec.md +220 -0
- package/src/components/SelectionControl/selection-item.spec.md +128 -0
- package/src/components/Separator/separator.spec.md +109 -0
- package/src/components/Sheet/sheet.spec.md +148 -0
- package/src/components/Sidebar/sidebar.spec.md +713 -0
- package/src/components/Skeleton/skeleton.spec.md +104 -0
- package/src/components/Slider/slider.spec.md +353 -0
- package/src/components/Steps/steps.spec.md +465 -0
- package/src/components/Switch/switch.spec.md +215 -0
- package/src/components/Tabs/tabs.spec.md +314 -0
- package/src/components/Tag/tag.spec.md +282 -0
- package/src/components/Textarea/textarea.spec.md +151 -0
- package/src/components/TimePicker/time-picker.spec.md +279 -0
- package/src/components/Toast/toast.spec.md +177 -0
- package/src/components/Tooltip/tooltip.spec.md +139 -0
- package/src/components/TreeView/tree-view.spec.md +374 -0
- package/src/patterns/action-bar/action-bar.spec.md +458 -0
- package/src/patterns/element-anatomy/element-anatomy.spec.md +215 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +315 -0
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1042 -0
- package/src/patterns/header-canonical/header-canonical.spec.md +285 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +191 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +428 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +109 -0
- package/src/tokens/color/color.spec.md +804 -0
- package/src/tokens/density/density.spec.md +127 -0
- package/src/tokens/elevation/elevation.spec.md +81 -0
- package/src/tokens/layoutSpace/layoutSpace.spec.md +314 -0
- package/src/tokens/motion/motion.spec.md +97 -0
- package/src/tokens/opacity/opacity.spec.md +78 -0
- package/src/tokens/orphan-tokens.spec.md +117 -0
- package/src/tokens/radius/radius.spec.md +123 -0
- package/src/tokens/typography/typography.spec.md +202 -0
- package/src/tokens/uiSize/uiSize.spec.md +438 -0
- package/src/styles/preset.css +0 -31
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Field
|
|
3
|
+
family: self-contained
|
|
4
|
+
traits:
|
|
5
|
+
- hasInteractiveStates
|
|
6
|
+
- isStructural
|
|
7
|
+
variants: {}
|
|
8
|
+
sizes: {}
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline; canonical source URLs in frontmatter benchmark list. -->
|
|
12
|
+
|
|
13
|
+
# Field 設計原則
|
|
14
|
+
|
|
15
|
+
> **Foundational SSOT rationale**(cap 800,2026-04-25 approved):
|
|
16
|
+
> 表單 layout container canonical。Input / NumberInput / Checkbox / RadioGroup / Switch / Select / Combobox / DatePicker 等 field controls 皆消費本 spec 的 `orientation` / `controlLayout` / context API(mode / disabled / required / invalid / id)/ 水平對齊公式。跨元件 pattern canonical,scope 本質 > 單一元件。
|
|
17
|
+
|
|
18
|
+
## 定位
|
|
19
|
+
|
|
20
|
+
Field 是**表單欄位的佈局容器**。只負責排版(label / control / description / error 的空間關係)與**狀態 context**(把 mode / disabled / required / invalid / id 傳給子元件),**不擁有任何資料型別邏輯**。
|
|
21
|
+
|
|
22
|
+
與資料相關的一切(格式化、驗證、readonly 呈現、DataTable cell 顯示)住在各個資料型別的 Control 元件本身(Input、NumberInput、Checkbox、Switch 等)。Field 不 wrap、不代理、不轉換它們的行為。
|
|
23
|
+
|
|
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
|
+
|
|
26
|
+
**Layout Family**:Field **不屬於** 4-Family Model 的 element layout families——它是 form composition pattern(包 Family 4 control + label + description)。見 CLAUDE.md「系統內部 Layout — 4-Family Model」→「Field Composition」段落。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 與 Field Controls 的職責切分
|
|
31
|
+
|
|
32
|
+
Field 和 Field Controls(Input / NumberInput / DatePicker / Select / Combobox / LinkInput / PeoplePicker / Textarea)是兩件事:
|
|
33
|
+
|
|
34
|
+
- **Field**(本元件):只管佈局 + 狀態 context
|
|
35
|
+
- **Field Controls / Checkbox / Switch / RadioGroup**:管自己的資料型別 + edit/readonly/disabled 三態 + 格式化(XxxDisplay 供 DataTable 共用)
|
|
36
|
+
|
|
37
|
+
這麼拆的理由:**Checkbox 在 table cell、form field、settings row 應該是同一個 primitive**,不該為了進到 form 就被包一層 CheckboxField;form 的高度對齊由 Field 的 control area 負責,不由 primitive 本身負責。Field Controls 的詳細共用規則見 `Field/field-controls.spec.md`。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 結構
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Field orientation="vertical | horizontal" labelWidth="120px">
|
|
45
|
+
<FieldLabel>姓名</FieldLabel>
|
|
46
|
+
<Input value={name} onChange={setName} />
|
|
47
|
+
<FieldDescription>會顯示在專案列表標題</FieldDescription>
|
|
48
|
+
<FieldError>{errors.name}</FieldError>
|
|
49
|
+
</Field>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
子元件有四種 slot:
|
|
53
|
+
|
|
54
|
+
| Slot | 元件 | 職責 |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| label | `<FieldLabel>` | 欄位名稱,handles required 星號、disabled 灰化、htmlFor 連結 |
|
|
57
|
+
| control | 任何非 label/desc/error 的 child | 輸入元件本體(Input、Checkbox、Switch...) |
|
|
58
|
+
| description | `<FieldDescription>` | 次要說明文字,fg-secondary |
|
|
59
|
+
| error | `<FieldError>` | 錯誤訊息,error-text 色,`role="alert"`,無內容時不渲染 |
|
|
60
|
+
|
|
61
|
+
子元件寫法採**扁平結構**(直接作為 Field children),Field 內部用 `displayName` 判別 slot 類型並自動組合。consumer 不需要包 wrapper。
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 樣式規範(全部 codify 在 field.tsx,不依賴 consumer)
|
|
66
|
+
|
|
67
|
+
| 元素 | Token / 值 |
|
|
68
|
+
|---|---|
|
|
69
|
+
| FieldLabel(一般) | `text-body`(14px)/ `text-foreground`(neutral-9) |
|
|
70
|
+
| FieldLabel(disabled) | `text-body` / `text-fg-disabled`(neutral-6) |
|
|
71
|
+
| FieldLabel 的 required `*` | `text-fg-muted`(neutral-7),**貼齊 label 文字無 gap**,disabled 時 `text-fg-disabled` |
|
|
72
|
+
| FieldDescription(一般) | `text-body`(14px)/ `text-fg-secondary`(neutral-8) |
|
|
73
|
+
| FieldDescription(disabled) | `text-body` / `text-fg-disabled` |
|
|
74
|
+
| FieldError | `text-body`(14px)/ `text-error-text` / `role="alert"` |
|
|
75
|
+
| Field 內部 gap(vertical) | `gap-1`(4px) |
|
|
76
|
+
| Field 內部 gap(horizontal,content 欄) | `gap-1`(4px) |
|
|
77
|
+
| Horizontal 模式 label 與 control 的 gap | `gap-x-3`(12px) |
|
|
78
|
+
| Control area(任何 size) | `min-h-field-{size}` + `flex items-center` |
|
|
79
|
+
|
|
80
|
+
**Label / Description / Error 字體固定 `text-body`(14px),不隨 field size 變。** Field size 只影響 input 高度,不影響表單佈局元素的 typography。世界級系統(Material、Ant Design、Atlassian、Carbon、Polaris)都是固定 label/helper text size。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Orientation
|
|
85
|
+
|
|
86
|
+
### vertical(預設)
|
|
87
|
+
|
|
88
|
+
Label 在上、control 在下、description 和 error 往下堆疊。垂直節奏 `gap-1`(4px)。
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
[Label]
|
|
92
|
+
[Control] ← min-h-field-md,control 垂直置中於此 box
|
|
93
|
+
[Description]
|
|
94
|
+
[Error]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
適用:標準表單、密集欄位。
|
|
98
|
+
|
|
99
|
+
### horizontal
|
|
100
|
+
|
|
101
|
+
Label 在左、control + description + error 在右欄垂直堆疊。Label 與 control **垂直對齊於第一行中線**(見下一段)。
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
[Label] [Control ]
|
|
105
|
+
[Description]
|
|
106
|
+
[Error]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
適用:settings 面板、寬螢幕 form、iOS-style 欄位。
|
|
110
|
+
|
|
111
|
+
Horizontal 模式下 label 的欄寬由 `labelWidth` prop 控制(任何 CSS length 值),預設 `auto` 由 label 內容撐開。內部用 CSS variable `--field-label-width` 傳給 grid template column。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Horizontal 模式 label 垂直對齊
|
|
116
|
+
|
|
117
|
+
依 `controlLayout`(Field 自動偵測)分兩套對齊語意:
|
|
118
|
+
|
|
119
|
+
**A. Inline control**(Input/Button/Switch/SegmentedControl)— control 有固定 `--field-height-{size}`,可談中線。
|
|
120
|
+
- label ≤ field-height → 第一行中線對齊 control 中線(視覺置中)
|
|
121
|
+
- label > field-height → 齊頂對齊 control top
|
|
122
|
+
- 實作:`min-h-field-{size} flex flex-col justify-center`(內容 < min-h → justify-center 置中;內容 > min-h → 容器被撐大,top 對齊)
|
|
123
|
+
|
|
124
|
+
**B. Block control**(RadioGroup/CheckboxGroup)— 多行群組無整體中線,錨點 = 第一個 item 第一行中線在 `field-height/2`(SelectionItem `py = calc((field-height - 1lh)/2)` 維持)。
|
|
125
|
+
- label 第一行永遠對齊第一個 item 第一行
|
|
126
|
+
- 實作:`padding-top: calc((field-height - 1lh)/2)`
|
|
127
|
+
|
|
128
|
+
**為什麼分兩套**:曾試 `min-h + flex-center` 統一 → block + 長 label regression(justify-center 失效 + 跟 SelectionItem 公式錯位)。Inline / block 不同對齊語意模型,必分兩套。純 CSS 跨 size/density/字體自動連動,無 JS 測量。對齊 Atlassian DSP / Salesforce Lightning(Polaris 用 baseline 修正,類似)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Control area:Inline vs Block
|
|
133
|
+
|
|
134
|
+
Field 的 control area 有兩種佈局模型,涵蓋所有 control 類型。**核心原則:無論哪一種,「control 第一行內容的中線」都錨在 `field-height/2`**——這是維持 FieldGroup 垂直韻律的關鍵錨點,也讓 horizontal 模式的 label 公式對 inline / block 都成立。
|
|
135
|
+
|
|
136
|
+
| Layout | Control area 樣式 | 適用 control |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| **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` + `padding-top: calc((field-height - 1lh) / 2)`,**不設 min-h** | RadioGroup / CheckboxGroup / FileDropzone / RichTextEditor / inline DataTable 等多行/任意高度區塊 |
|
|
140
|
+
|
|
141
|
+
### 兩種模式的對齊幾何
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
Inline Block
|
|
145
|
+
┌────┬───────────────┐ ┌────┬───────────────┐
|
|
146
|
+
│ │ │ │Lbl │ ● Option A │ ← 第一行中線 = field-height/2
|
|
147
|
+
│Lbl │ [Input ] │ ← 中線 │ │ ● Option B │
|
|
148
|
+
│ │ │ │ │ ● Option C │
|
|
149
|
+
└────┴───────────────┘ └────┴───────────────┘
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
兩個 Field 放在同一個 FieldGroup 時,Lbl 第一行中線完全對齊——Input 的中線與 Option A 的中線都落在同一條水平線上。
|
|
153
|
+
|
|
154
|
+
### 為什麼 block 不是「整組垂直置中」
|
|
155
|
+
|
|
156
|
+
直覺上會想說 control area 多行時整組 vertical center,但這會讓:
|
|
157
|
+
1. 第一個 Radio 往下掉,跟 label 第一行中線錯位
|
|
158
|
+
2. FieldGroup 裡 inline / block field 並排時節奏斷掉
|
|
159
|
+
|
|
160
|
+
正確做法是「**第一行對齊**」——後續 item 從第一行往下流,label 公式錨在第一行中線。Atlassian DSP / Polaris / Material 都是這個模型。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
161
|
+
|
|
162
|
+
### 為什麼 primitive 不自己變高
|
|
163
|
+
|
|
164
|
+
**Checkbox / Switch / RadioGroupItem 的 primitive 保持原生尺寸**(16-20px),不為了 form 而被拉高。世界級系統(shadcn、Radix、Material、Atlassian)全部這樣做。理由: <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
165
|
+
|
|
166
|
+
1. **Primitive 保持單一職責**——Checkbox 在 table cell、toolbar、menu 裡仍然是 16px,不受 form 高度污染
|
|
167
|
+
2. **高度節奏由 Field 容器提供**——一次設定,所有 primitive 在任何 size / density 都自動對齊
|
|
168
|
+
3. **DataTable cell 共用同一個 min-h 機制**——不需為 cell 另發明對齊邏輯
|
|
169
|
+
|
|
170
|
+
### 水平排列 Field 時的垂直對齊
|
|
171
|
+
|
|
172
|
+
多個 Field 並排時,每個 Field 的 control area 都依公式錨在 `field-height/2`,不論 inline 還是 block,**「控件第一行中線」都落在同一條線上**——Input、Checkbox、Switch、RadioGroup 第一個 option 並列時視覺上完全對齊。
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 如何宣告 block primitive(程式化機制)
|
|
177
|
+
|
|
178
|
+
block / inline 是**元件固有性質**(RadioGroup 永遠 block,Input 永遠 inline),由 primitive 自我宣告:
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
;(RadioGroup as unknown as { fieldLayout: 'block' }).fieldLayout = 'block'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Field render 時讀第一個 control child 的 `type.fieldLayout`,缺則 `'inline'`。Consumer 完全無感(`<Field><RadioGroup>...</RadioGroup></Field>` 自動切 block)。
|
|
185
|
+
|
|
186
|
+
**為什麼這機制**:Consumer 傳 prop 易忘 → 歪;Field 內寫死白名單 → 跟 primitive 名稱耦合。Primitive 自宣告 = Field 0 耦合,新增 block primitive 只在自己檔案加 1 行。
|
|
187
|
+
|
|
188
|
+
**逃生艙**:`Field controlLayout="inline"|"block"` prop 覆寫(consumer 手寫 JSX 當 control 無法偵測時用)。
|
|
189
|
+
|
|
190
|
+
**第一行對齊責任**:Field 的 block control area **不加 paddingTop**(避免跟 RadioGroup 自帶 `py = calc((field-height - 1lh) / 2)` double)。需 label 對齊的 block primitive 自行保證第一行位置(RadioGroup/CheckboxGroup 自帶 ✓;FileDropzone/RichTextEditor 自管)。
|
|
191
|
+
|
|
192
|
+
**已宣告 block**:`RadioGroup`。新增 `CheckboxGroup` / `FileDropzone` 等同模式加 `fieldLayout = 'block'`。
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## FieldContext — 子元件如何讀 Field 狀態
|
|
197
|
+
|
|
198
|
+
Field 透過 Context 暴露以下狀態給子元件(Primitive 可以透過 `useFieldContext()` 讀取):
|
|
199
|
+
|
|
200
|
+
| 欄位 | 用途 |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `id` | FieldLabel 自動 `htmlFor`、Input 自動 `id` |
|
|
203
|
+
| `descriptionId` | FieldDescription 的 id,Input 自動 `aria-describedby` |
|
|
204
|
+
| `errorId` | FieldError 的 id,Input 自動 `aria-errormessage` |
|
|
205
|
+
| `mode` | `edit` / `readonly` / `disabled`,Control 決定自己的視覺形態 |
|
|
206
|
+
| `disabled` | Control 顯示 disabled 樣式 |
|
|
207
|
+
| `required` | FieldLabel 自動渲染 `*`;Input 自動 `aria-required` |
|
|
208
|
+
| `invalid` | Control 顯示 error 邊框;Input 自動 `aria-invalid` |
|
|
209
|
+
| `size` | Control 自動同步尺寸(sm / md / lg) |
|
|
210
|
+
| `orientation` | FieldLabel 的垂直對齊策略(horizontal 模式套用 padding-top 公式) |
|
|
211
|
+
| `hasFieldWrapper` | Primitive 讀到此旗標時應忽略自己的 label / description prop,由 FieldLabel / FieldDescription 接管 |
|
|
212
|
+
|
|
213
|
+
### Primitive 的 Field-aware 行為
|
|
214
|
+
|
|
215
|
+
**Checkbox / Switch / RadioItem** 等 primitive 自己有 `label` / `description` props 可用於**獨立使用場景**(不在 Field 內),但在 Field context 內時:
|
|
216
|
+
|
|
217
|
+
- 讀到 `hasFieldWrapper === true` 就**忽略自己的 `label` / `description` props**(若 consumer 誤傳,開發環境可 warn)
|
|
218
|
+
- 改由 `<FieldLabel>` 和 `<FieldDescription>` 接管這兩個 slot
|
|
219
|
+
- 避免「雙層 label」
|
|
220
|
+
|
|
221
|
+
這個機制讓**同一個 primitive 可以在兩種情境下正確呈現**:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
// 情境 A:獨立使用(toolbar / settings row / dashboard widget)
|
|
225
|
+
<Checkbox label="訂閱電子報" description="每週一封" />
|
|
226
|
+
|
|
227
|
+
// 情境 B:form 內(Field 接管 label)
|
|
228
|
+
<Field>
|
|
229
|
+
<FieldLabel>訂閱電子報</FieldLabel>
|
|
230
|
+
<Checkbox />
|
|
231
|
+
<FieldDescription>每週一封</FieldDescription>
|
|
232
|
+
</Field>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
兩種情境用同一個 primitive,一份樣式規範。
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Required 星號
|
|
240
|
+
|
|
241
|
+
- Field 的 `required` prop 透過 context 傳給 FieldLabel 自動渲染 `*`
|
|
242
|
+
- 星號 `text-fg-muted`(neutral-7),**緊貼 label 文字無 gap**(不用 `margin-right`)
|
|
243
|
+
- Disabled 時星號改為 `text-fg-disabled`(neutral-6),與 label 同步降色
|
|
244
|
+
- 個別 FieldLabel 可用 `required` prop 覆寫 context 值
|
|
245
|
+
|
|
246
|
+
**為什麼貼齊無 gap**:星號是 label 語意的一部分(WCAG 友善——screen reader 先讀 required 再讀 label),不是獨立視覺元素,所以不需要間距。
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 驗證與 aria 屬性
|
|
251
|
+
|
|
252
|
+
- `invalid` 透過 context 讓 Control 顯示 error 邊框、自動 `aria-invalid`
|
|
253
|
+
- `errorId` 讓 Control 自動 `aria-errormessage`
|
|
254
|
+
- `descriptionId` 讓 Control 自動 `aria-describedby`
|
|
255
|
+
- `<FieldError>` 自帶 `role="alert"` 並有 id = `errorId`
|
|
256
|
+
- 無 children 的 `<FieldError>` 不渲染,不佔位
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## FieldGroup — 多 Field 垂直堆疊
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
<FieldGroup gap="normal" horizontalLabelWidth="120px">
|
|
264
|
+
<Field><FieldLabel>姓名</FieldLabel><Input /></Field>
|
|
265
|
+
<Field><FieldLabel>Email</FieldLabel><Input /></Field>
|
|
266
|
+
<Field orientation="horizontal">
|
|
267
|
+
<FieldLabel>訂閱通知</FieldLabel>
|
|
268
|
+
<Switch />
|
|
269
|
+
</Field>
|
|
270
|
+
<Field orientation="horizontal">
|
|
271
|
+
<FieldLabel>小字體</FieldLabel>
|
|
272
|
+
<Switch />
|
|
273
|
+
</Field>
|
|
274
|
+
</FieldGroup>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
gap 三個語意層級(具體 gap token 見 `field.tsx` cva):
|
|
278
|
+
|
|
279
|
+
| gap | 用途 |
|
|
280
|
+
|---|---|
|
|
281
|
+
| `compact` | 密集表單、dialog 內 |
|
|
282
|
+
| `normal`(預設) | 標準表單 |
|
|
283
|
+
| `loose` | 寬鬆大表單、settings 頁 |
|
|
284
|
+
|
|
285
|
+
### FieldGroup `horizontalLabelWidth` cascade(2026-04-20)
|
|
286
|
+
|
|
287
|
+
同一畫面 / 同一 FieldGroup 內多個 horizontal Field **必須共用 label 欄寬度**。若每個 Field 各自傳 `labelWidth`(或省略 → 內容撐開),label 寬度會參差不齊,Switch / Input 的左邊緣不對齊,視覺上每一行「歪七扭八」。
|
|
288
|
+
|
|
289
|
+
**世界級 idiom**:macOS System Settings / iOS Settings / GitHub Settings / Notion preferences / Figma 偏好設定——setting list 的 label 全部固定寬、control 全部右對齊,列與列對齊成可掃描的欄位格網。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
290
|
+
|
|
291
|
+
`FieldGroup horizontalLabelWidth` 透過 React Context cascade 到所有子 Field(含 `vertical` 模式的 Field 不受影響——vertical 無 label 欄概念)。單一 Field 仍可用自己的 `labelWidth` prop 覆寫 group 預設(罕見 — 通常 group 預設就是 canonical)。
|
|
292
|
+
|
|
293
|
+
**搭配 Switch 自動齊右**:horizontal Field 內的 Switch 自動 `ml-auto` 推到右邊緣。配合 FieldGroup `horizontalLabelWidth` → 整排 toggles 的 switch 視覺完全對齊。
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Button 作為 control 的判斷標準
|
|
298
|
+
|
|
299
|
+
Field 的 control 是「**承載這個欄位 value 的輸入介面**」。Button 在某些場景就是這個介面——例如 upload button(點擊開檔案選擇器,結果寫回 field)、picker opener(連結 OAuth、開選人對話框)。這類 Button **可以**作為 control:
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
<Field orientation="horizontal" labelWidth="120px">
|
|
303
|
+
<FieldLabel>合約附件</FieldLabel>
|
|
304
|
+
<Button startIcon={Upload} variant="secondary">上傳檔案</Button>
|
|
305
|
+
<FieldDescription>PDF / DOCX,最大 10 MB</FieldDescription>
|
|
306
|
+
</Field>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Button 的 height 與 `field-height` 共用同一組 token,放進 inline control area 自然對齊 Input 中線,horizontal 模式的 label 公式也自然對到——不需要任何特例。
|
|
310
|
+
|
|
311
|
+
**判斷標準**:點擊這顆 Button 是否產生 / 修改此欄位的 value?
|
|
312
|
+
|
|
313
|
+
| 是 | 否 |
|
|
314
|
+
|---|---|
|
|
315
|
+
| ✅ Upload(產生 file value) | ❌ 表單 submit / cancel(form action,放到 form footer) |
|
|
316
|
+
| ✅ Picker opener(開對話框選 value) | ❌ 「重設此欄位」(可放到 endAction 或 description 旁,不是 control) |
|
|
317
|
+
| ✅ Connect OAuth(產生 token value) | ❌ 頁面導覽、刪除整筆資料(page action) |
|
|
318
|
+
|
|
319
|
+
**規則**:Button 是 control 時通常 inline(高度 = field-height);若是 block 類的「上傳區塊」(FileDropzone),則該元件本身宣告為 block primitive。
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 禁止事項
|
|
324
|
+
|
|
325
|
+
- ❌ 不得在 Field 內再包 Field——Field 不支援巢狀
|
|
326
|
+
- ❌ 不得在 Field 內同時放 `<FieldLabel>` 和給 primitive 傳 `label` prop——Field context 會讓 primitive 忽略自己的 label,但語意上仍是重複宣告,避免誤導
|
|
327
|
+
- ❌ 不得跳過 `<FieldLabel>` 直接寫 `<label>`——失去 required 星號、disabled 處理、size 連動等 codified 樣式
|
|
328
|
+
- ❌ 不得為了「讓 Checkbox 與 Input 同高」而修改 Checkbox primitive——高度對齊由 Field control area 負責
|
|
329
|
+
- ❌ Horizontal 模式下不得對 FieldLabel 自訂 `padding-top`——會打破公式對齊
|
|
330
|
+
- ❌ 不得把**與此欄位 value 無關的 action**(form submit/cancel、頁面導覽、刪除整筆資料)放進 control area——Field 是資料輸入容器,不是 action container。Button 是否能當 control,看上一節的判斷標準
|
|
331
|
+
- ❌ 不得在 block control area 自訂 `min-height` 或改 `items-start`——會打破第一行對齊公式
|
|
332
|
+
- ❌ 不得在 block primitive 的 `.tsx` 漏掉 `fieldLayout = 'block'` 宣告——Field 會誤判成 inline 導致 layout 歪掉
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Field Controls 共用設計原則
|
|
337
|
+
|
|
338
|
+
Field 內的資料輸入控件(Input / NumberInput / DatePicker / Select / Combobox / LinkInput / PeoplePicker / Textarea)共用的三 mode、size、focus、endAction、Display 規則,詳見 `Field/field-controls.spec.md`。
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 何時用
|
|
343
|
+
|
|
344
|
+
- **表單欄位的 label + control + description + error 標準佈局**:登入表單、設定頁、建立對話框
|
|
345
|
+
- **需要 required 星號、disabled 狀態、invalid 驗證的統一行為**
|
|
346
|
+
- **需要 `horizontal` / `vertical` 排版切換**:設定頁常用 horizontal(label 左 / control 右)
|
|
347
|
+
- **多欄位垂直堆疊**:搭配 `FieldGroup` 管理 density-aware 間距
|
|
348
|
+
|
|
349
|
+
## 何時不用
|
|
350
|
+
|
|
351
|
+
| 場景 | 改用 | 原因 |
|
|
352
|
+
|------|------|------|
|
|
353
|
+
| 唯讀資訊展示 | `DescriptionList` | Field 是表單容器,純展示用 `dl/dt/dd` 語義 |
|
|
354
|
+
| 單一 Checkbox(勾選同意)without label 結構 | 直接 Checkbox + label prop | 單個 Checkbox 是 inline primitive,不需要 Field 佈局 |
|
|
355
|
+
| DataTable cell 編輯(inline editable)| 直接放 Field Control | Field 是頁面表單佈局,table cell 空間受限 |
|
|
356
|
+
| 純 action 按鈕(submit / cancel / 頁面導覽)| 頁面 footer / toolbar | Field 是資料輸入容器,不放頁面級 action |
|
|
357
|
+
| 巢狀 Field | ❌ 不支援 | Field 不支援巢狀,多欄位用 FieldGroup |
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 為何無 Inspector
|
|
362
|
+
|
|
363
|
+
Field 是**表單欄位容器**(label + helper + control + error 的佈局包裝),設計決策維度是 `orientation`(vertical / horizontal)× `size`(sm/md/lg)× `state`(required / invalid / disabled / readonly)× `color`——已由 `OrientationMatrix` / `SizeMatrix` / `StateBehavior` / `ColorMatrix` + 元件特有 `FieldGroupBehavior` 五張結構矩陣完整覆蓋。
|
|
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`。
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Field state machine SSOT(v13.3)
|
|
372
|
+
|
|
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 跟動。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
374
|
+
|
|
375
|
+
| State | Token | CSS |
|
|
376
|
+
|---|---|---|
|
|
377
|
+
| rest | `--border` | `border-border` |
|
|
378
|
+
| hover(無 focus)| `--border-hover` | `hover:border-border-hover` |
|
|
379
|
+
| **focus** | `--primary` | **`focus-within:!border-primary`**(`!important` 勝 data-state)|
|
|
380
|
+
| focus + hover | `--primary` | `focus-within:hover:!border-primary`(M11 AND case)|
|
|
381
|
+
| open(無 focus)| `--border-hover` | `data-[state=open]:border-border-hover` |
|
|
382
|
+
| error | `--error` | `focus-within:border-error focus-within:hover:border-error` |
|
|
383
|
+
|
|
384
|
+
副作用(自動達成 Ant「選後藍 / 取消灰」):選 option → Radix `onCloseAutoFocus` return focus → focus-within fires → 藍 / 點外取消 → focus 移外 → 灰。純 focus 機制無需 transient class。
|
|
385
|
+
|
|
386
|
+
**反模式 ❌**:per-control `open && 'border-primary'`(Combobox/Select/PeoplePicker)/ `data-[state=open]:border-primary` — 已 v13.3 全 retire。`hook check_field_state_token_consume` write-time 攔。
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Naked variant — Cell-as-input(M19 SSOT,v15)
|
|
391
|
+
|
|
392
|
+
`naked` = cell-as-input(Notion / Airtable / Excel / AG Grid:host cell 提供 box,Field 融入)。**核心**:完全繼承 Field default state machine,只改**物理尺寸 + 位置**。Combobox/Select 加新 stacking 子元件必檢查 cell context;cell 內元件不要自寫 `position:absolute/fixed` 對抗(必要時加 `// @field-state-ring-allow:` 通過 hook)。
|
|
393
|
+
|
|
394
|
+
### Layer 分配
|
|
395
|
+
|
|
396
|
+
| Layer | Home | 內容 |
|
|
397
|
+
|---|---|---|
|
|
398
|
+
| **L1 Slot** | `patterns/element-anatomy/item-anatomy.tsx` | `<ItemPrefix>` / `<ItemSuffix>`(永遠 `h-[1lh]` 對齊第 1 行) |
|
|
399
|
+
| **L2 Align** | `field-wrapper.tsx` `nakedCellRowModeAlign` | host cell `data-row-mode` propagate(autoRow→items-start / fixed→items-center)|
|
|
400
|
+
| **L3 State machine** | **繼承 Field default v13.3**(↑ 上方 SSOT)| 不重定義 |
|
|
401
|
+
| **L4 Display hover** | `field-wrapper.tsx` `nakedCellEditableDisplayHover` | editable cell display mode `outline-1 outline-offset:-1 outline-[var(--border-hover)]`(cell wrapper outline)。**v15.13 design intent(user-accepted 2026-05-07)**:hover ring 蓋 cell.borderRight。**known asymmetry**(2026-05-09 user 重抓):只 **right** 邊 outline 跟 cell own border-r 同位置覆蓋成 1 條;top/left/bottom 邊 outline 在 cell.outer 內 1px,grid lines(前 row border-b / 前 cell border-r / row 自己 border-b 在 cell.outer 外 1px)露出 → 視覺 4 邊不對稱。**Pending design decision**(等 user 拍板才 ship):4 邊都蓋(spec 1 selection-style)vs 4 邊都不蓋(spec 2 inset 1px)vs 維持現狀。三層 SSOT:state source = cell wrapper / paint owner = 混合(hover=cell / edit=Field)/ grid divider = DataTable own |
|
|
402
|
+
| **L5 Position(v14,2026-05-06 revert v12)** | naked compoundVariant flow | Field 留 layout flow,**不**用 `!absolute`(v12 absolute 跟 autoRowHeight 不相容)。視覺接受 cell `border-r grid` + Field border 2px 雙線;cell border-r divider 永遠保留(對齊 L4 Invariant divider-owner / editor-owner 分離)|
|
|
403
|
+
| **L6 Cell trigger indicator(v15,2026-05-09)** | naked-display branch + `showDisplayEndIcon?: boolean` opt-in | DataTable cell-registry 顯式傳 `showDisplayEndIcon` → Field component 內部 `<ItemSuffix>` 渲對應 trigger icon(Select/Combobox/PeoplePicker → ChevronDown / DatePicker → CalendarIcon / TimePicker → Clock / LinkInput 例外無 suffix)。table 外用法不傳此 prop = 純展示無 icon。詳 `.claude/planning/cell-indicator-ssot-rfc.md`。|
|
|
404
|
+
|
|
405
|
+
**前身 retire**:v4-v8 `outline-2` 平行 state(已下沉 L3)/ v9 border-only(focus 跟 open specificity tie)/ per-control `open && 'border-primary'`(v13.3 retire)/ `nakedCell{Hover,Focus,Open}Ring` 三 const / `nakedCell{Prefix,Suffix}Slot`(下沉 L1)/ **v12 `!absolute -inset-px` seamless grid**(2026-05-06 revert — autoRowHeight 不相容)/ **DataTable parallel `getEditIndicator`**(2026-05-09 D-path retire — 由 Field naked-display 透過 `showDisplayEndIcon` opt-in 自渲 ItemSuffix,跨元件 SSOT 統一)。
|
|
406
|
+
|
|
407
|
+
**反模式 ❌**: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
|
+
|
|
409
|
+
**機械防漂移**:hooks `check_naked_row_mode_propagation` / `check_field_state_token_consume`(v13 升級)/ `check_inline_action_canonical_gap` / `check_row_slot_handcraft`(write-time)+ audit `/design-system-audit` Group N M36-M39。
|
|
410
|
+
|
|
411
|
+
## 相關
|
|
412
|
+
|
|
413
|
+
- `./field-controls.spec.md` — Field Controls(Input/Select/etc.)共用規則:三 mode、size、focus、endAction、Display
|
|
414
|
+
- `./form-validation.spec.md` — 表單驗證標準(blur 驗證、zod schema、error 顯示)
|
|
415
|
+
- `../DescriptionList/description-list.spec.md` — 唯讀資訊展示(非表單)
|
|
416
|
+
- `../../patterns/element-anatomy/item-anatomy.spec.md` — SelectionItem 佈局(Checkbox / Radio 放進 Field 時 block 模式的參照)
|
|
417
|
+
- CLAUDE.md「元件 Props 命名原則」— Field 的 orientation / block control 宣告規則
|
|
418
|
+
|
|
419
|
+
## A11y 預設
|
|
420
|
+
|
|
421
|
+
**ARIA / Pattern**:對齊 [W3C ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/) 對應 pattern。
|
|
422
|
+
|
|
423
|
+
**Keyboard 行為**:
|
|
424
|
+
|
|
425
|
+
- Tab — focus internal control(Input / Select / DatePicker 等)
|
|
426
|
+
- Esc — 取消 edit mode(若 cell-as-input)
|
|
427
|
+
|
|
428
|
+
**Focus**:focus-visible ring 對齊 DS canonical(`outline: 2px solid var(--ring)`);focus management 由元件 own。
|
|
429
|
+
|
|
430
|
+
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
431
|
+
|
|
432
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
433
|
+
|
|
434
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
435
|
+
|
|
436
|
+
- `element-anatomy.spec.md`
|
|
437
|
+
- `radio-group.spec.md`
|
|
438
|
+
- `switch.spec.md`
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline; canonical source URLs in frontmatter benchmark list. -->
|
|
2
|
+
|
|
3
|
+
# Form Validation 設計原則
|
|
4
|
+
|
|
5
|
+
> **本 spec = 跨表單的 validation 方法論 rules,非 UI 元件 spec,不適用 Layout Family 分類**(Dim 16 豁免)。
|
|
6
|
+
> 元件級 validation 視覺規格住在 `Field/field.spec.md`(Field wrapper chrome)+ 各 form control spec。
|
|
7
|
+
|
|
8
|
+
表單層級的驗證行為規範。適用於所有包含 Field 元件的表單。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 表單驗證原則
|
|
13
|
+
|
|
14
|
+
### Submit Button 狀態
|
|
15
|
+
|
|
16
|
+
| 情境 | 按鈕預設 | 何時啟用 | 何時停用 |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| **新建**(Create) | **永遠 enabled** | — | — |
|
|
19
|
+
| **更新**(Update) | **disabled** | 使用者變更任何欄位(dirty) | 變更被還原回原值(pristine) |
|
|
20
|
+
|
|
21
|
+
新建永遠 enabled——不讓使用者猜「為什麼按不了」。更新用 disabled 明確表達「沒改就不用存」,有變更才亮起來。
|
|
22
|
+
|
|
23
|
+
### 驗證時機(Blur Validation)
|
|
24
|
+
|
|
25
|
+
所有 Field 統一使用 blur validation——使用者離開 field 時驗證,不在打字過程中即時驗證。
|
|
26
|
+
|
|
27
|
+
#### 尚未出錯的欄位
|
|
28
|
+
|
|
29
|
+
1. **Focus 中不顯示錯誤**——即使輸入內容不合法,focus 狀態不驗證、不顯示 error
|
|
30
|
+
2. **Blur 時驗證**——使用者離開 field 後才顯示 error
|
|
31
|
+
3. **Enter 等同 blur**——觸發驗證並離開編輯(適用於單行 field)
|
|
32
|
+
4. **Escape 取消**——回復原值,不觸發驗證
|
|
33
|
+
|
|
34
|
+
**為什麼 focus 中不報錯**:使用者可能才打到一半(例如 email 打了 `user@` 還沒打完),此時報錯是「提前判決」,打斷使用者思路。
|
|
35
|
+
|
|
36
|
+
#### 已出錯的欄位
|
|
37
|
+
|
|
38
|
+
5. **開始編輯時立即清除 error**——不論新輸入合法與否,只要欄位被編輯就移除 error 視覺,給使用者修正的空間
|
|
39
|
+
6. **Blur 時重新驗證**——離開欄位後重新判斷,如仍不合法則再次顯示 error
|
|
40
|
+
|
|
41
|
+
**為什麼不邊打邊重驗(onChange re-validation)**:逐字重驗太 aggressive——使用者才改了第一個字,error 又跳回來,感覺系統在「碎念」。清除 error 後等 blur 重驗,給使用者完整的修正空間。
|
|
42
|
+
|
|
43
|
+
### Submit 驗證
|
|
44
|
+
|
|
45
|
+
7. **Submit 驗證全部**——點擊 submit 時對所有欄位執行驗證(不依賴個別 field 的 blur 狀態)
|
|
46
|
+
8. **Anchor 到第一個錯誤**——若有任何欄位出錯,scroll 並 focus 到第一個錯誤欄位
|
|
47
|
+
9. **Async / cross-field 驗證 defer 到 submit**——某些驗證無法在 blur 當下完成(如「名稱是否重複」需要 API 查詢、跨欄位邏輯如「結束日不得早於開始日」),這些在 submit 時統一判斷。若有錯誤,同樣 anchor 到第一個出錯欄位。
|
|
48
|
+
|
|
49
|
+
### 驗證分層
|
|
50
|
+
|
|
51
|
+
| 層級 | 負責者 | 時機 | 範例 |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| **格式驗證** | Field 元件自身 | blur | email 格式、URL 格式、必填檢查 |
|
|
54
|
+
| **業務驗證** | Form 層 / 應用層 | submit | 名稱不可重複(API)、跨欄位邏輯 |
|
|
55
|
+
|
|
56
|
+
兩者都透過 `error` prop / Field context 的 `invalid` 呈現,視覺上一致(紅框 + error message)。
|
|
57
|
+
|
|
58
|
+
### 可程式化的部分
|
|
59
|
+
|
|
60
|
+
| 功能 | 實作位置 | 方式 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| Blur validation timing | Form library config(如 RHF `mode: 'onBlur'`) | 應用層 |
|
|
63
|
+
| Error clearing on edit | Form library config(`reValidateMode: 'onBlur'`)+ Field `onChange` clear | 應用層 |
|
|
64
|
+
| Dirty tracking(submit button 狀態) | Form library(`isDirty`)或自訂 `useFormDirty` | 應用層 |
|
|
65
|
+
| Scroll to first error | Form library(`scrollToFirstError`)或自訂 `focusFirstError` | 應用層 |
|
|
66
|
+
| Field error visual | Field 元件的 `error` prop / `invalid` context | **元件層(已有)** |
|
|
67
|
+
| Submit button disabled | `<Button disabled={!isDirty}>` | 應用層 consumer |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 世界級對照
|
|
72
|
+
|
|
73
|
+
對齊 M8(binary strict rule 必 ≥3 家對照),「禁 focus 中報錯」+「禁邊打邊重驗」是本 spec 的 binary strict rule,以下為支撐 rationale。
|
|
74
|
+
|
|
75
|
+
### 驗證 timing 哲學
|
|
76
|
+
|
|
77
|
+
| DS | 本 DS | Material 3 | Polaris | Ant Design | Carbon | iOS HIG | Atlassian Forge | GitHub Primer |
|
|
78
|
+
|----|-------|-----------|---------|-----------|--------|---------|-----------------|---------------|
|
|
79
|
+
| 預設 timing | **blur + submit**(無 onChange) | onBlur + 已出錯後 onChange(MUI/RHF default) | onBlur(明寫「don't validate while typing」)| **onChange + onBlur 雙模式**(`validateTrigger=['onChange','onBlur']`) | onBlur + submit | submit / Done(form 內延遲)| onBlur + onSubmit | submit only(極簡)|
|
|
80
|
+
| 已出錯後 | edit 立即清 error / blur 重驗 | re-validate onChange | re-validate onChange | re-validate onChange | edit 清 / blur 重驗 | submit 才驗 | edit 清 / blur 重驗 | submit 才驗 |
|
|
81
|
+
| Submit 失敗 | scroll + focus first error | focus first error | focus first error | scroll + focus | focus first error | shake animation | scroll + focus | inline alert |
|
|
82
|
+
|
|
83
|
+
### Submit button 狀態哲學
|
|
84
|
+
|
|
85
|
+
| DS | 本 DS | Material 3 | Polaris | Ant | Stripe Dashboard | Linear | Notion 設定頁 |
|
|
86
|
+
|----|-------|-----------|---------|-----|------------------|--------|---------------|
|
|
87
|
+
| 新建(Create)| **always enabled** | always enabled | always enabled | dirty 才 enable(差異)| always enabled | always enabled | always enabled |
|
|
88
|
+
| 更新(Update)| **disabled-until-dirty** | dirty 才 enable | dirty 才 enable | dirty 才 enable | dirty 才 enable | auto-save(無 explicit save)| disabled-until-dirty |
|
|
89
|
+
|
|
90
|
+
## 設計哲學
|
|
91
|
+
|
|
92
|
+
四個關鍵決策,各自有世界級先例支撐:
|
|
93
|
+
|
|
94
|
+
**(1) Blur-only validation(non-onChange)— 對齊 Polaris / Carbon「don't validate while typing」** <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
95
|
+
|
|
96
|
+
Ant Design default `validateTrigger=['onChange', 'onBlur']` 對使用者 aggressive — 才打「user@」就跳「invalid email」碎念,reader 思路被打斷。Polaris / Carbon / iOS / Atlassian 共識 onBlur + submit,讓使用者「先表達完意圖再評斷」。
|
|
97
|
+
|
|
98
|
+
捨棄 onChange 即時驗證的代價是「打錯看不到反饋」(打到第 3 位才發現密碼太短),但 Field 內可選 `realtime` mode 提供 hint(非 error)補足,本 spec 規範 default 行為。
|
|
99
|
+
|
|
100
|
+
**(2) Edit 清 error + blur 重驗(已出錯後),非 onChange 重驗 — 對齊 Carbon / Atlassian 兩階段哲學** <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
101
|
+
|
|
102
|
+
Material/Polaris/Ant 已出錯後 onChange re-validate(改第 1 字 error 又跳回)— 給使用者壓力。Carbon / Atlassian「edit 清 + blur 重驗」哲學:給使用者完整修正空間,離開時才再判決。
|
|
103
|
+
|
|
104
|
+
對應使用者心智:「修改」是過程,「離開 field」是動作完成的 boundary,在 boundary 評斷比每字評斷尊重 user agency。
|
|
105
|
+
|
|
106
|
+
**(3) Create always-enabled / Update disabled-until-dirty 不對稱 — 對齊 Stripe / Notion / Linear 現代慣例** <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
107
|
+
|
|
108
|
+
Ant 對「Create」也 disabled-until-dirty(填了所有 required 才亮)— 但這讓使用者第一次進 form 看到 disabled button 困惑「為什麼按不了」。Stripe / Notion / Material 共識:Create 永遠 enabled — 點擊後若 invalid,顯示 error 並 scroll,使用者明確知道為什麼。
|
|
109
|
+
|
|
110
|
+
Update 場景反向:沒改的 Update 沒提交意義(對齊「intent 才 commit」),disabled 表達「等你做動作」比 enabled 後點擊判斷「沒變化」更直接。對齊 Notion 設定頁 / Figma file rename 慣例。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
111
|
+
|
|
112
|
+
**(4) 格式驗證 vs 業務驗證分層(blur vs submit)— 對齊 Material/Carbon「local vs cross-cutting validation」哲學** <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
113
|
+
|
|
114
|
+
Email 格式 / URL 格式 / 必填等「single-field 純 syntax」blur 即可判斷;名稱重複(API 查)/ 結束日 ≥ 開始日(跨欄位)等「business / async」必須 submit 才能判 — 強行 blur 觸發 API 對使用者體驗差(每換 field 一次 API call)。
|
|
115
|
+
|
|
116
|
+
對齊 Material `<TextField error>` + Form layer error 分層 / Carbon「format vs business」雙軌。視覺一致(都紅框 + error message)避免 reader 區分「為什麼這個 error 是 blur 出來那個是 submit 出來」。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
117
|
+
|
|
118
|
+
## 禁止事項
|
|
119
|
+
|
|
120
|
+
- ❌ 在 onChange 即時 re-validate(已出錯後)— 違反「edit 清 error + blur 重驗」哲學,給使用者壓力
|
|
121
|
+
- ❌ 對 Create form 用 disabled-until-dirty Submit button — 第一次進 form 看到 disabled CTA 困惑,改為 always-enabled + submit 後 scroll-to-error
|
|
122
|
+
- ❌ Update form 用 always-enabled Submit — 沒改的 Update 沒提交意義,違反「intent 才 commit」
|
|
123
|
+
- ❌ 對 single-field syntax(email / URL / required)用 submit-time API validation — 浪費 round-trip,blur 即可判
|
|
124
|
+
- ❌ 對 business / async / cross-field 用 blur-time validation — 每換 field 觸 API call 體驗差,submit 才判
|
|
125
|
+
- ❌ 不同 validation 來源用不同視覺(blur 黃框 / submit 紅框)— 視覺必一致,user 不需區分「為什麼是 blur 來的」
|
|
126
|
+
|
|
127
|
+
## A11y 預設
|
|
128
|
+
|
|
129
|
+
Form validation 的 ARIA / 鍵盤行為(對齊 WCAG 3.3.1 Error Identification + 3.3.3 Error Suggestion):
|
|
130
|
+
|
|
131
|
+
- **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: see frontmatter benchmark list for canonical DS source URL -->
|
|
133
|
+
- **Error live region**:跨欄位 / async error 用 `aria-live="polite"` 容器宣告 — SR 在空閒時讀出,不中斷使用者打字
|
|
134
|
+
- **Required indicator**:label 的 `*` 必加 `aria-label="required"` 或 hidden span(SR 朗讀「required」),純視覺 `*` SR 讀「asterisk」語義不清
|
|
135
|
+
- **Color-only error 警告**:error border 紅色之外必有 icon 或文字(WCAG 1.4.1 不僅靠顏色)— DS error variant 自動 prefix `<AlertCircle/>` icon
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
139
|
+
|
|
140
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
141
|
+
|
|
142
|
+
- `field.spec.md`
|
|
143
|
+
- `link-input.spec.md`
|
|
144
|
+
- `textarea.spec.md`
|
|
145
|
+
- `time-picker.spec.md`
|
|
146
|
+
|
|
147
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
148
|
+
|
|
149
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
150
|
+
|
|
151
|
+
- `combobox.spec.md`
|
|
152
|
+
- `date-picker.spec.md`
|