@zhang_jifan/fanui 2.1.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/ACCESSIBILITY.md +61 -0
- package/CHANGELOG.md +112 -0
- package/COMMERCIAL-LICENSE.md +71 -0
- package/DESIGN-SYSTEM.md +294 -0
- package/LICENSE +661 -0
- package/README.md +348 -0
- package/USAGE.md +1926 -0
- package/_index.scss +20 -0
- package/dist/fanui.cjs +15 -0
- package/dist/fanui.css +28209 -0
- package/dist/fanui.d.ts +353 -0
- package/dist/fanui.esm.js +71 -0
- package/dist/fanui.js +4782 -0
- package/dist/fanui.min.css +1 -0
- package/dist/fanui.mjs +71 -0
- package/dist/modules/fanui-advanced.css +830 -0
- package/dist/modules/fanui-advanced.min.css +129 -0
- package/dist/modules/fanui-alert.css +575 -0
- package/dist/modules/fanui-alert.min.css +122 -0
- package/dist/modules/fanui-badge.css +426 -0
- package/dist/modules/fanui-badge.min.css +77 -0
- package/dist/modules/fanui-base.css +1394 -0
- package/dist/modules/fanui-base.min.css +236 -0
- package/dist/modules/fanui-blog.css +762 -0
- package/dist/modules/fanui-blog.min.css +108 -0
- package/dist/modules/fanui-button.css +526 -0
- package/dist/modules/fanui-button.min.css +56 -0
- package/dist/modules/fanui-card.css +291 -0
- package/dist/modules/fanui-card.min.css +52 -0
- package/dist/modules/fanui-choice.css +539 -0
- package/dist/modules/fanui-choice.min.css +83 -0
- package/dist/modules/fanui-dashboard.css +1057 -0
- package/dist/modules/fanui-dashboard.min.css +160 -0
- package/dist/modules/fanui-data.css +687 -0
- package/dist/modules/fanui-data.min.css +125 -0
- package/dist/modules/fanui-feedback.css +432 -0
- package/dist/modules/fanui-feedback.min.css +79 -0
- package/dist/modules/fanui-form.css +779 -0
- package/dist/modules/fanui-form.min.css +123 -0
- package/dist/modules/fanui-glass.css +497 -0
- package/dist/modules/fanui-glass.min.css +46 -0
- package/dist/modules/fanui-input-plus.css +799 -0
- package/dist/modules/fanui-input-plus.min.css +124 -0
- package/dist/modules/fanui-interactive.css +960 -0
- package/dist/modules/fanui-interactive.min.css +138 -0
- package/dist/modules/fanui-layout.css +4399 -0
- package/dist/modules/fanui-layout.min.css +1172 -0
- package/dist/modules/fanui-marketing.css +1125 -0
- package/dist/modules/fanui-marketing.min.css +144 -0
- package/dist/modules/fanui-nav.css +458 -0
- package/dist/modules/fanui-nav.min.css +76 -0
- package/dist/modules/fanui-overlay.css +547 -0
- package/dist/modules/fanui-overlay.min.css +91 -0
- package/dist/modules/fanui-table.css +290 -0
- package/dist/modules/fanui-table.min.css +62 -0
- package/dist/modules/fanui-tabs.css +333 -0
- package/dist/modules/fanui-tabs.min.css +58 -0
- package/dist/modules/fanui-themes.css +2728 -0
- package/dist/modules/fanui-themes.min.css +40 -0
- package/dist/modules/fanui-tokens.css +587 -0
- package/dist/modules/fanui-tokens.min.css +3 -0
- package/dist/modules/fanui-utilities.css +6557 -0
- package/dist/modules/fanui-utilities.min.css +1762 -0
- package/dist/modules/sizes.json +103 -0
- package/docs/MIGRATION-v2.md +179 -0
- package/examples/01-script-tag.html +149 -0
- package/examples/02-react/README.md +39 -0
- package/examples/02-react/index.html +12 -0
- package/examples/02-react/package.json +20 -0
- package/examples/02-react/src/App.jsx +108 -0
- package/examples/02-react/src/main.jsx +13 -0
- package/examples/02-react/src/useFanUI.js +87 -0
- package/examples/02-react/vite.config.js +6 -0
- package/examples/03-vue3/README.md +67 -0
- package/examples/03-vue3/index.html +12 -0
- package/examples/03-vue3/package.json +19 -0
- package/examples/03-vue3/src/App.vue +93 -0
- package/examples/03-vue3/src/main.js +8 -0
- package/examples/03-vue3/src/useFanUI.js +85 -0
- package/examples/03-vue3/vite.config.js +6 -0
- package/examples/04-vue2-cdn.html +93 -0
- package/examples/README.md +34 -0
- package/package.json +127 -0
- package/src/_functions.scss +319 -0
- package/src/_mixins.scss +1202 -0
- package/src/_tokens.scss +180 -0
- package/src/_variables.scss +491 -0
- package/src/base/_animations.scss +210 -0
- package/src/base/_reset.scss +359 -0
- package/src/base/_rtl.scss +133 -0
- package/src/base/_typography.scss +353 -0
- package/src/components/_advanced.scss +763 -0
- package/src/components/_alert.scss +322 -0
- package/src/components/_badge.scss +279 -0
- package/src/components/_blog.scss +693 -0
- package/src/components/_button.scss +351 -0
- package/src/components/_capabilities.scss +463 -0
- package/src/components/_card.scss +281 -0
- package/src/components/_choice.scss +444 -0
- package/src/components/_dashboard.scss +950 -0
- package/src/components/_data.scss +618 -0
- package/src/components/_feedback.scss +403 -0
- package/src/components/_form.scss +408 -0
- package/src/components/_glass.scss +374 -0
- package/src/components/_input-plus.scss +730 -0
- package/src/components/_interactive.scss +885 -0
- package/src/components/_marketing.scss +982 -0
- package/src/components/_nav.scss +480 -0
- package/src/components/_overlay.scss +517 -0
- package/src/components/_table.scss +276 -0
- package/src/components/_tabs.scss +338 -0
- package/src/fanui.d.ts +353 -0
- package/src/fanui.js +4782 -0
- package/src/fanui.scss +79 -0
- package/src/glass/_switches.scss +36 -0
- package/src/glass/_tokens.scss +259 -0
- package/src/layout/_container.scss +111 -0
- package/src/layout/_flex.scss +164 -0
- package/src/layout/_grid.scss +180 -0
- package/src/themes/_accents.scss +201 -0
- package/src/themes/_palettes.scss +180 -0
- package/src/utilities/_borders.scss +104 -0
- package/src/utilities/_display.scss +145 -0
- package/src/utilities/_effects.scss +210 -0
- package/src/utilities/_sizing.scss +123 -0
- package/src/utilities/_spacing.scss +118 -0
- package/src/utilities/_texture.scss +323 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tokens": {
|
|
3
|
+
"css": 22.4,
|
|
4
|
+
"min": 19.4
|
|
5
|
+
},
|
|
6
|
+
"themes": {
|
|
7
|
+
"css": 104,
|
|
8
|
+
"min": 90
|
|
9
|
+
},
|
|
10
|
+
"base": {
|
|
11
|
+
"css": 25.3,
|
|
12
|
+
"min": 21.2
|
|
13
|
+
},
|
|
14
|
+
"layout": {
|
|
15
|
+
"css": 69.3,
|
|
16
|
+
"min": 53.7
|
|
17
|
+
},
|
|
18
|
+
"button": {
|
|
19
|
+
"css": 20.5,
|
|
20
|
+
"min": 18.4
|
|
21
|
+
},
|
|
22
|
+
"input-plus": {
|
|
23
|
+
"css": 22.5,
|
|
24
|
+
"min": 19.7
|
|
25
|
+
},
|
|
26
|
+
"interactive": {
|
|
27
|
+
"css": 34.4,
|
|
28
|
+
"min": 30.9
|
|
29
|
+
},
|
|
30
|
+
"marketing": {
|
|
31
|
+
"css": 46.3,
|
|
32
|
+
"min": 42
|
|
33
|
+
},
|
|
34
|
+
"blog": {
|
|
35
|
+
"css": 25.3,
|
|
36
|
+
"min": 22.5
|
|
37
|
+
},
|
|
38
|
+
"dashboard": {
|
|
39
|
+
"css": 33.9,
|
|
40
|
+
"min": 30.1
|
|
41
|
+
},
|
|
42
|
+
"form": {
|
|
43
|
+
"css": 27,
|
|
44
|
+
"min": 24.4
|
|
45
|
+
},
|
|
46
|
+
"choice": {
|
|
47
|
+
"css": 17.9,
|
|
48
|
+
"min": 16
|
|
49
|
+
},
|
|
50
|
+
"card": {
|
|
51
|
+
"css": 7.2,
|
|
52
|
+
"min": 6.3
|
|
53
|
+
},
|
|
54
|
+
"table": {
|
|
55
|
+
"css": 7.5,
|
|
56
|
+
"min": 6.5
|
|
57
|
+
},
|
|
58
|
+
"badge": {
|
|
59
|
+
"css": 10.2,
|
|
60
|
+
"min": 8.9
|
|
61
|
+
},
|
|
62
|
+
"alert": {
|
|
63
|
+
"css": 17.1,
|
|
64
|
+
"min": 15.3
|
|
65
|
+
},
|
|
66
|
+
"nav": {
|
|
67
|
+
"css": 11.5,
|
|
68
|
+
"min": 9.9
|
|
69
|
+
},
|
|
70
|
+
"tabs": {
|
|
71
|
+
"css": 9.4,
|
|
72
|
+
"min": 8.2
|
|
73
|
+
},
|
|
74
|
+
"overlay": {
|
|
75
|
+
"css": 14.7,
|
|
76
|
+
"min": 12.8
|
|
77
|
+
},
|
|
78
|
+
"feedback": {
|
|
79
|
+
"css": 10.5,
|
|
80
|
+
"min": 9.1
|
|
81
|
+
},
|
|
82
|
+
"data": {
|
|
83
|
+
"css": 17.7,
|
|
84
|
+
"min": 15.5
|
|
85
|
+
},
|
|
86
|
+
"advanced": {
|
|
87
|
+
"css": 23.1,
|
|
88
|
+
"min": 20.2
|
|
89
|
+
},
|
|
90
|
+
"glass": {
|
|
91
|
+
"css": 30.9,
|
|
92
|
+
"min": 28.9
|
|
93
|
+
},
|
|
94
|
+
"utilities": {
|
|
95
|
+
"css": 103.4,
|
|
96
|
+
"min": 82.3
|
|
97
|
+
},
|
|
98
|
+
"_full": {
|
|
99
|
+
"css": 726.5,
|
|
100
|
+
"min": 629.2
|
|
101
|
+
},
|
|
102
|
+
"_js": 208
|
|
103
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# fanUI v1 → v2 迁移指南
|
|
2
|
+
|
|
3
|
+
> 本文对应 `CHANGELOG.md` 的 `[2.1.0]` / `[2.0.1]` / `[2.0.0]` 三节。
|
|
4
|
+
> 所有 API 名称与签名均取自仓库源码(`src/fanui.js`、`src/_mixins.scss`、`src/glass/`、`src/components/`),
|
|
5
|
+
> 可在对应文件里核对;文中标注了出处行号,方便逐条验证。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. 兼容性承诺(先看这里)
|
|
10
|
+
|
|
11
|
+
v2 **没有重命名任何 v1 的类名与 `data-*` 属性**,v1 的 JS API 也全部保留为「兼容别名」。
|
|
12
|
+
因此绝大多数项目**只需替换 `dist` 产物即可完成迁移**,不必改动模板。
|
|
13
|
+
|
|
14
|
+
需要人工处理的是三类:
|
|
15
|
+
|
|
16
|
+
| 类别 | 是否必须改 | 说明 |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| SCSS 源码定制(`@use` / `@include`) | 视用法而定 | v2 重新组织了 mixin 的「主名称」;旧名称保留为别名,但建议迁移到新名称 |
|
|
19
|
+
| 玻璃(Liquid Glass)相关写法 | 建议改 | v2 把「开关」从类名/局部 mixin 提升为**根属性 + 令牌强度体系** |
|
|
20
|
+
| 明暗切换的调用方式 | 可选 | v1 的 `FanUI.theme.set('dark')` 仍可用;推荐改用 `setMode()` 以区分「模式」与「主题色」 |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. 核心变化:从「单一明暗开关」到「三条正交轴」
|
|
25
|
+
|
|
26
|
+
v1 只有一条轴:明暗。v2 拆成三条**互相正交、可任意组合**的轴,全部由 `<html>` 上的属性驱动:
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<html data-fanui-accent="cyan" data-fanui-theme="dark" data-fanui-glass="on">
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| 轴 | 属性 | 取值 | 控制方式 |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| 主题色 | `data-fanui-accent` | 12 套(详见 `USAGE.md` §6) | `FanUI.theme.setAccent(key)` |
|
|
35
|
+
| 模式 | `data-fanui-theme` | `light` / `dark` / `auto` | `FanUI.theme.setMode(mode)` |
|
|
36
|
+
| 液态玻璃 | `data-fanui-glass` | `on` / `off` / 强度 0–2 / 预设名 | `FanUI.theme.setGlass()`、`setGlassIntensity()`、`setGlassPreset()` |
|
|
37
|
+
|
|
38
|
+
**`auto` 是 v2 新增取值**:跟随系统 `prefers-color-scheme`。v1 只认 `light` / `dark`,
|
|
39
|
+
如果你在 v1 里自己写了媒体查询做跟随,v2 可以直接删掉。
|
|
40
|
+
|
|
41
|
+
### 1.1 v1 调用方式的去向
|
|
42
|
+
|
|
43
|
+
v1 的 `get` / `set` / `toggle` 仍然可用,是 v2 `getMode` / `setMode` / `toggleMode` 的别名
|
|
44
|
+
(`src/fanui.js:1103` 起,「兼容 v1 API」段;`USAGE.md` §3.1 有对照表):
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// v1 写法(仍然有效)
|
|
48
|
+
FanUI.theme.set("dark");
|
|
49
|
+
FanUI.theme.toggle();
|
|
50
|
+
|
|
51
|
+
// v2 推荐写法(语义更明确:改的是「模式」,不是「主题色」)
|
|
52
|
+
FanUI.theme.setMode("dark");
|
|
53
|
+
FanUI.theme.toggleMode();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> 为什么要改名:v2 里「主题」一词同时能指主题色和明暗模式。`setMode` / `setAccent` 把两者区分开,
|
|
57
|
+
> 避免出现「调了主题色,结果明暗也变了」的误解。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. 液态玻璃(Liquid Glass)API 迁移
|
|
62
|
+
|
|
63
|
+
这是 v1 → v2 变化最大的一块。
|
|
64
|
+
|
|
65
|
+
### 2.1 SCSS mixin:主名称变了,旧名称保留为别名
|
|
66
|
+
|
|
67
|
+
| v1 用法 | v2 主名称 | v2 状态 |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `@include mx.fanui-liquid-glass($radius: 16px)` | `@include mx.fanui-glass-material($radius: 16px)` | v1 名称**保留为别名**,签名完全一致(`src/_mixins.scss:1144`) |
|
|
70
|
+
| `@include mx.fanui-glass($bg, $blur, $saturate)`(早期简单毛玻璃) | — | 保留,标注为「兼容旧 API」(`src/_mixins.scss:1188`) |
|
|
71
|
+
|
|
72
|
+
两者的完整签名(`src/_mixins.scss:1088`):
|
|
73
|
+
|
|
74
|
+
```scss
|
|
75
|
+
@include mx.fanui-glass-material(
|
|
76
|
+
$radius: null, // null → 取 --fanui-glass-radius,再退回 $fanui-radius-lg
|
|
77
|
+
$highlight: true, // 指针镜面高光
|
|
78
|
+
$edge: true, // 边缘流光(::after 遮罩描边)
|
|
79
|
+
$flow: false // 品牌色流体层
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**迁移动作**:把 `fanui-liquid-glass` 全局替换为 `fanui-glass-material`。
|
|
84
|
+
不改也能跑,但新写法才是 v2 的正式 API。
|
|
85
|
+
|
|
86
|
+
### 2.2 v2 新增的玻璃构件 mixin
|
|
87
|
+
|
|
88
|
+
v2 把玻璃拆成了可组合的构件,需要精细控制时用它们而不是整体材质:
|
|
89
|
+
|
|
90
|
+
- `fanui-glass-tint()` / `fanui-glass-sheen()` / `fanui-glass-layer($flow)` —— 底色 / 高光 / 流体层
|
|
91
|
+
- `fanui-glass-refraction()` / `fanui-glass-border-color()` / `fanui-glass-depth()` —— 折射 / 描边色 / 景深
|
|
92
|
+
- `fanui-glass-surface($sheen, $shadow, $border, $refraction)` —— 组件级表面
|
|
93
|
+
- `fanui-glass-off()` —— 单点强制退回实体表面
|
|
94
|
+
- `fanui-glass-legibility()` / `fanui-glass-readable-floor($alpha)` / `fanui-glass-readable-text()` —— 可读性保护
|
|
95
|
+
|
|
96
|
+
### 2.3 开关与强度:从「类名」到「令牌」
|
|
97
|
+
|
|
98
|
+
v1 的玻璃是「有或没有」。v2 由根属性 + 连续强度驱动:
|
|
99
|
+
|
|
100
|
+
- **总开关**:`data-fanui-glass="off"` 关闭、`"on"` 开启。
|
|
101
|
+
> ⚠️ v2.0.1 修过一个重要缺陷:关闭态声明因与主题块同权重、输出在前而被覆盖。
|
|
102
|
+
> 现已独立成 `src/glass/_switches.scss` 并置于产物最末(`CHANGELOG.md` `[2.0.1]`)。
|
|
103
|
+
> 若你曾用 `!important` 打补丁强行关玻璃,升级后可以移除。
|
|
104
|
+
- **连续强度**:`FanUI.theme.setGlassIntensity(1.4)`,取值范围 `0–2`;预设走 `setGlassPreset('thin'|'regular'|'thick'|'ultra'|'lens')`。
|
|
105
|
+
- **降级链**:`off` / `reduced-transparency` / `reduced-motion` / `@supports` / `forced-colors` 五档。
|
|
106
|
+
无 `backdrop-filter` 支持时自动退回实体表面(`src/_mixins.scss:1137`)。
|
|
107
|
+
- 关闭态兜底选择器从「手工列举 11 个」改为 `[class*="--glass"]` 子串匹配(`CHANGELOG.md` `[2.0.1]`)。
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 3. SCSS 配置入口
|
|
112
|
+
|
|
113
|
+
v1 文档宣传的 `@use "@zhang_jifan/fanui" with ($fanui-*: ...)` 曾**实际报错**,v2 已修复:
|
|
114
|
+
|
|
115
|
+
```scss
|
|
116
|
+
// 现在可用
|
|
117
|
+
@use "@zhang_jifan/fanui" with (
|
|
118
|
+
$fanui-primary: #6d5efc
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
修复方式是在入口补 `@forward "variables"`(`CHANGELOG.md` `[2.1.0]`)。
|
|
123
|
+
如果你在 v1 里因为该报错而放弃定制、改用覆盖 CSS 变量的方式,现在可以直接用配置入口了。
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. 无障碍相关(v2 收紧,可能需要调整)
|
|
128
|
+
|
|
129
|
+
v2 把无障碍从「建议」提到「门禁强制」,以下为**行为变化**而非 API 变化:
|
|
130
|
+
|
|
131
|
+
- **统一焦点环令牌** `--fanui-focus-ring-*`(C3)。自定义组件请改用它,别再手写 `outline`。
|
|
132
|
+
- **`skip-link` 统一实现**(C4)。若项目里自己写过跳过导航链接,可能与 fanUI 的重复。
|
|
133
|
+
- **`FanUI.a11y.focusableScrollables()`**:给「确实可滚动但内部无可聚焦元素」的容器自动补
|
|
134
|
+
`tabindex` + `role="region"` + `aria-label`,在 `init` / `load` / `resize` 时幂等执行。
|
|
135
|
+
这会**给部分容器新增 `tabindex`**,如果你的测试断言 DOM 结构须留意。
|
|
136
|
+
- **主题色自动满足 WCAG AA**:v2 派生的实心色在构建期校验对比度;v1 里为提对比度而硬编码的颜色可以移除。
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 5. 构件与产物
|
|
141
|
+
|
|
142
|
+
| 项 | v1 | v2 |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| JS 入口 | IIFE | IIFE + ESM(`dist/fanui.esm.js`)+ CJS(`dist/fanui.cjs`) |
|
|
145
|
+
| 非浏览器环境 | 导出 `null` | **导出服务端门面**(`isServer: true` + 可直接出标记的 `FanUI.ssr`) |
|
|
146
|
+
| 分模块产物 | — | `dist/modules/fanui-<name>.css`(24 个)+ `sizes.json` 体积清单 |
|
|
147
|
+
| 体积门禁 | — | `scripts/check-size.cjs` + `npm run gates` |
|
|
148
|
+
|
|
149
|
+
> **SSR 用户注意**:v2.1 起 `require("@zhang_jifan/fanui")` / `import FanUI from "@zhang_jifan/fanui"` 在 Node 下
|
|
150
|
+
> **不再返回 `null`**,而是返回服务端门面。若你有 `if (!FanUI) return;` 这类防御代码,
|
|
151
|
+
> 它不再会命中(门面始终为真值),可以直接删除。详见 `USAGE.md` §1.7。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 6. 升级步骤(清单)
|
|
156
|
+
|
|
157
|
+
1. **替换产物**:`dist/fanui.css` / `fanui.js`(或改 `package.json` 版本到 `^2.1.0`)。
|
|
158
|
+
2. **保留 v1 调用即可先跑通**:类名、`data-*`、`FanUI.theme.set/toggle` 均兼容。
|
|
159
|
+
3. **逐条替换 SCSS 旧名称**(仅源码定制用户):`fanui-liquid-glass` → `fanui-glass-material`。
|
|
160
|
+
4. **收敛玻璃开关**:移除自行打补丁的 `!important`;改用 `data-fanui-glass="off"` 与强度 API。
|
|
161
|
+
5. **明暗调用改名**(可选但推荐):`set` → `setMode`、`toggle` → `toggleMode`。
|
|
162
|
+
6. **检查无障碍断言**:`focusableScrollables()` 会新增 `tabindex`;焦点环改用统一令牌。
|
|
163
|
+
7. **跑门禁**:`npm run gates`(14 项:lint / audit / demo / matrix / examples / **ssr** / entries /
|
|
164
|
+
components / size / unit / a11y / browsers / visual / edge)。
|
|
165
|
+
8. **SSR 项目**:确认已启用 `FanUI.ssr` 直出,或删除 `if (!FanUI)` 守卫。
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 7. 需要你自行核对的部分
|
|
170
|
+
|
|
171
|
+
本文只覆盖**仓库内可验证**的变化。以下情况仓库里没有对应记录,升级前请自行确认:
|
|
172
|
+
|
|
173
|
+
- v1 中你**自行扩展**的 SCSS(`@use` 内部私有变量)——v2 重组了 `src/` 目录,私有路径可能变动。
|
|
174
|
+
- v1 中你**覆盖过的 CSS 变量**——v2 新增了角色令牌(`--{name}-ink` / `--{name}-fg`),
|
|
175
|
+
旧的直接覆写仍然生效,但可能不再是推荐做法。
|
|
176
|
+
- 视觉回归基线:v2 校正了多处对比度与玻璃可读性(见 `CHANGELOG.md` `[2.1.0]` 的「修复」节),
|
|
177
|
+
若你维护截图基线,需要重新生成 —— 执行 `node scripts/verify-visual.cjs` 并按提示更新 `shots/visual-baseline/`。
|
|
178
|
+
|
|
179
|
+
如发现本文与实际行为不符,请以源码为准并提 issue。
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!-- =============================================================================
|
|
3
|
+
fanUI · 示例 01:<script> 直接引入(下载后双击即可运行,不需要构建工具)
|
|
4
|
+
-----------------------------------------------------------------------------
|
|
5
|
+
本文件不需要 npm、不需要联网、不需要任何打包工具。
|
|
6
|
+
唯一的两个文件依赖:
|
|
7
|
+
../dist/fanui.css 样式(必选)
|
|
8
|
+
../dist/fanui.js 交互运行时(可选,仅用到交互/主题开关时才需要)
|
|
9
|
+
打开方式:直接双击本文件,或拖进浏览器。
|
|
10
|
+
========================================================================== -->
|
|
11
|
+
<html lang="zh-CN" data-fanui-theme="light" data-fanui-accent="cyan" data-fanui-glass="on">
|
|
12
|
+
<!-- 上面三个属性就是「三条运行时轴」:
|
|
13
|
+
data-fanui-theme 深浅模式:light | dark | auto(跟随系统)
|
|
14
|
+
data-fanui-accent 主题色:12 选 1,默认 cyan
|
|
15
|
+
data-fanui-glass 液态玻璃:on | off -->
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
19
|
+
<title>fanUI 示例 01 · script 引入</title>
|
|
20
|
+
|
|
21
|
+
<!-- ① 引入样式(必选,且必须放在所有组件之前) -->
|
|
22
|
+
<link rel="stylesheet" href="../dist/fanui.css">
|
|
23
|
+
|
|
24
|
+
<!-- 页面自己的小样式,仅用于排版演示内容 -->
|
|
25
|
+
<style>
|
|
26
|
+
body { padding: 32px; max-width: 900px; margin: 0 auto; }
|
|
27
|
+
.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
|
|
28
|
+
.card + .card { margin-top: 20px; }
|
|
29
|
+
h2 { margin: 0 0 4px; font-size: 1.05rem; }
|
|
30
|
+
.muted { color: var(--fanui-color-text-muted); font-size: .875rem; }
|
|
31
|
+
.bg-demo {
|
|
32
|
+
padding: 28px;
|
|
33
|
+
border-radius: 16px;
|
|
34
|
+
background: var(--fanui-bg-aurora, linear-gradient(120deg, #06b6d4, #8b5cf6));
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
<body class="fanui-bg-aurora">
|
|
39
|
+
|
|
40
|
+
<h1 class="fanui-fs-3xl fanui-fw-bold">fanUI · script 引入示例</h1>
|
|
41
|
+
<p class="muted">
|
|
42
|
+
当前主题色:<code class="fanui-code" id="accentNow">cyan</code> ·
|
|
43
|
+
模式:<code class="fanui-code" id="modeNow">light</code> ·
|
|
44
|
+
玻璃:<code class="fanui-code" id="glassNow">on</code>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<!-- ② 主题色 / 模式 / 玻璃 三轴控制按钮:全部是「纯 JS 调用样式」 -->
|
|
48
|
+
<div class="card fanui-card fanui-p-4">
|
|
49
|
+
<h2>三轴运行时控制</h2>
|
|
50
|
+
<p class="muted">下面每个按钮点击后立即生效,无需刷新、无需重新编译 CSS。</p>
|
|
51
|
+
<div class="row" style="margin-top:12px">
|
|
52
|
+
<button class="fanui-btn fanui-btn--primary" onclick="FanUI.theme.nextAccent()">下一个主题色</button>
|
|
53
|
+
<button class="fanui-btn fanui-btn--neutral" onclick="FanUI.theme.toggleMode()">浅色 / 深色</button>
|
|
54
|
+
<button class="fanui-btn fanui-btn--neutral" onclick="FanUI.theme.toggleGlass()">玻璃开 / 关</button>
|
|
55
|
+
<button class="fanui-btn fanui-btn--outline" onclick="FanUI.theme.setGlassIntensity(1.8)">液态强度 → 1.8×</button>
|
|
56
|
+
<button class="fanui-btn fanui-btn--ghost" onclick="FanUI.studio.api().open()">打开主题工作台</button>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- 12 个主题色快捷切换:遍历主题元数据生成 -->
|
|
60
|
+
<div class="row" id="accentGrid" style="margin-top:14px"></div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- ③ 常用组件:卡片 + 表单 + 徽标 + 提示 -->
|
|
64
|
+
<div class="card fanui-card fanui-card--glass fanui-p-4 card">
|
|
65
|
+
<h2>液态玻璃卡片</h2>
|
|
66
|
+
<p class="muted">class="fanui-card fanui-card--glass",玻璃开关关闭时会自动退回实体表面。</p>
|
|
67
|
+
<div class="row" style="margin-top:12px">
|
|
68
|
+
<span class="fanui-badge fanui-badge--primary">主要</span>
|
|
69
|
+
<span class="fanui-badge fanui-badge--success fanui-badge--soft">成功</span>
|
|
70
|
+
<span class="fanui-badge fanui-badge--warning fanui-badge--outline">警告</span>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="card fanui-card fanui-p-4 card">
|
|
75
|
+
<h2>表单与校验</h2>
|
|
76
|
+
<!-- data-fanui-validate 开启原生校验 + 自动错误态 -->
|
|
77
|
+
<form data-fanui-validate onsubmit="event.preventDefault(); Fansubmit(this)">
|
|
78
|
+
<div class="fanui-form-item">
|
|
79
|
+
<label class="fanui-form-label fanui-form-label--required" for="email">邮箱(必填)</label>
|
|
80
|
+
<input class="fanui-form-control" type="email" id="email" required placeholder="you@example.com">
|
|
81
|
+
<span class="fanui-form-feedback"></span>
|
|
82
|
+
</div>
|
|
83
|
+
<button class="fanui-btn fanui-btn--primary" type="submit">提交</button>
|
|
84
|
+
</form>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- ④ 浮层:模态框 + 触发按钮(声明式,无需写 JS) -->
|
|
88
|
+
<div class="row">
|
|
89
|
+
<button class="fanui-btn fanui-btn--primary" data-fanui-modal="#demoModal">打开模态框</button>
|
|
90
|
+
<button class="fanui-btn fanui-btn--neutral" onclick="FanUI.toast.success('操作成功','这是第二条说明文字')">弹一个 Toast</button>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- 尺寸修饰符写在 .fanui-modal 上:fanui-modal--sm / --md / --lg / --xl / --full -->
|
|
94
|
+
<div class="fanui-modal fanui-modal--md" id="demoModal" aria-hidden="true">
|
|
95
|
+
<div class="fanui-modal__backdrop"></div>
|
|
96
|
+
<div class="fanui-modal__dialog">
|
|
97
|
+
<header class="fanui-modal__header">
|
|
98
|
+
<h3 class="fanui-modal__title">声明式模态框</h3>
|
|
99
|
+
<button class="fanui-modal__close" data-fanui-dismiss aria-label="关闭">×</button>
|
|
100
|
+
</header>
|
|
101
|
+
<div class="fanui-modal__body">
|
|
102
|
+
<p>点右上角 ×、点遮罩、按 Esc 都能关闭。</p>
|
|
103
|
+
</div>
|
|
104
|
+
<footer class="fanui-modal__footer">
|
|
105
|
+
<button class="fanui-btn fanui-btn--neutral" data-fanui-dismiss>取消</button>
|
|
106
|
+
<button class="fanui-btn fanui-btn--primary" data-fanui-dismiss>确定</button>
|
|
107
|
+
</footer>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- ⑤ 引入运行时(可选;放在 </body> 前)。
|
|
112
|
+
它会自动初始化页面上所有 data-fanui-* 声明式组件。 -->
|
|
113
|
+
<script src="../dist/fanui.js"></script>
|
|
114
|
+
<script>
|
|
115
|
+
/* ---- 页面自己的业务代码 ---- */
|
|
116
|
+
function Fansubmit(form) {
|
|
117
|
+
FanUI.toast.success("表单已提交", "通过校验后才会走到这里");
|
|
118
|
+
form.reset();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* 渲染 12 个主题色色卡:FanUI.theme.accents 是主题元数据数组 */
|
|
122
|
+
var grid = document.getElementById("accentGrid");
|
|
123
|
+
FanUI.theme.accents.forEach(function (a) {
|
|
124
|
+
var b = document.createElement("button");
|
|
125
|
+
b.className = "fanui-btn fanui-btn--neutral fanui-btn--sm";
|
|
126
|
+
b.type = "button";
|
|
127
|
+
b.textContent = a.label + " " + a.en; // 例:青 Cyan
|
|
128
|
+
b.title = a.vibe; // 例:科技 · 清爽
|
|
129
|
+
b.onclick = function () { FanUI.theme.setAccent(a.key); };
|
|
130
|
+
grid.appendChild(b);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/* 把当前状态回显到页面;主题变化时自动更新 */
|
|
134
|
+
function sync() {
|
|
135
|
+
var s = FanUI.theme.snapshot();
|
|
136
|
+
document.getElementById("accentNow").textContent = s.accent;
|
|
137
|
+
document.getElementById("modeNow").textContent = s.mode + "(实际 " + s.effectiveMode + ")";
|
|
138
|
+
document.getElementById("glassNow").textContent = s.glass ? "on" : "off";
|
|
139
|
+
}
|
|
140
|
+
document.addEventListener("fanui:accentchange", sync);
|
|
141
|
+
document.addEventListener("fanui:themechange", sync);
|
|
142
|
+
document.addEventListener("fanui:glasschange", sync);
|
|
143
|
+
sync();
|
|
144
|
+
|
|
145
|
+
/* 可选:挂载右下角的主题工作台悬浮面板(Alt+T 开合,Alt+D 切深浅) */
|
|
146
|
+
FanUI.studio.mount({ defaultOpen: false });
|
|
147
|
+
</script>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# fanUI × React 示例(Vite + React 18)
|
|
2
|
+
|
|
3
|
+
## 运行
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd examples/02-react
|
|
7
|
+
npm install # fanui 通过 "file:../.." 直接引用仓库源码,无需发布到 npm
|
|
8
|
+
npm run dev # 打开终端输出的 http://localhost:5173
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 版本兼容
|
|
12
|
+
|
|
13
|
+
| React 版本 | 是否可用 | 需要改动 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| React 19 | ✅ | 无 |
|
|
16
|
+
| React 18 | ✅ | 无(本示例即 18) |
|
|
17
|
+
| React 17 | ✅ | `main.jsx` 改回 `ReactDOM.render(<App/>, root)` |
|
|
18
|
+
| React 16.8+ | ✅ | 同上(Hook 从 16.8 开始可用) |
|
|
19
|
+
| React < 16.8 | ✅(不用 Hook) | 不用本目录 Hook,改在 `componentDidMount` 里调用 `FanUI.init()` |
|
|
20
|
+
|
|
21
|
+
React 17 及以下的入口写法:
|
|
22
|
+
|
|
23
|
+
```jsx
|
|
24
|
+
import React from "react";
|
|
25
|
+
import ReactDOM from "react-dom";
|
|
26
|
+
import App from "./App.jsx";
|
|
27
|
+
import "@zhang_jifan/fanui/style.css";
|
|
28
|
+
|
|
29
|
+
ReactDOM.render(<App />, document.getElementById("root"));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 关键约定
|
|
33
|
+
|
|
34
|
+
1. **样式是全局 CSS**:`import "@zhang_jifan/fanui/style.css"` 一次即可,不要放进组件。
|
|
35
|
+
2. **类名直接照抄官方文档**:`className="fanui-btn fanui-btn--primary"`。
|
|
36
|
+
3. **JS API 走 Hook**:见 `src/useFanUI.js`(`useFanUI` / `useFanTheme` / `useFanOverlay` / `useToast`)。
|
|
37
|
+
4. **SSR(Next.js)**:fanUI 在无 DOM 环境安全导出 `null`;把 `FanUI.init()` 放进 `useEffect`,
|
|
38
|
+
或用 `next/dynamic` + `ssr: false`。样式仍可在 `_app.js` 顶层 `import "@zhang_jifan/fanui/style.css"`。
|
|
39
|
+
5. **React 18 StrictMode**:`FanUI.init()` 幂等(内部有防重复标记),double-effect 无副作用。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" data-fanui-theme="light" data-fanui-accent="cyan" data-fanui-glass="on">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>fanUI · React 示例</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fanui-example-react",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "2.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@zhang_jifan/fanui": "file:../..",
|
|
13
|
+
"react": "^18.3.1",
|
|
14
|
+
"react-dom": "^18.3.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
18
|
+
"vite": "^5.4.11"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { useFanUI, useFanTheme, useFanOverlay, useToast } from "./useFanUI";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* fanUI 在 React 中的使用范式:
|
|
6
|
+
* - HTML/CSS 部分照抄官方类名(fanui-*),完全不用改写成「React 组件」;
|
|
7
|
+
* - 需要「控制」的地方(切主题/开关浮层/弹 Toast)通过本文件的 Hook 调用 FanUI API。
|
|
8
|
+
*/
|
|
9
|
+
export default function App() {
|
|
10
|
+
useFanUI({ studio: true }); // 初始化一次(幂等)
|
|
11
|
+
const theme = useFanTheme();
|
|
12
|
+
const toast = useToast();
|
|
13
|
+
const modal = useFanOverlay("#demoModal", { onOpen: () => console.log("modal opened") });
|
|
14
|
+
const [brand, setBrand] = useState("#ff6b35");
|
|
15
|
+
|
|
16
|
+
if (!theme.state) {
|
|
17
|
+
return <p style={{ padding: 24 }}>当前环境无 DOM(SSR),请在客户端渲染。</p>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const s = theme.state;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<main className="fanui-container" style={{ padding: "32px 16px" }}>
|
|
24
|
+
<h1 className="fanui-fs-3xl fanui-fw-bold">fanUI × React 示例</h1>
|
|
25
|
+
<p className="fanui-text-muted">
|
|
26
|
+
主题色 {s.accent} · 模式 {s.mode}(实际 {s.effectiveMode})· 玻璃 {s.glass ? "on" : "off"}
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
{/* ---------- 主题色 12 选 1 ---------- */}
|
|
30
|
+
<section className="fanui-card fanui-p-6" style={{ marginTop: 24 }}>
|
|
31
|
+
<h2 className="fanui-fs-xl fanui-fw-semibold">主题色</h2>
|
|
32
|
+
<div className="fanui-flex fanui-flex-wrap fanui-gap-2" style={{ marginTop: 12 }}>
|
|
33
|
+
{s.accents.map((a) => (
|
|
34
|
+
<button
|
|
35
|
+
key={a.key}
|
|
36
|
+
type="button"
|
|
37
|
+
onClick={() => theme.setAccent(a.key)}
|
|
38
|
+
className={"fanui-btn fanui-btn--sm " + (s.accent === a.key ? "fanui-btn--primary" : "fanui-btn--neutral")}
|
|
39
|
+
title={a.vibe}
|
|
40
|
+
>
|
|
41
|
+
{a.label} {a.en}
|
|
42
|
+
</button>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* ---------- 深浅模式 / 玻璃 ---------- */}
|
|
47
|
+
<div className="fanui-flex fanui-flex-wrap fanui-gap-2" style={{ marginTop: 16 }}>
|
|
48
|
+
<button className="fanui-btn fanui-btn--neutral" onClick={theme.toggleMode}>浅色 / 深色</button>
|
|
49
|
+
<button className="fanui-btn fanui-btn--neutral" onClick={() => theme.setGlass(!s.glass)}>
|
|
50
|
+
玻璃{s.glass ? "关" : "开"}
|
|
51
|
+
</button>
|
|
52
|
+
<button className="fanui-btn fanui-btn--ghost" onClick={theme.nextAccent}>下一个主题色</button>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{/* ---------- 任意品牌色 → 主题 ---------- */}
|
|
56
|
+
<div className="fanui-flex fanui-gap-2" style={{ marginTop: 16 }}>
|
|
57
|
+
<input
|
|
58
|
+
className="fanui-form-control"
|
|
59
|
+
style={{ maxWidth: 200 }}
|
|
60
|
+
value={brand}
|
|
61
|
+
onChange={(e) => setBrand(e.target.value)}
|
|
62
|
+
placeholder="#ff6b35"
|
|
63
|
+
/>
|
|
64
|
+
<button
|
|
65
|
+
className="fanui-btn fanui-btn--primary"
|
|
66
|
+
onClick={() => { const name = theme.createAccent(brand); toast.success("已生成主题:" + name); }}
|
|
67
|
+
>
|
|
68
|
+
由品牌色生成主题
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
{/* ---------- 玻璃卡片 + 声明式组件 ---------- */}
|
|
74
|
+
<section className="fanui-card fanui-card--glass fanui-p-6" style={{ marginTop: 20 }}>
|
|
75
|
+
<h2 className="fanui-fs-xl fanui-fw-semibold">液态玻璃卡片</h2>
|
|
76
|
+
<div className="fanui-flex fanui-flex-wrap fanui-gap-2" style={{ marginTop: 12 }}>
|
|
77
|
+
<span className="fanui-badge fanui-badge--primary">React 16+</span>
|
|
78
|
+
<span className="fanui-badge fanui-badge--success fanui-badge--soft">零适配成本</span>
|
|
79
|
+
<span className="fanui-badge fanui-badge--warning fanui-badge--outline">CSS 全量可用</span>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<div className="fanui-flex fanui-gap-2" style={{ marginTop: 20 }}>
|
|
84
|
+
<button className="fanui-btn fanui-btn--primary" onClick={modal.open}>打开模态框</button>
|
|
85
|
+
<button className="fanui-btn fanui-btn--neutral" onClick={() => toast.success("操作成功", "来自 React 事件")}>
|
|
86
|
+
弹 Toast
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* 浮层 DOM 直接写在 JSX 里即可,class 名与官方文档完全一致 */}
|
|
91
|
+
<div className="fanui-modal fanui-modal--md" id="demoModal" aria-hidden="true">
|
|
92
|
+
<div className="fanui-modal__backdrop" />
|
|
93
|
+
<div className="fanui-modal__dialog">
|
|
94
|
+
<header className="fanui-modal__header">
|
|
95
|
+
<h3 className="fanui-modal__title">React 中的模态框</h3>
|
|
96
|
+
<button className="fanui-modal__close" data-fanui-dismiss aria-label="关闭">×</button>
|
|
97
|
+
</header>
|
|
98
|
+
<div className="fanui-modal__body">
|
|
99
|
+
<p>Esc / 遮罩 / 关闭按钮都能退出;组件卸载时 Hook 会自动 close。</p>
|
|
100
|
+
</div>
|
|
101
|
+
<footer className="fanui-modal__footer">
|
|
102
|
+
<button className="fanui-btn fanui-btn--primary" data-fanui-dismiss>知道了</button>
|
|
103
|
+
</footer>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</main>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import App from "./App.jsx";
|
|
4
|
+
|
|
5
|
+
/* ① 样式必须在 React 之前引入(全局 CSS) */
|
|
6
|
+
import "@zhang_jifan/fanui/style.css";
|
|
7
|
+
|
|
8
|
+
/* ② 挂载 React(React 18 写法;React 17 及以下见 README) */
|
|
9
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
10
|
+
<React.StrictMode>
|
|
11
|
+
<App />
|
|
12
|
+
</React.StrictMode>
|
|
13
|
+
);
|