@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.
Files changed (127) hide show
  1. package/ACCESSIBILITY.md +61 -0
  2. package/CHANGELOG.md +112 -0
  3. package/COMMERCIAL-LICENSE.md +71 -0
  4. package/DESIGN-SYSTEM.md +294 -0
  5. package/LICENSE +661 -0
  6. package/README.md +348 -0
  7. package/USAGE.md +1926 -0
  8. package/_index.scss +20 -0
  9. package/dist/fanui.cjs +15 -0
  10. package/dist/fanui.css +28209 -0
  11. package/dist/fanui.d.ts +353 -0
  12. package/dist/fanui.esm.js +71 -0
  13. package/dist/fanui.js +4782 -0
  14. package/dist/fanui.min.css +1 -0
  15. package/dist/fanui.mjs +71 -0
  16. package/dist/modules/fanui-advanced.css +830 -0
  17. package/dist/modules/fanui-advanced.min.css +129 -0
  18. package/dist/modules/fanui-alert.css +575 -0
  19. package/dist/modules/fanui-alert.min.css +122 -0
  20. package/dist/modules/fanui-badge.css +426 -0
  21. package/dist/modules/fanui-badge.min.css +77 -0
  22. package/dist/modules/fanui-base.css +1394 -0
  23. package/dist/modules/fanui-base.min.css +236 -0
  24. package/dist/modules/fanui-blog.css +762 -0
  25. package/dist/modules/fanui-blog.min.css +108 -0
  26. package/dist/modules/fanui-button.css +526 -0
  27. package/dist/modules/fanui-button.min.css +56 -0
  28. package/dist/modules/fanui-card.css +291 -0
  29. package/dist/modules/fanui-card.min.css +52 -0
  30. package/dist/modules/fanui-choice.css +539 -0
  31. package/dist/modules/fanui-choice.min.css +83 -0
  32. package/dist/modules/fanui-dashboard.css +1057 -0
  33. package/dist/modules/fanui-dashboard.min.css +160 -0
  34. package/dist/modules/fanui-data.css +687 -0
  35. package/dist/modules/fanui-data.min.css +125 -0
  36. package/dist/modules/fanui-feedback.css +432 -0
  37. package/dist/modules/fanui-feedback.min.css +79 -0
  38. package/dist/modules/fanui-form.css +779 -0
  39. package/dist/modules/fanui-form.min.css +123 -0
  40. package/dist/modules/fanui-glass.css +497 -0
  41. package/dist/modules/fanui-glass.min.css +46 -0
  42. package/dist/modules/fanui-input-plus.css +799 -0
  43. package/dist/modules/fanui-input-plus.min.css +124 -0
  44. package/dist/modules/fanui-interactive.css +960 -0
  45. package/dist/modules/fanui-interactive.min.css +138 -0
  46. package/dist/modules/fanui-layout.css +4399 -0
  47. package/dist/modules/fanui-layout.min.css +1172 -0
  48. package/dist/modules/fanui-marketing.css +1125 -0
  49. package/dist/modules/fanui-marketing.min.css +144 -0
  50. package/dist/modules/fanui-nav.css +458 -0
  51. package/dist/modules/fanui-nav.min.css +76 -0
  52. package/dist/modules/fanui-overlay.css +547 -0
  53. package/dist/modules/fanui-overlay.min.css +91 -0
  54. package/dist/modules/fanui-table.css +290 -0
  55. package/dist/modules/fanui-table.min.css +62 -0
  56. package/dist/modules/fanui-tabs.css +333 -0
  57. package/dist/modules/fanui-tabs.min.css +58 -0
  58. package/dist/modules/fanui-themes.css +2728 -0
  59. package/dist/modules/fanui-themes.min.css +40 -0
  60. package/dist/modules/fanui-tokens.css +587 -0
  61. package/dist/modules/fanui-tokens.min.css +3 -0
  62. package/dist/modules/fanui-utilities.css +6557 -0
  63. package/dist/modules/fanui-utilities.min.css +1762 -0
  64. package/dist/modules/sizes.json +103 -0
  65. package/docs/MIGRATION-v2.md +179 -0
  66. package/examples/01-script-tag.html +149 -0
  67. package/examples/02-react/README.md +39 -0
  68. package/examples/02-react/index.html +12 -0
  69. package/examples/02-react/package.json +20 -0
  70. package/examples/02-react/src/App.jsx +108 -0
  71. package/examples/02-react/src/main.jsx +13 -0
  72. package/examples/02-react/src/useFanUI.js +87 -0
  73. package/examples/02-react/vite.config.js +6 -0
  74. package/examples/03-vue3/README.md +67 -0
  75. package/examples/03-vue3/index.html +12 -0
  76. package/examples/03-vue3/package.json +19 -0
  77. package/examples/03-vue3/src/App.vue +93 -0
  78. package/examples/03-vue3/src/main.js +8 -0
  79. package/examples/03-vue3/src/useFanUI.js +85 -0
  80. package/examples/03-vue3/vite.config.js +6 -0
  81. package/examples/04-vue2-cdn.html +93 -0
  82. package/examples/README.md +34 -0
  83. package/package.json +127 -0
  84. package/src/_functions.scss +319 -0
  85. package/src/_mixins.scss +1202 -0
  86. package/src/_tokens.scss +180 -0
  87. package/src/_variables.scss +491 -0
  88. package/src/base/_animations.scss +210 -0
  89. package/src/base/_reset.scss +359 -0
  90. package/src/base/_rtl.scss +133 -0
  91. package/src/base/_typography.scss +353 -0
  92. package/src/components/_advanced.scss +763 -0
  93. package/src/components/_alert.scss +322 -0
  94. package/src/components/_badge.scss +279 -0
  95. package/src/components/_blog.scss +693 -0
  96. package/src/components/_button.scss +351 -0
  97. package/src/components/_capabilities.scss +463 -0
  98. package/src/components/_card.scss +281 -0
  99. package/src/components/_choice.scss +444 -0
  100. package/src/components/_dashboard.scss +950 -0
  101. package/src/components/_data.scss +618 -0
  102. package/src/components/_feedback.scss +403 -0
  103. package/src/components/_form.scss +408 -0
  104. package/src/components/_glass.scss +374 -0
  105. package/src/components/_input-plus.scss +730 -0
  106. package/src/components/_interactive.scss +885 -0
  107. package/src/components/_marketing.scss +982 -0
  108. package/src/components/_nav.scss +480 -0
  109. package/src/components/_overlay.scss +517 -0
  110. package/src/components/_table.scss +276 -0
  111. package/src/components/_tabs.scss +338 -0
  112. package/src/fanui.d.ts +353 -0
  113. package/src/fanui.js +4782 -0
  114. package/src/fanui.scss +79 -0
  115. package/src/glass/_switches.scss +36 -0
  116. package/src/glass/_tokens.scss +259 -0
  117. package/src/layout/_container.scss +111 -0
  118. package/src/layout/_flex.scss +164 -0
  119. package/src/layout/_grid.scss +180 -0
  120. package/src/themes/_accents.scss +201 -0
  121. package/src/themes/_palettes.scss +180 -0
  122. package/src/utilities/_borders.scss +104 -0
  123. package/src/utilities/_display.scss +145 -0
  124. package/src/utilities/_effects.scss +210 -0
  125. package/src/utilities/_sizing.scss +123 -0
  126. package/src/utilities/_spacing.scss +118 -0
  127. package/src/utilities/_texture.scss +323 -0
@@ -0,0 +1,763 @@
1
+ // =============================================================================
2
+ // fanUI · 进阶数据录入与展示组件 (Advanced Components)
3
+ // -----------------------------------------------------------------------------
4
+ // 覆盖缺口清单 A1–A9 + C4:
5
+ // .fanui-loading-bar 顶部全局加载条(A3)
6
+ // .fanui-datepanel 日期选择面板(A1,JS: FanUI.datePicker)
7
+ // .fanui-timepanel 时间选择面板(A2,JS: FanUI.timePicker)
8
+ // .fanui-colorpanel 颜色选择面板(A4,JS: FanUI.colorPicker)
9
+ // .fanui-cascader 级联选择器(A5,JS: FanUI.cascader)
10
+ // .fanui-tree 树形控件(A6,JS: FanUI.tree)
11
+ // .fanui-transfer 穿梭框(A7,JS: FanUI.transfer)
12
+ // .fanui-cluster 簇状布局(A8,纯 CSS)
13
+ // .fanui-logo-cloud 客户 Logo 墙(A9,纯 CSS)
14
+ // .fanui-skip-link 跳转主内容链接(C4,纯 CSS)
15
+ // 数据源约定:cascader / tree / transfer 均接受相邻
16
+ // <script type="application/json"> 或 FanUI.xxx(el, { items }) 传入的 JSON。
17
+ // =============================================================================
18
+
19
+ @use "../functions" as fn;
20
+ @use "../variables" as v;
21
+ @use "../mixins" as mx;
22
+
23
+ // -----------------------------------------------------------------------------
24
+ // 1. 顶部加载条 loading-bar
25
+ // -----------------------------------------------------------------------------
26
+
27
+ .fanui-loading-bar {
28
+ position: fixed;
29
+ top: 0;
30
+ left: 0;
31
+ right: 0;
32
+ z-index: fn.fanui-var("z-toast", v.$fanui-zindex-toast);
33
+ height: 3px;
34
+ pointer-events: none;
35
+ opacity: 0;
36
+ background-color: transparent;
37
+ @include mx.fanui-transition((opacity), v.$fanui-duration-fast);
38
+
39
+ &__inner {
40
+ height: 100%;
41
+ width: var(--fanui-loading-progress, 0%);
42
+ border-radius: 0 9999px 9999px 0;
43
+ background-image: linear-gradient(
44
+ 90deg,
45
+ fn.fanui-var("primary"),
46
+ fn.fanui-var("accent-vivid")
47
+ );
48
+ box-shadow: 0 0 8px fn.fanui-var("primary-ring");
49
+ @include mx.fanui-transition((width), 240ms, v.$fanui-ease-out);
50
+ }
51
+
52
+ &.is-loading { opacity: 1; }
53
+
54
+ // 不确定态(未调用 set() 前的缓动爬升由 JS 驱动;这里提供纯 CSS 备选)
55
+ &--indeterminate &__inner {
56
+ width: 40% !important;
57
+ animation: fanui-loading-slide 1.1s var(--fanui-ease-in-out, ease-in-out) infinite;
58
+ }
59
+ }
60
+
61
+ @keyframes fanui-loading-slide {
62
+ 0% { margin-inline-start: -40%; }
63
+ 100% { margin-inline-start: 100%; }
64
+ }
65
+
66
+ // -----------------------------------------------------------------------------
67
+ // 2. 浮层面板公共骨架(日期 / 时间 / 颜色共用)
68
+ // -----------------------------------------------------------------------------
69
+
70
+ %fanui-picker-panel-base {
71
+ position: absolute;
72
+ z-index: fn.fanui-var("z-popover", v.$fanui-zindex-popover);
73
+ min-width: 260px;
74
+ padding: fn.fanui-var("space-3");
75
+ background-color: fn.fanui-var("color-overlay");
76
+ border: 1px solid fn.fanui-var("color-border");
77
+ border-radius: fn.fanui-var("radius-lg");
78
+ box-shadow: fn.fanui-var("shadow-xl");
79
+ opacity: 0;
80
+ visibility: hidden;
81
+ transform: translateY(-4px) scale(0.98);
82
+ transform-origin: top left;
83
+ @include mx.fanui-transition((opacity, visibility, transform), v.$fanui-duration-fast, v.$fanui-ease-out);
84
+
85
+ &.is-open {
86
+ opacity: 1;
87
+ visibility: visible;
88
+ transform: translateY(0) scale(1);
89
+ }
90
+ }
91
+
92
+ .fanui-datepanel,
93
+ .fanui-timepanel,
94
+ .fanui-colorpanel {
95
+ @extend %fanui-picker-panel-base;
96
+ }
97
+
98
+ // -----------------------------------------------------------------------------
99
+ // 3. 日期选择 datepicker
100
+ // -----------------------------------------------------------------------------
101
+
102
+ .fanui-datepanel {
103
+ width: 292px;
104
+
105
+ &__head {
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: space-between;
109
+ gap: fn.fanui-var("space-2");
110
+ margin-bottom: fn.fanui-var("space-2");
111
+ }
112
+
113
+ &__title {
114
+ flex: 1 1 auto;
115
+ text-align: center;
116
+ font-size: fn.fanui-var("font-size-sm");
117
+ font-weight: fn.fanui-var("font-weight-semibold");
118
+ color: fn.fanui-var("color-text-strong");
119
+ }
120
+
121
+ &__nav {
122
+ display: grid;
123
+ place-items: center;
124
+ width: 28px;
125
+ height: 28px;
126
+ flex: 0 0 auto;
127
+ border: 0;
128
+ border-radius: fn.fanui-var("radius-sm");
129
+ background-color: transparent;
130
+ color: fn.fanui-var("color-text-muted");
131
+ cursor: pointer;
132
+ @include mx.fanui-transition((background-color, color), v.$fanui-duration-fast);
133
+
134
+ &:hover {
135
+ background-color: fn.fanui-var("color-hover");
136
+ color: fn.fanui-var("color-text-strong");
137
+ }
138
+ &:focus-visible { @include mx.fanui-focus-ring(); }
139
+ &:disabled { opacity: 0.35; pointer-events: none; }
140
+ }
141
+
142
+ &__week,
143
+ &__grid {
144
+ display: grid;
145
+ grid-template-columns: repeat(7, 1fr);
146
+ gap: 2px;
147
+ }
148
+
149
+ &__week > span {
150
+ padding: 4px 0;
151
+ text-align: center;
152
+ font-size: fn.fanui-var("font-size-xs");
153
+ font-weight: fn.fanui-var("font-weight-semibold");
154
+ color: fn.fanui-var("color-text-subtle");
155
+ }
156
+
157
+ &__day {
158
+ display: grid;
159
+ place-items: center;
160
+ aspect-ratio: 1;
161
+ border: 0;
162
+ border-radius: fn.fanui-var("radius-sm");
163
+ background-color: transparent;
164
+ font-size: fn.fanui-var("font-size-sm");
165
+ font-variant-numeric: tabular-nums;
166
+ color: fn.fanui-var("color-text");
167
+ cursor: pointer;
168
+ @include mx.fanui-transition((background-color, color), v.$fanui-duration-fast);
169
+
170
+ &:hover { background-color: fn.fanui-var("color-hover"); }
171
+ &:focus-visible { @include mx.fanui-focus-ring(); }
172
+
173
+ &.is-other { color: fn.fanui-var("color-text-subtle"); opacity: 0.55; }
174
+ &.is-today {
175
+ color: fn.fanui-var("primary");
176
+ font-weight: fn.fanui-var("font-weight-bold");
177
+ box-shadow: inset 0 0 0 1px fn.fanui-var("primary-border");
178
+ }
179
+ &.is-selected {
180
+ color: fn.fanui-var("primary-fg");
181
+ background-color: fn.fanui-var("primary");
182
+ font-weight: fn.fanui-var("font-weight-semibold");
183
+ }
184
+ &.is-in-range {
185
+ color: fn.fanui-var("primary-ink");
186
+ background-color: fn.fanui-var("primary-soft");
187
+ border-radius: 0;
188
+ }
189
+ &:disabled {
190
+ color: fn.fanui-var("color-text-subtle");
191
+ opacity: 0.35;
192
+ text-decoration: line-through;
193
+ pointer-events: none;
194
+ }
195
+ }
196
+
197
+ &__quick {
198
+ display: flex;
199
+ flex-wrap: wrap;
200
+ gap: 6px;
201
+ margin-top: fn.fanui-var("space-2");
202
+ padding-top: fn.fanui-var("space-2");
203
+ border-top: 1px solid fn.fanui-var("color-border-subtle");
204
+
205
+ button {
206
+ padding: 3px 10px;
207
+ border: 1px solid fn.fanui-var("color-border");
208
+ border-radius: fn.fanui-var("radius-full");
209
+ background-color: transparent;
210
+ font-size: fn.fanui-var("font-size-xs");
211
+ color: fn.fanui-var("color-text-muted");
212
+ cursor: pointer;
213
+ @include mx.fanui-transition((background-color, color, border-color), v.$fanui-duration-fast);
214
+
215
+ &:hover {
216
+ color: fn.fanui-var("primary");
217
+ border-color: fn.fanui-var("primary-border");
218
+ background-color: fn.fanui-var("primary-soft");
219
+ }
220
+ }
221
+ }
222
+
223
+ &__foot {
224
+ display: flex;
225
+ justify-content: space-between;
226
+ margin-top: fn.fanui-var("space-2");
227
+ font-size: fn.fanui-var("font-size-xs");
228
+ color: fn.fanui-var("color-text-subtle");
229
+ }
230
+ }
231
+
232
+ // -----------------------------------------------------------------------------
233
+ // 4. 时间选择 timepicker
234
+ // -----------------------------------------------------------------------------
235
+
236
+ .fanui-timepanel {
237
+ width: 160px;
238
+ max-height: 280px;
239
+ overflow-y: auto;
240
+ @include mx.fanui-scrollbar(6px);
241
+
242
+ &__opt {
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: space-between;
246
+ width: 100%;
247
+ padding: 7px 12px;
248
+ border: 0;
249
+ border-radius: fn.fanui-var("radius-sm");
250
+ background-color: transparent;
251
+ font-size: fn.fanui-var("font-size-sm");
252
+ font-variant-numeric: tabular-nums;
253
+ color: fn.fanui-var("color-text");
254
+ cursor: pointer;
255
+ @include mx.fanui-transition((background-color, color), v.$fanui-duration-fast);
256
+
257
+ &:hover { background-color: fn.fanui-var("color-hover"); }
258
+ &:focus-visible { @include mx.fanui-focus-ring(); }
259
+
260
+ &.is-selected {
261
+ color: fn.fanui-var("primary-fg");
262
+ background-color: fn.fanui-var("primary");
263
+ font-weight: fn.fanui-var("font-weight-semibold");
264
+
265
+ &::after { content: "✓"; font-size: 12px; }
266
+ }
267
+ }
268
+
269
+ &__empty {
270
+ padding: fn.fanui-var("space-4");
271
+ text-align: center;
272
+ font-size: fn.fanui-var("font-size-xs");
273
+ color: fn.fanui-var("color-text-subtle");
274
+ }
275
+ }
276
+
277
+ // -----------------------------------------------------------------------------
278
+ // 5. 颜色选择 colorpicker
279
+ // -----------------------------------------------------------------------------
280
+
281
+ .fanui-colorpanel {
282
+ width: 264px;
283
+
284
+ &__preview {
285
+ display: flex;
286
+ align-items: center;
287
+ gap: fn.fanui-var("space-3");
288
+ margin-bottom: fn.fanui-var("space-3");
289
+ }
290
+
291
+ &__swatch {
292
+ width: 44px;
293
+ height: 44px;
294
+ flex: 0 0 auto;
295
+ border-radius: fn.fanui-var("radius-md");
296
+ border: 1px solid fn.fanui-var("color-border");
297
+ box-shadow: inset 0 0 0 2px fn.fanui-var("color-surface");
298
+ }
299
+
300
+ &__value {
301
+ flex: 1 1 auto;
302
+ min-width: 0;
303
+ padding: 7px 10px;
304
+ border: 1px solid fn.fanui-var("color-border");
305
+ border-radius: fn.fanui-var("radius-md");
306
+ background-color: fn.fanui-var("color-surface-sunken");
307
+ font-family: fn.fanui-var("font-mono");
308
+ font-size: fn.fanui-var("font-size-sm");
309
+ color: fn.fanui-var("color-text");
310
+ text-transform: uppercase;
311
+ }
312
+
313
+ &__slider {
314
+ width: 100%;
315
+ margin: 0 0 fn.fanui-var("space-2");
316
+ accent-color: fn.fanui-var("primary");
317
+ cursor: pointer;
318
+ }
319
+
320
+ &__row {
321
+ display: flex;
322
+ align-items: center;
323
+ gap: fn.fanui-var("space-2");
324
+ margin-bottom: fn.fanui-var("space-2");
325
+ font-size: fn.fanui-var("font-size-xs");
326
+ color: fn.fanui-var("color-text-subtle");
327
+
328
+ label { flex: 0 0 32px; }
329
+ }
330
+
331
+ &__palette {
332
+ display: grid;
333
+ grid-template-columns: repeat(8, 1fr);
334
+ gap: 6px;
335
+ margin-top: fn.fanui-var("space-3");
336
+ padding-top: fn.fanui-var("space-3");
337
+ border-top: 1px solid fn.fanui-var("color-border-subtle");
338
+
339
+ button {
340
+ aspect-ratio: 1;
341
+ border: 0;
342
+ border-radius: fn.fanui-var("radius-sm");
343
+ cursor: pointer;
344
+ @include mx.fanui-transition((transform, box-shadow), v.$fanui-duration-fast);
345
+
346
+ &:hover { transform: scale(1.12); box-shadow: fn.fanui-var("shadow-md"); }
347
+ &.is-selected { box-shadow: 0 0 0 2px fn.fanui-var("color-surface"), 0 0 0 4px fn.fanui-var("primary"); }
348
+ &:focus-visible { @include mx.fanui-focus-ring(); }
349
+ }
350
+ }
351
+
352
+ &__actions {
353
+ display: flex;
354
+ justify-content: space-between;
355
+ gap: fn.fanui-var("space-2");
356
+ margin-top: fn.fanui-var("space-3");
357
+ }
358
+ }
359
+
360
+ // -----------------------------------------------------------------------------
361
+ // 6. 级联选择 cascader
362
+ // -----------------------------------------------------------------------------
363
+
364
+ .fanui-cascader {
365
+ position: relative;
366
+ display: inline-flex;
367
+ width: 100%;
368
+
369
+ &__trigger {
370
+ display: flex;
371
+ align-items: center;
372
+ justify-content: space-between;
373
+ gap: fn.fanui-var("space-2");
374
+ width: 100%;
375
+ padding: 8px 12px;
376
+ border: 1px solid fn.fanui-var("color-border");
377
+ border-radius: fn.fanui-var("radius-md");
378
+ background-color: fn.fanui-var("color-surface");
379
+ font-size: fn.fanui-var("font-size-sm");
380
+ color: fn.fanui-var("color-text");
381
+ cursor: pointer;
382
+ text-align: start;
383
+ @include mx.fanui-transition((border-color, box-shadow), v.$fanui-duration-fast);
384
+
385
+ &:hover { border-color: fn.fanui-var("primary-border"); }
386
+ &:focus-visible { @include mx.fanui-focus-ring(); }
387
+
388
+ &.is-active { border-color: fn.fanui-var("primary"); }
389
+
390
+ .fanui-cascader__placeholder { color: fn.fanui-var("color-text-subtle"); }
391
+ .fanui-cascader__caret { color: fn.fanui-var("color-text-subtle"); font-size: 10px; }
392
+ }
393
+
394
+ &__panel {
395
+ @extend %fanui-picker-panel-base;
396
+ display: none;
397
+ width: max-content;
398
+ max-width: min(560px, 92vw);
399
+
400
+ &.is-open { display: flex; }
401
+
402
+ // 搜索模式:单列
403
+ &.is-searching { display: block; }
404
+ }
405
+
406
+ &__search {
407
+ width: 100%;
408
+ padding: 7px 10px;
409
+ margin-bottom: fn.fanui-var("space-2");
410
+ border: 1px solid fn.fanui-var("color-border");
411
+ border-radius: fn.fanui-var("radius-sm");
412
+ background-color: fn.fanui-var("color-surface-sunken");
413
+ font-size: fn.fanui-var("font-size-sm");
414
+ color: fn.fanui-var("color-text");
415
+ }
416
+
417
+ &__cols {
418
+ display: flex;
419
+ max-height: 280px;
420
+ }
421
+
422
+ &__col {
423
+ flex: 0 0 auto;
424
+ width: 180px;
425
+ max-height: 280px;
426
+ overflow-y: auto;
427
+ padding: 4px;
428
+ border-inline-end: 1px solid fn.fanui-var("color-border-subtle");
429
+ @include mx.fanui-scrollbar(6px);
430
+
431
+ &:last-child { border-inline-end: 0; }
432
+ }
433
+
434
+ &__opt {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ gap: 6px;
439
+ width: 100%;
440
+ padding: 7px 10px;
441
+ border: 0;
442
+ border-radius: fn.fanui-var("radius-sm");
443
+ background-color: transparent;
444
+ font-size: fn.fanui-var("font-size-sm");
445
+ text-align: start;
446
+ color: fn.fanui-var("color-text");
447
+ cursor: pointer;
448
+ @include mx.fanui-transition((background-color, color), v.$fanui-duration-fast);
449
+
450
+ &:hover { background-color: fn.fanui-var("color-hover"); }
451
+ &:focus-visible { @include mx.fanui-focus-ring(); }
452
+
453
+ &.is-active {
454
+ color: fn.fanui-var("primary-ink");
455
+ background-color: fn.fanui-var("primary-soft");
456
+ font-weight: fn.fanui-var("font-weight-semibold");
457
+ }
458
+
459
+ .fanui-cascader__arrow { font-size: 10px; color: fn.fanui-var("color-text-subtle"); }
460
+ }
461
+
462
+ &__breadcrumb {
463
+ display: flex;
464
+ flex-wrap: wrap;
465
+ align-items: center;
466
+ gap: 4px;
467
+ margin-top: fn.fanui-var("space-2");
468
+ padding-top: fn.fanui-var("space-2");
469
+ border-top: 1px solid fn.fanui-var("color-border-subtle");
470
+ font-size: fn.fanui-var("font-size-xs");
471
+ color: fn.fanui-var("color-text-subtle");
472
+
473
+ b { color: fn.fanui-var("color-text-strong"); font-weight: fn.fanui-var("font-weight-semibold"); }
474
+ }
475
+ }
476
+
477
+ // -----------------------------------------------------------------------------
478
+ // 7. 树形控件 tree
479
+ // -----------------------------------------------------------------------------
480
+
481
+ .fanui-tree {
482
+ margin: 0;
483
+ padding: 0;
484
+ list-style: none;
485
+ font-size: fn.fanui-var("font-size-sm");
486
+ color: fn.fanui-var("color-text");
487
+
488
+ ul {
489
+ margin: 0;
490
+ padding: 0 0 0 20px;
491
+ list-style: none;
492
+ }
493
+
494
+ li { position: relative; }
495
+
496
+ &__row {
497
+ display: flex;
498
+ align-items: center;
499
+ gap: 6px;
500
+ padding: 5px 8px;
501
+ border-radius: fn.fanui-var("radius-sm");
502
+ cursor: pointer;
503
+ user-select: none;
504
+ @include mx.fanui-transition((background-color), v.$fanui-duration-fast);
505
+
506
+ &:hover { background-color: fn.fanui-var("color-hover"); }
507
+ &.is-selected {
508
+ color: fn.fanui-var("primary-ink");
509
+ background-color: fn.fanui-var("primary-soft");
510
+ }
511
+ }
512
+
513
+ &__caret {
514
+ display: grid;
515
+ place-items: center;
516
+ width: 18px;
517
+ height: 18px;
518
+ flex: 0 0 auto;
519
+ border: 0;
520
+ border-radius: fn.fanui-var("radius-sm");
521
+ background-color: transparent;
522
+ color: fn.fanui-var("color-text-subtle");
523
+ font-size: 9px;
524
+ cursor: pointer;
525
+ transition: transform 0.15s var(--fanui-ease-out, ease-out);
526
+ visibility: hidden; // 叶子节点隐藏但占位对齐
527
+
528
+ &.is-expandable { visibility: visible; }
529
+ &.is-open { transform: rotate(90deg); }
530
+ &:focus-visible { @include mx.fanui-focus-ring(); }
531
+ }
532
+
533
+ &__check {
534
+ flex: 0 0 auto;
535
+ width: 15px;
536
+ height: 15px;
537
+ accent-color: fn.fanui-var("primary");
538
+ cursor: pointer;
539
+ }
540
+
541
+ &__icon { flex: 0 0 auto; color: fn.fanui-var("primary"); font-size: 13px; }
542
+ &__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
543
+ &__badge {
544
+ flex: 0 0 auto;
545
+ padding: 1px 7px;
546
+ font-size: 10px;
547
+ color: fn.fanui-var("color-text-subtle");
548
+ background-color: fn.fanui-var("color-surface-sunken");
549
+ border-radius: fn.fanui-var("radius-full");
550
+ }
551
+
552
+ li.is-collapsed > ul { display: none; }
553
+
554
+ // 半选态(indeterminate 由 JS 设置属性)
555
+ &__check:indeterminate { opacity: 1; }
556
+ }
557
+
558
+ // -----------------------------------------------------------------------------
559
+ // 8. 穿梭框 transfer
560
+ // -----------------------------------------------------------------------------
561
+
562
+ .fanui-transfer {
563
+ display: grid;
564
+ grid-template-columns: 1fr auto 1fr;
565
+ gap: fn.fanui-var("space-3");
566
+ align-items: center;
567
+ width: 100%;
568
+
569
+ @include mx.fanui-media-down("md") {
570
+ grid-template-columns: 1fr;
571
+ justify-items: center;
572
+ }
573
+
574
+ &__panel {
575
+ display: flex;
576
+ flex-direction: column;
577
+ width: 100%;
578
+ min-width: 220px;
579
+ border: 1px solid fn.fanui-var("color-border");
580
+ border-radius: fn.fanui-var("radius-lg");
581
+ background-color: fn.fanui-var("color-surface");
582
+ overflow: hidden;
583
+ }
584
+
585
+ &__head {
586
+ display: flex;
587
+ align-items: center;
588
+ justify-content: space-between;
589
+ gap: fn.fanui-var("space-2");
590
+ padding: fn.fanui-var("space-2") fn.fanui-var("space-3");
591
+ border-bottom: 1px solid fn.fanui-var("color-border-subtle");
592
+ background-color: fn.fanui-var("color-surface-sunken");
593
+ font-size: fn.fanui-var("font-size-xs");
594
+ color: fn.fanui-var("color-text-muted");
595
+ }
596
+
597
+ &__title { font-weight: fn.fanui-var("font-weight-semibold"); color: fn.fanui-var("color-text-strong"); }
598
+
599
+ &__search {
600
+ width: 100%;
601
+ padding: 7px 10px;
602
+ border: 0;
603
+ border-bottom: 1px solid fn.fanui-var("color-border-subtle");
604
+ background-color: fn.fanui-var("color-surface");
605
+ font-size: fn.fanui-var("font-size-xs");
606
+ color: fn.fanui-var("color-text");
607
+ }
608
+
609
+ &__list {
610
+ max-height: 260px;
611
+ overflow-y: auto;
612
+ margin: 0;
613
+ padding: 4px;
614
+ list-style: none;
615
+ @include mx.fanui-scrollbar(6px);
616
+ }
617
+
618
+ &__opt {
619
+ display: flex;
620
+ align-items: center;
621
+ gap: 8px;
622
+ padding: 6px 8px;
623
+ border-radius: fn.fanui-var("radius-sm");
624
+ cursor: pointer;
625
+ @include mx.fanui-transition((background-color), v.$fanui-duration-fast);
626
+
627
+ &:hover { background-color: fn.fanui-var("color-hover"); }
628
+ &.is-selected { background-color: fn.fanui-var("primary-soft"); }
629
+ // C1:禁用态不要用整体 opacity 压暗 —— 那会把文字一起变淡到 1.5:1 左右。
630
+ // 改为「底色 + 文字令牌」显式表达禁用语义,文字仍保持 AA 可读。
631
+ &.is-disabled {
632
+ pointer-events: none;
633
+ background-color: fn.fanui-var("color-disabled-bg");
634
+ color: fn.fanui-var("color-disabled-text");
635
+
636
+ .fanui-transfer__opt-label,
637
+ .fanui-transfer__opt-tag { color: fn.fanui-var("color-disabled-text"); }
638
+ }
639
+ }
640
+
641
+ &__opt-check { accent-color: fn.fanui-var("primary"); flex: 0 0 auto; }
642
+ &__opt-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
643
+ &__opt-tag { flex: 0 0 auto; font-size: 10px; color: fn.fanui-var("color-text-subtle"); }
644
+
645
+ &__empty {
646
+ padding: fn.fanui-var("space-5") fn.fanui-var("space-3");
647
+ text-align: center;
648
+ font-size: fn.fanui-var("font-size-xs");
649
+ color: fn.fanui-var("color-text-subtle");
650
+ }
651
+
652
+ &__actions {
653
+ display: flex;
654
+ flex-direction: column;
655
+ gap: fn.fanui-var("space-2");
656
+
657
+ @include mx.fanui-media-down("md") { flex-direction: row; }
658
+ }
659
+
660
+ &__btn {
661
+ display: grid;
662
+ place-items: center;
663
+ width: 36px;
664
+ height: 36px;
665
+ border: 1px solid fn.fanui-var("color-border");
666
+ border-radius: fn.fanui-var("radius-md");
667
+ background-color: fn.fanui-var("color-surface");
668
+ color: fn.fanui-var("color-text-muted");
669
+ font-size: 14px;
670
+ cursor: pointer;
671
+ @include mx.fanui-transition((background-color, color, border-color), v.$fanui-duration-fast);
672
+
673
+ &:hover:not(:disabled) {
674
+ color: fn.fanui-var("primary-fg");
675
+ background-color: fn.fanui-var("primary");
676
+ border-color: fn.fanui-var("primary");
677
+ }
678
+ &:disabled { opacity: 0.4; pointer-events: none; }
679
+ &:focus-visible { @include mx.fanui-focus-ring(); }
680
+ }
681
+ }
682
+
683
+ // -----------------------------------------------------------------------------
684
+ // 9. 簇状布局 cluster(A8)
685
+ // -----------------------------------------------------------------------------
686
+
687
+ .fanui-cluster {
688
+ display: flex;
689
+ flex-wrap: wrap;
690
+ align-items: center;
691
+ gap: var(--fanui-cluster-gap, #{fn.fanui-var("space-3")});
692
+ // 负外边距抵消首尾 gap,保证与容器边缘对齐
693
+ margin-inline: calc(var(--fanui-cluster-gap, #{fn.fanui-var("space-3")}) * -0.5);
694
+
695
+ > * { margin-inline: calc(var(--fanui-cluster-gap, #{fn.fanui-var("space-3")}) * 0.5); }
696
+
697
+ &--center { justify-content: center; }
698
+ &--between { justify-content: space-between; }
699
+ &--end { justify-content: flex-end; }
700
+ &--tight { --fanui-cluster-gap: #{fn.fanui-var("space-2")}; }
701
+ &--loose { --fanui-cluster-gap: #{fn.fanui-var("space-5")}; }
702
+ }
703
+
704
+ // -----------------------------------------------------------------------------
705
+ // 10. 客户 Logo 墙 logo-cloud(A9)
706
+ // -----------------------------------------------------------------------------
707
+
708
+ .fanui-logo-cloud {
709
+ display: grid;
710
+ grid-template-columns: repeat(auto-fit, minmax(var(--fanui-logo-min, 150px), 1fr));
711
+ gap: fn.fanui-var("space-4");
712
+ align-items: stretch;
713
+
714
+ &__item {
715
+ display: grid;
716
+ place-items: center;
717
+ min-height: 72px;
718
+ padding: fn.fanui-var("space-4");
719
+ border: 1px solid fn.fanui-var("color-border-subtle");
720
+ border-radius: fn.fanui-var("radius-lg");
721
+ background-color: fn.fanui-var("color-surface");
722
+ font-weight: fn.fanui-var("font-weight-bold");
723
+ letter-spacing: fn.fanui-var("tracking-wide");
724
+ // C1:文字用令牌(AA 达标),「淡化」只作用在图片上 —— 避免整体 opacity
725
+ // 把品牌名文字一起压到 3:1 以下。
726
+ color: fn.fanui-var("color-text-subtle");
727
+ @include mx.fanui-transition((filter, opacity, transform, box-shadow), v.$fanui-duration-base);
728
+
729
+ img {
730
+ max-height: 32px;
731
+ max-width: 100%;
732
+ object-fit: contain;
733
+ opacity: 0.62;
734
+ filter: grayscale(1);
735
+ @include mx.fanui-transition((filter, opacity), v.$fanui-duration-base);
736
+ }
737
+
738
+ &:hover {
739
+ color: fn.fanui-var("primary");
740
+ transform: translateY(-2px);
741
+ box-shadow: fn.fanui-var("shadow-md");
742
+
743
+ img { opacity: 1; filter: none; }
744
+ }
745
+ }
746
+ }
747
+
748
+ // -----------------------------------------------------------------------------
749
+ // 11. 跳转主内容 skip-link(C4 无障碍)
750
+ // -----------------------------------------------------------------------------
751
+ // 统一实现在 base/_reset.scss(避免双实现互相覆盖);
752
+ // 此处仅保留说明:键盘 Tab 首个可达元素,聚焦后悬浮于左上角,点击跳转 #main。
753
+
754
+ // -----------------------------------------------------------------------------
755
+ // 12. 降级:减少动效
756
+ // -----------------------------------------------------------------------------
757
+
758
+ @include mx.fanui-motion-safe() {
759
+ .fanui-loading-bar--indeterminate .fanui-loading-bar__inner {
760
+ animation: none;
761
+ width: 100% !important;
762
+ }
763
+ }