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.
- package/01-tokens/README.md +236 -0
- package/01-tokens/behavior/dismissable.js +132 -0
- package/01-tokens/behavior/emit.js +76 -0
- package/01-tokens/behavior/focus-return.js +81 -0
- package/01-tokens/behavior/roving.js +152 -0
- package/01-tokens/behavior/typeahead.js +124 -0
- package/01-tokens/focus-ring-demo.html +133 -0
- package/01-tokens/focus-ring.css +102 -0
- package/01-tokens/page.css +244 -0
- package/01-tokens/polyfill.js +116 -0
- package/01-tokens/responsive-demo.html +171 -0
- package/01-tokens/scrollbar-demo.html +194 -0
- package/01-tokens/scrollbar.css +179 -0
- package/01-tokens/theme-toggle.js +103 -0
- package/01-tokens/tokens.css +972 -0
- package/01-tokens/typography.css +156 -0
- package/02-primitives/badge/README.md +71 -0
- package/02-primitives/badge/badge.css +141 -0
- package/02-primitives/badge/demo.html +101 -0
- package/02-primitives/button/README.md +99 -0
- package/02-primitives/button/button.css +353 -0
- package/02-primitives/button/demo.html +200 -0
- package/02-primitives/card/README.md +145 -0
- package/02-primitives/card/card.css +246 -0
- package/02-primitives/card/demo.html +170 -0
- package/02-primitives/choice/README.md +161 -0
- package/02-primitives/choice/choice.css +395 -0
- package/02-primitives/choice/demo.html +277 -0
- package/02-primitives/combobox/README.md +79 -0
- package/02-primitives/combobox/combobox.css +223 -0
- package/02-primitives/combobox/combobox.js +485 -0
- package/02-primitives/combobox/demo.html +109 -0
- package/02-primitives/date-range/README.md +100 -0
- package/02-primitives/date-range/date-range.css +206 -0
- package/02-primitives/date-range/date-range.js +301 -0
- package/02-primitives/date-range/demo.html +104 -0
- package/02-primitives/input/README.md +170 -0
- package/02-primitives/input/demo.html +246 -0
- package/02-primitives/input/input.css +239 -0
- package/02-primitives/popover/README.md +80 -0
- package/02-primitives/popover/demo.html +150 -0
- package/02-primitives/popover/popover.css +187 -0
- package/02-primitives/popover/popover.js +331 -0
- package/02-primitives/progress/README.md +82 -0
- package/02-primitives/progress/demo.html +154 -0
- package/02-primitives/progress/progress.css +172 -0
- package/02-primitives/select/README.md +95 -0
- package/02-primitives/select/demo.html +121 -0
- package/02-primitives/select/select.css +151 -0
- package/02-primitives/select/select.js +549 -0
- package/02-primitives/separator/README.md +63 -0
- package/02-primitives/separator/demo.html +84 -0
- package/02-primitives/separator/separator.css +73 -0
- package/02-primitives/skeleton/README.md +69 -0
- package/02-primitives/skeleton/demo.html +110 -0
- package/02-primitives/skeleton/skeleton.css +96 -0
- package/02-primitives/switch/README.md +65 -0
- package/02-primitives/switch/demo.html +115 -0
- package/02-primitives/switch/switch.css +370 -0
- package/03-patterns/accordion/README.md +121 -0
- package/03-patterns/accordion/accordion.css +453 -0
- package/03-patterns/accordion/accordion.js +369 -0
- package/03-patterns/accordion/demo.html +223 -0
- package/03-patterns/content/README.md +103 -0
- package/03-patterns/content/content.css +286 -0
- package/03-patterns/content/demo.html +88 -0
- package/03-patterns/drawer/README.md +79 -0
- package/03-patterns/drawer/demo.html +101 -0
- package/03-patterns/drawer/drawer.css +129 -0
- package/03-patterns/drawer/drawer.js +258 -0
- package/03-patterns/dropdown/README.md +78 -0
- package/03-patterns/dropdown/demo.html +101 -0
- package/03-patterns/dropdown/dropdown.css +135 -0
- package/03-patterns/dropdown/dropdown.js +451 -0
- package/03-patterns/form-validation/README.md +147 -0
- package/03-patterns/form-validation/demo.html +280 -0
- package/03-patterns/form-validation/form-validation.css +263 -0
- package/03-patterns/list/README.md +150 -0
- package/03-patterns/list/demo.html +225 -0
- package/03-patterns/list/flip.js +201 -0
- package/03-patterns/list/list.css +162 -0
- package/03-patterns/nav/README.md +225 -0
- package/03-patterns/nav/demo.html +131 -0
- package/03-patterns/nav/nav.css +314 -0
- package/03-patterns/nav/nav.js +430 -0
- package/03-patterns/nav/toc.js +111 -0
- package/03-patterns/overlay/README.md +196 -0
- package/03-patterns/overlay/demo.html +241 -0
- package/03-patterns/overlay/overlay.css +319 -0
- package/03-patterns/overlay/overlay.js +563 -0
- package/03-patterns/pagination/README.md +114 -0
- package/03-patterns/pagination/demo.html +165 -0
- package/03-patterns/pagination/pagination.css +195 -0
- package/03-patterns/pagination/pagination.js +395 -0
- package/03-patterns/reveal/README.md +91 -0
- package/03-patterns/reveal/demo.html +106 -0
- package/03-patterns/reveal/reveal.css +93 -0
- package/03-patterns/reveal/reveal.js +172 -0
- package/03-patterns/states/README.md +141 -0
- package/03-patterns/states/demo.html +240 -0
- package/03-patterns/states/states.css +252 -0
- package/03-patterns/tabs/README.md +84 -0
- package/03-patterns/tabs/demo.html +139 -0
- package/03-patterns/tabs/tabs.css +172 -0
- package/03-patterns/tabs/tabs.js +307 -0
- package/03-patterns/tooltip/README.md +96 -0
- package/03-patterns/tooltip/demo.html +78 -0
- package/03-patterns/tooltip/tooltip.css +89 -0
- package/03-patterns/tooltip/tooltip.js +288 -0
- package/03-patterns/tree/README.md +94 -0
- package/03-patterns/tree/demo.html +138 -0
- package/03-patterns/tree/tree.css +113 -0
- package/03-patterns/tree/tree.js +456 -0
- package/04-recipes/analysis-report/README.md +80 -0
- package/04-recipes/analysis-report/analysis-report.css +187 -0
- package/04-recipes/analysis-report/demo.html +274 -0
- package/04-recipes/data-showcase/README.md +45 -0
- package/04-recipes/data-showcase/demo.html +247 -0
- package/04-recipes/detail-page/README.md +93 -0
- package/04-recipes/detail-page/demo.html +55 -0
- package/04-recipes/detail-page/detail-page.css +190 -0
- package/04-recipes/detail-page/full.html +60 -0
- package/04-recipes/detail-page/null.html +60 -0
- package/04-recipes/detail-page/partial.html +60 -0
- package/04-recipes/detail-page/verify-skeleton.py +19 -0
- package/04-recipes/longform/README.md +93 -0
- package/04-recipes/longform/agc-memory.svg +4 -0
- package/04-recipes/longform/content.html +610 -0
- package/04-recipes/longform/content.md +51 -0
- package/04-recipes/longform/longform.html +627 -0
- package/04-recipes/report/README.md +176 -0
- package/04-recipes/report/demo.html +355 -0
- package/04-recipes/report/report.css +364 -0
- package/04-recipes/table/README.md +103 -0
- package/04-recipes/table/demo.html +455 -0
- package/04-recipes/table/table.css +372 -0
- package/04-recipes/tierA-tokens.css +189 -0
- package/09-assets/README.md +74 -0
- package/09-assets/bar/README.md +94 -0
- package/09-assets/bar/bar.css +166 -0
- package/09-assets/bar/bar.js +320 -0
- package/09-assets/bar/demo.html +176 -0
- package/09-assets/echarts-adapter/demo.html +192 -0
- package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
- package/09-assets/model-viewer/README.md +155 -0
- package/09-assets/model-viewer/demo.html +105 -0
- package/09-assets/model-viewer/model-viewer.css +143 -0
- package/09-assets/model-viewer/model-viewer.js +423 -0
- package/09-assets/scientific-plot/README.md +94 -0
- package/09-assets/scientific-plot/demo.html +252 -0
- package/09-assets/scientific-plot/scientific-plot.css +141 -0
- package/09-assets/scientific-plot/scientific-plot.js +610 -0
- package/09-assets/sparkline/README.md +153 -0
- package/09-assets/sparkline/demo.html +163 -0
- package/09-assets/sparkline/sparkline.css +158 -0
- package/09-assets/sparkline/sparkline.js +508 -0
- package/API.md +332 -0
- package/CHANGELOG.md +3138 -0
- package/CODE_OF_CONDUCT.md +64 -0
- package/CONTRIBUTING.md +89 -0
- package/ERRORS.md +1658 -0
- package/LICENSE +21 -0
- package/README.md +497 -2
- package/SECURITY.md +72 -0
- package/START-HERE.md +164 -0
- package/SUPPORT.md +109 -0
- package/adapters/presentation/README.md +113 -0
- package/adapters/presentation/check.js +101 -0
- package/adapters/presentation/deck.schema.json +74 -0
- package/adapters/presentation/to-doc.js +138 -0
- package/ai/START-HERE.md +104 -0
- package/ai/behaviors.json +380 -0
- package/ai/behaviors.schema.json +61 -0
- package/ai/capabilities.json +56 -0
- package/ai/cli.js +387 -0
- package/ai/components.json +1944 -0
- package/ai/components.meta.json +168 -0
- package/ai/components.schema.json +220 -0
- package/ai/contract.schema.json +147 -0
- package/ai/deprecations.json +55 -0
- package/ai/deprecations.schema.json +85 -0
- package/ai/motion-policy.json +22 -0
- package/ai/patch.js +323 -0
- package/ai/patch.schema.json +63 -0
- package/ai/profiles.json +92 -0
- package/ai/tokens.audience.json +55 -0
- package/ai/tokens.audience.schema.json +73 -0
- package/ai/tokens.json +762 -0
- package/ai/tokens.schema.json +58 -0
- package/ai/tokens.tree.json +752 -0
- package/ai/tokens.tree.schema.json +56 -0
- package/ai/validate.js +383 -0
- package/dist/01-tokens/behavior/dismissable.js +60 -0
- package/dist/01-tokens/behavior/emit.js +21 -0
- package/dist/01-tokens/behavior/focus-return.js +31 -0
- package/dist/01-tokens/behavior/roving.js +77 -0
- package/dist/01-tokens/behavior/typeahead.js +61 -0
- package/dist/01-tokens/focus-ring.css +1 -0
- package/dist/01-tokens/page.css +1 -0
- package/dist/01-tokens/polyfill.js +78 -0
- package/dist/01-tokens/scrollbar.css +1 -0
- package/dist/01-tokens/theme-toggle.js +61 -0
- package/dist/01-tokens/tokens.css +1 -0
- package/dist/01-tokens/typography.css +1 -0
- package/dist/02-primitives/badge/badge.css +1 -0
- package/dist/02-primitives/button/button.css +1 -0
- package/dist/02-primitives/card/card.css +1 -0
- package/dist/02-primitives/choice/choice.css +1 -0
- package/dist/02-primitives/combobox/combobox.css +1 -0
- package/dist/02-primitives/combobox/combobox.js +439 -0
- package/dist/02-primitives/date-range/date-range.css +1 -0
- package/dist/02-primitives/date-range/date-range.js +267 -0
- package/dist/02-primitives/input/input.css +1 -0
- package/dist/02-primitives/popover/popover.css +1 -0
- package/dist/02-primitives/popover/popover.js +257 -0
- package/dist/02-primitives/progress/progress.css +1 -0
- package/dist/02-primitives/select/select.css +1 -0
- package/dist/02-primitives/select/select.js +449 -0
- package/dist/02-primitives/separator/separator.css +1 -0
- package/dist/02-primitives/skeleton/skeleton.css +1 -0
- package/dist/02-primitives/switch/switch.css +1 -0
- package/dist/03-patterns/accordion/accordion.css +1 -0
- package/dist/03-patterns/accordion/accordion.js +219 -0
- package/dist/03-patterns/content/content.css +1 -0
- package/dist/03-patterns/drawer/drawer.css +1 -0
- package/dist/03-patterns/drawer/drawer.js +229 -0
- package/dist/03-patterns/dropdown/dropdown.css +1 -0
- package/dist/03-patterns/dropdown/dropdown.js +365 -0
- package/dist/03-patterns/form-validation/form-validation.css +1 -0
- package/dist/03-patterns/list/flip.js +117 -0
- package/dist/03-patterns/list/list.css +1 -0
- package/dist/03-patterns/nav/nav.css +1 -0
- package/dist/03-patterns/nav/nav.js +322 -0
- package/dist/03-patterns/nav/toc.js +76 -0
- package/dist/03-patterns/overlay/overlay.css +1 -0
- package/dist/03-patterns/overlay/overlay.js +398 -0
- package/dist/03-patterns/pagination/pagination.css +1 -0
- package/dist/03-patterns/pagination/pagination.js +273 -0
- package/dist/03-patterns/reveal/reveal.css +1 -0
- package/dist/03-patterns/reveal/reveal.js +138 -0
- package/dist/03-patterns/states/states.css +1 -0
- package/dist/03-patterns/tabs/tabs.css +1 -0
- package/dist/03-patterns/tabs/tabs.js +242 -0
- package/dist/03-patterns/tooltip/tooltip.css +1 -0
- package/dist/03-patterns/tooltip/tooltip.js +213 -0
- package/dist/03-patterns/tree/tree.css +1 -0
- package/dist/03-patterns/tree/tree.js +383 -0
- package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
- package/dist/04-recipes/detail-page/detail-page.css +1 -0
- package/dist/04-recipes/report/report.css +1 -0
- package/dist/04-recipes/table/table.css +1 -0
- package/dist/04-recipes/tierA-tokens.css +1 -0
- package/dist/09-assets/bar/bar.css +1 -0
- package/dist/09-assets/bar/bar.js +222 -0
- package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
- package/dist/09-assets/model-viewer/model-viewer.css +1 -0
- package/dist/09-assets/model-viewer/model-viewer.js +320 -0
- package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
- package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
- package/dist/09-assets/sparkline/sparkline.css +1 -0
- package/dist/09-assets/sparkline/sparkline.js +265 -0
- package/dist/COSTS.md +56 -0
- package/docs/AI-CONTRACT.md +197 -0
- package/docs/BACKLOG.md +127 -0
- package/docs/BENCHMARK.md +252 -0
- package/docs/GUIDANCE.md +154 -0
- package/docs/INVARIANT.md +372 -0
- package/docs/LAYER-BASELINE.md +158 -0
- package/docs/RELEASING.md +311 -0
- package/docs/RFC-THEME-SCOPE.md +141 -0
- package/docs/ROADMAP.md +182 -0
- package/index.html +82 -0
- package/package.json +107 -4
- package/types/index.d.ts +310 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Switch — 开关(APG: Switch Pattern)
|
|
3
|
+
============================================================================
|
|
4
|
+
states-exempt: error readonly
|
|
5
|
+
------------------------------------------------------------------------------
|
|
6
|
+
🔴 声明本文件**不适用** `error` / `readonly`:
|
|
7
|
+
|
|
8
|
+
states.py 按类名匹配规则组,而 `.switch__input` 里的 `input` 字面量
|
|
9
|
+
让本文件被当成 input 类,于是被要求实现校验态与只读态。
|
|
10
|
+
|
|
11
|
+
**但开关没有这两态** —— 它只有 开 / 关:
|
|
12
|
+
· error :开关不做校验,"开错了"不是表单提交时的错误
|
|
13
|
+
· readonly :开关一旦 readonly 就等于 disabled(用户不能改),
|
|
14
|
+
那不如直接用 disabled
|
|
15
|
+
|
|
16
|
+
两个坏的处理方式与为什么不用:
|
|
17
|
+
· 硬加假的 `[data-state="error"]` / `[readonly]` ⇒ 死状态进代码,
|
|
18
|
+
复用者会以为开关能校验
|
|
19
|
+
· 删门禁这条 ⇒ 所有 input 都不再被检查
|
|
20
|
+
⇒ 显式声明,且声明写在**被检查的文件里**,可 grep、可追溯。
|
|
21
|
+
|
|
22
|
+
🔴 为什么不复用 `.choice--checkbox` 的样式
|
|
23
|
+
------------------------------------------
|
|
24
|
+
checkbox 与 switch 的**语义不同**(APG 明确区分):
|
|
25
|
+
|
|
26
|
+
checkbox : 可以"选中/不选中"(多选),Space 切换
|
|
27
|
+
radio : 一组里选一个,方向键移动
|
|
28
|
+
switch : 立即生效的开关(设置项),**立即切换**
|
|
29
|
+
|
|
30
|
+
差别体现在读屏播报上:
|
|
31
|
+
checkbox 播报「复选框,已勾选」
|
|
32
|
+
switch 播报「开关,已开启」 ← 这个"立即生效"的暗示很重要
|
|
33
|
+
|
|
34
|
+
⇒ 结构上**共用 choice 的骨架**(原生 input + 可见 label),
|
|
35
|
+
但 switch 是独立的变体,因为形状(滑块)与语义都不同。
|
|
36
|
+
|
|
37
|
+
🔴 键盘:**只有 Space 切换,Enter 不切换**
|
|
38
|
+
------------------------------------------
|
|
39
|
+
这是 switch 与 button 的关键区别(APG Switch Pattern)。
|
|
40
|
+
很多实现图省事让 Enter 也能切 —— 那样读屏用户按 Enter 会**双重切换**
|
|
41
|
+
(button 默认行为 + 自定义 handler),或者在 form 里意外提交。
|
|
42
|
+
|
|
43
|
+
⇒ 本组件用原生 checkbox 实现:**浏览器自动只响应 Space**,
|
|
44
|
+
Enter 在 checkbox 上没有默认行为 ⇒ 天然正确,不需要写 JS 去拦。
|
|
45
|
+
|
|
46
|
+
⇒ 这一点是"用原生控件"的最大收益:**无障碍行为免费且必然正确**。
|
|
47
|
+
|
|
48
|
+
标签:必须用 `<label for>` 关联(不是 aria-label)。
|
|
49
|
+
----------------------------------------------------
|
|
50
|
+
视觉上的开关可以没有文字(只有一个滑块),
|
|
51
|
+
但语义上必须有名字 —— `<label class="visually-hidden">` 即可:
|
|
52
|
+
**看得见的可以没有,读屏必须听得到。**
|
|
53
|
+
========================================================================= */
|
|
54
|
+
|
|
55
|
+
.switch {
|
|
56
|
+
position: relative; /* 🔴 加:input 要相对它铺满整块 */
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: var(--sp-3);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
|
|
62
|
+
/* 🔴 加(iOS 真机实测反馈"点了不动"):
|
|
63
|
+
**视觉上滑块是 22px 高,但可点区域必须是 44px** ——
|
|
64
|
+
WCAG 2.1 · 2.5.5 Target Size (Enhanced) 要求 44×44 CSS px。
|
|
65
|
+
|
|
66
|
+
做法:**min-height 给 44,滑块本身仍然是 22** ——
|
|
67
|
+
看起来不变,但手指落在这条 44px 高的带内任意位置都能命中。
|
|
68
|
+
这就是「视觉尺寸 ≠ 命中尺寸」的标准做法(Apple HIG 也是这么解的)。
|
|
69
|
+
详见本文件末尾 `tap-target` 一节。 */
|
|
70
|
+
min-height: 44px;
|
|
71
|
+
min-width: 44px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* 🔴 **修一个真 bug**(iOS 真机实测发现"点了不动")
|
|
75
|
+
|
|
76
|
+
原来 input 被 clip 成 1px,而外层 `<span class="switch">` **不是 label**
|
|
77
|
+
(只有文字那个 `<label class="switch__label">` 是)
|
|
78
|
+
⇒ **点滑块等于点空气**。
|
|
79
|
+
|
|
80
|
+
为什么桌面没发现:用户往往点偏到文字上,文字是 label ⇒ 能中。
|
|
81
|
+
iOS 触屏没有 hover、手指更大更粗 ⇒ 几乎必中在滑块上。
|
|
82
|
+
|
|
83
|
+
⇒ 手法与 `.choice__input` **完全一致**(同一库里已经做对的做法):
|
|
84
|
+
`inset:0 + width/height:100% + opacity:0`
|
|
85
|
+
—— 铺满整块、**但保留在无障碍树与 Tab 序列里**。
|
|
86
|
+
|
|
87
|
+
🔴 绝不能用 `display:none` / `appearance:none` 代替:
|
|
88
|
+
那会让它从无障碍树消失、键盘也 Tab 不到。 */
|
|
89
|
+
.switch__input {
|
|
90
|
+
position: absolute;
|
|
91
|
+
inset: 0;
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: 100%;
|
|
94
|
+
margin: 0;
|
|
95
|
+
padding: 0;
|
|
96
|
+
opacity: 0;
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
/* 🔴 z-index 必须在 .switch__track 之上。
|
|
99
|
+
DOM 顺序是 input → track → label(同层 z-index:auto/0),
|
|
100
|
+
若 input 在第一层,**后画的 track 会盖住它** ⇒ 点了命中 track
|
|
101
|
+
而非 input,又是"点了不动"。显式提到第一层,点击直接命中 input。 */
|
|
102
|
+
z-index: 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* 滑块轨道 */
|
|
106
|
+
/* ---------------------------------------------------------------------------
|
|
107
|
+
🔴 **重做**。真机实测反馈:
|
|
108
|
+
「暗色模式下的开关**这个肯定得改**,因为你只是**做了照抄**,
|
|
109
|
+
在暗色模式下的表达是完完全全有问题的,甚至**边缘都是很粗糙的**」
|
|
110
|
+
|
|
111
|
+
⇒ 本次依据 **Radix Themes 的 Switch 真实实现**(实测抓自
|
|
112
|
+
`@radix-ui/themes@3.0.0/styles.css`,818KB,不是凭印象):
|
|
113
|
+
|
|
114
|
+
--switch-width: calc(var(--switch-height) * 1.75) ← 宽高比 1 : 1.75
|
|
115
|
+
--switch-thumb-inset: 1px ← 滑块**内缩 1px**
|
|
116
|
+
--switch-thumb-size: calc(height - inset*2)
|
|
117
|
+
.rt-SwitchThumb { background-color: white } ← **滑块恒为白**
|
|
118
|
+
|
|
119
|
+
⚠️ 我原来"照抄"错的三处(实测证据):
|
|
120
|
+
① 轨道直接用 `var(--accent)` ⇒ 暗色下是 `#7AA9DE` **亮蓝**,
|
|
121
|
+
在深底上**刺眼、发光**(暗色下品牌色要降彩度,不是照搬)。
|
|
122
|
+
② 滑块用 `var(--surface)` ⇒ 暗色下变成 `rgb(28,32,36)` **深灰**,
|
|
123
|
+
落在亮蓝轨道上像个"**暗洞**",边界很硬 ⇒ 反馈"边缘粗糙"。
|
|
124
|
+
**Radix 的做法是滑块恒为白**(两种模式下都一样)——
|
|
125
|
+
因为滑块是"实物",必须始终最亮,才有"可抓握"的物理感。
|
|
126
|
+
③ 滑块带 `1px solid var(--border-control)` 边框
|
|
127
|
+
⇒ 在暗色下那圈边框又粗又灰,边缘显得糙。
|
|
128
|
+
**Radix 没有边框**(用 `box-shadow` 表达层次)。
|
|
129
|
+
|
|
130
|
+
⇒ 下面按实测参数重写。
|
|
131
|
+
------------------------------------------------------------------------ */
|
|
132
|
+
.switch__track {
|
|
133
|
+
position: relative;
|
|
134
|
+
flex: 0 0 auto;
|
|
135
|
+
|
|
136
|
+
/* ==========================================================================
|
|
137
|
+
🔴 **彻底重写几何**(真机:「你的那个圆圈完全是歪的」)
|
|
138
|
+
|
|
139
|
+
真因(实测,不是猜):
|
|
140
|
+
· 轨道实际 44×26,而我在 CSS 里写的是 42×24
|
|
141
|
+
· 圆心落在 (30, 12),应在 (22, 13) ← **上紧下松,就是"歪"**
|
|
142
|
+
· transform 是硬编码的 `translateX(18px)`,与实际尺寸**脱钩**
|
|
143
|
+
⇒ 根因:**用「写死的数字」代替「推导」**。
|
|
144
|
+
改一个尺寸,别处就歪,且没人会注意到。
|
|
145
|
+
|
|
146
|
+
⇒ 修法(Radix 的做法,**全部由一个变量推导**):
|
|
147
|
+
--sw-h 轨道高
|
|
148
|
+
--sw-inset 滑块内缩(= 边框宽,让滑块压在内框上)
|
|
149
|
+
--sw-w 轨道宽 = 高 × 1.75 ← Radix 实测比例
|
|
150
|
+
--sw-thumb 滑块直径 = 高 − 内缩 × 2
|
|
151
|
+
--sw-travel 滑块行程 = 宽 − 高 ← Radix 的 --switch-thumb-translate-x
|
|
152
|
+
|
|
153
|
+
这样**改一个 --sw-h,整套几何自动自洽**,不可能再歪。
|
|
154
|
+
========================================================================== */
|
|
155
|
+
/* ==========================================================================
|
|
156
|
+
🔴 第二次修正:**变量里只放「基础量」**,
|
|
157
|
+
`calc()` 一律写在**用到它的地方**。
|
|
158
|
+
|
|
159
|
+
为什么不能把 calc 存进变量(实测踩到):
|
|
160
|
+
`--sw-travel: calc(calc(24px*1.75) - 1px*2 - …)` 这种**嵌套 calc**
|
|
161
|
+
在自定义属性里是「未求值的字符串」——
|
|
162
|
+
⚠️ `getComputedStyle` 读出来还是 `calc(...)` 本身,
|
|
163
|
+
`transform: translateX(var(--sw-travel))` 拿到的是
|
|
164
|
+
**字面量 18**(回退值),于是**滑块贴不到右边缘**
|
|
165
|
+
⇒ 视觉上就是 反馈的「歪、没画好」。
|
|
166
|
+
|
|
167
|
+
⇒ 现在只有三个"裸值"变量,尺寸关系全部在用到处写 calc。
|
|
168
|
+
========================================================================== */
|
|
169
|
+
--sw-h: 24px; /* 轨道高(含边框)*/
|
|
170
|
+
--sw-inset: 1px; /* = 边框宽 */
|
|
171
|
+
--sw-pad: 1px; /* 滑块与内框之间的呼吸 */
|
|
172
|
+
/* 🔴 滑块相对 border box 的偏移 = 边框 + 留白(这里都是 1px)*/
|
|
173
|
+
--sw-gap: var(--sw-pad);
|
|
174
|
+
/* 滑块直径 = 轨道内容高 − 留白×2
|
|
175
|
+
(border-box 下,轨道内容高 = 高 − 边框×2 = 24 − 2 = 22)*/
|
|
176
|
+
--sw-thumb: calc(var(--sw-h) - var(--sw-inset) * 2 - var(--sw-pad) * 2);
|
|
177
|
+
|
|
178
|
+
/* 🔴 关键修正:轨道是 **content-box**(本库默认),
|
|
179
|
+
⇒ `width: var(--sw-w)` 设的是**内容宽**,
|
|
180
|
+
渲染总宽 = 内容 + 边框×2 = 42 + 2 = **44px**(实测)。
|
|
181
|
+
而 `--sw-travel` 按 42 算 ⇒ **行程少 2px** ⇒
|
|
182
|
+
开态滑块**贴不到右边缘**,看起来就是"歪/没画好"。
|
|
183
|
+
|
|
184
|
+
⇒ 修法:**width/height 改成 border-box**,
|
|
185
|
+
让 `--sw-w` 就是**渲染总宽**,所有推导自洽。
|
|
186
|
+
(`--sw-inset` 已经是 1px,正好等于边框宽,滑块压在内框上。) */
|
|
187
|
+
box-sizing: border-box;
|
|
188
|
+
/* 宽高比 1 : 1.75(Radix 实测)*/
|
|
189
|
+
width: calc(var(--sw-h) * 1.75);
|
|
190
|
+
height: var(--sw-h);
|
|
191
|
+
border-radius: var(--r-pill);
|
|
192
|
+
background: var(--surface-sunken);
|
|
193
|
+
/* ⚠️ 关态边框用 decor(弱),不是 control(强)——
|
|
194
|
+
弱边界才不会"粗";强边框留给需要强调的控件 */
|
|
195
|
+
border: 1px solid var(--border-decor);
|
|
196
|
+
transition: background var(--dur-fast) var(--ease-out),
|
|
197
|
+
border-color var(--dur-fast) var(--ease-out);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* 滑块圆点 */
|
|
201
|
+
.switch__thumb {
|
|
202
|
+
/* ==========================================================================
|
|
203
|
+
🔴 **最终版:滑块定位改用 `top/left` 一次性算准**
|
|
204
|
+
|
|
205
|
+
前面几次都歪(真机:「圆圈完全是歪的」),根因是**用两套约束
|
|
206
|
+
(top+bottom,或 content-box + border)互相打架**。
|
|
207
|
+
实测最终数据:轨道 24 高、滑块 20 高、上 3 下 1 ⇒ 差 2px。
|
|
208
|
+
|
|
209
|
+
⇒ 现在只保留**一个真相来源**:
|
|
210
|
+
内框高 = 轨道高 − 边框×2 = 24 − 2 = 22
|
|
211
|
+
上下留白 = (22 − 20) / 2 = **1px**
|
|
212
|
+
top = 边框 1 + 留白 1 = **2px**
|
|
213
|
+
⚠️ 关键:`top` 是相对**padding box**(即 border 内侧)计算的,
|
|
214
|
+
所以 **不用再减边框** —— 浏览器已经处理了。
|
|
215
|
+
========================================================================== */
|
|
216
|
+
position: absolute;
|
|
217
|
+
/* 上下留白 = (轨道内容高 − 滑块) / 2;左右同理 */
|
|
218
|
+
top: var(--sw-gap);
|
|
219
|
+
inset-inline-start: var(--sw-gap);
|
|
220
|
+
|
|
221
|
+
width: var(--sw-thumb);
|
|
222
|
+
height: var(--sw-thumb);
|
|
223
|
+
border-radius: 50%;
|
|
224
|
+
/*🔴 改:原来写死 #FFFFFF。
|
|
225
|
+
真正的问题是:换主题色时它不会跟着调,且它是本组件
|
|
226
|
+
唯一不经过令牌的颜色。
|
|
227
|
+
--surface 两套模式都有定义(浅色 #fff / 暗色 #1C2024),
|
|
228
|
+
滑块作为「浮在轨道上的圆盘」用它是语义正确的。 */
|
|
229
|
+
background: var(--surface);
|
|
230
|
+
border: 0;
|
|
231
|
+
/* 阴影里的 rgba 是**阴影本身**的物理属性(不是主题色),
|
|
232
|
+
它在浅色与暗色下都该是黑色 —— 换令牌反而会变灰。 */
|
|
233
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, .28);
|
|
234
|
+
transition: transform var(--dur-fast) var(--ease-out),
|
|
235
|
+
box-shadow var(--dur-fast) var(--ease-out);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* 开 */
|
|
239
|
+
/* 开态轨道 —— 用**专门的开关令牌**,不是直接拿 --accent
|
|
240
|
+
⭐ 原因(实测):`--accent` 在暗色下是 `#7AA9DE`(**亮蓝**),
|
|
241
|
+
铺满 42×24 这么一大块 ⇒ 在深底上**发光、刺眼**。
|
|
242
|
+
暗色下的品牌色要**降彩度**(业界共识),
|
|
243
|
+
一个"大面积填充"的控件尤其需要。
|
|
244
|
+
⇒ 新增 `--switch-on`:浅色 = 品牌色;暗色 = 降彩度后的深蓝。 */
|
|
245
|
+
.switch__input:checked ~ .switch__track {
|
|
246
|
+
background: var(--switch-on, var(--accent));
|
|
247
|
+
border-color: var(--switch-on, var(--accent));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.switch__input:checked ~ .switch__track .switch__thumb {
|
|
251
|
+
/* 20px = 40(轨) - 16(点) - 4(左右各 2) */
|
|
252
|
+
/* 🔴 用推导值,不用写死的 18px(原来与实际尺寸脱钩 ⇒ 圆圈看着歪)*/
|
|
253
|
+
transform: translateX(calc(var(--sw-h) * 1.75
|
|
254
|
+
- var(--sw-inset) * 2 - var(--sw-pad) * 2
|
|
255
|
+
- (var(--sw-h) - var(--sw-inset) * 2
|
|
256
|
+
- var(--sw-pad) * 2)));
|
|
257
|
+
border-color: var(--accent);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* 焦点环在**看得见**的元素上(原生 outline 被 clip 了,看不见)。
|
|
261
|
+
|
|
262
|
+
🔴 补 `:focus`(原来只有 `:focus-visible`,states 门禁报缺)。
|
|
263
|
+
门禁的判据(不是我的偏好):
|
|
264
|
+
button 用 :focus-visible(鼠标点击时不该出环)
|
|
265
|
+
input 用 :focus (用户必须随时知道焦点在哪)
|
|
266
|
+
switch 归在 input 那一类(它就是个 checkbox),所以要 `:focus`。
|
|
267
|
+
写 `:focus` 而不是同时写两条,是因为**输入类控件的焦点必须始终可见** ——
|
|
268
|
+
只用 :focus-visible 的话,鼠标点过之后焦点就"看不见"了。 */
|
|
269
|
+
/* 🔴 修「点了就出边框、关不掉」(真机实测反馈)+ 一个我漏掉的根因
|
|
270
|
+
|
|
271
|
+
真因有两层,我之前只看到第一层:
|
|
272
|
+
|
|
273
|
+
① 我为了满足 states 门禁,把 `:focus-visible` 改成了 `:focus`。
|
|
274
|
+
⇒ iOS 上点触屏**也会给元素焦点** ⇒ `:focus` 永远成立 ⇒ 焦点环常驻。
|
|
275
|
+
⇒ 改回 `:focus-visible`(浏览器自己区分键盘/触摸)。
|
|
276
|
+
|
|
277
|
+
② 但**还有一层我漏了**:`input` 是原生控件,它**自己带一圈系统焦点环**
|
|
278
|
+
(Chromium 实测 3px,Safari 也一样)。
|
|
279
|
+
即使我的 `:focus-visible` 规则不触发,**原生那一圈照样在**
|
|
280
|
+
—— 因为原生 input 一直在"已聚焦"状态。
|
|
281
|
+
|
|
282
|
+
⇒ 光改选择器不够,**必须显式关掉原生环**:
|
|
283
|
+
`outline: none` 作为基线,只在 `:focus-visible` 时自己画一个。
|
|
284
|
+
(这正是本库 choice.css 早就做对的事——我又漏了。)
|
|
285
|
+
|
|
286
|
+
⇒ 两条一起改才对。 */
|
|
287
|
+
.switch__input {
|
|
288
|
+
/* 🔴 关掉原生焦点环(基线)。
|
|
289
|
+
没有这一条,系统那一圈会一直挂在视觉元素外面关不掉。 */
|
|
290
|
+
outline: none;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.switch__input:focus-visible ~ .switch__track {
|
|
294
|
+
outline: 2px solid var(--accent);
|
|
295
|
+
outline-offset: 2px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* 禁用
|
|
299
|
+
🔴 修正:原来用 `opacity: 0.5`。
|
|
300
|
+
states 门禁把它列为反模式,理由成立 ——
|
|
301
|
+
**opacity 会把元素内部所有颜色一起乘淡**,
|
|
302
|
+
于是"禁用"变成一种算出来的颜色,**对比度不再可控**
|
|
303
|
+
(0.5 乘完常常直接掉到 3:1 以下)。
|
|
304
|
+
⇒ 改成**显式换令牌**:`surface-sunken` 底 + `border-decor` 边,
|
|
305
|
+
视觉上"变灰"是确定的,且文字本身没有变淡 ——
|
|
306
|
+
WCAG 1.4.3 豁免的是"禁用控件"(不要求对比),但**不该靠乘算**。 */
|
|
307
|
+
.switch__input:disabled ~ .switch__track {
|
|
308
|
+
background: var(--surface-sunken);
|
|
309
|
+
border-color: var(--border-decor);
|
|
310
|
+
cursor: not-allowed;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.switch__input:disabled ~ .switch__track .switch__thumb {
|
|
314
|
+
background: var(--surface);
|
|
315
|
+
border-color: var(--border-decor);
|
|
316
|
+
box-shadow: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.switch__input:disabled ~ .switch__label {
|
|
320
|
+
color: var(--text-tertiary);
|
|
321
|
+
cursor: not-allowed;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* 标签文字 —— 可以没有(纯图标式开关),但语义名字必须有 */
|
|
325
|
+
.switch__label {
|
|
326
|
+
cursor: pointer;
|
|
327
|
+
user-select: none;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* 只有滑块、没有可见文字时的隐藏文字
|
|
331
|
+
⚠️ 这**不是** `display:none`(那样读屏也听不到),
|
|
332
|
+
是视觉隐藏但保留在无障碍树里。 */
|
|
333
|
+
.switch__label--sr {
|
|
334
|
+
position: absolute;
|
|
335
|
+
width: 1px;
|
|
336
|
+
height: 1px;
|
|
337
|
+
overflow: hidden;
|
|
338
|
+
clip: rect(0 0 0 0);
|
|
339
|
+
clip-path: inset(50%);
|
|
340
|
+
white-space: nowrap;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* 🔴 hover 门控:触屏上"点一下"会先点亮 hover 再 click ⇒ 状态粘住不褪。 */
|
|
344
|
+
@media (hover: hover) and (pointer: fine) {
|
|
345
|
+
.switch:not(.switch--disabled):hover .switch__track {
|
|
346
|
+
border-color: var(--accent-hover);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* 尺寸变体:两档(与 card / badge 一致) */
|
|
351
|
+
/* 🔴 大号:只改 --sw-h,整套几何(宽/滑块/行程)**自动跟着变**
|
|
352
|
+
—— 这就是变量推导的好处:不可能漏改某一处。 */
|
|
353
|
+
.switch--lg .switch__track {
|
|
354
|
+
--sw-h: 28px;
|
|
355
|
+
width: 48px;
|
|
356
|
+
height: 28px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.switch--lg .switch__thumb {
|
|
360
|
+
width: 22px;
|
|
361
|
+
height: 22px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* 大号滑块的位移:48 - 22 - 4 = 22px */
|
|
365
|
+
.switch--lg .switch__input:checked ~ .switch__track .switch__thumb {
|
|
366
|
+
transform: translateX(calc(var(--sw-h) * 1.75
|
|
367
|
+
- var(--sw-inset) * 2 - var(--sw-pad) * 2
|
|
368
|
+
- (var(--sw-h) - var(--sw-inset) * 2
|
|
369
|
+
- var(--sw-pad) * 2)));
|
|
370
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# accordion · 折叠面板
|
|
2
|
+
|
|
3
|
+
## 结构(APG 原文要求,不是我的设计)
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<div class="accordion" data-accordion>
|
|
7
|
+
<div class="accordion__item">
|
|
8
|
+
<h3 class="accordion__heading">
|
|
9
|
+
<button class="accordion__trigger" aria-expanded="true">
|
|
10
|
+
<span class="accordion__label">标题</span>
|
|
11
|
+
<span class="accordion__marker" aria-hidden="true"></span>
|
|
12
|
+
</button>
|
|
13
|
+
</h3>
|
|
14
|
+
<div class="accordion__panel">内容</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**button 必须在 heading 里面**,不能写反。APG 原文:
|
|
20
|
+
|
|
21
|
+
> "Make sure to place the heading tag around the button, **not inside it**,
|
|
22
|
+
> as this would prevent some screen readers (such as VoiceOver and TalkBack)
|
|
23
|
+
> from reading it."
|
|
24
|
+
|
|
25
|
+
写反了(`<button><h3>标题</h3></button>`)VoiceOver / TalkBack **读不出标题**。
|
|
26
|
+
|
|
27
|
+
**`aria-controls` / `aria-labelledby` / `role="region"` 由 JS 自动补齐** ——
|
|
28
|
+
这三处是机械的对应关系,让人手写只会写错。
|
|
29
|
+
|
|
30
|
+
## 类
|
|
31
|
+
|
|
32
|
+
| 类 | 作用 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `.accordion` | 容器(**默认单开**)|
|
|
35
|
+
| `data-accordion-multi` | 多个可同时展开(**data 属性**)|
|
|
36
|
+
| `.accordion--many` | 面板 > 6 时**不给** `role="region"` |
|
|
37
|
+
| `.accordion__item` | 一项 |
|
|
38
|
+
| `.accordion__heading` | heading 包装(真实 `<h2>`–`<h6>`)|
|
|
39
|
+
| `.accordion__trigger` | 原生 `<button>` |
|
|
40
|
+
| `.accordion__panel` | 内容区 |
|
|
41
|
+
|
|
42
|
+
## 两种模式
|
|
43
|
+
|
|
44
|
+
| 模式 | 类 | 行为 |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| **单开**(默认)| — | 开一个时自动关其他 |
|
|
47
|
+
| **多开** | `data-accordion-multi`(**data 属性,不是 class**)| 各自独立 |
|
|
48
|
+
|
|
49
|
+
判断依据:**同一个问题的不同答案**用单开;**各自独立的信息块**用多开。
|
|
50
|
+
|
|
51
|
+
### 为什么多开是 data 属性而不是 class
|
|
52
|
+
|
|
53
|
+
它**不是样式变体,是行为开关** —— 多开模式下展开的那一项,
|
|
54
|
+
和单开模式下展开的那一项**视觉上完全一样**(都是真的展开了)。
|
|
55
|
+
没有任何东西需要用样式去表达"这是多开模式"。
|
|
56
|
+
|
|
57
|
+
(对比 `tabs--manual`:那个我**给了**真实视觉差异 —— 选中项不加粗,
|
|
58
|
+
因为"还没生效的事不该看起来已生效"。两个决定标准是同一个:
|
|
59
|
+
**有没有真实的视觉差异需要表达**。)
|
|
60
|
+
|
|
61
|
+
## 键盘(APG 原文)
|
|
62
|
+
|
|
63
|
+
| 键 | 行为 |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `Enter` / `Space` | 切换 |
|
|
66
|
+
| `Tab` / `Shift+Tab` | 在**所有**可聚焦元素间移动(含面板内)|
|
|
67
|
+
|
|
68
|
+
### 🔴 本组件刻意**没有**方向键 / Home / End
|
|
69
|
+
|
|
70
|
+
APG 明确写:
|
|
71
|
+
|
|
72
|
+
> "arrow / Home / End key navigation is **no longer part of the
|
|
73
|
+
> APG Accordion keyboard interaction**."
|
|
74
|
+
|
|
75
|
+
那是 **tabs** 的契约(roving tabindex)。
|
|
76
|
+
**照抄给 accordion 是把两个模式混了。**
|
|
77
|
+
|
|
78
|
+
### 🔴 本组件刻意**不监听** `keydown`
|
|
79
|
+
|
|
80
|
+
原生 `<button>` **本身就响应** Enter/Space 并触发 click。
|
|
81
|
+
手写 keydown 会造成**双触发**(keydown 切一次、click 又切一次)。
|
|
82
|
+
|
|
83
|
+
判别力已验证:注入 keydown 监听 ⇒ 契约测试 2 项立即被抓。
|
|
84
|
+
|
|
85
|
+
## role="region" 的 ≤6 上限
|
|
86
|
+
|
|
87
|
+
APG 警告:
|
|
88
|
+
|
|
89
|
+
> "…in an accordion that contains more than **approximately 6** panels that
|
|
90
|
+
> can be expanded at the same time." ⇒ "landmark region proliferation"
|
|
91
|
+
|
|
92
|
+
地标泛滥时,读屏用户的地标列表会被几十个"分区"淹没。
|
|
93
|
+
**面板 > 6 时不要加** `role="region"` —— 用 `.accordion--many`。
|
|
94
|
+
|
|
95
|
+
## 折叠必须用 `hidden`
|
|
96
|
+
|
|
97
|
+
收起时用 `hidden` 属性 + `display:none`,
|
|
98
|
+
**不能只用 `max-height:0`** —— 那样内容仍在无障碍树里,
|
|
99
|
+
读屏会念出折叠区域的全部文字,键盘也能 Tab 进去。
|
|
100
|
+
|
|
101
|
+
## 语法约束
|
|
102
|
+
|
|
103
|
+
`accordion.js` **ES5**(无箭头函数 / const / let / 模板字符串),
|
|
104
|
+
也**不用 `Element.closest` / `Array.filter`** ——
|
|
105
|
+
理由与 `tabs.js` / `list.js` / `overlay.js` 一致:精简档 的 WebView 版本不确定,
|
|
106
|
+
**一致比方便重要**。
|
|
107
|
+
|
|
108
|
+
## 验证
|
|
109
|
+
|
|
110
|
+
`accordion-contract.js` **18 项全过**(真实按键):
|
|
111
|
+
|
|
112
|
+
- 结构:heading 包 button(非里面套)· 原生 button · ARIA 双向互指 · `role=region` · marker `aria-hidden`
|
|
113
|
+
- 单开:Enter 切换 · **原项自动收起** · 始终只有一项展开
|
|
114
|
+
- **收起的面板真正 `display:none`**(离开无障碍树)
|
|
115
|
+
- Space 切换(证明不监听 keydown 也对)· 点击切换
|
|
116
|
+
- 多开:两项可同时展开
|
|
117
|
+
- `aria-disabled` 点不开
|
|
118
|
+
- **>6 且加 `--many` ⇒ 不给 `role=region`**(实测 0/8)
|
|
119
|
+
- axe 0 违规 / 29 条通过
|
|
120
|
+
|
|
121
|
+
判别力验证:注入 keydown 双触发 ⇒ **2 项立即被抓**。
|