agentcss 0.11.10 → 0.11.11
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/page.css +71 -0
- package/02-primitives/button/demo.html +5 -15
- package/02-primitives/card/demo.html +3 -13
- package/02-primitives/choice/demo.html +1 -0
- package/02-primitives/combobox/demo.html +38 -0
- package/02-primitives/date-range/demo.html +36 -0
- package/02-primitives/input/demo.html +5 -16
- package/02-primitives/popover/README.md +15 -0
- package/02-primitives/popover/demo.html +45 -0
- package/02-primitives/progress/demo.html +25 -0
- package/02-primitives/select/demo.html +46 -0
- package/02-primitives/skeleton/demo.html +28 -0
- package/03-patterns/content/demo.html +20 -0
- package/03-patterns/drawer/demo.html +36 -0
- package/03-patterns/dropdown/demo.html +39 -0
- package/03-patterns/list/demo.html +48 -0
- package/03-patterns/nav/demo.html +34 -1
- package/03-patterns/pagination/demo.html +40 -0
- package/03-patterns/reveal/demo.html +24 -0
- package/03-patterns/states/demo.html +25 -1
- package/03-patterns/tooltip/demo.html +27 -0
- package/03-patterns/tree/demo.html +43 -0
- package/04-recipes/analysis-report/demo.html +34 -0
- package/04-recipes/data-showcase/README.md +1 -0
- package/04-recipes/data-showcase/demo.html +29 -0
- package/04-recipes/detail-page/demo.html +28 -0
- package/04-recipes/report/demo.html +24 -0
- package/04-recipes/table/README.md +22 -0
- package/04-recipes/table/demo.html +39 -0
- package/CHANGELOG.md +68 -0
- package/CONTRIBUTING.md +2 -1
- package/ai/components.json +38 -10
- package/dist/01-tokens/page.css +1 -1
- package/dist/COSTS.md +1 -1
- package/index.html +1 -1
- package/package.json +1 -1
package/01-tokens/page.css
CHANGED
|
@@ -216,6 +216,77 @@
|
|
|
216
216
|
background: var(--surface-sunken);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/* ============================================================================
|
|
220
|
+
6. 对错对照 `.cmp`(并排两栏:错写法 / 本库写法)
|
|
221
|
+
----------------------------------------------------------------------------
|
|
222
|
+
为什么要收编(量过才写的,不是凭印象):
|
|
223
|
+
3 个页面(button / card / input)各自写了一遍,**语义完全相同**,
|
|
224
|
+
但参数各是各的 —— `grid-template-columns` 都是 1.55fr 1fr(这一条一致,
|
|
225
|
+
说明它确实是个固定意图),而 gap 是 sp-4 还是 sp-5、断点是 640 还是 700,
|
|
226
|
+
三页三种。这不是"各页局部排布",是同一个东西的无意分叉。
|
|
227
|
+
判据与 `.row` 的区别就在这里:
|
|
228
|
+
`.row` 15 页 14 种写法 —— **语义**都不一样(有的是工具条、有的是按钮排),不收;
|
|
229
|
+
`.cmp` 3 页 3 种写法 —— **语义**一样,差的只是手抖,收。
|
|
230
|
+
另外 21 个页面缺"能看见的反例",补上之后若不收编,就是 24 份副本。
|
|
231
|
+
|
|
232
|
+
为什么列宽是 1fr 1fr(而不是原来那三页的 1.55fr 1fr):
|
|
233
|
+
那三页的左栏是"一大段解释 + 示例",右栏只是一句话,所以给左栏 1.55。
|
|
234
|
+
现在每一栏都是"一个小示例 + 一句理由",两栏等宽才不会让人以为
|
|
235
|
+
某一栏更重要。真需要不等宽时用 `.cmp--wide-first`,别在页面里重写栅格。
|
|
236
|
+
========================================================================= */
|
|
237
|
+
.page .cmp {
|
|
238
|
+
display: grid;
|
|
239
|
+
/* ⚠️ 必须是 `minmax(0, 1fr)`,不能写 `1fr`:`1fr` 的下限是 `auto`(= 内容最小宽度),
|
|
240
|
+
一栏里只要有不换行的长串(`.ls-clip` 的 nowrap、`.ar-long` 的股票代码、宽表格),
|
|
241
|
+
两栏就一起被顶宽 ⇒ **整页**横向溢出。
|
|
242
|
+
实测代价:本库 4 个页面在 320px / 834px / RTL 阿拉伯语膨胀后溢出 81–155px,
|
|
243
|
+
`zoom-reflow` `rtl-render` `responsive` 三道门禁同时红。
|
|
244
|
+
——这条正是 `04-recipes/analysis-report` 那一页在教别人的东西,
|
|
245
|
+
我写这个收编的时候照着旧三页抄了 `1fr`,把要讲的坑又踩了一遍。 */
|
|
246
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
247
|
+
gap: var(--sp-4);
|
|
248
|
+
/* 不写 align-items: start —— 两栏等高(stretch)边框才对得齐;
|
|
249
|
+
start 会让短的那栏提前结束,看起来像"没写完"。 */
|
|
250
|
+
}
|
|
251
|
+
.page .cmp--wide-first {
|
|
252
|
+
grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
|
|
253
|
+
}
|
|
254
|
+
/* 断点 640 而不是 720:文档页里的活标本 iframe 实测只有 734px 宽
|
|
255
|
+
(guide/xxx.html 里那个框),720 的断点意味着"再窄一点就叠成单栏"——
|
|
256
|
+
而叠成单栏,"并排"这件事就不存在了。留出 90px 余量。 */
|
|
257
|
+
@media (max-width: 640px) {
|
|
258
|
+
.page .cmp,
|
|
259
|
+
.page .cmp--wide-first,
|
|
260
|
+
.page .cmp--clip { grid-template-columns: minmax(0, 1fr); }
|
|
261
|
+
}
|
|
262
|
+
.page .cmp__pane {
|
|
263
|
+
padding: var(--sp-4);
|
|
264
|
+
border: 1px solid var(--border-decor);
|
|
265
|
+
border-radius: var(--r-md);
|
|
266
|
+
background: var(--surface-sunken);
|
|
267
|
+
/* 栅格项默认 min-width:auto ⇒ 内容最小宽度照样能把栏顶开。 */
|
|
268
|
+
min-width: 0;
|
|
269
|
+
}
|
|
270
|
+
/* 内容本身就是"宽、且故意不收缩"的页(超长不换行串 / 宽表格)用这个修饰符。
|
|
271
|
+
⚠️ 为什么不写进 `.cmp__pane` 全局:`popover` / `tooltip` / `dropdown` 的对照块里
|
|
272
|
+
气泡是绝对定位伸出栏外的,`overflow: hidden` 会把气泡直接裁掉 ——
|
|
273
|
+
那几页的演示就没了。裁的是"演示内容越出栏框",不是"栏内的布局算错":
|
|
274
|
+
栏内列的宽度仍按内容算,读者看到的还是被撑宽的那一栏。 */
|
|
275
|
+
.page .cmp--clip .cmp__pane { overflow: hidden; }
|
|
276
|
+
.page .cmp__pane > h3 {
|
|
277
|
+
margin: 0 0 var(--sp-3);
|
|
278
|
+
font-size: var(--fs-sm);
|
|
279
|
+
font-weight: 500;
|
|
280
|
+
}
|
|
281
|
+
.page .cmp__bad > h3 { color: var(--danger); }
|
|
282
|
+
.page .cmp__good > h3 { color: var(--success); }
|
|
283
|
+
.page .cmp__why {
|
|
284
|
+
margin: var(--sp-3) 0 0;
|
|
285
|
+
font-size: var(--fs-xs);
|
|
286
|
+
line-height: 1.6;
|
|
287
|
+
color: var(--text-secondary);
|
|
288
|
+
}
|
|
289
|
+
|
|
219
290
|
/* ============================================================================
|
|
220
291
|
反模式(本文件自己的边界)
|
|
221
292
|
----------------------------------------------------------------------------
|
|
@@ -16,16 +16,6 @@
|
|
|
16
16
|
.cell p { margin: 0 0 var(--sp-4); font-size: var(--fs-xs);
|
|
17
17
|
color: var(--text-tertiary); line-height: 1.55; }
|
|
18
18
|
.row { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }
|
|
19
|
-
.cmp { display: grid; gap: var(--sp-4); grid-template-columns: 1.55fr 1fr; }
|
|
20
|
-
@media (max-width: 640px) {.cmp { grid-template-columns: 1fr; }
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
.cmp>div { padding: var(--sp-4); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
24
|
-
.bad { background: var(--surface-sunken); }
|
|
25
|
-
.good { background: var(--surface-sunken); }
|
|
26
|
-
.cmp h3 { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); font-weight: 500; }
|
|
27
|
-
.bad h3 { color: var(--danger); }
|
|
28
|
-
.good h3 { color: var(--success); }
|
|
29
19
|
/* 反例演示用 */
|
|
30
20
|
.btn--naive { height: 44px; padding: 0 var(--sp-5); border: none; border-radius: var(--r-sm);
|
|
31
21
|
background: var(--accent); color: #fff; cursor: pointer; font-size: var(--fs-sm);
|
|
@@ -129,27 +119,27 @@
|
|
|
129
119
|
</p>
|
|
130
120
|
|
|
131
121
|
<h2 class="rule-bottom">对比:常见的错写法 vs 本库写法</h2>
|
|
132
|
-
<div class="cmp">
|
|
133
|
-
<div class="
|
|
122
|
+
<div class="cmp cmp--wide-first">
|
|
123
|
+
<div class="cmp__pane cmp__bad">
|
|
134
124
|
<h3>常见写法(某项目 现状)</h3>
|
|
135
125
|
<div class="row">
|
|
136
126
|
<button class="btn--naive">hover 看我</button>
|
|
137
127
|
<button class="btn--naive" disabled>disabled 看我</button>
|
|
138
128
|
</div>
|
|
139
|
-
<p
|
|
129
|
+
<p class="cmp__why">
|
|
140
130
|
<code>filter: brightness(1.08)</code> —— hover 色是算出来的,无法验证对比度,不同底色表现不一致。<br>
|
|
141
131
|
<code>transform: scale(1.05)</code> —— 按钮"长大",廉价感。<br>
|
|
142
132
|
<code>opacity: 0.5</code> —— 对比度变成随机数。<br>
|
|
143
133
|
<code>transition: all</code> —— 所有属性一起动画,性能和观感失控。
|
|
144
134
|
</p>
|
|
145
135
|
</div>
|
|
146
|
-
<div class="
|
|
136
|
+
<div class="cmp__pane cmp__good">
|
|
147
137
|
<h3>本库写法</h3>
|
|
148
138
|
<div class="row">
|
|
149
139
|
<button class="btn btn--md btn--primary">hover 看我</button>
|
|
150
140
|
<button class="btn btn--md btn--primary" disabled>disabled 看我</button>
|
|
151
141
|
</div>
|
|
152
|
-
<p
|
|
142
|
+
<p class="cmp__why">
|
|
153
143
|
明确的 <code>--accent-hover</code> 色值,实测 7.73 AAA。<br>
|
|
154
144
|
active 只下沉 1px。<br>
|
|
155
145
|
disabled 用明确的低对比色组,仍可读。<br>
|
|
@@ -12,16 +12,6 @@
|
|
|
12
12
|
<style>
|
|
13
13
|
p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6; }
|
|
14
14
|
.grid3 { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
|
|
15
|
-
.cmp { display: grid; gap: var(--sp-5); grid-template-columns: 1.55fr 1fr; }
|
|
16
|
-
@media (max-width: 700px) {.cmp { grid-template-columns: 1fr; }
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
.cmp>div { padding: var(--sp-5); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
20
|
-
.bad { background: var(--surface-sunken); }
|
|
21
|
-
.good { background: var(--surface-sunken); }
|
|
22
|
-
.cmp h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); font-weight: 500; }
|
|
23
|
-
.bad h3 { color: var(--danger); }
|
|
24
|
-
.good h3 { color: var(--success); }
|
|
25
15
|
.cmp .stack { display: flex; flex-direction: column; gap: var(--sp-3); }
|
|
26
16
|
/* ---- 反例卡片:hover 时改 border-width,引起回流 ---- */
|
|
27
17
|
.card-bad { position: relative; background: var(--surface);
|
|
@@ -52,8 +42,8 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
52
42
|
下面<strong>故意放大到 4px</strong>,好让你一次看清楚。请分别把鼠标移到两张卡上。
|
|
53
43
|
</div>
|
|
54
44
|
|
|
55
|
-
<div class="cmp">
|
|
56
|
-
<div class="
|
|
45
|
+
<div class="cmp cmp--wide-first">
|
|
46
|
+
<div class="cmp__pane cmp__bad">
|
|
57
47
|
<h3>反例:hover 改 border-width</h3>
|
|
58
48
|
<p class="small" style="margin:0 0 var(--sp-3)">
|
|
59
49
|
移上来 —— 里面那把尺子和文字整体往下挪了,卡片也变高了。
|
|
@@ -71,7 +61,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
71
61
|
</div>
|
|
72
62
|
</div>
|
|
73
63
|
|
|
74
|
-
<div class="
|
|
64
|
+
<div class="cmp__pane cmp__good">
|
|
75
65
|
<h3>本库:transform + box-shadow</h3>
|
|
76
66
|
<p class="small" style="margin:0 0 var(--sp-3)">
|
|
77
67
|
移上来 —— 卡片整体抬起 2px,<strong>里面的内容一个像素都不动</strong>,
|
|
@@ -188,6 +188,7 @@ p.small { font-size: var(--fs-xs); color: var(--text-tertiary); line-height: 1.6
|
|
|
188
188
|
|
|
189
189
|
<h2 class="rule-bottom">反例:用 display:none 隐藏原生控件</h2>
|
|
190
190
|
<div class="box bad">
|
|
191
|
+
<h3>反例:把原生勾选框 <code>display:none</code> 藏掉</h3>
|
|
191
192
|
<div class="choice choice--checkbox">
|
|
192
193
|
<input class="choice__input" type="checkbox" style="display:none" id="bad1">
|
|
193
194
|
<label class="choice__label" for="bad1">
|
|
@@ -19,6 +19,15 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
19
19
|
font-family: var(--mono); font-size: var(--fs-xs);
|
|
20
20
|
color: var(--text-secondary); min-height: 2.8em;
|
|
21
21
|
}
|
|
22
|
+
/* 一次性演示用:把选项摆成静态列表(组件里它是浮层,这里要它躺着) */
|
|
23
|
+
.cb-demo { display: grid; gap: var(--sp-1); }
|
|
24
|
+
.cb-demo__q { font-size: var(--fs-sm); color: var(--text-tertiary); padding: 0 0 var(--sp-1); }
|
|
25
|
+
.cb-demo__list {
|
|
26
|
+
list-style: none; margin: 0; padding: var(--sp-1);
|
|
27
|
+
background: var(--surface-raised);
|
|
28
|
+
border: 1px solid var(--border-decor);
|
|
29
|
+
border-radius: var(--r-sm);
|
|
30
|
+
}
|
|
22
31
|
</style>
|
|
23
32
|
</head>
|
|
24
33
|
<body class="page page">
|
|
@@ -66,6 +75,35 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
66
75
|
</div>
|
|
67
76
|
</div>
|
|
68
77
|
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
81
|
+
<div class="cmp">
|
|
82
|
+
<div class="cmp__pane cmp__bad">
|
|
83
|
+
<h3>❌ 常见写法:结果不高亮</h3>
|
|
84
|
+
<div class="cb-demo" aria-hidden="true">
|
|
85
|
+
<div class="cb-demo__q">半导体</div>
|
|
86
|
+
<ul class="cb-demo__list">
|
|
87
|
+
<li class="combo__opt">设备</li>
|
|
88
|
+
<li class="combo__opt">材料</li>
|
|
89
|
+
<li class="combo__opt">封测</li>
|
|
90
|
+
</ul>
|
|
91
|
+
</div>
|
|
92
|
+
<p class="cmp__why">输入"半导体"之后三条都留下了,但三条长得一模一样 —— 用户说不出<b>凭什么</b>是这三条,只会觉得"顺序乱了"。</p>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="cmp__pane cmp__good">
|
|
95
|
+
<h3>✅ 本库:命中片段高亮</h3>
|
|
96
|
+
<div class="cb-demo" aria-hidden="true">
|
|
97
|
+
<div class="cb-demo__q">半导体</div>
|
|
98
|
+
<ul class="cb-demo__list">
|
|
99
|
+
<li class="combo__opt"><mark>半导体</mark>设备</li>
|
|
100
|
+
<li class="combo__opt"><mark>半导体</mark>材料</li>
|
|
101
|
+
<li class="combo__opt"><mark>半导体</mark>封测</li>
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
<p class="cmp__why">高亮用的是本库的 <code>combo__opt mark</code>:accent 色 + 下划线,<b>不用</b>浏览器默认的黄底 —— 黄底与文字的对比度不可控,深色主题下更糟。</p>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
69
107
|
</main>
|
|
70
108
|
|
|
71
109
|
<script src="combobox.js"></script>
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
font-family: var(--mono); font-size: var(--fs-xs);
|
|
19
19
|
color: var(--text-secondary); min-height: 2.8em;
|
|
20
20
|
}
|
|
21
|
+
/* 反例专用:只改颜色 */
|
|
22
|
+
.dr-bad-field .drange__input { border-color: var(--danger); }
|
|
21
23
|
</style>
|
|
22
24
|
</head>
|
|
23
25
|
<body class="page page">
|
|
@@ -74,6 +76,40 @@
|
|
|
74
76
|
<div class="log" id="log2">(试着填一个 2019 年或 2027 年 ⇒ 会报错)</div>
|
|
75
77
|
</div>
|
|
76
78
|
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
82
|
+
<div class="cmp">
|
|
83
|
+
<div class="cmp__pane cmp__bad">
|
|
84
|
+
<h3>❌ 常见写法:错误只改边框颜色</h3>
|
|
85
|
+
<div class="drange" aria-hidden="true">
|
|
86
|
+
<div class="drange__field">
|
|
87
|
+
<label class="drange__label" for="dr-b1">开始</label>
|
|
88
|
+
<input id="dr-b1" class="drange__input" type="date" value="2026-01-10" readonly tabindex="-1">
|
|
89
|
+
</div>
|
|
90
|
+
<div class="drange__field dr-bad-field">
|
|
91
|
+
<label class="drange__label" for="dr-b2">结束</label>
|
|
92
|
+
<input id="dr-b2" class="drange__input" type="date" value="2026-01-03" readonly tabindex="-1">
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<p class="cmp__why">结束日期早于开始日期,界面上只是"红了一圈":色觉障碍用户看不出来,灰度打印也看不出来,而且<b>没有一句话</b>告诉你错在哪。</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="cmp__pane cmp__good">
|
|
98
|
+
<h3>✅ 本库:左侧竖线 + 一句话说明</h3>
|
|
99
|
+
<div class="drange" aria-hidden="true">
|
|
100
|
+
<div class="drange__field">
|
|
101
|
+
<label class="drange__label" for="dr-g1">开始</label>
|
|
102
|
+
<input id="dr-g1" class="drange__input" type="date" value="2026-01-10" readonly tabindex="-1">
|
|
103
|
+
</div>
|
|
104
|
+
<div class="drange__field">
|
|
105
|
+
<label class="drange__label" for="dr-g2">结束</label>
|
|
106
|
+
<input id="dr-g2" class="drange__input" type="date" value="2026-01-03" readonly tabindex="-1">
|
|
107
|
+
</div>
|
|
108
|
+
<div class="drange__error">结束日期早于开始日期</div>
|
|
109
|
+
</div>
|
|
110
|
+
<p class="cmp__why">形状(左侧竖线)与文字一起承担信息,颜色只是辅助 —— 这是 WCAG 1.4.1 的要求。同时 JS 会给两个字段打 <code>aria-invalid</code> 并用 <code>aria-describedby</code> 指到这句话。</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
77
113
|
</main>
|
|
78
114
|
|
|
79
115
|
<script src="date-range.js"></script>
|
|
@@ -22,17 +22,6 @@
|
|
|
22
22
|
.cell h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-sm); font-weight: 500; }
|
|
23
23
|
.cell p { margin: 0 0 var(--sp-3); font-size: var(--fs-xs);
|
|
24
24
|
color: var(--text-tertiary); line-height: 1.55; }
|
|
25
|
-
.cmp { display: grid; gap: var(--sp-4); grid-template-columns: 1.55fr 1fr; }
|
|
26
|
-
@media (max-width: 620px) {.cmp { grid-template-columns: 1fr; }
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
.cmp>div { padding: var(--sp-4); border-radius: var(--r-md); border: 1px solid var(--border-decor); }
|
|
30
|
-
.bad { background: var(--surface-sunken); }
|
|
31
|
-
.good { background: var(--surface-sunken); }
|
|
32
|
-
.cmp h3 { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); font-weight: 500; }
|
|
33
|
-
.bad h3 { color: var(--danger); }
|
|
34
|
-
.good h3 { color: var(--success); }
|
|
35
|
-
.cmp p { font-size: var(--fs-xs); margin: var(--sp-3) 0 0; line-height: 1.6; }
|
|
36
25
|
/* 反例演示 */
|
|
37
26
|
.bad-input { width: 100%; box-sizing: border-box; height: 44px; padding: 0 var(--sp-3);
|
|
38
27
|
font-size: 14px; border: 1px solid #ccc; border-radius: var(--r-sm); }
|
|
@@ -118,8 +107,8 @@
|
|
|
118
107
|
</div>
|
|
119
108
|
|
|
120
109
|
<h2 class="rule-bottom">对比:常见写法 vs 本库写法</h2>
|
|
121
|
-
<div class="cmp">
|
|
122
|
-
<div class="
|
|
110
|
+
<div class="cmp cmp--wide-first">
|
|
111
|
+
<div class="cmp__pane cmp__bad">
|
|
123
112
|
<h3>常见写法</h3>
|
|
124
113
|
<input class="bad-input err" value="abc" aria-hidden="true" tabindex="-1">
|
|
125
114
|
<!-- 🔴 加 aria-hidden。
|
|
@@ -129,21 +118,21 @@
|
|
|
129
118
|
反例对人要可见(不能 display:none),
|
|
130
119
|
但对辅助技术应当隐藏 —— aria-hidden 正合适。 -->
|
|
131
120
|
<p class="bad-err" aria-hidden="true">格式错误</p>
|
|
132
|
-
<p>
|
|
121
|
+
<p class="cmp__why">
|
|
133
122
|
<code>placeholder 当 label</code> —— 一输入就消失<br>
|
|
134
123
|
<code>只把边框变红</code> —— WCAG 1.4.1 禁止只靠颜色传达信息<br>
|
|
135
124
|
<code>font-size: 14px</code> —— iOS Safari 会自动放大整个页面<br>
|
|
136
125
|
错误文字没有 <code>aria-describedby</code> —— 屏幕阅读器读不到
|
|
137
126
|
</p>
|
|
138
127
|
</div>
|
|
139
|
-
<div class="
|
|
128
|
+
<div class="cmp__pane cmp__good">
|
|
140
129
|
<h3>本库写法</h3>
|
|
141
130
|
<div class="field">
|
|
142
131
|
<label class="field__label" for="g1">金额</label>
|
|
143
132
|
<input class="field__input" id="g1" value="abc" aria-invalid="true" aria-describedby="g1-err" inputmode="decimal" name="amount-g1" autocomplete="off"/>
|
|
144
133
|
<span class="field__error" id="g1-err" role="alert">请输入有效金额</span>
|
|
145
134
|
</div>
|
|
146
|
-
<p>
|
|
135
|
+
<p class="cmp__why">
|
|
147
136
|
有可见 label,不会消失<br>
|
|
148
137
|
红边框 + 图标 + 文字,三条线索<br>
|
|
149
138
|
<code>font-size: 16px</code> —— iOS 不缩放<br>
|
|
@@ -88,4 +88,19 @@ Popover.current; // 当前开着的实例(没有则null)
|
|
|
88
88
|
- ❌ **同时开两个 popover** —— 两个叠着,用户不知道该关哪个,也不知道点空白处关的是哪一个
|
|
89
89
|
- ❌ **把箭头塞进带 `overflow: hidden` 的容器** —— 箭头会被裁掉;所以本组件不用 overflow 方式隐藏
|
|
90
90
|
|
|
91
|
+
## 已知局限:不做落点检测
|
|
92
|
+
|
|
93
|
+
浮层靠 CSS 定位(`--bottom` / `--top` / `--start` / `--end` 四个方向类),
|
|
94
|
+
**不测量、不在运行时判断空间够不够**。这是刻意的取舍:
|
|
95
|
+
|
|
96
|
+
- 好处:没有布局抖动、没有"打开瞬间先闪一下再翻位"、不依赖 `getBoundingClientRect`
|
|
97
|
+
- 代价:锚点右边真的没有空间时,`--end` / `--bottom` 仍会朝那个方向弹出去
|
|
98
|
+
|
|
99
|
+
所以**调用方要给浮层留位置**:靠边的按钮用 `--start` 而不是 `--end`;
|
|
100
|
+
演示页里那一栏就是给气泡留了 `max-width` 的口子,而不是让气泡自己算。
|
|
101
|
+
|
|
102
|
+
- ❌ **指望组件"自动翻边"** —— 它不会。库里没有落点检测,也不会加:
|
|
103
|
+
一旦按测量定位,滚动、旋转屏幕、软键盘弹起都要重新算,代价比收益大
|
|
104
|
+
|
|
105
|
+
|
|
91
106
|
---
|
|
@@ -31,6 +31,17 @@
|
|
|
31
31
|
table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
|
|
32
32
|
td, th { text-align: left; padding: var(--sp-2); border-bottom: 1px solid var(--border-decor); }
|
|
33
33
|
th { font-weight: 600; }
|
|
34
|
+
/* 一次性演示用:给浮层一个定位舞台(组件里它挂在触发元素的锚点上) */
|
|
35
|
+
.pp-stage { position: relative; min-height: 210px; --popover-min-w: 0; }
|
|
36
|
+
/* 气泡不设最小宽度:320px 下栏内宽只有约 256px,`min-width:200px` + 起点 120px
|
|
37
|
+
会直接顶出视口(实测溢出 33px)。宽度改由 max-width 向栏内收。 */
|
|
38
|
+
/* 🔴 反例块里的气泡是"静态摆拍",**不接行为**,所以用 data-demo-open,
|
|
39
|
+
不能用 data-open:`data-open[='true']` 是组件真实的"开着"状态,
|
|
40
|
+
`Popover.closeAll()` 和契约门禁都按它判断 —— 演示块一旦写了它,
|
|
41
|
+
门禁里「Esc 关闭后必须没有开着的浮层」就永远为假(实测两个条目连红)。 */
|
|
42
|
+
.pp-stage .popover[data-demo-open] { opacity: 1; visibility: visible; }
|
|
43
|
+
.pp-stage .popover__desc { margin: var(--sp-1) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
|
|
44
|
+
.pp-stage .popover__title { margin: 0; font-size: var(--fs-sm); font-weight: 500; }
|
|
34
45
|
</style>
|
|
35
46
|
</head>
|
|
36
47
|
<body class="page page">
|
|
@@ -145,6 +156,40 @@ demo('bad1', 'bottom',
|
|
|
145
156
|
'<button class="btn btn--sm btn--danger">删除</button>' +
|
|
146
157
|
'</div>', '反面示例');
|
|
147
158
|
</script>
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
162
|
+
<div class="cmp">
|
|
163
|
+
<div class="cmp__pane cmp__bad">
|
|
164
|
+
<h3>❌ 常见写法:两个浮层同时开着</h3>
|
|
165
|
+
<div class="pp-stage" aria-hidden="true">
|
|
166
|
+
<button class="btn btn--sm btn--ghost" type="button" tabindex="-1">筛选</button>
|
|
167
|
+
<div class="popover popover--bottom" data-demo-open="true"
|
|
168
|
+
style="top:36px; inset-inline-start:8px; max-width:calc(100% - 16px); transform:none">
|
|
169
|
+
<p class="popover__title">筛选条件</p>
|
|
170
|
+
<p class="popover__desc">按板块过滤,可多选。</p>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="popover popover--bottom" data-demo-open="true"
|
|
173
|
+
style="top:84px; inset-inline-start:38%; max-width:52%; transform:none">
|
|
174
|
+
<p class="popover__title">排序方式</p>
|
|
175
|
+
<p class="popover__desc">按涨跌幅。</p>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<p class="cmp__why">两个都开着:点空白处到底关的是哪一个?读屏用户按 Esc 也不知道关掉的是哪个,而两个浮层的内容还叠在一起。</p>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="cmp__pane cmp__good">
|
|
181
|
+
<h3>✅ 本库:同一时刻只开一个</h3>
|
|
182
|
+
<div class="pp-stage" aria-hidden="true">
|
|
183
|
+
<button class="btn btn--sm btn--ghost" type="button" tabindex="-1">筛选</button>
|
|
184
|
+
<div class="popover popover--bottom" data-demo-open="true"
|
|
185
|
+
style="top:36px; inset-inline-start:8px; max-width:calc(100% - 16px); transform:none">
|
|
186
|
+
<p class="popover__title">筛选条件</p>
|
|
187
|
+
<p class="popover__desc">按板块过滤,可多选。</p>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
<p class="cmp__why"><code>popover.js</code> 的 <code>open()</code> 会先关掉当前打开的那个 —— 不是"约定",是写死在实现里的。</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
148
193
|
</main>
|
|
149
194
|
</body>
|
|
150
195
|
</html>
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
font-variant-numeric: tabular-nums; min-width: 3.2em; text-align: right; }
|
|
24
24
|
.bad-demo { background: var(--danger-bg); border-color: var(--danger); }
|
|
25
25
|
.bad { color: var(--danger); }
|
|
26
|
+
.pr-label { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); }
|
|
27
|
+
.pr-note { margin: var(--sp-2) 0 0; font-size: var(--fs-xs); color: var(--text-secondary); }
|
|
26
28
|
</style>
|
|
27
29
|
</head>
|
|
28
30
|
<body class="page page">
|
|
@@ -149,6 +151,29 @@ document.querySelectorAll('.progress, .progress-ring, .progress-steps').forEach(
|
|
|
149
151
|
}
|
|
150
152
|
});
|
|
151
153
|
</script>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
157
|
+
<div class="cmp">
|
|
158
|
+
<div class="cmp__pane cmp__bad">
|
|
159
|
+
<h3>❌ 常见写法:到 100% 就变绿</h3>
|
|
160
|
+
<p class="pr-label">导出 6 个文件 · 100%</p>
|
|
161
|
+
<span class="progress progress--success" role="progressbar" aria-valuenow="100"
|
|
162
|
+
aria-valuemin="0" aria-valuemax="100" aria-label="导出进度"
|
|
163
|
+
style="--progress:100%"><span class="progress__bar"></span></span>
|
|
164
|
+
<p class="pr-note">界面写"导出完成",条是绿的。</p>
|
|
165
|
+
<p class="cmp__why">100% 说的是"走到头了",不是"成功了"。这一批实际有 2 个文件失败,绿条仍然亮着 —— 用户以为可以走了。</p>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="cmp__pane cmp__good">
|
|
168
|
+
<h3>✅ 本库:颜色讲结果,数值只讲进度</h3>
|
|
169
|
+
<p class="pr-label">导出 6 个文件 · 100%</p>
|
|
170
|
+
<span class="progress progress--danger" role="progressbar" aria-valuenow="100"
|
|
171
|
+
aria-valuemin="0" aria-valuemax="100" aria-label="导出失败"
|
|
172
|
+
style="--progress:100%"><span class="progress__bar"></span></span>
|
|
173
|
+
<p class="pr-note">同样 100%,红了:2 个文件失败。</p>
|
|
174
|
+
<p class="cmp__why"><code>progress--success</code> / <code>--danger</code> 由调用方按<b>业务结果</b>给,不按数值自动切换 —— 颜色是语义,不是刻度。</p>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
152
177
|
</main>
|
|
153
178
|
</body>
|
|
154
179
|
</html>
|
|
@@ -19,6 +19,23 @@
|
|
|
19
19
|
color: var(--text-secondary); min-height: 3em;
|
|
20
20
|
}
|
|
21
21
|
label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
22
|
+
/* 一次性演示用:把选项摆成静态列表 */
|
|
23
|
+
.sl-wrap { display: grid; gap: var(--sp-1); }
|
|
24
|
+
.sl-head { font-size: var(--fs-sm); color: var(--text-tertiary); padding: 0 0 var(--sp-1); }
|
|
25
|
+
.sl-list {
|
|
26
|
+
list-style: none; margin: 0; padding: var(--sp-1);
|
|
27
|
+
background: var(--surface-raised);
|
|
28
|
+
border: 1px solid var(--border-decor);
|
|
29
|
+
border-radius: var(--r-sm);
|
|
30
|
+
}
|
|
31
|
+
/* 反例专用:把命中区压到 28px */
|
|
32
|
+
.sl-tight {
|
|
33
|
+
display: flex; align-items: center;
|
|
34
|
+
min-height: 28px; padding: 0 var(--sp-2);
|
|
35
|
+
border-radius: var(--r-xs);
|
|
36
|
+
font-size: var(--fs-sm);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
22
39
|
</style>
|
|
23
40
|
</head>
|
|
24
41
|
<body class="page page">
|
|
@@ -94,6 +111,35 @@ label.f { display: block; margin-bottom: var(--sp-1); font-size: var(--fs-sm); }
|
|
|
94
111
|
</div>
|
|
95
112
|
</div>
|
|
96
113
|
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
117
|
+
<div class="cmp">
|
|
118
|
+
<div class="cmp__pane cmp__bad">
|
|
119
|
+
<h3>❌ 常见写法:选项挤在一起</h3>
|
|
120
|
+
<div class="sl-wrap" aria-hidden="true">
|
|
121
|
+
<div class="sl-head">所属板块</div>
|
|
122
|
+
<ul class="sl-list">
|
|
123
|
+
<li class="sl-tight">半导体设备</li>
|
|
124
|
+
<li class="sl-tight">半导体材料</li>
|
|
125
|
+
<li class="sl-tight">半导体封测</li>
|
|
126
|
+
</ul>
|
|
127
|
+
</div>
|
|
128
|
+
<p class="cmp__why">行高只有 28px:手指点下去必然压到隔壁那一行。鼠标用户感觉不到,触屏用户每点错一次就要重来。</p>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="cmp__pane cmp__good">
|
|
131
|
+
<h3>✅ 本库:每项 44px 命中区</h3>
|
|
132
|
+
<div class="sl-wrap" aria-hidden="true">
|
|
133
|
+
<div class="sl-head">所属板块</div>
|
|
134
|
+
<ul class="sl-list">
|
|
135
|
+
<li class="select__opt">半导体设备</li>
|
|
136
|
+
<li class="select__opt">半导体材料</li>
|
|
137
|
+
<li class="select__opt">半导体封测</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
<p class="cmp__why">命中区 44px 是 AAA 的下限(<code>select.css</code> 里写着这条),与 <code>dropdown</code> 同规格 —— 两个控件的选项高度不该不一样。</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
97
143
|
</main>
|
|
98
144
|
|
|
99
145
|
<script src="select.js"></script>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="stylesheet" href="../../01-tokens/typography.css">
|
|
10
10
|
<link rel="stylesheet" href="../../01-tokens/page.css">
|
|
11
11
|
<link rel="stylesheet" href="../button/button.css">
|
|
12
|
+
<link rel="stylesheet" href="../progress/progress.css">
|
|
12
13
|
<link rel="stylesheet" href="skeleton.css">
|
|
13
14
|
<style>
|
|
14
15
|
.demo {
|
|
@@ -22,6 +23,9 @@
|
|
|
22
23
|
/* 反例演示:故意用深色底+白字,说明"骨架屏不该这样用" */
|
|
23
24
|
.bad-demo { background: var(--danger-bg); border-color: var(--danger); }
|
|
24
25
|
.bad { color: var(--danger); }
|
|
26
|
+
.sk-label { margin: 0 0 var(--sp-2); font-size: var(--fs-sm); }
|
|
27
|
+
.sk-pct { margin: 0 0 var(--sp-2); font-size: var(--fs-xs); color: var(--text-secondary); }
|
|
28
|
+
.sk-box { display: flex; flex-direction: column; gap: var(--sp-2); }
|
|
25
29
|
</style>
|
|
26
30
|
</head>
|
|
27
31
|
<body class="page page">
|
|
@@ -105,6 +109,30 @@
|
|
|
105
109
|
</p>
|
|
106
110
|
</div>
|
|
107
111
|
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
115
|
+
<div class="cmp">
|
|
116
|
+
<div class="cmp__pane cmp__bad">
|
|
117
|
+
<h3>❌ 常见写法:有确切进度却用骨架屏</h3>
|
|
118
|
+
<p class="sk-label">上传 素材包.zip(3.1 MB)</p>
|
|
119
|
+
<div class="sk-box" aria-hidden="true">
|
|
120
|
+
<span class="skeleton skeleton--text" style="width:82%"></span>
|
|
121
|
+
<span class="skeleton skeleton--text" style="width:61%"></span>
|
|
122
|
+
<span class="skeleton skeleton--text" style="width:44%"></span>
|
|
123
|
+
</div>
|
|
124
|
+
<p class="cmp__why">文件大小是已知的、进度是算得出来的,却只给一片灰条:用户等了 40 秒也不知道还差 3 秒还是 3 分钟。</p>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="cmp__pane cmp__good">
|
|
127
|
+
<h3>✅ 本库:确定进度用 progress,不确定才用 skeleton</h3>
|
|
128
|
+
<p class="sk-label">上传 素材包.zip(3.1 MB)</p>
|
|
129
|
+
<p class="sk-pct">已传 1.9 MB / 3.1 MB</p>
|
|
130
|
+
<span class="progress" role="progressbar" aria-valuenow="61"
|
|
131
|
+
aria-valuemin="0" aria-valuemax="100" aria-label="上传进度"
|
|
132
|
+
style="--progress:61%"><span class="progress__bar"></span></span>
|
|
133
|
+
<p class="cmp__why"><code>skeleton</code> 的语义是"<b>不知道</b>要多久"。知道比例就用 <code>progress</code> —— 两者的区别不是好看,是诚实。</p>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
108
136
|
</main>
|
|
109
137
|
</body>
|
|
110
138
|
</html>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
border-radius: var(--r-sm);
|
|
18
18
|
font-size: var(--fs-sm); color: var(--text-secondary);
|
|
19
19
|
}
|
|
20
|
+
.ct-p { margin: 0; font-size: var(--fs-sm); }
|
|
20
21
|
</style>
|
|
21
22
|
</head>
|
|
22
23
|
<body class="page page">
|
|
@@ -82,6 +83,25 @@
|
|
|
82
83
|
<p>详细口径见 <a href="#main">内部数据字典</a>。</p>
|
|
83
84
|
</div>
|
|
84
85
|
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
89
|
+
<div class="cmp">
|
|
90
|
+
<div class="cmp__pane cmp__bad">
|
|
91
|
+
<h3>❌ 常见写法:裸 <code><hr></code></h3>
|
|
92
|
+
<div aria-hidden="true"> <p class="ct-p">本季应收账款周转天数下降 6 天。</p>
|
|
93
|
+
<hr>
|
|
94
|
+
<p class="ct-p">下一节:存货跌价准备的计提口径。</p></div>
|
|
95
|
+
<p class="cmp__why">默认 <code><hr></code> 自带上下两条 inset 边框,不清 <code>height</code> 和 <code>border-top</code> 出来就是一条粗双线 —— 而且不同浏览器的厚度还不一样。</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="cmp__pane cmp__good">
|
|
98
|
+
<h3>✅ 本库:<code>.prose > hr</code></h3>
|
|
99
|
+
<div class="prose" aria-hidden="true"> <p class="ct-p">本季应收账款周转天数下降 6 天。</p>
|
|
100
|
+
<hr>
|
|
101
|
+
<p class="ct-p">下一节:存货跌价准备的计提口径。</p></div>
|
|
102
|
+
<p class="cmp__why"><code>content.css</code> 把 <code>height</code> 归零、只留一条上边 —— 渲染器(markdown-it)产出的裸 <code><hr></code> 直接就是对的。</p>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
85
105
|
</main>
|
|
86
106
|
|
|
87
107
|
</body>
|
|
@@ -21,6 +21,21 @@
|
|
|
21
21
|
}
|
|
22
22
|
ul.demo-list { padding-left: var(--sp-5); color: var(--text-secondary); }
|
|
23
23
|
ul.demo-list li { margin: var(--sp-1) 0; }
|
|
24
|
+
.dr-bar {
|
|
25
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
26
|
+
gap: var(--sp-3);
|
|
27
|
+
padding: var(--sp-2) var(--sp-3);
|
|
28
|
+
background: var(--surface-raised);
|
|
29
|
+
border: 1px solid var(--border-decor);
|
|
30
|
+
border-radius: var(--r-sm);
|
|
31
|
+
}
|
|
32
|
+
.dr-bar__title { font-size: var(--fs-sm); font-weight: 500; }
|
|
33
|
+
.dr-tiny {
|
|
34
|
+
width: 24px; height: 24px;
|
|
35
|
+
display: flex; align-items: center; justify-content: center;
|
|
36
|
+
background: none; border: 0; border-radius: var(--r-xs);
|
|
37
|
+
color: var(--text-secondary); font-size: 14px; line-height: 1; cursor: pointer;
|
|
38
|
+
}
|
|
24
39
|
</style>
|
|
25
40
|
</head>
|
|
26
41
|
<body class="page page">
|
|
@@ -49,6 +64,27 @@ ul.demo-list li { margin: var(--sp-1) 0; }
|
|
|
49
64
|
<button class="btn btn--primary" id="b2">打开(焦点落在第二个按钮)</button>
|
|
50
65
|
</div>
|
|
51
66
|
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
<h2 class="rule-bottom">对比:错写法 vs 本库写法</h2>
|
|
70
|
+
<div class="cmp">
|
|
71
|
+
<div class="cmp__pane cmp__bad">
|
|
72
|
+
<h3>❌ 常见写法:关闭按钮做小</h3>
|
|
73
|
+
<div class="dr-bar" aria-hidden="true">
|
|
74
|
+
<span class="dr-bar__title">筛选</span>
|
|
75
|
+
<button class="dr-tiny" type="button" tabindex="-1" aria-label="关闭">×</button>
|
|
76
|
+
</div>
|
|
77
|
+
<p class="cmp__why">关闭是**独立目标**:抽屉滑进来之后,用户第一件事就是找它。24px 的叉在触屏上要瞄准,点偏了就点到了背后的内容。</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="cmp__pane cmp__good">
|
|
80
|
+
<h3>✅ 本库:<code>drawer__close</code> 44px</h3>
|
|
81
|
+
<div class="dr-bar" aria-hidden="true">
|
|
82
|
+
<span class="dr-bar__title">筛选</span>
|
|
83
|
+
<button class="drawer__close" type="button" tabindex="-1" aria-label="关闭">×</button>
|
|
84
|
+
</div>
|
|
85
|
+
<p class="cmp__why">命中区 44px(<code>drawer.css</code> 里写明"关闭按钮是独立目标,用 44")—— 字形还是 20px,只是可点范围大了。</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
52
88
|
</main>
|
|
53
89
|
|
|
54
90
|
<script src="drawer.js"></script>
|