@xihan-ui/styles 1.0.0-alpha.2 → 1.0.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 (122) hide show
  1. package/CHANGELOG.md +2177 -0
  2. package/README.md +1 -1
  3. package/css/accordion.css +80 -6
  4. package/css/alert.css +43 -14
  5. package/css/anchor.css +8 -8
  6. package/css/avatar-group.css +7 -3
  7. package/css/avatar.css +9 -3
  8. package/css/back-top.css +22 -1
  9. package/css/badge.css +104 -32
  10. package/css/breadcrumb.css +25 -5
  11. package/css/button-group.css +18 -13
  12. package/css/button.css +48 -11
  13. package/css/calendar.css +150 -7
  14. package/css/card.css +20 -11
  15. package/css/carousel.css +36 -5
  16. package/css/cascader.css +211 -124
  17. package/css/checkbox-group.css +72 -22
  18. package/css/checkbox.css +70 -13
  19. package/css/clipboard.css +2 -1
  20. package/css/code-block.css +12 -7
  21. package/css/collapsible.css +49 -3
  22. package/css/color-picker.css +132 -46
  23. package/css/combobox.css +132 -60
  24. package/css/composer.css +5 -3
  25. package/css/context-menu.css +113 -74
  26. package/css/date-field.css +93 -16
  27. package/css/date-picker.css +293 -72
  28. package/css/descriptions.css +11 -5
  29. package/css/dialog.css +47 -14
  30. package/css/download-trigger.css +76 -0
  31. package/css/drawer.css +57 -16
  32. package/css/dynamic-input.css +75 -17
  33. package/css/editable.css +12 -10
  34. package/css/ellipsis.css +7 -1
  35. package/css/empty-state.css +22 -12
  36. package/css/field.css +16 -6
  37. package/css/fieldset.css +69 -0
  38. package/css/file-upload.css +46 -8
  39. package/css/flex.css +13 -7
  40. package/css/float-button.css +23 -1
  41. package/css/floating-panel.css +316 -0
  42. package/css/form.css +4 -4
  43. package/css/grid.css +11 -5
  44. package/css/heatmap.css +411 -0
  45. package/css/highlight.css +2 -2
  46. package/css/hotkeys.css +90 -0
  47. package/css/hover-card.css +43 -45
  48. package/css/image-cropper.css +233 -0
  49. package/css/image-viewer.css +155 -17
  50. package/css/image.css +2 -2
  51. package/css/infinite-scroll.css +1 -1
  52. package/css/json-viewer.css +274 -0
  53. package/css/layout.css +19 -15
  54. package/css/list.css +22 -13
  55. package/css/listbox.css +24 -6
  56. package/css/loading-bar.css +12 -5
  57. package/css/log.css +7 -3
  58. package/css/marquee.css +9 -0
  59. package/css/masonry.css +63 -0
  60. package/css/mention.css +100 -36
  61. package/css/menu.css +127 -50
  62. package/css/menubar.css +155 -61
  63. package/css/navigation-menu.css +56 -31
  64. package/css/notification.css +377 -0
  65. package/css/number-animation.css +3 -3
  66. package/css/number-field.css +182 -19
  67. package/css/overlay-arrow.css +51 -0
  68. package/css/page-header.css +12 -5
  69. package/css/pagination.css +171 -14
  70. package/css/password-input.css +356 -0
  71. package/css/pin-input.css +15 -12
  72. package/css/popconfirm.css +55 -12
  73. package/css/popover.css +73 -45
  74. package/css/popselect.css +207 -57
  75. package/css/progress.css +54 -8
  76. package/css/qr-code.css +1 -1
  77. package/css/radio-group.css +27 -10
  78. package/css/rating.css +4 -4
  79. package/css/reset.css +17 -0
  80. package/css/result.css +21 -11
  81. package/css/scroll-area.css +61 -89
  82. package/css/scrollbar.css +170 -0
  83. package/css/segmented.css +215 -0
  84. package/css/select.css +298 -131
  85. package/css/separator.css +2 -2
  86. package/css/side-nav.css +127 -24
  87. package/css/signature-pad.css +144 -0
  88. package/css/skeleton.css +7 -1
  89. package/css/slider.css +14 -12
  90. package/css/space.css +117 -0
  91. package/css/spinner.css +22 -10
  92. package/css/statistic.css +9 -9
  93. package/css/steps.css +29 -12
  94. package/css/switch.css +57 -8
  95. package/css/table.css +146 -44
  96. package/css/tabs.css +13 -5
  97. package/css/tag.css +179 -0
  98. package/css/tags-input.css +80 -42
  99. package/css/text-field.css +151 -38
  100. package/css/thread.css +11 -3
  101. package/css/time-field.css +99 -18
  102. package/css/time-picker.css +261 -71
  103. package/css/timeline.css +15 -15
  104. package/css/timer.css +119 -0
  105. package/css/toast.css +129 -55
  106. package/css/toggle-group.css +30 -4
  107. package/css/toggle.css +12 -6
  108. package/css/tone.css +57 -22
  109. package/css/toolbar.css +1 -1
  110. package/css/tooltip.css +40 -31
  111. package/css/tour.css +112 -61
  112. package/css/transfer.css +98 -21
  113. package/css/tree-select.css +215 -89
  114. package/css/tree.css +131 -5
  115. package/css/typography.css +5 -5
  116. package/css/undefined.css +12 -1
  117. package/css/virtualizer.css +3 -1
  118. package/css/watermark.css +1 -2
  119. package/index.css +18 -1
  120. package/index.unlayered.css +10356 -3255
  121. package/package.json +20 -4
  122. package/css/toaster.css +0 -74
package/css/side-nav.css CHANGED
@@ -1,3 +1,31 @@
1
+ @layer xihan.motion {
2
+ /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
+ 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
+ @keyframes xh-pop-in {
5
+ from {
6
+ opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
8
+ }
9
+
10
+ to {
11
+ opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+
16
+ @keyframes xh-pop-out {
17
+ from {
18
+ opacity: 1;
19
+ scale: 1;
20
+ }
21
+
22
+ to {
23
+ opacity: 0;
24
+ scale: var(--xh-motion-scale-enter);
25
+ }
26
+ }
27
+ }
28
+
1
29
  @layer xihan.components {
2
30
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
31
  [data-scope='side-nav'] {
@@ -5,6 +33,9 @@
5
33
  }
6
34
 
7
35
  [data-scope='side-nav'][data-part='root'] {
36
+ /* 作者塞进指示符槽的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
37
+ --xh-icon-size: var(--xh-side-nav-icon-size, var(--xh-glyph-size-text));
38
+
8
39
  inline-size: var(--xh-side-nav-w, 240px);
9
40
  display: flex;
10
41
  flex-direction: column;
@@ -45,9 +76,9 @@
45
76
  }
46
77
 
47
78
  [data-scope='side-nav'][data-part='group-label'] {
48
- padding-block: var(--xh-space-1);
49
- padding-inline: var(--xh-control-px-md);
50
- font-size: var(--xh-font-size-xs);
79
+ padding-block: var(--xh-side-nav-group-label-py, var(--xh-space-1));
80
+ padding-inline: var(--xh-side-nav-group-label-px, var(--xh-control-px-md));
81
+ font-size: var(--xh-text-caption-size);
51
82
  color: var(--xh-fg-subtle);
52
83
  user-select: none;
53
84
  }
@@ -64,17 +95,24 @@
64
95
  list-style: none;
65
96
  }
66
97
 
98
+ /* 叶子行的列表项:链接自己撑满,这一层只负责列表语义 */
99
+ [data-scope='side-nav'][data-part='item'] {
100
+ display: flex;
101
+ flex-direction: column;
102
+ list-style: none;
103
+ }
104
+
67
105
  /* —— 行:分支按钮与链接同一副长相 —— */
68
106
  [data-scope='side-nav'][data-part='branch-trigger'],
69
107
  [data-scope='side-nav'][data-part='link'] {
70
108
  display: flex;
71
109
  align-items: center;
72
- gap: var(--xh-control-gap-md);
110
+ gap: var(--xh-side-nav-link-gap, var(--xh-control-gap-md));
73
111
  inline-size: 100%;
74
112
  min-block-size: var(--xh-control-h-md);
75
- padding-inline: var(--xh-control-px-md);
113
+ padding-inline: var(--xh-side-nav-link-px, var(--xh-control-px-md));
76
114
  border: none;
77
- border-radius: var(--xh-shape-control);
115
+ border-radius: var(--xh-side-nav-link-radius, var(--xh-shape-control));
78
116
  background: none;
79
117
  color: inherit;
80
118
  font-size: var(--xh-text-body-size);
@@ -83,9 +121,10 @@
83
121
  cursor: pointer;
84
122
  }
85
123
 
86
- [data-scope='side-nav'][data-part='branch-trigger']:hover,
87
- [data-scope='side-nav'][data-part='link']:hover {
88
- background: var(--xh-bg-subtle-hover);
124
+ /* 悬停与键盘锚点共用中性灰轻档:锚点只认连接层打的 data-highlighted,不读 :focus */
125
+ [data-scope='side-nav'][data-part='branch-trigger']:not([data-disabled]):is(:hover, [data-highlighted]),
126
+ [data-scope='side-nav'][data-part='link']:not([data-disabled]):is(:hover, [data-highlighted]) {
127
+ background: var(--xh-side-nav-row-bg-hover, var(--xh-bg-subtle));
89
128
  }
90
129
 
91
130
  [data-scope='side-nav'][data-part='branch-trigger']:focus-visible,
@@ -94,15 +133,17 @@
94
133
  outline-offset: var(--xh-ring-offset);
95
134
  }
96
135
 
97
- /* 选中的那条:品牌淡底 + 强调字 */
98
- [data-scope='side-nav'][data-part='link'][data-selected] {
136
+ /* 选中的那条:品牌淡底 + 强调字。
137
+ 守卫与轻档那条同特指度,写在其后才压得住——键盘走到当前页那一行时底色仍是品牌淡底 */
138
+ [data-scope='side-nav'][data-part='link']:not([data-disabled])[data-current] {
99
139
  background: var(--xh-bg-brand-subtle);
100
- color: var(--xh-fg-brand);
140
+ /* 淡底上的字走 strong 档:fg-brand(600)压在 brand-subtle(100)上只有 4.21 */
141
+ color: var(--xh-fg-brand-strong);
101
142
  font-weight: var(--xh-font-weight-medium);
102
143
  }
103
144
 
104
145
  /* 选中项的祖先枝:只染字色,一路指到当前页 */
105
- [data-scope='side-nav'][data-part='branch-trigger'][data-active] {
146
+ [data-scope='side-nav'][data-part='branch-trigger'][data-in-path] {
106
147
  color: var(--xh-fg-brand);
107
148
  }
108
149
 
@@ -121,10 +162,25 @@
121
162
  white-space: nowrap;
122
163
  }
123
164
 
124
- /* 折叠成图标栏:文字与展开指示整个藏掉、行内容居中,不裁半个字。
165
+ /* 折叠成图标栏:行文字裁到看不见但仍参与播报,行内容居中,不裁半个字。
166
+ 用 clip-path 而不是 display:none——图标栏里按钮与链接的可及名就是这段文字,
167
+ 藏成 display:none 它们就一个名字都没有了。
168
+ white-space: nowrap 防止 1px 宽把文本挤成竖排。
125
169
  弹出面板里的行不在此列——面板是正常宽度的浮层,文字照常显示 */
126
170
  [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-text']:not([data-popout] *),
127
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='link-text']:not([data-popout] *),
171
+ [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='link-text']:not([data-popout] *) {
172
+ position: absolute;
173
+ inline-size: 1px;
174
+ block-size: 1px;
175
+ padding: 0;
176
+ margin: -1px;
177
+ overflow: hidden;
178
+ clip-path: inset(50%);
179
+ white-space: nowrap;
180
+ border: 0;
181
+ }
182
+
183
+ /* 展开指示是纯装饰(aria-hidden),折叠态整个不占位 */
128
184
  [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-indicator']:not([data-popout] *) {
129
185
  display: none;
130
186
  }
@@ -135,34 +191,81 @@
135
191
  padding-inline: 0;
136
192
  }
137
193
 
138
- /* 折叠态的弹出面板:贴着触发按钮旁侧的浮层卡片,坐标由连接层写在行内样式上。
194
+ /* 弹出面板的定位层:坐标由连接层写在行内样式上,这里只管层号与命中区。
195
+ 它被搬到浮层落点、不在侧栏子树内,所以卡片外观仍归面板本身;
196
+ 这里不设 display,UA 的 [hidden]{display:none} 就仍然压得住收起态 */
197
+ [data-scope='side-nav'][data-part='positioner'] {
198
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,面板继承它限高;
199
+ 引擎没算(尚未落位 / 无引擎 / 贴边归零)时不额外限高 */
200
+ --xh-_side-nav-available-h: 100vh;
201
+
202
+ position: fixed;
203
+ z-index: var(--xh-side-nav-popout-layer, var(--xh-layer-popover));
204
+ pointer-events: auto;
205
+ }
206
+
207
+ /* 还没落位就先藏着:visibility 而非 display,display:none 会让引擎量不到面板尺寸 */
208
+ [data-scope='side-nav'][data-part='positioner'][data-hidden] {
209
+ visibility: hidden;
210
+ }
211
+
212
+ /* 折叠态的弹出面板:贴着触发按钮旁侧的浮层卡片。
139
213
  面板自己不吃子层缩进(padding 整圈盖掉),面板内的嵌套子层照常缩进 */
140
214
  [data-scope='side-nav'][data-part='branch-content'][data-popout] {
141
- z-index: var(--xh-side-nav-popout-layer, var(--xh-layer-popover));
142
215
  min-inline-size: var(--xh-side-nav-popout-min-w, var(--xh-overlay-menu-min-w));
143
216
  max-inline-size: var(--xh-side-nav-popout-max-w, var(--xh-overlay-max-w));
144
217
  padding: var(--xh-side-nav-popout-p, var(--xh-space-1));
145
218
  border: var(--xh-stroke-thin) solid var(--xh-side-nav-popout-border, var(--xh-border-default));
146
219
  border-radius: var(--xh-side-nav-popout-radius, var(--xh-shape-surface));
147
220
  background: var(--xh-side-nav-popout-bg, var(--xh-bg-surface));
148
- box-shadow: var(--xh-side-nav-popout-shadow, var(--xh-elevation-2));
149
- pointer-events: auto;
221
+ box-shadow: var(--xh-side-nav-popout-shadow, var(--xh-elevation-floating));
222
+
223
+ /* 子导航条目数没有上限,矮视口下不限高就整块伸出视口且滚不到 */
224
+ max-block-size: min(var(--xh-side-nav-popout-max-h, var(--xh-overlay-menu-max-h)), var(--xh-_side-nav-available-h));
225
+ overflow-y: auto;
226
+ }
227
+
228
+ [data-scope='side-nav'][data-part='branch-content'][data-popout][data-state='open'] {
229
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
230
+ }
231
+
232
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
233
+ 免得动画结束到收起落下来之间闪一下原样 */
234
+ [data-scope='side-nav'][data-part='branch-content'][data-popout][data-state='closed'] {
235
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
150
236
  }
151
237
 
152
238
  [data-scope='side-nav'][data-part='branch-indicator'] {
153
239
  margin-inline-start: auto;
154
240
  display: inline-flex;
155
241
  flex: none;
156
- transition: rotate var(--xh-duration-fast) var(--xh-ease-standard);
242
+ transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
157
243
  }
158
244
 
159
245
  [data-scope='side-nav'][data-part='branch-indicator'][data-state='open'] {
160
246
  rotate: 90deg;
161
247
  }
162
248
 
163
- @media (prefers-reduced-motion: reduce) {
164
- [data-scope='side-nav'][data-part='branch-indicator'] {
165
- transition: none;
166
- }
249
+ /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
250
+ [data-scope='side-nav'][data-part='branch-indicator']:empty::before {
251
+ content: '';
252
+ display: inline-block;
253
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
254
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
255
+ vertical-align: var(--xh-glyph-baseline-shift);
256
+ background-color: currentColor;
257
+ -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
258
+ mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
259
+ }
260
+
261
+ /* 声明过任何 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去。
262
+ 写在文件末尾:兜底必须排在该部件所有 display 声明之后 */
263
+ [data-scope='side-nav'][data-part='root'][hidden],
264
+ [data-scope='side-nav'][data-part='group'][hidden],
265
+ [data-scope='side-nav'][data-part='branch'][hidden],
266
+ [data-scope='side-nav'][data-part='item'][hidden],
267
+ [data-scope='side-nav'][data-part='link'][hidden],
268
+ [data-scope='side-nav'][data-part='branch-indicator'][hidden] {
269
+ display: none;
167
270
  }
168
271
  }
@@ -0,0 +1,144 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='signature-pad'] {
4
+ --xh-signature-pad-skin: 1;
5
+ }
6
+
7
+ /* position 是给表单影子做定位基准的:那份输入被连接层内联写成绝对定位,
8
+ 根上不留基准它就会贴到更外层的定位祖先上,原生校验提示跟着飘到页面别处。 */
9
+ [data-scope='signature-pad'][data-part='root'] {
10
+ position: relative;
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--xh-signature-pad-gap, var(--xh-stack-gap-md));
14
+ }
15
+
16
+ [data-scope='signature-pad'][data-part='label'] {
17
+ color: var(--xh-signature-pad-label-fg, var(--xh-fg-muted));
18
+ font-size: var(--xh-signature-pad-label-font-size, var(--xh-text-label-size));
19
+ font-weight: var(--xh-signature-pad-label-font-weight, var(--xh-text-label-weight));
20
+ line-height: var(--xh-leading-none);
21
+ }
22
+
23
+ [data-scope='signature-pad'][data-part='label'][data-disabled] {
24
+ color: var(--xh-fg-subtle);
25
+ }
26
+
27
+ /* 画布是块状的写字区:宽度铺满外层,高度由宽高比决定。
28
+ 控件行高那把尺量的是一行控件,一块要写字的面积用不上它。
29
+ touch-action 由连接层内联写死,触摸落笔才不会被页面滚动抢走。 */
30
+ [data-scope='signature-pad'][data-part='control'] {
31
+ display: block;
32
+ inline-size: 100%;
33
+ aspect-ratio: var(--xh-signature-pad-aspect-ratio, 5 / 2);
34
+ border: var(--xh-stroke-thin) solid var(--xh-signature-pad-border, var(--xh-border-control));
35
+ border-radius: var(--xh-signature-pad-radius, var(--xh-shape-surface));
36
+ background: var(--xh-signature-pad-bg, var(--xh-bg-surface));
37
+ cursor: crosshair;
38
+ /* 一划就会把画布连同旁边的文字一起刷成选中态 */
39
+ user-select: none;
40
+ }
41
+
42
+ [data-scope='signature-pad'][data-part='control'][data-invalid] {
43
+ border-color: var(--xh-border-invalid);
44
+ }
45
+
46
+ /* 落笔期间把描边压深一档。border-strong 是装饰梯度里的一档,比 border-control 更淡,
47
+ 用在这儿会让描边在落笔时反而变弱 */
48
+ [data-scope='signature-pad'][data-part='control'][data-drawing] {
49
+ border-color: var(--xh-signature-pad-border-drawing, var(--xh-border-control-hover));
50
+ }
51
+
52
+ /* 只读仍看得见签名,只是改不动:收掉手型,别暗示"在这儿能写" */
53
+ [data-scope='signature-pad'][data-part='control'][data-readonly] {
54
+ cursor: default;
55
+ }
56
+
57
+ [data-scope='signature-pad'][data-part='control'][data-disabled] {
58
+ background: var(--xh-signature-pad-bg-disabled, var(--xh-bg-muted));
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ /* 基准线:位置由连接层按百分比给出,这里只管它长什么样。
63
+ 它是"请在这条线上签"的功能性提示,不是装饰,颜色走控件描边那一档才看得见 */
64
+ [data-scope='signature-pad'][data-part='guide'] {
65
+ stroke: var(--xh-signature-pad-guide-stroke, var(--xh-border-control));
66
+ stroke-width: var(--xh-stroke-thin);
67
+ stroke-dasharray: 6 6;
68
+ }
69
+
70
+ /* 笔迹是一条填充路径(每一笔是它的一条子路径),不是描边:
71
+ 粗细要随压感变,而 stroke-width 全线只有一个值 */
72
+ [data-scope='signature-pad'][data-part='segment'] {
73
+ fill: var(--xh-signature-pad-ink, var(--xh-fg-default));
74
+ stroke: none;
75
+ }
76
+
77
+ [data-scope='signature-pad'][data-part='clear-trigger'] {
78
+ display: inline-flex;
79
+ align-self: flex-start;
80
+ align-items: center;
81
+ justify-content: center;
82
+ gap: var(--xh-signature-pad-clear-gap, var(--xh-control-gap-sm));
83
+ block-size: var(--xh-signature-pad-clear-h, var(--xh-control-h-sm));
84
+ padding-inline: var(--xh-signature-pad-clear-px, var(--xh-control-px-sm));
85
+ border: var(--xh-stroke-thin) solid var(--xh-signature-pad-clear-border, var(--xh-border-control));
86
+ border-radius: var(--xh-signature-pad-clear-radius, var(--xh-shape-control));
87
+ background: var(--xh-signature-pad-clear-bg, var(--xh-bg-surface));
88
+ color: var(--xh-signature-pad-clear-fg, var(--xh-fg-default));
89
+ font-size: var(--xh-text-label-size);
90
+ line-height: var(--xh-leading-none);
91
+ cursor: pointer;
92
+ transition:
93
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
94
+ scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
95
+ }
96
+
97
+ [data-scope='signature-pad'][data-part='clear-trigger']:hover {
98
+ background: var(--xh-signature-pad-clear-bg-hover, var(--xh-bg-subtle-hover));
99
+ }
100
+
101
+ /* 按下:换到按压档底色并轻微下压,松手弹回;禁用钮不压 */
102
+ [data-scope='signature-pad'][data-part='clear-trigger']:not(:disabled):active {
103
+ background: var(--xh-signature-pad-clear-bg-active, var(--xh-bg-subtle-active));
104
+ scale: var(--xh-motion-scale-press);
105
+ }
106
+
107
+ [data-scope='signature-pad'][data-part='clear-trigger']:focus-visible {
108
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
109
+ outline-offset: var(--xh-ring-offset);
110
+ }
111
+
112
+ /* 空画布时按钮照常可按(收起它会让焦点掉回 body),只把它压淡一档 */
113
+ [data-scope='signature-pad'][data-part='clear-trigger'][data-empty] {
114
+ color: var(--xh-fg-muted);
115
+ }
116
+
117
+ /* 单体原生控件走 :disabled;集合条目那套 [data-disabled] 在这里命不中。
118
+ 禁用只换前景色,不再叠 opacity——两样一起上会把文字压到看不见 */
119
+ [data-scope='signature-pad'][data-part='clear-trigger']:disabled {
120
+ background: var(--xh-signature-pad-clear-bg-disabled, var(--xh-bg-muted));
121
+ color: var(--xh-fg-disabled);
122
+ cursor: not-allowed;
123
+ }
124
+
125
+ /* 签没签的那句话。读屏靠它播报,看得见的人也用得上,因此不做视觉隐藏 */
126
+ [data-scope='signature-pad'][data-part='status'] {
127
+ color: var(--xh-signature-pad-status-fg, var(--xh-fg-muted));
128
+ font-size: var(--xh-signature-pad-status-font-size, var(--xh-text-label-size));
129
+ line-height: var(--xh-leading-none);
130
+ }
131
+
132
+ /* 表单影子的视觉隐藏(绝对定位 + 1px + clip-path)连接层内联给全了,这里不重复也不覆盖,
133
+ 只掐掉指针命中:那个 1px 的框压在画布上会截走本该落到画布上的落笔。 */
134
+ [data-scope='signature-pad'][data-part='hidden-input'] {
135
+ pointer-events: none;
136
+ }
137
+
138
+ /* display 会盖掉 UA 的 [hidden]{display:none},凡是被赋了 display 的角色节点都要显式让位 */
139
+ [data-scope='signature-pad'][data-part='root'][hidden],
140
+ [data-scope='signature-pad'][data-part='control'][hidden],
141
+ [data-scope='signature-pad'][data-part='clear-trigger'][hidden] {
142
+ display: none;
143
+ }
144
+ }
package/css/skeleton.css CHANGED
@@ -46,7 +46,7 @@
46
46
  );
47
47
  background-repeat: no-repeat;
48
48
  background-size: 200% 100%;
49
- animation: xh-skeleton-shimmer var(--xh-skeleton-duration, calc(var(--xh-duration-slow) * 5)) var(--xh-ease-standard) infinite;
49
+ animation: xh-skeleton-shimmer var(--xh-skeleton-duration, var(--xh-shimmer-duration)) var(--xh-ease-standard) infinite;
50
50
  }
51
51
 
52
52
  /* 一行文字的高度取正文字号,圆角拉满,看起来就是一行还没排出来的字 */
@@ -73,6 +73,7 @@
73
73
  * 减少动效:撤掉微光那层,只留静态底色。
74
74
  * 骨架屏是自己冒出来、会一直循环到数据回来为止的动效,正是最容易引发不适的那一类;
75
75
  * 这里不能只把时长压到接近零——那样光带会停在某一帧上,留下一道说不清的渐变。
76
+ * 两条规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion。
76
77
  */
77
78
  @media (prefers-reduced-motion: reduce) {
78
79
  [data-scope='skeleton'][data-part='bone'] {
@@ -80,4 +81,9 @@
80
81
  animation: none;
81
82
  }
82
83
  }
84
+
85
+ :where([data-motion='reduce']) [data-scope='skeleton'][data-part='bone'] {
86
+ background-image: none;
87
+ animation: none;
88
+ }
83
89
  }
package/css/slider.css CHANGED
@@ -13,8 +13,8 @@
13
13
  /* 私有槽位:组件令牌 → 中档字面量两级回退。轨道厚度与拇指直径同时被 control / track / thumb
14
14
  三个角色节点用到,收成两个槽位挂在 root 上,尺寸档只改这两处,靠继承发到子部件。 */
15
15
  [data-scope='slider'][data-part='root'] {
16
- --xh-_thumb-size: var(--xh-slider-thumb-size, 18px);
17
- --xh-_track-thickness: var(--xh-slider-track-thickness, 6px);
16
+ --xh-_thumb-size: var(--xh-slider-thumb-size, var(--xh-track-thumb-size));
17
+ --xh-_track-thickness: var(--xh-slider-track-thickness, var(--xh-track-thickness));
18
18
 
19
19
  display: flex;
20
20
  flex-direction: column;
@@ -116,7 +116,7 @@
116
116
  }
117
117
 
118
118
  [data-scope='slider'][data-part='range'][data-invalid] {
119
- background: var(--xh-slider-range-bg-invalid, var(--xh-color-danger-500));
119
+ background: var(--xh-slider-range-bg-invalid, var(--xh-border-invalid));
120
120
  }
121
121
 
122
122
  /* 圆点浮在轨道之上:轨道是流内的,拇指是绝对定位的,天然后来居上,不必再堆 z-index。
@@ -130,9 +130,11 @@
130
130
  border: var(--xh-stroke-thick) solid var(--xh-slider-thumb-border, var(--xh-bg-surface));
131
131
  border-radius: var(--xh-slider-thumb-radius, var(--xh-shape-pill));
132
132
  background: var(--xh-slider-thumb-bg, var(--xh-_tone, var(--xh-bg-brand)));
133
- box-shadow: var(--xh-slider-thumb-shadow, var(--xh-elevation-1));
133
+ box-shadow: var(--xh-slider-thumb-shadow, var(--xh-elevation-raised));
134
134
  cursor: grab;
135
- transition: scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
135
+ transition:
136
+ scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
137
+ box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
136
138
  }
137
139
 
138
140
  /* 内联样式把 inset-inline-start 定在值所在的比例上,那是拇指的**起始边**不是圆心;
@@ -161,13 +163,13 @@
161
163
  /* 多拇指时只有手真正推着的那一个带 data-dragging,放大与光标因此不会全组一起变。
162
164
  放大用 scale 而不是改尺寸:变换绕自身中心发生,圆心不动,负外边距的算式也不用跟着改。 */
163
165
  [data-scope='slider'][data-part='thumb'][data-dragging] {
164
- scale: var(--xh-slider-thumb-scale-dragging, 1.12);
166
+ scale: var(--xh-slider-thumb-scale-dragging, var(--xh-motion-scale-drag));
165
167
  cursor: grabbing;
166
- box-shadow: var(--xh-slider-thumb-shadow-dragging, var(--xh-elevation-2));
168
+ box-shadow: var(--xh-slider-thumb-shadow-dragging, var(--xh-elevation-floating));
167
169
  }
168
170
 
169
171
  [data-scope='slider'][data-part='thumb'][data-invalid] {
170
- background: var(--xh-slider-thumb-bg-invalid, var(--xh-color-danger-500));
172
+ background: var(--xh-slider-thumb-bg-invalid, var(--xh-border-invalid));
171
173
  }
172
174
 
173
175
  [data-scope='slider'][data-part='thumb'][data-readonly] {
@@ -203,13 +205,13 @@
203
205
  inset-block-start: 50%;
204
206
  inline-size: var(--xh-slider-mark-size, var(--xh-space-1));
205
207
  block-size: var(--xh-slider-mark-size, var(--xh-space-1));
206
- border-radius: var(--xh-shape-pill);
208
+ border-radius: var(--xh-slider-mark-radius, var(--xh-shape-pill));
207
209
  background: var(--xh-slider-mark-bg, var(--xh-border-strong));
208
210
  translate: -50% -50%;
209
211
  }
210
212
 
211
213
  /* 落进已选区间的刻度点跟 range 同色 */
212
- [data-scope='slider'][data-part='mark'][data-active] {
214
+ [data-scope='slider'][data-part='mark'][data-passed] {
213
215
  background: var(--xh-slider-mark-bg-active, var(--xh-_tone, var(--xh-bg-brand)));
214
216
  }
215
217
 
@@ -218,7 +220,7 @@
218
220
  inset-block-start: 100%;
219
221
  margin-block-start: var(--xh-slider-mark-label-gap, var(--xh-space-1));
220
222
  color: var(--xh-slider-mark-label-fg, var(--xh-fg-subtle));
221
- font-size: var(--xh-slider-mark-label-font-size, var(--xh-font-size-xs));
223
+ font-size: var(--xh-slider-mark-label-font-size, var(--xh-text-caption-size));
222
224
  line-height: var(--xh-leading-none);
223
225
  white-space: nowrap;
224
226
  translate: -50% 0;
@@ -227,7 +229,7 @@
227
229
  user-select: none;
228
230
  }
229
231
 
230
- [data-scope='slider'][data-part='mark-label'][data-active] {
232
+ [data-scope='slider'][data-part='mark-label'][data-passed] {
231
233
  color: var(--xh-slider-mark-label-fg-active, var(--xh-fg-default));
232
234
  }
233
235
 
package/css/space.css ADDED
@@ -0,0 +1,117 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='space'] {
4
+ --xh-space-skin: 1;
5
+ }
6
+
7
+ [data-scope='space'][data-part='root'] {
8
+ display: flex;
9
+ flex-wrap: nowrap;
10
+ /* 不写档位也按 md 留白,这是与裸弹性容器的分野;使用者槽位排在档位之前,写死一个值即盖过所有档 */
11
+ gap: var(--xh-space-gap, var(--xh-layout-gap-md));
12
+ }
13
+
14
+ /* 声明了 display 就盖掉了 UA 的 [hidden] 规则,这里还回去 */
15
+ [data-scope='space'][data-part='root'][hidden] {
16
+ display: none;
17
+ }
18
+
19
+ /* 行内盒:宽度收到内容,能和文字排在同一行 */
20
+ [data-scope='space'][data-part='root'][data-inline]:not([hidden]) {
21
+ display: inline-flex;
22
+ }
23
+
24
+ /* 分隔符:夹在两个子项之间的装饰件,样子由放进去的内容决定。
25
+ 这里只兜住两件事——不被主轴挤扁(一条 1px 的竖线会被弹性收缩吃没),
26
+ 交叉轴上不单独发话,跟着容器的对齐走:横排随中线、竖排随拉伸 */
27
+ [data-scope='space'][data-part='split'] {
28
+ flex: none;
29
+ }
30
+
31
+ /* —— 方向与缺省对齐 —— */
32
+ /* 横排时按中线对齐:一排里混着文字、按钮、标签,高度本就不齐,居中最不容易看出错位 */
33
+ [data-scope='space'][data-part='root'][data-orientation='horizontal'] {
34
+ flex-direction: row;
35
+ align-items: center;
36
+ }
37
+
38
+ /* 竖排时拉伸:一列里的块级子项该占满容器宽度 */
39
+ [data-scope='space'][data-part='root'][data-orientation='vertical'] {
40
+ flex-direction: column;
41
+ align-items: stretch;
42
+ }
43
+
44
+ /* —— 折行 —— */
45
+ [data-scope='space'][data-part='root'][data-wrap] {
46
+ flex-wrap: wrap;
47
+ }
48
+
49
+ /* —— 交叉轴对齐 —— */
50
+ /* 排在方向之后:两者特指度相同,写了 align 就该压过随方向来的那份缺省 */
51
+ [data-scope='space'][data-part='root'][data-align='start'] {
52
+ align-items: flex-start;
53
+ }
54
+
55
+ [data-scope='space'][data-part='root'][data-align='center'] {
56
+ align-items: center;
57
+ }
58
+
59
+ [data-scope='space'][data-part='root'][data-align='end'] {
60
+ align-items: flex-end;
61
+ }
62
+
63
+ [data-scope='space'][data-part='root'][data-align='stretch'] {
64
+ align-items: stretch;
65
+ }
66
+
67
+ [data-scope='space'][data-part='root'][data-align='baseline'] {
68
+ align-items: baseline;
69
+ }
70
+
71
+ /* —— 主轴分布 —— */
72
+ [data-scope='space'][data-part='root'][data-justify='start'] {
73
+ justify-content: flex-start;
74
+ }
75
+
76
+ [data-scope='space'][data-part='root'][data-justify='center'] {
77
+ justify-content: center;
78
+ }
79
+
80
+ [data-scope='space'][data-part='root'][data-justify='end'] {
81
+ justify-content: flex-end;
82
+ }
83
+
84
+ [data-scope='space'][data-part='root'][data-justify='between'] {
85
+ justify-content: space-between;
86
+ }
87
+
88
+ [data-scope='space'][data-part='root'][data-justify='around'] {
89
+ justify-content: space-around;
90
+ }
91
+
92
+ [data-scope='space'][data-part='root'][data-justify='evenly'] {
93
+ justify-content: space-evenly;
94
+ }
95
+
96
+ /* —— 间距档位 —— */
97
+ /* 每一档指向一个间距令牌,折行时行与行之间也吃这份间距 */
98
+ [data-scope='space'][data-part='root'][data-gap='xs'] {
99
+ gap: var(--xh-space-gap, var(--xh-layout-gap-xs));
100
+ }
101
+
102
+ [data-scope='space'][data-part='root'][data-gap='sm'] {
103
+ gap: var(--xh-space-gap, var(--xh-layout-gap-sm));
104
+ }
105
+
106
+ [data-scope='space'][data-part='root'][data-gap='md'] {
107
+ gap: var(--xh-space-gap, var(--xh-layout-gap-md));
108
+ }
109
+
110
+ [data-scope='space'][data-part='root'][data-gap='lg'] {
111
+ gap: var(--xh-space-gap, var(--xh-layout-gap-lg));
112
+ }
113
+
114
+ [data-scope='space'][data-part='root'][data-gap='xl'] {
115
+ gap: var(--xh-space-gap, var(--xh-layout-gap-xl));
116
+ }
117
+ }
package/css/spinner.css CHANGED
@@ -30,25 +30,26 @@
30
30
  /* 边框算进直径,换厚度不会把圆环撑大 */
31
31
  box-sizing: border-box;
32
32
  flex: none;
33
- inline-size: var(--xh-spinner-size, var(--xh-control-action-size));
34
- block-size: var(--xh-spinner-size, var(--xh-control-action-size));
33
+ inline-size: var(--xh-spinner-size, var(--xh-glyph-size-md));
34
+ block-size: var(--xh-spinner-size, var(--xh-glyph-size-md));
35
35
  border: var(--xh-spinner-thickness, var(--xh-stroke-thick)) solid var(--xh-spinner-track, var(--xh-border-default));
36
36
 
37
37
  /* 一整圈是轨道色,只有起始边换成前景色,转起来才看得出方向 */
38
38
  border-block-start-color: var(--xh-spinner-fg, var(--xh-bg-brand));
39
39
  border-radius: var(--xh-spinner-radius, var(--xh-shape-pill));
40
- animation: xh-spinner-rotate var(--xh-spinner-duration, calc(var(--xh-duration-slow) * 2)) linear infinite;
40
+ animation: xh-spinner-rotate var(--xh-spinner-duration, var(--xh-spin-duration)) linear infinite;
41
41
  }
42
42
 
43
- /* 三档直径对齐控件档位,缺省档写在上面的基础规则里 */
43
+ /* 三档直径走字形尺寸那把尺:圆环与同一行文字里的图标同级,三档同族才不会在 compact 下错位。
44
+ 缺省档写在上面的基础规则里 */
44
45
  [data-scope='spinner'][data-part='root'][data-size='sm']::before {
45
- inline-size: var(--xh-control-indicator-size);
46
- block-size: var(--xh-control-indicator-size);
46
+ inline-size: var(--xh-glyph-size-sm);
47
+ block-size: var(--xh-glyph-size-sm);
47
48
  }
48
49
 
49
50
  [data-scope='spinner'][data-part='root'][data-size='lg']::before {
50
- inline-size: var(--xh-control-h-md);
51
- block-size: var(--xh-control-h-md);
51
+ inline-size: var(--xh-glyph-size-lg);
52
+ block-size: var(--xh-glyph-size-lg);
52
53
  }
53
54
 
54
55
  /* 语气只换起始边那一段,轨道留在中性描边上:轨道与起始边的反差就是转向的读法,
@@ -61,12 +62,13 @@
61
62
 
62
63
  [data-scope='spinner'][data-part='label'] {
63
64
  color: var(--xh-spinner-label-fg, var(--xh-fg-muted));
64
- font-size: var(--xh-spinner-label-size, var(--xh-font-size-sm));
65
+ font-size: var(--xh-spinner-label-size, var(--xh-text-secondary-size));
65
66
  line-height: var(--xh-leading-none);
66
67
  }
67
68
 
68
69
  /* 减少动效:圆环停下并整圈换成虚线,静止的形状仍然读得出"这里还没好";
69
- "在等什么"由 role=status 上的名字与可见文案继续承担 */
70
+ "在等什么"由 role=status 上的名字与可见文案继续承担。
71
+ 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
70
72
  @media (prefers-reduced-motion: reduce) {
71
73
  [data-scope='spinner'][data-part='root']::before {
72
74
  border-style: dotted;
@@ -79,4 +81,14 @@
79
81
  border-color: var(--xh-spinner-fg, var(--xh-_tone, var(--xh-bg-brand)));
80
82
  }
81
83
  }
84
+
85
+ :where([data-motion='reduce']) [data-scope='spinner'][data-part='root']::before {
86
+ border-style: dotted;
87
+ border-color: var(--xh-spinner-fg, var(--xh-bg-brand));
88
+ animation: none;
89
+ }
90
+
91
+ :where([data-motion='reduce']) [data-scope='spinner'][data-part='root'][data-tone]::before {
92
+ border-color: var(--xh-spinner-fg, var(--xh-_tone, var(--xh-bg-brand)));
93
+ }
82
94
  }