@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,146 @@
1
+ @layer xihan.components {
2
+ /* 根节点只把 trigger 与浮层收在一处,不参与定位(坐标系仍由祖先的定位上下文决定) */
3
+ [data-scope='hover-card'][data-part='root'] {
4
+ display: inline-block;
5
+ }
6
+
7
+ [data-scope='hover-card'][data-part='root'][hidden] {
8
+ display: none;
9
+ }
10
+
11
+ [data-scope='hover-card'][data-part='trigger'] {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ gap: var(--xh-hover-card-trigger-gap, var(--xh-control-gap-sm));
16
+ border: 0;
17
+ background: transparent;
18
+ color: inherit;
19
+ font: inherit;
20
+ cursor: inherit;
21
+ }
22
+
23
+ [data-scope='hover-card'][data-part='trigger'][hidden] {
24
+ display: none;
25
+ }
26
+
27
+ [data-scope='hover-card'][data-part='trigger']:focus-visible {
28
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
29
+ outline-offset: var(--xh-ring-offset);
30
+ }
31
+
32
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
33
+ [data-scope='hover-card'][data-part='positioner'] {
34
+ position: fixed;
35
+ z-index: var(--xh-layer-popover);
36
+ /* 空壳不吃指针:trigger 与 content 之间那段间隙要能点得穿,
37
+ 指针穿行的判定全靠 content 自己的进出事件 */
38
+ pointer-events: none;
39
+ }
40
+
41
+ /* 锚点被滚出可视区:整个浮层随之收起 */
42
+ [data-scope='hover-card'][data-part='positioner'][data-hidden] {
43
+ visibility: hidden;
44
+ }
45
+
46
+ /* 与 tooltip 不同:卡片本体承载交互,必须吃指针——
47
+ 指针停在卡片上就不该收起,里面的链接与按钮也要点得到 */
48
+ [data-scope='hover-card'][data-part='content'] {
49
+ /* 尺寸档只换这两个私有槽。size 落在 content 上而非 root:root 是可选部件,
50
+ 面板几何也长在 content 上。语气不接:卡片是内容容器,语气该由里面的内容表达 */
51
+ --xh-_hover-card-py: var(--xh-space-3);
52
+ --xh-_hover-card-px: var(--xh-control-px-md);
53
+ --xh-_hover-card-max-w: var(--xh-overlay-max-w);
54
+
55
+ position: relative;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: var(--xh-hover-card-gap, var(--xh-space-2));
59
+ inline-size: max-content;
60
+ max-inline-size: var(--xh-hover-card-max-w, var(--xh-_hover-card-max-w));
61
+ padding-block: var(--xh-hover-card-py, var(--xh-_hover-card-py));
62
+ padding-inline: var(--xh-hover-card-px, var(--xh-_hover-card-px));
63
+ border: var(--xh-stroke-thin) solid var(--xh-hover-card-border, var(--xh-border-default));
64
+ border-radius: var(--xh-hover-card-radius, var(--xh-shape-surface));
65
+ background: var(--xh-hover-card-bg, var(--xh-bg-surface));
66
+ color: var(--xh-hover-card-fg, var(--xh-fg-default));
67
+ font-size: var(--xh-hover-card-font-size, var(--xh-text-body-size));
68
+ line-height: var(--xh-leading-normal);
69
+ box-shadow: var(--xh-hover-card-shadow, var(--xh-elevation-2));
70
+ pointer-events: auto;
71
+ }
72
+
73
+ /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
74
+ [data-scope='hover-card'][data-part='content'][hidden] {
75
+ display: none;
76
+ }
77
+
78
+ /* 卡片是可编程聚焦的容器(tabindex=-1),焦点环留给里面真正的可聚焦元素 */
79
+ [data-scope='hover-card'][data-part='content']:focus-visible {
80
+ outline: none;
81
+ }
82
+
83
+ [data-scope='hover-card'][data-part='content'][data-state='open'] {
84
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
85
+ }
86
+
87
+ /* —— 尺寸 —— */
88
+ /* 缺省档写在 content 的基础规则里;行内内缩本就取 control-px 那把尺,上下各挪一档,
89
+ 纵向内缩按 space 档同步 */
90
+ [data-scope='hover-card'][data-part='content'][data-size='sm'] {
91
+ --xh-_hover-card-py: var(--xh-space-2);
92
+ --xh-_hover-card-px: var(--xh-control-px-sm);
93
+ --xh-_hover-card-max-w: var(--xh-overlay-max-w-sm);
94
+ }
95
+
96
+ [data-scope='hover-card'][data-part='content'][data-size='lg'] {
97
+ --xh-_hover-card-py: var(--xh-space-4);
98
+ --xh-_hover-card-px: var(--xh-control-px-lg);
99
+ --xh-_hover-card-max-w: var(--xh-overlay-max-w-lg);
100
+ }
101
+
102
+ /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
103
+ 引擎不产出箭头偏移,因此沿交叉轴固定居中 */
104
+ [data-scope='hover-card'][data-part='arrow'] {
105
+ position: absolute;
106
+ inline-size: var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size));
107
+ block-size: var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size));
108
+ border: var(--xh-stroke-thin) solid var(--xh-hover-card-border, var(--xh-border-default));
109
+ background: var(--xh-hover-card-bg, var(--xh-bg-surface));
110
+ rotate: 45deg;
111
+ }
112
+
113
+ [data-scope='hover-card'][data-part='arrow'][data-placement^='bottom'] {
114
+ inset-block-start: calc(var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / -2);
115
+ inset-inline-start: calc(50% - var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / 2);
116
+ border-block-end: 0;
117
+ border-inline-end: 0;
118
+ }
119
+
120
+ [data-scope='hover-card'][data-part='arrow'][data-placement^='top'] {
121
+ inset-block-end: calc(var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / -2);
122
+ inset-inline-start: calc(50% - var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / 2);
123
+ border-block-start: 0;
124
+ border-inline-start: 0;
125
+ }
126
+
127
+ [data-scope='hover-card'][data-part='arrow'][data-placement^='right'] {
128
+ inset-inline-start: calc(var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / -2);
129
+ inset-block-start: calc(50% - var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / 2);
130
+ border-block-start: 0;
131
+ border-inline-end: 0;
132
+ }
133
+
134
+ [data-scope='hover-card'][data-part='arrow'][data-placement^='left'] {
135
+ inset-inline-end: calc(var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / -2);
136
+ inset-block-start: calc(50% - var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size)) / 2);
137
+ border-block-end: 0;
138
+ border-inline-start: 0;
139
+ }
140
+
141
+ @media (prefers-reduced-motion: reduce) {
142
+ [data-scope='hover-card'][data-part='content'] {
143
+ animation-duration: 0.01ms !important;
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,64 @@
1
+ @layer xihan.components {
2
+ /* 定直径的图标底座:图元在正中,底色、描边与形状由三轴决定。
3
+ 直径与图元直径分两个槽位,底座换档时里面的图元跟着一起换 */
4
+ [data-scope='icon-wrapper'][data-part='root'] {
5
+ /* 私有槽位:底座直径与图元直径,换档只改这两个 */
6
+ --xh-_icon-wrapper-size: var(--xh-icon-wrapper-size, var(--xh-control-h-md));
7
+
8
+ /* 流给里面的图元;图元自己写了档位时仍按自己的档走 */
9
+ --xh-icon-size: var(--xh-icon-wrapper-glyph-size, var(--xh-glyph-size-md));
10
+
11
+ display: inline-flex;
12
+ flex: none;
13
+ align-items: center;
14
+ justify-content: center;
15
+ inline-size: var(--xh-_icon-wrapper-size);
16
+ block-size: var(--xh-_icon-wrapper-size);
17
+ border: var(--xh-stroke-thin) solid transparent;
18
+ border-radius: var(--xh-icon-wrapper-radius, var(--xh-shape-pill));
19
+ background: var(--xh-icon-wrapper-bg, var(--xh-bg-subtle));
20
+ color: var(--xh-icon-wrapper-fg, var(--xh-fg-default));
21
+ line-height: var(--xh-leading-none);
22
+ }
23
+
24
+ /* 给了 display 的角色节点必须补这条:display 声明会盖掉 UA 的 [hidden]{display:none},
25
+ 不补的话作者给节点加 hidden 后它照样显示 */
26
+ [data-scope='icon-wrapper'][data-part='root'][hidden] {
27
+ display: none;
28
+ }
29
+
30
+ /* —— 形态 —— */
31
+ /* 形态只决定颜色怎么用;用哪族颜色由 data-tone 决定,没写 tone 就沿用原来的品牌/中性配色 */
32
+ [data-scope='icon-wrapper'][data-part='root'][data-variant='solid'] {
33
+ background: var(--xh-_tone, var(--xh-bg-brand));
34
+ color: var(--xh-_tone-on, var(--xh-fg-on-brand));
35
+ }
36
+
37
+ [data-scope='icon-wrapper'][data-part='root'][data-variant='subtle'] {
38
+ background: var(--xh-_tone-subtle, var(--xh-bg-subtle));
39
+ color: var(--xh-_tone-fg, var(--xh-fg-default));
40
+ }
41
+
42
+ [data-scope='icon-wrapper'][data-part='root'][data-variant='outline'] {
43
+ border-color: var(--xh-_tone-border, var(--xh-border-default));
44
+ background: transparent;
45
+ color: var(--xh-_tone-fg, var(--xh-fg-default));
46
+ }
47
+
48
+ [data-scope='icon-wrapper'][data-part='root'][data-variant='ghost'] {
49
+ background: transparent;
50
+ color: var(--xh-_tone-fg, var(--xh-fg-default));
51
+ }
52
+
53
+ /* —— 尺寸 —— */
54
+ /* 缺省档写在上面的基础规则里 */
55
+ [data-scope='icon-wrapper'][data-part='root'][data-size='sm'] {
56
+ --xh-_icon-wrapper-size: var(--xh-icon-wrapper-size, var(--xh-control-h-sm));
57
+ --xh-icon-size: var(--xh-icon-wrapper-glyph-size, var(--xh-glyph-size-sm));
58
+ }
59
+
60
+ [data-scope='icon-wrapper'][data-part='root'][data-size='lg'] {
61
+ --xh-_icon-wrapper-size: var(--xh-icon-wrapper-size, var(--xh-control-h-lg));
62
+ --xh-icon-size: var(--xh-icon-wrapper-glyph-size, var(--xh-glyph-size-lg));
63
+ }
64
+ }
package/css/icon.css ADDED
@@ -0,0 +1,54 @@
1
+ @layer xihan.components {
2
+ /*
3
+ * 根 <svg>。直径、描边粗细、行内位置都由这里给,记录只带几何。
4
+ * 不写 fill / stroke:记录里那两条呈现属性取 currentColor,改这里会把
5
+ * 描边图标与实心图标一起改掉;皮肤只给 color,配色随上下文流下来。
6
+ */
7
+ [data-scope='icon'][data-part='root'] {
8
+ display: inline-block;
9
+ flex: none;
10
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-md));
11
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-md));
12
+
13
+ /* 替换元素的基线就是自身下边缘,落在文字基线上时整块图形都在基线以上、看着偏高。
14
+ 往下挪一小段,单位是 em,跟着相邻文字的字号一起缩放 */
15
+ vertical-align: var(--xh-icon-shift, var(--xh-glyph-baseline-shift));
16
+ color: var(--xh-icon-fg, currentColor);
17
+
18
+ /* 记录把 stroke-width 写成根上的呈现属性,呈现属性的层叠优先级低于任何 CSS 规则,
19
+ 这条与下面两档因此能接管粗细,再沿继承落到每个图元上 */
20
+ stroke-width: var(--xh-icon-stroke, var(--xh-glyph-stroke-regular));
21
+ }
22
+
23
+ /* 声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
24
+ [data-scope='icon'][data-part='root'][hidden] {
25
+ display: none;
26
+ }
27
+
28
+ /* 三档直径,缺省档写在上面的基础规则里 */
29
+ [data-scope='icon'][data-part='root'][data-size='sm'] {
30
+ inline-size: var(--xh-glyph-size-sm);
31
+ block-size: var(--xh-glyph-size-sm);
32
+ }
33
+
34
+ [data-scope='icon'][data-part='root'][data-size='lg'] {
35
+ inline-size: var(--xh-glyph-size-lg);
36
+ block-size: var(--xh-glyph-size-lg);
37
+ }
38
+
39
+ /* 三档描边,缺省档同样写在基础规则里 */
40
+ [data-scope='icon'][data-part='root'][data-weight='light'] {
41
+ stroke-width: var(--xh-glyph-stroke-light);
42
+ }
43
+
44
+ [data-scope='icon'][data-part='root'][data-weight='bold'] {
45
+ stroke-width: var(--xh-glyph-stroke-bold);
46
+ }
47
+
48
+ /* 语气:图标没有底色,语气只落在前景上,取普通背景上表达该语气的那档文字色。
49
+ 选择器上带 [data-tone] 是必需的:语气槽会沿继承流下来,不限定在写了语气的
50
+ 那个节点上,放在语气容器里的装饰图标会跟着一起改色 */
51
+ [data-scope='icon'][data-part='root'][data-tone] {
52
+ color: var(--xh-icon-fg, var(--xh-_tone-fg, currentColor));
53
+ }
54
+ }
package/css/image.css ADDED
@@ -0,0 +1,45 @@
1
+ @layer xihan.components {
2
+ /* 尺寸交给三个变量而不是写死:这个组件既要能当封面图,也要能当头图与缩略图。
3
+ 给了 --xh-image-ratio 才有确定高度,回退内容与图片在同一个盒子里换人时不会跳。 */
4
+ [data-scope='image'][data-part='root'] {
5
+ position: relative;
6
+ display: block;
7
+ overflow: hidden;
8
+ inline-size: var(--xh-image-w, 100%);
9
+ block-size: var(--xh-image-h, auto);
10
+ aspect-ratio: var(--xh-image-ratio, auto);
11
+ border-radius: var(--xh-image-radius, var(--xh-radius-md));
12
+ background: var(--xh-image-bg, var(--xh-bg-subtle));
13
+ }
14
+
15
+ /* 声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
16
+ [data-scope='image'][data-part='root'][hidden] {
17
+ display: none;
18
+ }
19
+
20
+ /* 不声明 display:加载完成前的 hidden 直接交给浏览器的 [hidden] 规则收起 */
21
+ [data-scope='image'][data-part='image'] {
22
+ inline-size: 100%;
23
+ block-size: 100%;
24
+ object-fit: var(--xh-image-fit, cover);
25
+ }
26
+
27
+ [data-scope='image'][data-part='fallback'] {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ inline-size: 100%;
32
+ block-size: 100%;
33
+ /* 没给 ratio 时根盒子高度是 auto,回退内容自己撑出一个不塌的高度 */
34
+ min-block-size: var(--xh-image-fallback-min-h, var(--xh-control-h-lg));
35
+ color: var(--xh-image-fallback-fg, var(--xh-fg-muted));
36
+ font-size: var(--xh-image-fallback-font-size, var(--xh-font-size-sm));
37
+ line-height: var(--xh-leading-none);
38
+ user-select: none;
39
+ }
40
+
41
+ /* display 会盖掉 hidden 属性的 UA 规则:延迟窗口内与加载完成后都靠这条收起 */
42
+ [data-scope='image'][data-part='fallback'][hidden] {
43
+ display: none;
44
+ }
45
+ }
@@ -0,0 +1,22 @@
1
+ @layer xihan.components {
2
+ /*
3
+ * 列表外壳这一层刻意不给盒模型:滚的是页面还是作者自己的 overflow 容器、列表长什么样,
4
+ * 都归作者的布局管,组件只往它身上挂 data-state / data-loading / data-disabled 供作者取用。
5
+ * 一旦声明 display,UA 的 [hidden]{display:none} 就压不住了,故连 display 也不写。
6
+ *
7
+ * 哨兵是纯机制,不是可见元素:给它一条最细的高度让它有个能被观察到的盒子,
8
+ * 高度为 0 的元素在部分实现里交叠比恒为 0,永远等不到进入。
9
+ * 不吃指针:它紧贴在列表末尾,吃了指针就会挡住最后一条的点击。
10
+ */
11
+ [data-scope='infinite-scroll'][data-part='sentinel'] {
12
+ display: block;
13
+ inline-size: 100%;
14
+ block-size: var(--xh-infinite-scroll-sentinel-size, var(--xh-stroke-thin));
15
+ pointer-events: none;
16
+ }
17
+
18
+ /* 声明过 display 就盖掉了 UA 的 [hidden]{display:none},作者要藏哨兵时得自己补回来 */
19
+ [data-scope='infinite-scroll'][data-part='sentinel'][hidden] {
20
+ display: none;
21
+ }
22
+ }
package/css/layers.css ADDED
@@ -0,0 +1,2 @@
1
+ /* 库层序单一真源:级联顺序由首次声明决定,与 @import 顺序解耦。 */
2
+ @layer xihan.reset, xihan.tokens, xihan.motion, xihan.components, xihan.overrides;
package/css/layout.css ADDED
@@ -0,0 +1,169 @@
1
+ @layer xihan.components {
2
+ /* 页面骨架排成三行两列:头与脚各横贯一整行,侧栏与内容并排占中间那一行。
3
+ 缺哪一段,对应的行/列就没有条目,宽高自动收成 0,不必为"少写一段"另立规则。
4
+
5
+ 侧栏两档宽度与头的高度声明在根上:各段自己读这些槽,换档只改这里。
6
+ 头的高度之所以也归到根上:头吸顶时头这一行的轨道要按同一个值定高,两处必须同源。 */
7
+ [data-scope='layout'][data-part='root'] {
8
+ display: grid;
9
+ grid-template-areas:
10
+ 'header header'
11
+ 'sider content'
12
+ 'footer footer';
13
+ grid-template-columns: auto minmax(0, 1fr);
14
+ grid-template-rows: auto minmax(0, 1fr) auto;
15
+ background: var(--xh-layout-bg, var(--xh-bg-canvas));
16
+ color: var(--xh-layout-fg, var(--xh-fg-default));
17
+ --xh-_layout-sider-w: var(--xh-layout-sider-width, 15rem);
18
+ --xh-_layout-sider-collapsed-w: var(--xh-layout-sider-collapsed-width, 4rem);
19
+ --xh-_layout-header-h: var(--xh-layout-header-h, 3.5rem);
20
+ /* 侧栏吸附时钉在哪条线上:缺省贴滚动容器上沿,头也吸顶时改成让开头的高度 */
21
+ --xh-_layout-sider-offset: 0px;
22
+ }
23
+
24
+ /* 侧栏挂到行尾:两列对调,内容在前 */
25
+ [data-scope='layout'][data-part='root'][data-sider-placement='end'] {
26
+ grid-template-areas:
27
+ 'header header'
28
+ 'content sider'
29
+ 'footer footer';
30
+ grid-template-columns: minmax(0, 1fr) auto;
31
+ }
32
+
33
+ /* —— 各段 —— */
34
+ [data-scope='layout'][data-part='header'] {
35
+ display: flex;
36
+ grid-area: header;
37
+ align-items: center;
38
+ gap: var(--xh-space-3);
39
+ block-size: var(--xh-_layout-header-h);
40
+ padding-inline: var(--xh-space-4);
41
+ background: var(--xh-layout-header-bg, var(--xh-bg-surface));
42
+ }
43
+
44
+ /* 宽度过渡在这里声明一次,展开与折叠两档、以及 props 直接写进内联的宽度,走的都是这一条 */
45
+ [data-scope='layout'][data-part='sider'] {
46
+ grid-area: sider;
47
+ inline-size: var(--xh-_layout-sider-w);
48
+ overflow: hidden;
49
+ padding: var(--xh-space-3);
50
+ background: var(--xh-layout-sider-bg, var(--xh-bg-subtle));
51
+ transition: inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
52
+ }
53
+
54
+ /* 折起来时不换行:窄到放不下的那几个字直接被 overflow 裁掉,不会挤成一列竖排 */
55
+ [data-scope='layout'][data-part='sider'][data-state='collapsed'] {
56
+ inline-size: var(--xh-_layout-sider-collapsed-w);
57
+ white-space: nowrap;
58
+ }
59
+
60
+ [data-scope='layout'][data-part='content'] {
61
+ grid-area: content;
62
+ /* 内容列已是 minmax(0, 1fr),这里再声明一次,宽表格与长串文本才撑不破列 */
63
+ min-inline-size: 0;
64
+ padding: var(--xh-space-4);
65
+ }
66
+
67
+ [data-scope='layout'][data-part='footer'] {
68
+ grid-area: footer;
69
+ padding: var(--xh-space-3) var(--xh-space-4);
70
+ background: var(--xh-layout-footer-bg, var(--xh-bg-surface));
71
+ color: var(--xh-fg-muted);
72
+ font-size: var(--xh-font-size-sm);
73
+ }
74
+
75
+ /* —— 折叠把手 —— */
76
+ [data-scope='layout'][data-part='sider-trigger'] {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ gap: var(--xh-control-gap-sm);
81
+ block-size: var(--xh-control-h-sm);
82
+ padding-inline: var(--xh-control-px-sm);
83
+ border: 0;
84
+ border-radius: var(--xh-layout-sider-trigger-radius, var(--xh-shape-control));
85
+ background: var(--xh-layout-sider-trigger-bg, transparent);
86
+ color: var(--xh-layout-sider-trigger-fg, var(--xh-fg-default));
87
+ font-size: var(--xh-font-size-sm);
88
+ line-height: var(--xh-leading-none);
89
+ cursor: pointer;
90
+ }
91
+
92
+ [data-scope='layout'][data-part='sider-trigger']:hover {
93
+ background: var(--xh-layout-sider-trigger-bg-hover, var(--xh-bg-subtle-hover));
94
+ }
95
+
96
+ [data-scope='layout'][data-part='sider-trigger']:focus-visible {
97
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
98
+ outline-offset: var(--xh-ring-offset);
99
+ }
100
+
101
+ /* —— 分隔线 —— */
102
+ /* 后代选择器带上 data-scope:骨架里装着整页内容,光凭 data-part 会连里面别的组件同名段一起画上线 */
103
+ [data-scope='layout'][data-part='root'][data-bordered] [data-scope='layout'][data-part='header'] {
104
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-layout-border, var(--xh-border-default));
105
+ }
106
+
107
+ [data-scope='layout'][data-part='root'][data-bordered] [data-scope='layout'][data-part='footer'] {
108
+ border-block-start: var(--xh-stroke-thin) solid var(--xh-layout-border, var(--xh-border-default));
109
+ }
110
+
111
+ /* 侧栏只在挨着内容的那一边画线,另一边贴着骨架外沿,画了就是一条多出来的边框 */
112
+ [data-scope='layout'][data-part='root'][data-bordered] [data-scope='layout'][data-part='sider'][data-placement='start'] {
113
+ border-inline-end: var(--xh-stroke-thin) solid var(--xh-layout-border, var(--xh-border-default));
114
+ }
115
+
116
+ [data-scope='layout'][data-part='root'][data-bordered] [data-scope='layout'][data-part='sider'][data-placement='end'] {
117
+ border-inline-start: var(--xh-stroke-thin) solid var(--xh-layout-border, var(--xh-border-default));
118
+ }
119
+
120
+ /* —— 固定定位 —— */
121
+ /* 滚动容器的分工:滚动发生在骨架外面——root 的高度跟着内容长,页面(或作者套在外层的
122
+ 那个定高盒子)才是滚动容器;content 不改成滚动容器。
123
+ 两条理由:一是 content 自己滚的话头与侧栏本来就不会动,两个开关也就没有区别了;
124
+ 二是 sticky 的行程被自己的包含块框住,root 一旦定高,行里的轨道就不再跟着内容长,
125
+ 钉住的那一段滚过一屏后会被包含块拽走。
126
+ 由此还有一条:给 root 写 overflow(比如为了圆角裁剪)会让它自己变成一个永远滚不动的
127
+ 滚动容器,里面的 sticky 随之成为空操作——要固定就把 overflow 挪到 root 外面那层。 */
128
+
129
+ /* 头这一行改成定高:下面那条让头跨满所有行,行 1 就没有条目撑着它了 */
130
+ [data-scope='layout'][data-part='root'][data-header-fixed] {
131
+ grid-template-rows: var(--xh-_layout-header-h) minmax(0, 1fr) auto;
132
+ }
133
+
134
+ /* 跨满所有行只为把包含块撑到整副骨架那么高:头若只占行 1,包含块与它自己一样高,
135
+ sticky 的行程就是 0,钉住与不钉住看不出区别。
136
+ 起始缘对齐把它按回行 1 的位置,视觉上与不固定时一样;高度被作者改成 auto 时也不会被拉满整块区域。
137
+ 头的背景(--xh-layout-header-bg)要留实色,否则滚过去的正文会从头底下透出来。 */
138
+ [data-scope='layout'][data-part='header'][data-fixed] {
139
+ position: sticky;
140
+ z-index: var(--xh-layout-header-layer, var(--xh-layer-sticky));
141
+ grid-row: 1 / -1;
142
+ align-self: start;
143
+ inset-block-start: 0;
144
+ }
145
+
146
+ /* 头也吸顶时侧栏钉在头的下沿,两段不叠在一起 */
147
+ [data-scope='layout'][data-part='root'][data-header-fixed][data-sider-fixed] {
148
+ --xh-_layout-sider-offset: var(--xh-_layout-header-h);
149
+ }
150
+
151
+ /* 侧栏保持默认的拉伸对齐,只加一道高度上限:内容短时它照旧填满整列,内容长过一屏时它收成
152
+ 可视区那么高,短出来的那一段就是 sticky 的行程。
153
+ 上限量的是滚动容器可视区的高度,缺省按视口算;滚动容器不是视口时由作者把那个高度写进
154
+ --xh-layout-scrollport-h。视口取 dvh:移动端地址栏收放会改变可视高度,dvh 跟着变。
155
+ 侧栏自己的条目多到装不下时在内部滚,横轴仍归上面那条 overflow: hidden 裁,
156
+ 折叠态靠它把放不下的字裁掉;逻辑属性 overflow-block 尚无可用实现,写物理轴。 */
157
+ [data-scope='layout'][data-part='sider'][data-fixed] {
158
+ position: sticky;
159
+ inset-block-start: var(--xh-_layout-sider-offset);
160
+ max-block-size: calc(var(--xh-layout-scrollport-h, 100dvh) - var(--xh-_layout-sider-offset));
161
+ overflow-y: auto;
162
+ }
163
+
164
+ @media (prefers-reduced-motion: reduce) {
165
+ [data-scope='layout'][data-part='sider'] {
166
+ transition: none;
167
+ }
168
+ }
169
+ }
package/css/list.css ADDED
@@ -0,0 +1,117 @@
1
+ @layer xihan.components {
2
+ [data-scope='list'][data-part='root'] {
3
+ /* 一个尺寸档只换这几个私有槽,条目与两行文字都从根上继承它们,
4
+ 换档不必给每个部件各写一条选择器 */
5
+ --xh-_list-item-py: var(--xh-space-3);
6
+ --xh-_list-item-px: var(--xh-space-4);
7
+ --xh-_list-item-gap: var(--xh-space-3);
8
+ --xh-_list-title-size: var(--xh-font-size-md);
9
+ --xh-_list-description-size: var(--xh-font-size-sm);
10
+
11
+ display: flex;
12
+ flex-direction: column;
13
+ /* 根可能写成 ul/ol,UA 的项目符号与左缩进在这里清掉:换哪个标签都排成同一个样子 */
14
+ margin: 0;
15
+ padding: 0;
16
+ list-style: none;
17
+ color: var(--xh-list-fg, var(--xh-fg-default));
18
+ }
19
+
20
+ /* 上面声明了 display,会盖掉 UA 的 [hidden] 规则,收起态要在这里接回来 */
21
+ [data-scope='list'][data-part='root'][hidden] {
22
+ display: none;
23
+ }
24
+
25
+ /* —— 尺寸 —— */
26
+ [data-scope='list'][data-part='root'][data-size='sm'] {
27
+ --xh-_list-item-py: var(--xh-space-2);
28
+ --xh-_list-item-px: var(--xh-space-3);
29
+ --xh-_list-item-gap: var(--xh-space-2);
30
+ --xh-_list-title-size: var(--xh-font-size-sm);
31
+ --xh-_list-description-size: var(--xh-font-size-xs);
32
+ }
33
+
34
+ [data-scope='list'][data-part='root'][data-size='lg'] {
35
+ --xh-_list-item-py: var(--xh-space-4);
36
+ --xh-_list-item-px: var(--xh-space-5);
37
+ --xh-_list-item-gap: var(--xh-space-4);
38
+ --xh-_list-title-size: var(--xh-font-size-lg);
39
+ }
40
+
41
+ /* —— 外框 —— */
42
+ [data-scope='list'][data-part='root'][data-bordered] {
43
+ border: var(--xh-stroke-thin) solid var(--xh-list-border, var(--xh-border-default));
44
+ border-radius: var(--xh-list-radius, var(--xh-shape-surface));
45
+ background: var(--xh-list-bg, var(--xh-bg-surface));
46
+ /* 首尾条目的悬停底色要被圆角裁住 */
47
+ overflow: hidden;
48
+ }
49
+
50
+ /* —— 条目 —— */
51
+ [data-scope='list'][data-part='item'] {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: var(--xh-list-item-gap, var(--xh-_list-item-gap));
55
+ padding-block: var(--xh-list-item-py, var(--xh-_list-item-py));
56
+ padding-inline: var(--xh-list-item-px, var(--xh-_list-item-px));
57
+ }
58
+
59
+ /* 分隔线画在后一条的上边,条与条之间才只有一条;第一条没有上邻居。
60
+ 只认自己的直接子条目,套在条目里的另一份列表由它自己的根管 */
61
+ [data-scope='list'][data-part='root'][data-split]
62
+ > [data-scope='list'][data-part='item']
63
+ + [data-scope='list'][data-part='item'] {
64
+ border-block-start: var(--xh-stroke-thin) solid var(--xh-list-border, var(--xh-border-subtle));
65
+ }
66
+
67
+ [data-scope='list'][data-part='root'][data-hoverable] > [data-scope='list'][data-part='item'] {
68
+ transition: background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
69
+ }
70
+
71
+ /* 悬停换底色:只在能用指针的设备上生效,触摸端不留一个按不掉的悬停态 */
72
+ @media (hover: hover) {
73
+ [data-scope='list'][data-part='root'][data-hoverable] > [data-scope='list'][data-part='item']:hover {
74
+ background: var(--xh-list-item-bg-hover, var(--xh-bg-subtle-hover));
75
+ }
76
+ }
77
+
78
+ /* —— 条目内的四个位 —— */
79
+ /* 媒体位不参与伸缩,宽度收到内容 */
80
+ [data-scope='list'][data-part='item-media'] {
81
+ display: flex;
82
+ flex: none;
83
+ align-items: center;
84
+ justify-content: center;
85
+ }
86
+
87
+ [data-scope='list'][data-part='item-content'] {
88
+ display: flex;
89
+ flex: 1 1 auto;
90
+ flex-direction: column;
91
+ gap: var(--xh-list-content-gap, var(--xh-space-1));
92
+ /* flex 项默认不肯收到内容以下,这行让长文本能被压窄 */
93
+ min-inline-size: 0;
94
+ }
95
+
96
+ [data-scope='list'][data-part='item-title'] {
97
+ color: var(--xh-list-title-fg, var(--xh-fg-default));
98
+ font-size: var(--xh-list-title-font-size, var(--xh-_list-title-size));
99
+ font-weight: var(--xh-list-title-font-weight, var(--xh-font-weight-medium));
100
+ line-height: var(--xh-leading-tight);
101
+ }
102
+
103
+ [data-scope='list'][data-part='item-description'] {
104
+ color: var(--xh-list-description-fg, var(--xh-fg-muted));
105
+ font-size: var(--xh-list-description-font-size, var(--xh-_list-description-size));
106
+ line-height: var(--xh-leading-normal);
107
+ }
108
+
109
+ /* 操作位推到条目末尾,宽度收到内容 */
110
+ [data-scope='list'][data-part='item-action'] {
111
+ display: flex;
112
+ flex: none;
113
+ align-items: center;
114
+ gap: var(--xh-list-action-gap, var(--xh-space-2));
115
+ margin-inline-start: auto;
116
+ }
117
+ }