agentcss 0.0.0-stage → 0.11.2
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 +3116 -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 +491 -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 +275 -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,239 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
frontend-lib / 02-primitives / input / input.css
|
|
3
|
+
输入框 · 状态矩阵 + 无障碍
|
|
4
|
+
|
|
5
|
+
依赖:01-tokens/tokens.css
|
|
6
|
+
版本:A ✅ / B ✅ / C ✅
|
|
7
|
+
|
|
8
|
+
🔴 与 button 最大的一处不同,别照抄:
|
|
9
|
+
button 用 :focus-visible —— 鼠标点击时不该冒出难看的框
|
|
10
|
+
input 用 :focus —— 用户必须随时知道"我现在在哪个框里",
|
|
11
|
+
不管是鼠标点进去的还是 Tab 过去的。
|
|
12
|
+
这一条搞反,表单会非常难用。
|
|
13
|
+
========================================================================= */
|
|
14
|
+
|
|
15
|
+
/* ---------------------------------------------------------------- 容器 */
|
|
16
|
+
.field {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: var(--sp-1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ---------------------------------------------------------------- label
|
|
23
|
+
🔴 label 不是装饰。没有 label 的输入框,屏幕阅读器读出来只是一句"编辑框"。
|
|
24
|
+
三种合法写法:
|
|
25
|
+
<label for="x"> + <input id="x"> ← 推荐
|
|
26
|
+
<label>包住 input</label> ← 也行
|
|
27
|
+
<input aria-label="x"> ← 仅在确实不能有可见 label 时
|
|
28
|
+
placeholder 不算 label —— 它一输入就消失,且对比度通常不达标。
|
|
29
|
+
---------------------------------------------------------------- */
|
|
30
|
+
.field__label {
|
|
31
|
+
font-size: var(--fs-sm);
|
|
32
|
+
font-weight: 500;
|
|
33
|
+
color: var(--text-primary);
|
|
34
|
+
line-height: var(--lh-tight);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* 必填标记。不要只靠星号颜色 —— 带上 required 属性让机器也能读。 */
|
|
38
|
+
.field__req {
|
|
39
|
+
color: var(--danger); /* 6.26 AA */
|
|
40
|
+
margin-inline-start: 2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ---------------------------------------------------------------- 输入框本体 */
|
|
44
|
+
.field__input {
|
|
45
|
+
width: 100%;
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
|
|
48
|
+
height: 44px; /* 同 button,触摸目标 */
|
|
49
|
+
padding: 0 var(--sp-3);
|
|
50
|
+
|
|
51
|
+
font-family: inherit;
|
|
52
|
+
/* 🔴 16px 是硬下限,不是审美。
|
|
53
|
+
iOS Safari 在 font-size < 16px 的输入框获得焦点时会**自动放大整个页面**,
|
|
54
|
+
用户会以为页面坏了。这是移动端最常见的表单 bug。 */
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
line-height: 1.5;
|
|
57
|
+
color: var(--text-primary);
|
|
58
|
+
|
|
59
|
+
background: var(--surface);
|
|
60
|
+
border: 1px solid var(--border-control); /* 3.56 达标(UI 边界需 >= 3.0) */
|
|
61
|
+
border-radius: var(--r-sm);
|
|
62
|
+
|
|
63
|
+
transition: border-color var(--dur-fast) var(--ease-out),
|
|
64
|
+
box-shadow var(--dur-fast) var(--ease-out),
|
|
65
|
+
background-color var(--dur-fast) var(--ease-out);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.field__input::placeholder {
|
|
69
|
+
color: var(--text-tertiary); /* 5.16 AA —— 占位符也要能读 */
|
|
70
|
+
opacity: 1; /* Firefox 默认给占位符加 opacity,会破坏对比度 */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* 多行 */
|
|
74
|
+
.field__input--textarea {
|
|
75
|
+
height: auto;
|
|
76
|
+
min-height: 88px;
|
|
77
|
+
padding: var(--sp-3);
|
|
78
|
+
resize: vertical;
|
|
79
|
+
line-height: var(--lh-base);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* 紧凑(表格行内等鼠标场景)
|
|
83
|
+
⚠️ demo 未用它,README 类名表列了它 ⇒ 是"已文档化的能力",不是死代码。 */
|
|
84
|
+
.field__input--sm { height: 36px; font-size: 16px;
|
|
85
|
+
/* 🔴 输入框 的命中区给到 44px(WCAG 2.5.5)。
|
|
86
|
+
视觉高度不变,只是**手指落在这条带内任意位置都能命中**。 */
|
|
87
|
+
min-height: 44px;}
|
|
88
|
+
|
|
89
|
+
/* ---------------------------------------------------------------- 状态:hover */
|
|
90
|
+
@media (hover: hover) and (pointer: fine) {
|
|
91
|
+
.field__input:hover:not(:disabled):not([readonly]) {
|
|
92
|
+
border-color: var(--text-tertiary);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/* ---------------------------------------------------------------- 状态:focus
|
|
96
|
+
用 :focus 不用 :focus-visible(理由见文件头)。
|
|
97
|
+
视觉 = 边框变色 + 外圈 ring。两层是为了在深色/浅色背景下都看得清。
|
|
98
|
+
---------------------------------------------------------------- */
|
|
99
|
+
.field__input:focus {
|
|
100
|
+
outline: none; /* 去掉默认,用下面的自绘替代 */
|
|
101
|
+
border-color: var(--accent);
|
|
102
|
+
box-shadow: 0 0 0 3px var(--accent-soft);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ---------------------------------------------------------------- 状态:disabled
|
|
106
|
+
不可聚焦、值不提交、光标 not-allowed。
|
|
107
|
+
---------------------------------------------------------------- */
|
|
108
|
+
.field__input:disabled {
|
|
109
|
+
background: var(--surface-sunken);
|
|
110
|
+
color: var(--text-tertiary);
|
|
111
|
+
border-color: var(--border-decor);
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ---------------------------------------------------------------- 状态:readonly
|
|
116
|
+
🔴 和 disabled 是两回事,别混用:
|
|
117
|
+
disabled : 不可聚焦、不可复制、**值不提交**
|
|
118
|
+
readonly : 可聚焦、可选中复制、**值会提交**
|
|
119
|
+
场景:展示计算结果、展示从别处带过来的值 —— 用 readonly,让用户能复制。
|
|
120
|
+
视觉上要能区分:readonly 保持可读(正常文字色),只把底色调暗一点。
|
|
121
|
+
---------------------------------------------------------------- */
|
|
122
|
+
.field__input[readonly] {
|
|
123
|
+
background: var(--surface-sunken);
|
|
124
|
+
color: var(--text-primary); /* 保持正常对比度,用户要读它 */
|
|
125
|
+
border-color: var(--border-decor);
|
|
126
|
+
cursor: default;
|
|
127
|
+
}
|
|
128
|
+
.field__input[readonly]:focus {
|
|
129
|
+
border-color: var(--text-tertiary);
|
|
130
|
+
box-shadow: 0 0 0 3px var(--surface-sunken);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ---------------------------------------------------------------- 状态:error
|
|
134
|
+
🔴 WCAG 1.4.1:不能**只靠颜色**传达信息。
|
|
135
|
+
红边框 + 错误文字 + 图标,三者至少要有两个。
|
|
136
|
+
只把边框变红 = 色盲用户完全看不出哪里错了。
|
|
137
|
+
---------------------------------------------------------------- */
|
|
138
|
+
.field__input[aria-invalid="true"] {
|
|
139
|
+
border-color: var(--danger);
|
|
140
|
+
}
|
|
141
|
+
.field__input[aria-invalid="true"]:focus {
|
|
142
|
+
border-color: var(--danger);
|
|
143
|
+
box-shadow: 0 0 0 3px var(--danger-bg);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* 错误图标:给色盲用户的第二条线索 */
|
|
147
|
+
.field__input[aria-invalid="true"] {
|
|
148
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23b3261e' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 8v5M12 16.5v.5'/%3E%3C/svg%3E");
|
|
149
|
+
background-repeat: no-repeat;
|
|
150
|
+
background-position: right var(--sp-3) center;
|
|
151
|
+
padding-inline-end: 40px; /* 给图标留位,否则文字会压在图标上 */
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ---------------------------------------------------------------- 辅助说明
|
|
155
|
+
放在输入框下方,比 placeholder 好 —— 它不会消失。
|
|
156
|
+
用 aria-describedby 关联到 input。
|
|
157
|
+
---------------------------------------------------------------- */
|
|
158
|
+
.field__hint {
|
|
159
|
+
font-size: var(--fs-xs);
|
|
160
|
+
color: var(--text-tertiary); /* 5.16 AA */
|
|
161
|
+
line-height: 1.5;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* ---------------------------------------------------------------- 错误提示
|
|
165
|
+
必须满足两件事:
|
|
166
|
+
1) 有 role="alert" 或 aria-live —— 动态出现时屏幕阅读器会读出来
|
|
167
|
+
2) 通过 aria-describedby 关联 —— 用户聚焦输入框时能听到"哪里错了"
|
|
168
|
+
只变红不写文字,等于对屏幕阅读器用户静默失败。
|
|
169
|
+
---------------------------------------------------------------- */
|
|
170
|
+
.field__error {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: flex-start;
|
|
173
|
+
gap: var(--sp-1);
|
|
174
|
+
font-size: var(--fs-xs);
|
|
175
|
+
color: var(--danger); /* 6.26 AA */
|
|
176
|
+
line-height: 1.5;
|
|
177
|
+
}
|
|
178
|
+
.field__error::before {
|
|
179
|
+
content: "";
|
|
180
|
+
flex: none;
|
|
181
|
+
width: 14px;
|
|
182
|
+
height: 14px;
|
|
183
|
+
margin-top: 3px;
|
|
184
|
+
background: currentColor;
|
|
185
|
+
/* 叹号圆标 —— 给色盲用户的第二条线索 */
|
|
186
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 8v5M12 16.5v.5'/%3E%3C/svg%3E") center/contain no-repeat;
|
|
187
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 8v5M12 16.5v.5'/%3E%3C/svg%3E") center/contain no-repeat;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ---------------------------------------------------------------- 成功态
|
|
191
|
+
只在"校验有明确通过语义"时用(如"该用户名可用")。
|
|
192
|
+
普通输入框加成功态是噪音。
|
|
193
|
+
|
|
194
|
+
⚠️ **本库没有 JS 自动加 `data-state="valid"`** ——
|
|
195
|
+
`aria-invalid="false"` 由浏览器/你自己维护,这个属性也要自己加。
|
|
196
|
+
这条规则是给复用者的钩子,不是现成功能。
|
|
197
|
+
---------------------------------------------------------------- */
|
|
198
|
+
.field__input[aria-invalid="false"][data-state="valid"] {
|
|
199
|
+
border-color: var(--success);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* ---------------------------------------------------------------- 布局变体
|
|
203
|
+
横向排列(label 在左)。窄屏自动回退为纵向。
|
|
204
|
+
---------------------------------------------------------------- */
|
|
205
|
+
.field--row {
|
|
206
|
+
flex-direction: row;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: var(--sp-3);
|
|
209
|
+
}
|
|
210
|
+
.field--row .field__label {
|
|
211
|
+
flex: none;
|
|
212
|
+
min-width: 88px;
|
|
213
|
+
}
|
|
214
|
+
@media (max-width: 480px) {
|
|
215
|
+
.field--row { flex-direction: column; align-items: stretch; }
|
|
216
|
+
.field--row .field__label { min-width: 0; }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* ============================================================================
|
|
220
|
+
反模式 —— 库内一律退回
|
|
221
|
+
=========================================================================
|
|
222
|
+
1. 用 placeholder 当 label
|
|
223
|
+
一输入就消失,对比度通常不达标,屏幕阅读器支持不一致。
|
|
224
|
+
|
|
225
|
+
2. 只把边框变红表示错误
|
|
226
|
+
WCAG 1.4.1 禁止只靠颜色传达信息。必须有文字 + 图标。
|
|
227
|
+
|
|
228
|
+
3. input 用 :focus-visible
|
|
229
|
+
鼠标点进去时没有视觉反馈,用户不知道光标在哪。input 用 :focus。
|
|
230
|
+
|
|
231
|
+
4. font-size < 16px
|
|
232
|
+
iOS Safari 会自动放大整个页面。
|
|
233
|
+
|
|
234
|
+
5. readonly 做成 disabled 的样子
|
|
235
|
+
用户会以为值不提交,且不能复制。
|
|
236
|
+
|
|
237
|
+
6. 错误提示没有 aria-live / 没有 aria-describedby
|
|
238
|
+
屏幕阅读器用户完全不知道错了。
|
|
239
|
+
========================================================================= */
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# popover · 气泡卡片
|
|
2
|
+
|
|
3
|
+
通行系统的必备件。**非模态**——背景仍可交互,这是它与 dialog 的根本区别。
|
|
4
|
+
|
|
5
|
+
## ⭐ 该用哪一个(记不住就问这句)
|
|
6
|
+
|
|
7
|
+
> **用户需要"在里面操作"吗?**
|
|
8
|
+
> 不需要 → `tooltip`
|
|
9
|
+
> 需要,但不**打断**当前上下文 → **popover**
|
|
10
|
+
> 需要,且**必须先处理完**才能继续 → `dialog`
|
|
11
|
+
|
|
12
|
+
| | 能不能放交互内容 | 焦点管理 |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `tooltip` | ❌ 只能纯文字 | 无 |
|
|
15
|
+
| **`popover`** | ✅ 表单、按钮、列表都行 | **焦点进入** |
|
|
16
|
+
| `dialog` | ✅ 但**模态**(背景锁死)| focus trap |
|
|
17
|
+
|
|
18
|
+
参照实现:Radix Popover / MUI Popover / Carbon Popover。
|
|
19
|
+
|
|
20
|
+
## 启用:DOM 由 JS 建,不用自己写
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const p = Popover.attach(按钮元素, { place: 'bottom', label: '标题' });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`attach()` 会自动:把触发器包进新建的 `.popover-anchor`(定位上下文)、
|
|
27
|
+
建出浮层本体并写好 `role` 与 `aria-label`。
|
|
28
|
+
|
|
29
|
+
| 参数 | 取值 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `place` | `bottom`(默认)/ `top` / `start` / `end` |
|
|
32
|
+
| `label` | 浮层的 `aria-label` |
|
|
33
|
+
|
|
34
|
+
返回的对象有 `open()` / `close()` / `toggle()`。
|
|
35
|
+
|
|
36
|
+
全局还有两个:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
Popover.closeAll(); // 关掉当前开着的那个
|
|
40
|
+
Popover.current; // 当前开着的实例(没有则null)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 内部结构(JS 生成,供你放内容)
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<span class="popover-anchor">
|
|
47
|
+
<button aria-haspopup="dialog" aria-expanded="false">…</button>
|
|
48
|
+
<div class="popover popover--bottom" role="dialog"
|
|
49
|
+
data-open="false" aria-label="标题" inert>
|
|
50
|
+
<div class="popover__title">…</div>
|
|
51
|
+
<div class="popover__desc">…可含表单、按钮、列表…</div>
|
|
52
|
+
<div class="popover__footer">…</div>
|
|
53
|
+
<span class="popover__arrow" aria-hidden="true"></span>
|
|
54
|
+
</div>
|
|
55
|
+
</span>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| 部件 | 说明 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `.popover-anchor` | **定位上下文** —— 箭头与浮层都相对它 |
|
|
61
|
+
| `.popover` + `--top/--bottom/--start/--end` | 浮层与四个方位,箭头自动指回触发器 |
|
|
62
|
+
| `.popover__title` / `__desc` / `__footer` | 内容分区 |
|
|
63
|
+
| `.popover__arrow` | 箭头 |
|
|
64
|
+
|
|
65
|
+
⚠️ **关闭态用 `inert`,不是 `hidden`** —— `inert` 让内部元素**不可聚焦**
|
|
66
|
+
(`hidden` 只是视觉隐藏,里面还能 Tab 到,这是常见错法)。
|
|
67
|
+
|
|
68
|
+
## 🔴 四个最容易做错的地方
|
|
69
|
+
|
|
70
|
+
| # | 要求 | 做错的后果 |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| ① | **非模态**:背景仍可交互,`role="dialog"` 但**不加 `aria-modal`** | 加了 `aria-modal` ⇒ 语义变成模态,读屏会锁焦点 |
|
|
73
|
+
| ② | 焦点**进入**浮层 | 焦点留在触发器 ⇒ 键盘用户进不去,浮层等于摆设 |
|
|
74
|
+
| ③ | `Esc` 关闭 **+ 焦点归还触发器** | 只关不还 ⇒ 焦点掉到 `<body>`,Tab 从头再来 |
|
|
75
|
+
| ④ | 点**外部**关闭,点**浮层内部不关** | 内部点击也关 ⇒ 表单填到一半全没了(最常见的 bug)|
|
|
76
|
+
|
|
77
|
+
## 动画
|
|
78
|
+
|
|
79
|
+
尊重 `prefers-reduced-motion` —— 用户在系统里关了动效,
|
|
80
|
+
组件的过渡也要跟着关。
|
|
@@ -0,0 +1,150 @@
|
|
|
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>气泡卡片 · popover</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="popover.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
|
+
.row { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
|
|
22
|
+
/* ⭐ 四个方向的演示必须**竖排**(跨引擎实测)
|
|
23
|
+
`end` 方向的浮层挂在锚点右侧(`inset-inline-start: 100%`),
|
|
24
|
+
横排时第 3 个按钮的浮层会顶到 483px —— 而手机视口是 393px。
|
|
25
|
+
⚠️ 这不是"改测试让它过":溢出是**真的**,Chromium 只是用
|
|
26
|
+
`innerWidth` 被内容撑大的方式把它量成了"没溢出"(WebKit 不会)。
|
|
27
|
+
竖排后每个方向都有整行宽度可用 ⇒ 演示成立,也不需要假装组件会翻转。 */
|
|
28
|
+
.row--stack { flex-direction: column; align-items: flex-start; }
|
|
29
|
+
.bad-demo { background: var(--danger-bg); border-color: var(--danger); }
|
|
30
|
+
.bad { color: var(--danger); }
|
|
31
|
+
table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
|
|
32
|
+
td, th { text-align: left; padding: var(--sp-2); border-bottom: 1px solid var(--border-decor); }
|
|
33
|
+
th { font-weight: 600; }
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body class="page page">
|
|
37
|
+
<main class="wrap">
|
|
38
|
+
<!-- 🔴 0.8.9:axe 报 landmark-one-main(moderate)—— 本页原本没有任何 landmark 容器。
|
|
39
|
+
加 <main> 是语义正确且不改布局的做法(<main> 默认不产生 box)。
|
|
40
|
+
⚠️ 试过给 <body> 加 role="main":axe 明确报 aria-allowed-role(该 role 对 <body>
|
|
41
|
+
是禁止的),而且也没解决 landmark 违规 ⇒ 已回退。 -->
|
|
42
|
+
|
|
43
|
+
<h1>气泡卡片 · popover</h1>
|
|
44
|
+
<p class="lede">用户需要<strong>在里面操作</strong>,但不该被强制中断时用。</p>
|
|
45
|
+
|
|
46
|
+
<h2>1 · 四个方向</h2>
|
|
47
|
+
<div class="demo">
|
|
48
|
+
<div class="row row--stack">
|
|
49
|
+
<button class="btn btn--md btn--secondary" id="p1">下方</button>
|
|
50
|
+
<button class="btn btn--md btn--secondary" id="p2">上方</button>
|
|
51
|
+
<button class="btn btn--md btn--secondary" id="p3">右侧</button>
|
|
52
|
+
<button class="btn btn--md btn--secondary" id="p4">左侧</button>
|
|
53
|
+
</div>
|
|
54
|
+
<p class="note">方向用<b>逻辑方向</b>写的(<code>end</code>/<code>start</code>),所以在 RTL 下会自动镜像。</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<h2>2 · 可以放交互内容(这是与 tooltip 的分界)</h2>
|
|
58
|
+
<div class="demo">
|
|
59
|
+
<button class="btn btn--md btn--primary" id="p5">筛选条件</button>
|
|
60
|
+
<p class="note">点开后焦点<b>直接进入</b>浮层,可以立刻操作 —— 键盘用户不用先 Tab 一路走过来。</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<h2>3 · 带表单的浮层</h2>
|
|
64
|
+
<div class="demo">
|
|
65
|
+
<button class="btn btn--md btn--secondary" id="p6">快速改备注</button>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<h2>怎么选?</h2>
|
|
69
|
+
<div class="demo">
|
|
70
|
+
<table>
|
|
71
|
+
<tr><th>组件</th><th>能放交互内容</th><th>背景是否锁死</th><th>用在哪</th></tr>
|
|
72
|
+
<tr><td><code>tooltip</code></td><td>❌ 只能纯文字</td><td>—</td><td>补充说明、术语解释</td></tr>
|
|
73
|
+
<tr><td><b><code>popover</code></b></td><td>✅</td><td><b>不锁</b></td><td>筛选、快捷设置、补充表单</td></tr>
|
|
74
|
+
<tr><td><code>dialog</code></td><td>✅</td><td><b>锁死</b></td><td>危险确认、必须处理完的事</td></tr>
|
|
75
|
+
</table>
|
|
76
|
+
<p class="note">记不住就问一句:<b>「用户需要在里面操作吗?」</b>不需要 → tooltip;需要但不打断 → popover;必须先处理完 → dialog。</p>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<h2>反例 · 不该这样用</h2>
|
|
80
|
+
<div class="demo bad-demo">
|
|
81
|
+
<button class="btn btn--md btn--danger" id="bad1">删除这份报告</button>
|
|
82
|
+
<p class="note bad">
|
|
83
|
+
<b>危险操作用 popover 是错的。</b><br>
|
|
84
|
+
二次确认必须用 <code>dialog</code>(模态 + focus trap)——
|
|
85
|
+
用了 popover,背景仍可点,用户很容易"顺手点掉别的东西"。<br>
|
|
86
|
+
<b>另外:</b>popover 里只放一行说明文字也不对 —— 那是 tooltip 的活。
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<script src="popover.js"></script>
|
|
91
|
+
<script>
|
|
92
|
+
/* 演示用内容。真实项目里这些 HTML 应该在服务端或模板里生成。 */
|
|
93
|
+
function demo(btnId, place, html, label) {
|
|
94
|
+
var el = document.getElementById(btnId);
|
|
95
|
+
if (!el) return;
|
|
96
|
+
window.Popover.attach(el, { place: place || 'bottom', html: html, label: label || '示例' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
demo('p1', 'bottom',
|
|
100
|
+
'<p class="popover__title">出现在下方</p>' +
|
|
101
|
+
'<p class="popover__desc">最常用。空间不够时应该往上翻。</p>', '方向示例');
|
|
102
|
+
|
|
103
|
+
demo('p2', 'top',
|
|
104
|
+
'<p class="popover__title">出现在上方</p>' +
|
|
105
|
+
'<p class="popover__desc">靠近屏幕底部时用这个方向。</p>', '方向示例');
|
|
106
|
+
|
|
107
|
+
demo('p3', 'end',
|
|
108
|
+
'<p class="popover__title">出现在右侧</p>' +
|
|
109
|
+
'<p class="popover__desc">横向空间富裕时用。</p>', '方向示例');
|
|
110
|
+
|
|
111
|
+
demo('p4', 'start',
|
|
112
|
+
'<p class="popover__title">出现在左侧</p>' +
|
|
113
|
+
'<p class="popover__desc">横向空间富裕时用。</p>', '方向示例');
|
|
114
|
+
|
|
115
|
+
demo('p5', 'bottom',
|
|
116
|
+
'<p class="popover__title">筛选条件</p>' +
|
|
117
|
+
'<p class="popover__desc">下面这些控件在浮层里直接操作。</p>' +
|
|
118
|
+
'<div class="row" style="gap:var(--sp-2)">' +
|
|
119
|
+
'<button class="btn btn--sm btn--secondary">近 7 天</button>' +
|
|
120
|
+
'<button class="btn btn--sm btn--secondary">近 30 天</button>' +
|
|
121
|
+
'</div>' +
|
|
122
|
+
'<div class="popover__footer">' +
|
|
123
|
+
'<button class="btn btn--sm btn--ghost">取消</button>' +
|
|
124
|
+
'<button class="btn btn--sm btn--primary">应用</button>' +
|
|
125
|
+
'</div>', '筛选条件');
|
|
126
|
+
|
|
127
|
+
demo('p6', 'bottom',
|
|
128
|
+
'<p class="popover__title">改备注</p>' +
|
|
129
|
+
'<p class="popover__desc">焦点打开时已经在输入框里。</p>' +
|
|
130
|
+
'<input class="input" type="text" placeholder="写点什么…" ' +
|
|
131
|
+
'style="width:100%;height:36px;padding:0 var(--sp-2);' +
|
|
132
|
+
'border:1px solid var(--border-decor);border-radius:var(--r-sm);' +
|
|
133
|
+
'background:var(--surface);color:var(--text-primary)">' +
|
|
134
|
+
'<div class="popover__footer">' +
|
|
135
|
+
'<button class="btn btn--sm btn--ghost">取消</button>' +
|
|
136
|
+
'<button class="btn btn--sm btn--primary">保存</button>' +
|
|
137
|
+
'</div>', '快速改备注');
|
|
138
|
+
|
|
139
|
+
/* 反例:故意用 popover 做危险确认(不要在真实项目里这么写) */
|
|
140
|
+
demo('bad1', 'bottom',
|
|
141
|
+
'<p class="popover__title">确定删除?</p>' +
|
|
142
|
+
'<p class="popover__desc">这个操作不可撤销。</p>' +
|
|
143
|
+
'<div class="popover__footer">' +
|
|
144
|
+
'<button class="btn btn--sm btn--ghost">取消</button>' +
|
|
145
|
+
'<button class="btn btn--sm btn--danger">删除</button>' +
|
|
146
|
+
'</div>', '反面示例');
|
|
147
|
+
</script>
|
|
148
|
+
</main>
|
|
149
|
+
</body>
|
|
150
|
+
</html>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
popover — 气泡卡片
|
|
3
|
+
============================================================================
|
|
4
|
+
为什么需要它(通行系统的必备件)
|
|
5
|
+
---------------------------------------------------------------------------
|
|
6
|
+
三种"浮层"职责不同,不能互相顶替:
|
|
7
|
+
|
|
8
|
+
┌──────────┬────────────────────────────────────┬──────────┐
|
|
9
|
+
│ │ 能不能放交互内容 │ 焦点管理 │
|
|
10
|
+
├──────────┼────────────────────────────────────┼──────────┤
|
|
11
|
+
│ tooltip │ ❌ 只能纯文字 │ 无 │
|
|
12
|
+
│ popover │ ✅ 表单、按钮、列表都行 │ **焦点进入**│
|
|
13
|
+
│ dialog │ ✅ 但**模态**(背景锁死) │ focus trap│
|
|
14
|
+
└──────────┴────────────────────────────────────┴──────────┘
|
|
15
|
+
|
|
16
|
+
⭐ 判据(记不住就问这句):
|
|
17
|
+
「用户需要**在里面操作**吗?」
|
|
18
|
+
不需要 → tooltip
|
|
19
|
+
需要,但**不打断**当前上下文 → **popover**
|
|
20
|
+
需要,且**必须先处理完**才能继续 → dialog
|
|
21
|
+
|
|
22
|
+
参照:Radix Popover / MUI Popover / Carbon Popover
|
|
23
|
+
|
|
24
|
+
设计要点
|
|
25
|
+
---------------------------------------------------------------------------
|
|
26
|
+
① **非模态**:背景仍可交互(这是与 dialog 的根本区别)
|
|
27
|
+
② 焦点**进入**浮层(键盘用户能操作里面的内容)
|
|
28
|
+
③ Esc 关闭 + 焦点归还
|
|
29
|
+
④ 点击**外部**关闭,但**点击浮层内部不关**(常见 bug)
|
|
30
|
+
⑤ 箭头:指向触发元素,且跟随位置变化
|
|
31
|
+
⑥ 动画尊重 prefers-reduced-motion
|
|
32
|
+
============================================================================ */
|
|
33
|
+
|
|
34
|
+
/* ---------------------------------------------------------------------------
|
|
35
|
+
触发器
|
|
36
|
+
--------------------------------------------------------------------------- */
|
|
37
|
+
.popover-anchor {
|
|
38
|
+
position: relative; /* 箭头与浮层的定位上下文 */
|
|
39
|
+
display: inline-block;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ---------------------------------------------------------------------------
|
|
43
|
+
浮层本体
|
|
44
|
+
--------------------------------------------------------------------------- */
|
|
45
|
+
.popover {
|
|
46
|
+
position: absolute;
|
|
47
|
+
z-index: var(--z-popover, 60);
|
|
48
|
+
min-width: var(--popover-min-w, 200px);
|
|
49
|
+
max-width: var(--popover-max-w, 320px);
|
|
50
|
+
padding: var(--popover-p, var(--sp-3));
|
|
51
|
+
background: var(--surface-raised);
|
|
52
|
+
color: var(--text-primary);
|
|
53
|
+
border: 1px solid var(--border-decor);
|
|
54
|
+
border-radius: var(--r-md);
|
|
55
|
+
box-shadow: var(--shadow-2);
|
|
56
|
+
/* ⚠️ 不用 overflow:hidden —— 会把箭头切掉 */
|
|
57
|
+
opacity: 0;
|
|
58
|
+
visibility: hidden;
|
|
59
|
+
transition: opacity var(--popover-dur, 140ms) var(--ease-out),
|
|
60
|
+
transform var(--popover-dur, 140ms) var(--ease-out);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* 打开态 */
|
|
64
|
+
.popover[data-open='true'] {
|
|
65
|
+
opacity: 1;
|
|
66
|
+
visibility: visible;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ---------------------------------------------------------------------------
|
|
70
|
+
四个方向(浮层相对锚点的位置)
|
|
71
|
+
⚠️ 用逻辑上/下/左/右而不是物理方向,这样 RTL 下自动镜像。
|
|
72
|
+
--------------------------------------------------------------------------- */
|
|
73
|
+
.popover--bottom { top: calc(100% + var(--popover-gap, 8px)); inset-inline-start: 50%; transform: translateX(-50%); }
|
|
74
|
+
.popover--top { bottom: calc(100% + var(--popover-gap, 8px)); inset-inline-start: 50%; transform: translateX(-50%); }
|
|
75
|
+
.popover--end { inset-inline-start: 100%; margin-inline-start: var(--popover-gap, 8px); top: 50%; transform: translateY(-50%); }
|
|
76
|
+
.popover--start { inset-inline-end: 100%; margin-inline-end: var(--popover-gap, 8px); top: 50%; transform: translateY(-50%); }
|
|
77
|
+
|
|
78
|
+
/* 打开时的位移(去掉 translate 的那部分,只留"进入"方向) */
|
|
79
|
+
.popover--bottom[data-open='true'] { transform: translateX(-50%) translateY(0); }
|
|
80
|
+
.popover--top[data-open='true'] { transform: translateX(-50%) translateY(0); }
|
|
81
|
+
.popover--end[data-open='true'] { transform: translateY(-50%) translateX(0); }
|
|
82
|
+
.popover--start[data-open='true'] { transform: translateY(-50%) translateX(0); }
|
|
83
|
+
|
|
84
|
+
/* ============================================================================
|
|
85
|
+
箭头
|
|
86
|
+
============================================================================
|
|
87
|
+
⭐ 箭头用**两个叠放的方块**而不是 border 三角:
|
|
88
|
+
border 三角无法跟随圆角边框,且在深色背景下边缘会有锯齿。
|
|
89
|
+
============================================================================ */
|
|
90
|
+
.popover__arrow {
|
|
91
|
+
position: absolute;
|
|
92
|
+
width: 10px;
|
|
93
|
+
height: 10px;
|
|
94
|
+
background: var(--surface-raised);
|
|
95
|
+
border: 1px solid var(--border-decor);
|
|
96
|
+
transform: rotate(45deg);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.popover--bottom .popover__arrow {
|
|
100
|
+
top: -6px;
|
|
101
|
+
inset-inline-start: 50%;
|
|
102
|
+
margin-inline-start: -5px;
|
|
103
|
+
border-inline-end: none;
|
|
104
|
+
border-bottom: none;
|
|
105
|
+
}
|
|
106
|
+
.popover--top .popover__arrow {
|
|
107
|
+
bottom: -6px;
|
|
108
|
+
inset-inline-start: 50%;
|
|
109
|
+
margin-inline-start: -5px;
|
|
110
|
+
border-inline-start: none;
|
|
111
|
+
border-top: none;
|
|
112
|
+
}
|
|
113
|
+
.popover--end .popover__arrow {
|
|
114
|
+
inset-inline-start: -6px;
|
|
115
|
+
top: 50%;
|
|
116
|
+
margin-top: -5px;
|
|
117
|
+
border-inline-end: none;
|
|
118
|
+
border-top: none;
|
|
119
|
+
}
|
|
120
|
+
.popover--start .popover__arrow {
|
|
121
|
+
inset-inline-end: -6px;
|
|
122
|
+
top: 50%;
|
|
123
|
+
margin-top: -5px;
|
|
124
|
+
border-inline-start: none;
|
|
125
|
+
border-bottom: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ============================================================================
|
|
129
|
+
结构件
|
|
130
|
+
============================================================================ */
|
|
131
|
+
.popover__title {
|
|
132
|
+
font-size: var(--fs-md);
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
margin: 0 0 var(--sp-1);
|
|
135
|
+
}
|
|
136
|
+
.popover__desc {
|
|
137
|
+
font-size: var(--fs-sm);
|
|
138
|
+
color: var(--text-secondary);
|
|
139
|
+
margin: 0 0 var(--sp-3);
|
|
140
|
+
}
|
|
141
|
+
/* 最后一个元素不要额外的下边距 */
|
|
142
|
+
.popover__desc:last-child { margin-bottom: 0; }
|
|
143
|
+
|
|
144
|
+
.popover__footer {
|
|
145
|
+
display: flex;
|
|
146
|
+
gap: var(--sp-2);
|
|
147
|
+
justify-content: flex-end;
|
|
148
|
+
margin-top: var(--sp-3);
|
|
149
|
+
padding-top: var(--sp-3);
|
|
150
|
+
border-top: 1px solid var(--border-decor);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* 关闭态下的内容不可聚焦(否则 Tab 会跑到"看不见"的地方) */
|
|
154
|
+
.popover:not([data-open='true']) * { pointer-events: none; }
|
|
155
|
+
|
|
156
|
+
/* ============================================================================
|
|
157
|
+
减少动态效果
|
|
158
|
+
⭐ 只去**位移**,保留 opacity —— 让"出现了"这件事仍然可感知。
|
|
159
|
+
⚠️ 与 progress 的教训一致:不要留下任何"位置暗示"(如半截色块)。
|
|
160
|
+
============================================================================ */
|
|
161
|
+
@media (prefers-reduced-motion: reduce) {
|
|
162
|
+
.popover {
|
|
163
|
+
transition: opacity var(--popover-dur, 100ms) linear;
|
|
164
|
+
transform: none !important;
|
|
165
|
+
}
|
|
166
|
+
.popover[data-open='true'] { transform: none !important; }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ============================================================================
|
|
170
|
+
焦点样式
|
|
171
|
+
============================================================================
|
|
172
|
+
⚠️ 本库虽然有全局 `:focus-visible` 兜底(focus-ring.css),
|
|
173
|
+
但契约要求**组件自己声明** ——
|
|
174
|
+
原因:全局兜底可能被别的样式覆盖,而"浮层里的控件必须可见焦点"
|
|
175
|
+
是这个组件的**硬需求**(键盘用户要在里面操作)。
|
|
176
|
+
============================================================================ */
|
|
177
|
+
.popover :focus-visible,
|
|
178
|
+
.popover-anchor :focus-visible {
|
|
179
|
+
outline: 2px solid var(--accent);
|
|
180
|
+
outline-offset: 2px;
|
|
181
|
+
border-radius: var(--r-xs);
|
|
182
|
+
}
|
|
183
|
+
/* 浮层容器自身获得焦点时(无可聚焦内容的情况)也要有焦点环 */
|
|
184
|
+
.popover[tabindex='-1']:focus-visible {
|
|
185
|
+
outline: 2px solid var(--accent);
|
|
186
|
+
outline-offset: 2px;
|
|
187
|
+
}
|