@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,207 @@
1
+ @layer xihan.components {
2
+ [data-scope='popselect'][data-part='root'] {
3
+ /* 尺寸档只换这四个私有槽,触发器与条目都从 root 继承它们,换档不必给每个部件各写一条选择器。
4
+ 浮层在 positioner 里、也是 root 的后代,一并继承 */
5
+ --xh-_popselect-h: var(--xh-control-h-md);
6
+ --xh-_popselect-px: var(--xh-control-px-md);
7
+ --xh-_popselect-gap: var(--xh-control-gap-md);
8
+ --xh-_popselect-font-size: var(--xh-text-body-size);
9
+
10
+ /* 形态档只换这三个槽:缺省即 outline(描边 + 画布底) */
11
+ --xh-_popselect-trigger-bg: var(--xh-bg-canvas);
12
+ --xh-_popselect-trigger-border: var(--xh-border-default);
13
+ --xh-_popselect-trigger-border-hover: var(--xh-border-strong);
14
+
15
+ /* 强调色随语气走,只落在聚焦与选中上,正文文字色不动。
16
+ 没写 data-tone 时 --xh-_tone 没被声明,三处都退回原来的取值 */
17
+ --xh-_popselect-ring: var(--xh-ring-focus);
18
+ --xh-_popselect-accent: var(--xh-_tone, var(--xh-bg-brand));
19
+ --xh-_popselect-item-highlight-bg: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
20
+ --xh-_popselect-item-selected-fg: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
21
+
22
+ display: inline-flex;
23
+ }
24
+
25
+ /* —— 尺寸 —— */
26
+ /* 缺省档写在上面的基础规则里 */
27
+ [data-scope='popselect'][data-part='root'][data-size='sm'] {
28
+ --xh-_popselect-h: var(--xh-control-h-sm);
29
+ --xh-_popselect-px: var(--xh-control-px-sm);
30
+ --xh-_popselect-gap: var(--xh-control-gap-sm);
31
+ --xh-_popselect-font-size: var(--xh-font-size-sm);
32
+ }
33
+
34
+ [data-scope='popselect'][data-part='root'][data-size='lg'] {
35
+ --xh-_popselect-h: var(--xh-control-h-lg);
36
+ --xh-_popselect-px: var(--xh-control-px-lg);
37
+ --xh-_popselect-gap: var(--xh-control-gap-lg);
38
+ --xh-_popselect-font-size: var(--xh-font-size-lg);
39
+ }
40
+
41
+ /* —— 形态 —— */
42
+ /* 形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
43
+ [data-scope='popselect'][data-part='root'][data-variant='outline'] {
44
+ --xh-_popselect-trigger-bg: var(--xh-bg-canvas);
45
+ --xh-_popselect-trigger-border: var(--xh-border-default);
46
+ --xh-_popselect-trigger-border-hover: var(--xh-border-strong);
47
+ }
48
+
49
+ /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
50
+ [data-scope='popselect'][data-part='root'][data-variant='subtle'] {
51
+ --xh-_popselect-trigger-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
52
+ --xh-_popselect-trigger-border: transparent;
53
+ --xh-_popselect-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-default));
54
+ }
55
+
56
+ [data-scope='popselect'][data-part='root'][data-variant='ghost'] {
57
+ --xh-_popselect-trigger-bg: transparent;
58
+ --xh-_popselect-trigger-border: transparent;
59
+ --xh-_popselect-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-default));
60
+ }
61
+
62
+ [data-scope='popselect'][data-part='trigger'] {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ justify-content: space-between;
66
+ gap: var(--xh-popselect-trigger-gap, var(--xh-_popselect-gap));
67
+ block-size: var(--xh-popselect-trigger-h, var(--xh-_popselect-h));
68
+ padding-inline: var(--xh-popselect-trigger-px, var(--xh-_popselect-px));
69
+ border: var(--xh-stroke-thin) solid var(--xh-popselect-trigger-border, var(--xh-_popselect-trigger-border));
70
+ border-radius: var(--xh-popselect-trigger-radius, var(--xh-shape-control));
71
+ background: var(--xh-popselect-trigger-bg, var(--xh-_popselect-trigger-bg));
72
+ color: var(--xh-popselect-trigger-fg, var(--xh-fg-default));
73
+ font-size: var(--xh-popselect-trigger-font-size, var(--xh-_popselect-font-size));
74
+ line-height: var(--xh-leading-none);
75
+ text-align: start;
76
+ cursor: pointer;
77
+ }
78
+
79
+ [data-scope='popselect'][data-part='trigger']:hover:not(:disabled) {
80
+ border-color: var(--xh-popselect-trigger-border-hover, var(--xh-_popselect-trigger-border-hover));
81
+ }
82
+
83
+ [data-scope='popselect'][data-part='trigger']:focus-visible {
84
+ outline: var(--xh-ring-width) solid var(--xh-_popselect-ring);
85
+ outline-offset: var(--xh-ring-offset);
86
+ }
87
+
88
+ /* trigger 是单体控件,用原生 disabled(条目那边一律 aria-disabled) */
89
+ [data-scope='popselect'][data-part='trigger']:disabled {
90
+ color: var(--xh-fg-disabled);
91
+ cursor: not-allowed;
92
+ }
93
+
94
+ /* 无选中:触发器上显示的是占位文字,画淡一档 */
95
+ [data-scope='popselect'][data-part='trigger'][data-placeholder] {
96
+ color: var(--xh-popselect-placeholder-fg, var(--xh-fg-subtle));
97
+ }
98
+
99
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
100
+ [data-scope='popselect'][data-part='positioner'] {
101
+ position: fixed;
102
+ z-index: var(--xh-layer-popover);
103
+ /* 空壳不吃指针,交互只发生在 content 上 */
104
+ pointer-events: none;
105
+ }
106
+
107
+ /* 锚点被滚出可视区:整个浮层随之收起 */
108
+ [data-scope='popselect'][data-part='positioner'][data-hidden] {
109
+ visibility: hidden;
110
+ }
111
+
112
+ [data-scope='popselect'][data-part='content'] {
113
+ display: flex;
114
+ flex-direction: column;
115
+ min-inline-size: var(--xh-popselect-content-min-w, var(--xh-overlay-min-w));
116
+ max-inline-size: var(--xh-popselect-content-max-w, var(--xh-overlay-max-w));
117
+ max-block-size: var(--xh-popselect-content-max-h, var(--xh-overlay-max-h));
118
+ padding: var(--xh-popselect-content-p, var(--xh-space-1));
119
+ border: var(--xh-stroke-thin) solid var(--xh-popselect-content-border, var(--xh-border-default));
120
+ border-radius: var(--xh-popselect-content-radius, var(--xh-shape-surface));
121
+ background: var(--xh-popselect-content-bg, var(--xh-bg-surface));
122
+ color: var(--xh-popselect-content-fg, var(--xh-fg-default));
123
+ box-shadow: var(--xh-popselect-content-shadow, var(--xh-elevation-2));
124
+ overflow-y: auto;
125
+ pointer-events: auto;
126
+ }
127
+
128
+ /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
129
+ [data-scope='popselect'][data-part='content'][hidden] {
130
+ display: none;
131
+ }
132
+
133
+ /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
134
+ [data-scope='popselect'][data-part='content']:focus-visible {
135
+ outline: none;
136
+ }
137
+
138
+ [data-scope='popselect'][data-part='content'][data-state='open'] {
139
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
140
+ }
141
+
142
+ [data-scope='popselect'][data-part='item'] {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: var(--xh-popselect-item-gap, var(--xh-_popselect-gap));
146
+ padding-block: var(--xh-popselect-item-py, var(--xh-space-1));
147
+ padding-inline: var(--xh-popselect-item-px, var(--xh-control-px-sm));
148
+ border-radius: var(--xh-popselect-item-radius, var(--xh-shape-control));
149
+ color: var(--xh-popselect-item-fg, var(--xh-fg-default));
150
+ font-size: var(--xh-popselect-item-font-size, var(--xh-_popselect-font-size));
151
+ /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
152
+ line-height: var(--xh-popselect-item-leading, var(--xh-leading-normal));
153
+ white-space: nowrap;
154
+ cursor: pointer;
155
+ user-select: none;
156
+ }
157
+
158
+ /* 键盘高亮与指针高亮共用一套底色:条目本身承载焦点,不另画描边 */
159
+ [data-scope='popselect'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
160
+ background: var(--xh-popselect-item-bg-highlight, var(--xh-_popselect-item-highlight-bg));
161
+ }
162
+
163
+ /* 选中只换文字色与字重,配合行尾的 ✓ 标记;底色整条通道留给高亮,两者因此能同时看见 */
164
+ [data-scope='popselect'][data-part='item'][data-state='checked'] {
165
+ color: var(--xh-popselect-item-fg-selected, var(--xh-_popselect-item-selected-fg));
166
+ font-weight: var(--xh-popselect-item-font-weight-selected, var(--xh-font-weight-medium));
167
+ }
168
+
169
+ [data-scope='popselect'][data-part='item']:focus {
170
+ outline: none;
171
+ }
172
+
173
+ /* 禁用走 aria-disabled 而非原生 disabled,条目仍可聚焦,样式由 data-disabled 表达 */
174
+ [data-scope='popselect'][data-part='item'][data-disabled] {
175
+ color: var(--xh-fg-disabled);
176
+ cursor: not-allowed;
177
+ }
178
+
179
+ [data-scope='popselect'][data-part='item-text'] {
180
+ flex: 1;
181
+ /* 弹性子项默认最小尺寸是内容尺寸,不归零则长文本把行撑破、省略号轮不上 */
182
+ min-inline-size: 0;
183
+ overflow: hidden;
184
+ white-space: nowrap;
185
+ text-overflow: ellipsis;
186
+ }
187
+
188
+ /* 勾选标记只在选中项上出现;未选中时占位不塌,切换选中不引起行宽抖动 */
189
+ [data-scope='popselect'][data-part='item-indicator'] {
190
+ display: inline-flex;
191
+ flex: none;
192
+ align-items: center;
193
+ justify-content: center;
194
+ inline-size: var(--xh-popselect-item-indicator-size, var(--xh-control-indicator-size));
195
+ color: var(--xh-popselect-item-indicator-fg, var(--xh-_popselect-accent));
196
+ visibility: hidden;
197
+ }
198
+
199
+ /* 没写内容就由皮肤画勾;作者往部件里塞了自己的图形,这条规则即不命中 */
200
+ [data-scope='popselect'][data-part='item-indicator']:empty::before {
201
+ content: '✓';
202
+ }
203
+
204
+ [data-scope='popselect'][data-part='item-indicator'][data-state='checked'] {
205
+ visibility: visible;
206
+ }
207
+ }
@@ -0,0 +1,37 @@
1
+ @layer xihan.components {
2
+ /* 私有槽位:组件令牌 → 中档字面量两级回退。厚度写在 root 上而不是 track 上,
3
+ 尺寸档才改得动一处就够,track 靠继承拿到值。 */
4
+ [data-scope='progress'][data-part='root'] {
5
+ --xh-_thickness: var(--xh-progress-thickness, 6px);
6
+
7
+ display: inline-block;
8
+ inline-size: 100%;
9
+ }
10
+
11
+ [data-scope='progress'][data-part='track'] {
12
+ overflow: hidden;
13
+ inline-size: 100%;
14
+ block-size: var(--xh-_thickness);
15
+ border-radius: var(--xh-shape-pill);
16
+ background: var(--xh-progress-track, var(--xh-bg-subtle-active));
17
+ }
18
+
19
+ /* 语气槽由 root 上的 data-tone 声明、经继承发下来;没写 data-tone 时退回原来的品牌色。
20
+ 轨道不跟着换族:只有已完成的那段换色,底槽保持中性才读得出"完成了多少"。 */
21
+ [data-scope='progress'][data-part='range'] {
22
+ block-size: 100%;
23
+ border-radius: var(--xh-shape-pill);
24
+ background: var(--xh-progress-range, var(--xh-_tone, var(--xh-bg-brand)));
25
+ transition: inline-size var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
26
+ }
27
+
28
+ /* —— 尺寸 —— */
29
+ /* 只有厚度分档;中间档不写规则,缺省即中档 */
30
+ [data-scope='progress'][data-part='root'][data-size='sm'] {
31
+ --xh-_thickness: var(--xh-progress-thickness, var(--xh-space-1));
32
+ }
33
+
34
+ [data-scope='progress'][data-part='root'][data-size='lg'] {
35
+ --xh-_thickness: var(--xh-progress-thickness, var(--xh-space-2));
36
+ }
37
+ }
@@ -0,0 +1,50 @@
1
+ @layer xihan.components {
2
+ /*
3
+ * 根 <svg>。深浅两色取固定档而不是语义前景/背景:语义色跟着主题走,深色主题下整张码
4
+ * 会翻成浅底深模块的反相图,一部分读码器不认反相码。要换配色改下面这三个覆盖槽。
5
+ *
6
+ * 换色只有一条硬约束:码点必须比底色深,且两者对比要足。反相(浅码点深底)与
7
+ * 深浅相近都会让一部分读码器扫不出来,而这件事肉眼看不出来。
8
+ *
9
+ * --xh-qr-code-fg 码点色,同时经 currentColor 传给码眼
10
+ * --xh-qr-code-bg 底色,挖空矩形也用它
11
+ * --xh-qr-code-eye-fg 码眼色,缺省跟随码点色
12
+ */
13
+ [data-scope='qr-code'][data-part='root'] {
14
+ display: block;
15
+ border-radius: var(--xh-qr-code-radius, var(--xh-radius-md));
16
+ background: var(--xh-qr-code-bg, var(--xh-color-neutral-0));
17
+
18
+ /* 模块那条 path 不带自己的填充色,从这里继承下去 */
19
+ color: var(--xh-qr-code-fg, var(--xh-color-neutral-950));
20
+ fill: currentColor;
21
+ }
22
+
23
+ /* 声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
24
+ [data-scope='qr-code'][data-part='root'][hidden] {
25
+ display: none;
26
+ }
27
+
28
+ /* 码眼那条 path 恒在(与形状无关),没单独指定就退回 currentColor,跟码点同色 */
29
+ [data-scope='qr-code'][data-part='root'] > [data-xh-geom='eyes'] {
30
+ fill: var(--xh-qr-code-eye-fg, currentColor);
31
+ }
32
+
33
+ /*
34
+ * logo 底下那块挖空:填的必须与根的背景逐字同一个表达式,两处一起改。
35
+ * 差一点就会在 logo 四周露出一圈色差,读码器会把那圈当成模块边界。
36
+ */
37
+ [data-scope='qr-code'][data-part='root'] > [data-xh-geom='logo-clear'] {
38
+ fill: var(--xh-qr-code-bg, var(--xh-color-neutral-0));
39
+ }
40
+
41
+ /*
42
+ * 没有可编码的内容、以及内容超出容量:两种情形都一个模块不铺,留一块带描边的占位。
43
+ * 占位用的是语义色——它是界面元素不是码面,跟着主题走才对。
44
+ */
45
+ [data-scope='qr-code'][data-part='root'][data-state='empty'],
46
+ [data-scope='qr-code'][data-part='root'][data-state='error'] {
47
+ background: var(--xh-qr-code-placeholder-bg, var(--xh-bg-subtle));
48
+ box-shadow: inset 0 0 0 var(--xh-stroke-thin) var(--xh-qr-code-placeholder-border, var(--xh-border-default));
49
+ }
50
+ }
@@ -0,0 +1,101 @@
1
+ @layer xihan.components {
2
+ [data-scope='radio-group'][data-part='root'] {
3
+ /* 尺寸档只换这两个私有槽,标题与条目都从 root 继承它们,换档不必给每个部件各写一条选择器 */
4
+ --xh-_radio-group-item-gap: var(--xh-control-gap-md);
5
+ --xh-_radio-group-font-size: var(--xh-text-label-size);
6
+ /* 选中色随语气走。没写 data-tone 时 --xh-_tone 没被声明,退回原来的品牌色 */
7
+ --xh-_radio-group-accent: var(--xh-_tone, var(--xh-bg-brand));
8
+
9
+ display: flex;
10
+ gap: var(--xh-radio-group-gap, var(--xh-stack-gap-md));
11
+ }
12
+
13
+ [data-scope='radio-group'][data-part='root'][data-orientation='vertical'] {
14
+ flex-direction: column;
15
+ }
16
+
17
+ [data-scope='radio-group'][data-part='root'][data-orientation='horizontal'] {
18
+ flex-direction: row;
19
+ align-items: center;
20
+ }
21
+
22
+ /* —— 尺寸 —— */
23
+ [data-scope='radio-group'][data-part='root'][data-size='sm'] {
24
+ --xh-_radio-group-item-gap: var(--xh-control-gap-sm);
25
+ --xh-_radio-group-font-size: var(--xh-font-size-sm);
26
+ }
27
+
28
+ [data-scope='radio-group'][data-part='root'][data-size='lg'] {
29
+ --xh-_radio-group-item-gap: var(--xh-control-gap-lg);
30
+ --xh-_radio-group-font-size: var(--xh-font-size-lg);
31
+ }
32
+
33
+ [data-scope='radio-group'][data-part='label'] {
34
+ color: var(--xh-radio-group-label-fg, var(--xh-fg-muted));
35
+ font-size: var(--xh-radio-group-label-font-size, var(--xh-_radio-group-font-size));
36
+ font-weight: var(--xh-radio-group-label-font-weight, var(--xh-text-label-weight));
37
+ line-height: var(--xh-leading-none);
38
+ }
39
+
40
+ /* position 是给条目内那份视觉隐藏的原生输入做定位基准,别去掉 */
41
+ [data-scope='radio-group'][data-part='item'] {
42
+ position: relative;
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: var(--xh-radio-group-item-gap, var(--xh-_radio-group-item-gap));
46
+ border-radius: var(--xh-radio-group-item-radius, var(--xh-shape-control));
47
+ color: var(--xh-radio-group-item-fg, var(--xh-fg-default));
48
+ font-size: var(--xh-radio-group-item-font-size, var(--xh-_radio-group-font-size));
49
+ line-height: var(--xh-leading-none);
50
+ cursor: pointer;
51
+ }
52
+
53
+ [data-scope='radio-group'][data-part='item']:focus-visible {
54
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
55
+ outline-offset: var(--xh-ring-offset);
56
+ }
57
+
58
+ /* 禁用走 aria-disabled 而非原生 disabled,条目仍可聚焦,样式由 data-disabled 表达 */
59
+ [data-scope='radio-group'][data-part='item'][data-disabled] {
60
+ color: var(--xh-radio-group-item-fg-disabled, var(--xh-fg-disabled));
61
+ cursor: not-allowed;
62
+ opacity: var(--xh-state-disabled-opacity);
63
+ }
64
+
65
+ [data-scope='radio-group'][data-part='indicator'] {
66
+ display: inline-flex;
67
+ flex: none;
68
+ align-items: center;
69
+ justify-content: center;
70
+ inline-size: 16px;
71
+ block-size: 16px;
72
+ border: var(--xh-stroke-thin) solid var(--xh-radio-group-indicator-border, var(--xh-border-default));
73
+ border-radius: var(--xh-shape-pill);
74
+ background: var(--xh-radio-group-indicator-bg, var(--xh-bg-canvas));
75
+ transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
76
+ }
77
+
78
+ [data-scope='radio-group'][data-part='indicator'][data-state='checked'] {
79
+ border-color: var(--xh-radio-group-indicator-border-checked, var(--xh-_radio-group-accent));
80
+ }
81
+
82
+ /* 圆点用 scale 收放,选中态切换不引起布局抖动 */
83
+ [data-scope='radio-group'][data-part='indicator']::before {
84
+ content: '';
85
+ inline-size: 8px;
86
+ block-size: 8px;
87
+ border-radius: var(--xh-shape-pill);
88
+ background: var(--xh-radio-group-indicator-dot, var(--xh-_radio-group-accent));
89
+ scale: 0;
90
+ transition: scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
91
+ }
92
+
93
+ [data-scope='radio-group'][data-part='indicator'][data-state='checked']::before {
94
+ scale: 1;
95
+ }
96
+
97
+ [data-scope='radio-group'][data-part='item-text'] {
98
+ color: inherit;
99
+ user-select: none;
100
+ }
101
+ }
package/css/rating.css ADDED
@@ -0,0 +1,141 @@
1
+ @layer xihan.components {
2
+ /* position 是给表单影子做定位基准的:那份输入被连接层内联写成绝对定位,
3
+ 根上不留基准它就会贴到更外层的定位祖先上,原生校验提示跟着飘到页面别处。 */
4
+ [data-scope='rating'][data-part='root'] {
5
+ /* 尺寸档只换这三个私有槽,标题与星星都从 root 继承它们,换档不必给每个部件各写一条选择器 */
6
+ --xh-_rating-item-gap: var(--xh-space-1);
7
+ --xh-_rating-item-size: var(--xh-font-size-2xl);
8
+ --xh-_rating-font-size: var(--xh-text-label-size);
9
+ /* 点亮色随语气走。没写 data-tone 时 --xh-_tone 没被声明,退回原来的警示色 */
10
+ --xh-_rating-accent: var(--xh-_tone, var(--xh-color-warning-500));
11
+
12
+ position: relative;
13
+ display: inline-flex;
14
+ flex-direction: column;
15
+ gap: var(--xh-rating-gap, var(--xh-stack-gap-md));
16
+ }
17
+
18
+ /* —— 尺寸 —— */
19
+ [data-scope='rating'][data-part='root'][data-size='sm'] {
20
+ --xh-_rating-item-gap: var(--xh-control-gap-sm);
21
+ --xh-_rating-item-size: var(--xh-font-size-xl);
22
+ --xh-_rating-font-size: var(--xh-font-size-sm);
23
+ }
24
+
25
+ [data-scope='rating'][data-part='root'][data-size='lg'] {
26
+ --xh-_rating-item-gap: var(--xh-control-gap-lg);
27
+ --xh-_rating-item-size: var(--xh-font-size-3xl);
28
+ --xh-_rating-font-size: var(--xh-font-size-lg);
29
+ }
30
+
31
+ /* 整体压暗只做一次,落在最外层:星星逐颗再叠 opacity 会越叠越黑 */
32
+ [data-scope='rating'][data-part='root'][data-disabled] {
33
+ cursor: not-allowed;
34
+ opacity: var(--xh-state-disabled-opacity);
35
+ }
36
+
37
+ [data-scope='rating'][data-part='label'] {
38
+ color: var(--xh-rating-label-fg, var(--xh-fg-muted));
39
+ font-size: var(--xh-rating-label-font-size, var(--xh-_rating-font-size));
40
+ font-weight: var(--xh-rating-label-font-weight, var(--xh-text-label-weight));
41
+ line-height: var(--xh-leading-none);
42
+ }
43
+
44
+ [data-scope='rating'][data-part='label'][data-disabled] {
45
+ color: var(--xh-fg-subtle);
46
+ }
47
+
48
+ [data-scope='rating'][data-part='control'] {
49
+ display: inline-flex;
50
+ align-items: center;
51
+ gap: var(--xh-rating-item-gap, var(--xh-_rating-item-gap));
52
+ }
53
+
54
+ /* 容器只是焦点域的兜底落点(焦点从带外进来先落在它身上,随即被转投给某颗星),
55
+ 落焦不画环:高亮永远长在星星上,容器再画一圈只是重影。 */
56
+ [data-scope='rating'][data-part='control']:focus-visible {
57
+ outline: none;
58
+ }
59
+
60
+ [data-scope='rating'][data-part='control'][data-readonly] {
61
+ cursor: default;
62
+ }
63
+
64
+ [data-scope='rating'][data-part='control'][data-disabled] {
65
+ cursor: not-allowed;
66
+ }
67
+
68
+ /* 星星字符由作者写,皮肤只负责上色与盒子。
69
+ inline-block 是判定"点在左半边还是右半边"的前提:连接层拿 offsetX / clientWidth 算比例,
70
+ 行内盒会把字符两侧的换行空白算进宽度,半颗的分界线就偏了。 */
71
+ [data-scope='rating'][data-part='item'] {
72
+ display: inline-block;
73
+ border-radius: var(--xh-rating-item-radius, var(--xh-shape-control));
74
+ color: var(--xh-rating-item-fg, var(--xh-fg-subtle));
75
+ font-size: var(--xh-rating-item-font-size, var(--xh-_rating-item-size));
76
+ line-height: var(--xh-leading-none);
77
+ cursor: pointer;
78
+ /* 从一颗星划到另一颗会把中间的字符连着选中,星带看上去像被刷蓝了 */
79
+ user-select: none;
80
+ transition: color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
81
+ }
82
+
83
+ /* 点亮的判据是 data-highlighted,不是 aria-checked:
84
+ aria-checked 只认落定的值,而悬停预览要求手划过去当场就亮。
85
+ 两者共用同一档颜色,预览与选中因此看不出差别——这正是"预览"该有的样子。 */
86
+ [data-scope='rating'][data-part='item'][data-highlighted] {
87
+ color: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
88
+ }
89
+
90
+ /* 半颗:一道 50% 硬边渐变裁到字形上,左半亮右半灰。
91
+ 必须排在 data-highlighted 之后:半颗那颗星同时带着 data-highlighted,两条选择器特异度相同,
92
+ 顺序一反 color 就把 transparent 覆盖回实色,渐变整个看不见。 */
93
+ [data-scope='rating'][data-part='item'][data-half] {
94
+ background-image: linear-gradient(
95
+ to right,
96
+ var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent)) 50%,
97
+ var(--xh-rating-item-fg, var(--xh-fg-subtle)) 50%
98
+ );
99
+ -webkit-background-clip: text;
100
+ background-clip: text;
101
+ color: transparent;
102
+ }
103
+
104
+ /* rtl 下"半颗"落在星的右半边(连接层按 1 - offsetX/width 判),亮的那半得跟着掉头。
105
+ 渐变方向没有逻辑关键字可用,只能靠 :dir() 分流,不能指望逻辑属性自动换向。 */
106
+ [data-scope='rating'][data-part='item'][data-half]:dir(rtl) {
107
+ background-image: linear-gradient(
108
+ to left,
109
+ var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent)) 50%,
110
+ var(--xh-rating-item-fg, var(--xh-fg-subtle)) 50%
111
+ );
112
+ }
113
+
114
+ [data-scope='rating'][data-part='item']:focus-visible {
115
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
116
+ outline-offset: var(--xh-ring-offset);
117
+ }
118
+
119
+ /* 只读仍进得了 Tab 序列、读屏也念得出,只是改不动:收掉手型,别暗示"点这儿能改" */
120
+ [data-scope='rating'][data-part='item'][data-readonly] {
121
+ cursor: default;
122
+ }
123
+
124
+ /* 集合条目一律 aria-disabled、没有原生 disabled,:disabled 永远命不中,只能认 data-disabled */
125
+ [data-scope='rating'][data-part='item'][data-disabled] {
126
+ cursor: not-allowed;
127
+ }
128
+
129
+ /* 表单影子的视觉隐藏(绝对定位 + 1px + clip-path)连接层内联给全了,这里不重复也不覆盖,
130
+ 只掐掉指针命中:那个 1px 的框压在星带上会截走本该落到星星上的点击。 */
131
+ [data-scope='rating'][data-part='hidden-input'] {
132
+ pointer-events: none;
133
+ }
134
+
135
+ /* display 会盖掉 UA 的 [hidden]{display:none},凡是被赋了 display 的角色节点都要显式让位 */
136
+ [data-scope='rating'][data-part='root'][hidden],
137
+ [data-scope='rating'][data-part='control'][hidden],
138
+ [data-scope='rating'][data-part='item'][hidden] {
139
+ display: none;
140
+ }
141
+ }
package/css/reset.css ADDED
@@ -0,0 +1,22 @@
1
+ /*
2
+ * 库自己的基线,只作用于带 data-scope 的库节点,不碰宿主页面的任何元素。
3
+ *
4
+ * 层序上 xihan.reset 在 xihan.components 之前,所以组件皮肤里的任何显式声明都盖得住这里。
5
+ */
6
+ @layer xihan.reset {
7
+ /* 尺寸一律含边框:皮肤里的宽高都是照可见盒子写的。 */
8
+ [data-scope],
9
+ [data-scope]::before,
10
+ [data-scope]::after {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ /*
15
+ * UA 样式会把表单控件的字体锁成系统默认,不跟随页面。
16
+ * 这几个元素是 UA 唯一强制字体的地方,标题一类不在其中,故不做全局 font: inherit。
17
+ */
18
+ [data-scope]:where(button, input, optgroup, select, textarea) {
19
+ font: inherit;
20
+ letter-spacing: inherit;
21
+ }
22
+ }
package/css/result.css ADDED
@@ -0,0 +1,104 @@
1
+ @layer xihan.components {
2
+ [data-scope='result'][data-part='root'] {
3
+ /* 三个尺寸档只换这几个私有槽,图标与标题都从 root 继承它们,
4
+ 换档不必给每个部件各写一条选择器 */
5
+ --xh-_result-gap: var(--xh-stack-gap-md);
6
+ --xh-_result-py: var(--xh-section-py);
7
+ --xh-_result-icon-size: 3.5rem;
8
+ --xh-_result-title-size: var(--xh-font-size-xl);
9
+ /* 说明的读行宽度:整句话不收窄就会拉成一条难读的长行 */
10
+ --xh-_result-measure: 32rem;
11
+ /* 图标区的语气色,由 data-status 换掉;没写 status 就退成中性 */
12
+ --xh-_result-accent: var(--xh-fg-subtle);
13
+
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ justify-content: center;
18
+ gap: var(--xh-result-gap, var(--xh-_result-gap));
19
+ padding-block: var(--xh-result-py, var(--xh-_result-py));
20
+ padding-inline: var(--xh-result-px, var(--xh-space-6));
21
+ color: var(--xh-result-fg, var(--xh-fg-default));
22
+ text-align: center;
23
+ }
24
+
25
+ /* 上面声明了 display,会盖掉 UA 的 [hidden] 规则,收起态要在这里接回来 */
26
+ [data-scope='result'][data-part='root'][hidden] {
27
+ display: none;
28
+ }
29
+
30
+ /* —— 尺寸 —— */
31
+ [data-scope='result'][data-part='root'][data-size='sm'] {
32
+ --xh-_result-gap: var(--xh-space-3);
33
+ --xh-_result-py: var(--xh-space-6);
34
+ --xh-_result-icon-size: 2.5rem;
35
+ --xh-_result-title-size: var(--xh-font-size-lg);
36
+ }
37
+
38
+ [data-scope='result'][data-part='root'][data-size='lg'] {
39
+ --xh-_result-gap: var(--xh-space-6);
40
+ --xh-_result-py: var(--xh-space-8);
41
+ --xh-_result-icon-size: 4.5rem;
42
+ --xh-_result-title-size: var(--xh-font-size-2xl);
43
+ }
44
+
45
+ /* —— 语气 —— */
46
+ /* 三个状态码各并进最接近的一族:找不到=提示,没权限=警示,服务出错=危险。
47
+ 用 500 档而不是 600:500 是装饰性强调档,600 是给实心底与文字用的 */
48
+ [data-scope='result'][data-part='root'][data-status='success'] {
49
+ --xh-_result-accent: var(--xh-color-success-500);
50
+ }
51
+
52
+ [data-scope='result'][data-part='root'][data-status='warning'],
53
+ [data-scope='result'][data-part='root'][data-status='403'] {
54
+ --xh-_result-accent: var(--xh-color-warning-500);
55
+ }
56
+
57
+ [data-scope='result'][data-part='root'][data-status='error'],
58
+ [data-scope='result'][data-part='root'][data-status='500'] {
59
+ --xh-_result-accent: var(--xh-color-danger-500);
60
+ }
61
+
62
+ [data-scope='result'][data-part='root'][data-status='info'],
63
+ [data-scope='result'][data-part='root'][data-status='404'] {
64
+ --xh-_result-accent: var(--xh-color-info-500);
65
+ }
66
+
67
+ /* —— 各段 —— */
68
+ [data-scope='result'][data-part='icon'] {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ flex: none;
73
+ block-size: var(--xh-result-icon-size, var(--xh-_result-icon-size));
74
+ inline-size: var(--xh-result-icon-size, var(--xh-_result-icon-size));
75
+ color: var(--xh-result-icon-fg, var(--xh-_result-accent));
76
+ /* 图标常是一个字形,字号跟着图标框走,line-height 不再另占高度 */
77
+ font-size: var(--xh-result-icon-font-size, var(--xh-_result-icon-size));
78
+ line-height: var(--xh-leading-none);
79
+ }
80
+
81
+ [data-scope='result'][data-part='title'] {
82
+ margin: 0;
83
+ color: var(--xh-result-title-fg, var(--xh-fg-default));
84
+ font-size: var(--xh-result-title-font-size, var(--xh-_result-title-size));
85
+ font-weight: var(--xh-result-title-font-weight, var(--xh-font-weight-semibold));
86
+ line-height: var(--xh-result-title-leading, var(--xh-leading-tight));
87
+ }
88
+
89
+ [data-scope='result'][data-part='description'] {
90
+ margin: 0;
91
+ max-inline-size: var(--xh-result-description-max-w, var(--xh-_result-measure));
92
+ color: var(--xh-result-description-fg, var(--xh-fg-muted));
93
+ font-size: var(--xh-result-description-font-size, var(--xh-text-body-size));
94
+ line-height: var(--xh-result-description-leading, var(--xh-text-body-leading));
95
+ }
96
+
97
+ [data-scope='result'][data-part='action'] {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ align-items: center;
101
+ justify-content: center;
102
+ gap: var(--xh-result-action-gap, var(--xh-space-2));
103
+ }
104
+ }