@coffic/cosy-ui 0.3.39 → 0.3.45
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.
- package/dist/app.css +1 -1
- package/dist/components/base/Image.astro +260 -248
- package/dist/components/data-display/Blog.astro +232 -179
- package/dist/components/data-display/TeamMember.astro +19 -16
- package/dist/components/data-display/TeamMembers.astro +52 -48
- package/dist/components/display/Banner.astro +105 -17
- package/dist/components/display/Card.astro +106 -75
- package/dist/components/display/CodeBlock.astro +90 -83
- package/dist/components/display/CodeExample.astro +126 -129
- package/dist/components/display/Hero.astro +112 -32
- package/dist/components/layouts/DocumentationLayout.astro +24 -3
- package/dist/components/layouts/Header.astro +404 -41
- package/dist/components/layouts/Sidebar.astro +1 -1
- package/dist/components/navigation/LanguageSwitcher.astro +88 -58
- package/dist/components/navigation/TableOfContents.astro +333 -320
- package/dist/components/navigation/ThemeSwitcher.astro +109 -68
- package/dist/components/typography/Heading.astro +163 -130
- package/dist/components/typography/Text.astro +73 -71
- package/dist/utils/theme.ts +74 -15
- package/package.json +75 -70
@@ -1,4 +1,59 @@
|
|
1
1
|
---
|
2
|
+
/**
|
3
|
+
* @component Header
|
4
|
+
*
|
5
|
+
* @description
|
6
|
+
* Header 组件是一个用于网站顶部导航的布局组件,提供了logo、导航菜单、语言切换等功能。
|
7
|
+
* 组件支持响应式设计,在不同屏幕尺寸下有良好的显示效果,并可选择固定在页面顶部。
|
8
|
+
*
|
9
|
+
* @design
|
10
|
+
* 设计理念:
|
11
|
+
* 1. 简洁实用 - 提供清晰的导航和品牌展示,不过度占用视觉空间
|
12
|
+
* 2. 响应式适配 - 在移动端和桌面端都有合适的展现形式
|
13
|
+
* 3. 可定制性 - 支持多种配置选项,适应不同网站的风格和需求
|
14
|
+
* 4. 多语言支持 - 内置语言切换功能,便于构建国际化网站
|
15
|
+
*
|
16
|
+
* @usage
|
17
|
+
* 基本用法:
|
18
|
+
* ```astro
|
19
|
+
* <Header
|
20
|
+
* logo={import("../assets/logo.png")}
|
21
|
+
* logoHref="/"
|
22
|
+
* navItems={[
|
23
|
+
* { href: "/docs", label: "文档", match: (path) => path.startsWith("/docs") },
|
24
|
+
* { href: "/components", label: "组件", match: (path) => path.startsWith("/components") }
|
25
|
+
* ]}
|
26
|
+
* />
|
27
|
+
* ```
|
28
|
+
*
|
29
|
+
* 自定义语言选项:
|
30
|
+
* ```astro
|
31
|
+
* <Header
|
32
|
+
* logo={import("../assets/logo.png")}
|
33
|
+
* languages={[
|
34
|
+
* { code: "zh-cn", name: "简体中文" },
|
35
|
+
* { code: "en", name: "English" },
|
36
|
+
* { code: "ja", name: "日本語" }
|
37
|
+
* ]}
|
38
|
+
* currentLocale="zh-cn"
|
39
|
+
* />
|
40
|
+
* ```
|
41
|
+
*
|
42
|
+
* 带有基础路径:
|
43
|
+
* ```astro
|
44
|
+
* <Header
|
45
|
+
* logo={import("../assets/logo.png")}
|
46
|
+
* basePath="/my-site"
|
47
|
+
* />
|
48
|
+
* ```
|
49
|
+
*
|
50
|
+
* 自定义高度:
|
51
|
+
* ```astro
|
52
|
+
* <Header
|
53
|
+
* logo={import("../assets/logo.png")}
|
54
|
+
* height="lg"
|
55
|
+
* />
|
56
|
+
*/
|
2
57
|
import Link from '../base/Link.astro';
|
3
58
|
import Image from '../base/Image.astro';
|
4
59
|
import '../../app.css';
|
@@ -50,19 +105,31 @@ interface Props {
|
|
50
105
|
* @default false
|
51
106
|
*/
|
52
107
|
defaultSidebarOpen?: boolean;
|
108
|
+
/**
|
109
|
+
* 导航栏高度
|
110
|
+
* @default "md"
|
111
|
+
*/
|
112
|
+
height?: '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
113
|
+
/**
|
114
|
+
* 是否显示高度设置菜单
|
115
|
+
* @default true
|
116
|
+
*/
|
117
|
+
showHeightSetting?: boolean;
|
53
118
|
}
|
54
119
|
|
55
120
|
const {
|
56
121
|
logo,
|
57
|
-
logoHref =
|
122
|
+
logoHref = '/',
|
58
123
|
navItems = [],
|
59
124
|
sticky = true,
|
60
125
|
languages = [
|
61
126
|
{ code: 'zh-cn', name: '中文' },
|
62
|
-
{ code: 'en', name: 'English' }
|
127
|
+
{ code: 'en', name: 'English' },
|
63
128
|
],
|
64
129
|
currentLocale = 'zh-cn',
|
65
130
|
basePath = '',
|
131
|
+
height = 'md',
|
132
|
+
showHeightSetting = true,
|
66
133
|
} = Astro.props;
|
67
134
|
|
68
135
|
type NavItem = { href: string; label: string; match: (path: string) => boolean };
|
@@ -82,66 +149,362 @@ function getLanguageUrl(langCode: string) {
|
|
82
149
|
// 如果有基础路径,需要加上
|
83
150
|
return `${basePath}/${langCode}${pathWithoutLocale}`;
|
84
151
|
}
|
152
|
+
|
153
|
+
// 定义可用的高度选项
|
154
|
+
const heightOptions = [
|
155
|
+
{ value: '3xs', label: '超超小' },
|
156
|
+
{ value: '2xs', label: '超小' },
|
157
|
+
{ value: 'xs', label: '小' },
|
158
|
+
{ value: 'sm', label: '较小' },
|
159
|
+
{ value: 'md', label: '中等' },
|
160
|
+
{ value: 'lg', label: '大' },
|
161
|
+
{ value: 'xl', label: '超大' },
|
162
|
+
];
|
163
|
+
|
164
|
+
// 获取初始高度(优先使用localStorage中的值)
|
165
|
+
type HeightType = '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
166
|
+
|
167
|
+
let initialHeight: HeightType;
|
168
|
+
if (typeof window !== 'undefined') {
|
169
|
+
const storedHeight = localStorage.getItem('cosy-header-height');
|
170
|
+
initialHeight = (storedHeight as HeightType) || height;
|
171
|
+
} else {
|
172
|
+
initialHeight = height;
|
173
|
+
}
|
174
|
+
|
175
|
+
// 根据高度设置样式
|
176
|
+
const headerHeightClasses = {
|
177
|
+
'3xs': 'cosy:h-4',
|
178
|
+
'2xs': 'cosy:h-6',
|
179
|
+
xs: 'cosy:h-8',
|
180
|
+
sm: 'cosy:h-10',
|
181
|
+
md: 'cosy:h-12',
|
182
|
+
lg: 'cosy:h-16',
|
183
|
+
xl: 'cosy:h-20',
|
184
|
+
};
|
185
|
+
|
186
|
+
const headerHeightClass = headerHeightClasses[initialHeight];
|
187
|
+
|
188
|
+
// 设置logo大小
|
189
|
+
const logoSizeClasses = {
|
190
|
+
'3xs': 'cosy:w-3 cosy:h-3',
|
191
|
+
'2xs': 'cosy:w-4 cosy:h-4',
|
192
|
+
xs: 'cosy:w-5 cosy:h-5',
|
193
|
+
sm: 'cosy:w-6 cosy:h-6',
|
194
|
+
md: 'cosy:w-8 cosy:h-8',
|
195
|
+
lg: 'cosy:w-10 cosy:h-10',
|
196
|
+
xl: 'cosy:w-12 cosy:h-12',
|
197
|
+
};
|
198
|
+
|
199
|
+
const logoSizeClass = logoSizeClasses[initialHeight];
|
200
|
+
|
201
|
+
// 设置占位空间高度
|
202
|
+
const spacerHeightClasses = {
|
203
|
+
'3xs': 'cosy:h-4',
|
204
|
+
'2xs': 'cosy:h-6',
|
205
|
+
xs: 'cosy:h-8',
|
206
|
+
sm: 'cosy:h-10',
|
207
|
+
md: 'cosy:h-12',
|
208
|
+
lg: 'cosy:h-16',
|
209
|
+
xl: 'cosy:h-20',
|
210
|
+
};
|
211
|
+
|
212
|
+
const spacerHeightClass = spacerHeightClasses[initialHeight];
|
85
213
|
---
|
86
214
|
|
87
|
-
<header
|
88
|
-
|
89
|
-
|
90
|
-
|
215
|
+
<header
|
216
|
+
class:list={[
|
217
|
+
'cosy:navbar cosy:bg-accent/70 cosy:backdrop-blur cosy:border-base-200 cosy:z-50 cosy:w-full cosy:m-0 cosy:p-0 cosy:min-h-1',
|
218
|
+
headerHeightClass,
|
219
|
+
{ 'cosy:fixed cosy:top-0': sticky },
|
220
|
+
]}>
|
91
221
|
<div class="cosy:navbar-start">
|
92
|
-
<Link
|
93
|
-
|
222
|
+
<Link
|
223
|
+
href={logoHref}
|
224
|
+
class:list={[
|
225
|
+
'cosy:btn cosy:btn-ghost',
|
226
|
+
initialHeight === 'xs' || initialHeight === '2xs' || initialHeight === '3xs'
|
227
|
+
? 'cosy:btn-xs cosy:h-auto cosy:min-h-0 cosy:p-1'
|
228
|
+
: initialHeight === 'sm'
|
229
|
+
? 'cosy:btn-sm'
|
230
|
+
: '',
|
231
|
+
]}>
|
232
|
+
<Image src={logo} alt="logo" class={logoSizeClass} />
|
94
233
|
</Link>
|
95
234
|
</div>
|
96
235
|
|
97
236
|
<!-- 导航 -->
|
98
237
|
<div class="cosy:hidden cosy:lg:flex cosy:navbar-center">
|
99
|
-
<ul
|
100
|
-
{
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
238
|
+
<ul
|
239
|
+
class:list={[
|
240
|
+
'cosy:px-1 cosy:menu cosy:menu-horizontal',
|
241
|
+
initialHeight === 'xs' || initialHeight === '2xs' || initialHeight === '3xs'
|
242
|
+
? 'cosy:menu-xs'
|
243
|
+
: initialHeight === 'sm'
|
244
|
+
? 'cosy:menu-sm'
|
245
|
+
: '',
|
246
|
+
]}>
|
247
|
+
{
|
248
|
+
navItems.map((item: NavItem) => (
|
249
|
+
<li>
|
250
|
+
<Link
|
251
|
+
href={item.href}
|
252
|
+
class:list={[
|
253
|
+
initialHeight === 'xs' || initialHeight === '2xs' || initialHeight === '3xs'
|
254
|
+
? 'cosy:py-0 cosy:px-2 cosy:min-h-0'
|
255
|
+
: initialHeight === 'sm'
|
256
|
+
? 'cosy:py-1 cosy:min-h-6'
|
257
|
+
: '',
|
258
|
+
]}>
|
259
|
+
{item.label}
|
260
|
+
</Link>
|
261
|
+
</li>
|
262
|
+
))
|
263
|
+
}
|
107
264
|
</ul>
|
108
265
|
</div>
|
109
266
|
|
110
267
|
<div class="cosy:navbar-end">
|
268
|
+
{
|
269
|
+
showHeightSetting && (
|
270
|
+
<div class="cosy:mr-2 cosy:dropdown cosy:dropdown-end">
|
271
|
+
<div
|
272
|
+
tabindex="0"
|
273
|
+
role="button"
|
274
|
+
class:list={[
|
275
|
+
'cosy:btn cosy:btn-ghost',
|
276
|
+
initialHeight === 'xs' || initialHeight === '2xs' || initialHeight === '3xs'
|
277
|
+
? 'cosy:btn-xs cosy:h-auto cosy:min-h-0 cosy:p-1'
|
278
|
+
: initialHeight === 'sm'
|
279
|
+
? 'cosy:btn-sm'
|
280
|
+
: 'cosy:btn-sm',
|
281
|
+
]}
|
282
|
+
id="height-dropdown-btn">
|
283
|
+
<span class="cosy:mr-1">高度</span>
|
284
|
+
<svg
|
285
|
+
xmlns="http://www.w3.org/2000/svg"
|
286
|
+
class="cosy:w-4 cosy:h-4"
|
287
|
+
fill="none"
|
288
|
+
viewBox="0 0 24 24"
|
289
|
+
stroke="currentColor">
|
290
|
+
<path
|
291
|
+
stroke-linecap="round"
|
292
|
+
stroke-linejoin="round"
|
293
|
+
stroke-width="2"
|
294
|
+
d="M19 9l-7 7-7-7"
|
295
|
+
/>
|
296
|
+
</svg>
|
297
|
+
</div>
|
298
|
+
<ul
|
299
|
+
tabindex="0"
|
300
|
+
class="cosy:z-[1] cosy:bg-base-100 cosy:shadow cosy:p-2 cosy:rounded-box cosy:w-40 cosy:dropdown-content cosy:menu"
|
301
|
+
id="height-dropdown-content">
|
302
|
+
{heightOptions.map((option) => (
|
303
|
+
<li data-height={option.value} class="height-option" id={`height-${option.value}`}>
|
304
|
+
<a class={initialHeight === option.value ? 'cosy:active' : ''}>{option.label}</a>
|
305
|
+
</li>
|
306
|
+
))}
|
307
|
+
</ul>
|
308
|
+
</div>
|
309
|
+
)
|
310
|
+
}
|
311
|
+
|
111
312
|
<!-- 语言切换按钮 -->
|
112
313
|
<div class="cosy:dropdown cosy:dropdown-end">
|
113
|
-
<div
|
314
|
+
<div
|
315
|
+
tabindex="0"
|
316
|
+
role="button"
|
317
|
+
class:list={[
|
318
|
+
'cosy:btn cosy:btn-ghost',
|
319
|
+
initialHeight === 'xs' || initialHeight === '2xs' || initialHeight === '3xs'
|
320
|
+
? 'cosy:btn-xs cosy:h-auto cosy:min-h-0 cosy:p-1'
|
321
|
+
: initialHeight === 'sm'
|
322
|
+
? 'cosy:btn-sm'
|
323
|
+
: 'cosy:btn-sm',
|
324
|
+
]}>
|
114
325
|
<span class="cosy:mr-1">{currentLocale === 'zh-cn' ? '中文' : 'English'}</span>
|
115
|
-
<svg
|
116
|
-
|
326
|
+
<svg
|
327
|
+
xmlns="http://www.w3.org/2000/svg"
|
328
|
+
class="cosy:w-4 cosy:h-4"
|
329
|
+
fill="none"
|
330
|
+
viewBox="0 0 24 24"
|
331
|
+
stroke="currentColor">
|
332
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"
|
333
|
+
></path>
|
117
334
|
</svg>
|
118
335
|
</div>
|
119
|
-
<ul
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
336
|
+
<ul
|
337
|
+
tabindex="0"
|
338
|
+
class="cosy:z-[1] cosy:bg-base-100 cosy:shadow cosy:p-2 cosy:rounded-box cosy:w-32 cosy:dropdown-content cosy:menu">
|
339
|
+
{
|
340
|
+
languages.map((lang) => (
|
341
|
+
<li class={currentLocale === lang.code ? 'cosy:disabled' : ''}>
|
342
|
+
<a
|
343
|
+
href={getLanguageUrl(lang.code)}
|
344
|
+
class={currentLocale === lang.code ? 'cosy:active' : ''}>
|
345
|
+
{lang.name}
|
346
|
+
</a>
|
347
|
+
</li>
|
348
|
+
))
|
349
|
+
}
|
127
350
|
</ul>
|
128
351
|
</div>
|
129
352
|
</div>
|
130
353
|
</header>
|
131
354
|
|
132
|
-
{sticky &&
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
const
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
355
|
+
{sticky && <div class={spacerHeightClass} id="header-spacer" />}
|
356
|
+
|
357
|
+
<script define:vars={{ sticky, height, headerHeightClasses, spacerHeightClasses }}>
|
358
|
+
// 等待DOM完全加载
|
359
|
+
document.addEventListener('DOMContentLoaded', () => {
|
360
|
+
// 获取存储的高度设置,如果没有则使用传入的默认高度
|
361
|
+
const storedHeight = localStorage.getItem('cosy-header-height');
|
362
|
+
let currentHeight = storedHeight || height;
|
363
|
+
|
364
|
+
// 更新DOM元素的高度类
|
365
|
+
function updateHeaderHeight(newHeight) {
|
366
|
+
const header = document.querySelector('header');
|
367
|
+
const spacer = document.getElementById('header-spacer');
|
368
|
+
|
369
|
+
if (!header) return;
|
370
|
+
|
371
|
+
// 先获取header当前的所有类名
|
372
|
+
const headerClasses = [...header.classList];
|
373
|
+
|
374
|
+
// 找出并移除当前的高度类
|
375
|
+
Object.values(headerHeightClasses).forEach((cls) => {
|
376
|
+
if (headerClasses.includes(cls)) {
|
377
|
+
header.classList.remove(cls);
|
378
|
+
}
|
379
|
+
});
|
380
|
+
|
381
|
+
// 添加新的高度类
|
382
|
+
const newHeaderClass = headerHeightClasses[newHeight];
|
383
|
+
header.classList.add(newHeaderClass);
|
384
|
+
|
385
|
+
// 更新占位元素高度
|
386
|
+
if (spacer) {
|
387
|
+
const spacerClasses = [...spacer.classList];
|
388
|
+
|
389
|
+
Object.values(spacerHeightClasses).forEach((cls) => {
|
390
|
+
const className = cls.replace('cosy:', '');
|
391
|
+
if (spacerClasses.includes(className)) {
|
392
|
+
spacer.classList.remove(className);
|
393
|
+
}
|
394
|
+
});
|
395
|
+
|
396
|
+
const newSpacerClass = spacerHeightClasses[newHeight].replace('cosy:', '');
|
397
|
+
spacer.classList.add(newSpacerClass);
|
398
|
+
}
|
399
|
+
|
400
|
+
// 更新样式类,需要处理:list中的类
|
401
|
+
// 1. 处理logo按钮
|
402
|
+
const logoBtn = document.querySelector('.cosy\\:navbar-start .cosy\\:btn');
|
403
|
+
if (logoBtn) {
|
404
|
+
// 清理之前的按钮大小类
|
405
|
+
['cosy:btn-xs', 'cosy:btn-sm', 'cosy:h-auto', 'cosy:min-h-0', 'cosy:p-1'].forEach((cls) => {
|
406
|
+
logoBtn.classList.remove(cls.replace('cosy:', ''));
|
407
|
+
});
|
408
|
+
|
409
|
+
// 根据新高度添加适当的类
|
410
|
+
if (newHeight === 'xs' || newHeight === '2xs' || newHeight === '3xs') {
|
411
|
+
['cosy:btn-xs', 'cosy:h-auto', 'cosy:min-h-0', 'cosy:p-1'].forEach((cls) => {
|
412
|
+
logoBtn.classList.add(cls.replace('cosy:', ''));
|
413
|
+
});
|
414
|
+
} else if (newHeight === 'sm') {
|
415
|
+
logoBtn.classList.add('btn-sm');
|
416
|
+
}
|
417
|
+
}
|
418
|
+
|
419
|
+
// 2. 处理导航菜单
|
420
|
+
const navMenu = document.querySelector('.cosy\\:navbar-center .cosy\\:menu');
|
421
|
+
if (navMenu) {
|
422
|
+
navMenu.classList.remove('menu-xs', 'menu-sm');
|
423
|
+
|
424
|
+
if (newHeight === 'xs' || newHeight === '2xs' || newHeight === '3xs') {
|
425
|
+
navMenu.classList.add('menu-xs');
|
426
|
+
} else if (newHeight === 'sm') {
|
427
|
+
navMenu.classList.add('menu-sm');
|
428
|
+
}
|
144
429
|
}
|
430
|
+
|
431
|
+
// 3. 处理菜单项
|
432
|
+
const navLinks = document.querySelectorAll('.cosy\\:navbar-center .cosy\\:menu li a');
|
433
|
+
navLinks.forEach((link) => {
|
434
|
+
['cosy:py-0', 'cosy:px-2', 'cosy:min-h-0', 'cosy:py-1', 'cosy:min-h-6'].forEach((cls) => {
|
435
|
+
link.classList.remove(cls.replace('cosy:', ''));
|
436
|
+
});
|
437
|
+
|
438
|
+
if (newHeight === 'xs' || newHeight === '2xs' || newHeight === '3xs') {
|
439
|
+
['cosy:py-0', 'cosy:px-2', 'cosy:min-h-0'].forEach((cls) => {
|
440
|
+
link.classList.add(cls.replace('cosy:', ''));
|
441
|
+
});
|
442
|
+
} else if (newHeight === 'sm') {
|
443
|
+
['cosy:py-1', 'cosy:min-h-6'].forEach((cls) => {
|
444
|
+
link.classList.add(cls.replace('cosy:', ''));
|
445
|
+
});
|
446
|
+
}
|
447
|
+
});
|
448
|
+
|
449
|
+
// 4. 处理下拉按钮
|
450
|
+
const dropdownBtns = document.querySelectorAll(
|
451
|
+
'.cosy\\:navbar-end .cosy\\:dropdown .cosy\\:btn'
|
452
|
+
);
|
453
|
+
dropdownBtns.forEach((btn) => {
|
454
|
+
['cosy:btn-xs', 'cosy:btn-sm', 'cosy:h-auto', 'cosy:min-h-0', 'cosy:p-1'].forEach((cls) => {
|
455
|
+
btn.classList.remove(cls.replace('cosy:', ''));
|
456
|
+
});
|
457
|
+
|
458
|
+
if (newHeight === 'xs' || newHeight === '2xs' || newHeight === '3xs') {
|
459
|
+
['cosy:btn-xs', 'cosy:h-auto', 'cosy:min-h-0', 'cosy:p-1'].forEach((cls) => {
|
460
|
+
btn.classList.add(cls.replace('cosy:', ''));
|
461
|
+
});
|
462
|
+
} else if (newHeight === 'sm' || newHeight === 'md') {
|
463
|
+
btn.classList.add('btn-sm');
|
464
|
+
}
|
465
|
+
});
|
466
|
+
|
467
|
+
// 标记当前选中的选项
|
468
|
+
document.querySelectorAll('.height-option a').forEach((item) => {
|
469
|
+
item.classList.remove('active');
|
470
|
+
});
|
471
|
+
|
472
|
+
const activeItem = document.querySelector(`#height-${newHeight} a`);
|
473
|
+
if (activeItem) {
|
474
|
+
activeItem.classList.add('active');
|
475
|
+
}
|
476
|
+
|
477
|
+
// 保存设置到本地存储
|
478
|
+
localStorage.setItem('cosy-header-height', newHeight);
|
479
|
+
currentHeight = newHeight;
|
480
|
+
}
|
481
|
+
|
482
|
+
// 如果有存储的高度设置,应用它
|
483
|
+
if (storedHeight) {
|
484
|
+
updateHeaderHeight(storedHeight);
|
485
|
+
}
|
486
|
+
|
487
|
+
// 绑定点击事件到高度选项
|
488
|
+
document.querySelectorAll('.height-option').forEach((item) => {
|
489
|
+
item.addEventListener('click', function () {
|
490
|
+
const newHeight = this.getAttribute('data-height');
|
491
|
+
updateHeaderHeight(newHeight);
|
492
|
+
|
493
|
+
// 关闭下拉菜单
|
494
|
+
document.activeElement.blur();
|
495
|
+
});
|
145
496
|
});
|
146
|
-
|
147
|
-
|
497
|
+
|
498
|
+
// 滚动时添加阴影
|
499
|
+
if (sticky) {
|
500
|
+
const header = document.querySelector('header');
|
501
|
+
window.addEventListener('scroll', () => {
|
502
|
+
if (window.pageYOffset > 0) {
|
503
|
+
header?.classList.add('cosy:shadow-lg'.replace('cosy:', ''));
|
504
|
+
} else {
|
505
|
+
header?.classList.remove('cosy:shadow-lg'.replace('cosy:', ''));
|
506
|
+
}
|
507
|
+
});
|
508
|
+
}
|
509
|
+
});
|
510
|
+
</script>
|
@@ -90,7 +90,7 @@ const currentSection = sidebarItems.find((section) =>
|
|
90
90
|
|
91
91
|
{/* 桌面端侧边栏 */}
|
92
92
|
<aside class:list={[className, debugClass, 'cosy:hidden cosy:lg:block']}>
|
93
|
-
<div class="cosy:top-16 cosy:sticky cosy:h-[calc(100vh-
|
93
|
+
<div class="cosy:top-16 cosy:sticky cosy:pb-48 cosy:h-[calc(100vh-0rem)] cosy:overflow-y-auto">
|
94
94
|
<SidebarNav sidebarItems={sidebarItems} currentPath={currentPath} debug={debug} />
|
95
95
|
</div>
|
96
96
|
</aside>
|
@@ -1,81 +1,111 @@
|
|
1
1
|
---
|
2
|
+
/**
|
3
|
+
* @component LanguageSwitcher
|
4
|
+
*
|
5
|
+
* @description
|
6
|
+
* LanguageSwitcher 组件提供一个语言切换下拉菜单,支持多语言网站的语言切换功能。
|
7
|
+
* 组件会自动处理 URL 路径,确保在切换语言时保留当前页面路径。
|
8
|
+
*
|
9
|
+
* @design
|
10
|
+
* 设计理念:
|
11
|
+
* 1. 简洁直观 - 清晰显示当前语言和可选语言
|
12
|
+
* 2. 无缝集成 - 自动处理 URL 路由,保持用户浏览上下文
|
13
|
+
* 3. 可定制性 - 支持自定义语言列表和当前语言设置
|
14
|
+
* 4. 一致的视觉风格 - 使用与整体设计系统一致的下拉菜单样式
|
15
|
+
*
|
16
|
+
* @usage
|
17
|
+
* 基本用法:
|
18
|
+
* ```astro
|
19
|
+
* <LanguageSwitcher />
|
20
|
+
* ```
|
21
|
+
*
|
22
|
+
* 自定义语言列表:
|
23
|
+
* ```astro
|
24
|
+
* <LanguageSwitcher
|
25
|
+
* languages={[
|
26
|
+
* { code: 'zh-cn', name: '简体中文' },
|
27
|
+
* { code: 'en', name: 'English' },
|
28
|
+
* { code: 'ja', name: '日本語' }
|
29
|
+
* ]}
|
30
|
+
* currentLocale="en"
|
31
|
+
* />
|
32
|
+
* ```
|
33
|
+
*/
|
34
|
+
|
2
35
|
import Button from '../base/Button.astro';
|
3
36
|
import Link from '../base/Link.astro';
|
37
|
+
import '../../app.css';
|
4
38
|
|
5
39
|
interface Language {
|
6
|
-
|
7
|
-
|
40
|
+
code: string;
|
41
|
+
name: string;
|
8
42
|
}
|
9
43
|
|
10
44
|
interface Props {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
45
|
+
/**
|
46
|
+
* 自定义类名
|
47
|
+
*/
|
48
|
+
class?: string;
|
15
49
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
50
|
+
/**
|
51
|
+
* 语言列表
|
52
|
+
* @default [{ code: 'zh-cn', name: '简体中文' }, { code: 'en', name: 'English' }]
|
53
|
+
*/
|
54
|
+
languages?: Language[];
|
21
55
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
56
|
+
/**
|
57
|
+
* 当前语言
|
58
|
+
* @default 'zh-cn'
|
59
|
+
*/
|
60
|
+
currentLocale?: string;
|
27
61
|
}
|
28
62
|
|
29
63
|
const {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
64
|
+
class: className,
|
65
|
+
languages = [
|
66
|
+
{ code: 'zh-cn', name: '简体中文' },
|
67
|
+
{ code: 'en', name: 'English' },
|
68
|
+
],
|
69
|
+
currentLocale = 'zh-cn',
|
36
70
|
} = Astro.props;
|
37
71
|
|
38
72
|
const currentLanguageName =
|
39
|
-
|
73
|
+
languages.find((lang: Language) => lang.code === currentLocale)?.name || '简体中文';
|
40
74
|
|
41
75
|
// 为每个语言生成对应的URL
|
42
76
|
function generateLanguageUrl(langCode: string): string {
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
77
|
+
const currentPath = Astro.url.pathname;
|
78
|
+
const pathParts = currentPath.split('/').filter(Boolean);
|
79
|
+
const firstPathPart = pathParts[0];
|
80
|
+
const supportedLanguages = languages.map((lang: Language) => lang.code);
|
81
|
+
const isFirstPartLang = supportedLanguages.includes(firstPathPart);
|
48
82
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
83
|
+
if (isFirstPartLang) {
|
84
|
+
pathParts[0] = langCode;
|
85
|
+
return '/' + pathParts.join('/');
|
86
|
+
} else {
|
87
|
+
return '/' + langCode + (currentPath === '/' ? '' : currentPath);
|
88
|
+
}
|
55
89
|
}
|
56
90
|
---
|
57
91
|
|
58
|
-
<div class:list={[
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
))
|
79
|
-
}
|
80
|
-
</ul>
|
81
|
-
</div>
|
92
|
+
<div class:list={['cosy:dropdown cosy:dropdown-end', className]}>
|
93
|
+
<Button variant="ghost" size="sm" class="cosy:p-1">
|
94
|
+
{currentLanguageName}
|
95
|
+
</Button>
|
96
|
+
<ul
|
97
|
+
tabindex={0}
|
98
|
+
class="cosy:bg-slate-900 cosy:dark:bg-slate-800 cosy:shadow-lg cosy:p-2 cosy:rounded-box cosy:w-40 cosy:dropdown-content cosy:menu">
|
99
|
+
{
|
100
|
+
languages.map((lang: Language) => (
|
101
|
+
<li>
|
102
|
+
<Link
|
103
|
+
href={generateLanguageUrl(lang.code)}
|
104
|
+
class:list={[{ 'cosy:active': lang.code === currentLocale }]}>
|
105
|
+
{lang.name}
|
106
|
+
</Link>
|
107
|
+
</li>
|
108
|
+
))
|
109
|
+
}
|
110
|
+
</ul>
|
111
|
+
</div>
|