@xihan-ui/styles 1.0.0-alpha.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 (114) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +27 -0
  4. package/css/accordion.css +108 -0
  5. package/css/affix.css +27 -0
  6. package/css/alert.css +108 -0
  7. package/css/anchor.css +137 -0
  8. package/css/avatar-group.css +82 -0
  9. package/css/avatar.css +55 -0
  10. package/css/back-top.css +61 -0
  11. package/css/badge.css +50 -0
  12. package/css/breadcrumb.css +127 -0
  13. package/css/button-group.css +131 -0
  14. package/css/button.css +98 -0
  15. package/css/calendar.css +219 -0
  16. package/css/card.css +116 -0
  17. package/css/carousel.css +193 -0
  18. package/css/cascader.css +397 -0
  19. package/css/checkbox-group.css +185 -0
  20. package/css/checkbox.css +91 -0
  21. package/css/clipboard.css +133 -0
  22. package/css/code-block.css +81 -0
  23. package/css/collapsible.css +63 -0
  24. package/css/color-picker.css +424 -0
  25. package/css/combobox.css +433 -0
  26. package/css/composer.css +127 -0
  27. package/css/context-menu.css +263 -0
  28. package/css/countdown.css +21 -0
  29. package/css/date-field.css +200 -0
  30. package/css/date-picker.css +231 -0
  31. package/css/descriptions.css +200 -0
  32. package/css/dialog.css +127 -0
  33. package/css/drawer.css +372 -0
  34. package/css/dynamic-input.css +157 -0
  35. package/css/editable.css +249 -0
  36. package/css/ellipsis.css +41 -0
  37. package/css/empty-state.css +78 -0
  38. package/css/field.css +65 -0
  39. package/css/file-upload.css +298 -0
  40. package/css/flex.css +95 -0
  41. package/css/float-button.css +121 -0
  42. package/css/form.css +185 -0
  43. package/css/gradient-text.css +62 -0
  44. package/css/grid.css +428 -0
  45. package/css/highlight.css +21 -0
  46. package/css/hover-card.css +146 -0
  47. package/css/icon-wrapper.css +64 -0
  48. package/css/icon.css +54 -0
  49. package/css/image.css +45 -0
  50. package/css/infinite-scroll.css +22 -0
  51. package/css/layers.css +2 -0
  52. package/css/layout.css +169 -0
  53. package/css/list.css +117 -0
  54. package/css/listbox.css +174 -0
  55. package/css/loading-bar.css +66 -0
  56. package/css/log.css +66 -0
  57. package/css/marquee.css +182 -0
  58. package/css/mention.css +222 -0
  59. package/css/menu.css +144 -0
  60. package/css/menubar.css +271 -0
  61. package/css/motion.css +58 -0
  62. package/css/navigation-menu.css +270 -0
  63. package/css/number-animation.css +32 -0
  64. package/css/number-field.css +219 -0
  65. package/css/page-header.css +85 -0
  66. package/css/pagination.css +148 -0
  67. package/css/pin-input.css +157 -0
  68. package/css/popconfirm.css +150 -0
  69. package/css/popover.css +150 -0
  70. package/css/popselect.css +207 -0
  71. package/css/progress.css +37 -0
  72. package/css/qr-code.css +50 -0
  73. package/css/radio-group.css +101 -0
  74. package/css/rating.css +141 -0
  75. package/css/reset.css +22 -0
  76. package/css/result.css +104 -0
  77. package/css/scroll-area.css +176 -0
  78. package/css/select.css +264 -0
  79. package/css/separator.css +17 -0
  80. package/css/skeleton.css +78 -0
  81. package/css/slider.css +197 -0
  82. package/css/spinner.css +74 -0
  83. package/css/splitter.css +127 -0
  84. package/css/statistic.css +67 -0
  85. package/css/steps.css +252 -0
  86. package/css/switch.css +61 -0
  87. package/css/table.css +404 -0
  88. package/css/tabs.css +157 -0
  89. package/css/tags-input.css +360 -0
  90. package/css/text-field.css +195 -0
  91. package/css/thread.css +107 -0
  92. package/css/time-field.css +200 -0
  93. package/css/time-picker.css +347 -0
  94. package/css/time.css +20 -0
  95. package/css/timeline.css +284 -0
  96. package/css/toast.css +195 -0
  97. package/css/toaster.css +69 -0
  98. package/css/toggle-group.css +160 -0
  99. package/css/toggle.css +103 -0
  100. package/css/tone.css +85 -0
  101. package/css/toolbar.css +114 -0
  102. package/css/tooltip.css +109 -0
  103. package/css/tour.css +305 -0
  104. package/css/transfer.css +318 -0
  105. package/css/tree-select.css +441 -0
  106. package/css/tree.css +204 -0
  107. package/css/typography.css +151 -0
  108. package/css/undefined.css +38 -0
  109. package/css/virtualizer.css +104 -0
  110. package/css/visually-hidden.css +13 -0
  111. package/css/watermark.css +48 -0
  112. package/index.css +113 -0
  113. package/index.unlayered.css +16164 -0
  114. package/package.json +163 -0
@@ -0,0 +1,284 @@
1
+ @layer xihan.components {
2
+ [data-scope='timeline'][data-part='root'] {
3
+ /* 尺寸档只换这几个私有槽,条目、圆点与文字都从 root 继承它们,
4
+ 换档不必给每个部件各写一条选择器 */
5
+ --xh-_timeline-dot-size: 10px;
6
+ --xh-_timeline-gutter: var(--xh-space-3);
7
+ --xh-_timeline-item-gap: var(--xh-space-5);
8
+ --xh-_timeline-title-font-size: var(--xh-text-label-size);
9
+ --xh-_timeline-caption-font-size: var(--xh-font-size-xs);
10
+ /* 标题首行占多高。圆点按它居中下移,圆心才落在标题这一行的中线上 */
11
+ --xh-_timeline-line-h: calc(
12
+ var(--xh-timeline-title-font-size, var(--xh-_timeline-title-font-size)) * var(--xh-leading-tight)
13
+ );
14
+
15
+ /* 条目之间不设 gap:留白由 content 自己的边距撑出来,连线才连得到下一条的圆点。
16
+ ol 的项目符号与内外边距是 UA 给的,库里没有全局 preflight,只能在这儿自己抹平 */
17
+ display: flex;
18
+ margin: 0;
19
+ padding: 0;
20
+ color: var(--xh-timeline-fg, var(--xh-fg-default));
21
+ list-style: none;
22
+ }
23
+
24
+ [data-scope='timeline'][data-part='root'][hidden] {
25
+ display: none;
26
+ }
27
+
28
+ [data-scope='timeline'][data-part='root'][data-orientation='vertical'] {
29
+ flex-direction: column;
30
+ }
31
+
32
+ [data-scope='timeline'][data-part='root'][data-orientation='horizontal'] {
33
+ flex-direction: row;
34
+ }
35
+
36
+ /* —— 尺寸 —— */
37
+ /* 缺省档写在上面的基础规则里。圆点直径不在控件高度那把尺上,两侧直接给像素 */
38
+ [data-scope='timeline'][data-part='root'][data-size='sm'] {
39
+ --xh-_timeline-dot-size: 8px;
40
+ --xh-_timeline-gutter: var(--xh-space-2);
41
+ --xh-_timeline-item-gap: var(--xh-space-4);
42
+ --xh-_timeline-title-font-size: var(--xh-font-size-sm);
43
+ /* 字号档最小就是 xs,时刻文本不再往下走,故不重声明 caption */
44
+ }
45
+
46
+ [data-scope='timeline'][data-part='root'][data-size='lg'] {
47
+ --xh-_timeline-dot-size: 12px;
48
+ --xh-_timeline-gutter: var(--xh-space-4);
49
+ --xh-_timeline-item-gap: var(--xh-space-6);
50
+ --xh-_timeline-title-font-size: var(--xh-font-size-lg);
51
+ --xh-_timeline-caption-font-size: var(--xh-font-size-sm);
52
+ }
53
+
54
+ /* —— 条目 —— */
55
+ /* 圆点与连线占一列(横排时占一行),内容占另一列,用 grid 而不是再包一层节点:
56
+ 解剖里没有那个包裹层,作者也不该被逼着多写一个 div */
57
+ [data-scope='timeline'][data-part='item'] {
58
+ display: grid;
59
+ column-gap: var(--xh-timeline-gutter, var(--xh-_timeline-gutter));
60
+ /* 库里没有全局 preflight,列表项的外边距在这里自己抹平 */
61
+ margin: 0;
62
+ /* 允许被压缩,内容里的省略号才有生效的余地 */
63
+ min-inline-size: 0;
64
+ }
65
+
66
+ [data-scope='timeline'][data-part='item'][hidden] {
67
+ display: none;
68
+ }
69
+
70
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'] {
71
+ grid-template-columns: auto minmax(0, 1fr);
72
+ grid-template-rows: auto minmax(0, 1fr);
73
+ }
74
+
75
+ /* 横排时每条等分整行 */
76
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'] {
77
+ grid-template-columns: auto minmax(0, 1fr);
78
+ grid-template-rows: auto minmax(0, 1fr);
79
+ flex: 1;
80
+ }
81
+
82
+ /* —— 圆点 —— */
83
+ [data-scope='timeline'][data-part='indicator'] {
84
+ display: flex;
85
+ flex: none;
86
+ align-items: center;
87
+ justify-content: center;
88
+ inline-size: var(--xh-timeline-indicator-size, var(--xh-_timeline-dot-size));
89
+ block-size: var(--xh-timeline-indicator-size, var(--xh-_timeline-dot-size));
90
+ border-radius: var(--xh-radius-full);
91
+ /* 使用者令牌排在语气之前;两者都没有时退回一个中性的实心点 */
92
+ background: var(--xh-timeline-indicator-bg, var(--xh-_tone-soft, var(--xh-fg-muted)));
93
+ color: var(--xh-timeline-indicator-fg, var(--xh-_tone-on, var(--xh-fg-on-brand)));
94
+ font-size: var(--xh-timeline-indicator-font-size, var(--xh-_timeline-caption-font-size));
95
+ line-height: var(--xh-leading-none);
96
+ }
97
+
98
+ [data-scope='timeline'][data-part='indicator'][hidden] {
99
+ display: none;
100
+ }
101
+
102
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'] [data-scope='timeline'][data-part='indicator'] {
103
+ grid-column: 1;
104
+ grid-row: 1;
105
+ /* 行高减去直径的一半:calc 里的除法要写成乘 0.5,除号在这儿是无效语法 */
106
+ margin-block-start: calc(
107
+ (var(--xh-_timeline-line-h) - var(--xh-timeline-indicator-size, var(--xh-_timeline-dot-size))) * 0.5
108
+ );
109
+ }
110
+
111
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'] [data-scope='timeline'][data-part='indicator'] {
112
+ grid-column: 1;
113
+ grid-row: 1;
114
+ }
115
+
116
+ /* —— 连线 —— */
117
+ [data-scope='timeline'][data-part='connector'] {
118
+ border-radius: var(--xh-radius-full);
119
+ background: var(--xh-timeline-connector-bg, var(--xh-border-default));
120
+ }
121
+
122
+ [data-scope='timeline'][data-part='connector'][hidden] {
123
+ display: none;
124
+ }
125
+
126
+ /* 竖排:从圆点下沿一直伸到条目底边,接上下一条的圆点。
127
+ min-block-size 兜住内容极短的条目,那时也还看得见一截线 */
128
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'] [data-scope='timeline'][data-part='connector'] {
129
+ grid-column: 1;
130
+ grid-row: 2;
131
+ justify-self: center;
132
+ inline-size: var(--xh-timeline-connector-thickness, var(--xh-stroke-thick));
133
+ min-block-size: var(--xh-timeline-connector-min-length, var(--xh-space-4));
134
+ margin-block-start: var(--xh-space-1);
135
+ }
136
+
137
+ /* 横排:线转成横的一条,与圆点同一行 */
138
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'] [data-scope='timeline'][data-part='connector'] {
139
+ grid-column: 2;
140
+ grid-row: 1;
141
+ align-self: center;
142
+ min-inline-size: var(--xh-timeline-connector-min-length, var(--xh-space-4));
143
+ block-size: var(--xh-timeline-connector-thickness, var(--xh-stroke-thick));
144
+ margin-inline-start: var(--xh-space-1);
145
+ }
146
+
147
+ /* 末条后面没有下一件事,那截线不该占地方;作者不写它也一样,写了就替他收掉 */
148
+ [data-scope='timeline'][data-part='item']:last-child [data-scope='timeline'][data-part='connector'] {
149
+ display: none;
150
+ }
151
+
152
+ /* —— 内容 —— */
153
+ [data-scope='timeline'][data-part='content'] {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: var(--xh-space-1);
157
+ min-inline-size: 0;
158
+ }
159
+
160
+ [data-scope='timeline'][data-part='content'][hidden] {
161
+ display: none;
162
+ }
163
+
164
+ /* 条目之间的留白由内容自己的边距撑出来,连线的那一列因此能一路连到下一条 */
165
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'] [data-scope='timeline'][data-part='content'] {
166
+ grid-column: 2;
167
+ grid-row: 1 / -1;
168
+ padding-block-end: var(--xh-timeline-item-gap, var(--xh-_timeline-item-gap));
169
+ }
170
+
171
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'] [data-scope='timeline'][data-part='content'] {
172
+ grid-column: 1 / -1;
173
+ grid-row: 2;
174
+ padding-block-start: var(--xh-space-2);
175
+ padding-inline-end: var(--xh-timeline-item-gap, var(--xh-_timeline-item-gap));
176
+ }
177
+
178
+ /* 末条的留白同样收掉:它后面已经没有要隔开的东西 */
179
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical']:last-child [data-scope='timeline'][data-part='content'] {
180
+ padding-block-end: 0;
181
+ }
182
+
183
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal']:last-child [data-scope='timeline'][data-part='content'] {
184
+ padding-inline-end: 0;
185
+ }
186
+
187
+ /* —— 侧别 —— */
188
+ /* 不写 placement 时内容落在结束侧,即上面那几条基础规则。
189
+ start 把线挪到结束侧、内容挪到起始侧,文字一并靠向线 */
190
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='start'] {
191
+ grid-template-columns: minmax(0, 1fr) auto;
192
+ }
193
+
194
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='start'] [data-scope='timeline'][data-part='indicator'],
195
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='start'] [data-scope='timeline'][data-part='connector'] {
196
+ grid-column: 2;
197
+ }
198
+
199
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='start'] [data-scope='timeline'][data-part='content'] {
200
+ grid-column: 1;
201
+ align-items: flex-end;
202
+ text-align: end;
203
+ }
204
+
205
+ /* alternate:线走中间,奇数条在结束侧、偶数条在起始侧 */
206
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='alternate'] {
207
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
208
+ }
209
+
210
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='alternate'] [data-scope='timeline'][data-part='indicator'],
211
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='alternate'] [data-scope='timeline'][data-part='connector'] {
212
+ grid-column: 2;
213
+ }
214
+
215
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='alternate'] [data-scope='timeline'][data-part='content'] {
216
+ grid-column: 3;
217
+ }
218
+
219
+ [data-scope='timeline'][data-part='item'][data-orientation='vertical'][data-placement='alternate']:nth-child(even) [data-scope='timeline'][data-part='content'] {
220
+ grid-column: 1;
221
+ align-items: flex-end;
222
+ text-align: end;
223
+ }
224
+
225
+ /* 横排的 start 是把内容挪到线的上方,内容贴着线的那一边对齐 */
226
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='start'] {
227
+ grid-template-rows: minmax(0, 1fr) auto;
228
+ }
229
+
230
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='start'] [data-scope='timeline'][data-part='indicator'],
231
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='start'] [data-scope='timeline'][data-part='connector'] {
232
+ grid-row: 2;
233
+ }
234
+
235
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='start'] [data-scope='timeline'][data-part='content'] {
236
+ grid-row: 1;
237
+ align-self: end;
238
+ padding-block-start: 0;
239
+ padding-block-end: var(--xh-space-2);
240
+ }
241
+
242
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='alternate'] {
243
+ grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr);
244
+ }
245
+
246
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='alternate'] [data-scope='timeline'][data-part='indicator'],
247
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='alternate'] [data-scope='timeline'][data-part='connector'] {
248
+ grid-row: 2;
249
+ }
250
+
251
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='alternate'] [data-scope='timeline'][data-part='content'] {
252
+ grid-row: 3;
253
+ align-self: start;
254
+ }
255
+
256
+ [data-scope='timeline'][data-part='item'][data-orientation='horizontal'][data-placement='alternate']:nth-child(even) [data-scope='timeline'][data-part='content'] {
257
+ grid-row: 1;
258
+ align-self: end;
259
+ padding-block-start: 0;
260
+ padding-block-end: var(--xh-space-2);
261
+ }
262
+
263
+ /* —— 文字 —— */
264
+ [data-scope='timeline'][data-part='title'] {
265
+ color: var(--xh-timeline-title-fg, var(--xh-fg-default));
266
+ font-size: var(--xh-timeline-title-font-size, var(--xh-_timeline-title-font-size));
267
+ font-weight: var(--xh-timeline-title-font-weight, var(--xh-text-label-weight));
268
+ line-height: var(--xh-leading-tight);
269
+ }
270
+
271
+ [data-scope='timeline'][data-part='description'] {
272
+ color: var(--xh-timeline-description-fg, var(--xh-fg-muted));
273
+ font-size: var(--xh-timeline-description-font-size, var(--xh-text-body-size));
274
+ line-height: var(--xh-leading-normal);
275
+ }
276
+
277
+ [data-scope='timeline'][data-part='time'] {
278
+ color: var(--xh-timeline-time-fg, var(--xh-fg-subtle));
279
+ font-size: var(--xh-timeline-time-font-size, var(--xh-_timeline-caption-font-size));
280
+ /* 等宽数字:时刻里的 1 与 7 在比例字体里宽度不同,不定住会让整列时间左右抖一格 */
281
+ font-variant-numeric: tabular-nums;
282
+ line-height: var(--xh-leading-normal);
283
+ }
284
+ }
package/css/toast.css ADDED
@@ -0,0 +1,195 @@
1
+ @layer xihan.motion {
2
+ /*
3
+ * 九个位共用同一条进出场,位移不跟着落位走:要按位置分方向,就得从 toaster 的
4
+ * data-placement 反向选到卡片上,等于把一个组件的动效写进另一个组件的选择器里,
5
+ * 两边任意一侧改名都会静默失效。竖向一小段位移在九个位上都说得通。
6
+ */
7
+ @keyframes xh-toast-in {
8
+ from {
9
+ opacity: 0;
10
+ transform: translateY(8px) scale(0.98);
11
+ }
12
+
13
+ to {
14
+ opacity: 1;
15
+ transform: none;
16
+ }
17
+ }
18
+
19
+ @keyframes xh-toast-out {
20
+ from {
21
+ opacity: 1;
22
+ transform: none;
23
+ }
24
+
25
+ to {
26
+ opacity: 0;
27
+ transform: translateY(4px) scale(0.96);
28
+ }
29
+ }
30
+
31
+ /* 通知是自己冒出来的,用户没按任何键:减弱动效的诉求在这里比别处更硬。
32
+ !important 在低层里反而更强(重要声明的层序是反的),足以压住组件层的动画。 */
33
+ @media (prefers-reduced-motion: reduce) {
34
+ [data-scope='toast'][data-part] {
35
+ animation-duration: 0.01ms !important;
36
+ }
37
+ }
38
+ }
39
+
40
+ @layer xihan.components {
41
+ [data-scope='toast'][data-part='root'] {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: var(--xh-toast-gap, var(--xh-space-1));
45
+ inline-size: var(--xh-toast-w, 20rem);
46
+ /* 摞是整面视口宽的定位面且带内边距,取 100% 就等于自动让出两侧留白,
47
+ 窄屏上不必再算一遍 viewport 减法 */
48
+ max-inline-size: 100%;
49
+ padding-block: var(--xh-toast-py, var(--xh-space-3));
50
+ padding-inline: var(--xh-toast-px, var(--xh-space-4));
51
+ border: var(--xh-stroke-thin) solid var(--xh-toast-border, var(--xh-border-default));
52
+ /* 语气色条做成起始侧的粗边,而不是伪元素:圆角与阴影都不用再对第二遍。
53
+ 两条必须排在 border 简写之后,否则被简写重置回细边与常规描边色。
54
+ 颜色由 connect 从 type 派生的 data-tone 经语气层给出,作者写 --xh-toast-accent
55
+ 就能把某一条整体改色;两者都没有时退回原来的 info 蓝 */
56
+ border-inline-start-width: var(--xh-toast-accent-width, 4px);
57
+ border-inline-start-color: var(--xh-toast-accent, var(--xh-_tone-soft, var(--xh-color-info-500)));
58
+ border-radius: var(--xh-toast-radius, var(--xh-shape-surface));
59
+ background: var(--xh-toast-bg, var(--xh-bg-surface-raised));
60
+ color: var(--xh-toast-fg, var(--xh-fg-default));
61
+ box-shadow: var(--xh-toast-shadow, var(--xh-elevation-3));
62
+ font-size: var(--xh-toast-font-size, var(--xh-font-size-sm));
63
+ line-height: var(--xh-toast-leading, var(--xh-leading-normal));
64
+ /* 摞的定位面整面铺开且不吃指针,卡片必须自己吃回来:
65
+ 悬停按住计时、按钮可点、焦点进出,全指着这一条 */
66
+ pointer-events: auto;
67
+ }
68
+
69
+ /* loading 说的是「事情还没完」,不是好消息也不是坏消息,走中性灰。
70
+ 它不是语气:连接层给它派生的 neutral 档是 500,而这里要的是 fg-muted(600) */
71
+ [data-scope='toast'][data-part='root'][data-type='loading'] {
72
+ border-inline-start-color: var(--xh-toast-accent, var(--xh-fg-muted));
73
+ }
74
+
75
+ /* 退场窗口走完连接层给的是 hidden 属性,而上面声明了 display——
76
+ 漏掉这条,已经"卸载"的通知会一直杵在屏幕上,且再也没人来收 */
77
+ [data-scope='toast'][data-part='root'][hidden] {
78
+ display: none;
79
+ }
80
+
81
+ /* 生命周期只有三段,unmounted 那段由上面的 [hidden] 收走,这里只管前两段 */
82
+ [data-scope='toast'][data-part='root'][data-state='visible'] {
83
+ animation: xh-toast-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
84
+ }
85
+
86
+ /* forwards 不能省:退场窗口(removeDelay 默认 200ms)比退场动画长,
87
+ 不定住末帧的话动画跑完会弹回全不透明,卡片在被收起前先闪一下 */
88
+ [data-scope='toast'][data-part='root'][data-state='dismissing'] {
89
+ animation: xh-toast-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
90
+ }
91
+
92
+ [data-scope='toast'][data-part='title'] {
93
+ /* 作者可能把它写成 h2/p,外边距得先归零,否则卡片内的行距对不上 */
94
+ margin: 0;
95
+ color: var(--xh-toast-title-fg, var(--xh-fg-default));
96
+ font-size: var(--xh-toast-title-font-size, var(--xh-font-size-md));
97
+ font-weight: var(--xh-toast-title-font-weight, var(--xh-font-weight-semibold));
98
+ line-height: var(--xh-toast-title-leading, var(--xh-leading-tight));
99
+ }
100
+
101
+ [data-scope='toast'][data-part='description'] {
102
+ margin: 0;
103
+ color: var(--xh-toast-description-fg, var(--xh-fg-muted));
104
+ font-size: var(--xh-toast-description-font-size, var(--xh-font-size-sm));
105
+ }
106
+
107
+ /* 没给文案的那一个仍然要留在 DOM 里(aria-labelledby / aria-describedby 指着它),
108
+ 但空盒子会照吃一份 gap,卡片看着就多出一截莫名的留白 */
109
+ [data-scope='toast'][data-part='title']:empty,
110
+ [data-scope='toast'][data-part='description']:empty {
111
+ display: none;
112
+ }
113
+
114
+ [data-scope='toast'][data-part='title'][hidden],
115
+ [data-scope='toast'][data-part='description'][hidden] {
116
+ display: none;
117
+ }
118
+
119
+ [data-scope='toast'][data-part='action-trigger'] {
120
+ /* 作为卡片直接子元素时不跟着拉满整行;被作者包进一行里时它只影响交叉轴,无副作用 */
121
+ align-self: flex-start;
122
+ display: inline-flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ block-size: var(--xh-toast-action-h, var(--xh-control-h-sm));
126
+ padding-inline: var(--xh-toast-action-px, var(--xh-control-px-sm));
127
+ border: var(--xh-stroke-thin) solid var(--xh-toast-action-border, var(--xh-border-default));
128
+ border-radius: var(--xh-toast-action-radius, var(--xh-shape-control));
129
+ background: var(--xh-toast-action-bg, var(--xh-bg-subtle));
130
+ color: var(--xh-toast-action-fg, var(--xh-fg-default));
131
+ /* 按钮不继承字族字号,不接回来的话卡片里会突然冒出一段系统 UI 字体 */
132
+ font: inherit;
133
+ font-weight: var(--xh-toast-action-font-weight, var(--xh-font-weight-medium));
134
+ line-height: var(--xh-leading-none);
135
+ cursor: pointer;
136
+ }
137
+
138
+ [data-scope='toast'][data-part='action-trigger'][hidden] {
139
+ display: none;
140
+ }
141
+
142
+ [data-scope='toast'][data-part='action-trigger']:hover:not(:disabled) {
143
+ background: var(--xh-toast-action-bg-hover, var(--xh-bg-subtle-hover));
144
+ }
145
+
146
+ [data-scope='toast'][data-part='action-trigger']:active:not(:disabled) {
147
+ background: var(--xh-toast-action-bg-active, var(--xh-bg-subtle-active));
148
+ }
149
+
150
+ [data-scope='toast'][data-part='action-trigger']:focus-visible {
151
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
152
+ outline-offset: var(--xh-ring-offset);
153
+ }
154
+
155
+ [data-scope='toast'][data-part='close-trigger'] {
156
+ align-self: flex-start;
157
+ /* 和操作按钮同处一行时把叉推到行尾;单独作为卡片子元素时则贴到行内末侧 */
158
+ margin-inline-start: auto;
159
+ display: inline-flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ block-size: var(--xh-toast-close-size, var(--xh-control-h-sm));
163
+ inline-size: var(--xh-toast-close-size, var(--xh-control-h-sm));
164
+ border: 0;
165
+ border-radius: var(--xh-toast-close-radius, var(--xh-shape-control));
166
+ background: transparent;
167
+ color: var(--xh-toast-close-fg, var(--xh-fg-muted));
168
+ font: inherit;
169
+ line-height: var(--xh-leading-none);
170
+ cursor: pointer;
171
+ }
172
+
173
+ /* closable=false 时连接层同时给了 hidden 与 disabled,这条是它真正收起的那一半:
174
+ 上面声明了 display,不补回来就会留下一个按不动的叉,比压根没有叉更让人困惑 */
175
+ [data-scope='toast'][data-part='close-trigger'][hidden] {
176
+ display: none;
177
+ }
178
+
179
+ [data-scope='toast'][data-part='close-trigger']:hover:not(:disabled) {
180
+ background: var(--xh-toast-close-bg-hover, var(--xh-bg-subtle));
181
+ color: var(--xh-toast-close-fg-hover, var(--xh-fg-default));
182
+ }
183
+
184
+ /* 单体控件用原生 disabled。常规路径下它与 hidden 同时出现看不见,
185
+ 但作者若在覆盖层把收起改了回来,至少还能看出这颗叉是按不动的 */
186
+ [data-scope='toast'][data-part='close-trigger']:disabled {
187
+ color: var(--xh-fg-disabled);
188
+ cursor: not-allowed;
189
+ }
190
+
191
+ [data-scope='toast'][data-part='close-trigger']:focus-visible {
192
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
193
+ outline-offset: var(--xh-ring-offset);
194
+ }
195
+ }
@@ -0,0 +1,69 @@
1
+ @layer xihan.components {
2
+ /* 地标容器不占布局:通知落在哪儿由九个 group 各自的 fixed 说了算,
3
+ root 只是给读屏一个可跳转的落点。它若留下一个块盒,写在页面中段的用法
4
+ (按钮和队列摆在一起)就会凭空多出一行空白。
5
+ 另一件必须提防的事:fixed 认的是视口,祖先上一旦出现
6
+ transform / filter / backdrop-filter / contain,包含块就被抢走,整摞通知立刻贴错位置——
7
+ 所以这里连 position 都不碰,也别往覆盖钩子里加这几个属性。 */
8
+ [data-scope='toaster'][data-part='root'] {
9
+ display: contents;
10
+ }
11
+
12
+ /* 只要声明过 display,就盖掉了 UA 的 [hidden]{display:none},收起态得自己补回来 */
13
+ [data-scope='toaster'][data-part='root'][hidden] {
14
+ display: none;
15
+ }
16
+
17
+ /*
18
+ * 一摞 = 一整面视口大小的定位面:贴边靠 padding,九个位靠 justify/align 表达。
19
+ * 不用四角 inset 逐个摆,是因为 column flex 的 align-items 走的是行内轴,
20
+ * RTL 下 start/end 自动跟着翻;写死 inset-inline-start 的话居中那三个位
21
+ * 还得再配一次 translate,方向一变就错。
22
+ * 条目次序按队列原样(旧的在前):贴底那三个位于是新的离屏幕边最近,符合直觉。
23
+ */
24
+ [data-scope='toaster'][data-part='group'] {
25
+ position: fixed;
26
+ inset-block: 0;
27
+ inset-inline: 0;
28
+ z-index: var(--xh-layer-toast);
29
+ display: flex;
30
+ flex-direction: column;
31
+ /* 基线按默认落位 bottom-end 写,下面只覆盖跟它不同的那几个位 */
32
+ justify-content: flex-end;
33
+ align-items: center;
34
+ padding-block: var(--xh-toaster-inset, var(--xh-space-6));
35
+ padding-inline: var(--xh-toaster-inset, var(--xh-space-6));
36
+ /* 摞内间距由连接层写进内联 style(两个适配器共用同一份),这里绝不重复写:
37
+ 写了也会被内联盖掉,只会让人误以为间距归样式层管 */
38
+ /* 整面铺开就等于罩住了整块视口。面本身不吃指针,只有卡片吃(见 toast.css),
39
+ 否则九个位一叠,底下的页面就再也点不动了 */
40
+ pointer-events: none;
41
+ }
42
+
43
+ [data-scope='toaster'][data-part='group'][hidden] {
44
+ display: none;
45
+ }
46
+
47
+ /* 空摞直接撤掉:九个位同时挂九张全屏定位面,白白多九个合成层 */
48
+ [data-scope='toaster'][data-part='group'][data-empty] {
49
+ display: none;
50
+ }
51
+
52
+ /* 块轴落位:^= 一次吃掉某一行的三个位(top / top-start / top-end) */
53
+ [data-scope='toaster'][data-part='group'][data-placement^='top'] {
54
+ justify-content: flex-start;
55
+ }
56
+
57
+ [data-scope='toaster'][data-part='group'][data-placement^='middle'] {
58
+ justify-content: center;
59
+ }
60
+
61
+ /* 行内轴落位:不带后缀的 top / middle / bottom 用基线的居中 */
62
+ [data-scope='toaster'][data-part='group'][data-placement$='-start'] {
63
+ align-items: flex-start;
64
+ }
65
+
66
+ [data-scope='toaster'][data-part='group'][data-placement$='-end'] {
67
+ align-items: flex-end;
68
+ }
69
+ }