@coffic/cosy-ui 0.9.18 → 0.9.19
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/src-astro/code-container/ButtonCodeToggle.astro +0 -1
- package/dist/src-astro/code-container/ButtonCopyCode.astro +0 -2
- package/dist/src-astro/code-container/CodeContainer.astro +0 -3
- package/dist/src-astro/code-container/CodeToolbar.astro +0 -2
- package/dist/src-astro/footer/Footer.astro +1 -0
- package/dist/src-astro/header/Header.astro +3 -44
- package/dist/src-astro/header/LogoLink.astro +87 -0
- package/dist/src-astro/language-switcher/LanguageSwitcher.astro +3 -1
- package/dist/src-astro/layout-app/AppLayout.astro +12 -12
- package/dist/src-astro/layout-app/SkeletonLoader.astro +191 -0
- package/dist/src-astro/layout-app/loading-classes.ts +55 -0
- package/dist/src-astro/layout-basic/BaseLayout.astro +6 -6
- package/dist/src-astro/layout-basic/index.ts +1 -9
- package/dist/src-astro/loading-overlay/LoadingOverlay.astro +147 -75
- package/dist/src-astro/loading-overlay/index.ts +1 -1
- package/dist/src-astro/sidebar/MobileNav.astro +55 -0
- package/dist/src-astro/sidebar/NavItem.astro +151 -0
- package/dist/src-astro/sidebar/Sidebar.astro +37 -98
- package/dist/src-astro/sidebar/SidebarNav.astro +10 -87
- package/dist/src-astro/sidebar/index.ts +3 -1
- package/dist/src-astro/sidebar/utils.ts +64 -0
- package/dist/src-astro/theme-switcher/ThemeSwitcher.astro +3 -1
- package/dist/src-astro/types/layout.ts +5 -0
- package/dist/src-astro/types/meta.ts +0 -6
- package/package.json +1 -1
- package/dist/src-astro/layout-basic/BaseLayoutBasic.astro +0 -17
- package/dist/src-astro/loading-overlay/types.ts +0 -12
@@ -3,17 +3,15 @@
|
|
3
3
|
* @component LoadingOverlay
|
4
4
|
*
|
5
5
|
* @description
|
6
|
-
* LoadingOverlay
|
7
|
-
*
|
8
|
-
* 为了避免频繁的视觉干扰,只有在加载时间超过指定延迟时间(默认1秒)时才显示。
|
6
|
+
* LoadingOverlay 组件是一个全屏加载遮罩层,用于在页面加载或路由切换时显示加载状态。
|
7
|
+
* 它提供了多种加载动画类型,支持延迟显示和背景模糊效果,确保良好的用户体验。
|
9
8
|
*
|
10
9
|
* @design
|
11
10
|
* 设计理念:
|
12
|
-
* 1.
|
13
|
-
* 2.
|
14
|
-
* 3.
|
15
|
-
* 4.
|
16
|
-
* 5. 可定制性 - 支持自定义加载文本、样式和延迟时间
|
11
|
+
* 1. 用户体验优先 - 通过延迟显示避免闪烁,确保最小显示时间
|
12
|
+
* 2. 视觉反馈 - 提供多种动画类型,满足不同场景需求
|
13
|
+
* 3. 无障碍设计 - 支持键盘导航和屏幕阅读器
|
14
|
+
* 4. 性能优化 - 使用 CSS 动画和事件监听,避免阻塞主线程
|
17
15
|
*
|
18
16
|
* @usage
|
19
17
|
* 基本用法:
|
@@ -22,22 +20,53 @@
|
|
22
20
|
* import { LoadingOverlay } from '@coffic/cosy-ui';
|
23
21
|
* ---
|
24
22
|
*
|
25
|
-
* <LoadingOverlay />
|
23
|
+
* <LoadingOverlay text="正在加载..." />
|
26
24
|
* ```
|
27
25
|
*
|
28
|
-
*
|
26
|
+
* 自定义动画类型:
|
29
27
|
* ```astro
|
30
|
-
* <LoadingOverlay
|
28
|
+
* <LoadingOverlay
|
29
|
+
* text="处理中..."
|
30
|
+
* spinnerType="spinner"
|
31
|
+
* loadingDelay={500}
|
32
|
+
* />
|
33
|
+
* ```
|
34
|
+
*
|
35
|
+
* 无背景遮罩:
|
36
|
+
* ```astro
|
37
|
+
* <LoadingOverlay
|
38
|
+
* text="加载中..."
|
39
|
+
* showBackdrop={false}
|
40
|
+
* class="cosy:bg-transparent"
|
41
|
+
* />
|
31
42
|
* ```
|
32
43
|
*
|
33
44
|
* 自定义样式:
|
34
45
|
* ```astro
|
35
46
|
* <LoadingOverlay
|
36
|
-
* text="
|
37
|
-
*
|
38
|
-
*
|
47
|
+
* text="请稍候..."
|
48
|
+
* spinnerType="pulse"
|
49
|
+
* class="cosy:bg-blue-500/20"
|
39
50
|
* />
|
40
51
|
* ```
|
52
|
+
*
|
53
|
+
* @props
|
54
|
+
* - text?: string - 加载文本,默认为 "Loading..."
|
55
|
+
* - class?: string - 自定义 CSS 类名
|
56
|
+
* - showSpinner?: boolean - 是否显示加载动画,默认为 true
|
57
|
+
* - spinnerType?: 'dots' | 'spinner' | 'pulse' - 加载动画类型,默认为 'dots'
|
58
|
+
* - loadingDelay?: number - 延迟显示时间(毫秒),默认为 1000ms
|
59
|
+
* - showBackdrop?: boolean - 是否显示背景模糊遮罩,默认为 true
|
60
|
+
*
|
61
|
+
* @slots
|
62
|
+
* 此组件没有插槽,所有内容通过 props 配置
|
63
|
+
*
|
64
|
+
* @events
|
65
|
+
* 组件会自动监听以下 Astro 事件:
|
66
|
+
* - astro:page-load - 页面加载完成时隐藏
|
67
|
+
* - astro:before-preparation - 页面准备前显示
|
68
|
+
* - astro:before-swap - 路由切换前显示
|
69
|
+
* - astro:after-swap - 路由切换后隐藏
|
41
70
|
*/
|
42
71
|
|
43
72
|
import '../../style.ts';
|
@@ -53,6 +82,8 @@ export interface Props {
|
|
53
82
|
spinnerType?: 'dots' | 'spinner' | 'pulse';
|
54
83
|
/** 延迟显示时间(毫秒),默认1000ms */
|
55
84
|
loadingDelay?: number;
|
85
|
+
/** 是否显示背景模糊遮罩,默认true */
|
86
|
+
showBackdrop?: boolean;
|
56
87
|
}
|
57
88
|
|
58
89
|
const {
|
@@ -61,23 +92,26 @@ const {
|
|
61
92
|
showSpinner = true,
|
62
93
|
spinnerType = 'dots',
|
63
94
|
loadingDelay = 1000,
|
95
|
+
showBackdrop = true,
|
64
96
|
} = Astro.props;
|
65
97
|
|
66
98
|
// 生成唯一的 ID
|
67
|
-
const overlayId = `loading-overlay
|
99
|
+
const overlayId = `loading-overlay`;
|
68
100
|
---
|
69
101
|
|
70
102
|
<div
|
71
103
|
id={overlayId}
|
104
|
+
transition:persist
|
72
105
|
class:list={[
|
73
|
-
'cosy:fixed cosy:inset-0 cosy:z-[9999]
|
106
|
+
'cosy:fixed cosy:inset-0 cosy:z-[9999]',
|
107
|
+
showBackdrop ? 'cosy:bg-black/50 cosy:backdrop-blur-sm' : '',
|
74
108
|
'cosy:flex cosy:items-center cosy:justify-center',
|
75
109
|
'cosy:opacity-0 cosy:pointer-events-none cosy:transition-opacity cosy:duration-300',
|
76
110
|
className,
|
77
111
|
]}
|
78
112
|
data-loading-overlay>
|
79
113
|
<div
|
80
|
-
class="cosy:bg-
|
114
|
+
class="cosy:bg-accent cosy:rounded-lg cosy:shadow-xl cosy:p-6 cosy:max-w-sm cosy:w-full cosy:mx-4">
|
81
115
|
<div class="cosy:flex cosy:flex-col cosy:items-center cosy:space-y-4">
|
82
116
|
{
|
83
117
|
showSpinner && (
|
@@ -112,67 +146,105 @@ const overlayId = `loading-overlay-${Math.random().toString(36).substr(2, 9)}`;
|
|
112
146
|
</div>
|
113
147
|
</div>
|
114
148
|
|
115
|
-
<script define:vars={{ overlayId, loadingDelay }}>
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
149
|
+
<script is:inline define:vars={{ overlayId, loadingDelay }}>
|
150
|
+
let showTimeout = null;
|
151
|
+
let hideTimeout = null;
|
152
|
+
let displayStartTime = null;
|
153
|
+
let loaded = false;
|
154
|
+
const minDisplayTime = 1000;
|
155
|
+
|
156
|
+
const reset = () => {
|
157
|
+
displayStartTime = null;
|
158
|
+
};
|
159
|
+
|
160
|
+
const isDomDisplayed = () => {
|
161
|
+
const overlay = document.getElementById(overlayId);
|
162
|
+
if (!overlay) {
|
163
|
+
return false;
|
164
|
+
}
|
165
|
+
return (
|
166
|
+
overlay.style.opacity === '1' && overlay.style.pointerEvents === 'auto'
|
167
|
+
);
|
168
|
+
};
|
169
|
+
|
170
|
+
// 显示加载弹出层的函数
|
171
|
+
const showOverlay = () => {
|
172
|
+
if (isDomDisplayed() || loaded) return;
|
173
|
+
|
174
|
+
// 设置延迟显示
|
175
|
+
showTimeout = setTimeout(() => {
|
176
|
+
const overlay = document.getElementById(overlayId);
|
177
|
+
if (!overlay) {
|
178
|
+
return;
|
143
179
|
}
|
144
180
|
|
145
|
-
|
146
|
-
|
181
|
+
if (loaded) {
|
182
|
+
return;
|
183
|
+
}
|
184
|
+
|
185
|
+
overlay.style.opacity = '1';
|
186
|
+
overlay.style.pointerEvents = 'auto';
|
187
|
+
displayStartTime = Date.now();
|
188
|
+
}, loadingDelay);
|
189
|
+
};
|
190
|
+
|
191
|
+
// 隐藏加载弹出层的函数
|
192
|
+
const hideOverlay = (reason) => {
|
193
|
+
console.log('LoadingOverlay: hideOverlay', reason);
|
194
|
+
|
195
|
+
// 清除显示延迟
|
196
|
+
if (showTimeout) {
|
197
|
+
clearTimeout(showTimeout);
|
198
|
+
showTimeout = null;
|
199
|
+
}
|
200
|
+
|
201
|
+
// 如果没有显示
|
202
|
+
if (!isDomDisplayed()) {
|
203
|
+
reset();
|
204
|
+
return;
|
205
|
+
}
|
206
|
+
|
207
|
+
hideTimeout = setTimeout(() => {
|
208
|
+
const overlay = document.getElementById(overlayId);
|
209
|
+
if (!overlay) {
|
210
|
+
return;
|
211
|
+
}
|
147
212
|
|
148
213
|
overlay.style.opacity = '0';
|
149
214
|
overlay.style.pointerEvents = 'none';
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
215
|
+
reset();
|
216
|
+
}, minDisplayTime);
|
217
|
+
};
|
218
|
+
|
219
|
+
// 监听 Astro 页面过渡事件
|
220
|
+
document.addEventListener('astro:page-load', () => {
|
221
|
+
loaded = true;
|
222
|
+
hideOverlay('astro:page-load');
|
223
|
+
});
|
224
|
+
|
225
|
+
document.addEventListener('astro:before-preparation', () => {
|
226
|
+
loaded = false;
|
227
|
+
showOverlay('astro:before-preparation');
|
228
|
+
});
|
229
|
+
|
230
|
+
// 监听路由变化
|
231
|
+
document.addEventListener('astro:before-swap', () => {
|
232
|
+
loaded = false;
|
233
|
+
showOverlay('astro:before-swap');
|
234
|
+
});
|
235
|
+
|
236
|
+
document.addEventListener('astro:after-swap', () => {
|
237
|
+
loaded = true;
|
238
|
+
hideOverlay('astro:after-swap');
|
239
|
+
});
|
240
|
+
|
241
|
+
// 页面卸载时清理定时器
|
242
|
+
window.addEventListener('beforeunload', () => {
|
243
|
+
if (showTimeout) {
|
244
|
+
clearTimeout(showTimeout);
|
245
|
+
}
|
246
|
+
if (hideTimeout) {
|
247
|
+
clearTimeout(hideTimeout);
|
248
|
+
}
|
249
|
+
});
|
178
250
|
</script>
|
@@ -1,2 +1,2 @@
|
|
1
1
|
export { default as LoadingOverlay } from './LoadingOverlay.astro';
|
2
|
-
export type { LoadingOverlayProps } from './
|
2
|
+
export type { Props as LoadingOverlayProps } from './LoadingOverlay.astro';
|
@@ -0,0 +1,55 @@
|
|
1
|
+
---
|
2
|
+
/**
|
3
|
+
* MobileNav组件
|
4
|
+
*
|
5
|
+
* 移动端导航栏组件
|
6
|
+
*/
|
7
|
+
|
8
|
+
import { MenuIcon } from '../../index-astro';
|
9
|
+
import { isPathMatch } from '../../src/utils/path.ts';
|
10
|
+
import type { ISidebarItem } from '../types/sidebar.ts';
|
11
|
+
|
12
|
+
interface Props {
|
13
|
+
/**
|
14
|
+
* 侧边栏项目
|
15
|
+
*/
|
16
|
+
sidebarItems: ISidebarItem[];
|
17
|
+
|
18
|
+
/**
|
19
|
+
* 当前路径
|
20
|
+
*/
|
21
|
+
currentPath: string;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* 是否开启调试模式
|
25
|
+
*/
|
26
|
+
debug?: boolean;
|
27
|
+
}
|
28
|
+
|
29
|
+
const { sidebarItems, currentPath, debug = false } = Astro.props;
|
30
|
+
|
31
|
+
const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
32
|
+
|
33
|
+
// 获取当前活动的一级导航项
|
34
|
+
const currentSection = sidebarItems.find((section) =>
|
35
|
+
section.items?.some((item) => isPathMatch(currentPath, item.href))
|
36
|
+
);
|
37
|
+
---
|
38
|
+
|
39
|
+
<div
|
40
|
+
class:list={[
|
41
|
+
'cosy:flex cosy:lg:hidden cosy:items-center cosy:justify-between cosy:px-4 cosy:py-2 cosy:border-b cosy:border-base-300 cosy:bg-base-100 cosy:relative cosy:z-10',
|
42
|
+
debugClass,
|
43
|
+
]}>
|
44
|
+
<div class="cosy:flex cosy:items-center cosy:gap-2">
|
45
|
+
<button
|
46
|
+
type="button"
|
47
|
+
class="cosy:p-2 cosy:btn cosy:btn-ghost cosy:btn-sm"
|
48
|
+
data-modal-target="mobile-sidebar">
|
49
|
+
<MenuIcon class="cosy:w-5 cosy:h-5" />
|
50
|
+
</button>
|
51
|
+
<span class="cosy:font-medium cosy:text-sm">
|
52
|
+
{currentSection?.text || '导航'}
|
53
|
+
</span>
|
54
|
+
</div>
|
55
|
+
</div>
|
@@ -0,0 +1,151 @@
|
|
1
|
+
---
|
2
|
+
/**
|
3
|
+
* NavItem组件
|
4
|
+
*
|
5
|
+
* 递归渲染导航项,支持无限层级
|
6
|
+
*/
|
7
|
+
|
8
|
+
import { isPathMatch } from '../../src/utils/path.ts';
|
9
|
+
import '../../style.ts';
|
10
|
+
import type { ISidebarItem } from '../types/sidebar.ts';
|
11
|
+
|
12
|
+
interface Props {
|
13
|
+
/**
|
14
|
+
* 导航项
|
15
|
+
*/
|
16
|
+
item: ISidebarItem;
|
17
|
+
|
18
|
+
/**
|
19
|
+
* 当前路径
|
20
|
+
*/
|
21
|
+
currentPath: string;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* 嵌套层级
|
25
|
+
* @default 0
|
26
|
+
*/
|
27
|
+
level?: number;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* 是否开启调试模式
|
31
|
+
*/
|
32
|
+
debug?: boolean;
|
33
|
+
}
|
34
|
+
|
35
|
+
const { item, currentPath, level = 0, debug = false } = Astro.props;
|
36
|
+
|
37
|
+
const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
38
|
+
const isActive = isPathMatch(currentPath, item.href);
|
39
|
+
const badgeSize = level === 0 ? 'cosy:badge-sm' : 'cosy:badge-xs';
|
40
|
+
---
|
41
|
+
|
42
|
+
<li class:list={[debugClass, level === 0 ? 'cosy:no-underline' : '']}>
|
43
|
+
<a
|
44
|
+
data-sidebar-item
|
45
|
+
data-current-path={currentPath}
|
46
|
+
data-href={item.href}
|
47
|
+
href={item.href}
|
48
|
+
class:list={[
|
49
|
+
'cosy:hover:bg-base-300',
|
50
|
+
level === 0 ? 'cosy:no-underline' : '',
|
51
|
+
{ 'cosy:menu-active': isActive },
|
52
|
+
debugClass,
|
53
|
+
]}>
|
54
|
+
{item.text}
|
55
|
+
{
|
56
|
+
item.badge !== undefined && item.badge !== null && (
|
57
|
+
<span class={`cosy:badge ${badgeSize} cosy:ml-2`}>{item.badge}</span>
|
58
|
+
)
|
59
|
+
}
|
60
|
+
</a>
|
61
|
+
{
|
62
|
+
item.items && item.items.length > 0 && (
|
63
|
+
<ul class:list={[debugClass]}>
|
64
|
+
{item.items.map((subitem) => (
|
65
|
+
<Astro.self
|
66
|
+
item={subitem}
|
67
|
+
currentPath={currentPath}
|
68
|
+
level={level + 1}
|
69
|
+
debug={debug}
|
70
|
+
/>
|
71
|
+
))}
|
72
|
+
</ul>
|
73
|
+
)
|
74
|
+
}
|
75
|
+
</li>
|
76
|
+
|
77
|
+
<script>
|
78
|
+
// 简化的路径匹配函数
|
79
|
+
function isPathMatch(currentPath: string, href: string): boolean {
|
80
|
+
if (href === currentPath) return true;
|
81
|
+
if (href === '/') return currentPath === '/';
|
82
|
+
if (href.endsWith('/')) {
|
83
|
+
return currentPath.startsWith(href) || currentPath === href.slice(0, -1);
|
84
|
+
}
|
85
|
+
return currentPath.startsWith(href);
|
86
|
+
}
|
87
|
+
|
88
|
+
// 立即处理导航项点击,更新高亮状态
|
89
|
+
document.addEventListener('click', (event) => {
|
90
|
+
const target = event.target as HTMLElement;
|
91
|
+
const navItem = target.closest('[data-sidebar-item]') as HTMLAnchorElement;
|
92
|
+
|
93
|
+
if (navItem) {
|
94
|
+
// 立即移除所有导航项的高亮状态
|
95
|
+
const allNavItems = document.querySelectorAll('[data-sidebar-item]');
|
96
|
+
allNavItems.forEach((item) => {
|
97
|
+
item.classList.remove('cosy:menu-active');
|
98
|
+
});
|
99
|
+
|
100
|
+
// 立即为当前点击的导航项添加高亮状态
|
101
|
+
navItem.classList.add('cosy:menu-active');
|
102
|
+
|
103
|
+
// 可选:添加一个短暂的视觉反馈
|
104
|
+
navItem.style.transition = 'background-color 0.2s ease';
|
105
|
+
setTimeout(() => {
|
106
|
+
navItem.style.transition = '';
|
107
|
+
}, 200);
|
108
|
+
}
|
109
|
+
});
|
110
|
+
|
111
|
+
// 处理 Astro 页面切换后的状态恢复
|
112
|
+
document.addEventListener('astro:page-load', () => {
|
113
|
+
const currentPath = window.location.pathname;
|
114
|
+
const allNavItems = document.querySelectorAll(
|
115
|
+
'[data-sidebar-item]'
|
116
|
+
) as NodeListOf<HTMLAnchorElement>;
|
117
|
+
|
118
|
+
// 移除所有高亮状态
|
119
|
+
allNavItems.forEach((item) => {
|
120
|
+
item.classList.remove('cosy:menu-active');
|
121
|
+
});
|
122
|
+
|
123
|
+
// 找到最精确匹配的导航项
|
124
|
+
let bestMatch: HTMLAnchorElement | null = null;
|
125
|
+
let bestMatchLength = 0;
|
126
|
+
|
127
|
+
allNavItems.forEach((item) => {
|
128
|
+
const href = item.getAttribute('data-href');
|
129
|
+
if (href) {
|
130
|
+
// 精确匹配优先
|
131
|
+
if (href === currentPath) {
|
132
|
+
bestMatch = item;
|
133
|
+
bestMatchLength = href.length;
|
134
|
+
}
|
135
|
+
// 如果没有精确匹配,选择最长的前缀匹配
|
136
|
+
else if (
|
137
|
+
currentPath.startsWith(href) &&
|
138
|
+
href.length > bestMatchLength
|
139
|
+
) {
|
140
|
+
bestMatch = item;
|
141
|
+
bestMatchLength = href.length;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
});
|
145
|
+
|
146
|
+
// 只高亮最匹配的项
|
147
|
+
if (bestMatch) {
|
148
|
+
(bestMatch as HTMLAnchorElement).classList.add('cosy:menu-active');
|
149
|
+
}
|
150
|
+
});
|
151
|
+
</script>
|
@@ -22,8 +22,9 @@
|
|
22
22
|
*/
|
23
23
|
|
24
24
|
import '../../style.ts';
|
25
|
-
import {
|
26
|
-
import {
|
25
|
+
import { SidebarNav, Modal } from '../../index-astro';
|
26
|
+
import { getMarginTopClass, getMarginBottomClass } from './utils.ts';
|
27
|
+
import MobileNav from './MobileNav.astro';
|
27
28
|
import type { ISidebarProps } from '../../index-astro';
|
28
29
|
|
29
30
|
export interface Props extends ISidebarProps {}
|
@@ -40,65 +41,22 @@ const {
|
|
40
41
|
const currentPath = Astro.url.pathname;
|
41
42
|
|
42
43
|
const debugClass = debug ? 'cosy:border cosy:border-red-500' : '';
|
43
|
-
|
44
|
-
function getVerticalMarginTopClasses(marginTop: string) {
|
45
|
-
if (marginTop === 'none') return 'cosy:mt-0';
|
46
|
-
if (marginTop === 'xs') return 'cosy:mt-1';
|
47
|
-
if (marginTop === 'sm') return 'cosy:mt-2';
|
48
|
-
if (marginTop === 'md') return 'cosy:mt-4';
|
49
|
-
if (marginTop === 'lg') return 'cosy:mt-6';
|
50
|
-
if (marginTop === 'xl') return 'cosy:mt-8';
|
51
|
-
if (marginTop === '2xl') return 'cosy:mt-10';
|
52
|
-
if (marginTop === '3xl') return 'cosy:mt-12';
|
53
|
-
if (marginTop === '4xl') return 'cosy:mt-16';
|
54
|
-
if (marginTop === '5xl') return 'cosy:mt-20';
|
55
|
-
return '';
|
56
|
-
}
|
57
|
-
|
58
|
-
function getVerticalMarginBottomClasses(marginBottom: string) {
|
59
|
-
if (marginBottom === 'none') return 'cosy:mb-0';
|
60
|
-
if (marginBottom === 'xs') return 'cosy:mb-1';
|
61
|
-
if (marginBottom === 'sm') return 'cosy:mb-2';
|
62
|
-
if (marginBottom === 'md') return 'cosy:mb-4';
|
63
|
-
if (marginBottom === 'lg') return 'cosy:mb-6';
|
64
|
-
if (marginBottom === 'xl') return 'cosy:mb-8';
|
65
|
-
if (marginBottom === '2xl') return 'cosy:mb-10';
|
66
|
-
if (marginBottom === '3xl') return 'cosy:mb-12';
|
67
|
-
if (marginBottom === '4xl') return 'cosy:mb-16';
|
68
|
-
if (marginBottom === '5xl') return 'cosy:mb-20';
|
69
|
-
return '';
|
70
|
-
}
|
71
|
-
|
72
|
-
// 获取当前活动的一级导航项
|
73
|
-
const currentSection = sidebarItems.find((section) =>
|
74
|
-
section.items?.some((item) => isPathMatch(currentPath, item.href))
|
75
|
-
);
|
76
44
|
---
|
77
45
|
|
78
46
|
{/* 移动端导航栏 */}
|
79
|
-
<
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
<div class="cosy:flex cosy:items-center cosy:gap-2">
|
85
|
-
<button
|
86
|
-
type="button"
|
87
|
-
class="cosy:p-2 cosy:btn cosy:btn-ghost cosy:btn-sm"
|
88
|
-
data-modal-target="mobile-sidebar">
|
89
|
-
<MenuIcon class="cosy:w-5 cosy:h-5" />
|
90
|
-
</button>
|
91
|
-
<span class="cosy:font-medium cosy:text-sm"
|
92
|
-
>{currentSection?.text || '导航'}</span
|
93
|
-
>
|
94
|
-
</div>
|
95
|
-
</div>
|
47
|
+
<MobileNav
|
48
|
+
sidebarItems={sidebarItems}
|
49
|
+
currentPath={currentPath}
|
50
|
+
debug={debug}
|
51
|
+
/>
|
96
52
|
|
97
53
|
{/* 移动端侧边栏弹出层 */}
|
98
54
|
<Modal
|
99
55
|
id="mobile-sidebar"
|
100
56
|
class="cosy:mx-4 cosy:lg:w-80 cosy:w-[calc(100vw-2rem)] cosy:max-w-full">
|
101
|
-
<div
|
57
|
+
<div
|
58
|
+
class="cosy:h-[calc(100vh-8rem)] cosy:overflow-y-auto"
|
59
|
+
data-mobile-sidebar-content>
|
102
60
|
<SidebarNav
|
103
61
|
sidebarItems={sidebarItems}
|
104
62
|
currentPath={currentPath}
|
@@ -117,11 +75,12 @@ const currentSection = sidebarItems.find((section) =>
|
|
117
75
|
className,
|
118
76
|
debugClass,
|
119
77
|
'cosy:hidden cosy:lg:block',
|
120
|
-
|
121
|
-
|
78
|
+
getMarginTopClass(marginTop),
|
79
|
+
getMarginBottomClass(marginBottom),
|
122
80
|
]}>
|
123
81
|
<div
|
124
|
-
class="cosy:top-16 cosy:sticky cosy:pb-48 cosy:h-[calc(100vh-0rem)] cosy:overflow-y-auto"
|
82
|
+
class="cosy:top-16 cosy:sticky cosy:pb-48 cosy:h-[calc(100vh-0rem)] cosy:overflow-y-auto"
|
83
|
+
data-desktop-sidebar-content>
|
125
84
|
<SidebarNav
|
126
85
|
sidebarItems={sidebarItems}
|
127
86
|
currentPath={currentPath}
|
@@ -131,62 +90,42 @@ const currentSection = sidebarItems.find((section) =>
|
|
131
90
|
</aside>
|
132
91
|
|
133
92
|
<script>
|
93
|
+
import { saveScrollPosition, restoreScrollPosition } from './utils.ts';
|
94
|
+
|
134
95
|
// 处理侧边栏滚动位置保存和恢复
|
135
96
|
document.addEventListener('astro:before-preparation', () => {
|
136
|
-
//
|
137
|
-
const
|
138
|
-
'
|
97
|
+
// 保存桌面端滚动位置
|
98
|
+
const desktopContent = document.querySelector(
|
99
|
+
'[data-desktop-sidebar-content]'
|
139
100
|
);
|
140
|
-
|
141
|
-
|
142
|
-
if (desktopSidebarContent) {
|
143
|
-
localStorage.setItem(
|
144
|
-
'sidebarScrollPosition',
|
145
|
-
desktopSidebarContent.scrollTop.toString()
|
146
|
-
);
|
101
|
+
if (desktopContent) {
|
102
|
+
saveScrollPosition(desktopContent, 'sidebarScrollPosition');
|
147
103
|
}
|
148
104
|
|
149
|
-
// 获取移动端侧边栏滚动容器
|
150
|
-
const mobileSidebarContent = document.querySelector(
|
151
|
-
'.cosy\\:h-\\[calc\\(100vh-8rem\\)\\].cosy\\:overflow-y-auto'
|
152
|
-
);
|
153
|
-
|
154
105
|
// 保存移动端滚动位置
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
);
|
106
|
+
const mobileContent = document.querySelector(
|
107
|
+
'[data-mobile-sidebar-content]'
|
108
|
+
);
|
109
|
+
if (mobileContent) {
|
110
|
+
saveScrollPosition(mobileContent, 'mobileSidebarScrollPosition');
|
160
111
|
}
|
161
112
|
});
|
162
113
|
|
163
114
|
document.addEventListener('astro:page-load', () => {
|
164
|
-
//
|
165
|
-
const
|
166
|
-
'
|
115
|
+
// 恢复桌面端滚动位置
|
116
|
+
const desktopContent = document.querySelector(
|
117
|
+
'[data-desktop-sidebar-content]'
|
167
118
|
);
|
168
|
-
|
169
|
-
|
170
|
-
if (desktopSidebarContent) {
|
171
|
-
const savedPosition = localStorage.getItem('sidebarScrollPosition');
|
172
|
-
if (savedPosition) {
|
173
|
-
desktopSidebarContent.scrollTop = parseInt(savedPosition, 10);
|
174
|
-
}
|
119
|
+
if (desktopContent) {
|
120
|
+
restoreScrollPosition(desktopContent, 'sidebarScrollPosition');
|
175
121
|
}
|
176
122
|
|
177
|
-
// 获取移动端侧边栏滚动容器
|
178
|
-
const mobileSidebarContent = document.querySelector(
|
179
|
-
'.cosy\\:h-\\[calc\\(100vh-8rem\\)\\].cosy\\:overflow-y-auto'
|
180
|
-
);
|
181
|
-
|
182
123
|
// 恢复移动端滚动位置
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
mobileSidebarContent.scrollTop = parseInt(savedMobilePosition, 10);
|
189
|
-
}
|
124
|
+
const mobileContent = document.querySelector(
|
125
|
+
'[data-mobile-sidebar-content]'
|
126
|
+
);
|
127
|
+
if (mobileContent) {
|
128
|
+
restoreScrollPosition(mobileContent, 'mobileSidebarScrollPosition');
|
190
129
|
}
|
191
130
|
});
|
192
131
|
</script>
|