@xihan-ui/styles 2.0.0 → 3.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 (156) hide show
  1. package/CHANGELOG.md +1328 -0
  2. package/README.md +6 -1
  3. package/component-tokens.d.ts +505 -111
  4. package/components.tokens.json +34750 -26291
  5. package/css/accordion.css +39 -13
  6. package/css/alert.css +61 -3
  7. package/css/anchor.css +49 -11
  8. package/css/approval.css +124 -84
  9. package/css/avatar-group.css +1 -1
  10. package/css/avatar.css +13 -5
  11. package/css/back-top.css +63 -44
  12. package/css/badge.css +63 -13
  13. package/css/bar-code.css +1 -1
  14. package/css/breadcrumb.css +63 -4
  15. package/css/button-group.css +5 -5
  16. package/css/button.css +69 -21
  17. package/css/calendar-picker.css +27 -8
  18. package/css/calendar-range-picker.css +48 -11
  19. package/css/card.css +116 -2
  20. package/css/carousel.css +153 -48
  21. package/css/cartesian-chart.css +830 -0
  22. package/css/cascader.css +184 -70
  23. package/css/checkbox-group.css +69 -99
  24. package/css/checkbox.css +21 -54
  25. package/css/citation.css +470 -0
  26. package/css/clipboard.css +23 -66
  27. package/css/code-view.css +103 -8
  28. package/css/collapsible.css +37 -11
  29. package/css/color-field.css +18 -0
  30. package/css/color-picker.css +57 -62
  31. package/css/color-slider.css +26 -19
  32. package/css/color-swatch-picker.css +8 -6
  33. package/css/combobox.css +82 -37
  34. package/css/command.css +48 -48
  35. package/css/context-menu.css +43 -28
  36. package/css/date-field.css +23 -0
  37. package/css/date-picker.css +57 -24
  38. package/css/date-range-picker.css +301 -14
  39. package/css/description.css +17 -1
  40. package/css/descriptions.css +43 -1
  41. package/css/dialog.css +99 -43
  42. package/css/diff-view.css +101 -16
  43. package/css/download-trigger.css +46 -80
  44. package/css/drawer.css +168 -133
  45. package/css/editable.css +19 -0
  46. package/css/empty-state.css +8 -7
  47. package/css/field-array.css +36 -0
  48. package/css/field.css +17 -19
  49. package/css/fieldset.css +1 -1
  50. package/css/file-upload.css +126 -136
  51. package/css/flex.css +324 -8
  52. package/css/float-button.css +168 -41
  53. package/css/floating-panel.css +59 -27
  54. package/css/form.css +26 -20
  55. package/css/funnel-chart.css +106 -0
  56. package/css/graph-chart.css +205 -0
  57. package/css/grid-list.css +235 -0
  58. package/css/grid.css +252 -1
  59. package/css/heatmap.css +119 -36
  60. package/css/hierarchy-chart.css +298 -0
  61. package/css/hover-card.css +33 -18
  62. package/css/icon.css +100 -0
  63. package/css/image-cropper.css +34 -8
  64. package/css/image-viewer.css +169 -26
  65. package/css/image.css +23 -4
  66. package/css/infinite-scroll.css +2 -2
  67. package/css/input-group.css +13 -15
  68. package/css/json-viewer.css +55 -7
  69. package/css/kbd.css +1 -1
  70. package/css/layout.css +71 -21
  71. package/css/liquid.css +106 -0
  72. package/css/list.css +2 -2
  73. package/css/listbox.css +72 -23
  74. package/css/loading-bar.css +27 -13
  75. package/css/log.css +131 -35
  76. package/css/markdown-stream.css +1 -1
  77. package/css/marquee.css +163 -7
  78. package/css/matrix-code.css +1 -1
  79. package/css/mention.css +35 -21
  80. package/css/menu.css +40 -28
  81. package/css/menubar.css +42 -28
  82. package/css/message-feed.css +174 -53
  83. package/css/navigation-menu.css +177 -18
  84. package/css/notification.css +431 -110
  85. package/css/number-field.css +22 -0
  86. package/css/page-header.css +4 -2
  87. package/css/pagination.css +129 -29
  88. package/css/password-input.css +40 -8
  89. package/css/pie-chart.css +247 -0
  90. package/css/popconfirm.css +50 -58
  91. package/css/popover.css +55 -21
  92. package/css/progress.css +308 -10
  93. package/css/prompt-input.css +37 -1
  94. package/css/question-flow.css +104 -64
  95. package/css/radar-chart.css +233 -0
  96. package/css/radio-group.css +372 -14
  97. package/css/rating.css +25 -27
  98. package/css/reasoning.css +62 -55
  99. package/css/reset.css +1 -1
  100. package/css/resizable.css +6 -1
  101. package/css/sankey-chart.css +174 -0
  102. package/css/scroll-area.css +32 -0
  103. package/css/scrollbar.css +1 -1
  104. package/css/select.css +76 -59
  105. package/css/side-nav.css +177 -30
  106. package/css/signature-pad.css +23 -8
  107. package/css/skeleton.css +13 -14
  108. package/css/slider.css +39 -17
  109. package/css/sortable.css +38 -8
  110. package/css/sparkline.css +164 -0
  111. package/css/spinner.css +50 -22
  112. package/css/splitter.css +8 -2
  113. package/css/statistic.css +10 -0
  114. package/css/steps.css +257 -38
  115. package/css/switch.css +30 -55
  116. package/css/table.css +168 -128
  117. package/css/tabs.css +243 -31
  118. package/css/tag-group.css +65 -37
  119. package/css/tag.css +55 -14
  120. package/css/tags-input.css +40 -3
  121. package/css/text-field.css +19 -0
  122. package/css/time-field.css +20 -1
  123. package/css/time-picker.css +44 -17
  124. package/css/time-range-picker.css +45 -18
  125. package/css/timeline.css +1 -1
  126. package/css/toggle-group.css +8 -8
  127. package/css/toggle.css +6 -5
  128. package/css/tool-call.css +64 -56
  129. package/css/toolbar.css +105 -7
  130. package/css/tooltip.css +18 -7
  131. package/css/tour.css +49 -21
  132. package/css/transfer.css +104 -81
  133. package/css/tree-select.css +235 -58
  134. package/css/tree.css +240 -147
  135. package/css/truncate.css +55 -12
  136. package/css/typography.css +93 -0
  137. package/css/virtualizer.css +57 -1
  138. package/css/watermark.css +23 -0
  139. package/family/action-control.css +14 -5
  140. package/family/chart.css +480 -0
  141. package/family/check-mark.css +94 -0
  142. package/family/choice-card.css +97 -0
  143. package/family/collection-item.css +70 -27
  144. package/family/field-chrome.css +1 -12
  145. package/family/loading-ring.css +95 -0
  146. package/family/material.css +72 -0
  147. package/family/menu-choice.css +56 -0
  148. package/family/motion.css +208 -11
  149. package/family/tag-list.css +59 -0
  150. package/index.css +23363 -14091
  151. package/index.unlayered.css +23198 -13922
  152. package/package.json +20 -8
  153. package/css/gradient-text.css +0 -91
  154. package/css/icon-wrapper.css +0 -81
  155. package/css/segmented.css +0 -280
  156. package/css/toast.css +0 -499
package/css/accordion.css CHANGED
@@ -17,7 +17,7 @@
17
17
  }
18
18
 
19
19
  [data-scope='accordion'][data-part='root'] {
20
- /* 作者塞进正文里的图标与皮肤画的兜底字形同一把尺:md 档(§6.5),标题栏上另按家族档取值 */
20
+ /* 作者塞进正文里的图标与皮肤画的兜底字形同一把尺:md 档,标题栏上另按家族档取值 */
21
21
  --xh-icon-size: var(--xh-accordion-icon-size, var(--xh-glyph-size-md));
22
22
 
23
23
  /* 尺寸档只换正文这两个私有槽,正文从 root 继承它们;标题栏的三档几何由家族 disclosure-trigger 档给 */
@@ -102,7 +102,7 @@
102
102
  border-inline-start: 0;
103
103
  }
104
104
 
105
- /* outline:一张静态内容面,--xh-border-default 描边 + surface 底 + 无影(§5.3 边界只由描边承担);
105
+ /* outline:一张静态内容面,--xh-border-default 描边 + surface 底 + 无影(边界只由描边承担);
106
106
  根的外边走 --xh-accordion-border,与条与条之间的分隔线槽 --xh-accordion-item-border 各管各的 */
107
107
  [data-scope='accordion'][data-part='root'][data-variant='outline'] {
108
108
  border: var(--xh-stroke-thin) solid var(--xh-accordion-border, var(--xh-border-default));
@@ -113,7 +113,7 @@
113
113
  }
114
114
 
115
115
  /* subtle:同一块连续表面,底换成淡底,描边让位给底色但留住 1px 边位(三档几何一致);
116
- 淡底承载面向标题栏下发 hover 200 → pressed 300(§7.2) */
116
+ 淡底承载面向标题栏下发 hover 200 → pressed 300 */
117
117
  [data-scope='accordion'][data-part='root'][data-variant='subtle'] {
118
118
  --xh-action-host-bg-hover: var(--xh-bg-subtle-hover);
119
119
  --xh-action-host-bg-pressed: var(--xh-bg-subtle-active);
@@ -142,7 +142,7 @@
142
142
  }
143
143
 
144
144
  /* 标题栏铺满一行,盒型、三档几何、悬停 / 按下 / 禁用面由 Action Control 的 disclosure-trigger 档给
145
- (ghost 形态:白底承载 hover 100 → pressed 200,淡底容器由根下发 host 槽;按下只换面不缩放,§9.2);
145
+ (ghost 形态:白底承载 hover 100 → pressed 200,淡底容器由根下发 host 槽;按下只换面不缩放);
146
146
  这里只把使用者槽映射到桥接槽:字色三态同值(家族的 hover / pressed 字色规则比皮肤更特定,不映射会被换回
147
147
  缺省字色),字形按标题栏自己的档位取 --xh-_action-profile-glyph-size;边归零贴住条目,几何与迁移前一致 */
148
148
  [data-scope='accordion'][data-part='trigger'] {
@@ -164,7 +164,7 @@
164
164
  }
165
165
 
166
166
  /* 展开态自带一个槽:与常态共用一个的话,作者一改常态字色,
167
- 展开那一档的语气高亮就跟着被抹平,再也分不开。悬停 / 按下的面与收起态同档(§7.3 open 与家族 hover 同档中性) */
167
+ 展开那一档的语气高亮就跟着被抹平,再也分不开。悬停 / 按下的面与收起态同档(open 与家族 hover 同档中性) */
168
168
  [data-scope='accordion'][data-part='trigger'][data-state='open'] {
169
169
  --xh-action-fg-rest: var(--xh-accordion-trigger-fg-open, var(--xh-_accordion-open-fg));
170
170
  --xh-action-fg-hover: var(--xh-accordion-trigger-fg-open, var(--xh-_accordion-open-fg));
@@ -184,8 +184,12 @@
184
184
  flex: none;
185
185
  color: var(--xh-accordion-indicator-fg, var(--xh-fg-muted));
186
186
  line-height: var(--xh-leading-none);
187
- /* 指示器与正文同档转向(§9.4):正文 enter 档展开,箭头也用同一段时长翻过去 */
188
- transition: rotate var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
187
+ }
188
+
189
+ /* 指示器与正文同档转向:收起时取 collapse 档,展开时(下面的打开态)取 expand 档。
190
+ 首帧就在的朝向直接呈现(data-instant),第一次开合起才转 */
191
+ [data-scope='accordion'][data-part='indicator']:not([data-instant]) {
192
+ transition: rotate var(--xh-motion-duration-collapse) var(--xh-motion-ease-enter-strong);
189
193
  }
190
194
 
191
195
  /* 没写内容就由皮肤画;作者往部件里塞了自己的图形,这条规则即不命中 */
@@ -204,6 +208,10 @@
204
208
  rotate: 180deg;
205
209
  }
206
210
 
211
+ [data-scope='accordion'][data-part='indicator'][data-state='open']:not([data-instant]) {
212
+ transition: rotate var(--xh-motion-duration-expand) var(--xh-motion-ease-enter-strong);
213
+ }
214
+
207
215
  /* 这里刻意没有 [data-part='content'][hidden] { display: none }:补了动画一帧都播不出来。
208
216
  收起由适配器在退场结束后写内联 display 落成,退场窗口内靠 connect 发的 inert 挡住焦点 */
209
217
  [data-scope='accordion'][data-part='content'] {
@@ -221,14 +229,32 @@
221
229
  line-height: var(--xh-leading-normal);
222
230
  }
223
231
 
224
- /* 展开那一档才提前备好行高这条通道:属性随 data-state 一起消失,收起后不长期占着 */
225
- [data-scope='accordion'][data-part='content'][data-state='open'] {
226
- will-change: grid-template-rows;
227
- animation: xh-disclosure-expand var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
232
+ /* 展开与收起只在开合时播:首帧就在的展开 / 收起直接呈现(data-instant) */
233
+ [data-scope='accordion'][data-part='content'][data-state='open']:not([data-instant]) {
234
+ animation: xh-disclosure-expand var(--xh-motion-duration-expand) var(--xh-motion-ease-enter-strong);
235
+ }
236
+
237
+ [data-scope='accordion'][data-part='content'][data-state='closed']:not([data-instant]) {
238
+ animation: xh-disclosure-collapse var(--xh-motion-duration-collapse) var(--xh-motion-ease-exit) forwards;
228
239
  }
229
240
 
230
- [data-scope='accordion'][data-part='content'][data-state='closed'] {
231
- animation: xh-disclosure-collapse var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
241
+ @media (forced-colors: active) {
242
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
243
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
244
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
245
+ 禁用取 GrayText */
246
+ [data-scope='accordion'][data-part='indicator']:empty::before {
247
+ forced-color-adjust: none;
248
+ background-color: ButtonText;
249
+ }
250
+
251
+ [data-scope='accordion'][data-part='trigger']:not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]) [data-scope='accordion'][data-part='indicator']:empty::before {
252
+ background-color: HighlightText;
253
+ }
254
+
255
+ [data-scope='accordion'][data-part='trigger'][data-disabled] [data-scope='accordion'][data-part='indicator']:empty::before {
256
+ background-color: GrayText;
257
+ }
232
258
  }
233
259
  }
234
260
 
package/css/alert.css CHANGED
@@ -7,6 +7,23 @@
7
7
 
8
8
  /* Alert 独立皮肤同时传递引入关闭钮的动作控件配方;full bundle 递归去重。 */
9
9
  @import '../family/action-control.css';
10
+ /* 淡出的关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
11
+ @import '../family/motion.css';
12
+
13
+ @layer xihan.motion {
14
+ /* 关闭时收起占位:从连接层量下的整块高度收到 0,内缩与描边一起收掉 */
15
+ @keyframes xh-alert-collapse {
16
+ from {
17
+ block-size: var(--xh-_alert-exit-block-size);
18
+ }
19
+
20
+ to {
21
+ block-size: 0;
22
+ padding-block: 0;
23
+ border-block-width: 0;
24
+ }
25
+ }
26
+ }
10
27
 
11
28
  @layer xihan.components {
12
29
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -14,15 +31,16 @@
14
31
  --xh-alert-skin: 1;
15
32
  }
16
33
 
17
- /* 提示本体是一层中性 surface:边界由 --xh-border-default 描边承担、无影(§5.3 静态反馈面),
34
+ /* 提示本体是一层中性 surface:边界由 --xh-border-default 描边承担、无影(静态反馈面),
18
35
  语气只落到图标与标题,不会用整块彩底抢走正文层级。 */
19
36
  [data-scope='alert'][data-part='root'] {
20
- /* 作者塞进指示符槽的图标与皮肤画的兜底字形同一把尺:Feedback 指示符统一 md 档(§6.5) */
37
+ /* 作者塞进指示符槽的图标与皮肤画的兜底字形同一把尺:Feedback 指示符统一 md 档 */
21
38
  --xh-icon-size: var(--xh-alert-icon-size, var(--xh-glyph-size-md));
22
39
 
23
40
  display: flex;
24
41
  align-items: flex-start;
25
- gap: var(--xh-alert-gap, var(--xh-space-4));
42
+ /* 指示符盒自带 --xh-space-1 内衬:与间距合起来,字形到正文 12px,与 Notification 同一个距离 */
43
+ gap: var(--xh-alert-gap, var(--xh-space-2));
26
44
  padding-block: var(--xh-alert-py, var(--xh-surface-py-sm));
27
45
  padding-inline: var(--xh-alert-px, var(--xh-surface-px-sm));
28
46
  border: var(--xh-stroke-thin) solid var(--xh-alert-border, var(--xh-border-default));
@@ -34,6 +52,27 @@
34
52
  line-height: var(--xh-alert-leading, var(--xh-leading-normal));
35
53
  }
36
54
 
55
+ /* 横幅:页面顶部的通栏,贴着页面或容器的边铺满整行。贴边的三条边是页面自己的边,画出来只会压在边上;
56
+ 边界只留朝向页面内容的块尾一道,与页面内容同一层、同一种描边。贴边铺满的栏不取圆角:
57
+ 圆了会在两个角露出底下的页面。面与语气规则与页内提示相同,横幅不换底色 */
58
+ [data-scope='alert'][data-part='root'][data-banner] {
59
+ inline-size: 100%;
60
+ border-block-start-width: 0;
61
+ border-inline-width: 0;
62
+ border-radius: 0;
63
+ }
64
+
65
+ /* 关闭的退场:先淡出,再把占位收起——下面的内容跟着一路平移上来,而不是整块跳上来。
66
+ 收占位接在淡出之后(延迟一个 exit),播完连接层才写 hidden。收起态常挂 hidden,这条只在退场途中命中;
67
+ 减弱动效下收占位瞬时完成,淡出照常 */
68
+ [data-scope='alert'][data-part='root'][data-state='closed'] {
69
+ box-sizing: border-box;
70
+ overflow: hidden;
71
+ animation:
72
+ xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards,
73
+ xh-alert-collapse var(--xh-motion-duration-collapse) var(--xh-motion-ease-exit) var(--xh-motion-duration-exit) forwards;
74
+ }
75
+
37
76
  /* 收起态连接层给的是 hidden 属性,而上面声明了 display——
38
77
  漏掉这条,关掉的提示还会一直摊在页面上 */
39
78
  [data-scope='alert'][data-part='root'][hidden] {
@@ -186,4 +225,23 @@
186
225
  [data-scope='alert'][data-part='action'][hidden] {
187
226
  display: none;
188
227
  }
228
+
229
+ @media (forced-colors: active) {
230
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
231
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
232
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
233
+ 禁用取 GrayText */
234
+ [data-scope='alert'][data-part='close-trigger']:empty::before {
235
+ forced-color-adjust: none;
236
+ background-color: ButtonText;
237
+ }
238
+
239
+ [data-scope='alert'][data-part='close-trigger']:not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
240
+ background-color: HighlightText;
241
+ }
242
+
243
+ [data-scope='alert'][data-part='close-trigger'][data-disabled]:empty::before {
244
+ background-color: GrayText;
245
+ }
246
+ }
189
247
  }
package/css/anchor.css CHANGED
@@ -24,7 +24,7 @@
24
24
  --xh-_anchor-link-px: var(--xh-control-px-md);
25
25
  /* 当前那一节的指示条与文字随语气走。没写 data-tone 时这两个槽都没被声明,退回品牌色。
26
26
  指示条是装饰性的色条,取柔和档;文字要压在普通背景上读,取语气的文字档——
27
- 导航当前页(§7.3)缺省是 --xh-fg-brand-strong,比指示条深一档。
27
+ 导航当前页缺省是 --xh-fg-brand-strong,比指示条深一档。
28
28
  聚焦环不在其列,它恒是 --xh-ring-focus */
29
29
  --xh-_anchor-accent: var(--xh-_tone-soft, var(--xh-fg-brand));
30
30
  --xh-_anchor-accent-text: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
@@ -86,7 +86,7 @@
86
86
  }
87
87
 
88
88
  /* 链接的几何、字号与面全经家族桥接槽:静息 muted / regular,悬停走白底承载 hover 100 + default 字,
89
- 按下 pressed 200 只换面(§7.2 / §9.2);当前那一节(§7.3 导航当前页)brand-strong 字色(随语气的
89
+ 按下 pressed 200 只换面;当前那一节(导航当前页)brand-strong 字色(随语气的
90
90
  --xh-_anchor-accent-text)+ medium 字重都跟着变,2px 指示线放了 indicator 部件由部件滑动、没放则由
91
91
  下面的静态线规则画在当前链接自己身上。本条 (0,2,0) 压过家族基础块 (0,1,0),不能再写 color / background。
92
92
  position: relative 是静态线的定位参照 */
@@ -127,21 +127,32 @@
127
127
  overflow: hidden;
128
128
  }
129
129
 
130
- /* 主轴上的位置与长度由连接层写成内联样式(它量得到,样式表量不到);
131
- 这里只管交叉轴的贴边与粗细,以及长什么样。它是可选部件——不渲染它时,
130
+ /* 当前链接的四个几何量由机器量好、连接层写成私有槽 --xh-_anchor-indicator-x / -y / -w / -h
131
+ (它量得到,样式表量不到);这里按排布取主轴那两支,落点交给 translate(只走合成),
132
+ 另管交叉轴的贴边与粗细,以及长什么样。它是可选部件——不渲染它时,
132
133
  当前那一节的链接自画一条同规格的静态线(见下) */
133
134
  [data-scope='anchor'][data-part='indicator'] {
135
+ /* 还没量到时的落点,量到即被机器写的私有槽盖过 */
136
+ --xh-_anchor-indicator-x: 0;
137
+ --xh-_anchor-indicator-y: 0;
138
+ --xh-_anchor-indicator-w: 0;
139
+ --xh-_anchor-indicator-h: 0;
140
+ --xh-_anchor-indicator-stretch: 0;
141
+
134
142
  position: absolute;
135
143
  display: block;
136
144
  /* 指示条也是列表项,宿主给 li 的外边距会把绝对定位的它推离条目 */
137
145
  margin: 0;
146
+ inset-block-start: 0;
138
147
  inset-inline-start: calc(var(--xh-anchor-indicator-thickness, var(--xh-stroke-thick)) * -1);
139
148
  inline-size: var(--xh-anchor-indicator-thickness, var(--xh-stroke-thick));
149
+ block-size: var(--xh-_anchor-indicator-h);
140
150
  border-radius: var(--xh-anchor-indicator-radius, var(--xh-shape-pill));
141
151
  background: var(--xh-anchor-indicator-color, var(--xh-_anchor-accent));
152
+ translate: 0 var(--xh-_anchor-indicator-y);
142
153
  transition:
143
- inset-block-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
144
- block-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
154
+ translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
155
+ block-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
145
156
  }
146
157
 
147
158
  /* display 会盖掉 hidden 属性的 UA 规则,无激活项时显式让位 */
@@ -151,16 +162,43 @@
151
162
 
152
163
  /* 横排:指示条改躺在底边的轨道上,交叉轴换成块轴 */
153
164
  [data-scope='anchor'][data-part='indicator'][data-orientation='horizontal'] {
154
- inset-inline-start: auto;
165
+ inset-block-start: auto;
166
+ inset-inline-start: 0;
155
167
  inset-block-end: calc(var(--xh-anchor-indicator-thickness, var(--xh-stroke-thick)) * -1);
156
- inline-size: auto;
168
+ inline-size: var(--xh-_anchor-indicator-w);
157
169
  block-size: var(--xh-anchor-indicator-thickness, var(--xh-stroke-thick));
170
+ translate: var(--xh-_anchor-indicator-x) 0;
158
171
  transition:
159
- inset-inline-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
160
- inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
172
+ translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
173
+ inline-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
174
+ }
175
+
176
+ /* translate 只认物理方向:RTL 下行首在右边,起始缘量出的距离往左推 */
177
+ [data-scope='anchor'][data-part='indicator'][data-orientation='horizontal']:dir(rtl) {
178
+ translate: calc(-1 * var(--xh-_anchor-indicator-x)) 0;
179
+ }
180
+
181
+ /* 首次落位与同一项的重量(窗口缩放、换上正式字体)直接到位,只有换项才滑:连接层在这一落点投影
182
+ data-instant。写在排布那几条之后,同一特异性按先后压过它们单写的过渡 */
183
+ [data-scope='anchor'][data-part='indicator'][data-instant] {
184
+ transition: none;
185
+ }
186
+
187
+ /* 液态档:两沿由弹簧逐帧推着走(连接层每一帧写好盒子),几何量不再补过渡;
188
+ 被拉长时在另一个方向上压扁(竖排缺省压行向,横排压块向),表达体积守恒,拉长到目标的两倍时压到 --xh-motion-scale-squash */
189
+ :where([data-material='liquid']) [data-scope='anchor'][data-part='indicator'] {
190
+ --xh-_anchor-indicator-squash: max(var(--xh-motion-scale-squash), calc(1 - var(--xh-_anchor-indicator-stretch) * (1 - var(--xh-motion-scale-squash))));
191
+
192
+ scale: var(--xh-_anchor-indicator-squash) 1;
193
+ transition: none;
194
+ }
195
+
196
+ :where([data-material='liquid']) [data-scope='anchor'][data-part='indicator'][data-orientation='horizontal'] {
197
+ scale: 1 var(--xh-_anchor-indicator-squash);
198
+ transition: none;
161
199
  }
162
200
 
163
- /* 目录里没放 indicator 部件时,当前那一节的链接自画一条静态指示线(§7.3 导航当前页):
201
+ /* 目录里没放 indicator 部件时,当前那一节的链接自画一条静态指示线(导航当前页):
164
202
  与 Tabs line 档同一取法,作者不必为了这条线多放一个部件。
165
203
  线画在当前链接的 ::after 上,以链接盒为定位参照(上面已给 position: relative),
166
204
  厚度 / 颜色 / 圆角与 indicator 部件读同一组槽与缺省——作者改一处,部件与静态线一起改。
package/css/approval.css CHANGED
@@ -5,44 +5,11 @@
5
5
 
6
6
  /* 定义 approval 样式。 */
7
7
 
8
+ /* 共享关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
9
+ @import '../family/motion.css';
8
10
  /* 独立皮肤同时传递引入授权行与两颗钮的动作控件配方,full bundle 递归去重。 */
9
11
  @import '../family/action-control.css';
10
-
11
- @layer xihan.motion {
12
- /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
13
- 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
14
-
15
- /* 幅度走令牌,减弱动效时由令牌层归零;位移用独立属性而非 transform 简写 */
16
- @keyframes xh-approval-in {
17
- from {
18
- opacity: 0;
19
- translate: 0 var(--xh-motion-distance-md);
20
- }
21
-
22
- to {
23
- opacity: 1;
24
- translate: 0 0;
25
- }
26
- }
27
-
28
- @keyframes xh-approval-result-in {
29
- from {
30
- opacity: 0;
31
- scale: var(--xh-motion-scale-enter);
32
- }
33
-
34
- to {
35
- opacity: 1;
36
- scale: 1;
37
- }
38
- }
39
-
40
- @keyframes xh-approval-rotate {
41
- to {
42
- rotate: 360deg;
43
- }
44
- }
45
- }
12
+ @import '../family/loading-ring.css';
46
13
 
47
14
  @layer xihan.components {
48
15
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -50,10 +17,10 @@
50
17
  --xh-approval-skin: 1;
51
18
  }
52
19
 
53
- /* 一块要人做决定的静态内容面:--xh-border-default 描边 + surface 底 + 无影(§5.3 边界只由描边承担),
20
+ /* 一块要人做决定的静态内容面:--xh-border-default 描边 + surface 底 + 无影(边界只由描边承担),
54
21
  作者打了语气时描边染成语气色作强调(下面那条),判定落定后描边退回中性 */
55
22
  [data-scope='approval'][data-part='root'] {
56
- /* 作者塞进标题 / 说明 / 结果条里的图标按档取尺(§6.5),缺省 md;勾选记号另按盒比例量 */
23
+ /* 作者塞进标题 / 说明 / 结果条里的图标按档取尺,缺省 md;勾选记号另按盒比例量 */
57
24
  --xh-_approval-icon-size: var(--xh-glyph-size-md);
58
25
  --xh-icon-size: var(--xh-approval-icon-size, var(--xh-_approval-icon-size));
59
26
 
@@ -66,6 +33,8 @@
66
33
  --xh-_approval-item-font-size: var(--xh-control-font-md);
67
34
  --xh-_approval-note-font-size: var(--xh-control-font-md);
68
35
 
36
+ /* 定位基准:待决的呼吸点钉在右上角 */
37
+ position: relative;
69
38
  display: flex;
70
39
  flex-direction: column;
71
40
  gap: var(--xh-approval-gap, var(--xh-_approval-gap));
@@ -74,8 +43,13 @@
74
43
  border-radius: var(--xh-approval-radius, var(--xh-shape-surface));
75
44
  background: var(--xh-approval-bg, var(--xh-bg-surface));
76
45
  box-shadow: var(--xh-approval-shadow, none);
77
- transition: border-color var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
78
- animation: xh-approval-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
46
+ /* 判定落定时描边随语气换色:属状态换色,与别处悬停、选中、校验的换色同一档 */
47
+ transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
48
+ }
49
+
50
+ /* 卡片出现时滑入;落在首帧就在的列表条目里(历史消息)时直接呈现,不跟着整屏一起入场 */
51
+ [data-scope='approval'][data-part='root']:not([data-scope][data-part='item'][data-instant] *) {
52
+ animation: xh-item-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
79
53
  }
80
54
 
81
55
  /* 上面声明的 display 会盖掉 UA 的 [hidden]{display:none},这里补回 */
@@ -83,12 +57,54 @@
83
57
  display: none;
84
58
  }
85
59
 
60
+ /* 待决的呼吸点:钉在右上角(RTL 下左上),不占作者排的版面;颜色随语气,没写语气时取警示色。
61
+ 呼吸只表达「正在进行、需要被察觉」:明暗与缩放的起伏持续到判定落定,外扩光环同周期播 3 轮后停下;
62
+ 判过了连接层就收起它,由结果那一格接着表达 */
63
+ [data-scope='approval'][data-part='pending-indicator'] {
64
+ position: absolute;
65
+ inset-block-start: var(--xh-approval-p, var(--xh-_approval-p));
66
+ inset-inline-end: var(--xh-approval-p, var(--xh-_approval-p));
67
+ inline-size: var(--xh-approval-pending-indicator-size, var(--xh-space-2));
68
+ block-size: var(--xh-approval-pending-indicator-size, var(--xh-space-2));
69
+ border-radius: var(--xh-approval-pending-indicator-radius, var(--xh-shape-circle));
70
+ background: var(--xh-approval-pending-indicator-color, var(--xh-_tone, var(--xh-fg-warning)));
71
+ animation: xh-breathe var(--xh-motion-loop-breathe) var(--xh-motion-ease-breathe) infinite;
72
+ }
73
+
74
+ [data-scope='approval'][data-part='pending-indicator']::after {
75
+ content: '';
76
+ position: absolute;
77
+ inset: 0;
78
+ border-radius: inherit;
79
+ background: inherit;
80
+ opacity: 0;
81
+ pointer-events: none;
82
+ animation: xh-breathe-halo var(--xh-motion-loop-breathe) var(--xh-motion-ease-breathe) 3;
83
+ }
84
+
85
+ /* 减弱动效:两者都停,圆点停在满不透明度,光环不出现 */
86
+ @media (prefers-reduced-motion: reduce) {
87
+ [data-scope='approval'][data-part='pending-indicator'],
88
+ [data-scope='approval'][data-part='pending-indicator']::after {
89
+ animation: none;
90
+ }
91
+ }
92
+
93
+ :where([data-motion='reduce']) [data-scope='approval'][data-part='pending-indicator'],
94
+ :where([data-motion='reduce']) [data-scope='approval'][data-part='pending-indicator']::after {
95
+ animation: none;
96
+ }
97
+
98
+ [data-scope='approval'][data-part='pending-indicator'][hidden] {
99
+ display: none;
100
+ }
101
+
86
102
  /* 语气只染描边:作者定的强调,待决时把这块面从正文里提出来 */
87
103
  [data-scope='approval'][data-part='root'][data-tone] {
88
104
  border-color: var(--xh-approval-border, var(--xh-_tone, var(--xh-border-default)));
89
105
  }
90
106
 
91
- /* 判过了就不再抢注意力:语气色边退回中性描边(根面外边不取 subtle,§5.3)。
107
+ /* 判过了就不再抢注意力:语气色边退回中性描边(根面外边不取 subtle)。
92
108
  排在语气那条之后:同等特指度,落定要盖得过语气 */
93
109
  [data-scope='approval'][data-part='root']:not([data-state='pending']) {
94
110
  border-color: var(--xh-approval-border-settled, var(--xh-border-default));
@@ -98,7 +114,7 @@
98
114
  /* 缺省档即 outline,写在上面那条基础规则里。
99
115
  两档排在落定那条之后:它们把描边整个撤掉,排在前面会被落定那条按同等特指度盖回来 */
100
116
  /* 底色分区:描边让位给底色,闸门嵌在别人的面里时不再多一圈线;
101
- 淡底承载面向授权行与拒绝钮下发 hover 200 → pressed 300(§7.2) */
117
+ 淡底承载面向授权行与拒绝钮下发 hover 200 → pressed 300 */
102
118
  [data-scope='approval'][data-part='root'][data-variant='subtle'] {
103
119
  --xh-action-host-bg-hover: var(--xh-bg-subtle-hover);
104
120
  --xh-action-host-bg-pressed: var(--xh-bg-subtle-active);
@@ -114,7 +130,7 @@
114
130
  box-shadow: none;
115
131
  }
116
132
 
117
- /* 标题走 Surface 标题档 14/600(§6.4) */
133
+ /* 标题走 Surface 标题档 14/600 */
118
134
  [data-scope='approval'][data-part='title'] {
119
135
  display: block;
120
136
  margin: 0;
@@ -164,9 +180,9 @@
164
180
  }
165
181
 
166
182
  /* 一项授权:整行可点、可聚焦,勾选记号在左。盒型、悬停 / 按下 / 禁用面由 Action Control 的 row 档给
167
- (ghost 形态:白底承载 hover 100 → pressed 200,按下只换面不缩放,§9.2;禁用守卫认 data-disabled),
183
+ (ghost 形态:白底承载 hover 100 → pressed 200,按下只换面不缩放;禁用守卫认 data-disabled),
168
184
  这里只把使用者槽映射到桥接槽,内衬与间距沿用本组件的档位。
169
- 行高不吃家族的单行档:row 档允许换行、高度随内容,一条会折行的授权文案走正文行高(§6.4) */
185
+ 行高不吃家族的单行档:row 档允许换行、高度随内容,一条会折行的授权文案走正文行高 */
170
186
  [data-scope='approval'][data-part='item'] {
171
187
  --xh-action-gap: var(--xh-approval-item-gap, var(--xh-space-1_5));
172
188
  --xh-action-padding-inline: var(--xh-approval-item-px, var(--xh-space-2));
@@ -198,7 +214,7 @@
198
214
  inline-size: var(--xh-approval-indicator-size, var(--xh-control-indicator-size));
199
215
  block-size: var(--xh-approval-indicator-size, var(--xh-control-indicator-size));
200
216
  transition:
201
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
217
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
202
218
  border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
203
219
  color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
204
220
  }
@@ -272,6 +288,13 @@
272
288
  color: var(--xh-fg-disabled);
273
289
  }
274
290
 
291
+ /* 要求写理由而空着就按了拒绝:描边换成无效色;焦点也已带到这里,焦点环压在描边外面,一并换色 */
292
+ [data-scope='approval'][data-part='note'][data-invalid] {
293
+ --xh-_ring-color: var(--xh-approval-note-ring-invalid, var(--xh-ring-invalid));
294
+
295
+ border-color: var(--xh-approval-note-border-invalid, var(--xh-border-invalid));
296
+ }
297
+
275
298
  /* 剩余时间:等宽数字,逐秒跳字时不左右晃 */
276
299
  [data-scope='approval'][data-part='timer'] {
277
300
  display: block;
@@ -289,25 +312,31 @@
289
312
  display: inline-flex;
290
313
  align-self: flex-start;
291
314
  align-items: center;
292
- gap: var(--xh-approval-result-gap, var(--xh-space-1_5));
293
- padding-inline: var(--xh-approval-result-px, var(--xh-space-2));
294
- padding-block: var(--xh-approval-result-py, var(--xh-space-1));
315
+ gap: var(--xh-approval-result-gap, var(--xh-space-1));
316
+ padding-inline: var(--xh-approval-result-px, var(--xh-space-1_5));
317
+ padding-block: var(--xh-approval-result-py, var(--xh-space-0_5));
318
+ border: var(--xh-stroke-thin) solid transparent;
295
319
  border-radius: var(--xh-approval-result-radius, var(--xh-shape-pill));
296
- background: var(--xh-approval-result-bg, color-mix(in oklab, var(--xh-fg-success) 12%, var(--xh-bg-surface)));
297
- color: var(--xh-approval-result-fg, var(--xh-fg-success));
320
+ background: var(--xh-approval-result-bg, var(--xh-_tone-subtle));
321
+ color: var(--xh-approval-result-fg, var(--xh-_tone-fg));
298
322
  font-size: var(--xh-approval-result-font-size, var(--xh-text-caption-size));
299
- font-weight: var(--xh-approval-result-font-weight, var(--xh-text-label-weight));
300
- animation: xh-approval-result-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
323
+ font-weight: var(--xh-approval-result-font-weight, var(--xh-font-weight-medium));
324
+ line-height: var(--xh-control-indicator-md);
325
+ }
326
+
327
+ /* 落定结果弹出;落在首帧就在的列表条目里(历史消息)时直接呈现,不跟着整屏一起入场 */
328
+ [data-scope='approval'][data-part='result']:not([data-scope][data-part='item'][data-instant] *) {
329
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
301
330
  }
302
331
 
303
332
  [data-scope='approval'][data-part='result'][hidden] {
304
333
  display: none;
305
334
  }
306
335
 
307
- /* 超时按拒绝收口,两者同档:视觉上不该看着像第三种结果 */
336
+ /* 超时按拒绝收口,两者同档(连接层同投 danger 语气):视觉上不该看着像第三种结果 */
308
337
  [data-scope='approval'][data-part='result']:is([data-state='denied'], [data-state='expired']) {
309
- background: var(--xh-approval-result-bg-denied, color-mix(in oklab, var(--xh-fg-danger) 12%, var(--xh-bg-surface)));
310
- color: var(--xh-approval-result-fg-denied, var(--xh-fg-danger));
338
+ background: var(--xh-approval-result-bg-denied, var(--xh-_tone-subtle));
339
+ color: var(--xh-approval-result-fg-denied, var(--xh-_tone-fg));
311
340
  }
312
341
 
313
342
  /* 动作行:两颗按钮靠右排,间距归库管。
@@ -327,7 +356,7 @@
327
356
 
328
357
  /* 两颗按钮是离散动作控件:盒几何、悬停 / 按下与 0.97 按压、焦点环、禁用与在途面全由 Action Control 的
329
358
  text 档给,组件只映射既有使用者槽。批准走 solid 档:判定闸门里的主要动作,与 Button 缺省同为品牌实心;
330
- 拒绝走 outline 档:非 Button 的触发器缺省中性描边(§7.2.2)。
359
+ 拒绝走 outline 档:非 Button 的触发器缺省中性描边。
331
360
  三档守卫由连接层投影:判定在途是 data-loading + aria-disabled(保住可聚焦,家族给在途面:底色不置灰、
332
361
  指针 progress),必选项没勾满与落定是 data-disabled(家族给禁用面:置灰底、置灰字、not-allowed)。
333
362
  两颗共用的几何与字号写在一起 */
@@ -397,35 +426,17 @@
397
426
  圆环挂在这一行上而不是某一颗钮上:连接层只拿到「有一条判定在途」,
398
427
  拿不到是哪一颗按下的,画在两颗钮上会有一颗在说假话 */
399
428
 
400
- [data-scope='approval'][data-part='root'][data-loading] [data-scope='approval'][data-part='footer']::before {
401
- content: '';
402
- flex: none;
403
-
404
- /* 圆环那一枚跟着两颗钮的字号走,不跟页面的:download-trigger 与 clipboard 的圆环
405
- 1em 量的都是它所在那颗钮,这里的宿主是整行,不锁字号就会比按钮上的大一圈 */
406
- font-size: var(--xh-approval-action-font-size, var(--xh-text-label-size));
407
- inline-size: 1em;
408
- block-size: 1em;
409
- border: var(--xh-stroke-thin) solid transparent;
410
- border-block-start-color: currentColor;
411
- border-radius: var(--xh-shape-circle);
412
- animation: xh-approval-rotate var(--xh-approval-loading-duration, var(--xh-spin-duration)) var(--xh-motion-ease-loop) infinite;
413
- }
414
-
415
- /* 减少动效:圆环停下并整圈换成虚线,静止的形状仍然读得出"还没好"。
416
- 两条规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
417
- @media (prefers-reduced-motion: reduce) {
418
- [data-scope='approval'][data-part='root'][data-loading] [data-scope='approval'][data-part='footer']::before {
419
- border-style: dotted;
420
- border-color: currentColor;
421
- animation: none;
422
- }
429
+ /* 环由加载环配方画在这一行的 ::before 上(连接层投影 data-xh-loading-ring 与 data-loading),
430
+ 随在途淡入淡出、转与停;直径取所在尺寸档的图标档,转速认使用者槽 */
431
+ [data-scope='approval'][data-part='footer'] {
432
+ --xh-_loading-ring-duration: var(--xh-approval-loading-duration, var(--xh-motion-loop-spin));
423
433
  }
424
434
 
425
- :where([data-motion='reduce']) [data-scope='approval'][data-part='root'][data-loading] [data-scope='approval'][data-part='footer']::before {
426
- border-style: dotted;
427
- border-color: currentColor;
428
- animation: none;
435
+ [data-scope='approval'][data-part='footer']::before {
436
+ content: '';
437
+ flex: none;
438
+ inline-size: var(--xh-_approval-icon-size);
439
+ block-size: var(--xh-_approval-icon-size);
429
440
  }
430
441
 
431
442
  /* 尺寸档改内衬、间距、按钮高度、行内字号与图标档 */
@@ -458,4 +469,33 @@
458
469
  --xh-_approval-item-font-size: var(--xh-control-font-lg);
459
470
  --xh-_approval-note-font-size: var(--xh-control-font-lg);
460
471
  }
472
+
473
+ /* 强制色:底色会被系统背景吃掉,呼吸点改用系统前景色 */
474
+ @media (forced-colors: active) {
475
+ [data-scope='approval'][data-part='pending-indicator'] {
476
+ background: CanvasText;
477
+ }
478
+ }
479
+
480
+ @media print {
481
+ /* 打印:待决的呼吸点是一瞬间的状态,纸上没有「等着人来判」 */
482
+ [data-scope='approval'][data-part='pending-indicator'] {
483
+ display: none;
484
+ }
485
+ }
486
+
487
+ @media (forced-colors: active) {
488
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
489
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
490
+ 落在勾选盒自己的实心面上(这一档里是 Canvas),整行悬停不换面,取 CanvasText;
491
+ 禁用取 GrayText */
492
+ [data-scope='approval'][data-part='item-indicator']:not([data-state='unchecked']):empty::before {
493
+ forced-color-adjust: none;
494
+ background-color: CanvasText;
495
+ }
496
+
497
+ [data-scope='approval'][data-part='item'][data-disabled] [data-scope='approval'][data-part='item-indicator']:not([data-state='unchecked']):empty::before {
498
+ background-color: GrayText;
499
+ }
500
+ }
461
501
  }
@@ -85,7 +85,7 @@
85
85
  inline-size: var(--xh-_avatar-group-size);
86
86
  block-size: var(--xh-_avatar-group-size);
87
87
  border-radius: var(--xh-avatar-group-radius, var(--xh-shape-circle));
88
- background: var(--xh-avatar-group-overflow-item-bg, var(--xh-bg-muted));
88
+ background: var(--xh-avatar-group-overflow-item-bg, var(--xh-bg-muted-opaque));
89
89
  color: var(--xh-avatar-group-overflow-item-fg, var(--xh-fg-muted));
90
90
  font-size: var(--xh-_avatar-group-font-size);
91
91
  font-weight: var(--xh-avatar-group-font-weight, var(--xh-font-weight-medium));