@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,160 @@
1
+ @layer xihan.components {
2
+ /* 分段控件:段与段之间不留缝,靠共边连成一体,所以 root 不设 gap。
3
+ 排布只能读 data-orientation——aria-orientation 在多选模式下连接层根本不输出(role=group 不收) */
4
+ [data-scope='toggle-group'][data-part='root'] {
5
+ display: inline-flex;
6
+ align-items: stretch;
7
+ }
8
+
9
+ [data-scope='toggle-group'][data-part='root'][data-orientation='horizontal'] {
10
+ flex-direction: row;
11
+ }
12
+
13
+ [data-scope='toggle-group'][data-part='root'][data-orientation='vertical'] {
14
+ flex-direction: column;
15
+ }
16
+
17
+ /* root 会短暂拿到 tabindex=0,但它的 onFocus 立刻把焦点转投给条目,
18
+ 在它身上画环只会闪一下就跳走,高亮要长在条目上 */
19
+ [data-scope='toggle-group'][data-part='root']:focus-visible {
20
+ outline: none;
21
+ }
22
+
23
+ [data-scope='toggle-group'][data-part='root'][data-disabled] {
24
+ cursor: not-allowed;
25
+ }
26
+
27
+ [data-scope='toggle-group'][data-part='item'] {
28
+ /* 私有槽位:组件令牌 → 语义令牌两级回退。状态规则只改写槽位,
29
+ 底色/描边/前景三者永远同步换,不会出现选中了底色却没跟上描边 */
30
+ --xh-_bg: var(--xh-toggle-group-item-bg, var(--xh-bg-canvas));
31
+ --xh-_bg-hover: var(--xh-toggle-group-item-bg-hover, var(--xh-bg-subtle-hover));
32
+ --xh-_bg-active: var(--xh-toggle-group-item-bg-active, var(--xh-bg-subtle-active));
33
+ --xh-_fg: var(--xh-toggle-group-item-fg, var(--xh-fg-default));
34
+ --xh-_border: var(--xh-toggle-group-item-border, var(--xh-border-default));
35
+
36
+ /* 相邻两段靠负外边距压掉一条重边,谁的描边浮在上面必须能指定,
37
+ 因此条目必须是定位元素——静态元素的 z-index 不生效 */
38
+ position: relative;
39
+ z-index: 0;
40
+ display: inline-flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ gap: var(--xh-toggle-group-item-gap, var(--xh-control-gap-md));
44
+ block-size: var(--xh-toggle-group-item-h, var(--xh-control-h-md));
45
+ padding-inline: var(--xh-toggle-group-item-px, var(--xh-control-px-md));
46
+ border: var(--xh-stroke-thin) solid var(--xh-_border);
47
+ /* 中间段一律直角,圆角只由首末两端补回来 */
48
+ border-radius: 0;
49
+ background: var(--xh-_bg);
50
+ color: var(--xh-_fg);
51
+ font-size: var(--xh-toggle-group-item-font-size, var(--xh-text-label-size));
52
+ font-weight: var(--xh-toggle-group-item-font-weight, var(--xh-text-label-weight));
53
+ line-height: var(--xh-leading-none);
54
+ white-space: nowrap;
55
+ cursor: pointer;
56
+ user-select: none;
57
+ transition:
58
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
59
+ border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
60
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
61
+ }
62
+
63
+ /* 共边:后一段往回挪一个描边宽度,两条相邻的边重合成一条。
64
+ 方向跟着 data-orientation 走,横排收 inline 轴、竖排收 block 轴 */
65
+ [data-scope='toggle-group'][data-part='root'][data-orientation='horizontal']
66
+ > [data-scope='toggle-group'][data-part='item']
67
+ + [data-scope='toggle-group'][data-part='item'] {
68
+ margin-inline-start: calc(-1 * var(--xh-stroke-thin));
69
+ }
70
+
71
+ [data-scope='toggle-group'][data-part='root'][data-orientation='vertical']
72
+ > [data-scope='toggle-group'][data-part='item']
73
+ + [data-scope='toggle-group'][data-part='item'] {
74
+ margin-block-start: calc(-1 * var(--xh-stroke-thin));
75
+ }
76
+
77
+ /* 两端圆角用逻辑角属性写,rtl 与竖排都跟着换边,不必另写一套。
78
+ 只认 root 的直接子节点:条目之间插了别的节点时宁可退化成直角,也不要圆错位置 */
79
+ [data-scope='toggle-group'][data-part='root'][data-orientation='horizontal']
80
+ > [data-scope='toggle-group'][data-part='item']:first-child {
81
+ border-start-start-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
82
+ border-end-start-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
83
+ }
84
+
85
+ [data-scope='toggle-group'][data-part='root'][data-orientation='horizontal']
86
+ > [data-scope='toggle-group'][data-part='item']:last-child {
87
+ border-start-end-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
88
+ border-end-end-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
89
+ }
90
+
91
+ [data-scope='toggle-group'][data-part='root'][data-orientation='vertical']
92
+ > [data-scope='toggle-group'][data-part='item']:first-child {
93
+ border-start-start-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
94
+ border-start-end-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
95
+ }
96
+
97
+ [data-scope='toggle-group'][data-part='root'][data-orientation='vertical']
98
+ > [data-scope='toggle-group'][data-part='item']:last-child {
99
+ border-end-start-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
100
+ border-end-end-radius: var(--xh-toggle-group-radius, var(--xh-shape-control));
101
+ }
102
+
103
+ [data-scope='toggle-group'][data-part='item']:not([data-disabled]):hover {
104
+ background: var(--xh-_bg-hover);
105
+ }
106
+
107
+ [data-scope='toggle-group'][data-part='item']:not([data-disabled]):active {
108
+ background: var(--xh-_bg-active);
109
+ }
110
+
111
+ /* 选中态:单选是 role=radio + aria-checked,多选是原生按钮 + aria-pressed,
112
+ 两套 ARIA 各出一半,样式一条都对不上——只有 data-state 是两种模式共同的出口 */
113
+ [data-scope='toggle-group'][data-part='item'][data-state='on'] {
114
+ --xh-_bg: var(--xh-toggle-group-item-bg-on, var(--xh-bg-brand));
115
+ --xh-_bg-hover: var(--xh-toggle-group-item-bg-on-hover, var(--xh-bg-brand-hover));
116
+ --xh-_bg-active: var(--xh-toggle-group-item-bg-on-active, var(--xh-bg-brand-active));
117
+ --xh-_fg: var(--xh-toggle-group-item-fg-on, var(--xh-fg-on-brand));
118
+ --xh-_border: var(--xh-toggle-group-item-border-on, var(--xh-bg-brand));
119
+
120
+ /* 抬一层,让 brand 描边压住邻居的灰边。共边处两条边重合在同一像素上,
121
+ 不抬层就由文档序决定谁在上,选中段的左右两侧会一边有色一边灰 */
122
+ z-index: 1;
123
+ }
124
+
125
+ /* 集合条目一律 aria-disabled 而非原生 disabled(禁用项仍要能当方向键的起点),
126
+ 所以这里读 data-disabled,写 :disabled 永远不命中。
127
+ 另外禁用不能用 opacity 变淡:半透明会把压在下面那条边透出来,共边处当场变成两条线 */
128
+ [data-scope='toggle-group'][data-part='item'][data-disabled] {
129
+ --xh-_bg: var(--xh-toggle-group-item-bg-disabled, var(--xh-bg-muted));
130
+ --xh-_fg: var(--xh-toggle-group-item-fg-disabled, var(--xh-fg-disabled));
131
+ --xh-_border: var(--xh-toggle-group-item-border-disabled, var(--xh-border-subtle));
132
+
133
+ cursor: not-allowed;
134
+ }
135
+
136
+ /* 禁用且选中:底色不能一路退回 muted,那样"这一段是当前值"的信息就整个没了。
137
+ 掺一半 muted 进 brand,既看得出选中,也看得出改不动 */
138
+ [data-scope='toggle-group'][data-part='item'][data-disabled][data-state='on'] {
139
+ --xh-_bg: var(
140
+ --xh-toggle-group-item-bg-on-disabled,
141
+ color-mix(in oklab, var(--xh-bg-brand) 50%, var(--xh-bg-muted))
142
+ );
143
+ --xh-_fg: var(--xh-toggle-group-item-fg-on-disabled, var(--xh-fg-on-brand));
144
+ --xh-_border: var(--xh-toggle-group-item-border-on-disabled, var(--xh-bg-brand));
145
+ }
146
+
147
+ /* 焦点环要浮在共边之上:outline 画在元素外侧,不抬层就会被后一段的底色与描边盖掉一半 */
148
+ [data-scope='toggle-group'][data-part='item']:focus-visible {
149
+ z-index: 2;
150
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
151
+ outline-offset: var(--xh-ring-offset);
152
+ }
153
+
154
+ /* 给了 display 的角色节点必须补这条:display 声明会盖掉 UA 的 [hidden]{display:none},
155
+ 不补的话作者给节点加 hidden 后它照样显示 */
156
+ [data-scope='toggle-group'][data-part='root'][hidden],
157
+ [data-scope='toggle-group'][data-part='item'][hidden] {
158
+ display: none;
159
+ }
160
+ }
package/css/toggle.css ADDED
@@ -0,0 +1,103 @@
1
+ @layer xihan.components {
2
+ [data-scope='toggle'][data-part='root'] {
3
+ /* 私有槽位:组件令牌 → 语义令牌两级回退,变体只改这几个槽位 */
4
+ --xh-_bg: var(--xh-toggle-bg, transparent);
5
+ --xh-_bg-hover: var(--xh-toggle-bg-hover, var(--xh-bg-subtle-hover));
6
+ --xh-_fg: var(--xh-toggle-fg, var(--xh-fg-default));
7
+
8
+ /* 按下态:强调交给底色。前景不用品牌色——subtle-active 这一档底上任何品牌色都过不了
9
+ AA(浅色最好的 brand-700 只有 4.37),强调靠底、可读靠前景是更稳的分工 */
10
+ --xh-_bg-pressed: var(--xh-toggle-bg-pressed, var(--xh-bg-subtle-active));
11
+ --xh-_fg-pressed: var(--xh-toggle-fg-pressed, var(--xh-fg-default));
12
+
13
+ display: inline-flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ gap: var(--xh-toggle-gap, var(--xh-control-gap-md));
17
+ block-size: var(--xh-toggle-h, var(--xh-control-h-md));
18
+ padding-inline: var(--xh-toggle-px, var(--xh-control-px-md));
19
+ border: var(--xh-stroke-thin) solid transparent;
20
+ border-radius: var(--xh-toggle-radius, var(--xh-shape-control));
21
+ background: var(--xh-_bg);
22
+ color: var(--xh-_fg);
23
+ font-size: var(--xh-toggle-font-size, var(--xh-text-label-size));
24
+ font-weight: var(--xh-toggle-font-weight, var(--xh-text-label-weight));
25
+ line-height: var(--xh-leading-none);
26
+ white-space: nowrap;
27
+ cursor: pointer;
28
+ user-select: none;
29
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
30
+ }
31
+
32
+ [data-scope='toggle'][data-part='root']:hover {
33
+ background: var(--xh-_bg-hover);
34
+ }
35
+
36
+ [data-scope='toggle'][data-part='root'][data-disabled] {
37
+ cursor: not-allowed;
38
+ opacity: var(--xh-state-disabled-opacity);
39
+ }
40
+
41
+ [data-scope='toggle'][data-part='root']:focus-visible {
42
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
43
+ outline-offset: var(--xh-ring-offset);
44
+ }
45
+
46
+ /* —— 变体 —— */
47
+ /* 形态只决定颜色怎么用;用哪族颜色由 data-tone 决定,没写 tone 就沿用原来的中性配色 */
48
+ [data-scope='toggle'][data-part='root'][data-variant='solid'] {
49
+ --xh-_bg: transparent;
50
+ --xh-_bg-hover: var(--xh-_tone-subtle, var(--xh-bg-subtle-hover));
51
+ --xh-_fg: var(--xh-_tone-fg, var(--xh-fg-default));
52
+ --xh-_bg-pressed: var(--xh-_tone, var(--xh-bg-brand));
53
+ --xh-_fg-pressed: var(--xh-_tone-on, var(--xh-fg-on-brand));
54
+ }
55
+
56
+ [data-scope='toggle'][data-part='root'][data-variant='subtle'] {
57
+ --xh-_bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
58
+ --xh-_bg-hover: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
59
+ --xh-_fg: var(--xh-_tone-fg, var(--xh-fg-default));
60
+ --xh-_bg-pressed: var(--xh-_tone-subtle-active, var(--xh-bg-subtle-active));
61
+ --xh-_fg-pressed: var(--xh-_tone-fg, var(--xh-fg-default));
62
+ }
63
+
64
+ [data-scope='toggle'][data-part='root'][data-variant='outline'] {
65
+ --xh-_bg: transparent;
66
+ --xh-_bg-hover: var(--xh-_tone-subtle, var(--xh-bg-subtle-hover));
67
+ --xh-_fg: var(--xh-_tone-fg, var(--xh-fg-default));
68
+ --xh-_bg-pressed: var(--xh-_tone-subtle-active, var(--xh-bg-subtle-active));
69
+ --xh-_fg-pressed: var(--xh-_tone-fg, var(--xh-fg-default));
70
+
71
+ border-color: var(--xh-_tone-border, var(--xh-border-default));
72
+ }
73
+
74
+ [data-scope='toggle'][data-part='root'][data-variant='ghost'] {
75
+ --xh-_bg: transparent;
76
+ --xh-_bg-hover: var(--xh-_tone-subtle, var(--xh-bg-subtle-hover));
77
+ --xh-_fg: var(--xh-_tone-fg, var(--xh-fg-default));
78
+ --xh-_bg-pressed: var(--xh-_tone-subtle-active, var(--xh-bg-subtle-active));
79
+ --xh-_fg-pressed: var(--xh-_tone-fg, var(--xh-fg-default));
80
+ }
81
+
82
+ /* 按下态排在变体之后:两者特异度相同,后写的那条赢,
83
+ 变体只负责给出按下档的取值,切换由这里一处完成 */
84
+ [data-scope='toggle'][data-part='root'][data-state='on'] {
85
+ --xh-_bg: var(--xh-_bg-pressed);
86
+ --xh-_bg-hover: var(--xh-_bg-pressed);
87
+
88
+ color: var(--xh-_fg-pressed);
89
+ }
90
+
91
+ /* —— 尺寸 —— */
92
+ [data-scope='toggle'][data-part='root'][data-size='sm'] {
93
+ block-size: var(--xh-control-h-sm);
94
+ padding-inline: var(--xh-control-px-sm);
95
+ font-size: var(--xh-font-size-sm);
96
+ }
97
+
98
+ [data-scope='toggle'][data-part='root'][data-size='lg'] {
99
+ block-size: var(--xh-control-h-lg);
100
+ padding-inline: var(--xh-control-px-lg);
101
+ font-size: var(--xh-font-size-lg);
102
+ }
103
+ }
package/css/tone.css ADDED
@@ -0,0 +1,85 @@
1
+ /*
2
+ * 语气层:与组件无关,全库共用一份。
3
+ *
4
+ * 三个视觉轴各管一件事,互不干涉:
5
+ * data-variant 形态 solid / subtle / outline / ghost —— 颜色怎么用
6
+ * data-tone 语气 brand / neutral / success / warning / danger / info —— 用哪族颜色
7
+ * data-size 尺寸 sm / md / lg
8
+ *
9
+ * 这里只声明私有槽,各组件的形态规则去消费。所以加一个语气是改这一处 6 条规则,
10
+ * 而不是逐个组件写 6 遍。
11
+ *
12
+ * --xh-_tone-on 是实心底上的前景色,按实测对比度分派而非统一白字:
13
+ * 600 档上白字对 brand 5.08、neutral 7.80、danger 4.83 达标,而 success 3.04、
14
+ * warning 2.70、info 3.47 都不到 4.5,这三族必须配深字。
15
+ *
16
+ * --xh-_tone-shift 是交互态要往哪个方向挪:永远背离前景色走,悬停与按下才不会
17
+ * 反过来压低对比度。白字族往深处挪,深字族往浅处挪。
18
+ *
19
+ * 这两个槽用的都是固定原语而不是 --xh-fg-default:实心底本身是原语色、不随主题翻,
20
+ * 配色若跟着主题翻,暗色下就会变成白字压在浅黄底上(实测对比度 2.70)。
21
+ */
22
+ @layer xihan.components {
23
+ [data-tone] {
24
+ /* 先给一份中性缺省:写了 data-tone 却拼错取值时退成中性,
25
+ 而不是让 --xh-_tone 悬空——悬空会让下面每条 color-mix 变成计算期无效,
26
+ 用它的声明会整条失效,且 var() 兜底也救不回来(槽位是「已声明但无效」)。 */
27
+ --xh-_tone: var(--xh-color-neutral-600);
28
+ /* 装饰性强调(色条、图标这类)用 500 档:600 是给实心底与文字用的强调档,
29
+ 压在细长的色条上偏重。alert 与 toast 原本就用 500,接语气层不该改变它们的观感 */
30
+ --xh-_tone-soft: var(--xh-color-neutral-500);
31
+ --xh-_tone-on: var(--xh-color-neutral-0);
32
+ --xh-_tone-shift: var(--xh-color-neutral-950);
33
+
34
+ /* 交互态与淡底都从主色兑出来,兑的对象是主题色,深浅两态自动跟随 */
35
+ --xh-_tone-hover: color-mix(in oklab, var(--xh-_tone) 88%, var(--xh-_tone-shift));
36
+ --xh-_tone-active: color-mix(in oklab, var(--xh-_tone) 78%, var(--xh-_tone-shift));
37
+ --xh-_tone-subtle: color-mix(in oklab, var(--xh-_tone) 12%, var(--xh-bg-surface));
38
+ --xh-_tone-subtle-hover: color-mix(in oklab, var(--xh-_tone) 20%, var(--xh-bg-surface));
39
+ --xh-_tone-subtle-active: color-mix(in oklab, var(--xh-_tone) 28%, var(--xh-bg-surface));
40
+ --xh-_tone-border: color-mix(in oklab, var(--xh-_tone) 40%, var(--xh-bg-surface));
41
+ /* 在普通背景上表达该语气的文字色:往前景色兑,浅色态变深、深色态变浅 */
42
+ --xh-_tone-fg: color-mix(in oklab, var(--xh-_tone) 72%, var(--xh-fg-default));
43
+ }
44
+
45
+ [data-tone='brand'] {
46
+ --xh-_tone: var(--xh-color-brand-600);
47
+ --xh-_tone-soft: var(--xh-color-brand-500);
48
+ --xh-_tone-on: var(--xh-color-neutral-0);
49
+ --xh-_tone-shift: var(--xh-color-neutral-950);
50
+ }
51
+
52
+ [data-tone='neutral'] {
53
+ --xh-_tone: var(--xh-color-neutral-600);
54
+ --xh-_tone-on: var(--xh-color-neutral-0);
55
+ --xh-_tone-shift: var(--xh-color-neutral-950);
56
+ }
57
+
58
+ [data-tone='danger'] {
59
+ --xh-_tone: var(--xh-color-danger-600);
60
+ --xh-_tone-soft: var(--xh-color-danger-500);
61
+ --xh-_tone-on: var(--xh-color-neutral-0);
62
+ --xh-_tone-shift: var(--xh-color-neutral-950);
63
+ }
64
+
65
+ [data-tone='success'] {
66
+ --xh-_tone: var(--xh-color-success-600);
67
+ --xh-_tone-soft: var(--xh-color-success-500);
68
+ --xh-_tone-on: var(--xh-color-neutral-950);
69
+ --xh-_tone-shift: var(--xh-color-neutral-0);
70
+ }
71
+
72
+ [data-tone='warning'] {
73
+ --xh-_tone: var(--xh-color-warning-600);
74
+ --xh-_tone-soft: var(--xh-color-warning-500);
75
+ --xh-_tone-on: var(--xh-color-neutral-950);
76
+ --xh-_tone-shift: var(--xh-color-neutral-0);
77
+ }
78
+
79
+ [data-tone='info'] {
80
+ --xh-_tone: var(--xh-color-info-600);
81
+ --xh-_tone-soft: var(--xh-color-info-500);
82
+ --xh-_tone-on: var(--xh-color-neutral-950);
83
+ --xh-_tone-shift: var(--xh-color-neutral-0);
84
+ }
85
+ }
@@ -0,0 +1,114 @@
1
+ @layer xihan.components {
2
+ /* 一条控件带:条目之间留固定间距,分隔线再把它们切成语义上的几段。
3
+ 排布只能读 data-orientation——它与 aria-orientation 同源,但样式不该去读 ARIA */
4
+ [data-scope='toolbar'][data-part='root'] {
5
+ /* 尺寸档只换这两个私有槽:条目之间的间距,与整条的内边距 */
6
+ --xh-_toolbar-gap: var(--xh-space-1);
7
+ --xh-_toolbar-p: var(--xh-space-1);
8
+
9
+ display: flex;
10
+ align-items: center;
11
+ gap: var(--xh-toolbar-gap, var(--xh-_toolbar-gap));
12
+ padding-block: var(--xh-toolbar-py, var(--xh-_toolbar-p));
13
+ padding-inline: var(--xh-toolbar-px, var(--xh-_toolbar-p));
14
+ border: var(--xh-stroke-thin) solid var(--xh-toolbar-border, var(--xh-border-default));
15
+ border-radius: var(--xh-toolbar-radius, var(--xh-shape-surface));
16
+ background: var(--xh-toolbar-bg, var(--xh-bg-surface));
17
+ color: var(--xh-toolbar-fg, var(--xh-fg-default));
18
+ }
19
+
20
+ [data-scope='toolbar'][data-part='root'][data-orientation='horizontal'] {
21
+ flex-direction: row;
22
+ }
23
+
24
+ /* 竖排时交叉轴改为拉伸:竖着的一条里每个条目该一样宽,居中会让它们参差不齐 */
25
+ [data-scope='toolbar'][data-part='root'][data-orientation='vertical'] {
26
+ flex-direction: column;
27
+ align-items: stretch;
28
+ }
29
+
30
+ /* —— 尺寸 —— */
31
+ /* 缺省档写在上面的基础规则里。工具条自己不定条目的高度与字号(那归条目的皮肤),
32
+ 这里只换排布尺寸;缺省的 --xh-space-1 已经是控件间距那把尺的小档,容不下三档,故一律取空间尺 */
33
+ [data-scope='toolbar'][data-part='root'][data-size='sm'] {
34
+ --xh-_toolbar-gap: var(--xh-space-0_5);
35
+ --xh-_toolbar-p: var(--xh-space-0_5);
36
+ }
37
+
38
+ [data-scope='toolbar'][data-part='root'][data-size='lg'] {
39
+ --xh-_toolbar-gap: var(--xh-space-2);
40
+ --xh-_toolbar-p: var(--xh-space-2);
41
+ }
42
+
43
+ /* root 会在焦点进条前短暂拿到 tabindex=0,但它的 onFocus 立刻把焦点转投给条目,
44
+ 在它身上画环只会闪一下就跳走,高亮要长在条目上 */
45
+ [data-scope='toolbar'][data-part='root']:focus-visible {
46
+ outline: none;
47
+ }
48
+
49
+ [data-scope='toolbar'][data-part='root'][data-disabled] {
50
+ background: var(--xh-toolbar-bg-disabled, var(--xh-bg-muted));
51
+ color: var(--xh-fg-disabled);
52
+ cursor: not-allowed;
53
+ }
54
+
55
+ /* 给了 display 的角色节点必须补这条:display 声明会盖掉 UA 的 [hidden]{display:none},
56
+ 不补的话作者给节点加 hidden 后它照样显示 */
57
+ [data-scope='toolbar'][data-part='root'][hidden] {
58
+ display: none;
59
+ }
60
+
61
+ /* 分组把一串相关控件收得更紧:组内间距比条上的总间距小,
62
+ "这几个是一伙的"才看得出来 */
63
+ [data-scope='toolbar'][data-part='group'] {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--xh-toolbar-group-gap, var(--xh-space-0_5));
67
+ }
68
+
69
+ [data-scope='toolbar'][data-part='group'][data-orientation='vertical'] {
70
+ flex-direction: column;
71
+ align-items: stretch;
72
+ }
73
+
74
+ [data-scope='toolbar'][data-part='group'][hidden] {
75
+ display: none;
76
+ }
77
+
78
+ /* 条目是作者自己的控件(按钮、切换钮、下拉触发器),观感归它自己的皮肤。
79
+ 这里只补两件与导航直接相关的事:焦点环与禁用光标。
80
+ 刻意不给 display——给了会把条目自身皮肤里的排布盖掉,也就不必补 [hidden] 那条 */
81
+ [data-scope='toolbar'][data-part='item']:focus-visible {
82
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
83
+ outline-offset: var(--xh-ring-offset);
84
+ }
85
+
86
+ /* 集合条目一律 aria-disabled 而非原生 disabled(禁用项仍要能当方向键的起点),
87
+ 所以这里读 data-disabled,写 :disabled 永远不命中 */
88
+ [data-scope='toolbar'][data-part='item'][data-disabled] {
89
+ color: var(--xh-fg-disabled);
90
+ cursor: not-allowed;
91
+ }
92
+
93
+ /* 分隔线画的是它自己的朝向:横排工具条里它是竖线,连接层给的就是 vertical。
94
+ 粗的那一维写死描边宽度,长的那一维交给 align-self 拉伸,
95
+ 沿长边再内缩一点,线才不会顶到工具条的上下边 */
96
+ [data-scope='toolbar'][data-part='separator'] {
97
+ flex: none;
98
+ align-self: stretch;
99
+ border-radius: var(--xh-toolbar-separator-radius, var(--xh-radius-full));
100
+ background: var(--xh-toolbar-separator-color, var(--xh-border-default));
101
+ }
102
+
103
+ [data-scope='toolbar'][data-part='separator'][data-orientation='vertical'] {
104
+ inline-size: var(--xh-toolbar-separator-thickness, var(--xh-stroke-thin));
105
+ margin-block: var(--xh-toolbar-separator-inset, var(--xh-space-1));
106
+ margin-inline: var(--xh-toolbar-separator-gap, var(--xh-space-1));
107
+ }
108
+
109
+ [data-scope='toolbar'][data-part='separator'][data-orientation='horizontal'] {
110
+ block-size: var(--xh-toolbar-separator-thickness, var(--xh-stroke-thin));
111
+ margin-block: var(--xh-toolbar-separator-gap, var(--xh-space-1));
112
+ margin-inline: var(--xh-toolbar-separator-inset, var(--xh-space-1));
113
+ }
114
+ }
@@ -0,0 +1,109 @@
1
+ @layer xihan.components {
2
+ [data-scope='tooltip'][data-part='trigger'] {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ gap: var(--xh-tooltip-trigger-gap, var(--xh-control-gap-sm));
7
+ border: 0;
8
+ background: transparent;
9
+ color: inherit;
10
+ font: inherit;
11
+ cursor: inherit;
12
+ }
13
+
14
+ [data-scope='tooltip'][data-part='trigger']:focus-visible {
15
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
16
+ outline-offset: var(--xh-ring-offset);
17
+ }
18
+
19
+ /* 坐标由定位引擎写在行内 style 上,这里只兜底 SSR 首帧 */
20
+ [data-scope='tooltip'][data-part='positioner'] {
21
+ position: fixed;
22
+ inset-block-start: 0;
23
+ inset-inline-start: 0;
24
+ z-index: var(--xh-layer-tooltip);
25
+ /* 浮层不承载交互:指针必须能穿过去点到底下的东西 */
26
+ pointer-events: none;
27
+ }
28
+
29
+ /* 锚点滚出可视区时连浮层一起隐藏 */
30
+ [data-scope='tooltip'][data-part='positioner'][data-hidden] {
31
+ visibility: hidden;
32
+ }
33
+
34
+ [data-scope='tooltip'][data-part='content'] {
35
+ /* 尺寸档只换这三个私有槽。tooltip 没有 root 部件,两轴都落在浮层树最外层的 content 上,
36
+ 箭头是它的后代,语气槽一并继承 */
37
+ --xh-_tooltip-py: var(--xh-space-1);
38
+ --xh-_tooltip-px: var(--xh-space-2);
39
+ --xh-_tooltip-font-size: var(--xh-font-size-sm);
40
+
41
+ /* 与 positioner 相反:浮层本体要能接指针,指针停在提示上就不该收起
42
+ (长文案需要停留细看)。positioner 仍是 none,空白区域照样点得穿。 */
43
+ pointer-events: auto;
44
+ max-inline-size: var(--xh-tooltip-max-w, var(--xh-overlay-max-w));
45
+ padding-block: var(--xh-tooltip-py, var(--xh-_tooltip-py));
46
+ padding-inline: var(--xh-tooltip-px, var(--xh-_tooltip-px));
47
+ border-radius: var(--xh-tooltip-radius, var(--xh-shape-control));
48
+ /* 实心底与其上的文字色随语气走。没写 data-tone 时两个槽都没被声明,退回原来的取值 */
49
+ background: var(--xh-tooltip-bg, var(--xh-_tone, var(--xh-fg-default)));
50
+ color: var(--xh-tooltip-fg, var(--xh-_tone-on, var(--xh-bg-surface)));
51
+ font-size: var(--xh-tooltip-font-size, var(--xh-_tooltip-font-size));
52
+ line-height: var(--xh-leading-normal);
53
+ box-shadow: var(--xh-tooltip-shadow, var(--xh-elevation-2));
54
+ }
55
+
56
+ [data-scope='tooltip'][data-part='content'][data-state='open'] {
57
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
58
+ }
59
+
60
+ /* —— 尺寸 —— */
61
+ /* 缺省档写在 content 的基础规则里。提示的内边距与字号本就不在 control-* 那把尺上
62
+ (它是浮层不是控件),故按 space / font-size 档上下各挪一级 */
63
+ [data-scope='tooltip'][data-part='content'][data-size='sm'] {
64
+ --xh-_tooltip-py: var(--xh-space-0_5);
65
+ --xh-_tooltip-px: var(--xh-space-1_5);
66
+ --xh-_tooltip-font-size: var(--xh-font-size-xs);
67
+ }
68
+
69
+ [data-scope='tooltip'][data-part='content'][data-size='lg'] {
70
+ --xh-_tooltip-py: var(--xh-space-1_5);
71
+ --xh-_tooltip-px: var(--xh-space-2_5);
72
+ --xh-_tooltip-font-size: var(--xh-font-size-md);
73
+ }
74
+
75
+ /* 箭头是旋转 45° 的方块,按落定朝向贴在 content 对侧边的中点 */
76
+ [data-scope='tooltip'][data-part='arrow'] {
77
+ position: absolute;
78
+ inline-size: var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size));
79
+ block-size: var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size));
80
+ background: var(--xh-tooltip-bg, var(--xh-_tone, var(--xh-fg-default)));
81
+ transform: rotate(45deg);
82
+ }
83
+
84
+ [data-scope='tooltip'][data-part='positioner'][data-placement^='bottom'] [data-scope='tooltip'][data-part='arrow'] {
85
+ inset-block-start: calc(var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / -2);
86
+ inset-inline-start: calc(50% - var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / 2);
87
+ }
88
+
89
+ [data-scope='tooltip'][data-part='positioner'][data-placement^='top'] [data-scope='tooltip'][data-part='arrow'] {
90
+ inset-block-end: calc(var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / -2);
91
+ inset-inline-start: calc(50% - var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / 2);
92
+ }
93
+
94
+ [data-scope='tooltip'][data-part='positioner'][data-placement^='left'] [data-scope='tooltip'][data-part='arrow'] {
95
+ inset-block-start: calc(50% - var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / 2);
96
+ inset-inline-end: calc(var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / -2);
97
+ }
98
+
99
+ [data-scope='tooltip'][data-part='positioner'][data-placement^='right'] [data-scope='tooltip'][data-part='arrow'] {
100
+ inset-block-start: calc(50% - var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / 2);
101
+ inset-inline-start: calc(var(--xh-tooltip-arrow-size, var(--xh-overlay-arrow-size)) / -2);
102
+ }
103
+
104
+ @media (prefers-reduced-motion: reduce) {
105
+ [data-scope='tooltip'][data-part='content'] {
106
+ animation-duration: 0.01ms !important;
107
+ }
108
+ }
109
+ }