@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.
Files changed (307) hide show
  1. package/CLAUDE.md +201 -0
  2. package/README.md +7 -15
  3. package/cli-init.mjs +90 -0
  4. package/ds-canonical/commands/README.md +26 -0
  5. package/ds-canonical/commands/gov-status.md +79 -0
  6. package/ds-canonical/hooks/README.md +145 -0
  7. package/ds-canonical/hooks/_log-fire.sh +44 -0
  8. package/ds-canonical/hooks/block_prototype_imports.py +111 -0
  9. package/ds-canonical/hooks/check_app_shell_primary_header_consistency.sh +68 -0
  10. package/ds-canonical/hooks/check_audit_post_report_validator.sh +88 -0
  11. package/ds-canonical/hooks/check_audit_sample_escape.sh +73 -0
  12. package/ds-canonical/hooks/check_benchmark_citation.sh +106 -0
  13. package/ds-canonical/hooks/check_canonical_propagation.sh +189 -0
  14. package/ds-canonical/hooks/check_chrome_header_handcraft.sh +70 -0
  15. package/ds-canonical/hooks/check_codex_brief_invariants.sh +83 -0
  16. package/ds-canonical/hooks/check_codex_collab_5step.sh +108 -0
  17. package/ds-canonical/hooks/check_datatable_invariants.sh +117 -0
  18. package/ds-canonical/hooks/check_dim_count_drift.sh +72 -0
  19. package/ds-canonical/hooks/check_field_controls_contracts.sh +110 -0
  20. package/ds-canonical/hooks/check_field_family_invariants.sh +205 -0
  21. package/ds-canonical/hooks/check_file_size_budget.sh +60 -0
  22. package/ds-canonical/hooks/check_header_with_tabs_border.sh +87 -0
  23. package/ds-canonical/hooks/check_main_branch_workbench.sh +93 -0
  24. package/ds-canonical/hooks/check_naming_and_abstraction.sh +165 -0
  25. package/ds-canonical/hooks/check_opacity_token_usage.sh +149 -0
  26. package/ds-canonical/hooks/check_pattern_invariants.sh +194 -0
  27. package/ds-canonical/hooks/check_peoplepicker_ssot_drift.sh +56 -0
  28. package/ds-canonical/hooks/check_pixel_quantified_audit.sh +53 -0
  29. package/ds-canonical/hooks/check_propose_plain_chinese.sh +74 -0
  30. package/ds-canonical/hooks/check_propose_pre_grep_verify.sh +70 -0
  31. package/ds-canonical/hooks/check_select_all_canonical.sh +58 -0
  32. package/ds-canonical/hooks/check_solo_workflow.sh +258 -0
  33. package/ds-canonical/hooks/check_spec_class_drift.sh +88 -0
  34. package/ds-canonical/hooks/check_story_invariants.sh +612 -0
  35. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +105 -0
  36. package/ds-canonical/hooks/check_tab_lg_chrome_header_equal.sh +66 -0
  37. package/ds-canonical/hooks/check_wrapper_primitive_schema_drift.sh +104 -0
  38. package/ds-canonical/hooks/enforce_home_charter.sh +44 -0
  39. package/ds-canonical/hooks/inject_pending_self_audit.sh +204 -0
  40. package/ds-canonical/hooks/lib/_approval_re.sh +33 -0
  41. package/ds-canonical/hooks/lib/_code_quality.sh +73 -0
  42. package/ds-canonical/hooks/lib/_cva_default_sync.sh +69 -0
  43. package/ds-canonical/hooks/lib/_governance_coverage_check.sh +49 -0
  44. package/ds-canonical/hooks/lib/_hardcoded_strings.sh +163 -0
  45. package/ds-canonical/hooks/lib/_layout_space_canonical.sh +56 -0
  46. package/ds-canonical/hooks/lib/_overlay_handcraft.sh +141 -0
  47. package/ds-canonical/hooks/lib/_person_data_richness.sh +42 -0
  48. package/ds-canonical/hooks/lib/_story_compile_drift.sh +48 -0
  49. package/ds-canonical/hooks/lib/_token_hygiene.sh +95 -0
  50. package/ds-canonical/hooks/log_governance_fires.sh +50 -0
  51. package/ds-canonical/hooks/log_skill_invokes.sh +41 -0
  52. package/ds-canonical/hooks/post_edit_dispatcher.sh +62 -0
  53. package/ds-canonical/hooks/retired/check_anatomy_section_numbering.sh +106 -0
  54. package/ds-canonical/hooks/retired/check_avatar_hovercard.sh +90 -0
  55. package/ds-canonical/hooks/retired/check_button_icon_literal.sh.retired-2026-04-28 +38 -0
  56. package/ds-canonical/hooks/retired/check_container_breathing.sh +142 -0
  57. package/ds-canonical/hooks/retired/check_governance_compliance.sh +61 -0
  58. package/ds-canonical/hooks/retired/check_icon_only_padding_formula.sh +104 -0
  59. package/ds-canonical/hooks/retired/check_item_content_primitive.sh +150 -0
  60. package/ds-canonical/hooks/retired/check_item_list_gap.sh +153 -0
  61. package/ds-canonical/hooks/retired/check_sideoffset_canonical.sh +65 -0
  62. package/ds-canonical/hooks/retired/check_spec_iteration_tag.sh +87 -0
  63. package/ds-canonical/hooks/retired/check_ssot_consultation.sh +88 -0
  64. package/ds-canonical/hooks/retired/check_sync_update.sh +20 -0
  65. package/ds-canonical/hooks/retired/check_third_party_dom_verified.sh +95 -0
  66. package/ds-canonical/hooks/retired/enforce_home_charter.sh +125 -0
  67. package/ds-canonical/hooks/retired/post_edit_canonical_interrogate.sh +109 -0
  68. package/ds-canonical/hooks/retired/pre_edit_spec_check.sh +68 -0
  69. package/ds-canonical/hooks/retired/pre_new_component_spec.sh +39 -0
  70. package/ds-canonical/hooks/retired/pre_write_subsumption_check.sh +112 -0
  71. package/ds-canonical/hooks/retired/stop_meta_self_audit.sh.retired-2026-05-13 +76 -0
  72. package/ds-canonical/hooks/retired/tests/test_check_anatomy_section_numbering.sh +14 -0
  73. package/ds-canonical/hooks/retired/tests/test_check_avatar_hovercard.sh +15 -0
  74. package/ds-canonical/hooks/retired/tests/test_check_container_breathing.sh +15 -0
  75. package/ds-canonical/hooks/retired/tests/test_check_governance_compliance.sh +15 -0
  76. package/ds-canonical/hooks/retired/tests/test_check_icon_only_padding_formula.sh +79 -0
  77. package/ds-canonical/hooks/retired/tests/test_check_item_content_primitive.sh +15 -0
  78. package/ds-canonical/hooks/retired/tests/test_check_item_list_gap.sh +163 -0
  79. package/ds-canonical/hooks/retired/tests/test_check_sideoffset_canonical.sh +15 -0
  80. package/ds-canonical/hooks/retired/tests/test_check_spec_iteration_tag.sh +15 -0
  81. package/ds-canonical/hooks/retired/tests/test_check_ssot_consultation.sh +15 -0
  82. package/ds-canonical/hooks/retired/tests/test_check_sync_update.sh +14 -0
  83. package/ds-canonical/hooks/retired/tests/test_check_third_party_dom_verified.sh +15 -0
  84. package/ds-canonical/hooks/retired/tests/test_enforce_home_charter.sh +15 -0
  85. package/ds-canonical/hooks/retired/tests/test_pre_edit_spec_check.sh +15 -0
  86. package/ds-canonical/hooks/retired/tests/test_pre_new_component_spec.sh +15 -0
  87. package/ds-canonical/hooks/retired/tests/test_pre_write_subsumption_check.sh +63 -0
  88. package/ds-canonical/hooks/session_start_governance_check.sh +263 -0
  89. package/ds-canonical/hooks/stop_passive_logging.sh +322 -0
  90. package/ds-canonical/hooks/stop_self_audit.sh +450 -0
  91. package/ds-canonical/hooks/tests/KNOWN-BROKEN.md +15 -0
  92. package/ds-canonical/hooks/tests/run-all.sh +76 -0
  93. package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +143 -0
  94. package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +140 -0
  95. package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +115 -0
  96. package/ds-canonical/hooks/tests/test_check_audit_sample_escape.sh +93 -0
  97. package/ds-canonical/hooks/tests/test_check_benchmark_citation.sh +115 -0
  98. package/ds-canonical/hooks/tests/test_check_canonical_propagation.sh +133 -0
  99. package/ds-canonical/hooks/tests/test_check_chrome_header_handcraft.sh +123 -0
  100. package/ds-canonical/hooks/tests/test_check_code_quality.sh +15 -0
  101. package/ds-canonical/hooks/tests/test_check_codex_collab_5step.sh +96 -0
  102. package/ds-canonical/hooks/tests/test_check_cva_default_sync.sh +15 -0
  103. package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +122 -0
  104. package/ds-canonical/hooks/tests/test_check_dim_count_drift.sh +98 -0
  105. package/ds-canonical/hooks/tests/test_check_field_controls_contracts.sh +126 -0
  106. package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +194 -0
  107. package/ds-canonical/hooks/tests/test_check_file_size_budget.sh +32 -0
  108. package/ds-canonical/hooks/tests/test_check_hardcoded_strings.sh +14 -0
  109. package/ds-canonical/hooks/tests/test_check_header_with_tabs_border.sh +110 -0
  110. package/ds-canonical/hooks/tests/test_check_layout_space_canonical.sh +73 -0
  111. package/ds-canonical/hooks/tests/test_check_main_branch_workbench.sh +147 -0
  112. package/ds-canonical/hooks/tests/test_check_naming_and_abstraction.sh +136 -0
  113. package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +110 -0
  114. package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +126 -0
  115. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +148 -0
  116. package/ds-canonical/hooks/tests/test_check_peoplepicker_ssot_drift.sh +108 -0
  117. package/ds-canonical/hooks/tests/test_check_person_data_richness.sh +58 -0
  118. package/ds-canonical/hooks/tests/test_check_pixel_quantified_audit.sh +142 -0
  119. package/ds-canonical/hooks/tests/test_check_propose_plain_chinese.sh +126 -0
  120. package/ds-canonical/hooks/tests/test_check_propose_pre_grep_verify.sh +117 -0
  121. package/ds-canonical/hooks/tests/test_check_select_all_canonical.sh +125 -0
  122. package/ds-canonical/hooks/tests/test_check_solo_workflow.sh +201 -0
  123. package/ds-canonical/hooks/tests/test_check_spec_class_drift.sh +135 -0
  124. package/ds-canonical/hooks/tests/test_check_story_anatomy.sh.broken +197 -0
  125. package/ds-canonical/hooks/tests/test_check_story_category.sh.broken +187 -0
  126. package/ds-canonical/hooks/tests/test_check_story_compile_drift.sh +15 -0
  127. package/ds-canonical/hooks/tests/test_check_story_invariants.sh +209 -0
  128. package/ds-canonical/hooks/tests/test_check_story_name_jargon.sh.broken +53 -0
  129. package/ds-canonical/hooks/tests/test_check_story_slot_split.sh +156 -0
  130. package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +176 -0
  131. package/ds-canonical/hooks/tests/test_check_tab_lg_chrome_header_equal.sh +138 -0
  132. package/ds-canonical/hooks/tests/test_check_token_hygiene.sh +21 -0
  133. package/ds-canonical/hooks/tests/test_check_wrapper_primitive_schema_drift.sh +169 -0
  134. package/ds-canonical/hooks/tests/test_enforce_home_charter.sh +77 -0
  135. package/ds-canonical/hooks/tests/test_inject_pending_self_audit.sh +125 -0
  136. package/ds-canonical/hooks/tests/test_log_governance_fires.sh +10 -0
  137. package/ds-canonical/hooks/tests/test_log_skill_invokes.sh +7 -0
  138. package/ds-canonical/hooks/tests/test_post_edit_dispatcher.sh +108 -0
  139. package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +143 -0
  140. package/ds-canonical/hooks/tests/test_stop_capture_metrics.sh +95 -0
  141. package/ds-canonical/hooks/tests/test_stop_governance_drift_check.sh.broken +125 -0
  142. package/ds-canonical/hooks/tests/test_stop_harvest_corrections.sh +10 -0
  143. package/ds-canonical/hooks/tests/test_stop_passive_logging.sh +100 -0
  144. package/ds-canonical/hooks/tests/test_stop_self_audit.sh +76 -0
  145. package/ds-canonical/hooks/tests/test_stop_tsc_sanity.sh +10 -0
  146. package/ds-canonical/references/README.md +43 -0
  147. package/ds-canonical/references/audit-coverage-vs-24-checklist.md +74 -0
  148. package/ds-canonical/references/build-ui-canonicals.md +69 -0
  149. package/ds-canonical/references/cva-patterns.md +41 -0
  150. package/ds-canonical/references/drag-canonical.md +331 -0
  151. package/ds-canonical/references/item-anatomy-recipe.md +225 -0
  152. package/ds-canonical/references/naming-conventions.md +56 -0
  153. package/ds-canonical/references/principle-dim-map.json +515 -0
  154. package/ds-canonical/references/props-naming.md +45 -0
  155. package/ds-canonical/references/spec-rules.md +58 -0
  156. package/ds-canonical/references/ssot-consultation.md +63 -0
  157. package/ds-canonical/references/ssot-index.md +40 -0
  158. package/ds-canonical/references/story-baseline-registry.json +79 -0
  159. package/ds-canonical/references/structural-token-retention.md +42 -0
  160. package/ds-canonical/references/tailwind-gotchas.md +87 -0
  161. package/ds-canonical/references/ui-dev-rules.md +60 -0
  162. package/ds-canonical/rules/README.md +34 -0
  163. package/ds-canonical/rules/meta-patterns.md +87 -0
  164. package/ds-canonical/rules/self-verify.md +53 -0
  165. package/ds-canonical/rules/spec-rules.md +25 -0
  166. package/ds-canonical/rules/story-rules.md +56 -0
  167. package/ds-canonical/rules/ui-development.md +87 -0
  168. package/ds-canonical/skills/README.md +88 -0
  169. package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +181 -0
  170. package/ds-canonical/skills/code-quality-audit/SKILL.md +63 -0
  171. package/ds-canonical/skills/codex-collab/SKILL.md +249 -0
  172. package/ds-canonical/skills/codex-collab/references/brief-template.md +48 -0
  173. package/ds-canonical/skills/codex-collab/references/transport.md +58 -0
  174. package/ds-canonical/skills/codify-corrections/SKILL.md +184 -0
  175. package/ds-canonical/skills/codify-principle/SKILL.md +151 -0
  176. package/ds-canonical/skills/component-quality-gate/SKILL.md +102 -0
  177. package/ds-canonical/skills/component-quality-gate/references/checklist.md +79 -0
  178. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +247 -0
  179. package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +123 -0
  180. package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +165 -0
  181. package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +91 -0
  182. package/ds-canonical/skills/delivery-handoff/SKILL.md +229 -0
  183. package/ds-canonical/skills/delivery-handoff/references/flow-diagram.md +180 -0
  184. package/ds-canonical/skills/delivery-handoff/references/handoff-template.md +177 -0
  185. package/ds-canonical/skills/delivery-handoff/references/inventory-checklist.md +196 -0
  186. package/ds-canonical/skills/design-system-audit/SKILL.md +343 -0
  187. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1260 -0
  188. package/ds-canonical/skills/design-system-audit/references/checkpoints.md +240 -0
  189. package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +240 -0
  190. package/ds-canonical/skills/design-system-audit/references/principle-audit-protocol.md +364 -0
  191. package/ds-canonical/skills/design-system-audit/references/rule-placement.md +175 -0
  192. package/ds-canonical/skills/design-system-audit/references/spec-template.md +66 -0
  193. package/ds-canonical/skills/ensure-canonical/SKILL.md +196 -0
  194. package/ds-canonical/skills/governance-health/SKILL.md +146 -0
  195. package/ds-canonical/skills/knowledge-prune/SKILL.md +303 -0
  196. package/ds-canonical/skills/new-component/SKILL.md +170 -0
  197. package/ds-canonical/skills/new-component/references/new-component-checklist.md +85 -0
  198. package/ds-canonical/skills/performance-audit/SKILL.md +107 -0
  199. package/ds-canonical/skills/product-ui-audit/SKILL.md +230 -0
  200. package/ds-canonical/skills/product-ui-audit/references/audit-checks.md +246 -0
  201. package/ds-canonical/skills/product-ui-audit/references/common-misuses.md +329 -0
  202. package/ds-canonical/skills/product-ui-audit/references/report-template.md +159 -0
  203. package/ds-canonical/skills/propose-options/SKILL.md +177 -0
  204. package/ds-canonical/skills/prototype/SKILL.md +244 -0
  205. package/ds-canonical/skills/prototype/references/audit-checks.md +37 -0
  206. package/ds-canonical/skills/prototype/references/benchmark-sources.md +94 -0
  207. package/ds-canonical/skills/prototype/references/checkpoints.md +191 -0
  208. package/ds-canonical/skills/prototype/references/evaluation-matrix.md +141 -0
  209. package/ds-canonical/skills/prototype/references/ooux-template.md +198 -0
  210. package/ds-canonical/skills/prototype/references/proposal-template.md +229 -0
  211. package/ds-canonical/skills/scan-similar-bugs/SKILL.md +198 -0
  212. package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +159 -0
  213. package/ds-canonical/skills/story-writing/SKILL.md +122 -0
  214. package/ds-canonical/skills/story-writing/references/anatomy-standard.md +217 -0
  215. package/ds-canonical/skills/story-writing/references/category-templates.md +174 -0
  216. package/ds-canonical/skills/story-writing/references/example-selection.md +70 -0
  217. package/ds-canonical/skills/story-writing/references/self-check.md +20 -0
  218. package/ds-canonical/skills/ux-audit/SKILL.md +130 -0
  219. package/ds-canonical/skills/visual-audit/SKILL.md +245 -0
  220. package/ds-canonical/skills/visual-audit/output/.gitkeep +0 -0
  221. package/ds-canonical/skills/visual-audit/references/audit-architecture.md +100 -0
  222. package/ds-canonical/skills/visual-audit/references/visual-checklist.md +297 -0
  223. package/ds-canonical/skills/visual-audit/references/world-class-benchmarks.md +198 -0
  224. package/package.json +9 -5
  225. package/src/components/Accordion/accordion.spec.md +114 -0
  226. package/src/components/Alert/alert.spec.md +197 -0
  227. package/src/components/AppShell/app-shell.spec.md +331 -0
  228. package/src/components/AspectRatio/aspect-ratio.spec.md +134 -0
  229. package/src/components/Avatar/avatar.spec.md +329 -0
  230. package/src/components/Badge/badge.spec.md +380 -0
  231. package/src/components/Breadcrumb/breadcrumb.spec.md +257 -0
  232. package/src/components/BulkActionBar/bulk-action-bar.spec.md +210 -0
  233. package/src/components/Button/button.spec.md +460 -0
  234. package/src/components/Calendar/calendar.spec.md +242 -0
  235. package/src/components/Carousel/carousel.spec.md +253 -0
  236. package/src/components/Chart/chart.spec.md +155 -0
  237. package/src/components/Checkbox/checkbox.spec.md +344 -0
  238. package/src/components/Chip/chip.spec.md +237 -0
  239. package/src/components/CircularProgress/circular-progress.spec.md +268 -0
  240. package/src/components/Coachmark/coachmark.spec.md +230 -0
  241. package/src/components/Combobox/combobox.spec.md +180 -0
  242. package/src/components/Command/command.spec.md +171 -0
  243. package/src/components/DataTable/data-table.spec.md +525 -0
  244. package/src/components/DateGrid/date-grid.spec.md +215 -0
  245. package/src/components/DatePicker/date-picker.spec.md +334 -0
  246. package/src/components/DescriptionList/description-list.spec.md +214 -0
  247. package/src/components/Dialog/dialog.spec.md +202 -0
  248. package/src/components/DropdownMenu/dropdown-menu.spec.md +250 -0
  249. package/src/components/Empty/empty.spec.md +214 -0
  250. package/src/components/Field/field-controls.spec.md +338 -0
  251. package/src/components/Field/field.spec.md +438 -0
  252. package/src/components/Field/form-validation.spec.md +152 -0
  253. package/src/components/FieldControlGroup/field-control-group.spec.md +176 -0
  254. package/src/components/FileItem/file-item.spec.md +467 -0
  255. package/src/components/FileUpload/file-upload.spec.md +123 -0
  256. package/src/components/FileViewer/file-viewer.spec.md +373 -0
  257. package/src/components/HoverCard/hover-card.spec.md +157 -0
  258. package/src/components/Input/input.spec.md +193 -0
  259. package/src/components/LinkInput/link-input.spec.md +130 -0
  260. package/src/components/Menu/menu-item.spec.md +290 -0
  261. package/src/components/NameCard/name-card.spec.md +171 -0
  262. package/src/components/Notice/notice.spec.md +149 -0
  263. package/src/components/NumberInput/number-input.spec.md +126 -0
  264. package/src/components/OverflowIndicator/overflow-indicator.spec.md +120 -0
  265. package/src/components/PeoplePicker/people-picker.spec.md +263 -0
  266. package/src/components/Popover/popover.spec.md +198 -0
  267. package/src/components/ProgressBar/progress-bar.spec.md +232 -0
  268. package/src/components/RadioGroup/radio-group.spec.md +141 -0
  269. package/src/components/Rating/rating.spec.md +208 -0
  270. package/src/components/ScrollArea/scroll-area.spec.md +145 -0
  271. package/src/components/SegmentedControl/segmented-control.spec.md +295 -0
  272. package/src/components/Select/select.spec.md +299 -0
  273. package/src/components/SelectMenu/select-menu.spec.md +220 -0
  274. package/src/components/SelectionControl/selection-item.spec.md +128 -0
  275. package/src/components/Separator/separator.spec.md +109 -0
  276. package/src/components/Sheet/sheet.spec.md +148 -0
  277. package/src/components/Sidebar/sidebar.spec.md +713 -0
  278. package/src/components/Skeleton/skeleton.spec.md +104 -0
  279. package/src/components/Slider/slider.spec.md +353 -0
  280. package/src/components/Steps/steps.spec.md +465 -0
  281. package/src/components/Switch/switch.spec.md +215 -0
  282. package/src/components/Tabs/tabs.spec.md +314 -0
  283. package/src/components/Tag/tag.spec.md +282 -0
  284. package/src/components/Textarea/textarea.spec.md +151 -0
  285. package/src/components/TimePicker/time-picker.spec.md +279 -0
  286. package/src/components/Toast/toast.spec.md +177 -0
  287. package/src/components/Tooltip/tooltip.spec.md +139 -0
  288. package/src/components/TreeView/tree-view.spec.md +374 -0
  289. package/src/patterns/action-bar/action-bar.spec.md +458 -0
  290. package/src/patterns/element-anatomy/element-anatomy.spec.md +215 -0
  291. package/src/patterns/element-anatomy/inline-action.spec.md +315 -0
  292. package/src/patterns/element-anatomy/item-anatomy.spec.md +1042 -0
  293. package/src/patterns/header-canonical/header-canonical.spec.md +285 -0
  294. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +191 -0
  295. package/src/patterns/overlay-surface/overlay-surface.spec.md +428 -0
  296. package/src/patterns/resize-handle/resize-handle.spec.md +109 -0
  297. package/src/tokens/color/color.spec.md +804 -0
  298. package/src/tokens/density/density.spec.md +127 -0
  299. package/src/tokens/elevation/elevation.spec.md +81 -0
  300. package/src/tokens/layoutSpace/layoutSpace.spec.md +314 -0
  301. package/src/tokens/motion/motion.spec.md +97 -0
  302. package/src/tokens/opacity/opacity.spec.md +78 -0
  303. package/src/tokens/orphan-tokens.spec.md +117 -0
  304. package/src/tokens/radius/radius.spec.md +123 -0
  305. package/src/tokens/typography/typography.spec.md +202 -0
  306. package/src/tokens/uiSize/uiSize.spec.md +438 -0
  307. package/src/styles/preset.css +0 -31
@@ -0,0 +1,428 @@
1
+ ---
2
+ pattern: overlay-surface
3
+ internal: true
4
+ scope: overlay shell sub-components (SurfaceHeader / SurfaceBody / SurfaceFooter + padding SSOT) — DS-internal consumer only(Dialog / Sheet / Popover / HoverCard / Coachmark wrap)
5
+ ---
6
+
7
+ <!-- @benchmark-cited: D5 retrofit 2026-05-18 — body claims marked per-claim @benchmark-unverified inline; canonical source URLs in frontmatter benchmark list. -->
8
+
9
+ # Overlay Surface 設計原則
10
+
11
+ > **Foundational SSOT rationale**(cap 800,2026-04-25 approved):
12
+ > Dialog / Sheet / Popover / Coachmark / HoverCard 等 overlay 元件 structure 跨 pattern SSOT。定義 SurfaceHeader / SurfaceBody / SurfaceFooter sub-components + v5 `data-unbounded` slot trick + padding-based header canonical + dismiss size canonical。多 overlay 元件消費,scope 本質 > 單一 pattern。
13
+
14
+ ## 定位
15
+
16
+ Dialog 和 Popover 的**結構化 sub-components 共用 primitive**——提供 Header / Body / Footer 的統一 padding + 分隔線語言。本 pattern 是 **SSOT**,Dialog 與 Popover 不自寫 padding token。
17
+
18
+ **Layout Family**:非上述 family — structural container primitive(不是 element-level layout,是 surface-level 分區)。
19
+
20
+ **Consumers**:`Dialog` / `Popover`。未來任何其他「elevation-200 浮層」(如 Drawer / Sheet)的結構化 sub-components 都應消費本 primitive。
21
+
22
+ ---
23
+
24
+ ## 規則
25
+
26
+ ### SurfaceHeader
27
+ - `border-b border-divider`(上下分隔)
28
+ - `px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]`
29
+ - `flex items-center gap-2 shrink-0`(不被 flex-grow 壓縮)
30
+
31
+ ### SurfaceBody
32
+ - `px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]`
33
+ - **無額外 flex 屬性**——consumer 依浮層類型決定:
34
+ - **Popover**:多數 bare consume,padding 即是總 padding
35
+ - **Dialog / Sheet**:consumer **不直接 wrap SurfaceBody**——走 ScrollArea canonical(下節),padding 搬到 ScrollArea viewport 內層 div
36
+
37
+ ---
38
+
39
+ ## Body overflow canonical(Dialog / Sheet 必用 ScrollArea)
40
+
41
+ **規則**:Dialog / Sheet 的 body 會 viewport-fill + 長內容需捲動時,**必須用 `<ScrollArea>` wrap**,禁止自寫 `overflow-y-auto` / `overflow-auto`。
42
+
43
+ **Rationale**:
44
+ - Native scrollbar 跨 OS 不一致(macOS overlay / Windows 永遠吃 ~17px 寬度)——Dialog / Sheet 內容會因 OS 不同跑版
45
+ - ScrollArea(Radix primitive)用自建 overlay 捲軸 → **跨 OS 一致不吃寬度**,捲動時浮現
46
+ - SSOT 見 `components/ScrollArea/scroll-area.spec.md`「何時用」已列明「Sheet / Dialog body 太長」
47
+
48
+ **實作模板**:
49
+ ```tsx
50
+ // DialogBody / SheetBody 內部:
51
+ <ScrollArea className="flex-1 min-h-0">
52
+ <div className="px-[var(--layout-space-loose)] pt-[var(--layout-space-tight)] pb-[var(--layout-space-bottom)]">
53
+ {children}
54
+ </div>
55
+ </ScrollArea>
56
+ ```
57
+
58
+ - `flex-1 min-h-0` → 撐滿 Content 剩餘高度(min-h-0 防止 flex child 撐破 container)
59
+ - Padding 搬進 ScrollArea viewport 內的 inner div(因 ScrollArea Root 自己是 `overflow-hidden`,padding 應在捲動內容上)
60
+ - `pb-bottom` 保留 Dialog / Sheet「大容器底部多一拍」的 canonical
61
+
62
+ **Popover 例外**:Popover 無 viewport-fill、內容預期短,PopoverBody 直接消費 SurfaceBody bare;若未來有長內容 Popover consumer,同樣應 wrap ScrollArea。
63
+
64
+ **Coachmark 例外**:Coachmark 內容短(media + 2 行 title/description),不設計 body 捲動;不適用本規則。
65
+
66
+ ---
67
+
68
+ ## List-as-region in overlay body(2026-05-01 canonical,取代 v4 flush API)
69
+
70
+ 當 overlay body(Dialog / Sheet / Popover)**內容是一個 unbounded list**(contact picker / settings menu / command palette / nav)時 — body 不該有 chrome padding,讓 list 自管視覺節奏。
71
+
72
+ ### 為什麼**不**做成 body variant(`flush`)
73
+
74
+ 2026-05-01 移除 `<DialogBody flush>` / `<SheetBody flush>` / `<PopoverBody flush>` variant。原因:
75
+
76
+ 1. **Variant 不解決底層脆弱**:flush 只省一行 chrome padding override;consumer 仍要管 list outer `py-2` + item `px-loose rounded-md` — 加 1 row search/banner 就破功(body 反而沒 chrome padding,更難排版)。
77
+ 2. **世界級主流不做 universal flush**:Material M3 / Atlassian Dialog / Mantine Modal / shadcn Dialog 都讓 consumer 用 className override 處理。Polaris 有 flush API 但 scope 極窄(只 ResourceList in Modal)。Mainstream 把這個 case 歸 consumer 自管。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
78
+ 3. **Single API surface**:body 一律 chrome padded,list-only 場景用 `className="!px-0 !pt-0 !pb-0"` override + 自管 list outer wrapper — surface 概念清楚,不雙路徑。
79
+
80
+ ### Canonical pattern
81
+
82
+ ```tsx
83
+ <DialogBody className="!px-0 !pt-0 !pb-0"> {/* body 撤 chrome padding */}
84
+ <div className="py-2"> {/* list outer wrapper:menu group 8px breathing */}
85
+ {items.map(item => (
86
+ <MenuItem key={item.id} className="px-[var(--layout-space-loose)]">{item.label}</MenuItem>
87
+ // 或 hand-craft Family 2 row(prefix+content)時:
88
+ // <div className="flex items-center gap-3 py-2 px-[var(--layout-space-loose)] rounded-md hover:bg-neutral-hover">
89
+ ))}
90
+ </div>
91
+ </DialogBody>
92
+ ```
93
+
94
+ **3 條 invariant**(unique 解):
95
+ 1. **Hover bg 貼邊 chrome**:item `px-loose` 讓 hover bg 鋪滿 chrome 內邊(Linear / Cmd+K idiom)
96
+ 2. **Content 對齊 header title**:item content 左 = chrome `px-loose` 起點 = header title 左 X 軸對齊
97
+ 3. **Content 在 hover bg 內有 breathing**:item `px-loose rounded-md` → content 離 bg 邊緣 loose 距離
98
+
99
+ 幾何:
100
+ ```
101
+ chrome 邊 ─ hover bg 左邊 ─────── [ loose breathing ] ─────── content 左邊
102
+ (x=0) (x=0, flush chrome) (x=loose, 對齊 header)
103
+ ```
104
+
105
+ ### 世界級對照(Linear-family canonical;≥5 家) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
106
+
107
+ | DS | Body padding | Item padding | Hover bg flush chrome? |
108
+ |----|---|---|---|
109
+ | Linear Cmd+K | 0 | loose | ✓ |
110
+ | Notion page list | 0 | loose | ✓ |
111
+ | Slack channel list | 0 | loose | ✓ |
112
+ | Raycast / Spotlight | 0 | loose | ✓ |
113
+ | VS Code Quick Pick | 0 | loose | ✓ |
114
+ | Material M3 / Polaris Modal+List | px only | item inset | ✗(另一合法家族,鬆散版)|
115
+
116
+ 本 DS 選 Linear-family。
117
+
118
+ ### When list 上方有 search / banner(multi-row)
119
+
120
+ list 不再是 body 唯一 region → **不該撤 body chrome padding**(撤了反而 search row 沒呼吸)。直接用預設 `<DialogBody>`(chrome padded),list 自管 outer wrapper。
121
+
122
+ ```tsx
123
+ <DialogBody> {/* 預設 chrome padding */}
124
+ <Input search /> {/* search row */}
125
+ <div className="mt-tight py-2 -mx-loose"> {/* list outer:撤 body 水平 padding 讓 item flush chrome */}
126
+ {items.map(...)}
127
+ </div>
128
+ </DialogBody>
129
+ ```
130
+
131
+ (此 case 罕見,優先考慮:這個 flow 該用 `Combobox` / `SelectMenu` 而非 Dialog)
132
+
133
+ ### Menu 移植到 Dialog body(short text options / single-click commit)
134
+
135
+ 用 `MenuItem` primitive,不自刻 `<button>` hand-craft:
136
+
137
+ ```tsx
138
+ <DialogBody className="!px-0 !pt-0 !pb-0">
139
+ <div className="py-2">
140
+ {options.map(o => (
141
+ // MenuItem 預設 px-3 → className override 為 px-loose 對齊 dialog header
142
+ <MenuItem key={o.value} className="px-[var(--layout-space-loose)]" onSelect={...}>
143
+ {o.label}
144
+ </MenuItem>
145
+ ))}
146
+ </div>
147
+ </DialogBody>
148
+ ```
149
+
150
+ **何時該用 MenuItem vs hand-craft**:
151
+ | 情境 | 選 | 理由 |
152
+ |------|----|------|
153
+ | 純文字 / icon + label 選項(scanning mode)| MenuItem | Family 1 menu rhythm |
154
+ | avatar + title + description(reading mode)| hand-craft Family 2 結構 | MenuItem 是 scanning typography |
155
+ | 要做 `<Command>` 搜尋 | SelectMenu(cmdk-based)| dialog 內用獨立 primitive |
156
+
157
+ **更高層設計判斷**:
158
+ - 單擊即生效 → `DropdownMenu` / `SelectMenu`(浮層),**不用 Dialog**
159
+ - 暫存選擇 + Save CTA 才 commit → `Dialog + MenuItem`(本 pattern)
160
+
161
+ ### M11 state walk hover 檢查(三 invariant 必同時 ✓)
162
+
163
+ 1. hover bg 左右邊 = chrome 邊?
164
+ 2. content 左邊 = header title 左邊?
165
+ 3. content 離 hover bg 邊 ≥ loose?
166
+
167
+ ### ❌ 禁止
168
+
169
+ - 重新引入 `flush` variant prop(或 `variant="list"` / `density="list"`)
170
+ - Item `px=0` 讓 content 直接觸 hover bg 邊(content-inside-bg breathing 違反)
171
+ - list outer 重複 `py-4` + item 各自 `py-2`(過鬆)
172
+ - 不對稱 padding 無 rationale
173
+ ```
174
+
175
+ ### SurfaceFooter
176
+ - `border-t border-divider`
177
+ - `px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]`
178
+ - `flex items-center justify-end gap-2 shrink-0`(右對齊按鈕列,不被壓縮)
179
+
180
+ ---
181
+
182
+ ## Overlay title typography canonical(modal vs non-modal 分級,2026-04-22)
183
+
184
+ Overlay family 的 header title typography 依「modal vs non-modal」分級,**跟 chrome padding / dismiss behavior 同 rationale(modal 重量級 vs non-modal 輕量)**:
185
+
186
+ | 家族 | Title typography | 套用元件 |
187
+ |------|------------------|---------|
188
+ | **Modal**(阻斷互動)| `text-body-lg font-medium truncate`(**16px**)| `Dialog` / `Sheet` |
189
+ | **Non-modal**(可忽略)| `text-body font-medium truncate`(**14px**)| `Popover` / `Coachmark` / `HoverCard` / `Tooltip` |
190
+
191
+ **Rationale**:
192
+ - **Modal**:title 是決策 anchor,user 必須 process → 需視覺重量(16px + 重字重)
193
+ - **Non-modal**:title 是輔助標籤,user 可忽略 → 輕量字級(14px)不搶 body content
194
+
195
+ **世界級對照**(7 家 DS 同 split):
196
+ | DS | Modal title | Non-modal popover title |
197
+ |----|-------------|------------------------|
198
+ | Material M3 | Headline 24 / body 16 | Smaller 14-16 |
199
+ | Polaris | Modal 大 | Popover 較小 |
200
+ | Atlassian | Modal 大 | InlineDialog 小 |
201
+ | Notion | 16+ | 14 |
202
+ | Linear | 16 | 14 |
203
+ | Figma | 16 | 13-14 |
204
+ | GitHub Primer | 16 | 14 |
205
+
206
+ **跟「density 鎖 md」同源**:Popover / Coachmark 把「輕量」貫穿 chrome:density 鎖 md + chrome button 透過 v5 unbounded trick 縮 layout + **title 小一級**。三件一起構成「輕量浮層」視覺語言,跟 Modal heavy 形成明確分化。
207
+
208
+ **禁止**:consumer 自刻 `<h2 className="text-body-lg">` 繞 `PopoverTitle` — 若需要大 title 代表該用 Dialog 而非 Popover(選錯元件)。
209
+
210
+ **SSOT**:
211
+ - `components/Popover/popover.spec.md`「Title typography canonical(non-modal 特化)」(Popover 專用細節)
212
+ - `components/Dialog/dialog.spec.md`「Title」(Modal 專用細節)
213
+
214
+ ---
215
+
216
+ ## 為什麼 SurfaceHeader 是 padding-based(而非 fixed-h)(2026-04-22 設計原則)
217
+
218
+ **SurfaceHeader 永遠 padding-based**(`py-tight`,**無** min-h)。Header 高度 = `max(title line-height, button slot)+ 2×py`,**slot 設計為 ≤ title line-height** 讓 title 主導 → header 高度 = title 決定。**Slot 透過 `--chrome-slot-h` CSS var 參數化**(default `var(--field-height-xs)` = 24 = body-lg 24,Dialog/Sheet 自然 48 ✓;Popover override `1.25rem` = 20 ≤ body 21,自然 ~45 輕一級)。Q10 修法不該動 header min-h 而動 button 佔位。SurfaceFooter 同 trick(若內含 unbounded)。**語義宣告**:
219
+
220
+ **Padding-based 宣告**:「本 chrome **可以成長** — title 可換行、可附 subtitle / description」。適用 overlay family(Dialog / Sheet / Popover / Coachmark),這些都是 modal/semi-modal 情境,title 有可能長(例如「確認要永久刪除 Marketing Q4 Campaign 這個專案嗎?」兩行 title),或有 subtitle(「這個操作無法復原」補充說明)。
221
+
222
+ **Fixed-height 宣告**:「本 chrome **永遠單行固定結構** — 不會長高」。適用 chrome 類如 Sidebar / FileViewer toolbar / app top bar,這些 chrome 的內容是 logo / icons / 短固定 label,不會 grow。
223
+
224
+ **兩者視覺上在單行 content 時都是 48/56,但是不同的設計宣告**:
225
+ - 若 Dialog 用 fixed-h,未來塞兩行 title / subtitle 會被剪掉 → 違反 modal 作為「完整決策 context」的職責
226
+ - 若 Sidebar 用 padding-based,chrome 可能在長 label 時變動 → 違反 sidebar chrome「剛性佈局」的職責
227
+
228
+ **判斷 tree**:問「這個 chrome 的 title 有可能多行 / 有 subtitle / 有 description 嗎?」
229
+ - **會** → padding-based(SurfaceHeader 或自刻 py-tight)
230
+ - **不會** → fixed-h(`h-[var(--chrome-header-height)]`)
231
+
232
+ **完整 canonical + 世界級對照**:`tokens/uiSize/uiSize.spec.md`「消費 --chrome-header-height 的 2 種實作 pattern」節
233
+
234
+ ---
235
+
236
+ ## Size canonical(per-overlay default size SSOT,2026-05-04)
237
+
238
+ > **背景**:Popover / HoverCard 是「輕量浮層」(chrome 45 / slot 20 / title 小一級 / density 鎖 md);Dialog / Sheet 是「heavy commitment chrome」(chrome 48 / slot 24)。Per-surface body+footer button/field default size 跟「輕量 vs heavy」分化對應。
239
+ >
240
+ > **依據**:Linear / Notion / Airtable / Carbon / Material Menu / Atlassian Popup popover bodies 共識用 sm-density;Material AlertDialog / Carbon Modal / shadcn Dialog 共識用 md。world-class 雙派分明依「浮層密度」分。
241
+
242
+ | Overlay | Header chrome | Body field controls | Body inline action(drag handle / trash / +CTA)| Footer action Buttons |
243
+ |--|--|--|--|--|
244
+ | **Popover** | unbounded sm → 20 slot(輕)| **sm**(輕量 dense info)| inline-action sm/md = **16+18** | **sm** |
245
+ | **HoverCard** | (同 Popover)| **sm** | 同 | **sm** |
246
+ | **Dialog** | unbounded sm → 24 slot | **md** | inline-action sm/md = 16+18 | **md** |
247
+ | **Sheet** | (同 Dialog)| **md** | 同 | **md** |
248
+ | **BulkActionBar** | N/A | N/A | N/A | **md**(default footer placement);未來 top-toolbar variant → sm |
249
+
250
+ **核心 invariant**:overlay surface 內 **body field controls + footer Buttons 同一 size**(per-surface 一致)。違反 = 視覺重量割裂。
251
+
252
+ **為什麼 Popover all-sm 而非 mix**:Popover 整體已是「輕量浮層」設計語言(chrome 短、title 小、density 鎖 md)。如果 body 用 md fields(32 高)+ footer sm Buttons(28 高),視覺重量割裂破壞「輕量」一致性。**all-sm 是內部一致性**(body 28 / footer 28 同高,延伸 chrome 輕一級的密度)。
253
+
254
+ **為什麼 Dialog all-md 而非 mix**:Dialog 是 heavy commitment chrome,body 操作往往 form-heavy,md (32) 是 Field family default(`--field-height-md` SSOT),footer Buttons 同 md commit 視覺重量配得上 modal 結論性。
255
+
256
+ **Drag handle / inline-action sizing**(對齊 `patterns/element-anatomy/inline-action.spec.md`):
257
+ - Field sm/md 同 row → inline action **16 icon / 18 hover bg**
258
+ - Field lg 同 row → inline action **20 icon / 22 hover bg**
259
+
260
+ 亦即:在 Popover(field=sm)或 Dialog(field=md)body 內,inline action 都同樣 16+18,差別只在 lg-density 場景。
261
+
262
+ ---
263
+
264
+ ## Chrome dismiss size canonical
265
+
266
+ **User 設計 insight**:header 的 padding-based sizing 在 **unbounded button**(text variant / dismiss,無 bg/border)場景視覺 padding 過大;在 **bounded button** 則剛好。解法 = **保持 button native size 不變(touch target / 視覺 render 都是 sm 原尺寸),但 layout 佔位縮回 xs(24)** via 負 margin。
267
+
268
+ **Canonical**:button native size **保留 sm**(touch target / 視覺 render 不動);**unbounded 的靠 CSS 負 my 把 layout 佔位縮到 `--chrome-slot-h`** ≤ title line-height,讓 title 主導 chrome 高度。
269
+
270
+ | Button 類型 | 判定(button.tsx L362)| Trick 套用 | Layout 佔位 | Dialog/Sheet header (slot 24)| Popover header (slot 20)|
271
+ |--|--|--|--|--|--|
272
+ | **Unbounded** | `variant === 'text'` OR `dismiss` → `data-unbounded="true"` | ✓ 套負 my | = `var(--chrome-slot-h)` | max(24, 24) + py-tight = **48** | max(21, 20) + py-tight = **45** |
273
+ | **Bounded** | `primary` / `tertiary` / `outline` 等(有 bg/border) | ✗ 不套 | = native sm (28 md / 32 lg) | max(24, 28) + py-tight = **52**(自然長)| max(21, 28) + py-tight = **52**(自然長)|
274
+
275
+ **為什麼差別**:Unbounded 沒視覺邊界 → native 28 是純 hit-target padding,縮 layout 不損視覺;Bounded 的 bg/border 就是內容,縮會切掉 → 必須讓 chrome 自然長高,**這是設計宣告「此 chrome 有重要 action,視覺重量該配得上」**。
276
+
277
+ **實作**:
278
+
279
+ ```tsx
280
+ // button.tsx ── 自動標記 data-unbounded
281
+ const unboundedAttr = resolvedVariant === 'text' || dismiss ? { 'data-unbounded': 'true' } : {}
282
+
283
+ // overlay-surface.tsx ── slot 透過 CSS var 參數化(2026-05-04 v3 Q10)
284
+ const CHROME_UNBOUNDED_SLOT =
285
+ '[&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,var(--field-height-xs))-var(--field-height-sm))/2)]'
286
+
287
+ // SurfaceHeader default → slot = field-height-xs (24)
288
+ // PopoverHeader override → className "[--chrome-slot-h:1.25rem]" (20)
289
+ // 公式 = (slot - native) / 2 ── md unbounded sm: (24-28)/2 = -2px / Popover (20-28)/2 = -4px
290
+ ```
291
+
292
+ **覆蓋範圍**:
293
+ - Dismiss X(`<Button dismiss />`)→ data-unbounded ✓
294
+ - Text variant action(`<Button variant="text" />` 如 Share / Refresh / Settings)→ data-unbounded ✓
295
+ - 所有無視覺邊界的 button,不限 dismiss
296
+
297
+ **為什麼用負 margin 而非 fixed wrapper / size="xs"**:
298
+ - `size="xs"` 會縮小 button 本身,**touch target 也變 24**(違反 a11y 最小 24+ hit target,也違反 user 意圖「touch 仍 sm」)
299
+ - `min-h-chrome-header-height` fixed wrapper 會鎖死高度,**bounded button 失去自然長高能力**(違反 user 意圖)
300
+ - 負 margin:button render / touch target 不變,僅影響 parent flex layout 計算 → 剛好 user 想要的「layout 24,視覺 / 觸控 28」
301
+
302
+ **Consumer 使用方式**:
303
+
304
+ ```tsx
305
+ // Dialog / Sheet / Popover / Coachmark(透過 SurfaceHeader)
306
+ <Button data-dismiss iconOnly dismiss size="sm" startIcon={X} aria-label="關閉" />
307
+ // SurfaceHeader 自動套負 my,無需 consumer 手動 y 調整
308
+
309
+ // Header 若塞 bounded button(primary sm)→ 該 button 無 `data-dismiss`,不套負 my → 自然長高
310
+ <Button variant="primary" size="sm">套用</Button>
311
+
312
+ // Notification banner(Notice / Alert / Toast):px-4 py-3 fixed,dismiss 用 xs 簡化(無 margin trick)
313
+ <Button iconOnly dismiss size="xs" startIcon={X} aria-label="關閉通知" />
314
+ ```
315
+
316
+ **Consumer 實際高度範例**:
317
+ - Dialog header 只有 title + close X(sm + `data-dismiss`)→ layout 佔位 24 → header = 48 md / 56 lg ✓
318
+ - Dialog header 有 refresh/share/close 全 data-dismiss sm → 全部 layout 佔位 24 → header 仍 48/56
319
+ - Dialog header 塞 primary sm(無 data-dismiss)→ primary layout 佔 28 → header = 52 md(自然長)
320
+ - Popover header 同 pattern:48 md / 56 lg
321
+
322
+ **Rationale**:Unbounded 無 bg/border → 2×py-tight 過大,縮 layout 佔位至 xs(24)讓 header = chrome-header-height = 48 自然閉合,跟 Sidebar / page header / top bar 對齊。Bounded 自帶視覺重量,自然長到 52+,跟 footer 一致。
323
+
324
+ ---
325
+
326
+ ## Viewport-aware scroll chain invariant(2026-05-04 K11 升 SSOT)
327
+
328
+ > **背景**:Popover / HoverCard / Dialog / Sheet content 設 `max-h-[var(--radix-*-available-height)] flex flex-col overflow-hidden`,讓 viewport 太小時 header/footer 永遠 in-viewport,body 壓縮 scroll。但**中間任何 wrapper div 沒 forward `flex flex-col h-full` 就斷鏈**,SurfaceBody flex-1 失效,body 不會 scroll。
329
+ >
330
+ > **真實 bug(2026-05-04)**:Filter / Sort panel 內 wrapper div 設 `w-[640px]` 無 flex-col → user 縮視窗時 body 不 scroll,內容被 clip。NameCard 之所以 work 因為它直接是 PopoverContent 唯一 child(無 wrapper)+ 自設 max-h flex-col。
331
+
332
+ **Invariant**:從 `*Content`(浮層 root)到 `SurfaceBody` 之間的**所有中間 wrapper 都必 `flex flex-col h-full min-h-0`**(K11 v2,2026-05-04)。`min-h-0` 必須 — flex item default `min-height: auto` 會讓 content 撐高度,`h-full` 失效;加 `min-h-0` 才能正確 shrink 到 PopoverContent max-h cap。
333
+
334
+ ```tsx
335
+ <div ref={ref} className="flex flex-col h-full min-h-0 w-[640px]"> // ✓
336
+ <SurfaceHeader />
337
+ <SurfaceBody />
338
+ </div>
339
+ ```
340
+
341
+ **禁止**:`w-[640px]` 單獨用(❌ 斷鏈)/ `flex flex-col h-full` 無 `min-h-0`(❌ flex item 不 shrink,scroll 失效)。
342
+
343
+ **DS-wide consumer 必檢點**:`grep '<PopoverContent\|<HoverCardContent\|<DialogContent\|<SheetContent'` 內第一層 wrapper 是否含 `flex flex-col h-full`(若該 panel 用 SurfaceBody)。Hook `check_overlay_panel_scroll_chain.sh` 機械化攔截。
344
+
345
+ **共通 rationale**(全 overlay + banner 家族):corner close X 屬 **action group region**,必用 `<Button>` primitive(不自刻 `<button><X /></button>` 繞 DS token / a11y,不用 `ItemInlineActionButton`)。
346
+
347
+ **本 session 震盪歷史備忘(M12 FP 記憶)**:
348
+ - ❌ v1「chrome dismiss 全 xs(DS-wide 統一)」→ 錯:過度簡化 rationale
349
+ - ❌ v2「三家族 modal sm / non-modal xs / banner xs」→ 錯:overlay 內部不必分化
350
+ - ❌ v3「overlay 統一 sm + min-h chrome-header-height 強鎖 48/56」→ 錯:強鎖會讓 bounded button 被鎖死 slot
351
+ - ❌ v4「padding-based + unbounded=xs / bounded=natural」→ 錯:xs 縮小 button 連 touch target 也變 24(違反 a11y / user 意圖)
352
+ - ✅ v5「padding-based + unbounded `data-dismiss` 套負 my(native size sm 不變)/ bounded natural」→ 對:button native size 與 touch target 保 sm,僅 layout 佔位縮回 24,48/56 chrome-header-height 自然達成
353
+
354
+ **SSOT 關聯**:
355
+ - `tokens/uiSize/uiSize.spec.md`「--chrome-header-height」+ `globals.css` 聲明(md=3rem / lg=3.5rem)
356
+ - `tokens/layoutSpace/layoutSpace.spec.md` tight = 12 md / 16 lg
357
+ - `patterns/element-anatomy/inline-action.spec.md`「Dismiss canonical — X close only」
358
+ - `components/Button/button.spec.md`「Dismiss 視覺類」+ unbounded / bounded 判斷
359
+
360
+ ---
361
+
362
+ ## Control + List 視覺對稱原則 → SSOT 規則 3 補充
363
+
364
+ → `tokens/layoutSpace/layoutSpace.spec.md`「規則 3:元素間 gap」+ `## Notes` 節「List-as-region in overlay body」(本原則本質是 inline → block 對稱在 list 場景的特殊化,SSOT 移上游避免重複 — Rule-of-3)。
365
+
366
+ ---
367
+
368
+ ## Consumer rule:必消費 primitive 不自刻 chrome(2026-04-29)
369
+
370
+ 寫 Popover / Dialog / Sheet 內容必消費 `SurfaceHeader/Body/Footer`(或上層 `PopoverHeader/...`),**禁自刻 `<div className="px-loose ... border-(b|t)">` 取代**。
371
+
372
+ **Why**:primitive 自帶 padding token + PopoverHeader auto close X(line 72)+ PopoverTitle typography + `data-popover-body` autofocus 標記;自刻 = padding/border/close X/title 大小 4 向 drift 起點(對齊 mindset #2)。
373
+
374
+ **Hook**:`.claude/hooks/check_overlay_handcraft.sh` 攔此 pattern;escape hatch `// overlay-handcraft-allow: <reason>` 同/前行。
375
+
376
+ ---
377
+
378
+ ## 不屬本 primitive 的職責
379
+
380
+ - **Close 按鈕渲染**:由 consumer(Dialog / Sheet / Popover)自己包 `<Button iconOnly dismiss>` 在 Header 內,綁各自 Radix Close primitive。SurfaceHeader 本身不渲染 close,避免 pattern 與 consumer 的職責耦合。
381
+ - **viewport-fill 高度邏輯**:Dialog 特有(填滿 viewport - inset),由 DialogContent 自行計算 `height: calc(100vh - inset*2)`,與 Body 協作 `flex-1 overflow-y-auto`。
382
+ - **radius / border / shadow / bg**:浮層外殼職責,由 Dialog / Popover 的 Content 自己套(都套同一組 token:`bg-surface-raised` / `border-border` / `rounded-lg` / `shadow-[var(--elevation-200)]`——這部分 CLAUDE.md 已經寫明對齊規則,不另外抽 primitive)。
383
+
384
+ ---
385
+
386
+ ## A11y 預設
387
+
388
+ overlay-surface 是 **layout pattern**(`SurfaceHeader` / `SurfaceBody` / `SurfaceFooter`),不持有互動行為 — a11y 大宗在 consumer overlay primitive(Dialog / Sheet / Popover / HoverCard)上,由 Radix 處理:
389
+
390
+ - **Role + ARIA**:Radix `Dialog.Content` / `Popover.Content` / `Sheet.Content` 已自帶 `role="dialog"` + `aria-modal`(modal only)+ `aria-labelledby`(Title)+ `aria-describedby`(optional Description)
391
+ - **Focus trap**:Dialog / Sheet 自帶 modal focus trap;Popover / HoverCard 不 trap(non-modal canonical)
392
+ - **Esc / 點外面關閉**:Radix 處理(可被 `onEscapeKeyDown` / `onPointerDownOutside` 攔截)
393
+ - **AutoFocus on open**:consumer 自管 `onOpenAutoFocus`(Popover 範例:`handlePopoverOpenAutoFocus` 找 body 第一個 interactive 元素,跳過 close X 避免 tooltip leak)
394
+
395
+ **SurfaceHeader Title 可被 ARIA 關聯**:consumer 把 `id` 傳到 SurfaceHeader 的 Title 元素,Radix Content 用 `aria-labelledby={id}`。本 pattern 不強制 id naming(consumer 自決)。
396
+
397
+ ---
398
+
399
+ ## 何時不用
400
+
401
+ - **Toast / Alert**(Family 2 List item 視覺對齊):那是 row-item layout 不是 surface-section,不要套本 pattern。
402
+ - **Tooltip**(純文字短提示):無結構化需求,不包 Header/Body/Footer。
403
+ - **HoverCard**(自由組合互動浮層):目前 consumer 自行組合內容,視未來是否引入 Header/Body/Footer 需求再納入 consumer。
404
+
405
+ ---
406
+
407
+ ## 相關
408
+
409
+ - `../../components/Dialog/dialog.spec.md` — modal 浮層 consumer
410
+ - `../../components/Popover/popover.spec.md` — non-modal 浮層 consumer
411
+ - `../../tokens/layoutSpace/layoutSpace.spec.md` — padding token 來源(`--layout-space-loose` / `--layout-space-tight` / `--layout-space-bottom`)
412
+
413
+ ## 被引用(auto-maintained,Dim 3 reciprocal audit)
414
+
415
+ > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
416
+
417
+ - `coachmark.spec.md`
418
+ - `element-anatomy.spec.md`
419
+ - `notice.spec.md`
420
+ - `sheet.spec.md`
421
+
422
+ ## 被引用(auto-maintained,Dim 3 reciprocal audit)
423
+
424
+ > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
425
+
426
+ - `app-shell.spec.md`
427
+ - `header-canonical.spec.md`
428
+ - `motion.spec.md`
@@ -0,0 +1,109 @@
1
+ ---
2
+ component: ResizeHandle
3
+ family: null # primitive pattern, not row-layout family
4
+ traits:
5
+ - isStructural
6
+ benchmark:
7
+ - AG Grid column resize handle: github.com/ag-grid/ag-grid/tree/latest/community-modules/core/src/headerRendering
8
+ - Material X-DataGrid MuiDataGrid-iconSeparator: github.com/mui/mui-x/tree/master/packages/grid/x-data-grid
9
+ - Notion column / sidebar resize: notion.so app inspect
10
+ - VS Code activity bar / sidebar resize: github.com/microsoft/vscode/tree/main/src/vs/workbench/browser/parts
11
+ - Figma left panel resize: figma.com app inspect
12
+ ---
13
+
14
+ # ResizeHandle 設計原則
15
+
16
+ ## 定位
17
+
18
+ **Drag-to-resize 視覺 + a11y 共同 primitive**——統一 column resize / sidebar drag-resize / row resize / panel resize 的命中區、cursor、視覺 line、a11y attributes。
19
+
20
+ **消費者(計畫)**:
21
+ - DataTable column resize(目前自畫 — Phase 2 migrate)
22
+ - Sidebar drag-resize(目前無 — Phase 3 enable)
23
+ - AppShell Aside drag-resize(目前無 — Phase 4 enable)
24
+
25
+ **不耦合 drag math** — consumer 自管 width state(TanStack `header.getResizeHandler()` / 手刻 pointer math / `useResizeObserver` 各種路徑)。本 primitive only ship 視覺 + cursor + a11y。
26
+
27
+ **Layout Family**:N/A(self-contained primitive,非 row-layout family member)。
28
+
29
+ ## 為什麼需要 SSOT
30
+
31
+ `DataTable column resize`(已 ship 2026-05-06)+ 未來 sidebar / aside drag-resize 若各自手刻命中區尺寸、cursor、line 視覺,跨元件視覺漂移 = 用戶感受不一致。對齊 mindset #2「優先消費既有」+ M17「同概念出現 3 處 = 必抽 primitive」+ user 2026-05-21 directive「style 難道不用跟 data table column resize 維持 ssot」。
32
+
33
+ ## API
34
+
35
+ ```tsx
36
+ <ResizeHandle
37
+ direction="horizontal" | "vertical"
38
+ position="end" // | "start"
39
+ isResizing?: boolean // consumer 自管 drag state
40
+ disabled?: boolean // consumer 自決 resizable opt-in
41
+ aria-label="拖曳調整欄寬" // 必傳
42
+ showLine?: boolean // default true,false = consumer paint own line
43
+ lineInsetStart?: string // line start inset(eg. var(--table-cell-py))
44
+ lineInsetEnd?: string // line end inset
45
+ onPointerDownCapture?: (e: React.PointerEvent) => void // consumer 接 drag math
46
+ />
47
+ ```
48
+
49
+ **direction**:
50
+ - `horizontal`(拖左右)→ `cursor: col-resize` + `aria-orientation="vertical"`(separator 軸垂直於 drag)
51
+ - `vertical`(拖上下)→ `cursor: row-resize` + `aria-orientation="horizontal"`
52
+
53
+ **position**:`end` 命中區在右(horizontal)/ 底(vertical)— column right edge / sidebar right edge 典型;`start` 左 / 上 — 罕用。
54
+
55
+ ## 視覺 canonical(對齊 DataTable v11)
56
+
57
+ - **命中區**:7px 寬(horizontal)/ 高(vertical),`-3px` outward offset 跨 boundary 抓得到
58
+ - **Visual line**:1px,positioned `right-[3px]` / `bottom-[3px]`,default full-extent
59
+ - **idle**:`bg-divider`
60
+ - **disabled**:`bg-divider`(無 hover affordance)
61
+ - **hover**:`bg-[var(--border-hover)]`(via `group/resize` selector)
62
+ - **dragging**:`bg-primary`(consumer 傳 `isResizing=true`)
63
+ - **Cursor**:`col-resize` / `row-resize`(`disabled` 時無)
64
+ - **`select-none`**:防 drag 時 text select(`disabled` 時關閉)
65
+
66
+ ## a11y
67
+
68
+ - **`role="separator"`** + **`aria-orientation`** + **`aria-label`**:對齊 WAI-ARIA「separator widget」pattern(MDN: separator role)
69
+ - 鍵盤導覽 future Phase: Arrow keys 微調 + Enter 顯式 commit(consumer Phase 2+ 補)
70
+ - `disabled` 時不掛 role / orientation / label(語意正確 — disabled separator 不該為 a11y tree 入口)
71
+
72
+ ## 何時用 / 何時不用
73
+
74
+ ✅ **用**:水平 column / vertical row / sidebar / panel 拖拉調整尺寸
75
+
76
+ ❌ **不用**:
77
+ - Modal / Dialog 大小(那是 `<Sheet>` 自帶 handle,不該重發明)
78
+ - Image crop 邊界(專用 `<ImageCropTool>` 領域)
79
+ - Splitter pane (`resizable-panes` style)— 雙端 drag with linked state(future 評估抽 `<SplitPane>` 上層 primitive 消費本 handle)
80
+
81
+ ## Roadmap(用 user 既有的 v2 framing)
82
+
83
+ | Phase | Scope | Status |
84
+ |---|---|---|
85
+ | **Phase 1** | Ship primitive `<ResizeHandle>` + spec.md | ✅ 2026-05-21 |
86
+ | Phase 2 | DataTable column resize migrate consume primitive(TanStack `header.getResizeHandler()` 接) | Pending |
87
+ | Phase 3 | Sidebar drag-resize enable(consume primitive + localStorage 持久化) | Pending |
88
+ | Phase 4 | AppShell Aside drag-resize enable(consume primitive) | Pending |
89
+
90
+ Phase 2-4 需獨立 RFC + 各別 user approval,本 spec 只 ship Phase 1 + 鎖住視覺 canonical。
91
+
92
+ ## 世界級對照細節
93
+
94
+ | DS / Library | 命中區 | Line | Cursor | a11y |
95
+ |---|---|---|---|---|
96
+ | **AG Grid** | 7-8px | 1px primary on drag | col-resize | role="separator" |
97
+ | **Material X-DataGrid** | `MuiDataGrid-iconSeparator` 8px | 1px hairline | col-resize | aria-label "Resize column" |
98
+ | **Notion(column / sidebar)** | ~6-8px | 1px line | col-resize | (DOM-only,無 role)|
99
+ | **VS Code** | 8px(activity bar)| bg highlight on drag | col-resize | aria-label "Resize" |
100
+ | **Figma** | 8px | 1px line | col-resize | role separator |
101
+
102
+ 共識:7-8px hit zone / 1px line / cursor 對應 direction / role="separator" + aria-label。本 primitive 7px hit zone + 全對齊。
103
+
104
+ ## 禁止事項
105
+
106
+ - ❌ 自畫 resize handle 視覺(`<div className="cursor-col-resize">`)— 必消費本 primitive
107
+ - ❌ 直接給 ResizeHandle 加 drag state hook(eg. `useColumnResize`)— drag math 是 consumer concern,不污染 primitive
108
+ - ❌ Phase 2/3/4 不走 RFC + user approval 自動 migrate — Audit-vs-execute 分權違反
109
+ - ❌ 重新發明 a11y attributes — `role="separator"` + `aria-orientation` + `aria-label` SSOT 不可漂移