@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,730 @@
1
+ // =============================================================================
2
+ // fanUI · 表单增强组件 (Advanced Inputs)
3
+ // -----------------------------------------------------------------------------
4
+ // 覆盖市面主流但基础层未含的输入形态,全部依赖主题色令牌,换肤 / 玻璃开关自动跟随:
5
+ // .fanui-slider 单值 / 双值滑块(带数值气泡)
6
+ // .fanui-rate 评分(支持半分、只读、清空)
7
+ // .fanui-upload 上传(拖拽区 + 文件卡片列表 + 进度)
8
+ // .fanui-tag-input 标签输入(回车成标签、可删、可建议)
9
+ // .fanui-stepper 数字步进器
10
+ // .fanui-pin 验证码 / OTP 分格输入
11
+ // .fanui-swatch 色板选择器(含自定义色)
12
+ // .fanui-combobox 组合框(输入即筛选)
13
+ // =============================================================================
14
+
15
+ @use "sass:map";
16
+ @use "../functions" as fn;
17
+ @use "../variables" as v;
18
+ @use "../mixins" as mx;
19
+
20
+ // -----------------------------------------------------------------------------
21
+ // 1. 滑块 Slider
22
+ // -----------------------------------------------------------------------------
23
+ // 用 --fanui-slider-fill 表示已选比例(JS 或内联样式写入),
24
+ // 轨道填充色自动取主题色,因此换主题后滑块颜色立即跟随。
25
+
26
+ .fanui-slider {
27
+ --fanui-slider-fill: 50%;
28
+ --fanui-slider-track: #{fn.fanui-var("color-border")};
29
+ --fanui-slider-thumb: #ffffff;
30
+ --fanui-slider-size: 6px;
31
+ --fanui-slider-thumb-size: 18px;
32
+
33
+ display: block;
34
+ width: 100%;
35
+ margin: 0;
36
+ padding: 0;
37
+ background: transparent;
38
+ appearance: none;
39
+ -webkit-appearance: none;
40
+ cursor: pointer;
41
+ @include mx.fanui-transition(opacity, v.$fanui-duration-fast);
42
+
43
+ &:focus-visible {
44
+ outline: none;
45
+
46
+ &::-webkit-slider-thumb { @include mx.fanui-focus-ring(); }
47
+ &::-moz-range-thumb { @include mx.fanui-focus-ring(); }
48
+ }
49
+
50
+ &::-webkit-slider-runnable-track {
51
+ height: var(--fanui-slider-size);
52
+ border-radius: var(--fanui-radius-full);
53
+ background-image: linear-gradient(
54
+ to right,
55
+ var(--fanui-primary) 0%,
56
+ var(--fanui-primary-vivid) var(--fanui-slider-fill),
57
+ var(--fanui-slider-track) var(--fanui-slider-fill),
58
+ var(--fanui-slider-track) 100%
59
+ );
60
+ }
61
+
62
+ &::-moz-range-track {
63
+ height: var(--fanui-slider-size);
64
+ border-radius: var(--fanui-radius-full);
65
+ background-color: var(--fanui-slider-track);
66
+ }
67
+
68
+ &::-moz-range-progress {
69
+ height: var(--fanui-slider-size);
70
+ border-radius: var(--fanui-radius-full);
71
+ background-image: linear-gradient(to right, var(--fanui-primary), var(--fanui-primary-vivid));
72
+ }
73
+
74
+ &::-webkit-slider-thumb {
75
+ width: var(--fanui-slider-thumb-size);
76
+ height: var(--fanui-slider-thumb-size);
77
+ margin-top: calc((var(--fanui-slider-size) - var(--fanui-slider-thumb-size)) / 2);
78
+ border: 2px solid var(--fanui-primary);
79
+ border-radius: var(--fanui-radius-full);
80
+ background-color: var(--fanui-slider-thumb);
81
+ box-shadow: 0 1px 4px fn.fanui-alpha(#020617, 0.22);
82
+ appearance: none;
83
+ -webkit-appearance: none;
84
+ @include mx.fanui-transition((transform, box-shadow), v.$fanui-duration-fast);
85
+ }
86
+
87
+ &::-moz-range-thumb {
88
+ width: var(--fanui-slider-thumb-size);
89
+ height: var(--fanui-slider-thumb-size);
90
+ border: 2px solid var(--fanui-primary);
91
+ border-radius: var(--fanui-radius-full);
92
+ background-color: var(--fanui-slider-thumb);
93
+ box-shadow: 0 1px 4px fn.fanui-alpha(#020617, 0.22);
94
+ }
95
+
96
+ &:hover::-webkit-slider-thumb { transform: scale(1.12); }
97
+ &:active::-webkit-slider-thumb { transform: scale(1.2); }
98
+
99
+ &:disabled {
100
+ opacity: 0.5;
101
+ cursor: not-allowed;
102
+
103
+ &::-webkit-slider-thumb { border-color: var(--fanui-color-border-strong); }
104
+ }
105
+
106
+ // 尺寸
107
+ &--sm { --fanui-slider-size: 4px; --fanui-slider-thumb-size: 14px; }
108
+ &--lg { --fanui-slider-size: 8px; --fanui-slider-thumb-size: 24px; }
109
+ }
110
+
111
+ // 带数值气泡的滑块:气泡位置由 --fanui-slider-fill 驱动,随拖动实时移动
112
+ .fanui-slider-field {
113
+ position: relative;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: fn.fanui-var("space-3");
117
+ padding-top: fn.fanui-var("space-5");
118
+
119
+ .fanui-slider { flex: 1 1 auto; min-width: 0; }
120
+
121
+ &__bubble {
122
+ position: absolute;
123
+ top: 0;
124
+ left: var(--fanui-slider-fill, 50%);
125
+ transform: translateX(-50%);
126
+ padding: 1px 7px;
127
+ font-size: fn.fanui-var("font-size-xs");
128
+ font-variant-numeric: tabular-nums;
129
+ color: fn.fanui-var("primary-fg");
130
+ background-color: fn.fanui-var("primary");
131
+ border-radius: fn.fanui-var("radius-sm");
132
+ white-space: nowrap;
133
+ pointer-events: none;
134
+ }
135
+
136
+ &__value {
137
+ min-width: 3.5em;
138
+ font-size: fn.fanui-var("font-size-sm");
139
+ font-variant-numeric: tabular-nums;
140
+ text-align: end;
141
+ color: fn.fanui-var("color-text-muted");
142
+ }
143
+
144
+ &__marks {
145
+ display: flex;
146
+ justify-content: space-between;
147
+ margin-top: fn.fanui-var("space-1");
148
+ font-size: fn.fanui-var("font-size-xs");
149
+ color: fn.fanui-var("color-text-subtle");
150
+ }
151
+ }
152
+
153
+ // 双值滑块(区间):两根轨道叠加,视觉上呈现"已选区间"
154
+ .fanui-slider-range {
155
+ position: relative;
156
+ height: var(--fanui-slider-thumb-size, 18px);
157
+
158
+ .fanui-slider {
159
+ position: absolute;
160
+ inset: 0;
161
+ width: 100%;
162
+ pointer-events: none;
163
+
164
+ &::-webkit-slider-thumb { pointer-events: auto; }
165
+ &::-moz-range-thumb { pointer-events: auto; }
166
+ }
167
+
168
+ &__rail {
169
+ position: absolute;
170
+ top: 50%;
171
+ left: 0;
172
+ right: 0;
173
+ height: var(--fanui-slider-size, 6px);
174
+ transform: translateY(-50%);
175
+ border-radius: var(--fanui-radius-full);
176
+ background-color: var(--fanui-color-border);
177
+ }
178
+
179
+ &__fill {
180
+ position: absolute;
181
+ top: 50%;
182
+ height: var(--fanui-slider-size, 6px);
183
+ transform: translateY(-50%);
184
+ border-radius: var(--fanui-radius-full);
185
+ background-image: linear-gradient(to right, var(--fanui-primary), var(--fanui-primary-vivid));
186
+ }
187
+ }
188
+
189
+ // -----------------------------------------------------------------------------
190
+ // 2. 评分 Rate
191
+ // -----------------------------------------------------------------------------
192
+ // 采用「行内反转 + 兄弟选择器」实现纯 CSS 高亮,无需 JS 也能工作。
193
+ // 结构:<div class="fanui-rate"><input><label>★</label>…(倒序书写)</div>
194
+
195
+ .fanui-rate {
196
+ display: inline-flex;
197
+ flex-direction: row-reverse;
198
+ justify-content: flex-end;
199
+ gap: 2px;
200
+ font-size: 22px;
201
+ line-height: 1;
202
+
203
+ &__input {
204
+ @include mx.fanui-visually-hidden();
205
+
206
+ &:focus-visible ~ .fanui-rate__star {
207
+ outline: 2px solid fn.fanui-var("primary-ring");
208
+ outline-offset: 2px;
209
+ }
210
+ }
211
+
212
+ &__star {
213
+ color: fn.fanui-var("color-border-strong");
214
+ cursor: pointer;
215
+ user-select: none;
216
+ @include mx.fanui-transition((color, transform), v.$fanui-duration-fast);
217
+
218
+ &::before { content: "★"; }
219
+
220
+ &:hover {
221
+ transform: scale(1.15);
222
+ }
223
+ }
224
+
225
+ // 选中及其之后的星(DOM 倒序 → 视觉上是"之前的星")
226
+ &__input:checked ~ &__star {
227
+ color: fn.fanui-var("warning-vivid");
228
+ }
229
+
230
+ // 悬停时,悬停项及其之后全部高亮
231
+ &__star:hover,
232
+ &__star:hover ~ &__star {
233
+ color: fn.fanui-var("warning");
234
+ }
235
+
236
+ // 语义色变体
237
+ &--primary {
238
+ .fanui-rate__input:checked ~ .fanui-rate__star { color: fn.fanui-var("primary"); }
239
+ .fanui-rate__star:hover,
240
+ .fanui-rate__star:hover ~ .fanui-rate__star { color: fn.fanui-var("primary-hover"); }
241
+ }
242
+
243
+ &--sm { font-size: 16px; }
244
+ &--lg { font-size: 30px; }
245
+
246
+ &--readonly {
247
+ .fanui-rate__star { cursor: default; pointer-events: none; }
248
+
249
+ .fanui-rate__star:hover { transform: none; }
250
+ }
251
+
252
+ &__value {
253
+ margin-inline-start: fn.fanui-var("space-2");
254
+ font-size: fn.fanui-var("font-size-sm");
255
+ color: fn.fanui-var("color-text-muted");
256
+ align-self: center;
257
+ }
258
+
259
+ &__desc {
260
+ margin-inline-start: fn.fanui-var("space-2");
261
+ font-size: fn.fanui-var("font-size-xs");
262
+ color: fn.fanui-var("color-text-subtle");
263
+ align-self: center;
264
+ }
265
+ }
266
+
267
+ // -----------------------------------------------------------------------------
268
+ // 3. 上传 Upload
269
+ // -----------------------------------------------------------------------------
270
+
271
+ .fanui-upload {
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: fn.fanui-var("space-3");
275
+
276
+ &__drop {
277
+ position: relative;
278
+ display: flex;
279
+ flex-direction: column;
280
+ align-items: center;
281
+ justify-content: center;
282
+ gap: fn.fanui-var("space-2");
283
+ padding: fn.fanui-var("space-8") fn.fanui-var("space-4");
284
+ text-align: center;
285
+ border: 2px dashed fn.fanui-var("color-border-strong");
286
+ border-radius: fn.fanui-var("radius-lg");
287
+ background-color: fn.fanui-var("color-surface-sunken");
288
+ color: fn.fanui-var("color-text-muted");
289
+ cursor: pointer;
290
+ @include mx.fanui-transition((border-color, background-color, color, transform), v.$fanui-duration-fast);
291
+
292
+ input[type="file"] {
293
+ @include mx.fanui-visually-hidden();
294
+ }
295
+
296
+ &:hover,
297
+ &.is-dragover {
298
+ border-color: fn.fanui-var("primary");
299
+ background-color: fn.fanui-var("primary-soft");
300
+ color: fn.fanui-var("primary-ink");
301
+ }
302
+
303
+ &.is-dragover {
304
+ transform: scale(1.01);
305
+ box-shadow: 0 0 0 4px fn.fanui-var("primary-ring");
306
+ }
307
+ }
308
+
309
+ &__icon {
310
+ display: grid;
311
+ place-items: center;
312
+ width: 44px;
313
+ height: 44px;
314
+ border-radius: fn.fanui-var("radius-full");
315
+ background-color: fn.fanui-var("primary-soft");
316
+ color: fn.fanui-var("primary");
317
+ font-size: fn.fanui-var("font-size-xl");
318
+ }
319
+
320
+ &__hint {
321
+ font-size: fn.fanui-var("font-size-xs");
322
+ color: fn.fanui-var("color-text-subtle");
323
+ }
324
+
325
+ &__list {
326
+ display: flex;
327
+ flex-direction: column;
328
+ gap: fn.fanui-var("space-2");
329
+ margin: 0;
330
+ padding: 0;
331
+ list-style: none;
332
+ }
333
+
334
+ // 文件卡片:含名称、大小、进度、操作
335
+ &__item {
336
+ display: flex;
337
+ align-items: center;
338
+ gap: fn.fanui-var("space-3");
339
+ padding: fn.fanui-var("space-2") fn.fanui-var("space-3");
340
+ border: 1px solid fn.fanui-var("color-border");
341
+ border-radius: fn.fanui-var("radius-md");
342
+ background-color: fn.fanui-var("color-surface");
343
+ @include mx.fanui-transition((border-color, background-color), v.$fanui-duration-fast);
344
+
345
+ &:hover { border-color: fn.fanui-var("color-border-strong"); }
346
+
347
+ &.is-error {
348
+ border-color: fn.fanui-var("danger-border");
349
+ background-color: fn.fanui-var("danger-soft");
350
+ }
351
+
352
+ &.is-done &__state { color: fn.fanui-var("success"); }
353
+ }
354
+
355
+ &__thumb {
356
+ display: grid;
357
+ place-items: center;
358
+ width: 36px;
359
+ height: 36px;
360
+ flex: 0 0 auto;
361
+ border-radius: fn.fanui-var("radius-sm");
362
+ background-color: fn.fanui-var("color-surface-sunken");
363
+ color: fn.fanui-var("color-text-muted");
364
+ overflow: hidden;
365
+
366
+ img { width: 100%; height: 100%; object-fit: cover; }
367
+ }
368
+
369
+ &__meta {
370
+ flex: 1 1 auto;
371
+ min-width: 0;
372
+ }
373
+
374
+ &__name {
375
+ display: block;
376
+ font-size: fn.fanui-var("font-size-sm");
377
+ color: fn.fanui-var("color-text");
378
+ @include mx.fanui-truncate(1);
379
+ }
380
+
381
+ &__size {
382
+ font-size: fn.fanui-var("font-size-xs");
383
+ color: fn.fanui-var("color-text-subtle");
384
+ font-variant-numeric: tabular-nums;
385
+ }
386
+
387
+ &__state { color: fn.fanui-var("color-text-muted"); flex: 0 0 auto; }
388
+ }
389
+
390
+ // -----------------------------------------------------------------------------
391
+ // 4. 标签输入 Tag Input
392
+ // -----------------------------------------------------------------------------
393
+
394
+ .fanui-tag-input {
395
+ display: flex;
396
+ flex-wrap: wrap;
397
+ align-items: center;
398
+ gap: fn.fanui-var("space-1");
399
+ min-height: fn.fanui-var("control-h");
400
+ padding: 5px 8px;
401
+ border: 1px solid fn.fanui-var("color-border");
402
+ border-radius: fn.fanui-var("control-radius");
403
+ background-color: fn.fanui-var("color-surface");
404
+ cursor: text;
405
+ @include mx.fanui-transition((border-color, box-shadow), v.$fanui-duration-fast);
406
+
407
+ &:hover { border-color: fn.fanui-var("color-border-strong"); }
408
+
409
+ &:focus-within {
410
+ border-color: fn.fanui-var("primary");
411
+ @include mx.fanui-focus-ring();
412
+ }
413
+
414
+ &__tag {
415
+ display: inline-flex;
416
+ align-items: center;
417
+ gap: 4px;
418
+ max-width: 100%;
419
+ padding: 2px 4px 2px 8px;
420
+ font-size: fn.fanui-var("font-size-xs");
421
+ line-height: 1.5;
422
+ color: fn.fanui-var("primary-ink");
423
+ background-color: fn.fanui-var("primary-soft");
424
+ border: 1px solid fn.fanui-var("primary-border");
425
+ border-radius: fn.fanui-var("radius-full");
426
+ animation: fanui-tag-pop 180ms v.$fanui-ease-spring both;
427
+
428
+ span { @include mx.fanui-truncate(1); max-width: 12em; }
429
+ }
430
+
431
+ &__remove {
432
+ display: grid;
433
+ place-items: center;
434
+ width: 16px;
435
+ height: 16px;
436
+ padding: 0;
437
+ border: 0;
438
+ border-radius: fn.fanui-var("radius-full");
439
+ background: transparent;
440
+ color: currentColor;
441
+ cursor: pointer;
442
+ opacity: 0.65;
443
+ @include mx.fanui-transition((opacity, background-color), v.$fanui-duration-fast);
444
+
445
+ &::before { content: "×"; font-size: 14px; line-height: 1; }
446
+ &:hover { opacity: 1; background-color: fn.fanui-alpha(#020617, 0.08); }
447
+ }
448
+
449
+ &__field {
450
+ flex: 1 1 6em;
451
+ min-width: 6em;
452
+ padding: 2px 4px;
453
+ border: 0;
454
+ background: transparent;
455
+ font: inherit;
456
+ font-size: fn.fanui-var("font-size-base");
457
+ color: fn.fanui-var("color-text");
458
+
459
+ &:focus { outline: none; }
460
+ &::placeholder { color: fn.fanui-var("color-text-subtle"); }
461
+ }
462
+
463
+ &__suggest {
464
+ position: absolute;
465
+ z-index: fn.fanui-var("z-dropdown");
466
+ margin-top: fn.fanui-var("space-1");
467
+ padding: fn.fanui-var("space-1");
468
+ min-width: 180px;
469
+ border: 1px solid fn.fanui-var("color-border");
470
+ border-radius: fn.fanui-var("radius-md");
471
+ background-color: fn.fanui-var("color-overlay");
472
+ box-shadow: fn.fanui-var("shadow-lg");
473
+
474
+ button {
475
+ display: block;
476
+ width: 100%;
477
+ padding: 6px 10px;
478
+ border: 0;
479
+ border-radius: fn.fanui-var("radius-sm");
480
+ background: transparent;
481
+ font: inherit;
482
+ font-size: fn.fanui-var("font-size-sm");
483
+ text-align: start;
484
+ color: fn.fanui-var("color-text");
485
+ cursor: pointer;
486
+
487
+ &:hover { background-color: fn.fanui-var("color-hover"); }
488
+ &.is-active { background-color: fn.fanui-var("primary-soft"); color: fn.fanui-var("primary-ink"); }
489
+ }
490
+ }
491
+ }
492
+
493
+ @keyframes fanui-tag-pop {
494
+ from { opacity: 0; transform: scale(0.86); }
495
+ to { opacity: 1; transform: scale(1); }
496
+ }
497
+
498
+ // -----------------------------------------------------------------------------
499
+ // 5. 步进器 Stepper
500
+ // -----------------------------------------------------------------------------
501
+
502
+ .fanui-stepper {
503
+ display: inline-flex;
504
+ align-items: stretch;
505
+ border: 1px solid fn.fanui-var("color-border");
506
+ border-radius: fn.fanui-var("control-radius");
507
+ background-color: fn.fanui-var("color-surface");
508
+ overflow: hidden;
509
+ @include mx.fanui-transition((border-color, box-shadow), v.$fanui-duration-fast);
510
+
511
+ &:focus-within {
512
+ border-color: fn.fanui-var("primary");
513
+ @include mx.fanui-focus-ring();
514
+ }
515
+
516
+ &__btn {
517
+ display: grid;
518
+ place-items: center;
519
+ width: calc(var(--fanui-control-h) + 4px);
520
+ border: 0;
521
+ background: transparent;
522
+ color: fn.fanui-var("color-text-muted");
523
+ font-size: 16px;
524
+ line-height: 1;
525
+ cursor: pointer;
526
+ @include mx.fanui-transition((background-color, color), v.$fanui-duration-fast);
527
+
528
+ &:hover:not(:disabled) {
529
+ background-color: fn.fanui-var("primary-soft");
530
+ color: fn.fanui-var("primary-ink");
531
+ }
532
+
533
+ &:disabled { opacity: 0.4; cursor: not-allowed; }
534
+ }
535
+
536
+ &__input {
537
+ width: 3.6em;
538
+ min-height: fn.fanui-var("control-h");
539
+ padding: 0 4px;
540
+ border: 0;
541
+ border-inline: 1px solid fn.fanui-var("color-border-subtle");
542
+ background: transparent;
543
+ font: inherit;
544
+ font-size: fn.fanui-var("font-size-base");
545
+ font-variant-numeric: tabular-nums;
546
+ text-align: center;
547
+ color: fn.fanui-var("color-text");
548
+
549
+ &:focus { outline: none; }
550
+ &::-webkit-outer-spin-button,
551
+ &::-webkit-inner-spin-button { appearance: none; margin: 0; }
552
+ }
553
+
554
+ &--sm { --fanui-control-h: #{map.get(v.$fanui-control-heights, "sm")}; }
555
+ &--lg { --fanui-control-h: #{map.get(v.$fanui-control-heights, "lg")}; }
556
+ }
557
+
558
+ // -----------------------------------------------------------------------------
559
+ // 6. 验证码 / PIN 输入
560
+ // -----------------------------------------------------------------------------
561
+
562
+ .fanui-pin {
563
+ display: inline-flex;
564
+ gap: fn.fanui-var("space-2");
565
+
566
+ &__cell {
567
+ width: 44px;
568
+ height: 52px;
569
+ padding: 0;
570
+ border: 1px solid fn.fanui-var("color-border");
571
+ border-radius: fn.fanui-var("radius-md");
572
+ background-color: fn.fanui-var("color-surface");
573
+ font-family: fn.fanui-var("font-mono");
574
+ font-size: fn.fanui-var("font-size-xl");
575
+ font-variant-numeric: tabular-nums;
576
+ text-align: center;
577
+ color: fn.fanui-var("color-text-strong");
578
+ @include mx.fanui-transition((border-color, box-shadow, transform), v.$fanui-duration-fast);
579
+
580
+ &:focus {
581
+ outline: none;
582
+ border-color: fn.fanui-var("primary");
583
+ @include mx.fanui-focus-ring();
584
+ transform: translateY(-2px);
585
+ }
586
+
587
+ &.is-filled {
588
+ border-color: fn.fanui-var("primary-border");
589
+ background-color: fn.fanui-var("primary-soft");
590
+ }
591
+
592
+ &:disabled { background-color: fn.fanui-var("color-disabled-bg"); }
593
+ }
594
+
595
+ &--filled &__cell { background-color: fn.fanui-var("color-surface-sunken"); }
596
+
597
+ &--sm &__cell { width: 36px; height: 42px; font-size: fn.fanui-var("font-size-lg"); }
598
+ &--separated { gap: fn.fanui-var("space-4"); }
599
+ }
600
+
601
+ // -----------------------------------------------------------------------------
602
+ // 7. 色板选择 Swatch
603
+ // -----------------------------------------------------------------------------
604
+
605
+ .fanui-swatch {
606
+ display: inline-flex;
607
+ flex-wrap: wrap;
608
+ gap: fn.fanui-var("space-2");
609
+
610
+ &__item {
611
+ position: relative;
612
+ display: grid;
613
+ place-items: center;
614
+ width: 30px;
615
+ height: 30px;
616
+ padding: 0;
617
+ border: 1px solid fn.fanui-alpha(#020617, 0.12);
618
+ border-radius: fn.fanui-var("radius-md");
619
+ background-color: var(--fanui-swatch-color, var(--fanui-color-border));
620
+ cursor: pointer;
621
+ @include mx.fanui-transition((transform, box-shadow), v.$fanui-duration-fast);
622
+
623
+ &:hover { transform: translateY(-2px); box-shadow: fn.fanui-var("shadow-md"); }
624
+
625
+ &[aria-checked="true"],
626
+ &.is-selected {
627
+ box-shadow: 0 0 0 2px fn.fanui-var("color-surface"), 0 0 0 4px var(--fanui-swatch-color);
628
+ }
629
+
630
+ &[aria-checked="true"]::after {
631
+ content: "";
632
+ width: 10px;
633
+ height: 6px;
634
+ border-inline-start: 2px solid #fff;
635
+ border-bottom: 2px solid #fff;
636
+ transform: rotate(-45deg) translate(1px, -1px);
637
+ filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4));
638
+ }
639
+ }
640
+
641
+ &--lg &__item { width: 40px; height: 40px; }
642
+ &--round &__item { border-radius: fn.fanui-var("radius-full"); }
643
+ }
644
+
645
+ // -----------------------------------------------------------------------------
646
+ // 8. 组合框 Combobox(输入即筛选)
647
+ // -----------------------------------------------------------------------------
648
+
649
+ .fanui-combobox {
650
+ position: relative;
651
+
652
+ &__control {
653
+ position: relative;
654
+ display: flex;
655
+ align-items: center;
656
+ }
657
+
658
+ &__input {
659
+ @include mx.fanui-form-control();
660
+ padding-inline-end: 34px;
661
+ }
662
+
663
+ &__toggle {
664
+ position: absolute;
665
+ inset-inline-end: 6px;
666
+ display: grid;
667
+ place-items: center;
668
+ width: 24px;
669
+ height: 24px;
670
+ border: 0;
671
+ border-radius: fn.fanui-var("radius-sm");
672
+ background: transparent;
673
+ color: fn.fanui-var("color-text-muted");
674
+ cursor: pointer;
675
+
676
+ &:hover { background-color: fn.fanui-var("color-hover"); }
677
+ }
678
+
679
+ &__list {
680
+ position: absolute;
681
+ inset-inline: 0;
682
+ top: calc(100% + 4px);
683
+ z-index: fn.fanui-var("z-dropdown");
684
+ max-height: 260px;
685
+ margin: 0;
686
+ padding: fn.fanui-var("space-1");
687
+ list-style: none;
688
+ overflow-y: auto;
689
+ border: 1px solid fn.fanui-var("color-border");
690
+ border-radius: fn.fanui-var("radius-md");
691
+ background-color: fn.fanui-var("color-overlay");
692
+ box-shadow: fn.fanui-var("shadow-xl");
693
+ opacity: 0;
694
+ visibility: hidden;
695
+ transform: translateY(-4px);
696
+ @include mx.fanui-transition((opacity, transform, visibility), v.$fanui-duration-fast);
697
+
698
+ &.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
699
+ }
700
+
701
+ &__option {
702
+ display: flex;
703
+ align-items: center;
704
+ justify-content: space-between;
705
+ gap: fn.fanui-var("space-2");
706
+ padding: 7px 10px;
707
+ border-radius: fn.fanui-var("radius-sm");
708
+ font-size: fn.fanui-var("font-size-sm");
709
+ color: fn.fanui-var("color-text");
710
+ cursor: pointer;
711
+
712
+ &:hover,
713
+ &.is-active { background-color: fn.fanui-var("color-hover"); }
714
+ &.is-selected { color: fn.fanui-var("primary-ink"); background-color: fn.fanui-var("primary-soft"); }
715
+ &.is-disabled { opacity: 0.5; cursor: not-allowed; }
716
+ }
717
+
718
+ &__empty {
719
+ padding: fn.fanui-var("space-4");
720
+ text-align: center;
721
+ font-size: fn.fanui-var("font-size-sm");
722
+ color: fn.fanui-var("color-text-subtle");
723
+ }
724
+
725
+ &__mark {
726
+ background-color: fn.fanui-var("warning-soft-2");
727
+ color: inherit;
728
+ font-weight: fn.fanui-var("font-weight-semibold");
729
+ }
730
+ }