agentcss 0.0.0-stage → 0.11.3

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 (274) hide show
  1. package/01-tokens/README.md +236 -0
  2. package/01-tokens/behavior/dismissable.js +132 -0
  3. package/01-tokens/behavior/emit.js +76 -0
  4. package/01-tokens/behavior/focus-return.js +81 -0
  5. package/01-tokens/behavior/roving.js +152 -0
  6. package/01-tokens/behavior/typeahead.js +124 -0
  7. package/01-tokens/focus-ring-demo.html +133 -0
  8. package/01-tokens/focus-ring.css +102 -0
  9. package/01-tokens/page.css +244 -0
  10. package/01-tokens/polyfill.js +116 -0
  11. package/01-tokens/responsive-demo.html +171 -0
  12. package/01-tokens/scrollbar-demo.html +194 -0
  13. package/01-tokens/scrollbar.css +179 -0
  14. package/01-tokens/theme-toggle.js +103 -0
  15. package/01-tokens/tokens.css +972 -0
  16. package/01-tokens/typography.css +156 -0
  17. package/02-primitives/badge/README.md +71 -0
  18. package/02-primitives/badge/badge.css +141 -0
  19. package/02-primitives/badge/demo.html +101 -0
  20. package/02-primitives/button/README.md +99 -0
  21. package/02-primitives/button/button.css +353 -0
  22. package/02-primitives/button/demo.html +200 -0
  23. package/02-primitives/card/README.md +145 -0
  24. package/02-primitives/card/card.css +246 -0
  25. package/02-primitives/card/demo.html +170 -0
  26. package/02-primitives/choice/README.md +161 -0
  27. package/02-primitives/choice/choice.css +395 -0
  28. package/02-primitives/choice/demo.html +277 -0
  29. package/02-primitives/combobox/README.md +79 -0
  30. package/02-primitives/combobox/combobox.css +223 -0
  31. package/02-primitives/combobox/combobox.js +485 -0
  32. package/02-primitives/combobox/demo.html +109 -0
  33. package/02-primitives/date-range/README.md +100 -0
  34. package/02-primitives/date-range/date-range.css +206 -0
  35. package/02-primitives/date-range/date-range.js +301 -0
  36. package/02-primitives/date-range/demo.html +104 -0
  37. package/02-primitives/input/README.md +170 -0
  38. package/02-primitives/input/demo.html +246 -0
  39. package/02-primitives/input/input.css +239 -0
  40. package/02-primitives/popover/README.md +80 -0
  41. package/02-primitives/popover/demo.html +150 -0
  42. package/02-primitives/popover/popover.css +187 -0
  43. package/02-primitives/popover/popover.js +331 -0
  44. package/02-primitives/progress/README.md +82 -0
  45. package/02-primitives/progress/demo.html +154 -0
  46. package/02-primitives/progress/progress.css +172 -0
  47. package/02-primitives/select/README.md +95 -0
  48. package/02-primitives/select/demo.html +121 -0
  49. package/02-primitives/select/select.css +151 -0
  50. package/02-primitives/select/select.js +549 -0
  51. package/02-primitives/separator/README.md +63 -0
  52. package/02-primitives/separator/demo.html +84 -0
  53. package/02-primitives/separator/separator.css +73 -0
  54. package/02-primitives/skeleton/README.md +69 -0
  55. package/02-primitives/skeleton/demo.html +110 -0
  56. package/02-primitives/skeleton/skeleton.css +96 -0
  57. package/02-primitives/switch/README.md +65 -0
  58. package/02-primitives/switch/demo.html +115 -0
  59. package/02-primitives/switch/switch.css +370 -0
  60. package/03-patterns/accordion/README.md +121 -0
  61. package/03-patterns/accordion/accordion.css +453 -0
  62. package/03-patterns/accordion/accordion.js +369 -0
  63. package/03-patterns/accordion/demo.html +223 -0
  64. package/03-patterns/content/README.md +103 -0
  65. package/03-patterns/content/content.css +286 -0
  66. package/03-patterns/content/demo.html +88 -0
  67. package/03-patterns/drawer/README.md +79 -0
  68. package/03-patterns/drawer/demo.html +101 -0
  69. package/03-patterns/drawer/drawer.css +129 -0
  70. package/03-patterns/drawer/drawer.js +258 -0
  71. package/03-patterns/dropdown/README.md +78 -0
  72. package/03-patterns/dropdown/demo.html +101 -0
  73. package/03-patterns/dropdown/dropdown.css +135 -0
  74. package/03-patterns/dropdown/dropdown.js +451 -0
  75. package/03-patterns/form-validation/README.md +147 -0
  76. package/03-patterns/form-validation/demo.html +280 -0
  77. package/03-patterns/form-validation/form-validation.css +263 -0
  78. package/03-patterns/list/README.md +150 -0
  79. package/03-patterns/list/demo.html +225 -0
  80. package/03-patterns/list/flip.js +201 -0
  81. package/03-patterns/list/list.css +162 -0
  82. package/03-patterns/nav/README.md +225 -0
  83. package/03-patterns/nav/demo.html +131 -0
  84. package/03-patterns/nav/nav.css +314 -0
  85. package/03-patterns/nav/nav.js +430 -0
  86. package/03-patterns/nav/toc.js +111 -0
  87. package/03-patterns/overlay/README.md +196 -0
  88. package/03-patterns/overlay/demo.html +241 -0
  89. package/03-patterns/overlay/overlay.css +319 -0
  90. package/03-patterns/overlay/overlay.js +563 -0
  91. package/03-patterns/pagination/README.md +114 -0
  92. package/03-patterns/pagination/demo.html +165 -0
  93. package/03-patterns/pagination/pagination.css +195 -0
  94. package/03-patterns/pagination/pagination.js +395 -0
  95. package/03-patterns/reveal/README.md +91 -0
  96. package/03-patterns/reveal/demo.html +106 -0
  97. package/03-patterns/reveal/reveal.css +93 -0
  98. package/03-patterns/reveal/reveal.js +172 -0
  99. package/03-patterns/states/README.md +141 -0
  100. package/03-patterns/states/demo.html +240 -0
  101. package/03-patterns/states/states.css +252 -0
  102. package/03-patterns/tabs/README.md +84 -0
  103. package/03-patterns/tabs/demo.html +139 -0
  104. package/03-patterns/tabs/tabs.css +172 -0
  105. package/03-patterns/tabs/tabs.js +307 -0
  106. package/03-patterns/tooltip/README.md +96 -0
  107. package/03-patterns/tooltip/demo.html +78 -0
  108. package/03-patterns/tooltip/tooltip.css +89 -0
  109. package/03-patterns/tooltip/tooltip.js +288 -0
  110. package/03-patterns/tree/README.md +94 -0
  111. package/03-patterns/tree/demo.html +138 -0
  112. package/03-patterns/tree/tree.css +113 -0
  113. package/03-patterns/tree/tree.js +456 -0
  114. package/04-recipes/analysis-report/README.md +80 -0
  115. package/04-recipes/analysis-report/analysis-report.css +187 -0
  116. package/04-recipes/analysis-report/demo.html +274 -0
  117. package/04-recipes/data-showcase/README.md +45 -0
  118. package/04-recipes/data-showcase/demo.html +247 -0
  119. package/04-recipes/detail-page/README.md +93 -0
  120. package/04-recipes/detail-page/demo.html +55 -0
  121. package/04-recipes/detail-page/detail-page.css +190 -0
  122. package/04-recipes/detail-page/full.html +60 -0
  123. package/04-recipes/detail-page/null.html +60 -0
  124. package/04-recipes/detail-page/partial.html +60 -0
  125. package/04-recipes/detail-page/verify-skeleton.py +19 -0
  126. package/04-recipes/longform/README.md +93 -0
  127. package/04-recipes/longform/agc-memory.svg +4 -0
  128. package/04-recipes/longform/content.html +610 -0
  129. package/04-recipes/longform/content.md +51 -0
  130. package/04-recipes/longform/longform.html +627 -0
  131. package/04-recipes/report/README.md +176 -0
  132. package/04-recipes/report/demo.html +355 -0
  133. package/04-recipes/report/report.css +364 -0
  134. package/04-recipes/table/README.md +103 -0
  135. package/04-recipes/table/demo.html +455 -0
  136. package/04-recipes/table/table.css +372 -0
  137. package/04-recipes/tierA-tokens.css +189 -0
  138. package/09-assets/README.md +74 -0
  139. package/09-assets/bar/README.md +94 -0
  140. package/09-assets/bar/bar.css +166 -0
  141. package/09-assets/bar/bar.js +320 -0
  142. package/09-assets/bar/demo.html +176 -0
  143. package/09-assets/echarts-adapter/demo.html +192 -0
  144. package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
  145. package/09-assets/model-viewer/README.md +155 -0
  146. package/09-assets/model-viewer/demo.html +105 -0
  147. package/09-assets/model-viewer/model-viewer.css +143 -0
  148. package/09-assets/model-viewer/model-viewer.js +423 -0
  149. package/09-assets/scientific-plot/README.md +94 -0
  150. package/09-assets/scientific-plot/demo.html +252 -0
  151. package/09-assets/scientific-plot/scientific-plot.css +141 -0
  152. package/09-assets/scientific-plot/scientific-plot.js +610 -0
  153. package/09-assets/sparkline/README.md +153 -0
  154. package/09-assets/sparkline/demo.html +163 -0
  155. package/09-assets/sparkline/sparkline.css +158 -0
  156. package/09-assets/sparkline/sparkline.js +508 -0
  157. package/API.md +332 -0
  158. package/CHANGELOG.md +3138 -0
  159. package/CODE_OF_CONDUCT.md +64 -0
  160. package/CONTRIBUTING.md +89 -0
  161. package/ERRORS.md +1658 -0
  162. package/LICENSE +21 -0
  163. package/README.md +497 -2
  164. package/SECURITY.md +72 -0
  165. package/START-HERE.md +164 -0
  166. package/SUPPORT.md +109 -0
  167. package/adapters/presentation/README.md +113 -0
  168. package/adapters/presentation/check.js +101 -0
  169. package/adapters/presentation/deck.schema.json +74 -0
  170. package/adapters/presentation/to-doc.js +138 -0
  171. package/ai/START-HERE.md +104 -0
  172. package/ai/behaviors.json +380 -0
  173. package/ai/behaviors.schema.json +61 -0
  174. package/ai/capabilities.json +56 -0
  175. package/ai/cli.js +387 -0
  176. package/ai/components.json +1944 -0
  177. package/ai/components.meta.json +168 -0
  178. package/ai/components.schema.json +220 -0
  179. package/ai/contract.schema.json +147 -0
  180. package/ai/deprecations.json +55 -0
  181. package/ai/deprecations.schema.json +85 -0
  182. package/ai/motion-policy.json +22 -0
  183. package/ai/patch.js +323 -0
  184. package/ai/patch.schema.json +63 -0
  185. package/ai/profiles.json +92 -0
  186. package/ai/tokens.audience.json +55 -0
  187. package/ai/tokens.audience.schema.json +73 -0
  188. package/ai/tokens.json +762 -0
  189. package/ai/tokens.schema.json +58 -0
  190. package/ai/tokens.tree.json +752 -0
  191. package/ai/tokens.tree.schema.json +56 -0
  192. package/ai/validate.js +383 -0
  193. package/dist/01-tokens/behavior/dismissable.js +60 -0
  194. package/dist/01-tokens/behavior/emit.js +21 -0
  195. package/dist/01-tokens/behavior/focus-return.js +31 -0
  196. package/dist/01-tokens/behavior/roving.js +77 -0
  197. package/dist/01-tokens/behavior/typeahead.js +61 -0
  198. package/dist/01-tokens/focus-ring.css +1 -0
  199. package/dist/01-tokens/page.css +1 -0
  200. package/dist/01-tokens/polyfill.js +78 -0
  201. package/dist/01-tokens/scrollbar.css +1 -0
  202. package/dist/01-tokens/theme-toggle.js +61 -0
  203. package/dist/01-tokens/tokens.css +1 -0
  204. package/dist/01-tokens/typography.css +1 -0
  205. package/dist/02-primitives/badge/badge.css +1 -0
  206. package/dist/02-primitives/button/button.css +1 -0
  207. package/dist/02-primitives/card/card.css +1 -0
  208. package/dist/02-primitives/choice/choice.css +1 -0
  209. package/dist/02-primitives/combobox/combobox.css +1 -0
  210. package/dist/02-primitives/combobox/combobox.js +439 -0
  211. package/dist/02-primitives/date-range/date-range.css +1 -0
  212. package/dist/02-primitives/date-range/date-range.js +267 -0
  213. package/dist/02-primitives/input/input.css +1 -0
  214. package/dist/02-primitives/popover/popover.css +1 -0
  215. package/dist/02-primitives/popover/popover.js +257 -0
  216. package/dist/02-primitives/progress/progress.css +1 -0
  217. package/dist/02-primitives/select/select.css +1 -0
  218. package/dist/02-primitives/select/select.js +449 -0
  219. package/dist/02-primitives/separator/separator.css +1 -0
  220. package/dist/02-primitives/skeleton/skeleton.css +1 -0
  221. package/dist/02-primitives/switch/switch.css +1 -0
  222. package/dist/03-patterns/accordion/accordion.css +1 -0
  223. package/dist/03-patterns/accordion/accordion.js +219 -0
  224. package/dist/03-patterns/content/content.css +1 -0
  225. package/dist/03-patterns/drawer/drawer.css +1 -0
  226. package/dist/03-patterns/drawer/drawer.js +229 -0
  227. package/dist/03-patterns/dropdown/dropdown.css +1 -0
  228. package/dist/03-patterns/dropdown/dropdown.js +365 -0
  229. package/dist/03-patterns/form-validation/form-validation.css +1 -0
  230. package/dist/03-patterns/list/flip.js +117 -0
  231. package/dist/03-patterns/list/list.css +1 -0
  232. package/dist/03-patterns/nav/nav.css +1 -0
  233. package/dist/03-patterns/nav/nav.js +322 -0
  234. package/dist/03-patterns/nav/toc.js +76 -0
  235. package/dist/03-patterns/overlay/overlay.css +1 -0
  236. package/dist/03-patterns/overlay/overlay.js +398 -0
  237. package/dist/03-patterns/pagination/pagination.css +1 -0
  238. package/dist/03-patterns/pagination/pagination.js +273 -0
  239. package/dist/03-patterns/reveal/reveal.css +1 -0
  240. package/dist/03-patterns/reveal/reveal.js +138 -0
  241. package/dist/03-patterns/states/states.css +1 -0
  242. package/dist/03-patterns/tabs/tabs.css +1 -0
  243. package/dist/03-patterns/tabs/tabs.js +242 -0
  244. package/dist/03-patterns/tooltip/tooltip.css +1 -0
  245. package/dist/03-patterns/tooltip/tooltip.js +213 -0
  246. package/dist/03-patterns/tree/tree.css +1 -0
  247. package/dist/03-patterns/tree/tree.js +383 -0
  248. package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
  249. package/dist/04-recipes/detail-page/detail-page.css +1 -0
  250. package/dist/04-recipes/report/report.css +1 -0
  251. package/dist/04-recipes/table/table.css +1 -0
  252. package/dist/04-recipes/tierA-tokens.css +1 -0
  253. package/dist/09-assets/bar/bar.css +1 -0
  254. package/dist/09-assets/bar/bar.js +222 -0
  255. package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
  256. package/dist/09-assets/model-viewer/model-viewer.css +1 -0
  257. package/dist/09-assets/model-viewer/model-viewer.js +320 -0
  258. package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
  259. package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
  260. package/dist/09-assets/sparkline/sparkline.css +1 -0
  261. package/dist/09-assets/sparkline/sparkline.js +265 -0
  262. package/dist/COSTS.md +56 -0
  263. package/docs/AI-CONTRACT.md +197 -0
  264. package/docs/BACKLOG.md +127 -0
  265. package/docs/BENCHMARK.md +252 -0
  266. package/docs/GUIDANCE.md +154 -0
  267. package/docs/INVARIANT.md +372 -0
  268. package/docs/LAYER-BASELINE.md +158 -0
  269. package/docs/RELEASING.md +311 -0
  270. package/docs/RFC-THEME-SCOPE.md +141 -0
  271. package/docs/ROADMAP.md +182 -0
  272. package/index.html +82 -0
  273. package/package.json +107 -4
  274. package/types/index.d.ts +310 -0
@@ -0,0 +1,84 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link rel="icon" href="data:,">
7
+ <title>separator · 分隔线</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/page.css">
11
+ <link rel="stylesheet" href="../../02-primitives/separator/separator.css">
12
+ <style>
13
+ .row { padding: var(--sp-2) 0; }
14
+ </style>
15
+ </head>
16
+ <body class="page page">
17
+ <main class="wrap">
18
+ <h1>separator · 分隔线</h1>
19
+ <p class="lede">
20
+ 分隔线有两种,<strong>必须分清</strong>:装饰性的只分开内容块,
21
+ 语义性的分开<strong>不同的东西</strong>。对辅助技术的意义完全不同。
22
+ </p>
23
+
24
+ <h2>装饰性</h2>
25
+ <div class="box">
26
+ <div class="row">基本信息</div>
27
+ <hr class="separator">
28
+ <div class="row">这条线只分开内容块,读屏不需要播报</div>
29
+ </div>
30
+
31
+ <h2>装饰性(紧凑档)</h2>
32
+ <div class="box">
33
+ <div class="row">上一块</div>
34
+ <hr class="separator separator--tight">
35
+ <div class="row">间距更小,用在同组内部</div>
36
+ </div>
37
+
38
+ <h2>语义性(带文字)</h2>
39
+ <div class="box">
40
+ <hr class="separator separator--label" role="separator"
41
+ aria-label="高级设置">
42
+ <div class="row">这条线分开的是<strong>不同性质的设置</strong>,读屏应当播报</div>
43
+ <hr class="separator separator--label separator--both" role="separator"
44
+ aria-label="高级设置">
45
+ <div class="row">两侧都画线</div>
46
+ </div>
47
+
48
+ <h2>两档颜色的实测差别</h2>
49
+ <div class="box">
50
+ <p>本库实测(vs <code>--surface</code>,WCAG 1.4.11 非文字对比要 ≥ 3:1):</p>
51
+ <p>
52
+ <code>--border-decor</code> 浅 <strong>1.26</strong> · 暗 <strong>1.23</strong> → &lt; 3<br>
53
+ <code>--border-control</code> 浅 <strong>3.42</strong> · 暗 <strong>4.34</strong> → ✅ ≥ 3
54
+ </p>
55
+ <p class="small">
56
+ 差 <strong>2.7 倍</strong>。装饰性用 <code>--border-decor</code> ——
57
+ 1.4.11 不适用于它(不承担语义)。语义性用 <code>--border-control</code> ——
58
+ 它是读屏用户<strong>唯一能看到的线索</strong>,视觉上必须够明显。
59
+ </p>
60
+ </div>
61
+
62
+ <h2>反例</h2>
63
+ <div class="box bad">
64
+ <h3>反例:语义分隔用装饰色</h3>
65
+ <p class="small">
66
+ 分隔"基本信息"与"高级设置"——这是<strong>两类不同的东西</strong>,
67
+ 但如果用 <code>--border-decor</code>(1.26:1),
68
+ 低视力用户与暗色显示下几乎<strong>看不见这条线</strong>,
69
+ 而读屏只会念"分隔符"、不会解释两边是什么。
70
+ ⇒ 该线是唯一线索时,必须用达标的档。
71
+ </p>
72
+ </div>
73
+
74
+ <div class="box bad">
75
+ <h3>反例:用 div + border-bottom 代替 hr</h3>
76
+ <p class="small">
77
+ <code>&lt;div class="line"&gt;</code> 对读屏是<strong>什么都没有</strong>。
78
+ 语义分隔必须用 <code>&lt;hr&gt;</code>(或 <code>role="separator"</code>),
79
+ 装饰性的才可以用纯 div。
80
+ </p>
81
+ </div>
82
+ </main>
83
+ </body>
84
+ </html>
@@ -0,0 +1,73 @@
1
+ /* ============================================================================
2
+ Separator — 分隔线
3
+ ============================================================================
4
+ 🔴 两种分隔线,必须分清(APG: Separator Pattern)
5
+ ------------------------------------------------
6
+ 装饰性(.separator) 纯视觉,把内容分成块
7
+ → <hr> 或 <div role="separator">
8
+ → 读屏**不需要**播报
9
+
10
+ 语义性(.separator--label) 分开了**不同的东西**
11
+ (章节与章节、标题与标题)
12
+ → role="separator" + 内部文字
13
+ → 读屏**应该**播报"分隔符"
14
+
15
+ 为什么会混:两者视觉上都是一条线,但**对辅助技术的意义完全不同** ——
16
+ 装饰性的播报是噪音,语义性的不播报则丢信息。
17
+
18
+ 为什么用两档颜色(实测,不是拍脑袋)
19
+ --------------------------------
20
+ 本库实测(vs --surface,WCAG 1.4.11 非文字对比要 ≥ 3:1):
21
+
22
+ --border-decor 浅 1.26 · 暗 1.23 < 3
23
+ --border-decor-str 浅 1.60 · 暗 1.61 < 3
24
+ --border-control 浅 3.42 · 暗 4.34 ✅ ≥ 3
25
+
26
+ ⇒ **装饰性**用 `--border-decor`:它只分开内容块,
27
+ 不承担"这是两个不同的东西"的语义,1.4.11 不适用。
28
+ ⇒ **语义性**用 `--border-control`:它是**唯一的视觉线索**,
29
+ 读屏用户看不到 ⇒ 必须在视觉上也足够明显,否则等于没分。
30
+
31
+ 这就是"不要一档打天下"的实测依据 ——
32
+ 两档的差距是 1.26 vs 3.42,差 2.7 倍。
33
+ ========================================================================= */
34
+
35
+ .separator {
36
+ border: 0;
37
+ border-top: 1px solid var(--border-decor);
38
+ margin: var(--sp-6) 0;
39
+ height: 0; /* hr 默认有高度,显式归零 */
40
+ }
41
+
42
+ .separator--tight {
43
+ margin: var(--sp-4) 0;
44
+ }
45
+
46
+ /* 语义性:带文字的分隔("基本信息" / "高级设置") */
47
+ .separator--label {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: var(--sp-3);
51
+ border: 0;
52
+ margin: var(--sp-6) 0 var(--sp-3);
53
+ font-size: var(--fs-xs);
54
+ font-weight: 600;
55
+ color: var(--text-secondary);
56
+ }
57
+
58
+ /* 🔴 用 border 而不是 box-shadow —— 分隔线是 1px 的线,
59
+ 用 box-shadow 在某些浏览器上会渲染成 2px(亚像素取整)。 */
60
+ .separator--label::before,
61
+ .separator--label::after {
62
+ content: "";
63
+ flex: 1;
64
+ border-top: 1px solid var(--border-control); /* 语义型用可达标的档 */
65
+ }
66
+
67
+ .separator--label::after {
68
+ display: none; /* 默认只在文字前画线;要两侧就用 .--both */
69
+ }
70
+
71
+ .separator--label.separator--both::after {
72
+ display: block;
73
+ }
@@ -0,0 +1,69 @@
1
+ # skeleton · 骨架屏
2
+
3
+ > 加载态是移动端**最高频**的状态。没有它,每个业务方都会自己造一个
4
+ > ⇒ 形状不统一、闪烁频率不一致、暗色下颜色不对。
5
+
6
+ ## 用法
7
+
8
+ ```html
9
+ <link rel="stylesheet" href="skeleton.css">
10
+
11
+ <!-- 文本骨架(多行) -->
12
+ <div aria-busy="true">
13
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:92%"></span>
14
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:78%"></span>
15
+ </div>
16
+
17
+ <!-- 块状(图片/卡片占位) -->
18
+ <div class="skeleton skeleton--block" aria-hidden="true" style="height:96px"></div>
19
+
20
+ <!-- 圆形(头像占位) -->
21
+ <span class="skeleton skeleton--circle" aria-hidden="true"></span>
22
+
23
+ <!-- 列表骨架(最常用形态) -->
24
+ <div class="skeleton-list" aria-busy="true">
25
+ <div class="skeleton-list__row">
26
+ <span class="skeleton skeleton--circle" aria-hidden="true"></span>
27
+ <div style="flex:1">
28
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:40%"></span>
29
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:88%"></span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ ```
34
+
35
+ ## 令牌
36
+
37
+ | 令牌 | 默认 | 说明 |
38
+ |---|---|---|
39
+ | `--skeleton-bg` | `#e9ecef` / 暗色 `#2E343C` | 底色。**与纸面亮度差必须 > 0.02**(契约强制)|
40
+ | `--skeleton-shimmer` | `rgba(255,255,255,.72)` | 微光高光 |
41
+ | `--skeleton-h` | `1em` | 单行高度 |
42
+ | `--skeleton-size` | `40px` | circle 直径 |
43
+ | `--skeleton-duration` | `1.4s` | 微光周期 |
44
+
45
+ ## 无障碍(必读)
46
+
47
+ 骨架屏**没有文字**。若不处理,读屏会念出一堆空白。
48
+
49
+ ```html
50
+ <!-- ✅ 正确:外层标 aria-busy(读屏会念"忙"),骨架逐个 aria-hidden -->
51
+ <div aria-busy="true">
52
+ <span class="skeleton" aria-hidden="true"></span>
53
+ </div>
54
+ ```
55
+
56
+ ⚠️ `prefers-reduced-motion: reduce` 时**动画停掉,但骨架屏仍显示** ——
57
+ 用户依然知道正在加载。(WCAG 2.3.3)
58
+
59
+ ## 何时**不该**用
60
+
61
+ - ❌ **很短的文字**(< 12px):骨架屏会读成"渲染坏了",读屏还会念空白
62
+ - ❌ **只要 200ms 就完成**的加载:闪一下更伤体验,直接不显示
63
+ - ❌ **已知时长的操作**(如上传 3MB):用 `progress` 而不是骨架屏
64
+ - ❌ **骨架屏里放 spinner**:两者都是"不确定进度"的信号,同时出现是噪声
65
+
66
+ ## 相关
67
+
68
+ - 已知进度 ⇒ `progress`(已提供)
69
+ - 短反馈 ⇒ `toast`(**待补**)
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link rel="icon" href="data:,">
7
+ <title>骨架屏 · skeleton</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/page.css">
11
+ <link rel="stylesheet" href="../button/button.css">
12
+ <link rel="stylesheet" href="skeleton.css">
13
+ <style>
14
+ .demo {
15
+ background: var(--surface);
16
+ border: 1px solid var(--border-decor);
17
+ border-radius: var(--r-md);
18
+ padding: var(--sp-4);
19
+ margin-bottom: var(--sp-4);
20
+ }
21
+ .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); }
22
+ /* 反例演示:故意用深色底+白字,说明"骨架屏不该这样用" */
23
+ .bad-demo { background: var(--danger-bg); border-color: var(--danger); }
24
+ .bad { color: var(--danger); }
25
+ </style>
26
+ </head>
27
+ <body class="page page">
28
+ <main class="wrap">
29
+ <!-- 🔴 0.8.9:axe 报 landmark-one-main(moderate)—— 本页原本没有任何 landmark 容器。
30
+ 加 <main> 是语义正确且不改布局的做法(<main> 默认不产生 box)。
31
+ ⚠️ 试过给 <body> 加 role="main":axe 明确报 aria-allowed-role(该 role 对 <body>
32
+ 是禁止的),而且也没解决 landmark 违规 ⇒ 已回退。 -->
33
+
34
+ <h1>骨架屏 · skeleton</h1>
35
+ <p class="lede">加载态是移动端最高频的状态。形状不统一的加载态会让页面看起来"没做完"。</p>
36
+
37
+ <h2>1 · 文本骨架(多行)</h2>
38
+ <div class="demo" aria-busy="true">
39
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:92%"></span>
40
+ <span class="skeleton skeleton--text" style="width:78%" aria-hidden="true"></span>
41
+ <span class="skeleton skeleton--text" style="width:56%" aria-hidden="true"></span>
42
+ <p class="note">最后一行不带下边距(用 <code>:last-child</code>),列表尾部不会多一块空白。</p>
43
+ </div>
44
+
45
+ <h2>2 · 块状骨架(图片/卡片占位)</h2>
46
+ <div class="demo" aria-busy="true">
47
+ <div class="row2">
48
+ <div>
49
+ <div class="skeleton skeleton--block" style="height:96px" aria-hidden="true"></div>
50
+ <span class="skeleton skeleton--text" style="width:70%; margin-top:var(--sp-3)" aria-hidden="true"></span>
51
+ <span class="skeleton skeleton--text" style="width:45%" aria-hidden="true"></span>
52
+ </div>
53
+ <div>
54
+ <div class="skeleton skeleton--block" style="height:96px" aria-hidden="true"></div>
55
+ <span class="skeleton skeleton--text" style="width:62%; margin-top:var(--sp-3)" aria-hidden="true"></span>
56
+ <span class="skeleton skeleton--text" style="width:52%" aria-hidden="true"></span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <h2>3 · 列表骨架(最常用形态)</h2>
62
+ <div class="demo" aria-busy="true">
63
+ <div class="skeleton-list">
64
+ <div class="skeleton-list__row">
65
+ <span class="skeleton skeleton--circle" aria-hidden="true"></span>
66
+ <div style="flex:1">
67
+ <span class="skeleton skeleton--text" style="width:40%" aria-hidden="true"></span>
68
+ <span class="skeleton skeleton--text" style="width:88%" aria-hidden="true"></span>
69
+ </div>
70
+ </div>
71
+ <div class="skeleton-list__row">
72
+ <span class="skeleton skeleton--circle" style="--skeleton-size:32px" aria-hidden="true"></span>
73
+ <div style="flex:1">
74
+ <span class="skeleton skeleton--text" style="width:34%" aria-hidden="true"></span>
75
+ <span class="skeleton skeleton--text" style="width:72%" aria-hidden="true"></span>
76
+ </div>
77
+ </div>
78
+ <div class="skeleton-list__row">
79
+ <span class="skeleton skeleton--circle" style="--skeleton-size:32px" aria-hidden="true"></span>
80
+ <div style="flex:1">
81
+ <span class="skeleton skeleton--text" style="width:44%" aria-hidden="true"></span>
82
+ <span class="skeleton skeleton--text" style="width:80%" aria-hidden="true"></span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <h2>4 · 减少动态效果时</h2>
89
+ <div class="demo" aria-busy="true">
90
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:70%"></span>
91
+ <span class="skeleton skeleton--text" style="width:52%" aria-hidden="true"></span>
92
+ <p class="note">
93
+ 系统开启「减少动态效果」后,微光动画会停掉,
94
+ 但<b>骨架屏本身仍然显示</b> —— 用户依然知道正在加载。
95
+ </p>
96
+ </div>
97
+
98
+ <h2>反例 · 不该这样用</h2>
99
+ <div class="demo bad-demo">
100
+ <span class="skeleton skeleton--text" aria-hidden="true" style="width:70%"></span>
101
+ <p class="note bad">
102
+ <b>别把骨架屏当"灰条"用在一段很短的文字上</b>(&lt; 12px)。
103
+ 短文字用骨架屏会读成"渲染坏了",而且屏幕阅读器会念出一堆空白。
104
+ 短文案直接给 spinner 或干脆不给反馈。
105
+ </p>
106
+ </div>
107
+
108
+ </main>
109
+ </body>
110
+ </html>
@@ -0,0 +1,96 @@
1
+ /* ============================================================================
2
+ skeleton — 骨架屏
3
+ ============================================================================
4
+ 为什么需要它(通行系统的必备件)
5
+ ---------------------------------------------------------------------------
6
+ 加载态是移动端**最高频**的状态。没有骨架屏的页面,每个业务方都会自己造一个,
7
+ 于是:形状不统一、闪烁频率不一致、暗色下颜色不对。
8
+ ⭐ 参照:Ant Design Skeleton / MUI Skeleton / Vercel 的 streaming skeleton。
9
+
10
+ 设计要点
11
+ ---------------------------------------------------------------------------
12
+ ① **不用 JS**:纯 CSS,`--skeleton-*` 令牌可调
13
+ ② **动画尊重 prefers-reduced-motion**(本库铁律,也是 WCAG 2.3.3)
14
+ ③ 暗色下用**令牌**而非硬编码(暗色令牌已就绪)
15
+ ④ 提供 3 种形状:text(多行文本)/ block(矩形块)/ circle(头像圆)
16
+ ============================================================================ */
17
+
18
+ .skeleton {
19
+ display: block;
20
+ background: var(--skeleton-bg);
21
+ border-radius: var(--r-sm);
22
+ /* ⭐ 尺寸用令牌,方便统一调整密度 */
23
+ height: var(--skeleton-h, 1em);
24
+ width: 100%;
25
+ position: relative;
26
+ overflow: hidden;
27
+ }
28
+
29
+ /* 形状变体 */
30
+ .skeleton--text {
31
+ height: auto;
32
+ /* 用 padding-bottom 撑出多行文本的高度(不写死行高) */
33
+ padding-bottom: 0.55em;
34
+ margin-bottom: var(--sp-2);
35
+ border-radius: var(--r-xs);
36
+ }
37
+ .skeleton--block {
38
+ border-radius: var(--r-md);
39
+ }
40
+ .skeleton--circle {
41
+ border-radius: 50%;
42
+ width: var(--skeleton-size, 40px);
43
+ height: var(--skeleton-size, 40px);
44
+ flex: none;
45
+ }
46
+
47
+ /* 最后一行不加下边距(避免列表尾部多一块空白) */
48
+ .skeleton--text:last-child {
49
+ margin-bottom: 0;
50
+ }
51
+
52
+ /* ---------------------------------------------------------------------------
53
+ 加载中的微光动画
54
+ ---------------------------------------------------------------------------
55
+ ⚠️ 用 transform 不用 background-position —— 前者可用 transform 合成,
56
+ 在低端机上开销明显更小。
57
+ ⭐ 但必须整体尊重「减少动态效果」偏好(WCAG 2.3.3)。
58
+ --------------------------------------------------------------------------- */
59
+ @media (prefers-reduced-motion: no-preference) {
60
+ .skeleton::after {
61
+ content: '';
62
+ position: absolute;
63
+ inset: 0;
64
+ transform: translateX(-100%);
65
+ background: linear-gradient(
66
+ 90deg,
67
+ transparent 0%,
68
+ var(--skeleton-shimmer) 50%,
69
+ transparent 100%
70
+ );
71
+ animation: skeleton-sweep var(--skeleton-duration, 1.4s) ease-in-out infinite;
72
+ }
73
+ }
74
+
75
+ @keyframes skeleton-sweep {
76
+ 100% { transform: translateX(100%); }
77
+ }
78
+
79
+ /* ⚠️ 减少动态效果:动画停掉,但骨架屏**依然存在**(用户仍知道在加载) */
80
+ @media (prefers-reduced-motion: reduce) {
81
+ .skeleton::after { animation: none; opacity: 0; }
82
+ }
83
+
84
+ /* ---------------------------------------------------------------------------
85
+ 组合件:列表骨架(最常用的形态)
86
+ --------------------------------------------------------------------------- */
87
+ .skeleton-list {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: var(--sp-3);
91
+ }
92
+ .skeleton-list__row {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: var(--sp-3);
96
+ }
@@ -0,0 +1,65 @@
1
+ # switch · 开关
2
+
3
+ 用于**立即生效的设置项**("开/关通知")。
4
+
5
+ ## 它和 checkbox 的差别是**语义**,不是形状
6
+
7
+ | | 读屏播报 | 键盘 |
8
+ |---|---|---|
9
+ | `.choice--checkbox` | 「复选框,已勾选」 | Space 切换 |
10
+ | `.switch` | 「**开关**,已开启」 | **Space 切换,Enter 不切换** |
11
+
12
+ APG 把两者分成不同模式,正是因为这个 —— 复选框是"待提交的选择",
13
+ 开关是"已经生效的状态"。
14
+
15
+ ## 类
16
+
17
+ | 类 | 作用 |
18
+ |---|---|
19
+ | `.switch` | 容器 |
20
+ | `.switch__input` | **原生 `<input type="checkbox" role="switch">`** |
21
+ | `.switch__track` | 滑块轨道(焦点环画在这里)|
22
+ | `.switch__thumb` | 滑块圆点 |
23
+ | `.switch__label` | 标签(`<label for>` 关联)|
24
+ | `.switch__label--sr` | 视觉隐藏、语义保留(只有滑块时用)|
25
+ | `.switch--lg` | 较大一档(48×28)|
26
+
27
+ ## 用法
28
+
29
+ ```html
30
+ <span class="switch">
31
+ <input class="switch__input" type="checkbox" role="switch" id="s1">
32
+ <span class="switch__track"><span class="switch__thumb"></span></span>
33
+ <label class="switch__label" for="s1">开启动价推送</label>
34
+ </span>
35
+ ```
36
+
37
+ ## 为什么用原生 checkbox 而不是 div + role
38
+
39
+ **用原生控件 ⇒ 无障碍行为免费且必然正确**:
40
+ 浏览器天生只响应 Space,**不需要写 JS 去拦 Enter** ——
41
+ 也就不会有"忘拦"的风险。焦点环看不见原生控件,所以画在 `.switch__track` 上。
42
+
43
+ 对比:`<div role="switch" tabindex="0" onclick>` 要自己处理
44
+ Space 键、焦点样式、状态播报 —— **三件事漏一件就坏**,
45
+ 而且 axe 只看得见 DOM,看不见你的 JS 有没绑对。
46
+
47
+ ## 组件零 JS
48
+
49
+ `switch.css` 不含任何 JavaScript。demo 里那个 `change` 监听器
50
+ **只是用来显示当前状态的**,不是组件的一部分。
51
+
52
+ ## 尺寸
53
+
54
+ 默认 40×22 · 较大 48×28 —— 两档(与 card / badge 一致)。
55
+
56
+ ## 验证
57
+
58
+ `switch-contract.js` 10 项全过(**用真实按键**,不是 dispatchEvent):
59
+
60
+ - 可聚焦 · `role="switch"` · 原生 checkbox · `label[for]` 关联
61
+ - 焦点环画在可见元素上
62
+ - **Space 切换**(真实按键)· **Enter 不切换**(APG 要求,真实按键)
63
+ - 滑块位移随状态变化 · axe 0 违规 / 24 条通过
64
+
65
+ 判别力验证:去掉 `role` 与 `label` ⇒ 4 项立即被抓。
@@ -0,0 +1,115 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link rel="icon" href="data:,">
7
+ <title>switch · 开关</title>
8
+ <link rel="stylesheet" href="../../01-tokens/tokens.css">
9
+ <link rel="stylesheet" href="../../01-tokens/typography.css">
10
+ <link rel="stylesheet" href="../../01-tokens/page.css">
11
+ <link rel="stylesheet" href="../../02-primitives/switch/switch.css">
12
+ <style>
13
+ .row { display: flex; gap: var(--sp-5); flex-wrap: wrap; padding: var(--sp-4) 0;
14
+ align-items: center; }
15
+ .state { font-family: var(--mono); font-size: var(--fs-xs);
16
+ color: var(--text-secondary); }
17
+ </style>
18
+ </head>
19
+ <body class="page page">
20
+ <main class="wrap">
21
+ <h1>switch · 开关</h1>
22
+ <p class="lede">
23
+ 开关用于<strong>立即生效的设置项</strong>("开/关通知")。
24
+ 它与 checkbox 的差别不是形状,是<strong>语义</strong> ——
25
+ 读屏会播报「开关,已开启」而不是「复选框,已勾选」。
26
+ </p>
27
+
28
+ <h2 class="rule-top">基本用法</h2>
29
+ <div class="row">
30
+ <span class="switch">
31
+ <input class="switch__input" type="checkbox" role="switch" id="s1">
32
+ <span class="switch__track"><span class="switch__thumb"></span></span>
33
+ <label class="switch__label" for="s1">开启动价推送</label>
34
+ </span>
35
+ <span class="state" id="st1">当前:<b>开</b></span>
36
+ </div>
37
+
38
+ <h2 class="rule-top">只有滑块(文字视觉隐藏,语义保留)</h2>
39
+ <div class="row">
40
+ <span class="switch">
41
+ <input class="switch__input" type="checkbox" role="switch" id="s2" checked>
42
+ <span class="switch__track"><span class="switch__thumb"></span></span>
43
+ <label class="switch__label switch__label--sr" for="s2">暗色模式</label>
44
+ </span>
45
+ <span class="small">看起来只有一个滑块,但读屏仍会念「暗色模式,开关,已开启」</span>
46
+ </div>
47
+
48
+ <h2 class="rule-top">两档尺寸</h2>
49
+ <div class="row">
50
+ <span class="switch">
51
+ <input class="switch__input" type="checkbox" role="switch" id="s3">
52
+ <span class="switch__track"><span class="switch__thumb"></span></span>
53
+ <label class="switch__label" for="s3">默认 22px</label>
54
+ </span>
55
+ <span class="switch switch--lg">
56
+ <input class="switch__input" type="checkbox" role="switch" id="s4" checked>
57
+ <span class="switch__track"><span class="switch__thumb"></span></span>
58
+ <label class="switch__label" for="s4">较大 28px</label>
59
+ </span>
60
+ </div>
61
+
62
+ <h2 class="rule-top">禁用态</h2>
63
+ <div class="row">
64
+ <span class="switch">
65
+ <input class="switch__input" type="checkbox" role="switch" id="s5" disabled>
66
+ <span class="switch__track"><span class="switch__thumb"></span></span>
67
+ <label class="switch__label" for="s5">不可用(未开通)</label>
68
+ </span>
69
+ </div>
70
+
71
+ <h2 class="rule-top">键盘行为(APG Switch Pattern)</h2>
72
+ <div class="box">
73
+ <p>
74
+ <strong>Space 切换 —— 这是唯一能切换的键。</strong><br>
75
+ <strong>Enter 不切换</strong>(button 默认行为会切,但 switch 不是 button)。
76
+ </p>
77
+ <p class="small">
78
+ 本组件用<strong>原生 checkbox</strong> 实现,所以浏览器天然只响应 Space ——
79
+ <strong>不需要写 JS 去拦 Enter</strong>,也就不会有"忘拦"的风险。
80
+ 这是"用原生控件"最大的收益:<strong>无障碍行为免费且必然正确</strong>。
81
+ </p>
82
+ </div>
83
+
84
+ <h2 class="rule-top">反例</h2>
85
+ <div class="box bad">
86
+ <h3>反例:用 div + role="switch",自己管所有状态</h3>
87
+ <p class="small">
88
+ 常见写法是 <code>&lt;div role="switch" tabindex="0" onclick="..."&gt;</code>,
89
+ 然后手写 <code>aria-checked</code> 切换。这需要自己处理:
90
+ Space 键、焦点样式、状态播报。<strong>三件事漏一件就坏</strong>,
91
+ 而且测试工具(axe)只看得见 DOM,看不见你的 JS 有没有绑对。
92
+ </p>
93
+ </div>
94
+
95
+ <div class="box bad">
96
+ <h3>反例:给 label 加 display:none</h3>
97
+ <p class="small">
98
+ 为了"只留滑块"而把 <code>&lt;label&gt;</code> 隐藏掉,
99
+ 开关就<strong>失去了可点击的文字区域</strong>,也失去了语义名字 ——
100
+ 读屏只会念「开关」,不知道它是干什么的。
101
+ 正确做法是 <code>.switch__label--sr</code>:视觉隐藏,语义保留。
102
+ </p>
103
+ </div>
104
+ </main>
105
+ <script>
106
+ // 仅为 demo:显示当前状态(组件本身零 JS)
107
+ ['s1'].forEach(function (id) {
108
+ var el = document.getElementById(id);
109
+ var out = document.getElementById('st1');
110
+ function sync() { out.innerHTML = '当前:<b>' + (el.checked ? '开' : '关') + '</b>'; }
111
+ el.addEventListener('change', sync);
112
+ });
113
+ </script>
114
+ </body>
115
+ </html>