agentcss 0.11.14 → 0.12.0
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 +54 -3
- package/01-tokens/market.css +48 -0
- package/01-tokens/tokens.css +66 -0
- package/02-primitives/combobox/combobox.js +1 -1
- package/02-primitives/date-range/date-range.js +1 -1
- package/02-primitives/delta/README.md +92 -0
- package/02-primitives/delta/delta.css +113 -0
- package/02-primitives/delta/demo.html +162 -0
- package/02-primitives/metric/README.md +93 -0
- package/02-primitives/metric/demo.html +176 -0
- package/02-primitives/metric/metric.css +157 -0
- package/02-primitives/popover/demo.html +1 -1
- package/02-primitives/popover/popover.js +1 -1
- package/02-primitives/select/select.js +1 -1
- package/03-patterns/accordion/accordion.js +25 -5
- package/03-patterns/drawer/drawer.js +1 -1
- package/03-patterns/dropdown/dropdown.js +1 -1
- package/03-patterns/list/flip.js +1 -1
- package/03-patterns/nav/nav.js +6 -3
- package/03-patterns/nav/toc.js +1 -1
- package/03-patterns/overlay/overlay.js +1 -1
- package/03-patterns/pagination/demo.html +1 -1
- package/03-patterns/pagination/pagination.js +9 -3
- package/03-patterns/reveal/reveal.js +4 -1
- package/03-patterns/tabs/tabs.js +12 -3
- package/03-patterns/tooltip/tooltip.js +1 -1
- package/03-patterns/tree/tree.js +1 -1
- package/04-recipes/report/demo.html +2 -2
- package/04-recipes/tierA-tokens.css +12 -6
- package/09-assets/echarts-adapter/demo.html +5 -5
- package/09-assets/model-viewer/demo.html +2 -2
- package/API.md +52 -3
- package/CHANGELOG.md +356 -0
- package/CONTRIBUTING.md +2 -1
- package/README.zh-CN.md +9 -7
- package/START-HERE.md +7 -5
- package/ai/START-HERE.md +1 -1
- package/ai/components.json +103 -8
- package/ai/components.meta.json +8 -0
- package/ai/deprecations.schema.json +2 -2
- package/ai/tokens.audience.json +3 -2
- package/ai/tokens.json +21 -2
- package/ai/tokens.tree.json +20 -2
- package/dist/01-tokens/market.css +1 -0
- package/dist/01-tokens/tokens.css +1 -1
- package/dist/02-primitives/combobox/combobox.js +1 -1
- package/dist/02-primitives/date-range/date-range.js +1 -1
- package/dist/02-primitives/delta/delta.css +1 -0
- package/dist/02-primitives/metric/metric.css +1 -0
- package/dist/02-primitives/popover/popover.js +1 -1
- package/dist/02-primitives/select/select.js +1 -1
- package/dist/03-patterns/accordion/accordion.js +10 -4
- package/dist/03-patterns/drawer/drawer.js +1 -1
- package/dist/03-patterns/dropdown/dropdown.js +1 -1
- package/dist/03-patterns/list/flip.js +1 -1
- package/dist/03-patterns/nav/nav.js +4 -3
- package/dist/03-patterns/nav/toc.js +1 -1
- package/dist/03-patterns/overlay/overlay.js +1 -1
- package/dist/03-patterns/pagination/pagination.js +4 -3
- package/dist/03-patterns/reveal/reveal.js +2 -1
- package/dist/03-patterns/tabs/tabs.js +8 -3
- package/dist/03-patterns/tooltip/tooltip.js +1 -1
- package/dist/03-patterns/tree/tree.js +1 -1
- package/dist/04-recipes/tierA-tokens.css +1 -1
- package/dist/COSTS.md +35 -33
- package/docs/ACCESSIBILITY.md +3 -3
- package/docs/CONVENTIONS.md +28 -2
- package/docs/DEPRECATIONS.md +128 -0
- package/esm/accordion.mjs +7 -0
- package/esm/combobox.mjs +7 -0
- package/esm/date-range.mjs +7 -0
- package/esm/drawer.mjs +7 -0
- package/esm/dropdown.mjs +7 -0
- package/esm/flip.mjs +7 -0
- package/esm/index.mjs +38 -0
- package/esm/nav.mjs +7 -0
- package/esm/overlay.mjs +7 -0
- package/esm/pagination.mjs +7 -0
- package/esm/popover.mjs +7 -0
- package/esm/reveal.mjs +7 -0
- package/esm/select.mjs +7 -0
- package/esm/tabs.mjs +7 -0
- package/esm/toc.mjs +7 -0
- package/esm/tooltip.mjs +7 -0
- package/esm/tree.mjs +7 -0
- package/index.html +4 -2
- package/package.json +10 -3
- package/types/index.d.ts +131 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# metric · 指标块
|
|
2
|
+
|
|
3
|
+
报表里**“一个数”的最小完整单元**:一个标签、一个值、一句上下文。
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<link rel="stylesheet" href="02-primitives/metric/metric.css">
|
|
7
|
+
|
|
8
|
+
<dl class="metric">
|
|
9
|
+
<dt class="metric__label">营业总收入</dt>
|
|
10
|
+
<dd class="metric__value">1,294.06<span class="metric__unit">亿元</span></dd>
|
|
11
|
+
<dd class="metric__foot">
|
|
12
|
+
<span class="delta delta--up">12.40%</span>
|
|
13
|
+
<span class="metric__note">同比</span>
|
|
14
|
+
</dd>
|
|
15
|
+
</dl>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 类
|
|
19
|
+
|
|
20
|
+
| 类 | 作用 |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `.metric` | 基础块(推荐 `<dl>`,用 `<div>` 也能渲染) |
|
|
23
|
+
| `.metric__label` | 标签:12px、次要色 |
|
|
24
|
+
| `.metric__value` | 值:等宽字族(`--mono`)+ 默认不换行(装不下才断) |
|
|
25
|
+
| `.metric__unit` | 单位:与数字同基线,小一号 |
|
|
26
|
+
| `.metric__foot` | 脚下那行:通常放 `delta` + 说明词 |
|
|
27
|
+
| `.metric__note` | 说明词(“同比” / “较上季”) |
|
|
28
|
+
| `.metric__spark` | 迷你走势槽位,放不放都行 |
|
|
29
|
+
| `.metric--lg` | 主指标档 |
|
|
30
|
+
| `.metric--sm` | 紧凑档(密集网格) |
|
|
31
|
+
| `.metric--null` | 缺数档:值降为 tertiary、去字重 |
|
|
32
|
+
| `.metric-grid` | KPI 行的自适应网格 |
|
|
33
|
+
|
|
34
|
+
## 三条硬规矩
|
|
35
|
+
|
|
36
|
+
1. **缺数写 `—`(U+2014)并说明原因**,不填 0、不留空、不只把字变淡。
|
|
37
|
+
2. **单位放 `metric__unit`**,不要拼进值字符串。
|
|
38
|
+
3. **变化量放 `metric__foot`**,不要混进 `metric__value`。
|
|
39
|
+
|
|
40
|
+
## 反例
|
|
41
|
+
|
|
42
|
+
- ❌ **缺数填 0** —— `0.00` 与 `—` 在报表上是两个不同结论。填 0 会让读者把「未披露」读成「现金流为零」,而它其实可能只是没出报表
|
|
43
|
+
- ❌ **把单位拼进值字符串** —— `1,294.06亿元` 让单位跟着 30px 字号走、和数字一样重,读者第一眼会把“亿元”当成数值的一部分;而且没法单选单位调样式
|
|
44
|
+
- ❌ **把 `delta` 塞进 `metric__value`** —— 值里混进变化量之后等宽对齐失效(两段数字位数不同),读屏也会念成一串。变化量属于 `metric__foot`
|
|
45
|
+
- ❌ **用 `opacity` 表达缺数** —— `--text-tertiary` 的 5.33:1 已经是文字色地板,再乘一次透明度就跌破 4.5。缺数靠“不给数字”表达,不靠让它看不清
|
|
46
|
+
- ❌ **把 `metric` 当卡片用** —— 它没有边框、没有内边距、不承载操作区。要“一块带边框的内容”用 `card`;`metric` 只负责一个数
|
|
47
|
+
|
|
48
|
+
## 为什么推荐 `<dl>`
|
|
49
|
+
|
|
50
|
+
“标签—值”在 HTML 里有现成语义:术语 + 描述。读屏会念
|
|
51
|
+
「营业总收入 1,294.06 亿元 +12.40% 同比」;用 `<div>` 只会念一串没有关系的词。
|
|
52
|
+
CSS 只认类名,所以 `<div>` 也能渲染(兼容既有页面),但文档一律示范 `<dl>`。
|
|
53
|
+
|
|
54
|
+
## 网格
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<div class="metric-grid">
|
|
58
|
+
<dl class="metric"> … </dl>
|
|
59
|
+
<dl class="metric"> … </dl>
|
|
60
|
+
</div>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
内部是 `repeat(auto-fit, minmax(min(100%, 12rem), 1fr))`。
|
|
64
|
+
那层 `min()` 不是冗余:窄容器下 12rem 会比可用宽度还大,没有它就会横向溢出
|
|
65
|
+
(`05-audit/metric-check.js` 判据 ⑧ 在 375px 视口下直接量 `scrollWidth`)。
|
|
66
|
+
|
|
67
|
+
## 缺数是一等状态
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<dl class="metric metric--null">
|
|
71
|
+
<dt class="metric__label">经营活动现金流净额</dt>
|
|
72
|
+
<dd class="metric__value">—</dd>
|
|
73
|
+
<dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
|
|
74
|
+
</dl>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`metric--null` 只把值的颜色降到 tertiary、去掉字重,**不隐藏这一块** ——
|
|
78
|
+
版面塌掉比显示“没有”更糟。这是本库从 `04-recipes/detail-page` 的 null 变体
|
|
79
|
+
继承下来的同一条纪律。
|
|
80
|
+
|
|
81
|
+
## 对本组件不做的事
|
|
82
|
+
|
|
83
|
+
- **不做数值格式化**。千分位、小数位、单位换算由数据侧决定;CSS 不知道
|
|
84
|
+
`1,294.06` 与 `1294.06` 哪个对。库保证的是等宽对齐与不换行。
|
|
85
|
+
- **不判断方向**。涨跌颜色交给 `delta`,本组件不含任何方向令牌引用
|
|
86
|
+
(`metric-check` 判据 ⑦ 静态检查这件事)。
|
|
87
|
+
|
|
88
|
+
## 预览
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
python -m http.server 8000
|
|
92
|
+
# http://127.0.0.1:8000/02-primitives/metric/demo.html
|
|
93
|
+
```
|
|
@@ -0,0 +1,176 @@
|
|
|
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>metric · 指标块</title>
|
|
8
|
+
<link rel="stylesheet" href="../../01-tokens/tokens.css">
|
|
9
|
+
<link rel="stylesheet" href="../../01-tokens/typography.css">
|
|
10
|
+
<link rel="stylesheet" href="../../01-tokens/page.css">
|
|
11
|
+
<link rel="stylesheet" href="../../02-primitives/metric/metric.css">
|
|
12
|
+
<link rel="stylesheet" href="../../02-primitives/delta/delta.css">
|
|
13
|
+
<link rel="stylesheet" href="../../09-assets/sparkline/sparkline.css">
|
|
14
|
+
<style>
|
|
15
|
+
.pad { padding: var(--sp-4) 0; }
|
|
16
|
+
.frame { border: 1px solid var(--border-decor); border-radius: var(--r-md);
|
|
17
|
+
padding: var(--sp-5); background: var(--surface); }
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body class="page page">
|
|
21
|
+
<main class="wrap">
|
|
22
|
+
<h1>metric · 指标块</h1>
|
|
23
|
+
<p class="lede">
|
|
24
|
+
报表里<strong>“一个数”的最小完整单元</strong>:一个标签、一个值、一句上下文。
|
|
25
|
+
KPI 行、详情页顶部、监控面板,都由它排出来。
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<h2 class="rule-top">一个完整的 metric</h2>
|
|
29
|
+
<div class="frame">
|
|
30
|
+
<dl class="metric" style="max-width: 18rem">
|
|
31
|
+
<dt class="metric__label">营业总收入</dt>
|
|
32
|
+
<dd class="metric__value">1,294.06<span class="metric__unit">亿元</span></dd>
|
|
33
|
+
<dd class="metric__foot">
|
|
34
|
+
<span class="delta delta--up">12.40%</span>
|
|
35
|
+
<span class="metric__note">同比</span>
|
|
36
|
+
</dd>
|
|
37
|
+
</dl>
|
|
38
|
+
</div>
|
|
39
|
+
<p class="small">
|
|
40
|
+
四件必须一起做对的事:<strong>数字等宽</strong>(上下两行数位对齐,竖着扫能比大小)、
|
|
41
|
+
<strong>单位分离成槽位</strong>(拼进字符串就没法单独调样式,也没法让数字右对齐时把单位甩在外面)、
|
|
42
|
+
<strong>缺数是一等状态</strong>(见下)、<strong>方向交给 delta</strong>(本组件不管颜色)。
|
|
43
|
+
<br>
|
|
44
|
+
结构用 <code><dl></code> 是因为“标签—值”在 HTML 里有现成语义:
|
|
45
|
+
读屏会念「营业总收入 1,294.06 亿元 +12.40% 同比」。用 <code><div></code>
|
|
46
|
+
只会念一串没有关系的词(CSS 只认类名,用 div 也能渲染,但不推荐)。
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<h2 class="rule-top">一行四个 KPI</h2>
|
|
50
|
+
<div class="metric-grid">
|
|
51
|
+
<dl class="metric">
|
|
52
|
+
<dt class="metric__label">营业总收入</dt>
|
|
53
|
+
<dd class="metric__value">1,294.06<span class="metric__unit">亿元</span></dd>
|
|
54
|
+
<dd class="metric__foot"><span class="delta delta--up">12.40%</span><span class="metric__note">同比</span></dd>
|
|
55
|
+
</dl>
|
|
56
|
+
<dl class="metric">
|
|
57
|
+
<dt class="metric__label">归母净利润</dt>
|
|
58
|
+
<dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
|
|
59
|
+
<dd class="metric__foot"><span class="delta delta--up">8.60%</span><span class="metric__note">同比</span></dd>
|
|
60
|
+
</dl>
|
|
61
|
+
<dl class="metric">
|
|
62
|
+
<dt class="metric__label">销售毛利率</dt>
|
|
63
|
+
<dd class="metric__value">41.28<span class="metric__unit">%</span></dd>
|
|
64
|
+
<dd class="metric__foot"><span class="delta delta--down">1.34%</span><span class="metric__note">同比</span></dd>
|
|
65
|
+
</dl>
|
|
66
|
+
<dl class="metric metric--null">
|
|
67
|
+
<dt class="metric__label">经营活动现金流净额</dt>
|
|
68
|
+
<dd class="metric__value">—</dd>
|
|
69
|
+
<dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
|
|
70
|
+
</dl>
|
|
71
|
+
</div>
|
|
72
|
+
<p class="small">
|
|
73
|
+
第四块是<strong>缺数</strong>:值写 <code>—</code>(U+2014),并在脚下说明原因。
|
|
74
|
+
它<strong>不隐藏、不变灰到看不见、也不填 0</strong> —— 版面塌掉比显示“没有”更糟,
|
|
75
|
+
而填 0 是伪造数据:读者会把“未披露”读成“现金流为零”。
|
|
76
|
+
<br>
|
|
77
|
+
网格用 <code>metric-grid</code>,它内部是
|
|
78
|
+
<code>minmax(min(100%, 12rem), 1fr)</code> —— 那层 <code>min()</code> 不是冗余:
|
|
79
|
+
窄容器下 12rem 会比可用宽度还大,没有它就会横向溢出。
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<h2 class="rule-top">三档字号</h2>
|
|
83
|
+
<div class="frame">
|
|
84
|
+
<div class="metric-grid">
|
|
85
|
+
<dl class="metric metric--lg">
|
|
86
|
+
<dt class="metric__label">主指标(lg)</dt>
|
|
87
|
+
<dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
|
|
88
|
+
</dl>
|
|
89
|
+
<dl class="metric">
|
|
90
|
+
<dt class="metric__label">默认</dt>
|
|
91
|
+
<dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
|
|
92
|
+
</dl>
|
|
93
|
+
<dl class="metric metric--sm">
|
|
94
|
+
<dt class="metric__label">紧凑档(sm)</dt>
|
|
95
|
+
<dd class="metric__value">318.72<span class="metric__unit">亿元</span></dd>
|
|
96
|
+
</dl>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<p class="small">
|
|
100
|
+
三档够了。出现第四档说明 KPI 行的层级没规划好(与 <code>badge</code> / <code>card</code> 同一判断标准)。
|
|
101
|
+
</p>
|
|
102
|
+
|
|
103
|
+
<h2 class="rule-top">迷你走势槽位</h2>
|
|
104
|
+
<div class="frame">
|
|
105
|
+
<div class="metric-grid">
|
|
106
|
+
<dl class="metric">
|
|
107
|
+
<dt class="metric__label">近九个交易日收盘</dt>
|
|
108
|
+
<dd class="metric__value">1,684.00</dd>
|
|
109
|
+
<dd class="metric__foot"><span class="delta delta--up">5.12%</span><span class="metric__note">五日</span></dd>
|
|
110
|
+
<dd class="metric__spark">
|
|
111
|
+
<span class="sparkline" data-spark="1602,1618,1609,1631,1625,1648,1662,1655,1684"></span>
|
|
112
|
+
</dd>
|
|
113
|
+
</dl>
|
|
114
|
+
<dl class="metric">
|
|
115
|
+
<dt class="metric__label">近九个交易日成交量</dt>
|
|
116
|
+
<dd class="metric__value">3.42<span class="metric__unit">亿手</span></dd>
|
|
117
|
+
<dd class="metric__foot"><span class="delta delta--down">2.08%</span><span class="metric__note">五日</span></dd>
|
|
118
|
+
<dd class="metric__spark">
|
|
119
|
+
<span class="sparkline" data-spark="3.9,3.7,3.8,3.6,3.5,3.55,3.48,3.5,3.42"></span>
|
|
120
|
+
</dd>
|
|
121
|
+
</dl>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
<p class="small">
|
|
125
|
+
<code>metric__spark</code> 只是一个<strong>槽位</strong> —— 它不关心里面放什么。
|
|
126
|
+
上面放的是 <code>sparkline</code>,也可以什么都不放(前三块就没有)。
|
|
127
|
+
槽位存在但为空时不留空白高度,所以同一个网格里有没有走势图都能对齐。
|
|
128
|
+
</p>
|
|
129
|
+
|
|
130
|
+
<h2 class="rule-top">反例:缺数填 0</h2>
|
|
131
|
+
<div class="cmp">
|
|
132
|
+
<div class="cmp__pane cmp__bad" data-bad=".metric__value|glyphs|=4">
|
|
133
|
+
<h3>错:拿 0 冒充“没有”</h3>
|
|
134
|
+
<dl class="metric">
|
|
135
|
+
<dt class="metric__label">经营活动现金流净额</dt>
|
|
136
|
+
<dd class="metric__value">0.00</dd>
|
|
137
|
+
<dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
|
|
138
|
+
</dl>
|
|
139
|
+
<p class="small">
|
|
140
|
+
值有 4 个字形。读者读到的是“现金流为零”—— 一个具体的、错误的结论。
|
|
141
|
+
</p>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="cmp__pane cmp__good" data-good=".metric__value|glyphs|=1">
|
|
144
|
+
<h3>对:留 <code>—</code> 并说明原因</h3>
|
|
145
|
+
<dl class="metric metric--null">
|
|
146
|
+
<dt class="metric__label">经营活动现金流净额</dt>
|
|
147
|
+
<dd class="metric__value">—</dd>
|
|
148
|
+
<dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
|
|
149
|
+
</dl>
|
|
150
|
+
<p class="small">
|
|
151
|
+
值只有 1 个字形(U+2014),且 <code>metric--null</code> 把颜色降到 tertiary。
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
<p class="small">
|
|
156
|
+
<strong>为什么不能靠“变淡”表达缺数</strong>:颜色淡了但字还在,读者仍会读那个数字。
|
|
157
|
+
缺数的正确表达是<strong>不给出数字</strong>,再加一句为什么。
|
|
158
|
+
<br>
|
|
159
|
+
⚠️ 也不要用 <code>opacity</code> 压淡 —— <code>--text-tertiary</code> 的 5.33:1
|
|
160
|
+
已经是地板,再乘一次透明度就跌破 4.5。改令牌才是对的做法。
|
|
161
|
+
</p>
|
|
162
|
+
|
|
163
|
+
<h2 class="rule-top">两条边界</h2>
|
|
164
|
+
<p class="small">
|
|
165
|
+
① <strong>本组件不做格式化</strong>。千分位、小数位、单位换算都由数据侧决定 ——
|
|
166
|
+
CSS 不知道“1,294.06”和“1294.06”哪个对。库能保证的是
|
|
167
|
+
<strong>等宽对齐</strong>与<strong>不换行</strong>。
|
|
168
|
+
<br>
|
|
169
|
+
② <strong>不要把 delta 写进 <code>metric__value</code></strong>。值里混进变化量之后,
|
|
170
|
+
等宽对齐就失效了(数字位数不同),而且读屏会念成一串。
|
|
171
|
+
变化量放 <code>metric__foot</code>。
|
|
172
|
+
</p>
|
|
173
|
+
</main>
|
|
174
|
+
<script src="../../09-assets/sparkline/sparkline.js"></script>
|
|
175
|
+
</body>
|
|
176
|
+
</html>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Metric — 指标块(一个标签 + 一个值 + 一句上下文)
|
|
3
|
+
============================================================================
|
|
4
|
+
一句话:**报表里“一个数”的最小完整单元**。
|
|
5
|
+
财务页、监控页、详情页顶部的 KPI 行,都是由它排出来的。
|
|
6
|
+
|
|
7
|
+
🔴 为什么它是组件,而不是几个工具类
|
|
8
|
+
----------------------------------------------------------------------------
|
|
9
|
+
因为“一个数”有四件事必须一起做对,少一件就会在真实报表里出错:
|
|
10
|
+
|
|
11
|
+
① 数字要**等宽**(用等宽字族,不靠比例字体的默认)—— 否则上下两行的数位对不齐,
|
|
12
|
+
竖着扫一眼比大小这件事就废了;这是「财务表格」与「装饰表格」的分界
|
|
13
|
+
② 单位要**分离**成槽位 —— 拼进字符串后("1,294.06亿元")既不能单独调样式,
|
|
14
|
+
也没法让数字右对齐时把单位甩在外面
|
|
15
|
+
③ **缺数是一等状态** —— 不能渲染成 0、不能留空、不能只把字变淡;
|
|
16
|
+
必须给出“为什么没有”(本库的既有纪律,见 04-recipes/detail-page 的 null 变体)
|
|
17
|
+
④ 变化的**方向**要能表达 —— 所以它给 delta 留了位置,但它自己不管颜色
|
|
18
|
+
|
|
19
|
+
这四件事里任意一件交给调用方“记得做”,就会漏。本库在同一条路上栽过:
|
|
20
|
+
`table__unit` 的括号是组件加的,不是要求每个调用方记得加。
|
|
21
|
+
|
|
22
|
+
🔴 为什么推荐 `<dl>`
|
|
23
|
+
----------------------------------------------------------------------------
|
|
24
|
+
“标签—值”在 HTML 里有现成语义:术语 + 描述。用 `<dl>` 读屏会念
|
|
25
|
+
「营业总收入 1,294.06 亿元 +12.4% 同比」,用 `<div>` 只会念一串没有关系的词。
|
|
26
|
+
|
|
27
|
+
本文件的 CSS **只认类名,不认标签** —— 用 `<div>` 也能正常渲染
|
|
28
|
+
(兼容既有页面),但 demo 与文档一律示范 `<dl>`。
|
|
29
|
+
|
|
30
|
+
🔴 缺数怎么写
|
|
31
|
+
----------------------------------------------------------------------------
|
|
32
|
+
<dl class="metric metric--null">
|
|
33
|
+
<dt class="metric__label">经营现金流</dt>
|
|
34
|
+
<dd class="metric__value">—</dd> <!-- U+2014,不是 0、不是空 -->
|
|
35
|
+
<dd class="metric__foot"><span class="metric__note">本期未披露</span></dd>
|
|
36
|
+
</dl>
|
|
37
|
+
|
|
38
|
+
`metric--null` 只做一件事:把值的颜色降到 tertiary、去掉字重。
|
|
39
|
+
它**不隐藏**这块 —— 版面塌掉比显示“没有”更糟。
|
|
40
|
+
========================================================================= */
|
|
41
|
+
|
|
42
|
+
.metric {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: var(--sp-1);
|
|
46
|
+
margin: 0; /* dl 自带外边距,会破坏网格对齐 */
|
|
47
|
+
min-width: 0; /* flex/grid 子项默认不缩,长标签会撑破列 */
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* dl 的 dd 默认有 40px 起始缩进 —— 只在结构与类名同时出现时清掉 */
|
|
51
|
+
.metric > dd {
|
|
52
|
+
margin-inline-start: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.metric__label {
|
|
56
|
+
margin: 0;
|
|
57
|
+
font-size: var(--fs-xs);
|
|
58
|
+
font-weight: 500;
|
|
59
|
+
line-height: 1.5;
|
|
60
|
+
color: var(--text-secondary);
|
|
61
|
+
letter-spacing: var(--tracking-normal); /* 小字号中文给一点字距更清;令牌在暗色段有另一档 */
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* 值:这一块的全部意义所在 */
|
|
65
|
+
.metric__value {
|
|
66
|
+
margin: 0;
|
|
67
|
+
font-family: var(--mono); /* 等宽字族 ⇒ 数字天然对齐,不需要 tabular-nums */
|
|
68
|
+
font-size: var(--fs-xl);
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
line-height: var(--lh-tight);
|
|
71
|
+
color: var(--text-primary);
|
|
72
|
+
/* 🔴 数字**默认不换行**,但极端情况下必须能断,否则顶破视口。
|
|
73
|
+
第一版写的是 `white-space: nowrap`,RTL 下阿语膨胀 1.3× 后
|
|
74
|
+
整个值变成一串装不下的长词,横向溢出 212px(实测)。
|
|
75
|
+
`overflow-wrap: anywhere` 的语义正好是「只有当一个词装不下整行时才断」——
|
|
76
|
+
正常列宽下 `1,294.06` 绝不会被拆开,只有真的放不下才断。 */
|
|
77
|
+
white-space: normal;
|
|
78
|
+
overflow-wrap: anywhere;
|
|
79
|
+
/* 值 + 单位按 flex 排,**允许整体换行**(单位可以整块掉到下一行)。 */
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
align-items: baseline;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* 单位:与数字同基线、小一号、次要色
|
|
86
|
+
⚠️ 用 margin-inline-start 而不是空格字符:
|
|
87
|
+
空格在等宽字族里也占一格,而 4 位数 × 十几行就会看出错位。 */
|
|
88
|
+
.metric__unit {
|
|
89
|
+
margin-inline-start: 0.3em;
|
|
90
|
+
font-size: var(--fs-sm);
|
|
91
|
+
font-weight: 400;
|
|
92
|
+
color: var(--text-secondary);
|
|
93
|
+
/* 🔴 单位**允许换行**。第一版给了 `nowrap`,结果 RTL 下阿语膨胀 1.3×
|
|
94
|
+
直接顶破列宽(实测溢出 379px):`nowrap` 会把单位变成列的**最小内容宽度**,
|
|
95
|
+
网格再想收也收不了。
|
|
96
|
+
`overflow-wrap: anywhere` 是给「一个词长过整列」的兜底 ——
|
|
97
|
+
阿/希的单位词本来就比中文长,不加它还是会溢出。 */
|
|
98
|
+
white-space: normal;
|
|
99
|
+
overflow-wrap: anywhere;
|
|
100
|
+
min-width: 0; /* flex 子项默认不缩 —— 不写这句上面的换行等于没写 */
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* 脚下那行:通常放一个 delta + 一个说明词("同比" / "较上季")*/
|
|
104
|
+
.metric__foot {
|
|
105
|
+
margin: 0;
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: baseline;
|
|
108
|
+
flex-wrap: wrap;
|
|
109
|
+
gap: var(--sp-2);
|
|
110
|
+
font-size: var(--fs-xs);
|
|
111
|
+
line-height: 1.5;
|
|
112
|
+
color: var(--text-tertiary);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.metric__note {
|
|
116
|
+
color: var(--text-tertiary);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* 迷你图槽位:放 sparkline 之类。给它一点上间距,避免贴着数字 */
|
|
120
|
+
.metric__spark {
|
|
121
|
+
margin-block-start: var(--sp-1);
|
|
122
|
+
color: var(--text-tertiary);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* 缺数:值降到 tertiary、去字重,但**仍然占位**
|
|
126
|
+
⚠️ 不要用 opacity —— 它会让对比度跌破 4.5(--text-tertiary 已经是 5.33 的地板,
|
|
127
|
+
再乘一次透明度就没了)。改令牌才是对的做法。 */
|
|
128
|
+
.metric--null .metric__value {
|
|
129
|
+
color: var(--text-tertiary);
|
|
130
|
+
font-weight: 400;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ---------------------------------------------------------------- 字号三档
|
|
134
|
+
三档够了。出现第四档说明 KPI 行的层级没规划好
|
|
135
|
+
(与 badge / card 的判断标准一致)。 */
|
|
136
|
+
.metric--lg .metric__value {
|
|
137
|
+
font-size: var(--fs-2xl);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.metric--sm .metric__value {
|
|
141
|
+
font-size: var(--fs-md);
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* ---------------------------------------------------------------- 网格容器
|
|
146
|
+
KPI 行几乎总是成排出现,把重复的栅格收进来一次。
|
|
147
|
+
⚠️ `minmax(min(100%, 12rem), 1fr)` 里的 `min(100%, …)` 不是冗余:
|
|
148
|
+
窄容器(手机单列、侧栏)下 12rem 会比可用宽度还大,
|
|
149
|
+
没有这层 min() 就会横向溢出 —— 这是 auto-fit 最常见的翻车方式。 */
|
|
150
|
+
.metric-grid {
|
|
151
|
+
display: grid;
|
|
152
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
|
|
153
|
+
gap: var(--sp-5) var(--sp-5);
|
|
154
|
+
margin: 0;
|
|
155
|
+
padding: 0;
|
|
156
|
+
list-style: none; /* 用 <ul> 包 metric 时不想看到圆点 */
|
|
157
|
+
}
|
|
@@ -78,7 +78,7 @@ th { font-weight: 600; }
|
|
|
78
78
|
|
|
79
79
|
<h2>怎么选?</h2>
|
|
80
80
|
<div class="demo">
|
|
81
|
-
<table>
|
|
81
|
+
<table aria-label="tooltip / popover / dialog 三者的选择依据">
|
|
82
82
|
<tr><th>组件</th><th>能放交互内容</th><th>背景是否锁死</th><th>用在哪</th></tr>
|
|
83
83
|
<tr><td><code>tooltip</code></td><td>❌ 只能纯文字</td><td>—</td><td>补充说明、术语解释</td></tr>
|
|
84
84
|
<tr><td><b><code>popover</code></b></td><td>✅</td><td><b>不锁</b></td><td>筛选、快捷设置、补充表单</td></tr>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* 否则造成 "landmark region proliferation"(地标泛滥)。
|
|
29
29
|
* ⇒ 加 .accordion--many 即可关闭。
|
|
30
30
|
*/
|
|
31
|
-
(function () {
|
|
31
|
+
(function (global) {
|
|
32
32
|
'use strict';
|
|
33
33
|
// 🔴 手风琴实例序号(保证 id 全局唯一,见 init 里的说明)
|
|
34
34
|
var uid = 0;
|
|
@@ -60,8 +60,13 @@
|
|
|
60
60
|
|
|
61
61
|
function toArray(x) { return Array.prototype.slice.call(x); }
|
|
62
62
|
|
|
63
|
-
/* requestAnimationFrame 的 ES5 安全版
|
|
64
|
-
|
|
63
|
+
/* requestAnimationFrame 的 ES5 安全版
|
|
64
|
+
⚠️ 必须先判 `window` 存在再摸 `.requestAnimationFrame`:
|
|
65
|
+
`typeof window.requestAnimationFrame` 会**先解引用 window**,
|
|
66
|
+
在 Node / SSR 里直接 ReferenceError(不是返回 'undefined')。
|
|
67
|
+
这条曾经让本文件在 Next.js 里 import 即崩。 */
|
|
68
|
+
var raf = (typeof window !== 'undefined' &&
|
|
69
|
+
typeof window.requestAnimationFrame === 'function')
|
|
65
70
|
? window.requestAnimationFrame.bind(window)
|
|
66
71
|
: function (fn) { return setTimeout(fn, 16); };
|
|
67
72
|
|
|
@@ -85,6 +90,12 @@
|
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
function Accordion(root, opts) {
|
|
93
|
+
/* 🔴 同一 root 只允许一个实例。
|
|
94
|
+
不加这条时 `new Accordion(root)` 两次会留下**两份** click 监听
|
|
95
|
+
(旧实例不会被回收:它的监听器闭包referencing this,还挂在 root 上)
|
|
96
|
+
⇒ 一次点击 toggle 两次,肉眼看到的是"点了没反应"。
|
|
97
|
+
构造器返回对象是 JS 合法行为,会覆盖 `this`,调用方拿到的就是旧实例。 */
|
|
98
|
+
if (root && root.accordionInstance) return root.accordionInstance;
|
|
88
99
|
this.root = root;
|
|
89
100
|
this.opts = opts || {};
|
|
90
101
|
this.items = toArray(root.querySelectorAll('.accordion__item'));
|
|
@@ -351,19 +362,28 @@
|
|
|
351
362
|
// 这是「用原生控件」的又一处收益:**无障碍行为免费且必然正确**。
|
|
352
363
|
};
|
|
353
364
|
|
|
354
|
-
|
|
365
|
+
global.Accordion = Accordion;
|
|
355
366
|
|
|
356
367
|
function init() {
|
|
357
368
|
var nodes = document.querySelectorAll('[data-accordion]');
|
|
358
369
|
for (var i = 0; i < nodes.length; i++) {
|
|
370
|
+
/* 🔴 幂等:已经初始化过就跳过。
|
|
371
|
+
不加守卫的后果是**静默的**:第二次 new 会把 accordionInstance 覆盖成新实例,
|
|
372
|
+
而**旧实例挂在 root 上的 click 监听还在** ⇒ 一次点击 toggle 两次
|
|
373
|
+
(展开又收起,看上去"点了没反应")。
|
|
374
|
+
SPA 路由切换、HMR、重复引入同一份脚本都会触发这条路。 */
|
|
375
|
+
if (nodes[i].accordionInstance) continue;
|
|
359
376
|
nodes[i].accordionInstance = new Accordion(nodes[i]);
|
|
360
377
|
}
|
|
361
378
|
}
|
|
362
379
|
|
|
363
380
|
// @autoinit: [data-accordion] —— 自动初始化;与 API.md「自动初始化」表由 autoinit-gate 双向校验
|
|
381
|
+
// ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
|
|
382
|
+
// 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
|
|
383
|
+
if (typeof document === 'undefined') return;
|
|
364
384
|
if (document.readyState === 'loading') {
|
|
365
385
|
document.addEventListener('DOMContentLoaded', init);
|
|
366
386
|
} else {
|
|
367
387
|
init();
|
|
368
388
|
}
|
|
369
|
-
})();
|
|
389
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
package/03-patterns/list/flip.js
CHANGED
package/03-patterns/nav/nav.js
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* 手动:new Nav(rootEl, { labels: { open: '打开菜单', close: '关闭菜单' } })
|
|
60
60
|
* 事件:fl-nav-open / fl-nav-close(detail = { root: <nav> })
|
|
61
61
|
*/
|
|
62
|
-
(function () {
|
|
62
|
+
(function (global) {
|
|
63
63
|
'use strict';
|
|
64
64
|
|
|
65
65
|
/* ==== BEHAVIOR INJECT BEGIN: emit ==== */
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
return root.navInstance;
|
|
414
414
|
};
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
global.Nav = Nav;
|
|
417
417
|
|
|
418
418
|
function init() {
|
|
419
419
|
each(document.querySelectorAll('[data-nav]'), function (el) {
|
|
@@ -422,9 +422,12 @@
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
// @autoinit: [data-nav] —— 扫属性自动建实例(详见 README「自动初始化」)
|
|
425
|
+
// ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
|
|
426
|
+
// 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
|
|
427
|
+
if (typeof document === 'undefined') return;
|
|
425
428
|
if (document.readyState === 'loading') {
|
|
426
429
|
document.addEventListener('DOMContentLoaded', init);
|
|
427
430
|
} else {
|
|
428
431
|
init();
|
|
429
432
|
}
|
|
430
|
-
})();
|
|
433
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
package/03-patterns/nav/toc.js
CHANGED
|
@@ -69,7 +69,7 @@ th { font-size: var(--fs-xs); color: var(--text-secondary); font-weight: 500; }
|
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<h2 class="rule-top">2 · 结构:六条硬要求</h2>
|
|
72
|
-
<table>
|
|
72
|
+
<table aria-label="分页容器的六条结构要求与理由">
|
|
73
73
|
<tr><th>要求</th><th>为什么</th></tr>
|
|
74
74
|
<tr><td>包在 <code><nav aria-label></code> 里</td>
|
|
75
75
|
<td>页面常有多个 nav(主导航/面包屑/分页),
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* });
|
|
36
36
|
* </script>
|
|
37
37
|
*/
|
|
38
|
-
(function () {
|
|
38
|
+
(function (global) {
|
|
39
39
|
'use strict';
|
|
40
40
|
|
|
41
41
|
/* ==== BEHAVIOR INJECT BEGIN: emit ==== */
|
|
@@ -385,11 +385,17 @@
|
|
|
385
385
|
},
|
|
386
386
|
};
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
/* global 已在 IIFE 收尾处解析成 window 或 globalThis(无 DOM 环境),
|
|
389
|
+
所以这里**无条件挂**——再套一层 `typeof window !== 'undefined'` 会让
|
|
390
|
+
SSR 里 require 得到 undefined(`esm/` 包装就取不到 API)。 */
|
|
391
|
+
global.Pagination = API;
|
|
389
392
|
// @autoinit: [data-pagination] —— 扫属性自动 update()(详见 API.md「自动初始化」)
|
|
393
|
+
// ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
|
|
394
|
+
// 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
|
|
395
|
+
if (typeof document === 'undefined') return;
|
|
390
396
|
if (document.readyState === 'loading') {
|
|
391
397
|
document.addEventListener('DOMContentLoaded', function () { API.update(); });
|
|
392
398
|
} else {
|
|
393
399
|
API.update();
|
|
394
400
|
}
|
|
395
|
-
})();
|
|
401
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -164,9 +164,12 @@
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// @autoinit: conditional[data-reveal-auto] —— 只有 <html data-reveal-auto> 才 enable();不写则完全不动
|
|
167
|
+
// ⚠️ 无 DOM 环境(SSR / 构建期 / Node 里 import)不自动初始化。
|
|
168
|
+
// 之前这里直接摸 document ⇒ Next.js 等 SSR 里 import 即 ReferenceError。
|
|
169
|
+
if (typeof document === 'undefined') return;
|
|
167
170
|
if (document.readyState === 'loading') {
|
|
168
171
|
document.addEventListener('DOMContentLoaded', auto, false);
|
|
169
172
|
} else {
|
|
170
173
|
auto();
|
|
171
174
|
}
|
|
172
|
-
}(
|
|
175
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|