@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,222 @@
1
+ @layer xihan.components {
2
+ [data-scope='mention'][data-part='root'] {
3
+ /* 尺寸档只换这四个私有槽,输入框与候选都从 root 继承它们,换档不必给每个部件各写一条选择器。
4
+ 浮层在 positioner 里、也是 root 的后代,一并继承 */
5
+ --xh-_mention-px: var(--xh-control-px-md);
6
+ --xh-_mention-py: var(--xh-field-py);
7
+ --xh-_mention-gap: var(--xh-control-gap-md);
8
+ --xh-_mention-font-size: var(--xh-text-body-size);
9
+
10
+ /* 形态档只换这三个槽:缺省即 outline(描边 + 画布底) */
11
+ --xh-_mention-input-bg: var(--xh-bg-canvas);
12
+ --xh-_mention-input-border: var(--xh-border-default);
13
+ --xh-_mention-input-border-hover: var(--xh-border-strong);
14
+
15
+ /* 强调色随语气走,只落在聚焦与高亮上,正文文字色不动。
16
+ 没写 data-tone 时 --xh-_tone 没被声明,三处都退回原来的取值 */
17
+ --xh-_mention-ring: var(--xh-ring-focus);
18
+ --xh-_mention-item-highlight-bg: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
19
+ --xh-_mention-item-highlight-fg: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
20
+
21
+ display: block;
22
+ }
23
+
24
+ /* 只要声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
25
+ [data-scope='mention'][data-part='root'][hidden] {
26
+ display: none;
27
+ }
28
+
29
+ /* —— 尺寸 —— */
30
+ /* 缺省档写在上面的基础规则里 */
31
+ [data-scope='mention'][data-part='root'][data-size='sm'] {
32
+ --xh-_mention-px: var(--xh-control-px-sm);
33
+ --xh-_mention-py: var(--xh-space-1);
34
+ --xh-_mention-gap: var(--xh-control-gap-sm);
35
+ --xh-_mention-font-size: var(--xh-font-size-sm);
36
+ }
37
+
38
+ [data-scope='mention'][data-part='root'][data-size='lg'] {
39
+ --xh-_mention-px: var(--xh-control-px-lg);
40
+ --xh-_mention-py: var(--xh-space-3);
41
+ --xh-_mention-gap: var(--xh-control-gap-lg);
42
+ --xh-_mention-font-size: var(--xh-font-size-lg);
43
+ }
44
+
45
+ /* —— 形态 —— */
46
+ /* 输入类不做 solid:实心底压着可编辑的正文,读起来像块不可点的色板。
47
+ 形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
48
+ [data-scope='mention'][data-part='root'][data-variant='outline'] {
49
+ --xh-_mention-input-bg: var(--xh-bg-canvas);
50
+ --xh-_mention-input-border: var(--xh-border-default);
51
+ --xh-_mention-input-border-hover: var(--xh-border-strong);
52
+ }
53
+
54
+ /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
55
+ [data-scope='mention'][data-part='root'][data-variant='subtle'] {
56
+ --xh-_mention-input-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
57
+ --xh-_mention-input-border: transparent;
58
+ --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-default));
59
+ }
60
+
61
+ [data-scope='mention'][data-part='root'][data-variant='ghost'] {
62
+ --xh-_mention-input-bg: transparent;
63
+ --xh-_mention-input-border: transparent;
64
+ --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-default));
65
+ }
66
+
67
+ /* 框与聚焦环都长在输入框自己身上:这里没有把输入框和别的控件裹成一体的输入行,
68
+ 整个组件在视觉上就是这一个可打字的方框 */
69
+ [data-scope='mention'][data-part='input'] {
70
+ display: block;
71
+ inline-size: 100%;
72
+ padding-block: var(--xh-mention-input-py, var(--xh-_mention-py));
73
+ padding-inline: var(--xh-mention-input-px, var(--xh-_mention-px));
74
+ border: var(--xh-stroke-thin) solid var(--xh-mention-input-border, var(--xh-_mention-input-border));
75
+ border-radius: var(--xh-mention-input-radius, var(--xh-shape-control));
76
+ background: var(--xh-mention-input-bg, var(--xh-_mention-input-bg));
77
+ color: var(--xh-mention-input-fg, var(--xh-fg-default));
78
+ /* 表单控件不继承页面字体,只给字号会让输入框用 UA 默认字族,与周围文本对不上 */
79
+ font-family: inherit;
80
+ font-size: var(--xh-mention-input-font-size, var(--xh-_mention-font-size));
81
+ line-height: var(--xh-leading-normal);
82
+ /* 多行宿主只许纵向拉伸:横向拉宽会把浮层与输入框的对齐拽歪 */
83
+ resize: vertical;
84
+ transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
85
+ }
86
+
87
+ [data-scope='mention'][data-part='input'][hidden] {
88
+ display: none;
89
+ }
90
+
91
+ [data-scope='mention'][data-part='input']::placeholder {
92
+ color: var(--xh-mention-placeholder-fg, var(--xh-fg-subtle));
93
+ }
94
+
95
+ [data-scope='mention'][data-part='input']:hover:not(:disabled) {
96
+ border-color: var(--xh-mention-input-border-hover, var(--xh-_mention-input-border-hover));
97
+ }
98
+
99
+ [data-scope='mention'][data-part='input']:focus-visible {
100
+ border-color: var(--xh-mention-input-border-focus, var(--xh-_mention-ring));
101
+ outline: var(--xh-ring-width) solid var(--xh-_mention-ring);
102
+ outline-offset: var(--xh-ring-offset);
103
+ }
104
+
105
+ /* 输入框是单体表单控件,禁用走原生 disabled(候选那边一律 aria-disabled) */
106
+ [data-scope='mention'][data-part='input']:disabled {
107
+ background: var(--xh-mention-input-bg-disabled, var(--xh-bg-subtle));
108
+ color: var(--xh-fg-disabled);
109
+ /* 禁用时连拉伸把手也一并收走,免得留一个拖得动却改不了内容的角 */
110
+ resize: none;
111
+ cursor: not-allowed;
112
+ }
113
+
114
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
115
+ [data-scope='mention'][data-part='positioner'] {
116
+ position: fixed;
117
+ z-index: var(--xh-layer-popover);
118
+ /* 空壳不吃指针,交互只发生在 content 上 */
119
+ pointer-events: none;
120
+ }
121
+
122
+ /* 输入框被滚出可视区:整个浮层随之收起 */
123
+ [data-scope='mention'][data-part='positioner'][data-hidden] {
124
+ visibility: hidden;
125
+ }
126
+
127
+ [data-scope='mention'][data-part='content'] {
128
+ display: flex;
129
+ flex-direction: column;
130
+ min-inline-size: var(--xh-mention-content-min-w, var(--xh-overlay-min-w));
131
+ max-inline-size: var(--xh-mention-content-max-w, var(--xh-overlay-max-w));
132
+ max-block-size: var(--xh-mention-content-max-h, var(--xh-overlay-max-h));
133
+ padding-block: var(--xh-mention-content-py, var(--xh-space-1));
134
+ padding-inline: var(--xh-mention-content-px, var(--xh-space-1));
135
+ border: var(--xh-stroke-thin) solid var(--xh-mention-content-border, var(--xh-border-default));
136
+ border-radius: var(--xh-mention-content-radius, var(--xh-shape-surface));
137
+ background: var(--xh-mention-content-bg, var(--xh-bg-surface));
138
+ color: var(--xh-mention-content-fg, var(--xh-fg-default));
139
+ box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-2));
140
+ overflow-y: auto;
141
+ pointer-events: auto;
142
+ }
143
+
144
+ /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
145
+ [data-scope='mention'][data-part='content'][hidden] {
146
+ display: none;
147
+ }
148
+
149
+ /* 宿主把候选全筛没了:浮层还开着,但这一副框壳没有内容可框,整个让位。
150
+ 组件不替宿主判空——「没有匹配项」要不要说话、说什么,是宿主的事 */
151
+ [data-scope='mention'][data-part='content']:empty {
152
+ display: none;
153
+ }
154
+
155
+ /* 列表从不承载焦点(焦点恒在输入框,高亮走 aria-activedescendant),
156
+ 可滚动容器在某些浏览器里会被自动塞进 Tab 序列,落焦时不画环 */
157
+ [data-scope='mention'][data-part='content']:focus-visible {
158
+ outline: none;
159
+ }
160
+
161
+ [data-scope='mention'][data-part='content'][data-state='open'] {
162
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
163
+ }
164
+
165
+ [data-scope='mention'][data-part='item'] {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: var(--xh-mention-item-gap, var(--xh-_mention-gap));
169
+ padding-block: var(--xh-mention-item-py, var(--xh-space-1));
170
+ padding-inline: var(--xh-mention-item-px, var(--xh-control-px-sm));
171
+ border-radius: var(--xh-mention-item-radius, var(--xh-shape-control));
172
+ color: var(--xh-mention-item-fg, var(--xh-fg-default));
173
+ font-size: var(--xh-mention-item-font-size, var(--xh-_mention-font-size));
174
+ /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
175
+ line-height: var(--xh-mention-item-leading, var(--xh-leading-normal));
176
+ white-space: nowrap;
177
+ cursor: pointer;
178
+ user-select: none;
179
+ }
180
+
181
+ [data-scope='mention'][data-part='item'][hidden] {
182
+ display: none;
183
+ }
184
+
185
+ /* 只认 data-highlighted,不写 :hover:指针划过时连接层已经把高亮搬过来了,
186
+ 两套底色各画各的会出现"鼠标停在 A 上、回车却插进了 B" */
187
+ [data-scope='mention'][data-part='item'][data-highlighted]:not([data-disabled]) {
188
+ background: var(--xh-mention-item-bg-highlight, var(--xh-_mention-item-highlight-bg));
189
+ color: var(--xh-mention-item-fg-highlight, var(--xh-_mention-item-highlight-fg));
190
+ font-weight: var(--xh-mention-item-font-weight-highlight, var(--xh-font-weight-medium));
191
+ }
192
+
193
+ /* 禁用走 aria-disabled 而非原生 disabled(原生 disabled 不派 click,禁用守卫就走不到),
194
+ 所以只能靠 data-disabled 表达,写 :disabled 永远不命中 */
195
+ [data-scope='mention'][data-part='item'][data-disabled] {
196
+ color: var(--xh-fg-disabled);
197
+ cursor: not-allowed;
198
+ }
199
+
200
+ [data-scope='mention'][data-part='item-text'] {
201
+ flex: 1;
202
+ /* 弹性项默认 min-width: auto 不肯收缩到内容以下,省略号因此永不生效 */
203
+ min-inline-size: 0;
204
+ overflow: hidden;
205
+ white-space: nowrap;
206
+ text-overflow: ellipsis;
207
+ }
208
+
209
+ [data-scope='mention'][data-part='item-text'][hidden] {
210
+ display: none;
211
+ }
212
+
213
+ /* 候选行里除文本以外的部分(作者摆的头像、副标题)由作者自己画;
214
+ 这里只把强调色透出去,作者要用就取它 */
215
+ [data-scope='mention'][data-part='item'][data-highlighted] [data-part='item-text'] {
216
+ color: var(--xh-mention-item-fg-highlight, var(--xh-_mention-item-highlight-fg));
217
+ }
218
+
219
+ [data-scope='mention'][data-part='item'][data-disabled] [data-part='item-text'] {
220
+ color: var(--xh-fg-disabled);
221
+ }
222
+ }
package/css/menu.css ADDED
@@ -0,0 +1,144 @@
1
+ @layer xihan.components {
2
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
3
+ [data-scope='menu'][data-part='positioner'] {
4
+ position: fixed;
5
+ z-index: var(--xh-layer-popover);
6
+ /* 空壳不吃指针,交互只发生在 content 上 */
7
+ pointer-events: none;
8
+ }
9
+
10
+ /* 锚点被滚出可视区:整个浮层随之收起 */
11
+ [data-scope='menu'][data-part='positioner'][data-hidden] {
12
+ visibility: hidden;
13
+ }
14
+
15
+ [data-scope='menu'][data-part='content'] {
16
+ /* 尺寸档只换这四个私有槽,条目从 content 继承它们,换档不必给每个部件各写一条选择器。
17
+ 菜单没有 root 部件(浮层树的最外层就是 content),三轴属性都落在这里 */
18
+ --xh-_menu-item-py: var(--xh-space-2);
19
+ --xh-_menu-item-px: var(--xh-control-px-sm);
20
+ --xh-_menu-item-gap: var(--xh-control-gap-md);
21
+ --xh-_menu-font-size: var(--xh-text-body-size);
22
+
23
+ /* 条目高亮底随语气走。没写 data-tone 时 --xh-_tone-subtle-hover 没被声明,退回原来的取值 */
24
+ --xh-_menu-item-highlight-bg: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
25
+
26
+ display: flex;
27
+ flex-direction: column;
28
+ min-inline-size: var(--xh-menu-min-w, var(--xh-overlay-menu-min-w));
29
+ max-inline-size: var(--xh-menu-max-w, var(--xh-overlay-max-w));
30
+ padding-block: var(--xh-menu-py, var(--xh-space-1));
31
+ padding-inline: var(--xh-menu-px, var(--xh-space-1));
32
+ border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
33
+ border-radius: var(--xh-menu-radius, var(--xh-shape-surface));
34
+ background: var(--xh-menu-bg, var(--xh-bg-surface));
35
+ color: var(--xh-menu-fg, var(--xh-fg-default));
36
+ box-shadow: var(--xh-menu-shadow, var(--xh-elevation-2));
37
+ pointer-events: auto;
38
+ }
39
+
40
+ /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
41
+ [data-scope='menu'][data-part='content'][hidden] {
42
+ display: none;
43
+ }
44
+
45
+ /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
46
+ [data-scope='menu'][data-part='content']:focus-visible {
47
+ outline: none;
48
+ }
49
+
50
+ [data-scope='menu'][data-part='content'][data-state='open'] {
51
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
52
+ }
53
+
54
+ /* —— 尺寸 —— */
55
+ /* 缺省档写在 content 的基础规则里。条目内外边距缺省档本就不在 control-h/px 那把尺上
56
+ (高度由 padding 撑出来、行内内缩取的是 sm 档),故按 space 档上下各挪一级 */
57
+ [data-scope='menu'][data-part='content'][data-size='sm'] {
58
+ --xh-_menu-item-py: var(--xh-space-1);
59
+ --xh-_menu-item-px: var(--xh-space-1);
60
+ --xh-_menu-item-gap: var(--xh-control-gap-sm);
61
+ --xh-_menu-font-size: var(--xh-font-size-sm);
62
+ }
63
+
64
+ [data-scope='menu'][data-part='content'][data-size='lg'] {
65
+ --xh-_menu-item-py: var(--xh-space-3);
66
+ --xh-_menu-item-px: var(--xh-space-3);
67
+ --xh-_menu-item-gap: var(--xh-control-gap-lg);
68
+ --xh-_menu-font-size: var(--xh-font-size-lg);
69
+ }
70
+
71
+ [data-scope='menu'][data-part='item'] {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: var(--xh-menu-item-gap, var(--xh-_menu-item-gap));
75
+ padding-block: var(--xh-menu-item-py, var(--xh-_menu-item-py));
76
+ padding-inline: var(--xh-menu-item-px, var(--xh-_menu-item-px));
77
+ border-radius: var(--xh-menu-item-radius, var(--xh-shape-control));
78
+ color: var(--xh-menu-item-fg, var(--xh-fg-default));
79
+ font-size: var(--xh-menu-item-font-size, var(--xh-_menu-font-size));
80
+ line-height: var(--xh-leading-none);
81
+ white-space: nowrap;
82
+ cursor: pointer;
83
+ user-select: none;
84
+ }
85
+
86
+ /* 键盘高亮与指针高亮共用一套底色:条目本身承载焦点,不另画描边 */
87
+ [data-scope='menu'][data-part='item']:not([data-disabled]):is(:hover, :focus) {
88
+ background: var(--xh-menu-item-bg-highlight, var(--xh-_menu-item-highlight-bg));
89
+ }
90
+
91
+ [data-scope='menu'][data-part='item']:focus {
92
+ outline: none;
93
+ }
94
+
95
+ [data-scope='menu'][data-part='item'][data-disabled] {
96
+ color: var(--xh-fg-disabled);
97
+ cursor: not-allowed;
98
+ }
99
+
100
+ [data-scope='menu'][data-part='separator'] {
101
+ block-size: var(--xh-menu-separator-thickness, var(--xh-stroke-thin));
102
+ margin-block: var(--xh-menu-separator-my, var(--xh-space-1));
103
+ background: var(--xh-menu-separator-color, var(--xh-border-default));
104
+ }
105
+
106
+ /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
107
+ 引擎不产出箭头偏移,因此沿交叉轴固定居中 */
108
+ [data-scope='menu'][data-part='arrow'] {
109
+ position: absolute;
110
+ inline-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
111
+ block-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
112
+ background: var(--xh-menu-bg, var(--xh-bg-surface));
113
+ border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
114
+ rotate: 45deg;
115
+ }
116
+
117
+ [data-scope='menu'][data-part='arrow'][data-placement^='bottom'] {
118
+ inset-block-start: calc(var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / -2);
119
+ inset-inline-start: calc(50% - var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / 2);
120
+ border-block-end: 0;
121
+ border-inline-end: 0;
122
+ }
123
+
124
+ [data-scope='menu'][data-part='arrow'][data-placement^='top'] {
125
+ inset-block-end: calc(var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / -2);
126
+ inset-inline-start: calc(50% - var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / 2);
127
+ border-block-start: 0;
128
+ border-inline-start: 0;
129
+ }
130
+
131
+ [data-scope='menu'][data-part='arrow'][data-placement^='right'] {
132
+ inset-inline-start: calc(var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / -2);
133
+ inset-block-start: calc(50% - var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / 2);
134
+ border-block-start: 0;
135
+ border-inline-end: 0;
136
+ }
137
+
138
+ [data-scope='menu'][data-part='arrow'][data-placement^='left'] {
139
+ inset-inline-end: calc(var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / -2);
140
+ inset-block-start: calc(50% - var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size)) / 2);
141
+ border-block-end: 0;
142
+ border-inline-start: 0;
143
+ }
144
+ }
@@ -0,0 +1,271 @@
1
+ @layer xihan.components {
2
+ /* 一排 trigger。主轴跟着 data-orientation 走:菜单栏本身可以横排也可以竖排 */
3
+ [data-scope='menubar'][data-part='root'] {
4
+ /* 尺寸档只换这五个私有槽,trigger 与菜单里的条目都从 root 继承它们,
5
+ 换档不必给每个部件各写一条选择器。菜单浮层在 positioner 里、也是 root 的后代,一并继承 */
6
+ --xh-_menubar-trigger-py: var(--xh-space-1);
7
+ --xh-_menubar-trigger-px: var(--xh-control-px-sm);
8
+ --xh-_menubar-item-py: var(--xh-space-2);
9
+ --xh-_menubar-item-px: var(--xh-control-px-sm);
10
+ --xh-_menubar-font-size: var(--xh-text-body-size);
11
+
12
+ /* 高亮底随语气走:展开着的那张菜单的 trigger 与菜单里的高亮条目共用这一档。
13
+ 没写 data-tone 时 --xh-_tone-subtle-hover 没被声明,退回原来的中性底。
14
+ 聚焦环不在其列,它恒是 --xh-ring-focus */
15
+ --xh-_menubar-highlight-bg: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
16
+
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--xh-menubar-gap, var(--xh-space-1));
20
+ padding-block: var(--xh-menubar-py, var(--xh-space-1));
21
+ padding-inline: var(--xh-menubar-px, var(--xh-space-1));
22
+ border-radius: var(--xh-menubar-radius, var(--xh-shape-control));
23
+ background: var(--xh-menubar-bg, transparent);
24
+ color: var(--xh-menubar-fg, var(--xh-fg-default));
25
+ }
26
+
27
+ [data-scope='menubar'][data-part='root'][hidden] {
28
+ display: none;
29
+ }
30
+
31
+ [data-scope='menubar'][data-part='root'][data-orientation='vertical'] {
32
+ flex-direction: column;
33
+ align-items: stretch;
34
+ }
35
+
36
+ /* —— 尺寸 —— */
37
+ /* 缺省档写在上面的基础规则里。内边距的缺省档已经落在控件内边距尺的 sm 那一格,
38
+ 往下没有更小的一格,sm 档改取空间尺;lg 档回到控件尺的下一格 */
39
+ [data-scope='menubar'][data-part='root'][data-size='sm'] {
40
+ --xh-_menubar-trigger-py: var(--xh-space-0_5);
41
+ --xh-_menubar-trigger-px: var(--xh-space-1_5);
42
+ --xh-_menubar-item-py: var(--xh-space-1_5);
43
+ --xh-_menubar-item-px: var(--xh-space-1_5);
44
+ --xh-_menubar-font-size: var(--xh-font-size-sm);
45
+ }
46
+
47
+ [data-scope='menubar'][data-part='root'][data-size='lg'] {
48
+ --xh-_menubar-trigger-py: var(--xh-space-2);
49
+ --xh-_menubar-trigger-px: var(--xh-control-px-md);
50
+ --xh-_menubar-item-py: var(--xh-space-3);
51
+ --xh-_menubar-item-px: var(--xh-control-px-md);
52
+ --xh-_menubar-font-size: var(--xh-font-size-lg);
53
+ }
54
+
55
+ /* root 只是 roving tabindex 的兜底落点,焦点转投给 trigger 之后就不再停留,不画环 */
56
+ [data-scope='menubar'][data-part='root']:focus-visible {
57
+ outline: none;
58
+ }
59
+
60
+ [data-scope='menubar'][data-part='root'][data-disabled] {
61
+ color: var(--xh-fg-disabled);
62
+ }
63
+
64
+ [data-scope='menubar'][data-part='trigger'] {
65
+ display: inline-flex;
66
+ flex: none;
67
+ align-items: center;
68
+ gap: var(--xh-menubar-trigger-gap, var(--xh-control-gap-sm));
69
+ padding-block: var(--xh-menubar-trigger-py, var(--xh-_menubar-trigger-py));
70
+ padding-inline: var(--xh-menubar-trigger-px, var(--xh-_menubar-trigger-px));
71
+ border: none;
72
+ border-radius: var(--xh-menubar-trigger-radius, var(--xh-shape-control));
73
+ background: transparent;
74
+ color: inherit;
75
+ font: inherit;
76
+ font-size: var(--xh-menubar-trigger-font-size, var(--xh-_menubar-font-size));
77
+ line-height: var(--xh-leading-none);
78
+ white-space: nowrap;
79
+ cursor: pointer;
80
+ user-select: none;
81
+ }
82
+
83
+ [data-scope='menubar'][data-part='trigger'][hidden] {
84
+ display: none;
85
+ }
86
+
87
+ /* 展开着的那一项与指针悬停共用一套底色:菜单栏里"哪一张开着"必须一眼看得出来 */
88
+ [data-scope='menubar'][data-part='trigger']:not([data-disabled]):is(:hover, [data-state='open']) {
89
+ background: var(--xh-menubar-trigger-bg-active, var(--xh-_menubar-highlight-bg));
90
+ }
91
+
92
+ /* trigger 自带 Tab 位(整条菜单栏唯一的那个),必须画得出焦点 */
93
+ [data-scope='menubar'][data-part='trigger']:focus-visible {
94
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
95
+ outline-offset: var(--xh-ring-offset);
96
+ }
97
+
98
+ /* 禁用走 aria-disabled 而非原生 disabled:trigger 仍要可聚焦(它还得当方向键的起点),
99
+ 所以只能靠 data-disabled 表达,写 :disabled 永远不命中 */
100
+ [data-scope='menubar'][data-part='trigger'][data-disabled] {
101
+ color: var(--xh-fg-disabled);
102
+ cursor: not-allowed;
103
+ }
104
+
105
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
106
+ [data-scope='menubar'][data-part='positioner'] {
107
+ position: fixed;
108
+ z-index: var(--xh-layer-popover);
109
+ /* 空壳不吃指针,交互只发生在 content 上 */
110
+ pointer-events: none;
111
+ }
112
+
113
+ /* 锚点被滚出可视区:整个浮层随之收起 */
114
+ [data-scope='menubar'][data-part='positioner'][data-hidden] {
115
+ visibility: hidden;
116
+ }
117
+
118
+ [data-scope='menubar'][data-part='content'] {
119
+ display: flex;
120
+ flex-direction: column;
121
+ min-inline-size: var(--xh-menubar-menu-min-w, var(--xh-overlay-menu-min-w));
122
+ max-inline-size: var(--xh-menubar-menu-max-w, 20rem);
123
+ padding-block: var(--xh-menubar-menu-py, var(--xh-space-1));
124
+ padding-inline: var(--xh-menubar-menu-px, var(--xh-space-1));
125
+ border: var(--xh-stroke-thin) solid var(--xh-menubar-menu-border, var(--xh-border-default));
126
+ border-radius: var(--xh-menubar-menu-radius, var(--xh-shape-surface));
127
+ background: var(--xh-menubar-menu-bg, var(--xh-bg-surface));
128
+ color: var(--xh-menubar-menu-fg, var(--xh-fg-default));
129
+ box-shadow: var(--xh-menubar-menu-shadow, var(--xh-elevation-2));
130
+ pointer-events: auto;
131
+ }
132
+
133
+ /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位。
134
+ 连接层给 content 发 hidden,这一条漏了整张菜单就永远收不起来 */
135
+ [data-scope='menubar'][data-part='content'][hidden] {
136
+ display: none;
137
+ }
138
+
139
+ /* 容器只是"没有条目锚点时"的 Tab 兜底落点,落焦不画环:高亮永远长在条目上 */
140
+ [data-scope='menubar'][data-part='content']:focus-visible {
141
+ outline: none;
142
+ }
143
+
144
+ /* 展开时轻微涨开。只在切换到别张菜单时不重播——那由 data-state 的翻转天然保证:
145
+ 一张关一张开,动画各跑各的 */
146
+ [data-scope='menubar'][data-part='content'][data-state='open'] {
147
+ animation: xh-menubar-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
148
+ }
149
+
150
+ /* 关掉动效的用户不该被涨开动画晃一下;这里只管本组件,公共那份不动 */
151
+ @media (prefers-reduced-motion: reduce) {
152
+ [data-scope='menubar'][data-part='content'][data-state='open'] {
153
+ animation-duration: 0.01ms;
154
+ }
155
+ }
156
+
157
+ [data-scope='menubar'][data-part='group'] {
158
+ display: flex;
159
+ flex-direction: column;
160
+ }
161
+
162
+ [data-scope='menubar'][data-part='group'][hidden] {
163
+ display: none;
164
+ }
165
+
166
+ /* 分组标题不是条目、进不了导航,画成小号次要文字,
167
+ 内缩与条目对齐(同一个 padding-inline),标题与条目文字才在一条竖线上 */
168
+ [data-scope='menubar'][data-part='group-label'] {
169
+ display: block;
170
+ padding-block: var(--xh-menubar-group-label-py, var(--xh-space-1));
171
+ padding-inline: var(--xh-menubar-group-label-px, var(--xh-_menubar-item-px));
172
+ color: var(--xh-menubar-group-label-fg, var(--xh-fg-subtle));
173
+ font-size: var(--xh-menubar-group-label-font-size, var(--xh-font-size-xs));
174
+ font-weight: var(--xh-menubar-group-label-font-weight, var(--xh-font-weight-medium));
175
+ line-height: var(--xh-leading-none);
176
+ user-select: none;
177
+ }
178
+
179
+ [data-scope='menubar'][data-part='group-label'][hidden] {
180
+ display: none;
181
+ }
182
+
183
+ [data-scope='menubar'][data-part='item'] {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: var(--xh-menubar-item-gap, var(--xh-control-gap-md));
187
+ padding-block: var(--xh-menubar-item-py, var(--xh-_menubar-item-py));
188
+ padding-inline: var(--xh-menubar-item-px, var(--xh-_menubar-item-px));
189
+ border-radius: var(--xh-menubar-item-radius, var(--xh-shape-control));
190
+ color: var(--xh-menubar-item-fg, var(--xh-fg-default));
191
+ font-size: var(--xh-menubar-item-font-size, var(--xh-_menubar-font-size));
192
+ line-height: var(--xh-leading-none);
193
+ white-space: nowrap;
194
+ cursor: pointer;
195
+ user-select: none;
196
+ }
197
+
198
+ /* 条目可以按需收起(按上下文过滤掉的命令),有 display 就得补这条 */
199
+ [data-scope='menubar'][data-part='item'][hidden] {
200
+ display: none;
201
+ }
202
+
203
+ /* 键盘高亮与指针高亮共用一套底色:条目本身承载焦点,不另画描边。
204
+ data-highlighted 跟的是连接层记的焦点条目,与 :focus 说的是同一件事,
205
+ 子部件够不着那个伪类,只能读这个标记 */
206
+ [data-scope='menubar'][data-part='item']:not([data-disabled]):is(:hover, :focus, [data-highlighted]) {
207
+ background: var(--xh-menubar-item-bg-highlight, var(--xh-_menubar-highlight-bg));
208
+ }
209
+
210
+ [data-scope='menubar'][data-part='item']:focus {
211
+ outline: none;
212
+ }
213
+
214
+ [data-scope='menubar'][data-part='item'][data-disabled] {
215
+ color: var(--xh-fg-disabled);
216
+ cursor: not-allowed;
217
+ }
218
+
219
+ [data-scope='menubar'][data-part='item-text'] {
220
+ display: block;
221
+ flex: 1;
222
+ overflow: hidden;
223
+ white-space: nowrap;
224
+ text-overflow: ellipsis;
225
+ }
226
+
227
+ [data-scope='menubar'][data-part='item-text'][hidden] {
228
+ display: none;
229
+ }
230
+
231
+ /* 标记位(勾选符号 / 图标 / 快捷键提示):定宽占位,切换内容不引起行宽抖动 */
232
+ [data-scope='menubar'][data-part='item-indicator'] {
233
+ display: inline-flex;
234
+ flex: none;
235
+ align-items: center;
236
+ justify-content: center;
237
+ inline-size: var(--xh-menubar-item-indicator-size, var(--xh-control-indicator-size));
238
+ color: var(--xh-menubar-item-indicator-fg, var(--xh-fg-subtle));
239
+ }
240
+
241
+ [data-scope='menubar'][data-part='item-indicator'][hidden] {
242
+ display: none;
243
+ }
244
+
245
+ [data-scope='menubar'][data-part='item-indicator'][data-disabled] {
246
+ color: var(--xh-fg-disabled);
247
+ }
248
+
249
+ /* 分隔线横跨菜单,与菜单栏自身的 orientation 无关:菜单永远是一列条目。
250
+ 不给 display,因此 UA 的 [hidden]{display:none} 自己就压得住 */
251
+ [data-scope='menubar'][data-part='separator'] {
252
+ block-size: var(--xh-menubar-separator-thickness, var(--xh-stroke-thin));
253
+ margin-block: var(--xh-menubar-separator-my, var(--xh-space-1));
254
+ background: var(--xh-menubar-separator-color, var(--xh-border-default));
255
+ }
256
+ }
257
+
258
+ @layer xihan.motion {
259
+ /* 自带前缀独占一个名字,不动公共的那份关键帧 */
260
+ @keyframes xh-menubar-in {
261
+ from {
262
+ opacity: 0;
263
+ scale: 0.96;
264
+ }
265
+
266
+ to {
267
+ opacity: 1;
268
+ scale: 1;
269
+ }
270
+ }
271
+ }
package/css/motion.css ADDED
@@ -0,0 +1,58 @@
1
+ @layer xihan.motion {
2
+ @keyframes xh-spin {
3
+ to {
4
+ transform: rotate(360deg);
5
+ }
6
+ }
7
+
8
+ @keyframes xh-fade-in {
9
+ from {
10
+ opacity: 0;
11
+ }
12
+
13
+ to {
14
+ opacity: 1;
15
+ }
16
+ }
17
+
18
+ @keyframes xh-fade-out {
19
+ from {
20
+ opacity: 1;
21
+ }
22
+
23
+ to {
24
+ opacity: 0;
25
+ }
26
+ }
27
+
28
+ @keyframes xh-dialog-in {
29
+ from {
30
+ opacity: 0;
31
+ transform: translateY(8px) scale(0.98);
32
+ }
33
+
34
+ to {
35
+ opacity: 1;
36
+ transform: none;
37
+ }
38
+ }
39
+
40
+ @keyframes xh-dialog-out {
41
+ from {
42
+ opacity: 1;
43
+ transform: none;
44
+ }
45
+
46
+ to {
47
+ opacity: 0;
48
+ transform: translateY(8px) scale(0.98);
49
+ }
50
+ }
51
+
52
+ @media (prefers-reduced-motion: reduce) {
53
+ [data-scope='dialog'] [data-part],
54
+ [data-scope='button'] [data-part] {
55
+ animation-duration: 0.01ms !important;
56
+ }
57
+ }
58
+ }