@wsxjs/wsx-base-components 0.0.16 → 0.0.18

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 (42) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +28 -28
  3. package/dist/index.cjs +14 -2
  4. package/dist/index.js +4971 -2032
  5. package/dist/style.css +1 -1
  6. package/package.json +16 -7
  7. package/src/{XyButton.css → Button.css} +1 -1
  8. package/src/{XyButton.wsx → Button.wsx} +18 -9
  9. package/src/ButtonGroup.css +30 -0
  10. package/src/{XyButtonGroup.wsx → ButtonGroup.wsx} +26 -14
  11. package/src/CodeBlock.css +275 -0
  12. package/src/CodeBlock.types.ts +25 -0
  13. package/src/CodeBlock.wsx +296 -0
  14. package/src/ColorPicker.wsx +6 -5
  15. package/src/Combobox.css +254 -0
  16. package/src/Combobox.types.ts +32 -0
  17. package/src/Combobox.wsx +352 -0
  18. package/src/Dropdown.css +178 -0
  19. package/src/Dropdown.types.ts +28 -0
  20. package/src/Dropdown.wsx +221 -0
  21. package/src/LanguageSwitcher.css +148 -0
  22. package/src/LanguageSwitcher.wsx +190 -0
  23. package/src/OverflowDetector.ts +169 -0
  24. package/src/ResponsiveNav.css +555 -0
  25. package/src/ResponsiveNav.types.ts +30 -0
  26. package/src/ResponsiveNav.wsx +450 -0
  27. package/src/SvgIcon.wsx +2 -2
  28. package/src/index.ts +17 -9
  29. package/src/types/wsx.d.ts +4 -3
  30. package/src/ReactiveCounter.css +0 -304
  31. package/src/ReactiveCounter.wsx +0 -231
  32. package/src/SimpleReactiveDemo.wsx +0 -59
  33. package/src/SvgDemo.wsx +0 -241
  34. package/src/TodoList.css +0 -197
  35. package/src/TodoList.wsx +0 -264
  36. package/src/TodoListLight.css +0 -198
  37. package/src/TodoListLight.wsx +0 -263
  38. package/src/UserProfile.css +0 -146
  39. package/src/UserProfile.wsx +0 -247
  40. package/src/UserProfileLight.css +0 -146
  41. package/src/UserProfileLight.wsx +0 -256
  42. package/src/XyButtonGroup.css +0 -30
@@ -0,0 +1,169 @@
1
+ /**
2
+ * OverflowDetector
3
+ * 可复用的 overflow 检测工具类
4
+ * 用于检测容器中的子元素是否超出可用空间,并计算哪些元素应该可见/隐藏
5
+ */
6
+
7
+ export interface OverflowDetectorConfig {
8
+ /** 容器元素 */
9
+ container: HTMLElement;
10
+ /** 子元素列表(按顺序) */
11
+ items: HTMLElement[];
12
+ /** 子元素之间的间距(px) */
13
+ gap?: number;
14
+ /** 容器内其他固定元素占用的宽度(px) */
15
+ reservedWidth?: number;
16
+ /** overflow 按钮的宽度(px,如果存在) */
17
+ overflowButtonWidth?: number;
18
+ /** 容器内边距(px) */
19
+ padding?: number;
20
+ /** 是否至少保留一个可见项 */
21
+ minVisibleItems?: number;
22
+ }
23
+
24
+ export interface OverflowResult {
25
+ /** 可见项的索引数组 */
26
+ visibleIndices: number[];
27
+ /** 隐藏项的索引数组 */
28
+ hiddenIndices: number[];
29
+ /** 是否需要显示 overflow 按钮 */
30
+ needsOverflow: boolean;
31
+ }
32
+
33
+ /**
34
+ * OverflowDetector 类
35
+ * 提供静态方法用于检测和处理 overflow
36
+ */
37
+ export class OverflowDetector {
38
+ /**
39
+ * 检测 overflow 并计算可见/隐藏项
40
+ * @param config 配置对象
41
+ * @returns OverflowResult
42
+ */
43
+ static detect(config: OverflowDetectorConfig): OverflowResult {
44
+ const {
45
+ container,
46
+ items,
47
+ gap = 16,
48
+ reservedWidth = 0,
49
+ overflowButtonWidth = 0,
50
+ padding = 0,
51
+ minVisibleItems = 1,
52
+ } = config;
53
+
54
+ if (!container || items.length === 0) {
55
+ return {
56
+ visibleIndices: [],
57
+ hiddenIndices: [],
58
+ needsOverflow: false,
59
+ };
60
+ }
61
+
62
+ const containerWidth = container.offsetWidth;
63
+ const availableWidth = containerWidth - reservedWidth - padding * 2;
64
+
65
+ // 第一步:尝试显示所有项(不考虑 overflow 按钮)
66
+ let totalWidth = 0;
67
+ const allVisibleIndices: number[] = [];
68
+ const allHiddenIndices: number[] = [];
69
+
70
+ for (let i = 0; i < items.length; i++) {
71
+ const item = items[i];
72
+ if (!item || item.offsetWidth === 0) {
73
+ // 如果元素不存在或宽度为0,先假设可见
74
+ allVisibleIndices.push(i);
75
+ continue;
76
+ }
77
+
78
+ const itemWidth = item.offsetWidth + (i > 0 ? gap : 0);
79
+ if (totalWidth + itemWidth <= availableWidth) {
80
+ totalWidth += itemWidth;
81
+ allVisibleIndices.push(i);
82
+ } else {
83
+ allHiddenIndices.push(i);
84
+ }
85
+ }
86
+
87
+ // 如果所有项都能显示,不需要 overflow
88
+ if (allHiddenIndices.length === 0) {
89
+ return {
90
+ visibleIndices: allVisibleIndices,
91
+ hiddenIndices: [],
92
+ needsOverflow: false,
93
+ };
94
+ }
95
+
96
+ // 第二步:如果有隐藏项,需要显示 overflow 按钮
97
+ // 重新计算可用宽度(减去 overflow 按钮宽度)
98
+ const availableWidthWithOverflow = availableWidth - overflowButtonWidth - gap;
99
+
100
+ // 第三步:最大化可见项数量
101
+ totalWidth = 0;
102
+ const visibleIndices: number[] = [];
103
+ const hiddenIndices: number[] = [];
104
+
105
+ for (let i = 0; i < items.length; i++) {
106
+ const item = items[i];
107
+ if (!item || item.offsetWidth === 0) {
108
+ // 如果元素不存在或宽度为0,先假设可见
109
+ if (visibleIndices.length < minVisibleItems) {
110
+ visibleIndices.push(i);
111
+ } else {
112
+ hiddenIndices.push(i);
113
+ }
114
+ continue;
115
+ }
116
+
117
+ const itemWidth = item.offsetWidth + (visibleIndices.length > 0 ? gap : 0);
118
+ if (totalWidth + itemWidth <= availableWidthWithOverflow) {
119
+ totalWidth += itemWidth;
120
+ visibleIndices.push(i);
121
+ } else {
122
+ hiddenIndices.push(i);
123
+ }
124
+ }
125
+
126
+ // 确保至少显示 minVisibleItems 个项
127
+ if (visibleIndices.length < minVisibleItems && items.length > 0) {
128
+ // 从隐藏项中取出前几个,强制显示
129
+ const needed = minVisibleItems - visibleIndices.length;
130
+ const toShow = hiddenIndices.splice(0, needed);
131
+ visibleIndices.push(...toShow);
132
+ visibleIndices.sort((a, b) => a - b);
133
+ }
134
+
135
+ return {
136
+ visibleIndices,
137
+ hiddenIndices,
138
+ needsOverflow: hiddenIndices.length > 0,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * 批量计算多个元素的宽度(包括间距)
144
+ * @param items 元素数组
145
+ * @param gap 元素间距
146
+ * @returns 总宽度
147
+ */
148
+ static calculateTotalWidth(items: HTMLElement[], gap: number = 16): number {
149
+ if (items.length === 0) return 0;
150
+ return items.reduce((sum, item, index) => {
151
+ const itemWidth = item.offsetWidth || 0;
152
+ return sum + itemWidth + (index > 0 ? gap : 0);
153
+ }, 0);
154
+ }
155
+
156
+ /**
157
+ * 获取元素的实际宽度(包括 margin)
158
+ * @param element 元素
159
+ * @returns 总宽度(包括 margin)
160
+ */
161
+ static getElementTotalWidth(element: HTMLElement): number {
162
+ if (!element) return 0;
163
+ const style = window.getComputedStyle(element);
164
+ const width = element.offsetWidth;
165
+ const marginLeft = parseFloat(style.marginLeft) || 0;
166
+ const marginRight = parseFloat(style.marginRight) || 0;
167
+ return width + marginLeft + marginRight;
168
+ }
169
+ }
@@ -0,0 +1,555 @@
1
+ /* ResponsiveNav Component Styles - Modern Design with Theme Support */
2
+
3
+ :host {
4
+ display: block;
5
+ }
6
+
7
+ .responsive-nav {
8
+ position: fixed;
9
+ top: 0;
10
+ left: 0;
11
+ right: 0;
12
+ z-index: 1000;
13
+ /* 使用主题变量,完全由主题系统控制背景色 */
14
+ background: var(--nav-bg, var(--bg-primary));
15
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
16
+ backdrop-filter: blur(20px) saturate(180%);
17
+ border-bottom: 1px solid var(--nav-border, var(--border-color));
18
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
19
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
20
+ }
21
+
22
+ .responsive-nav.nav-scrolled {
23
+ background: var(--nav-bg-scrolled, var(--bg-primary));
24
+ box-shadow: 0 4px 24px var(--nav-shadow, var(--card-shadow));
25
+ border-bottom-color: var(--nav-border, var(--border-color));
26
+ }
27
+
28
+ .nav-container {
29
+ max-width: 1280px;
30
+ margin: 0 auto;
31
+ padding: 0 2rem;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: space-between;
35
+ height: 72px;
36
+ gap: 1.5rem;
37
+ }
38
+
39
+ .nav-brand {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 0.75rem;
43
+ font-weight: 800;
44
+ font-size: 1.5rem;
45
+ color: var(--nav-brand-color, var(--text-primary));
46
+ flex-shrink: 0;
47
+ transition: transform 0.2s ease;
48
+ cursor: pointer;
49
+ }
50
+
51
+ .nav-brand:hover {
52
+ transform: scale(1.05);
53
+ }
54
+
55
+ .nav-brand-icon {
56
+ display: flex;
57
+ align-items: center;
58
+ font-size: 1.8rem;
59
+ transition: transform 0.3s ease;
60
+ }
61
+
62
+ .nav-brand:hover .nav-brand-icon {
63
+ transform: rotate(5deg) scale(1.1);
64
+ }
65
+
66
+ .nav-brand-text {
67
+ background: linear-gradient(
68
+ 135deg,
69
+ var(--hero-gradient-start, var(--primary-red)),
70
+ var(--hero-gradient-end, var(--accent-orange))
71
+ );
72
+ -webkit-background-clip: text;
73
+ -webkit-text-fill-color: transparent;
74
+ background-clip: text;
75
+ letter-spacing: -0.02em;
76
+ position: relative;
77
+ }
78
+
79
+ .nav-menu {
80
+ display: flex;
81
+ align-items: center;
82
+ gap: 0.5rem;
83
+ flex: 1;
84
+ justify-content: flex-end;
85
+ overflow: visible;
86
+ padding-right: 0.5rem;
87
+ }
88
+
89
+ /* wsx-link 组件在导航栏中的样式 - 使用 ::part() 选择器 */
90
+ wsx-link.nav-link {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ wsx-link.nav-link::part(link) {
97
+ color: var(--nav-link-color, var(--text-secondary));
98
+ text-decoration: none !important;
99
+ font-weight: 500;
100
+ font-size: 0.95rem;
101
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
102
+ position: relative;
103
+ white-space: nowrap;
104
+ padding: 0.5rem 0.75rem;
105
+ border-radius: 0.5rem;
106
+ display: inline-flex;
107
+ align-items: center;
108
+ }
109
+
110
+ wsx-link.nav-link::part(link):hover {
111
+ color: var(--nav-link-hover-color, var(--text-primary));
112
+ background: var(--nav-link-hover-bg, var(--bg-secondary));
113
+ text-decoration: none !important;
114
+ transform: translateY(-1px);
115
+ }
116
+
117
+ /* 覆盖全局样式的 ::after,禁用它的下划线 */
118
+ wsx-link.nav-link::part(link)::after {
119
+ content: none !important;
120
+ display: none !important;
121
+ width: 0 !important;
122
+ height: 0 !important;
123
+ background: none !important;
124
+ }
125
+
126
+ /* 禁用 hover 状态的全局样式下划线 */
127
+ wsx-link.nav-link::part(link):hover::after {
128
+ content: none !important;
129
+ display: none !important;
130
+ width: 0 !important;
131
+ height: 0 !important;
132
+ background: none !important;
133
+ }
134
+
135
+ /* 正常和 hover 状态不使用下划线,只有激活状态才显示 */
136
+ wsx-link.nav-link::part(link)::before {
137
+ display: none;
138
+ }
139
+
140
+ /* 激活状态的 wsx-link */
141
+ wsx-link.nav-link[active]::part(link),
142
+ wsx-link.nav-link.nav-link-active::part(link) {
143
+ color: var(--nav-link-active-color, var(--primary-red)) !important;
144
+ font-weight: 600;
145
+ background: var(--nav-link-active-bg, var(--bg-secondary));
146
+ text-decoration: none !important;
147
+ }
148
+
149
+ /* 激活状态:禁用全局样式的 ::after */
150
+ wsx-link.nav-link[active]::part(link)::after,
151
+ wsx-link.nav-link.nav-link-active::part(link)::after {
152
+ content: none !important;
153
+ display: none !important;
154
+ width: 0 !important;
155
+ height: 0 !important;
156
+ background: none !important;
157
+ }
158
+
159
+ /* 激活状态:使用 ::before 显示下划线 */
160
+ wsx-link.nav-link[active]::part(link)::before,
161
+ wsx-link.nav-link.nav-link-active::part(link)::before {
162
+ content: "";
163
+ display: block;
164
+ position: absolute;
165
+ bottom: 0.125rem;
166
+ left: 50%;
167
+ transform: translateX(-50%);
168
+ width: calc(100% - 1.5rem);
169
+ height: 3px;
170
+ background: linear-gradient(
171
+ 135deg,
172
+ var(--hero-gradient-start, var(--primary-red)),
173
+ var(--hero-gradient-end, var(--accent-orange))
174
+ );
175
+ border-radius: 2px;
176
+ z-index: 1;
177
+ }
178
+
179
+ /* 向后兼容:保留 .nav-link 类选择器(用于非 wsx-link 元素) */
180
+ .nav-link {
181
+ color: var(--nav-link-color, var(--text-secondary));
182
+ text-decoration: none;
183
+ font-weight: 500;
184
+ font-size: 0.95rem;
185
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
186
+ position: relative;
187
+ white-space: nowrap;
188
+ flex-shrink: 0;
189
+ padding: 0.5rem 0.75rem;
190
+ border-radius: 0.5rem;
191
+ display: inline-flex;
192
+ align-items: center;
193
+ }
194
+
195
+ .nav-link:hover {
196
+ color: var(--nav-link-hover-color, var(--text-primary));
197
+ background: var(--nav-link-hover-bg, var(--bg-secondary));
198
+ transform: translateY(-1px);
199
+ }
200
+
201
+ .nav-link::after {
202
+ content: "";
203
+ position: absolute;
204
+ bottom: 0.25rem;
205
+ left: 50%;
206
+ transform: translateX(-50%);
207
+ width: 0;
208
+ height: 2px;
209
+ background: linear-gradient(
210
+ 135deg,
211
+ var(--hero-gradient-start, var(--primary-red)),
212
+ var(--hero-gradient-end, var(--accent-orange))
213
+ );
214
+ border-radius: 2px;
215
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
216
+ }
217
+
218
+ .nav-link:hover::after {
219
+ width: calc(100% - 1.5rem);
220
+ }
221
+
222
+ .nav-link-active {
223
+ color: var(--nav-link-active-color, var(--primary-red)) !important;
224
+ font-weight: 600;
225
+ background: var(--nav-link-active-bg, var(--bg-secondary));
226
+ }
227
+
228
+ .nav-link-active::after {
229
+ width: calc(100% - 1.5rem) !important;
230
+ height: 3px !important;
231
+ bottom: 0.125rem !important;
232
+ }
233
+
234
+ .nav-overflow {
235
+ position: relative;
236
+ flex-shrink: 0;
237
+ }
238
+
239
+ .nav-overflow-button {
240
+ padding: 0.5rem 0.875rem;
241
+ background: var(--nav-overflow-bg, transparent);
242
+ border: 1px solid var(--nav-overflow-border, var(--border-color));
243
+ border-radius: 0.5rem;
244
+ color: var(--nav-link-color, var(--text-secondary));
245
+ cursor: pointer;
246
+ font-size: 0.875rem;
247
+ font-weight: 500;
248
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
249
+ display: inline-flex;
250
+ align-items: center;
251
+ gap: 0.25rem;
252
+ }
253
+
254
+ .nav-overflow-button:hover {
255
+ background: var(--nav-overflow-hover-bg, var(--bg-secondary));
256
+ color: var(--nav-link-hover-color, var(--text-primary));
257
+ border-color: var(--nav-overflow-hover-border, var(--border-color));
258
+ transform: translateY(-1px);
259
+ box-shadow: 0 2px 8px var(--card-shadow);
260
+ }
261
+
262
+ .nav-overflow-menu {
263
+ position: absolute;
264
+ top: calc(100% + 0.75rem);
265
+ right: 0;
266
+ min-width: 180px;
267
+ background: var(--nav-overflow-menu-bg, var(--bg-primary));
268
+ border: 1px solid var(--nav-overflow-menu-border, var(--border-color));
269
+ border-radius: 0.75rem;
270
+ box-shadow:
271
+ 0 10px 25px -5px rgba(0, 0, 0, 0.1),
272
+ 0 8px 10px -6px rgba(0, 0, 0, 0.1);
273
+ padding: 0.5rem;
274
+ z-index: 1001;
275
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
276
+ backdrop-filter: blur(20px) saturate(180%);
277
+ animation: slideDown 0.2s cubic-bezier(0.4, 0, 0.2, 1);
278
+ }
279
+
280
+ @keyframes slideDown {
281
+ from {
282
+ opacity: 0;
283
+ transform: translateY(-8px);
284
+ }
285
+ to {
286
+ opacity: 1;
287
+ transform: translateY(0);
288
+ }
289
+ }
290
+
291
+ /* Overflow 菜单中的 wsx-link 样式 */
292
+ wsx-link.nav-overflow-link::part(link) {
293
+ display: block;
294
+ padding: 0.625rem 0.875rem;
295
+ color: var(--nav-link-color, var(--text-secondary));
296
+ text-decoration: none !important;
297
+ font-weight: 500;
298
+ font-size: 0.9rem;
299
+ border-radius: 0.5rem;
300
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
301
+ margin-bottom: 0.25rem;
302
+ border-left: 3px solid transparent;
303
+ }
304
+
305
+ wsx-link.nav-overflow-link::part(link):hover {
306
+ background: var(--nav-overflow-link-hover-bg, var(--bg-secondary));
307
+ color: var(--nav-link-hover-color, var(--text-primary));
308
+ text-decoration: none !important;
309
+ transform: translateX(4px);
310
+ }
311
+
312
+ wsx-link.nav-overflow-link[active]::part(link),
313
+ wsx-link.nav-overflow-link.nav-link-active::part(link) {
314
+ background: var(--nav-overflow-link-active-bg, var(--bg-secondary));
315
+ color: var(--nav-link-active-color, var(--primary-red));
316
+ font-weight: 600;
317
+ border-left-color: var(--primary-red);
318
+ padding-left: 0.625rem;
319
+ }
320
+
321
+ /* 向后兼容 */
322
+ .nav-overflow-link {
323
+ display: block;
324
+ padding: 0.625rem 0.875rem;
325
+ color: var(--nav-link-color, var(--text-secondary));
326
+ text-decoration: none;
327
+ font-weight: 500;
328
+ font-size: 0.9rem;
329
+ border-radius: 0.5rem;
330
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
331
+ margin-bottom: 0.25rem;
332
+ border-left: 3px solid transparent;
333
+ }
334
+
335
+ .nav-overflow-link:hover {
336
+ background: var(--nav-overflow-link-hover-bg, var(--bg-secondary));
337
+ color: var(--nav-link-hover-color, var(--text-primary));
338
+ transform: translateX(4px);
339
+ }
340
+
341
+ .nav-overflow-link.nav-link-active {
342
+ background: var(--nav-overflow-link-active-bg, var(--bg-secondary));
343
+ color: var(--nav-link-active-color, var(--primary-red));
344
+ font-weight: 600;
345
+ border-left-color: var(--primary-red);
346
+ padding-left: 0.625rem;
347
+ }
348
+
349
+ .nav-actions {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 0.75rem;
353
+ flex-shrink: 0;
354
+ margin-left: 0.5rem;
355
+ z-index: 1002;
356
+ padding-left: 0.75rem;
357
+ border-left: 1px solid var(--nav-actions-border, var(--border-color));
358
+ }
359
+
360
+ .nav-action {
361
+ display: flex;
362
+ align-items: center;
363
+ transition: transform 0.2s ease;
364
+ }
365
+
366
+ .nav-action:hover {
367
+ transform: scale(1.05);
368
+ }
369
+
370
+ .nav-toggle {
371
+ display: none;
372
+ flex-direction: column;
373
+ gap: 5px;
374
+ background: var(--nav-toggle-bg, transparent);
375
+ border: 1px solid var(--nav-toggle-border, var(--border-color));
376
+ border-radius: 0.5rem;
377
+ cursor: pointer;
378
+ padding: 0.625rem 0.5rem;
379
+ z-index: 1001;
380
+ transition: all 0.25s ease;
381
+ }
382
+
383
+ .nav-toggle:hover {
384
+ background: var(--nav-toggle-hover-bg, var(--bg-secondary));
385
+ border-color: var(--nav-toggle-hover-border, var(--border-color));
386
+ }
387
+
388
+ .nav-toggle-line {
389
+ width: 24px;
390
+ height: 3px;
391
+ background: var(--nav-toggle-color, var(--text-primary));
392
+ border-radius: 3px;
393
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
394
+ transform-origin: center;
395
+ }
396
+
397
+ .nav-toggle-line.open:nth-child(1) {
398
+ transform: rotate(45deg) translate(8px, 8px);
399
+ }
400
+
401
+ .nav-toggle-line.open:nth-child(2) {
402
+ opacity: 0;
403
+ }
404
+
405
+ .nav-toggle-line.open:nth-child(3) {
406
+ transform: rotate(-45deg) translate(7px, -7px);
407
+ }
408
+
409
+ .nav-mobile-menu {
410
+ position: fixed;
411
+ top: 72px;
412
+ left: 0;
413
+ right: 0;
414
+ background: var(--nav-mobile-bg, var(--bg-primary));
415
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
416
+ backdrop-filter: blur(20px) saturate(180%);
417
+ flex-direction: column;
418
+ padding: 1.5rem;
419
+ gap: 0.5rem;
420
+ transform: translateY(-100%);
421
+ opacity: 0;
422
+ visibility: hidden;
423
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
424
+ border-bottom: 1px solid var(--nav-border, var(--border-color));
425
+ max-height: calc(100vh - 72px);
426
+ overflow-y: auto;
427
+ z-index: 999;
428
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
429
+ }
430
+
431
+ .nav-mobile-menu.open {
432
+ transform: translateY(0);
433
+ opacity: 1;
434
+ visibility: visible;
435
+ }
436
+
437
+ /* 移动端菜单中的 wsx-link 样式 */
438
+ wsx-link.nav-mobile-link::part(link) {
439
+ display: block;
440
+ padding: 0.875rem 1.25rem;
441
+ color: var(--nav-link-color, var(--text-secondary));
442
+ text-decoration: none !important;
443
+ font-weight: 500;
444
+ font-size: 1rem;
445
+ border-radius: 0.625rem;
446
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
447
+ border-left: 3px solid transparent;
448
+ }
449
+
450
+ wsx-link.nav-mobile-link::part(link):hover {
451
+ background: var(--nav-mobile-link-hover-bg, var(--bg-secondary));
452
+ color: var(--nav-link-hover-color, var(--text-primary));
453
+ text-decoration: none !important;
454
+ transform: translateX(4px);
455
+ border-left-color: var(--primary-red);
456
+ }
457
+
458
+ wsx-link.nav-mobile-link[active]::part(link),
459
+ wsx-link.nav-mobile-link.nav-link-active::part(link) {
460
+ background: var(--nav-mobile-link-active-bg, var(--bg-secondary));
461
+ color: var(--nav-link-active-color, var(--primary-red));
462
+ font-weight: 600;
463
+ border-left-color: var(--primary-red);
464
+ box-shadow: 0 2px 8px var(--card-shadow);
465
+ }
466
+
467
+ /* 向后兼容 */
468
+ .nav-mobile-link {
469
+ display: block;
470
+ padding: 0.875rem 1.25rem;
471
+ color: var(--nav-link-color, var(--text-secondary));
472
+ text-decoration: none;
473
+ font-weight: 500;
474
+ font-size: 1rem;
475
+ border-radius: 0.625rem;
476
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
477
+ border-left: 3px solid transparent;
478
+ }
479
+
480
+ .nav-mobile-link:hover {
481
+ background: var(--nav-mobile-link-hover-bg, var(--bg-secondary));
482
+ color: var(--nav-link-hover-color, var(--text-primary));
483
+ transform: translateX(4px);
484
+ border-left-color: var(--primary-red);
485
+ }
486
+
487
+ .nav-mobile-link.nav-link-active {
488
+ background: var(--nav-mobile-link-active-bg, var(--bg-secondary));
489
+ color: var(--nav-link-active-color, var(--primary-red));
490
+ font-weight: 600;
491
+ border-left-color: var(--primary-red);
492
+ box-shadow: 0 2px 8px var(--card-shadow);
493
+ }
494
+
495
+ /* 响应式设计 */
496
+ @media (max-width: 768px) {
497
+ .nav-container {
498
+ padding: 0 1.25rem;
499
+ height: 68px;
500
+ }
501
+
502
+ .nav-brand {
503
+ font-size: 1.35rem;
504
+ }
505
+
506
+ .nav-menu {
507
+ display: none;
508
+ }
509
+
510
+ .nav-actions {
511
+ margin-left: 0;
512
+ gap: 0.5rem;
513
+ padding-left: 0.5rem;
514
+ border-left: none;
515
+ }
516
+
517
+ .nav-toggle {
518
+ display: flex;
519
+ margin-left: 0.5rem;
520
+ }
521
+
522
+ .nav-mobile-menu {
523
+ display: flex;
524
+ top: 68px;
525
+ max-height: calc(100vh - 68px);
526
+ }
527
+ }
528
+
529
+ @media (min-width: 769px) {
530
+ .nav-mobile-menu {
531
+ display: none !important;
532
+ }
533
+ }
534
+
535
+ /* 平滑滚动支持 */
536
+ @media (prefers-reduced-motion: no-preference) {
537
+ .responsive-nav,
538
+ .nav-link,
539
+ .nav-overflow-button,
540
+ .nav-toggle {
541
+ transition-duration: 0.25s;
542
+ }
543
+ }
544
+
545
+ /* 减少动画(无障碍支持) */
546
+ @media (prefers-reduced-motion: reduce) {
547
+ .responsive-nav,
548
+ .nav-link,
549
+ .nav-overflow-button,
550
+ .nav-toggle,
551
+ .nav-mobile-menu {
552
+ transition: none;
553
+ animation: none;
554
+ }
555
+ }