@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
@@ -0,0 +1,63 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='masonry'] {
4
+ --xh-masonry-skin: 1;
5
+ }
6
+
7
+ [data-scope='masonry'][data-part='root'] {
8
+ display: flex;
9
+ /* 各列按自己的内容收高,不被最高的那列拉平——列不等高正是瀑布流的样子 */
10
+ align-items: flex-start;
11
+ gap: var(--xh-_masonry-gap);
12
+
13
+ /* 列间距与列内的项间距共用这一份取值,声明在根上、由列继承下去;
14
+ 不写 gap 档位就不留间距。使用者槽位排在档位之前,写死一个值即盖过所有档 */
15
+ --xh-_masonry-gap: var(--xh-masonry-gap, var(--xh-space-0));
16
+ }
17
+
18
+ /* 声明了 display 就盖掉了 UA 的 [hidden] 规则,这里还回去 */
19
+ [data-scope='masonry'][data-part='root'][hidden] {
20
+ display: none;
21
+ }
22
+
23
+ [data-scope='masonry'][data-part='column'] {
24
+ display: flex;
25
+ flex-direction: column;
26
+ /* 各列等宽;下限写 0 而不是 auto,一段长内容才不会把自己那列撑宽 */
27
+ flex: 1 1 0;
28
+ min-inline-size: 0;
29
+ gap: var(--xh-_masonry-gap);
30
+ }
31
+
32
+ /* 当前档位用不上的那几列由适配器收起;这条兜住作者自己写 hidden 的情形 */
33
+ [data-scope='masonry'][data-part='column'][hidden] {
34
+ display: none;
35
+ }
36
+
37
+ /* 项不发话,高度由内容定;只兜住一件事——不被列的弹性压缩,压缩了量到的高度就不是它真实的高度 */
38
+ [data-scope='masonry'][data-part='item'] {
39
+ flex: none;
40
+ }
41
+
42
+ /* —— 间距档位 —— */
43
+ /* 每一档指向一个间距令牌,列之间与列内项之间吃同一份间距 */
44
+ [data-scope='masonry'][data-part='root'][data-gap='xs'] {
45
+ --xh-_masonry-gap: var(--xh-masonry-gap, var(--xh-layout-gap-xs));
46
+ }
47
+
48
+ [data-scope='masonry'][data-part='root'][data-gap='sm'] {
49
+ --xh-_masonry-gap: var(--xh-masonry-gap, var(--xh-layout-gap-sm));
50
+ }
51
+
52
+ [data-scope='masonry'][data-part='root'][data-gap='md'] {
53
+ --xh-_masonry-gap: var(--xh-masonry-gap, var(--xh-layout-gap-md));
54
+ }
55
+
56
+ [data-scope='masonry'][data-part='root'][data-gap='lg'] {
57
+ --xh-_masonry-gap: var(--xh-masonry-gap, var(--xh-layout-gap-lg));
58
+ }
59
+
60
+ [data-scope='masonry'][data-part='root'][data-gap='xl'] {
61
+ --xh-_masonry-gap: var(--xh-masonry-gap, var(--xh-layout-gap-xl));
62
+ }
63
+ }
package/css/mention.css CHANGED
@@ -1,13 +1,26 @@
1
1
  @layer xihan.motion {
2
2
  /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
3
  动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
- @keyframes xh-fade-in {
4
+ @keyframes xh-pop-in {
5
5
  from {
6
6
  opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
7
8
  }
8
9
 
9
10
  to {
10
11
  opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+ @keyframes xh-pop-out {
16
+ from {
17
+ opacity: 1;
18
+ scale: 1;
19
+ }
20
+
21
+ to {
22
+ opacity: 0;
23
+ scale: var(--xh-motion-scale-enter);
11
24
  }
12
25
  }
13
26
  }
@@ -18,13 +31,36 @@
18
31
  --xh-mention-skin: 1;
19
32
  }
20
33
 
21
- [data-scope='mention'][data-part='root'] {
22
- /* 尺寸档只换这四个私有槽,输入框与候选都从 root 继承它们,换档不必给每个部件各写一条选择器。
23
- 浮层在 positioner 里、也是 root 的后代,一并继承 */
34
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
35
+ [data-scope='mention'][data-part='positioner'] {
36
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,内容层继承它限高;
37
+ 引擎没算(尚未落位 / 无引擎)时不额外限高 */
38
+ --xh-_mention-available-h: 100vh;
39
+
40
+ /* 候选列表的自绘条浮在内容之上:轨道不上底色,实色轨道会在列表右缘糊一条竖灰带 */
41
+ --xh-scrollbar-track-bg: transparent;
42
+
43
+ position: fixed;
44
+ z-index: var(--xh-mention-layer, var(--xh-layer-popover));
45
+ /* 空壳不吃指针,交互只发生在 content 上 */
46
+ pointer-events: none;
47
+ }
48
+
49
+ /* 输入框被滚出可视区:整个浮层随之收起 */
50
+ [data-scope='mention'][data-part='positioner'][data-hidden] {
51
+ visibility: hidden;
52
+ }
53
+
54
+ /* 私有槽同时声明在根与 positioner 上:浮层被搬到 portal 落点,不是根的后代,继承不过去 */
55
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner']) {
56
+ /* 尺寸档只换这五个私有槽,输入框与候选都从这两处继承它们,换档不必给每个部件各写一条选择器 */
57
+ --xh-_mention-h: var(--xh-control-h-md);
24
58
  --xh-_mention-px: var(--xh-control-px-md);
59
+ --xh-_mention-item-py: var(--xh-list-option-py-md);
60
+ --xh-_mention-item-px: var(--xh-control-px-md);
25
61
  --xh-_mention-py: var(--xh-field-py);
26
62
  --xh-_mention-gap: var(--xh-control-gap-md);
27
- --xh-_mention-font-size: var(--xh-text-body-size);
63
+ --xh-_mention-font-size: var(--xh-control-font-md);
28
64
 
29
65
  /* 形态档只换这三个槽:缺省即 outline(描边 + 画布底) */
30
66
  --xh-_mention-input-bg: var(--xh-bg-canvas);
@@ -33,7 +69,9 @@
33
69
 
34
70
  /* 聚焦环恒用固定环色,不随语气走 */
35
71
  --xh-_mention-ring: var(--xh-ring-focus);
72
+ }
36
73
 
74
+ [data-scope='mention'][data-part='root'] {
37
75
  display: block;
38
76
  }
39
77
 
@@ -44,37 +82,43 @@
44
82
 
45
83
  /* —— 尺寸 —— */
46
84
  /* 缺省档写在上面的基础规则里 */
47
- [data-scope='mention'][data-part='root'][data-size='sm'] {
85
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-size='sm'] {
86
+ --xh-_mention-h: var(--xh-control-h-sm);
48
87
  --xh-_mention-px: var(--xh-control-px-sm);
88
+ --xh-_mention-item-py: var(--xh-list-option-py-sm);
89
+ --xh-_mention-item-px: var(--xh-control-px-sm);
49
90
  --xh-_mention-py: var(--xh-space-1);
50
91
  --xh-_mention-gap: var(--xh-control-gap-sm);
51
- --xh-_mention-font-size: var(--xh-font-size-sm);
92
+ --xh-_mention-font-size: var(--xh-control-font-sm);
52
93
  }
53
94
 
54
- [data-scope='mention'][data-part='root'][data-size='lg'] {
95
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-size='lg'] {
96
+ --xh-_mention-h: var(--xh-control-h-lg);
55
97
  --xh-_mention-px: var(--xh-control-px-lg);
98
+ --xh-_mention-item-py: var(--xh-list-option-py-lg);
99
+ --xh-_mention-item-px: var(--xh-control-px-lg);
56
100
  --xh-_mention-py: var(--xh-space-3);
57
101
  --xh-_mention-gap: var(--xh-control-gap-lg);
58
- --xh-_mention-font-size: var(--xh-font-size-lg);
102
+ --xh-_mention-font-size: var(--xh-control-font-lg);
59
103
  }
60
104
 
61
105
  /* —— 形态 —— */
62
106
  /* 输入类不做 solid:实心底压着可编辑的正文,读起来像块不可点的色板。
63
107
  形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
64
- [data-scope='mention'][data-part='root'][data-variant='outline'] {
108
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='outline'] {
65
109
  --xh-_mention-input-bg: var(--xh-bg-canvas);
66
110
  --xh-_mention-input-border: var(--xh-border-control);
67
111
  --xh-_mention-input-border-hover: var(--xh-border-control-hover);
68
112
  }
69
113
 
70
114
  /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
71
- [data-scope='mention'][data-part='root'][data-variant='subtle'] {
115
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='subtle'] {
72
116
  --xh-_mention-input-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
73
117
  --xh-_mention-input-border: transparent;
74
118
  --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
75
119
  }
76
120
 
77
- [data-scope='mention'][data-part='root'][data-variant='ghost'] {
121
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='ghost'] {
78
122
  --xh-_mention-input-bg: transparent;
79
123
  --xh-_mention-input-border: transparent;
80
124
  --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
@@ -85,6 +129,9 @@
85
129
  [data-scope='mention'][data-part='input'] {
86
130
  display: block;
87
131
  inline-size: 100%;
132
+ min-inline-size: var(--xh-mention-input-min-w, var(--xh-control-min-w));
133
+ /* 缺省宿主是 textarea:高度按行数撑,控件档只兜住底线 */
134
+ min-block-size: var(--xh-mention-input-h, var(--xh-_mention-h));
88
135
  padding-block: var(--xh-mention-input-py, var(--xh-_mention-py));
89
136
  padding-inline: var(--xh-mention-input-px, var(--xh-_mention-px));
90
137
  border: var(--xh-stroke-thin) solid var(--xh-mention-input-border, var(--xh-_mention-input-border));
@@ -100,6 +147,13 @@
100
147
  transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
101
148
  }
102
149
 
150
+ /* 单行宿主定高到控件档,纵向内距归零,与并排的其他控件等高。
151
+ 单行与多行在 DOM 上只差一个标签名,没有别的标记可选,所以按标签名分支 */
152
+ input[data-scope='mention'][data-part='input'] {
153
+ block-size: var(--xh-mention-input-h, var(--xh-_mention-h));
154
+ padding-block: 0;
155
+ }
156
+
103
157
  [data-scope='mention'][data-part='input'][hidden] {
104
158
  display: none;
105
159
  }
@@ -108,16 +162,28 @@
108
162
  color: var(--xh-mention-placeholder-fg, var(--xh-fg-subtle));
109
163
  }
110
164
 
111
- [data-scope='mention'][data-part='input']:hover:not(:disabled) {
165
+ [data-scope='mention'][data-part='input']:hover:not(:disabled, [data-invalid]) {
112
166
  border-color: var(--xh-mention-input-border-hover, var(--xh-_mention-input-border-hover));
113
167
  }
114
168
 
115
169
  [data-scope='mention'][data-part='input']:focus-visible {
116
- border-color: var(--xh-mention-input-border-focus, var(--xh-_mention-ring));
170
+ border-color: var(--xh-mention-input-border-focus, var(--xh-_tone, var(--xh-border-control-focus)));
117
171
  outline: var(--xh-ring-width) solid var(--xh-_mention-ring);
118
172
  outline-offset: var(--xh-ring-offset);
119
173
  }
120
174
 
175
+ [data-scope='mention'][data-part='input'][data-invalid] {
176
+ border-color: var(--xh-mention-input-border-invalid, var(--xh-border-invalid));
177
+
178
+ /* 校验失败时环也跟着变红:两圈等宽相邻却一红一蓝,读的人不知道哪个才是当前状态 */
179
+ --xh-_mention-ring: var(--xh-ring-invalid);
180
+ }
181
+
182
+ /* 只读仍可聚焦、可复制,所以不置灰,只把底色压下去表示"改不动" */
183
+ [data-scope='mention'][data-part='input'][readonly] {
184
+ background: var(--xh-mention-input-bg-readonly, var(--xh-bg-subtle));
185
+ }
186
+
121
187
  /* 输入框是单体表单控件,禁用走原生 disabled(候选那边一律 aria-disabled) */
122
188
  [data-scope='mention'][data-part='input']:disabled {
123
189
  background: var(--xh-mention-input-bg-disabled, var(--xh-bg-subtle));
@@ -127,40 +193,29 @@
127
193
  cursor: not-allowed;
128
194
  }
129
195
 
130
- /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
131
- [data-scope='mention'][data-part='positioner'] {
132
- position: fixed;
133
- z-index: var(--xh-layer-popover);
134
- /* 空壳不吃指针,交互只发生在 content 上 */
135
- pointer-events: none;
136
- }
137
-
138
- /* 输入框被滚出可视区:整个浮层随之收起 */
139
- [data-scope='mention'][data-part='positioner'][data-hidden] {
140
- visibility: hidden;
141
- }
142
-
143
196
  [data-scope='mention'][data-part='content'] {
144
197
  display: flex;
145
198
  flex-direction: column;
199
+ gap: var(--xh-mention-content-gap, var(--xh-list-option-gap));
146
200
  min-inline-size: var(--xh-mention-content-min-w, var(--xh-overlay-min-w));
147
201
  max-inline-size: var(--xh-mention-content-max-w, var(--xh-overlay-max-w));
148
- max-block-size: var(--xh-mention-content-max-h, var(--xh-overlay-max-h));
202
+ max-block-size: min(var(--xh-mention-content-max-h, var(--xh-overlay-max-h)), var(--xh-_mention-available-h));
149
203
  padding-block: var(--xh-mention-content-py, var(--xh-space-1));
150
204
  padding-inline: var(--xh-mention-content-px, var(--xh-space-1));
151
205
  border: var(--xh-stroke-thin) solid var(--xh-mention-content-border, var(--xh-border-default));
152
206
  border-radius: var(--xh-mention-content-radius, var(--xh-shape-surface));
153
207
  background: var(--xh-mention-content-bg, var(--xh-bg-surface));
154
208
  color: var(--xh-mention-content-fg, var(--xh-fg-default));
155
- box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-2));
209
+ box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-floating));
156
210
  overflow-y: auto;
157
211
  pointer-events: auto;
158
212
  }
159
213
 
160
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
161
- [data-scope='mention'][data-part='content'][hidden] {
162
- display: none;
163
- }
214
+ /* 这里刻意没有 [data-part='content'][hidden] { display: none },与 dialog 一致:
215
+ 收起态连接层会给 contenthidden,而上面那条 display 正是要盖掉 UA 的
216
+ [hidden]{display:none}——退场动画得先能播。presence 读的是 content 的 animationName,
217
+ 真正的收起由宿主在退场结束后落成内联 display:none(Vue 那边直接不渲染)。
218
+ 其余声明了 display 的角色节点仍补 [hidden],那几处没有退场动画。 */
164
219
 
165
220
  /* 宿主把候选全筛没了:浮层还开着,但这一副框壳没有内容可框,整个让位。
166
221
  组件不替宿主判空——「没有匹配项」要不要说话、说什么,是宿主的事 */
@@ -175,15 +230,21 @@
175
230
  }
176
231
 
177
232
  [data-scope='mention'][data-part='content'][data-state='open'] {
178
- animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
233
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
234
+ }
235
+
236
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
237
+ 免得动画结束到真收起之间闪一下原样 */
238
+ [data-scope='mention'][data-part='content'][data-state='closed'] {
239
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
179
240
  }
180
241
 
181
242
  [data-scope='mention'][data-part='item'] {
182
243
  display: flex;
183
244
  align-items: center;
184
245
  gap: var(--xh-mention-item-gap, var(--xh-_mention-gap));
185
- padding-block: var(--xh-mention-item-py, var(--xh-space-1_5));
186
- padding-inline: var(--xh-mention-item-px, var(--xh-control-px-md));
246
+ padding-block: var(--xh-mention-item-py, var(--xh-_mention-item-py));
247
+ padding-inline: var(--xh-mention-item-px, var(--xh-_mention-item-px));
187
248
  border-radius: var(--xh-mention-item-radius, var(--xh-shape-control));
188
249
  color: var(--xh-mention-item-fg, var(--xh-fg-default));
189
250
  font-size: var(--xh-mention-item-font-size, var(--xh-_mention-font-size));
@@ -205,6 +266,9 @@
205
266
  两套底色各画各的会出现"鼠标停在 A 上、回车却插进了 B" */
206
267
  [data-scope='mention'][data-part='item'][data-highlighted]:not([data-disabled]) {
207
268
  background: var(--xh-mention-item-bg-hover, var(--xh-bg-subtle));
269
+ /* 焦点恒在输入框,高亮条目自带内收环;列表是滚动容器,环画在外沿会被裁掉 */
270
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
271
+ outline-offset: calc(-1 * var(--xh-ring-width));
208
272
  }
209
273
 
210
274
  /* 禁用走 aria-disabled 而非原生 disabled(原生 disabled 不派 click,禁用守卫就走不到),
package/css/menu.css CHANGED
@@ -1,13 +1,26 @@
1
1
  @layer xihan.motion {
2
2
  /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
3
  动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
- @keyframes xh-fade-in {
4
+ @keyframes xh-pop-in {
5
5
  from {
6
6
  opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
7
8
  }
8
9
 
9
10
  to {
10
11
  opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+ @keyframes xh-pop-out {
16
+ from {
17
+ opacity: 1;
18
+ scale: 1;
19
+ }
20
+
21
+ to {
22
+ opacity: 0;
23
+ scale: var(--xh-motion-scale-enter);
11
24
  }
12
25
  }
13
26
  }
@@ -20,8 +33,15 @@
20
33
 
21
34
  /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
22
35
  [data-scope='menu'][data-part='positioner'] {
36
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,content 继承它限高;
37
+ 引擎没算(尚未落位 / 无引擎)时不额外限高 */
38
+ --xh-_menu-available-h: 100vh;
39
+
40
+ /* 条目列表的自绘条浮在内容之上:轨道不上底色,实色轨道会在菜单右缘糊一条竖灰带 */
41
+ --xh-scrollbar-track-bg: transparent;
42
+
23
43
  position: fixed;
24
- z-index: var(--xh-layer-popover);
44
+ z-index: var(--xh-menu-layer, var(--xh-layer-popover));
25
45
  /* 空壳不吃指针,交互只发生在 content 上 */
26
46
  pointer-events: none;
27
47
  }
@@ -31,13 +51,29 @@
31
51
  visibility: hidden;
32
52
  }
33
53
 
54
+ /* 触发器的长相归作者,皮肤只表达展开态。私有槽声明在它自己身上:
55
+ 菜单没有 root 部件,触发器上头没有别的 menu 节点可以挂 */
56
+ [data-scope='menu'][data-part='trigger'] {
57
+ /* 展开着的那张菜单的触发器走品牌淡底强档,随语气换色相;没写 data-tone 时退回品牌淡底 */
58
+ --xh-_menu-active-bg: var(--xh-_tone-subtle, var(--xh-bg-brand-subtle));
59
+ }
60
+
61
+ /* 展开着的那一张是打开路径,走品牌淡底强档:哪一张开着必须一眼看得出来。
62
+ 不加粗:加粗会推挤相邻内容 */
63
+ [data-scope='menu'][data-part='trigger']:not([data-disabled])[data-state='open'] {
64
+ background: var(--xh-menu-trigger-bg-active, var(--xh-_menu-active-bg));
65
+ }
66
+
34
67
  [data-scope='menu'][data-part='content'] {
68
+ /* 作者塞进条目里的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
69
+ --xh-icon-size: var(--xh-menu-icon-size, var(--xh-glyph-size-text));
70
+
35
71
  /* 尺寸档只换这四个私有槽,条目从 content 继承它们,换档不必给每个部件各写一条选择器。
36
72
  菜单没有 root 部件(浮层树的最外层就是 content),三轴属性都落在这里 */
37
- --xh-_menu-item-py: var(--xh-space-1_5);
73
+ --xh-_menu-item-py: var(--xh-list-option-py-md);
38
74
  --xh-_menu-item-px: var(--xh-control-px-md);
39
75
  --xh-_menu-item-gap: var(--xh-control-gap-md);
40
- --xh-_menu-font-size: var(--xh-text-body-size);
76
+ --xh-_menu-font-size: var(--xh-control-font-md);
41
77
 
42
78
  /* 打开子菜单的触发条目走品牌淡底强档,随语气换色相;没写 data-tone 时退回品牌淡底。
43
79
  悬停与键盘焦点恒中性灰轻档、不接语气槽——两档同色相的话打开路径就认不出来了 */
@@ -45,22 +81,29 @@
45
81
 
46
82
  display: flex;
47
83
  flex-direction: column;
84
+ gap: var(--xh-menu-content-gap, var(--xh-list-option-gap));
48
85
  min-inline-size: var(--xh-menu-min-w, var(--xh-overlay-menu-min-w));
49
86
  max-inline-size: var(--xh-menu-max-w, var(--xh-overlay-max-w));
87
+ /* 两道上限取更小的那道:自己的高度上限,以及落定那一侧真正剩下的空间。
88
+ 列表族的 16rem 对命令菜单偏矮,这里另给一档 */
89
+ max-block-size: min(var(--xh-menu-max-h, var(--xh-overlay-menu-max-h)), var(--xh-_menu-available-h));
50
90
  padding-block: var(--xh-menu-py, var(--xh-space-1));
51
91
  padding-inline: var(--xh-menu-px, var(--xh-space-1));
52
92
  border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
53
93
  border-radius: var(--xh-menu-radius, var(--xh-shape-surface));
54
94
  background: var(--xh-menu-bg, var(--xh-bg-surface));
55
95
  color: var(--xh-menu-fg, var(--xh-fg-default));
56
- box-shadow: var(--xh-menu-shadow, var(--xh-elevation-2));
96
+ box-shadow: var(--xh-menu-shadow, var(--xh-elevation-floating));
97
+ /* 条目超过上限就在菜单内部滚。条目的焦点环是内收的,滚动容器裁在内边距缘上,环切不着 */
98
+ overflow-y: auto;
57
99
  pointer-events: auto;
58
100
  }
59
101
 
60
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
61
- [data-scope='menu'][data-part='content'][hidden] {
62
- display: none;
63
- }
102
+ /* 这里刻意没有 [data-part='content'][hidden] { display: none },与 dialog 一致:
103
+ 收起态连接层会给 contenthidden,而上面那条 display 正是要盖掉 UA 的
104
+ [hidden]{display:none}——退场动画得先能播。presence 读的是 content 的 animationName,
105
+ 真正的收起由宿主在退场结束后落成内联 display:none(Vue 那边直接不渲染)。
106
+ 其余声明了 display 的角色节点仍补 [hidden],那几处没有退场动画。 */
64
107
 
65
108
  /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
66
109
  [data-scope='menu'][data-part='content']:focus-visible {
@@ -68,24 +111,30 @@
68
111
  }
69
112
 
70
113
  [data-scope='menu'][data-part='content'][data-state='open'] {
71
- animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
114
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
115
+ }
116
+
117
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
118
+ 免得动画结束到真收起之间闪一下原样 */
119
+ [data-scope='menu'][data-part='content'][data-state='closed'] {
120
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
72
121
  }
73
122
 
74
123
  /* —— 尺寸 —— */
75
124
  /* 缺省档写在 content 的基础规则里。条目高度由 padding 撑出来:
76
125
  纵向走 space 档、行内内缩走 control-px 档(随密度轴),换档各挪一级 */
77
126
  [data-scope='menu'][data-part='content'][data-size='sm'] {
78
- --xh-_menu-item-py: var(--xh-space-1);
127
+ --xh-_menu-item-py: var(--xh-list-option-py-sm);
79
128
  --xh-_menu-item-px: var(--xh-control-px-sm);
80
129
  --xh-_menu-item-gap: var(--xh-control-gap-sm);
81
- --xh-_menu-font-size: var(--xh-font-size-sm);
130
+ --xh-_menu-font-size: var(--xh-control-font-sm);
82
131
  }
83
132
 
84
133
  [data-scope='menu'][data-part='content'][data-size='lg'] {
85
- --xh-_menu-item-py: var(--xh-space-2);
134
+ --xh-_menu-item-py: var(--xh-list-option-py-lg);
86
135
  --xh-_menu-item-px: var(--xh-control-px-lg);
87
136
  --xh-_menu-item-gap: var(--xh-control-gap-lg);
88
- --xh-_menu-font-size: var(--xh-font-size-lg);
137
+ --xh-_menu-font-size: var(--xh-control-font-lg);
89
138
  }
90
139
 
91
140
  [data-scope='menu'][data-part='item'] {
@@ -106,8 +155,8 @@
106
155
  color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
107
156
  }
108
157
 
109
- /* 悬停与键盘焦点共用中性灰轻档:条目本身承载焦点,不另画描边 */
110
- [data-scope='menu'][data-part='item']:not([data-disabled]):is(:hover, :focus) {
158
+ /* 悬停与键盘锚点共用中性灰轻档:锚点只认连接层打的 data-highlighted,不读 :focus */
159
+ [data-scope='menu'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
111
160
  background: var(--xh-menu-item-bg-hover, var(--xh-bg-subtle));
112
161
  }
113
162
 
@@ -118,7 +167,13 @@
118
167
  font-weight: var(--xh-menu-item-active-font-weight, var(--xh-font-weight-semibold));
119
168
  }
120
169
 
121
- [data-scope='menu'][data-part='item']:focus {
170
+ /* 键盘落焦的条目画内收环;指针触发的聚焦不画 */
171
+ [data-scope='menu'][data-part='item']:focus-visible {
172
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
173
+ outline-offset: calc(-1 * var(--xh-ring-width));
174
+ }
175
+
176
+ [data-scope='menu'][data-part='item']:focus:not(:focus-visible) {
122
177
  outline: none;
123
178
  }
124
179
 
@@ -127,52 +182,74 @@
127
182
  cursor: not-allowed;
128
183
  }
129
184
 
185
+ /* 带子菜单的条目行尾画右向小箭头:不点开也看得出还有下一级。
186
+ 位置靠 margin 顶到行尾,图形取兜底字形令牌 */
187
+ [data-scope='menu'][data-part='item'][aria-haspopup='menu']::after {
188
+ content: '';
189
+ flex: none;
190
+ margin-inline-start: auto;
191
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
192
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
193
+ background-color: currentColor;
194
+ -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
195
+ mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
196
+ }
197
+
198
+ /* rtl 下子菜单开在左侧,箭头跟着指向行进方向 */
199
+ [dir='rtl'] [data-scope='menu'][data-part='item'][aria-haspopup='menu']::after {
200
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-left);
201
+ mask-image: var(--xh-glyph-mark-chevron-left);
202
+ }
203
+
204
+ [data-scope='menu'][data-part='group'] {
205
+ display: flex;
206
+ flex-direction: column;
207
+ gap: var(--xh-menu-group-gap, var(--xh-list-option-gap));
208
+ }
209
+
210
+ [data-scope='menu'][data-part='group'][hidden] {
211
+ display: none;
212
+ }
213
+
214
+ /* 分组标题不是条目、进不了导航,画成小号次要文字,
215
+ 内缩与条目对齐(同一个 padding-inline),标题与条目文字才在一条竖线上 */
216
+ [data-scope='menu'][data-part='group-label'] {
217
+ display: block;
218
+ padding-block: var(--xh-menu-group-label-py, var(--xh-space-1));
219
+ padding-inline: var(--xh-menu-group-label-px, var(--xh-_menu-item-px));
220
+ color: var(--xh-menu-group-label-fg, var(--xh-fg-subtle));
221
+ font-size: var(--xh-menu-group-label-font-size, var(--xh-text-caption-size));
222
+ font-weight: var(--xh-menu-group-label-font-weight, var(--xh-font-weight-medium));
223
+ line-height: var(--xh-leading-none);
224
+ user-select: none;
225
+ }
226
+
227
+ [data-scope='menu'][data-part='group-label'][hidden] {
228
+ display: none;
229
+ }
230
+
130
231
  [data-scope='menu'][data-part='separator'] {
131
232
  block-size: var(--xh-menu-separator-thickness, var(--xh-stroke-thin));
132
- margin-block: var(--xh-menu-separator-my, var(--xh-space-1));
233
+ margin-block: var(--xh-menu-separator-my, var(--xh-space-0_5));
133
234
  background: var(--xh-menu-separator-color, var(--xh-border-default));
134
235
  }
135
236
 
136
- /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
237
+ /* 箭头的底色与描边色,几何在 overlay-arrow.css。
137
238
  交叉轴落点由定位引擎经内联自定义属性写进来,没给时退回居中 */
138
239
  [data-scope='menu'][data-part='arrow'] {
139
- --xh-_menu-arrow-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
140
240
  --xh-_menu-arrow-x: 50%;
141
241
  --xh-_menu-arrow-y: 50%;
242
+ --xh-_overlay-arrow-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
243
+ --xh-_overlay-arrow-x: var(--xh-_menu-arrow-x);
244
+ --xh-_overlay-arrow-y: var(--xh-_menu-arrow-y);
142
245
 
143
- position: absolute;
144
- inline-size: var(--xh-_menu-arrow-size);
145
- block-size: var(--xh-_menu-arrow-size);
146
246
  background: var(--xh-menu-bg, var(--xh-bg-surface));
147
247
  border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
148
- rotate: 45deg;
149
- }
150
-
151
- [data-scope='menu'][data-part='arrow'][data-placement^='bottom'] {
152
- inset-block-start: calc(var(--xh-_menu-arrow-size) / -2);
153
- inset-inline-start: calc(var(--xh-_menu-arrow-x) - var(--xh-_menu-arrow-size) / 2);
154
- border-block-end: 0;
155
- border-inline-end: 0;
156
248
  }
157
249
 
158
- [data-scope='menu'][data-part='arrow'][data-placement^='top'] {
159
- inset-block-end: calc(var(--xh-_menu-arrow-size) / -2);
160
- inset-inline-start: calc(var(--xh-_menu-arrow-x) - var(--xh-_menu-arrow-size) / 2);
161
- border-block-start: 0;
162
- border-inline-start: 0;
163
- }
164
-
165
- [data-scope='menu'][data-part='arrow'][data-placement^='right'] {
166
- inset-inline-start: calc(var(--xh-_menu-arrow-size) / -2);
167
- inset-block-start: calc(var(--xh-_menu-arrow-y) - var(--xh-_menu-arrow-size) / 2);
168
- border-block-start: 0;
169
- border-inline-end: 0;
170
- }
171
-
172
- [data-scope='menu'][data-part='arrow'][data-placement^='left'] {
173
- inset-inline-end: calc(var(--xh-_menu-arrow-size) / -2);
174
- inset-block-start: calc(var(--xh-_menu-arrow-y) - var(--xh-_menu-arrow-size) / 2);
175
- border-block-end: 0;
176
- border-inline-start: 0;
250
+ /* 声明过任何 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去。
251
+ 写在文件末尾:兜底必须排在该部件所有 display 声明之后 */
252
+ [data-scope='menu'][data-part='item'][hidden] {
253
+ display: none;
177
254
  }
178
255
  }