@coffic/cosy-ui 0.9.17 → 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.
Files changed (27) hide show
  1. package/dist/app.css +1 -1
  2. package/dist/src-astro/code-container/ButtonCodeToggle.astro +0 -1
  3. package/dist/src-astro/code-container/ButtonCopyCode.astro +0 -2
  4. package/dist/src-astro/code-container/CodeContainer.astro +0 -3
  5. package/dist/src-astro/code-container/CodeToolbar.astro +0 -2
  6. package/dist/src-astro/footer/Footer.astro +1 -0
  7. package/dist/src-astro/header/Header.astro +3 -44
  8. package/dist/src-astro/header/LogoLink.astro +87 -0
  9. package/dist/src-astro/language-switcher/LanguageSwitcher.astro +3 -1
  10. package/dist/src-astro/layout-app/AppLayout.astro +12 -12
  11. package/dist/src-astro/layout-app/SkeletonLoader.astro +191 -0
  12. package/dist/src-astro/layout-app/loading-classes.ts +55 -0
  13. package/dist/src-astro/layout-basic/BaseLayout.astro +6 -5
  14. package/dist/src-astro/layout-basic/index.ts +1 -9
  15. package/dist/src-astro/loading-overlay/LoadingOverlay.astro +150 -60
  16. package/dist/src-astro/loading-overlay/index.ts +1 -1
  17. package/dist/src-astro/sidebar/MobileNav.astro +55 -0
  18. package/dist/src-astro/sidebar/NavItem.astro +151 -0
  19. package/dist/src-astro/sidebar/Sidebar.astro +37 -98
  20. package/dist/src-astro/sidebar/SidebarNav.astro +10 -87
  21. package/dist/src-astro/sidebar/index.ts +3 -1
  22. package/dist/src-astro/sidebar/utils.ts +64 -0
  23. package/dist/src-astro/theme-switcher/ThemeSwitcher.astro +3 -1
  24. package/dist/src-astro/types/layout.ts +5 -0
  25. package/package.json +1 -1
  26. package/dist/src-astro/layout-basic/BaseLayoutBasic.astro +0 -17
  27. package/dist/src-astro/loading-overlay/types.ts +0 -10
@@ -3,15 +3,15 @@
3
3
  * @component LoadingOverlay
4
4
  *
5
5
  * @description
6
- * LoadingOverlay 组件是一个全屏加载弹出层,用于在页面跳转时显示加载状态。
7
- * 它监听 Astro 的页面过渡事件,在跳转开始时显示,跳转完成后隐藏。
6
+ * LoadingOverlay 组件是一个全屏加载遮罩层,用于在页面加载或路由切换时显示加载状态。
7
+ * 它提供了多种加载动画类型,支持延迟显示和背景模糊效果,确保良好的用户体验。
8
8
  *
9
9
  * @design
10
10
  * 设计理念:
11
- * 1. 全屏覆盖 - 使用固定定位覆盖整个视口
12
- * 2. 居中显示 - 加载指示器在屏幕中央显示
13
- * 3. 平滑动画 - 使用淡入淡出动画效果
14
- * 4. 可定制性 - 支持自定义加载文本和样式
11
+ * 1. 用户体验优先 - 通过延迟显示避免闪烁,确保最小显示时间
12
+ * 2. 视觉反馈 - 提供多种动画类型,满足不同场景需求
13
+ * 3. 无障碍设计 - 支持键盘导航和屏幕阅读器
14
+ * 4. 性能优化 - 使用 CSS 动画和事件监听,避免阻塞主线程
15
15
  *
16
16
  * @usage
17
17
  * 基本用法:
@@ -20,21 +20,53 @@
20
20
  * import { LoadingOverlay } from '@coffic/cosy-ui';
21
21
  * ---
22
22
  *
23
- * <LoadingOverlay />
23
+ * <LoadingOverlay text="正在加载..." />
24
24
  * ```
25
25
  *
26
- * 自定义文本:
26
+ * 自定义动画类型:
27
27
  * ```astro
28
- * <LoadingOverlay text="页面加载中..." />
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
+ * />
29
42
  * ```
30
43
  *
31
44
  * 自定义样式:
32
45
  * ```astro
33
46
  * <LoadingOverlay
34
- * text="正在跳转..."
35
- * class="custom-loading-overlay"
47
+ * text="请稍候..."
48
+ * spinnerType="pulse"
49
+ * class="cosy:bg-blue-500/20"
36
50
  * />
37
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 - 路由切换后隐藏
38
70
  */
39
71
 
40
72
  import '../../style.ts';
@@ -48,30 +80,38 @@ export interface Props {
48
80
  showSpinner?: boolean;
49
81
  /** 加载动画类型 */
50
82
  spinnerType?: 'dots' | 'spinner' | 'pulse';
83
+ /** 延迟显示时间(毫秒),默认1000ms */
84
+ loadingDelay?: number;
85
+ /** 是否显示背景模糊遮罩,默认true */
86
+ showBackdrop?: boolean;
51
87
  }
52
88
 
53
89
  const {
54
- text = '页面加载中...',
90
+ text = 'Loading...',
55
91
  class: className,
56
92
  showSpinner = true,
57
93
  spinnerType = 'dots',
94
+ loadingDelay = 1000,
95
+ showBackdrop = true,
58
96
  } = Astro.props;
59
97
 
60
98
  // 生成唯一的 ID
61
- const overlayId = `loading-overlay-${Math.random().toString(36).substr(2, 9)}`;
99
+ const overlayId = `loading-overlay`;
62
100
  ---
63
101
 
64
102
  <div
65
103
  id={overlayId}
104
+ transition:persist
66
105
  class:list={[
67
- 'cosy:fixed cosy:inset-0 cosy:z-[9999] cosy:bg-black/50 cosy:backdrop-blur-sm',
106
+ 'cosy:fixed cosy:inset-0 cosy:z-[9999]',
107
+ showBackdrop ? 'cosy:bg-black/50 cosy:backdrop-blur-sm' : '',
68
108
  'cosy:flex cosy:items-center cosy:justify-center',
69
109
  'cosy:opacity-0 cosy:pointer-events-none cosy:transition-opacity cosy:duration-300',
70
110
  className,
71
111
  ]}
72
112
  data-loading-overlay>
73
113
  <div
74
- class="cosy:bg-white cosy:dark:bg-gray-800 cosy:rounded-lg cosy:shadow-xl cosy:p-6 cosy:max-w-sm cosy:w-full cosy:mx-4">
114
+ class="cosy:bg-accent cosy:rounded-lg cosy:shadow-xl cosy:p-6 cosy:max-w-sm cosy:w-full cosy:mx-4">
75
115
  <div class="cosy:flex cosy:flex-col cosy:items-center cosy:space-y-4">
76
116
  {
77
117
  showSpinner && (
@@ -106,55 +146,105 @@ const overlayId = `loading-overlay-${Math.random().toString(36).substr(2, 9)}`;
106
146
  </div>
107
147
  </div>
108
148
 
109
- <script define:vars={{ overlayId }}>
110
- // 获取加载弹出层元素
111
- const overlay = document.getElementById(overlayId);
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;
112
155
 
113
- if (overlay) {
114
- console.log('LoadingOverlay initialized with ID:', overlayId);
156
+ const reset = () => {
157
+ displayStartTime = null;
158
+ };
115
159
 
116
- // 监听 Astro 页面过渡事件
117
- document.addEventListener('astro:page-load', () => {
118
- console.log('astro:page-load - hiding overlay');
119
- overlay.style.opacity = '0';
120
- overlay.style.pointerEvents = 'none';
121
- });
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
+ };
122
169
 
123
- document.addEventListener('astro:before-preparation', () => {
124
- console.log('astro:before-preparation - showing overlay');
125
- overlay.style.opacity = '1';
126
- overlay.style.pointerEvents = 'auto';
127
- });
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;
179
+ }
180
+
181
+ if (loaded) {
182
+ return;
183
+ }
128
184
 
129
- // 监听路由变化
130
- document.addEventListener('astro:before-swap', () => {
131
- console.log('astro:before-swap - showing overlay');
132
185
  overlay.style.opacity = '1';
133
186
  overlay.style.pointerEvents = 'auto';
134
- });
135
-
136
- document.addEventListener('astro:after-swap', () => {
137
- console.log('astro:after-swap - hiding overlay');
138
- setTimeout(() => {
139
- overlay.style.opacity = '0';
140
- overlay.style.pointerEvents = 'none';
141
- }, 100);
142
- });
143
-
144
- // 监听所有 Astro 相关事件
145
- const astroEvents = [
146
- 'astro:before-preparation',
147
- 'astro:after-preparation',
148
- 'astro:before-swap',
149
- 'astro:after-swap',
150
- 'astro:page-load',
151
- 'astro:page-loading',
152
- ];
153
-
154
- astroEvents.forEach((eventName) => {
155
- document.addEventListener(eventName, (e) => {
156
- console.log(`Event triggered: ${eventName}`, e);
157
- });
158
- });
159
- }
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
+ }
212
+
213
+ overlay.style.opacity = '0';
214
+ overlay.style.pointerEvents = 'none';
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
+ });
160
250
  </script>
@@ -1,2 +1,2 @@
1
1
  export { default as LoadingOverlay } from './LoadingOverlay.astro';
2
- export type { LoadingOverlayProps } from './types';
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 { isPathMatch } from '../../src/utils/path.ts';
26
- import { MenuIcon, SidebarNav, Modal } from '../../index-astro';
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
- <div
80
- class:list={[
81
- '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',
82
- debugClass,
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 class="cosy:h-[calc(100vh-8rem)] cosy:overflow-y-auto">
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
- getVerticalMarginTopClasses(marginTop),
121
- getVerticalMarginBottomClasses(marginBottom),
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 desktopSidebarContent = document.querySelector(
138
- 'aside[data-sidebar] .cosy\\:overflow-y-auto'
97
+ // 保存桌面端滚动位置
98
+ const desktopContent = document.querySelector(
99
+ '[data-desktop-sidebar-content]'
139
100
  );
140
-
141
- // 保存滚动位置到localStorage
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
- if (mobileSidebarContent) {
156
- localStorage.setItem(
157
- 'mobileSidebarScrollPosition',
158
- mobileSidebarContent.scrollTop.toString()
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 desktopSidebarContent = document.querySelector(
166
- 'aside[data-sidebar] .cosy\\:overflow-y-auto'
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
- if (mobileSidebarContent) {
184
- const savedMobilePosition = localStorage.getItem(
185
- 'mobileSidebarScrollPosition'
186
- );
187
- if (savedMobilePosition) {
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>