@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,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Select
|
|
3
|
+
family: 4
|
|
4
|
+
variants: {}
|
|
5
|
+
sizes: {}
|
|
6
|
+
traits:
|
|
7
|
+
- hasInteractiveStates
|
|
8
|
+
- isInputLike
|
|
9
|
+
benchmark:
|
|
10
|
+
- Radix Select primitive: github.com/radix-ui/primitives/tree/main/packages/react/select
|
|
11
|
+
- Ant Design Select: github.com/ant-design/ant-design/tree/master/components/select
|
|
12
|
+
- MUI Select: github.com/mui/material-ui/tree/master/packages/mui-material/src/Select
|
|
13
|
+
- Polaris Select: github.com/Shopify/polaris/tree/main/polaris-react/src/components/Select
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline; canonical source URLs in frontmatter benchmark list. -->
|
|
17
|
+
|
|
18
|
+
# Select 設計原則
|
|
19
|
+
|
|
20
|
+
## 定位
|
|
21
|
+
|
|
22
|
+
Select 是**單選下拉的表單 control**——從 3+ 選項中挑恰好一個,選項收在 dropdown 內展開。底層走原生 `<select>`,透過 CSS 客製視覺。
|
|
23
|
+
|
|
24
|
+
共用規則見 `field-controls.spec.md`。本文件只記錄 Select 特有的原則。
|
|
25
|
+
|
|
26
|
+
**Layout Family**:CLAUDE.md 4-Family Model **Family 4(Field control layout)** 消費者。結構繼承 `components/Field/field-controls.spec.md` 的 `fieldWrapperStyles + [startIcon?] [<editable>] [endAction?]` 規格,視覺對齊 Family 1(Menu item)讓 SelectMenu trigger + options 連續一致。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Controlled / Uncontrolled dual-mode(Dim 26,2026-05-21 D3 audit ship)
|
|
31
|
+
|
|
32
|
+
本元件支援 **dual-mode**:consumer 可選 controlled OR uncontrolled,對齊 React `<input>` / Radix Select / shadcn Select 共識。**互斥規則**:同時傳 `value` + `defaultValue` 走 controlled(value 勝),`defaultValue` 僅 first-mount 用。
|
|
33
|
+
|
|
34
|
+
### Controlled 模式(consumer 自管 state)
|
|
35
|
+
|
|
36
|
+
傳 `value` + `onChange`,Select **不**自管 state:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
const [country, setCountry] = useState('tw')
|
|
40
|
+
<Select value={country} onChange={setCountry} options={...} />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**何時用**:Form library(react-hook-form / Formik)/ Redux / 跨元件 sync / server-state driven。
|
|
44
|
+
|
|
45
|
+
### Uncontrolled 模式(Select 自管 state)
|
|
46
|
+
|
|
47
|
+
不傳 `value`(可傳 `defaultValue` 設初始值),Select 內部 `useState`,變更時 fire `onChange` callback 通知 consumer:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<Select defaultValue="tw" onChange={(v) => console.log('changed:', v)} options={...} />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**何時用**:純展示 / 一次性表單 submit / consumer 不需要 read 當前值。
|
|
54
|
+
|
|
55
|
+
### 為什麼支援 dual-mode(非 controlled-only)
|
|
56
|
+
|
|
57
|
+
- **React canonical**:`<input>` / `<select>` 原生兩模式並存,Radix / shadcn / MUI / Ant 共識 form-like primitive 必支援 dual-mode
|
|
58
|
+
- **Consumer ergonomics**:簡單場景不該強制管 state(prototype / 一次性 form / a11y testing)
|
|
59
|
+
- **互斥規則消除 ambiguity**:`value !== undefined` 為 controlled signal,decisive 不模糊
|
|
60
|
+
|
|
61
|
+
### 歷史
|
|
62
|
+
|
|
63
|
+
- **2026-05-21 前**:刻意 controlled-only,理由「內部狀態複雜易 race」
|
|
64
|
+
- **2026-05-21 D3 audit**:per Phase A deep audit Dim 26 verify + user verbatim「決策三照妳建議」+「都給我做到好」→ 補 `defaultValue` + `useState` internal state + 互斥 signal。實作:`select.tsx:L443 isControlled = valueProp !== undefined` + `internalValue` state + `handleValueChange` 內 `if (!isControlled) setInternalValue(v)`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 何時用
|
|
69
|
+
|
|
70
|
+
- **表單中節省垂直空間**:create user 的 role、profile settings 的 timezone、product form 的 category
|
|
71
|
+
- **Toolbar / filter 的選擇**:table 上方的 category filter、sort by、狀態篩選
|
|
72
|
+
- **Table cell 的 inline edit**:Jira-style task 的 status / priority / assignee(見下文「即時 vs on-submit」)
|
|
73
|
+
- **選項 label 自帶語意**:國家、類別、時區——使用者看 label 就知道要選什麼,不需要額外 description
|
|
74
|
+
- **選項 10+ 且不需搜尋**:時區、國家這類使用者熟悉的清單,依賴 native select 的 type-to-jump 快速定位
|
|
75
|
+
|
|
76
|
+
## 何時不用
|
|
77
|
+
|
|
78
|
+
| 場景 | 改用 | 原因 |
|
|
79
|
+
|------|------|------|
|
|
80
|
+
| 2-5 個選項且選擇是「決策節點」 | `RadioGroup` | 使用者需要看到全部選項比較——見下「與 RadioGroup 的分界」 |
|
|
81
|
+
| 2-5 個緊湊切換(filter / view mode) | `SegmentedControl` | 更 compact,跟 Button / Input 並排不違和。詳見 `segmented-control.spec.md` |
|
|
82
|
+
| 選項需要多行描述或圖文並列 | `RadioGroup` | Select option 是單行純文字,無法承載複雜排版 |
|
|
83
|
+
| 多選 | `Combobox` | Select 永遠單選 |
|
|
84
|
+
| 布林切換(on / off) | `Switch` | 布林不需要「選一個」的心智模型 |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 與 RadioGroup 的分界
|
|
89
|
+
|
|
90
|
+
兩者都能「從 N 個選項中挑一個」,在 2-5 個選項的範圍內常被誤用互換。判斷**不是數量**,而是以下三個角度——**任何一個明確傾向哪邊就選哪邊**:
|
|
91
|
+
|
|
92
|
+
### 1. Progressive disclosure 成本
|
|
93
|
+
|
|
94
|
+
- **Select**:選項隱藏,使用者多一次點擊才看到。適合「label 本身就夠清楚」的場景(國家、類別、角色)——使用者已經知道自己要選什麼,只是需要一個 picker
|
|
95
|
+
- **RadioGroup**:全部選項一次可見。適合「使用者需要看完所有選項才能決定」——付款方式、訂閱方案、票種,選擇本身是決策動作
|
|
96
|
+
|
|
97
|
+
### 2. 視覺重量
|
|
98
|
+
|
|
99
|
+
- **Select**:O(1)——永遠佔 field height 一格空間,無論 3 個或 300 個選項
|
|
100
|
+
- **RadioGroup**:O(n)——每個選項各佔一行(block 模式),總高度隨選項數線性增加。超過 5 個 option 會開始主導 form 的視覺節奏
|
|
101
|
+
|
|
102
|
+
### 3. 評估深度
|
|
103
|
+
|
|
104
|
+
- **Select**:使用者已經知道要選什麼,label 只是確認。例:我知道我要選「Electronics」,dropdown 只是找到它的工具
|
|
105
|
+
- **RadioGroup**:使用者需要仔細讀每個選項的 label(有時連帶 description 比較價格 / 權限 / 時長),評估才做選擇
|
|
106
|
+
|
|
107
|
+
### Fallback heuristic
|
|
108
|
+
|
|
109
|
+
- **表單中要節省垂直空間 / label 自帶語意 / 使用者熟悉選項 → Select**
|
|
110
|
+
- **選擇本身是決策節點(使用者需要對比評估) → RadioGroup**
|
|
111
|
+
- **灰色地帶時,問:「使用者看一眼 label 就能下決定嗎?」** 能 → Select;不能、需要閱讀 → RadioGroup
|
|
112
|
+
|
|
113
|
+
### 情境對照表
|
|
114
|
+
|
|
115
|
+
| 場景 | 選哪個 | 原因 |
|
|
116
|
+
|------|-------|------|
|
|
117
|
+
| 國家 / 時區選擇 | Select(可配 searchable) | label 自帶語意、使用者熟悉、選項數多 |
|
|
118
|
+
| Table cell 的 status 切換 | Select | 空間受限、使用者熟悉 status 名 |
|
|
119
|
+
| 類別(Electronics / Furniture / Food)| Select 或 RadioGroup | 看容器空間;3-4 個且在 form 中傾向 RadioGroup |
|
|
120
|
+
| **付款方式**(信用卡 / 轉帳 / 現金)| RadioGroup | 決策節點、通常連帶手續費或處理時間 description |
|
|
121
|
+
| **訂閱方案**(Basic / Pro / Enterprise)| RadioGroup | 決策節點、通常連帶價格比較與 feature list |
|
|
122
|
+
| **票種**(成人 / 兒童 / 敬老)| RadioGroup | 決策節點、通常連帶價格 |
|
|
123
|
+
| 使用者角色(Admin / Editor / Viewer)| Select 或 RadioGroup | 新手用 RadioGroup(要解釋權限)、熟悉使用者 Select |
|
|
124
|
+
| 時區選擇 | Select + searchable | 選項 > 100 個、使用者記得時區名 |
|
|
125
|
+
|
|
126
|
+
**本節是 Select vs RadioGroup 的 SSOT**,`radio-group.spec.md` 和 `checkbox.spec.md` 點回本節,不重複書寫。
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 即時 vs on-submit
|
|
131
|
+
|
|
132
|
+
Select 的值套用時機是**由 onChange handler 的副作用決定**,不是 Select 本身的屬性——兩種場景都是正當用法:
|
|
133
|
+
|
|
134
|
+
### 即時套用(change → API)
|
|
135
|
+
|
|
136
|
+
選值一變就直接觸發外部動作,不經 form submit:
|
|
137
|
+
|
|
138
|
+
- **Jira-style view/edit**:task 的 status / priority / assignee 欄位,改了立刻寫回 DB
|
|
139
|
+
- **篩選器**:table 上方的 category / status filter,改了立刻篩 table
|
|
140
|
+
- **即時設定**:notification preference、theme dropdown
|
|
141
|
+
- **URL param 綁定**:改 value 立刻 push URL(deep-linkable)
|
|
142
|
+
|
|
143
|
+
這類場景的 onChange 通常呼叫 mutation / API / setState 更新父層。**沒有「取消」的概念**——改了就是改了。
|
|
144
|
+
|
|
145
|
+
### 隨 form 送出(change → local state → submit)
|
|
146
|
+
|
|
147
|
+
選值先寫進 local state,等 form submit 才套用:
|
|
148
|
+
|
|
149
|
+
- **建立 / 編輯表單**:create project 裡的 type、edit user 的 role
|
|
150
|
+
- **對話框設定**:dialog 內的選項,按確認才生效
|
|
151
|
+
- **精靈流程**:多步驟表單的中間選擇
|
|
152
|
+
|
|
153
|
+
這類場景 onChange 只更新 React state,直到 submit handler 才送出。**有「取消」可回復**。
|
|
154
|
+
|
|
155
|
+
### 設計規則
|
|
156
|
+
|
|
157
|
+
- 即時場景:用 `aria-label` 或旁邊的 label 明確告知「這個改了會立刻套用」
|
|
158
|
+
- on-submit 場景:搭配 `<Field>` 容器 + submit button,使用者清楚哪個動作觸發儲存
|
|
159
|
+
- **不要讓使用者搞不清楚是哪種**——這是 DS 最常見的信任破壞點
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Searchable 開啟判斷
|
|
164
|
+
|
|
165
|
+
`searchable` 啟用後 field 變 input,打字即篩選 options。判斷依以下順序:
|
|
166
|
+
|
|
167
|
+
### 主判準:label 性質
|
|
168
|
+
|
|
169
|
+
- ✅ **需要 searchable**:label 是獨特關鍵字 / 代碼 / 非自然語言,使用者**無法靠捲動快速定位**
|
|
170
|
+
- 產品代碼:`SKU-4837`、`ORDER-2024-001`
|
|
171
|
+
- 郵遞區號、機場代碼(TPE / NRT / JFK)
|
|
172
|
+
- 使用者 ID、專案 slug、ticket number
|
|
173
|
+
- 中文姓名(同姓大量集中,字母跳不動)
|
|
174
|
+
- ❌ **不需要 searchable**:label 是流暢自然語言、品類名稱,native select 的 type-to-jump 就夠用
|
|
175
|
+
- `Electronics`、`Furniture`、`Food`
|
|
176
|
+
- `Draft` / `In progress` / `Done`
|
|
177
|
+
- 國家英文名(按首字母跳夠快)
|
|
178
|
+
|
|
179
|
+
### 次要啟發:選項數量
|
|
180
|
+
|
|
181
|
+
數量只是啟發,主判準永遠是 label 性質。
|
|
182
|
+
|
|
183
|
+
- **< 10** + 自然語言 label → 不開
|
|
184
|
+
- **10-50** → 往搜尋傾斜(但仍看 label 性質)
|
|
185
|
+
- **> 50** → 幾乎必開
|
|
186
|
+
|
|
187
|
+
### 為什麼不用純數量 threshold
|
|
188
|
+
|
|
189
|
+
- 100 個 `a` / `b` / `c` / ... 不需要搜尋(native type-to-jump 直達)
|
|
190
|
+
- 5 個 `SKU-4837` / `SKU-8210` / ... 需要搜尋(使用者記不起哪個代碼對應哪個產品)
|
|
191
|
+
|
|
192
|
+
純量化規則會誤判這兩端。label 性質是唯一可靠的主判準。
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 顯示模式(`display` prop)
|
|
197
|
+
|
|
198
|
+
| 模式 | 何時使用 |
|
|
199
|
+
|------|---------|
|
|
200
|
+
| `plain`(預設) | 選項語意靠文字表達(類別、國家、角色) |
|
|
201
|
+
| `tag` | 選項有色彩語意,顏色加速掃視(狀態:紅黃綠) |
|
|
202
|
+
|
|
203
|
+
**命名 rationale**(2026-05-01 從 `'text'` 改 `'plain'`):跨元件 prop value 衝突防避 — `Button variant="text"` 表「無 chrome 文字按鈕」,跟 Select.display 的「純文字呈現」是兩個不同概念,共用 `'text'` 字面值會造成 consumer 認知衝突(命名三 test 第 3 條)。改 `'plain'` 後語意專屬:plain = 樸素文字呈現,對立 tag = 用 Tag 元件呈現。
|
|
204
|
+
|
|
205
|
+
### plain 模式
|
|
206
|
+
|
|
207
|
+
- 原生 select 純文字 + ChevronDown
|
|
208
|
+
- 可搭配 `startIcon`——**field-level leading indicator**(色 muted,對齊 `Input.startIcon` search-icon pattern;`Mail` / `Globe` / `Lock` / `Flag` 等提示「這個 field 的類型 / 屬性」,跟 selected value 變動無關)
|
|
209
|
+
- **代表 value 的 icon(value-bound)走 `option.icon` per-option API**,Select 渲 selected 時 inherit fg-default(對齊 `MenuItem.startIcon` 跨元件 SSOT)
|
|
210
|
+
- 兩 prop 不互斥:`startIcon` field-level 顯示時優先;unset 才落到 selected `option.icon` fallback(select.tsx:197/369 `SelectedIcon` path)
|
|
211
|
+
- **icon kind canonical(2026-05-09 clarified)**:DS 兩種 icon 角色明確區分 — **value icon**(代表 label / 選中項)→ fg-default(MenuItem 內 / `option.icon`)/ **indicator icon**(field-level leading hint)→ muted(Input.startIcon / Select.startIcon)
|
|
212
|
+
|
|
213
|
+
### tag 模式
|
|
214
|
+
|
|
215
|
+
- Tag 元件呈現選中值 + 隱藏的原生 select overlay
|
|
216
|
+
- Tag 設為 `pointer-events-none`,點擊穿透到底層 select
|
|
217
|
+
- edit 模式:Tag + ChevronDown + 可選 clear
|
|
218
|
+
- readonly / disabled:Tag 只顯示,無 ChevronDown
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Clearable
|
|
223
|
+
|
|
224
|
+
`clearable` 在有值時顯示 clear 按鈕。
|
|
225
|
+
|
|
226
|
+
- Clear 按鈕在 ChevronDown 左側
|
|
227
|
+
- 清除後回到 placeholder 狀態
|
|
228
|
+
- 只在 edit 模式顯示
|
|
229
|
+
|
|
230
|
+
**何時開 clearable**:
|
|
231
|
+
- 「無選擇」是有效狀態(選填欄位、可清除的 filter)→ 開
|
|
232
|
+
- 必須有選擇(每個項目都必須有 status)→ 不開
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 常見誤解
|
|
237
|
+
|
|
238
|
+
**誤解**:「Select 只用於表單送出,即時套用應該用別的元件」。
|
|
239
|
+
**事實**:Select 的表單送出與即時套用都是正當用法。Jira、Linear、Notion 的 inline status dropdown 都是 Select + 即時 onChange。關鍵是 onChange 的副作用清楚,不是 Select 本身。
|
|
240
|
+
|
|
241
|
+
**誤解**:「3-5 個選項一定要用 RadioGroup」。
|
|
242
|
+
**事實**:看容器與決策性質,不看數量。Toolbar 的 3 個 filter 必須是 Select / SegmentedControl(RadioGroup 不符 toolbar 視覺);form 中 3 個付款方式用 RadioGroup(決策節點)。見「與 RadioGroup 的分界」。
|
|
243
|
+
|
|
244
|
+
**誤解**:「選項超過 N 個一定要開 searchable」。
|
|
245
|
+
**事實**:主判準是 label 性質,不是數量。見「Searchable 開啟判斷」。
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Loading
|
|
250
|
+
|
|
251
|
+
`loading?: boolean`(forward 給 SelectMenu SSOT,2026-05-15 audit B 補):dropdown body 內取代 options 顯 `<Empty icon={<CircularProgress size={48}/>}/>`(消費 既有 empty.spec.md:182 SSOT)+ `aria-busy`。Trigger 不變(chevron 保留,user 隨時可開)。對齊 MUI Autocomplete `loadingText` 雙層 + Ant Select loading,反 trigger spinner 派。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 禁止事項
|
|
256
|
+
|
|
257
|
+
- ❌ `startIcon` 不可用於 tag 模式——Tag 本身已有視覺標記,startIcon 是冗餘的
|
|
258
|
+
- ❌ 不自建 dropdown menu——使用原生 `<select>` 保證無障礙和行動裝置體驗
|
|
259
|
+
- ❌ 讓使用者搞不清楚是即時還是 on-submit——用 label / 按鈕位置明確傳達
|
|
260
|
+
- ❌ 把「決策節點」選擇塞進 Select(付款方式、訂閱方案)——使用者需要對比評估,用 RadioGroup
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 為何無 StateBehavior
|
|
265
|
+
|
|
266
|
+
Select 是 **Field Controls family 成員**——互動狀態(focus / invalid / disabled / readonly)完全繼承 `../Field/field-controls.spec.md` SSOT「Mode 狀態」。dropdown 開啟時的 MenuItem hover / selected 狀態由 MenuItem primitive own(`patterns/element-anatomy/item-anatomy.spec.md`)。Select 層級無自有 state 行為。重寫 StateBehavior = 複製 Field Controls + MenuItem SSOT 內容,雙邊漂移風險。
|
|
267
|
+
|
|
268
|
+
對應 anatomy story:保留 `Overview` + `Inspector` + `ColorMatrix` + `SizeMatrix`。Field-level state 見 Input `StateBehavior` + field-controls.spec.md;item-level state 見 MenuItem `ColorMatrix`。
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 相關
|
|
273
|
+
|
|
274
|
+
- `segmented-control.spec.md` — 2-5 個緊湊互斥切換;「何時不用」的主要去處
|
|
275
|
+
- `../Checkbox/checkbox.spec.md`(含 RadioGroup 設計原則)— RadioGroup 共用規則
|
|
276
|
+
- `combobox.spec.md` — 多選的對應元件
|
|
277
|
+
- `../Switch/switch.spec.md` — 布林切換
|
|
278
|
+
- `../Field/field-controls.spec.md` — Select 作為 Field control 時的共用規則(mode、size、endAction)
|
|
279
|
+
|
|
280
|
+
## A11y 預設
|
|
281
|
+
|
|
282
|
+
**ARIA / Pattern**:native `<input>` element 預設 a11y;Field wrapper 補 `aria-labelledby` / `aria-invalid` / `aria-describedby`。
|
|
283
|
+
|
|
284
|
+
**Keyboard 行為**:
|
|
285
|
+
|
|
286
|
+
- Tab — focus
|
|
287
|
+
- 字母鍵 — 輸入
|
|
288
|
+
- Esc — 清空(若 clearable + 有值)
|
|
289
|
+
|
|
290
|
+
**Focus**:native input focus ring;DS focus-visible ring(`focus-visible:!border-primary`)由 Field wrapper 提供。
|
|
291
|
+
|
|
292
|
+
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
293
|
+
|
|
294
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
295
|
+
|
|
296
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
297
|
+
|
|
298
|
+
- `people-picker.spec.md`
|
|
299
|
+
- `select-menu.spec.md`
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: SelectMenu
|
|
3
|
+
family: 4
|
|
4
|
+
variants: {}
|
|
5
|
+
sizes: {}
|
|
6
|
+
traits:
|
|
7
|
+
- isInputLike
|
|
8
|
+
- isInternal
|
|
9
|
+
benchmark:
|
|
10
|
+
- Polaris Listbox: github.com/Shopify/polaris/tree/main/polaris-react/src/components/Listbox
|
|
11
|
+
- Radix Select primitive: github.com/radix-ui/primitives/tree/main/packages/react/select
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline; canonical source URLs in frontmatter benchmark list. -->
|
|
15
|
+
|
|
16
|
+
# SelectMenu 設計原則
|
|
17
|
+
|
|
18
|
+
## 定位
|
|
19
|
+
|
|
20
|
+
SelectMenu 是 **Popover + Command 組成的完整下拉選單浮層**——提供搜尋 + 鍵盤導覽 + 分組 + 可建立新選項,作為**選值類**元件的 internal primitive(不直接使用)。
|
|
21
|
+
|
|
22
|
+
**實作基礎**:基於 cmdk(搜尋 / 鍵盤導覽)+ shadcn Popover(浮動容器)+ 消費 MenuItem primitive(item 佈局)。
|
|
23
|
+
|
|
24
|
+
**Layout Family**:非上述 family — composite / multi-section(多區塊組合,自 own layout)。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Controlled-only rationale(Dim 26)
|
|
29
|
+
|
|
30
|
+
本元件刻意採 **controlled-only** 模式:`value` + `onChange` 必傳,不支援 `defaultValue` uncontrolled fallback。
|
|
31
|
+
|
|
32
|
+
**為什麼**:
|
|
33
|
+
- 內部狀態複雜(search filter / range / menu open state)跟 `value` 雙向 sync 會產生 race condition
|
|
34
|
+
- Consumer 幾乎一定有外部 state(form library / app state),強制 controlled 消除 ambiguity
|
|
35
|
+
- 世界級對照:Ant Design DatePicker / Material MUI Select 皆支援 dual-mode;我們選 controlled-only 對齊狀態一致性優先 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
36
|
+
|
|
37
|
+
**若未來要改 dual-mode**:需引入 `useControllableState` helper + 測試 controlled↔uncontrolled switch 場景,屬 major API 擴充,非本 session scope。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 何時用 / 何時不用
|
|
42
|
+
|
|
43
|
+
**SelectMenu 是 internal primitive**——不直接使用,透過外層選值元件消費。
|
|
44
|
+
|
|
45
|
+
| 場景 | 正確做法 |
|
|
46
|
+
|------|---------|
|
|
47
|
+
| 人員選擇器(搜尋 + Avatar)| 用 `PeoplePicker`(內部消費 SelectMenu)|
|
|
48
|
+
| 大量選項單選 + 搜尋 | 用 `Select` with `searchable`(內部會切換到 SelectMenu 模式)|
|
|
49
|
+
| 大量選項多選 + 搜尋 | 用 `Combobox` with `searchable`(內部會切換到 SelectMenu 模式)|
|
|
50
|
+
| 可建立新選項(creatable tag)| 用 `Combobox` + `creatable` prop |
|
|
51
|
+
| 直接在 JSX 中用 `<SelectMenu>` | ❌ **禁止**——失去外層 Select / Combobox / PeoplePicker 的 Field 整合、trigger 行為、state 管理 |
|
|
52
|
+
|
|
53
|
+
### 消費者
|
|
54
|
+
|
|
55
|
+
- `../PeoplePicker/people-picker.tsx` — 人員選擇(永遠 searchable)
|
|
56
|
+
- `../Select/select.tsx` — `searchable` 模式會切換到 SelectMenu 浮層
|
|
57
|
+
- `../Combobox/combobox.tsx` — `searchable` 模式會切換到 SelectMenu 浮層
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 架構
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Popover(浮動容器,handle 展開 / 定位)
|
|
65
|
+
└─ Command(cmdk — 搜尋 + 鍵盤導覽)
|
|
66
|
+
├─ CommandInput(搜尋框,選項 > 5 時顯示)
|
|
67
|
+
├─ CommandList(捲動區)
|
|
68
|
+
│ └─ CommandGroup(分組標題)
|
|
69
|
+
│ └─ MenuItem(選項 row,消費 item-layout)
|
|
70
|
+
└─ Footer(多選全選 checkbox,選填)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**定位**:SelectMenu 的 `sideOffset` 與 `align` 直接走 Popover canonical——`sideOffset=8` / `align` 跟隨 trigger 位置(見 `../Popover/popover.spec.md`「Align 對齊 canonical(跨浮層 SSOT)」)。SelectMenu 不自訂浮層定位規則。
|
|
74
|
+
|
|
75
|
+
**視覺 vs 語意**(2026-04-20 精緻化):SelectMenu 的 **width 預設跟 trigger(input)同寬**(Radix trigger-width),此時 `start` / `center` / `end` 三種 align 呈現視覺完全相同(popover 正好跟 input 貼合,左右邊緣對齊無差異)。但當 consumer 傳 `minWidth` 大於 input 寬度(例:長 option label 要空間展示)、或 option 內容撐開導致 popover 寬於 trigger,**align 的視覺差異立刻顯現**——此時嚴格照 structured overlay canonical(trigger 在左 → start / 中 → center / 右 → end)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
76
|
+
|
|
77
|
+
換言之 SelectMenu **永遠照 canonical**,只是大多數情況 width=trigger-width 讓 canonical 視覺上被 mask,一旦 popover 突破 trigger 寬度 canonical 立刻生效。規則沒例外,只是呈現條件。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 單選 vs 多選
|
|
82
|
+
|
|
83
|
+
透過 `value` / `onValueChange` 的類型決定:
|
|
84
|
+
|
|
85
|
+
- **單選**:`value: string | null`,選中後立即關閉浮層
|
|
86
|
+
- **多選**:`value: string[]`,選中不關閉,可繼續選(footer 可顯示全選 checkbox)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Creatable(建立新選項)
|
|
91
|
+
|
|
92
|
+
透過 `onCreate` prop 啟用。搜尋無結果時顯示「建立 "xxx"」提示(`Plus` icon + 使用者輸入的字)。
|
|
93
|
+
|
|
94
|
+
**何時啟用**:
|
|
95
|
+
- Tag input 允許使用者建立新 tag
|
|
96
|
+
- Assignee 選擇允許邀請外部人員
|
|
97
|
+
- Label / category 自由建立
|
|
98
|
+
|
|
99
|
+
**何時不啟用**:
|
|
100
|
+
- 固定選項清單(狀態、類別、角色)
|
|
101
|
+
- 需要後端驗證合法性的 value(避免建立無效選項)
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 分組(group)
|
|
106
|
+
|
|
107
|
+
透過 `groups` prop 定義分組標籤,每個 option 的 `group` 欄位指向 group key。
|
|
108
|
+
|
|
109
|
+
**何時使用**:
|
|
110
|
+
- 選項明顯分兩個以上邏輯群組(「Recent」/「All」、「Your team」/「Others」)
|
|
111
|
+
- 超過 10 個選項需要視覺分區降低認知負擔
|
|
112
|
+
|
|
113
|
+
**何時不用**:
|
|
114
|
+
- 選項少於 6 個(分組反而增加視覺雜訊)
|
|
115
|
+
- 選項本質平行(沒有自然分組)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Empty state
|
|
120
|
+
|
|
121
|
+
搜尋無結果時顯示 `Empty` 元件,可透過 `emptyText` 自訂訊息(預設「無符合的選項」)。
|
|
122
|
+
|
|
123
|
+
- **Creatable 時**:即使搜尋無結果,仍顯示「建立 "xxx"」讓使用者補建選項
|
|
124
|
+
- **非 creatable**:顯示 emptyText 提示使用者修改搜尋詞
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Loading(2026-05-15 audit B fix, codify 2026-05-16 audit Dim 7+8)
|
|
129
|
+
|
|
130
|
+
非同步載入選項時,consumer 透過 `loading={true}` 控制:
|
|
131
|
+
|
|
132
|
+
- **Trigger 不變**:dropdown 隨時可開(user 看 chevron 不會被 disable)
|
|
133
|
+
- **Dropdown 開啟時**:options 替換為 panel-center `<Empty icon={<CircularProgress size={48}/>} description={loadingText}/>` compose 視覺
|
|
134
|
+
- **CircularProgress 預設 24px**,但在 Empty wrapper 內 48px(取代 Empty 的 48px Avatar icon slot)
|
|
135
|
+
|
|
136
|
+
對齊 MUI Autocomplete `loadingText` dropdown-body + Ant Select loading idiom + DS `empty.spec.md:182`「全頁 loading = Empty + CircularProgress compose」SSOT。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
137
|
+
|
|
138
|
+
**消費**:Select / Combobox / PeoplePicker forward `loading` prop 到 SelectMenu,本元件不需 consumer 直接知道 Empty + CircularProgress 組合(封裝在內)。
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 禁止事項
|
|
143
|
+
|
|
144
|
+
- ❌ 直接在 JSX 用 `<SelectMenu>`——透過外層元件(Select / Combobox / PeoplePicker)消費
|
|
145
|
+
- ❌ 跳過 SelectMenu 自建 Popover + Command 組合——會漂移出共用 layout 與 item-layout 規則
|
|
146
|
+
- ❌ 不搭配 trigger / field 使用——SelectMenu 是浮層,一定需要觸發元件
|
|
147
|
+
- ❌ 超過 50 個選項不開搜尋——純捲動會變低效
|
|
148
|
+
- ❌ 分組少於 2 組——分組本身是視覺成本,只有一組等於沒分組
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 邊界案例
|
|
153
|
+
|
|
154
|
+
- **Disabled option**:individual MenuItem 透過 `disabled?: boolean` 控制(SelectMenu primitive option contract)。視覺繼承 `MenuItem` SSOT:text → `text-fg-disabled`(M24)、無 hover bg、`aria-disabled="true"`、Enter / click 不觸發 onChange、鍵盤導覽自動 skip。
|
|
155
|
+
- **Disabled trigger**:trigger 由 consumer(Select / Combobox / PeoplePicker)的 `disabled` prop own,本元件不獨立 disable trigger。
|
|
156
|
+
- **Loading**:已 codify(見「Loading」段),`loading=true` 時 dropdown body 切 panel-center Empty + CircularProgress 48px。
|
|
157
|
+
- **Empty**:已 codify(見「Empty state」段),搜尋無結果 + 非 creatable 時渲 emptyText;creatable 時保留「建立 "xxx"」row。
|
|
158
|
+
- **Dark mode**:走 Popover / MenuItem semantic token 自動 adapt。
|
|
159
|
+
- **Density**:row height 由 `MenuItem` SSOT 控(sm/md/lg);SelectMenu 不獨立 own density。
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 為何無 ColorMatrix
|
|
164
|
+
|
|
165
|
+
SelectMenu 是**多區塊 composite primitive**,不擁有獨立色彩決策:
|
|
166
|
+
|
|
167
|
+
- **無 ColorMatrix**:視覺完全繼承既有 DS primitive 的 token(`MenuItem` row / `Command` search / `Empty` layout / `Popover` surface),無自己的 bg / border / hover 決策。色彩漂移由 primitive layer 控制——SelectMenu 層級若加 ColorMatrix 會重複 MenuItem / Popover 的矩陣。
|
|
168
|
+
|
|
169
|
+
對應 anatomy story:保留 `Overview` / `Inspector` / `SizeMatrix` / `StateBehavior`,額外追加元件特有的 `ModeMatrix`(single / multi / searchable / creatable / grouped 等功能組合矩陣,這是 SelectMenu 真正的決策面向——取代 ColorMatrix)。
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## shadcn passthrough 例外說明
|
|
174
|
+
|
|
175
|
+
SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem list + 浮動 surface),純 declarative API。**不套 `forwardRef` / `...props` spread`**,同 PeoplePicker 理由:
|
|
176
|
+
|
|
177
|
+
- **沒有單一 DOM root 可 ref**:trigger / search input / list / content portal 各自 DOM tree 離散
|
|
178
|
+
- **`...props` spread 目標不明**:composite 的 root wrapper 只是 control 容器,spread 到那裡 consumer 無從預期作用
|
|
179
|
+
- **API 邊界明確**:SelectMenu 暴露「選值」語意(value / onChange / options / mode),不暴露 DOM 細節
|
|
180
|
+
|
|
181
|
+
`displayName = 'SelectMenu'` 保留。若 consumer 需要 DOM-level 控制(custom trigger / portal / search input ref),改用底層 Popover + Command 自組。
|
|
182
|
+
|
|
183
|
+
`asChild` 不支援(composite 非 Slot-compat)。
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 相關
|
|
188
|
+
|
|
189
|
+
- `../Menu/menu-item.spec.md` — 選項 row 的 item-layout 共用規則(SelectMenu 消費 MenuItem)
|
|
190
|
+
- `../Popover/popover.tsx` — 浮動容器(SelectMenu 消費)
|
|
191
|
+
- `../Command/command.tsx` — cmdk 搜尋 + 鍵盤導覽(SelectMenu 消費)
|
|
192
|
+
- `../Empty/empty.spec.md` — 搜尋無結果的 empty state
|
|
193
|
+
- `../Select/select.spec.md` — 主要消費者之一(searchable 時切換到 SelectMenu)
|
|
194
|
+
- `../Combobox/combobox.spec.md` — 主要消費者之一(searchable 多選時切換到 SelectMenu)
|
|
195
|
+
- `../PeoplePicker/people-picker.spec.md` — 永遠使用 SelectMenu 的消費者
|
|
196
|
+
- `../../patterns/element-anatomy/item-anatomy.spec.md` — item-layout pattern(MenuItem 繼承)
|
|
197
|
+
|
|
198
|
+
## A11y 預設
|
|
199
|
+
|
|
200
|
+
**ARIA / Pattern**:基於 `cmdk` library a11y(combobox / listbox / option role + aria-activedescendant)。詳 [cmdk a11y](https://cmdk.paco.me/#accessibility)。
|
|
201
|
+
|
|
202
|
+
**Keyboard 行為**:
|
|
203
|
+
|
|
204
|
+
- Tab — focus trigger
|
|
205
|
+
- Enter / Space / ↓ — 開啟 menu
|
|
206
|
+
- ↑/↓ — 導覽 options
|
|
207
|
+
- Enter — 選擇
|
|
208
|
+
- 字母鍵 — type-ahead 過濾(search 模式)
|
|
209
|
+
- Esc — 關閉
|
|
210
|
+
|
|
211
|
+
**Focus**:menu 開啟時 focus 第一 option / 選中項;關閉時 focus 回 trigger。
|
|
212
|
+
|
|
213
|
+
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
214
|
+
|
|
215
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
216
|
+
|
|
217
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
218
|
+
|
|
219
|
+
- `command.spec.md`
|
|
220
|
+
- `dropdown-menu.spec.md`
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: SelectionItem
|
|
3
|
+
family: 2
|
|
4
|
+
variants: {}
|
|
5
|
+
sizes:
|
|
6
|
+
sm: {}
|
|
7
|
+
md: {}
|
|
8
|
+
lg: {}
|
|
9
|
+
traits:
|
|
10
|
+
- hasSizes
|
|
11
|
+
- isStructural
|
|
12
|
+
- isInternal
|
|
13
|
+
benchmark:
|
|
14
|
+
- Radix Checkbox primitive: github.com/radix-ui/primitives/tree/main/packages/react/checkbox
|
|
15
|
+
- Radix RadioGroup primitive: github.com/radix-ui/primitives/tree/main/packages/react/radio-group
|
|
16
|
+
- Polaris Checkbox: github.com/Shopify/polaris/tree/main/polaris-react/src/components/Checkbox
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# SelectionItem 設計原則
|
|
20
|
+
|
|
21
|
+
## 定位
|
|
22
|
+
|
|
23
|
+
SelectionItem 是 **Checkbox 和 RadioGroup 共用的 item 佈局 primitive**——提供 control + optional prefix + content(label/description)+ optional suffix 的 4-slot 結構,並處理 padding 公式(`py = (field-height - 1lh) / 2`)讓單行高度對齊同 size 的 Input。
|
|
24
|
+
|
|
25
|
+
> **關於資料夾命名**: `SelectionControl/` 是**概念群組**名稱(對齊 `Menu/` 包 `menu-item.tsx` / `Field/` 包 `field.tsx` 的專案慣例),其內包含主要 primitive `selection-item.tsx`。未來可能增加 `selection-indicator.tsx` 等相關 primitive 到同一資料夾。spec / file 以 main primitive 為準命名(見 `menu-item.spec.md` 先例)。
|
|
26
|
+
|
|
27
|
+
**實作基礎**:自建 internal primitive——純視覺佈局 + padding 公式,無 external primitive base。
|
|
28
|
+
|
|
29
|
+
**Layout Family**:CLAUDE.md 4-Family Model **Family 2(List item layout)** 消費者。結構繼承 `patterns/element-anatomy/item-anatomy.spec.md`「List item layout」章節的 reading-mode 規格。SelectionItem 是 Family 2 的 SelectionItem variant:prefix 放 Checkbox/Radio indicator 而非 icon/avatar。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 何時用 / 何時不用
|
|
34
|
+
|
|
35
|
+
**SelectionControl 是 internal primitive**——不直接使用,透過 Checkbox / RadioGroup 消費。
|
|
36
|
+
|
|
37
|
+
| 場景 | 正確做法 |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| 建立一組 Checkbox 選項 | 用 `Checkbox`(內部消費 SelectionItem)|
|
|
40
|
+
| 建立一組 Radio 選項 | 用 `RadioGroup` + `RadioGroupItem`(內部消費 SelectionItem)|
|
|
41
|
+
| 直接用 `<SelectionItem>` | ❌ **禁止**——會失去 Checkbox / Radio 的 ARIA state、keyboard、form integration |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 為什麼要獨立 primitive
|
|
46
|
+
|
|
47
|
+
Checkbox 和 Radio 視覺幾乎完全一致(差異只在形狀 `rounded-md` vs `rounded-full` 和指示器 check icon vs filled dot),佈局邏輯(prefix / content / suffix 對齊、`py` padding 公式、clamp 政策、disabled 狀態處理)100% 共享。
|
|
48
|
+
|
|
49
|
+
**不獨立**的話兩邊各自實作會漂移——某天改了 Checkbox 的 gap 或 clamp,Radio 會忘記同步。獨立成 SelectionItem 保證兩者視覺 / 行為永遠一致。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 結構
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
[control] [optional prefix (icon | avatar)] [content: label + description] [optional suffix]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `flex items-start gap-2`:控件與 content 對齊第一行
|
|
60
|
+
- 控件包在 `h-[1lh]` 容器中,跟第一行文字垂直置中
|
|
61
|
+
- 多行 label 時,控件保持對齊第一行(不跳到中間)
|
|
62
|
+
|
|
63
|
+
### 字體
|
|
64
|
+
|
|
65
|
+
- `text-body` (sm/md) / `text-body-lg` (lg)——建立 `1lh` context 讓控件高度跟隨字體
|
|
66
|
+
|
|
67
|
+
### Padding 公式
|
|
68
|
+
|
|
69
|
+
`py = (field-height - 1lh) / 2`
|
|
70
|
+
|
|
71
|
+
- 單行時總高度 = field-height(對齊同 size 的 Input)
|
|
72
|
+
- 多行時 padding 不變,自然撐高
|
|
73
|
+
- density 切換時 field-height 自動調整,padding 跟著算
|
|
74
|
+
|
|
75
|
+
### Clamp 政策
|
|
76
|
+
|
|
77
|
+
見 `../Checkbox/checkbox.spec.md`「Clamp 政策」——Label / Description 預設 `'none'`(完整閱讀優先),非「掃視優先」。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 禁止事項
|
|
82
|
+
|
|
83
|
+
- ❌ 直接在 JSX 用 `<SelectionItem>`——透過 Checkbox / Radio 消費
|
|
84
|
+
- ❌ 在 Checkbox / Radio 之外複製 SelectionItem 邏輯——共用源頭一定是 SelectionControl
|
|
85
|
+
- ❌ 改動 `py` padding 公式只針對某一 variant——Checkbox 和 Radio 必須同步
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 為何無 ColorMatrix / StateBehavior
|
|
90
|
+
|
|
91
|
+
SelectionItem 是**純 layout primitive**,只處理 4-slot 結構 + padding 公式,無獨立色彩與互動狀態:
|
|
92
|
+
|
|
93
|
+
- **無 ColorMatrix**:SelectionItem 本身**不設** bg / border color,也**不擁有** control 視覺(Checkbox 方框 / Radio 圓圈)——control 由 consumer 傳入(`control` prop 接 ReactNode)。色彩決策屬於 Checkbox / Radio 層級,其 `.anatomy.stories.tsx` 負責 ColorMatrix(如 Checkbox 的 unchecked / checked / indeterminate × default / hover / disabled 矩陣)。
|
|
94
|
+
- **無 StateBehavior**:SelectionItem 只有 `disabled` 影響 label 顏色(`text-fg-disabled`),無 selected / checked / hover / active——這些狀態屬於傳入的 `control`。Disabled 行為在 `Inspector` 的 props 已足夠展示,不需獨立 story。
|
|
95
|
+
|
|
96
|
+
對應 anatomy story:保留 `Overview` / `Inspector` / `SizeMatrix`,額外追加元件特有的 `PrefixAlignment`(展示 24px 閾值下 icon/avatar 的 inline vs block 對齊行為——這是 SelectionItem 最重要的結構規則,取代 ColorMatrix)。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 邊界案例
|
|
101
|
+
|
|
102
|
+
- **Disabled**:`disabled` 影響 label 顏色(`text-fg-disabled`,M24);control(Checkbox 方框 / Radio 圓圈)的 disabled 視覺由 consumer control 自己 own。SelectionItem 不獨立 own control state。
|
|
103
|
+
- **Loading**:SelectionItem 為 layout primitive 非 async surface;無 loading state。Group-level loading 由 consumer(CheckboxGroup / RadioGroup)在外層處理(Skeleton stack)。
|
|
104
|
+
- **Empty**:label 必傳(internal API contract);無 label-empty 場景。若 `description` 缺,layout 自動收斂為單行。
|
|
105
|
+
- **Dark mode / density**:走 semantic token 自動 adapt;`py` padding 公式對齊 `--field-height-{sm/md/lg}` density token。
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 相關
|
|
110
|
+
|
|
111
|
+
- `../Checkbox/checkbox.spec.md` — 主要消費者之一,含 Clamp 政策 SSOT
|
|
112
|
+
- `../RadioGroup/radio-group.spec.md` — 另一消費者
|
|
113
|
+
- `../../patterns/element-anatomy/item-anatomy.spec.md` — 4-slot 結構的 pattern 來源
|
|
114
|
+
- `../../tokens/uiSize/uiSize.spec.md` — `--field-height-*` token
|
|
115
|
+
|
|
116
|
+
## A11y 預設
|
|
117
|
+
|
|
118
|
+
**ARIA / Pattern**:對齊 [W3C ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/) 對應 pattern。
|
|
119
|
+
|
|
120
|
+
**Keyboard 行為**:
|
|
121
|
+
|
|
122
|
+
- Tab — focus
|
|
123
|
+
- Space — toggle
|
|
124
|
+
|
|
125
|
+
**Focus**:focus-visible ring 對齊 DS canonical(`outline: 2px solid var(--ring)`);focus management 由元件 own。
|
|
126
|
+
|
|
127
|
+
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
128
|
+
|