@coffic/cosy-ui 0.1.29 → 0.2.2
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/README.md +43 -22
- package/dist/app.css +1 -0
- package/dist/assets/logo-rounded.png +0 -0
- package/dist/assets/logo.png +0 -0
- package/dist/components/base/Alert.astro +186 -0
- package/dist/components/base/Button.astro +103 -0
- package/dist/components/base/Image.astro +291 -0
- package/dist/components/base/Link.astro +131 -0
- package/dist/components/containers/Container.astro +103 -0
- package/dist/components/containers/Main.astro +167 -0
- package/dist/components/containers/Section.astro +145 -0
- package/dist/components/containers/index.ts +3 -0
- package/dist/components/data-display/Blog.astro +195 -0
- package/dist/components/data-display/TeamMember.astro +135 -0
- package/dist/components/data-display/TeamMembers.astro +101 -0
- package/dist/components/display/Banner.astro +57 -0
- package/dist/components/display/Card.astro +135 -0
- package/dist/components/display/CodeBlock.astro +147 -0
- package/dist/components/display/CodeExample.astro +330 -0
- package/dist/components/display/Hero.astro +119 -0
- package/dist/components/display/Modal.astro +115 -0
- package/dist/components/icons/AlertTriangle.astro +35 -0
- package/dist/components/icons/CalendarIcon.astro +38 -0
- package/dist/components/icons/CheckCircle.astro +36 -0
- package/dist/components/icons/CheckIcon.astro +38 -0
- package/dist/components/icons/ClipboardIcon.astro +39 -0
- package/dist/components/icons/CloseIcon.astro +38 -0
- package/dist/components/icons/ErrorIcon.astro +35 -0
- package/dist/components/icons/GithubIcon.astro +31 -0
- package/dist/components/icons/InfoCircle.astro +37 -0
- package/dist/components/icons/InfoIcon.astro +38 -0
- package/dist/components/icons/LinkIcon.astro +39 -0
- package/dist/components/icons/LinkedinIcon.astro +31 -0
- package/dist/components/icons/MenuIcon.astro +41 -0
- package/dist/components/icons/SearchIcon.astro +40 -0
- package/dist/components/icons/SocialIcon.astro +100 -0
- package/dist/components/icons/SuccessIcon.astro +35 -0
- package/dist/components/icons/SunCloudyIcon.astro +45 -0
- package/dist/components/icons/TwitterIcon.astro +31 -0
- package/dist/components/icons/UserIcon.astro +35 -0
- package/dist/components/icons/WarningIcon.astro +38 -0
- package/dist/components/icons/XCircle.astro +37 -0
- package/dist/components/layouts/BaseLayout.astro +144 -0
- package/dist/components/layouts/DashboardLayout.astro +660 -0
- package/dist/components/layouts/DefaultLayout.astro +170 -0
- package/dist/components/layouts/DocumentationLayout.astro +469 -0
- package/dist/components/layouts/Flex.astro +138 -0
- package/dist/components/layouts/Footer.astro +284 -0
- package/dist/components/layouts/Grid.astro +182 -0
- package/dist/components/layouts/Header.astro +114 -0
- package/dist/components/layouts/LandingLayout.astro +388 -0
- package/dist/components/layouts/Stack.astro +149 -0
- package/dist/components/navigation/LanguageSwitcher.astro +81 -0
- package/dist/components/navigation/TableOfContents.astro +352 -0
- package/dist/components/navigation/ThemeSwitcher.astro +89 -0
- package/dist/components/typography/Article.astro +144 -0
- package/dist/components/typography/Heading.astro +205 -0
- package/dist/components/typography/Text.astro +187 -0
- package/dist/index.ts +70 -0
- package/dist/integration.ts +14 -0
- package/dist/style.ts +1 -0
- package/{src → dist}/types/footer.ts +1 -0
- package/dist/utils/theme.ts +55 -0
- package/package.json +67 -59
- package/index.ts +0 -18
- package/src/components/Alert.astro +0 -78
- package/src/components/Article.astro +0 -11
- package/src/components/Banner.astro +0 -49
- package/src/components/Blog.astro +0 -115
- package/src/components/Button.astro +0 -49
- package/src/components/Card.astro +0 -113
- package/src/components/CodeBlock.astro +0 -186
- package/src/components/Footer.astro +0 -148
- package/src/components/Header.astro +0 -305
- package/src/components/Hero.astro +0 -69
- package/src/components/Image.astro +0 -251
- package/src/components/Link.astro +0 -82
- package/src/components/Modal.astro +0 -67
- package/src/components/SocialIcon.astro +0 -36
- package/src/components/TeamMember.astro +0 -68
- package/src/components/TeamMembers.astro +0 -43
- package/src/env.d.ts +0 -0
- /package/{src/components → dist/components/base}/ThemeItem.astro +0 -0
- /package/{src → dist}/utils/social.ts +0 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
---
|
2
|
+
import SearchIcon from '../icons/SearchIcon.astro';
|
3
|
+
import Link from '../base/Link.astro';
|
4
|
+
import Button from '../base/Button.astro';
|
5
|
+
import Image from '../base/Image.astro';
|
6
|
+
import '../../app.css';
|
7
|
+
|
8
|
+
// 自定义图片元数据接口
|
9
|
+
interface ImageMetadata {
|
10
|
+
src: string;
|
11
|
+
width: number;
|
12
|
+
height: number;
|
13
|
+
format: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
interface Props {
|
17
|
+
logo: ImageMetadata;
|
18
|
+
languages?: Array<{ code: string; name: string }>;
|
19
|
+
currentLocale?: string;
|
20
|
+
navItems?: Array<{
|
21
|
+
href: string;
|
22
|
+
label: string;
|
23
|
+
match: (path: string) => boolean;
|
24
|
+
}>;
|
25
|
+
socialLinks?: Array<{
|
26
|
+
name: string;
|
27
|
+
url: string;
|
28
|
+
icon: any;
|
29
|
+
}>;
|
30
|
+
/**
|
31
|
+
* 是否固定在顶部
|
32
|
+
* @default true
|
33
|
+
*/
|
34
|
+
sticky?: boolean;
|
35
|
+
}
|
36
|
+
|
37
|
+
const {
|
38
|
+
logo,
|
39
|
+
navItems = [],
|
40
|
+
sticky = true,
|
41
|
+
} = Astro.props;
|
42
|
+
|
43
|
+
type NavItem = { href: string; label: string; match: (path: string) => boolean };
|
44
|
+
---
|
45
|
+
|
46
|
+
<header class:list={[
|
47
|
+
"navbar bg-accent/80 backdrop-blur border-b border-base-200 z-50 w-full",
|
48
|
+
{ "fixed top-0": sticky }
|
49
|
+
]}>
|
50
|
+
<div class="navbar-start">
|
51
|
+
<!-- 移动端菜单 -->
|
52
|
+
<div class="dropdown lg:hidden">
|
53
|
+
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
|
54
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
55
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
|
56
|
+
</svg>
|
57
|
+
</div>
|
58
|
+
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
59
|
+
{navItems.map((item: NavItem) => (
|
60
|
+
<li>
|
61
|
+
<Link href={item.href}>
|
62
|
+
{item.label}
|
63
|
+
</Link>
|
64
|
+
</li>
|
65
|
+
))}
|
66
|
+
</ul>
|
67
|
+
</div>
|
68
|
+
|
69
|
+
<Link href="/" class="btn btn-ghost">
|
70
|
+
<Image src={logo} alt="logo" class="w-10 h-10" />
|
71
|
+
</Link>
|
72
|
+
</div>
|
73
|
+
|
74
|
+
<!-- 桌面端导航 -->
|
75
|
+
<div class="navbar-center hidden lg:flex">
|
76
|
+
<ul class="menu menu-horizontal px-1">
|
77
|
+
{navItems.map((item: NavItem) => (
|
78
|
+
<li>
|
79
|
+
<Link href={item.href}>
|
80
|
+
{item.label}
|
81
|
+
</Link>
|
82
|
+
</li>
|
83
|
+
))}
|
84
|
+
</ul>
|
85
|
+
</div>
|
86
|
+
|
87
|
+
<div class="navbar-end">
|
88
|
+
<Button
|
89
|
+
variant="ghost"
|
90
|
+
size="sm"
|
91
|
+
class="btn-circle"
|
92
|
+
onClick="my_modal_1.showModal()"
|
93
|
+
>
|
94
|
+
<SearchIcon class="w-5 h-5" />
|
95
|
+
</Button>
|
96
|
+
</div>
|
97
|
+
</header>
|
98
|
+
|
99
|
+
{sticky && (
|
100
|
+
<div class="h-16"></div>
|
101
|
+
)}
|
102
|
+
|
103
|
+
<script define:vars={{ sticky }}>
|
104
|
+
if (sticky) {
|
105
|
+
const header = document.querySelector('header');
|
106
|
+
window.addEventListener('scroll', () => {
|
107
|
+
if (window.pageYOffset > 0) {
|
108
|
+
header?.classList.add('shadow-lg');
|
109
|
+
} else {
|
110
|
+
header?.classList.remove('shadow-lg');
|
111
|
+
}
|
112
|
+
});
|
113
|
+
}
|
114
|
+
</script>
|
@@ -0,0 +1,388 @@
|
|
1
|
+
---
|
2
|
+
/**
|
3
|
+
* LandingLayout组件
|
4
|
+
*
|
5
|
+
* 适用于落地页的布局,包含页眉、页脚和多个区块
|
6
|
+
*
|
7
|
+
* @example
|
8
|
+
* ```astro
|
9
|
+
* ---
|
10
|
+
* import LandingLayout from '../layouts/LandingLayout.astro';
|
11
|
+
* ---
|
12
|
+
*
|
13
|
+
* <LandingLayout
|
14
|
+
* title="产品名称 - 简短的产品描述"
|
15
|
+
* description="详细的产品描述,包含关键词"
|
16
|
+
* >
|
17
|
+
* <div slot="hero">
|
18
|
+
* <h1>产品标题</h1>
|
19
|
+
* <p>产品简介</p>
|
20
|
+
* <button>立即开始</button>
|
21
|
+
* </div>
|
22
|
+
*
|
23
|
+
* <section>
|
24
|
+
* <h2>产品特点</h2>
|
25
|
+
* <p>详细介绍产品特点...</p>
|
26
|
+
* </section>
|
27
|
+
* </LandingLayout>
|
28
|
+
* ```
|
29
|
+
*/
|
30
|
+
|
31
|
+
import BaseLayout from './BaseLayout.astro';
|
32
|
+
|
33
|
+
// 导入样式
|
34
|
+
import '../../app.css';
|
35
|
+
|
36
|
+
export interface NavItem {
|
37
|
+
href: string;
|
38
|
+
label: string;
|
39
|
+
isButton?: boolean;
|
40
|
+
}
|
41
|
+
|
42
|
+
export interface FooterLink {
|
43
|
+
href: string;
|
44
|
+
label: string;
|
45
|
+
}
|
46
|
+
|
47
|
+
export interface FooterSection {
|
48
|
+
title: string;
|
49
|
+
links: FooterLink[];
|
50
|
+
}
|
51
|
+
|
52
|
+
export interface SocialLink {
|
53
|
+
href: string;
|
54
|
+
icon: string;
|
55
|
+
label: string;
|
56
|
+
}
|
57
|
+
|
58
|
+
export interface Props {
|
59
|
+
/**
|
60
|
+
* 页面标题
|
61
|
+
*/
|
62
|
+
title: string;
|
63
|
+
|
64
|
+
/**
|
65
|
+
* 页面描述
|
66
|
+
*/
|
67
|
+
description?: string;
|
68
|
+
|
69
|
+
/**
|
70
|
+
* 页面关键词
|
71
|
+
*/
|
72
|
+
keywords?: string;
|
73
|
+
|
74
|
+
/**
|
75
|
+
* 品牌名称
|
76
|
+
* @default "品牌名称"
|
77
|
+
*/
|
78
|
+
brandName?: string;
|
79
|
+
|
80
|
+
/**
|
81
|
+
* 品牌Logo URL
|
82
|
+
*/
|
83
|
+
logoUrl?: string;
|
84
|
+
|
85
|
+
/**
|
86
|
+
* 导航链接
|
87
|
+
*/
|
88
|
+
navItems?: NavItem[];
|
89
|
+
|
90
|
+
/**
|
91
|
+
* 页脚链接分组
|
92
|
+
*/
|
93
|
+
footerSections?: FooterSection[];
|
94
|
+
|
95
|
+
/**
|
96
|
+
* 社交媒体链接
|
97
|
+
*/
|
98
|
+
socialLinks?: SocialLink[];
|
99
|
+
|
100
|
+
/**
|
101
|
+
* 版权信息
|
102
|
+
*/
|
103
|
+
copyright?: string;
|
104
|
+
|
105
|
+
/**
|
106
|
+
* 自定义头部内容
|
107
|
+
*/
|
108
|
+
head?: astroHTML.JSX.Element;
|
109
|
+
|
110
|
+
/**
|
111
|
+
* 页面类名
|
112
|
+
*/
|
113
|
+
class?: string;
|
114
|
+
|
115
|
+
/**
|
116
|
+
* 类名列表
|
117
|
+
*/
|
118
|
+
'class:list'?: any;
|
119
|
+
}
|
120
|
+
|
121
|
+
const {
|
122
|
+
title,
|
123
|
+
description,
|
124
|
+
keywords,
|
125
|
+
brandName = "品牌名称",
|
126
|
+
logoUrl,
|
127
|
+
navItems = [
|
128
|
+
{ href: "/", label: "首页" },
|
129
|
+
{ href: "/features", label: "特性" },
|
130
|
+
{ href: "/pricing", label: "价格" },
|
131
|
+
{ href: "/about", label: "关于我们" },
|
132
|
+
{ href: "/contact", label: "联系我们" },
|
133
|
+
{ href: "/signup", label: "注册", isButton: true }
|
134
|
+
],
|
135
|
+
footerSections = [
|
136
|
+
{
|
137
|
+
title: "产品",
|
138
|
+
links: [
|
139
|
+
{ href: "/features", label: "特性" },
|
140
|
+
{ href: "/pricing", label: "价格" },
|
141
|
+
{ href: "/docs", label: "文档" },
|
142
|
+
{ href: "/releases", label: "更新日志" }
|
143
|
+
]
|
144
|
+
},
|
145
|
+
{
|
146
|
+
title: "公司",
|
147
|
+
links: [
|
148
|
+
{ href: "/about", label: "关于我们" },
|
149
|
+
{ href: "/team", label: "团队" },
|
150
|
+
{ href: "/careers", label: "招聘" },
|
151
|
+
{ href: "/contact", label: "联系我们" }
|
152
|
+
]
|
153
|
+
},
|
154
|
+
{
|
155
|
+
title: "资源",
|
156
|
+
links: [
|
157
|
+
{ href: "/blog", label: "博客" },
|
158
|
+
{ href: "/community", label: "社区" },
|
159
|
+
{ href: "/support", label: "支持" }
|
160
|
+
]
|
161
|
+
},
|
162
|
+
{
|
163
|
+
title: "法律",
|
164
|
+
links: [
|
165
|
+
{ href: "/privacy", label: "隐私政策" },
|
166
|
+
{ href: "/terms", label: "服务条款" }
|
167
|
+
]
|
168
|
+
}
|
169
|
+
],
|
170
|
+
socialLinks = [
|
171
|
+
{ href: "https://twitter.com", icon: "twitter", label: "Twitter" },
|
172
|
+
{ href: "https://github.com", icon: "github", label: "GitHub" },
|
173
|
+
{ href: "https://linkedin.com", icon: "linkedin", label: "LinkedIn" }
|
174
|
+
],
|
175
|
+
copyright = `© ${new Date().getFullYear()} ${brandName}. 保留所有权利。`,
|
176
|
+
head,
|
177
|
+
class: className,
|
178
|
+
'class:list': classList,
|
179
|
+
...rest
|
180
|
+
} = Astro.props;
|
181
|
+
|
182
|
+
// 社交媒体图标映射
|
183
|
+
const socialIconMap: Record<string, string> = {
|
184
|
+
twitter: "🐦",
|
185
|
+
github: "🐙",
|
186
|
+
linkedin: "🔗",
|
187
|
+
facebook: "👤",
|
188
|
+
instagram: "📷",
|
189
|
+
youtube: "📺",
|
190
|
+
discord: "💬",
|
191
|
+
slack: "🔶",
|
192
|
+
wechat: "💬",
|
193
|
+
weibo: "微"
|
194
|
+
};
|
195
|
+
---
|
196
|
+
|
197
|
+
<BaseLayout
|
198
|
+
title={title}
|
199
|
+
description={description}
|
200
|
+
keywords={keywords}
|
201
|
+
head={head}
|
202
|
+
class="landing-layout"
|
203
|
+
{...rest}
|
204
|
+
>
|
205
|
+
<!-- 页眉 -->
|
206
|
+
<header class="site-header">
|
207
|
+
<div class="container">
|
208
|
+
<div class="header-content">
|
209
|
+
<div class="brand">
|
210
|
+
<a href="/" class="brand-link">
|
211
|
+
{logoUrl ? (
|
212
|
+
<img src={logoUrl} alt={brandName} class="brand-logo" />
|
213
|
+
) : (
|
214
|
+
<span class="brand-name">{brandName}</span>
|
215
|
+
)}
|
216
|
+
</a>
|
217
|
+
</div>
|
218
|
+
|
219
|
+
<nav class="main-nav">
|
220
|
+
<ul class="nav-list">
|
221
|
+
{navItems.map((item: NavItem) => (
|
222
|
+
<li class="nav-item">
|
223
|
+
{item.isButton ? (
|
224
|
+
<a href={item.href} class="btn btn-primary btn-sm">{item.label}</a>
|
225
|
+
) : (
|
226
|
+
<a href={item.href} class="nav-link">{item.label}</a>
|
227
|
+
)}
|
228
|
+
</li>
|
229
|
+
))}
|
230
|
+
</ul>
|
231
|
+
</nav>
|
232
|
+
|
233
|
+
<button class="mobile-menu-button" aria-label="菜单" id="mobile-menu-toggle">
|
234
|
+
<span class="menu-icon">☰</span>
|
235
|
+
</button>
|
236
|
+
</div>
|
237
|
+
</div>
|
238
|
+
</header>
|
239
|
+
|
240
|
+
<!-- 移动端导航菜单 -->
|
241
|
+
<div class="mobile-menu" id="mobile-menu">
|
242
|
+
<div class="mobile-menu-container">
|
243
|
+
<div class="mobile-menu-header">
|
244
|
+
<div class="brand">
|
245
|
+
{logoUrl ? (
|
246
|
+
<img src={logoUrl} alt={brandName} class="brand-logo" />
|
247
|
+
) : (
|
248
|
+
<span class="brand-name">{brandName}</span>
|
249
|
+
)}
|
250
|
+
</div>
|
251
|
+
<button class="close-menu-button" aria-label="关闭菜单" id="close-mobile-menu">
|
252
|
+
<span class="close-icon">✕</span>
|
253
|
+
</button>
|
254
|
+
</div>
|
255
|
+
|
256
|
+
<nav class="mobile-nav">
|
257
|
+
<ul class="mobile-nav-list">
|
258
|
+
{navItems.map((item: NavItem) => (
|
259
|
+
<li class="mobile-nav-item">
|
260
|
+
<a href={item.href} class="mobile-nav-link">
|
261
|
+
{item.label}
|
262
|
+
</a>
|
263
|
+
</li>
|
264
|
+
))}
|
265
|
+
</ul>
|
266
|
+
</nav>
|
267
|
+
</div>
|
268
|
+
</div>
|
269
|
+
|
270
|
+
<!-- 英雄区块 -->
|
271
|
+
<section class="hero-section">
|
272
|
+
<div class="container">
|
273
|
+
<slot name="hero" />
|
274
|
+
</div>
|
275
|
+
</section>
|
276
|
+
|
277
|
+
<!-- 主要内容 -->
|
278
|
+
<main class="main-content">
|
279
|
+
<slot />
|
280
|
+
</main>
|
281
|
+
|
282
|
+
<!-- 页脚 -->
|
283
|
+
<footer class="site-footer">
|
284
|
+
<div class="container">
|
285
|
+
<div class="footer-content">
|
286
|
+
<div class="footer-brand">
|
287
|
+
<div class="brand">
|
288
|
+
{logoUrl ? (
|
289
|
+
<img src={logoUrl} alt={brandName} class="brand-logo" />
|
290
|
+
) : (
|
291
|
+
<span class="brand-name">{brandName}</span>
|
292
|
+
)}
|
293
|
+
</div>
|
294
|
+
<p class="brand-tagline">
|
295
|
+
<slot name="footer-tagline">简短的品牌标语或描述</slot>
|
296
|
+
</p>
|
297
|
+
|
298
|
+
<div class="social-links">
|
299
|
+
{socialLinks.map((link: SocialLink) => (
|
300
|
+
<a href={link.href} class="social-link" aria-label={link.label} target="_blank" rel="noopener noreferrer">
|
301
|
+
<span class="social-icon">{socialIconMap[link.icon] || "🔗"}</span>
|
302
|
+
</a>
|
303
|
+
))}
|
304
|
+
</div>
|
305
|
+
</div>
|
306
|
+
|
307
|
+
<div class="footer-links">
|
308
|
+
{footerSections.map((section: FooterSection) => (
|
309
|
+
<div class="footer-section">
|
310
|
+
<h3 class="footer-title">{section.title}</h3>
|
311
|
+
<ul class="footer-list">
|
312
|
+
{section.links.map((link: FooterLink) => (
|
313
|
+
<li class="footer-item">
|
314
|
+
<a href={link.href} class="footer-link">{link.label}</a>
|
315
|
+
</li>
|
316
|
+
))}
|
317
|
+
</ul>
|
318
|
+
</div>
|
319
|
+
))}
|
320
|
+
</div>
|
321
|
+
</div>
|
322
|
+
|
323
|
+
<div class="footer-bottom">
|
324
|
+
<p class="copyright">{copyright}</p>
|
325
|
+
<slot name="footer-extra" />
|
326
|
+
</div>
|
327
|
+
</div>
|
328
|
+
</footer>
|
329
|
+
</BaseLayout>
|
330
|
+
|
331
|
+
<script>
|
332
|
+
// 移动端菜单功能
|
333
|
+
document.addEventListener('DOMContentLoaded', () => {
|
334
|
+
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
|
335
|
+
const closeMobileMenu = document.getElementById('close-mobile-menu');
|
336
|
+
const mobileMenu = document.getElementById('mobile-menu');
|
337
|
+
const mobileNavLinks = document.querySelectorAll('.mobile-nav-link');
|
338
|
+
|
339
|
+
if (mobileMenuToggle && mobileMenu) {
|
340
|
+
mobileMenuToggle.addEventListener('click', () => {
|
341
|
+
mobileMenu.classList.add('active');
|
342
|
+
document.body.style.overflow = 'hidden';
|
343
|
+
});
|
344
|
+
}
|
345
|
+
|
346
|
+
if (closeMobileMenu && mobileMenu) {
|
347
|
+
closeMobileMenu.addEventListener('click', () => {
|
348
|
+
mobileMenu.classList.remove('active');
|
349
|
+
document.body.style.overflow = '';
|
350
|
+
});
|
351
|
+
}
|
352
|
+
|
353
|
+
// 点击导航链接后关闭菜单
|
354
|
+
mobileNavLinks.forEach(link => {
|
355
|
+
link.addEventListener('click', () => {
|
356
|
+
if (mobileMenu) {
|
357
|
+
mobileMenu.classList.remove('active');
|
358
|
+
document.body.style.overflow = '';
|
359
|
+
}
|
360
|
+
});
|
361
|
+
});
|
362
|
+
|
363
|
+
// 页眉滚动效果
|
364
|
+
const header = document.querySelector('.site-header');
|
365
|
+
let lastScrollTop = 0;
|
366
|
+
|
367
|
+
window.addEventListener('scroll', () => {
|
368
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
369
|
+
|
370
|
+
if (header) {
|
371
|
+
if (scrollTop > 100) {
|
372
|
+
header.classList.add('scrolled');
|
373
|
+
|
374
|
+
if (scrollTop > lastScrollTop) {
|
375
|
+
header.classList.add('hidden');
|
376
|
+
} else {
|
377
|
+
header.classList.remove('hidden');
|
378
|
+
}
|
379
|
+
} else {
|
380
|
+
header.classList.remove('scrolled');
|
381
|
+
header.classList.remove('hidden');
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
lastScrollTop = scrollTop;
|
386
|
+
});
|
387
|
+
});
|
388
|
+
</script>
|
@@ -0,0 +1,149 @@
|
|
1
|
+
---
|
2
|
+
/**
|
3
|
+
* @component Stack
|
4
|
+
*
|
5
|
+
* @description
|
6
|
+
* Stack 组件是一个垂直堆叠布局组件,用于垂直排列元素并控制它们之间的间距。
|
7
|
+
* 它提供了灵活的间距控制、对齐方式和分隔线选项,使垂直布局更加简单。
|
8
|
+
*
|
9
|
+
* @design
|
10
|
+
* 设计理念:
|
11
|
+
* 1. 简化布局 - 提供简单直观的方式创建垂直堆叠布局
|
12
|
+
* 2. 一致性 - 通过预设的间距选项确保布局一致性
|
13
|
+
* 3. 灵活性 - 支持多种对齐方式和分隔线选项
|
14
|
+
* 4. 可组合性 - 可以与其他布局组件组合使用
|
15
|
+
*
|
16
|
+
* @usage
|
17
|
+
* 基本用法:
|
18
|
+
* ```astro
|
19
|
+
* <Stack>
|
20
|
+
* <div>第一个元素</div>
|
21
|
+
* <div>第二个元素</div>
|
22
|
+
* <div>第三个元素</div>
|
23
|
+
* </Stack>
|
24
|
+
* ```
|
25
|
+
*
|
26
|
+
* 自定义间距:
|
27
|
+
* ```astro
|
28
|
+
* <Stack gap="lg">
|
29
|
+
* <div>大间距</div>
|
30
|
+
* <div>垂直排列</div>
|
31
|
+
* </Stack>
|
32
|
+
* ```
|
33
|
+
*
|
34
|
+
* 带分隔线:
|
35
|
+
* ```astro
|
36
|
+
* <Stack dividers={true} dividerColor="primary">
|
37
|
+
* <div>带分隔线的堆叠</div>
|
38
|
+
* <div>使用主题色</div>
|
39
|
+
* </Stack>
|
40
|
+
* ```
|
41
|
+
*
|
42
|
+
* 自定义对齐:
|
43
|
+
* ```astro
|
44
|
+
* <Stack align="center">
|
45
|
+
* <div>居中对齐</div>
|
46
|
+
* <div>的元素</div>
|
47
|
+
* </Stack>
|
48
|
+
* ```
|
49
|
+
*/
|
50
|
+
|
51
|
+
// 导入样式
|
52
|
+
import '../../app.css';
|
53
|
+
|
54
|
+
// 定义间距类型
|
55
|
+
type GapSize = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
56
|
+
|
57
|
+
// 定义对齐方式类型
|
58
|
+
type AlignType = 'start' | 'center' | 'end' | 'stretch';
|
59
|
+
|
60
|
+
// 定义分隔线颜色类型
|
61
|
+
type DividerColor = 'gray' | 'light' | 'dark' | 'primary';
|
62
|
+
|
63
|
+
interface Props {
|
64
|
+
/**
|
65
|
+
* 元素间距
|
66
|
+
* @default "md"
|
67
|
+
*/
|
68
|
+
gap?: GapSize;
|
69
|
+
|
70
|
+
/**
|
71
|
+
* 是否显示分隔线
|
72
|
+
* @default false
|
73
|
+
*/
|
74
|
+
dividers?: boolean;
|
75
|
+
|
76
|
+
/**
|
77
|
+
* 分隔线颜色
|
78
|
+
* @default "gray"
|
79
|
+
*/
|
80
|
+
dividerColor?: DividerColor;
|
81
|
+
|
82
|
+
/**
|
83
|
+
* 对齐方式
|
84
|
+
* @default "stretch"
|
85
|
+
*/
|
86
|
+
align?: AlignType;
|
87
|
+
|
88
|
+
/**
|
89
|
+
* 自定义类名
|
90
|
+
*/
|
91
|
+
class?: string;
|
92
|
+
|
93
|
+
/**
|
94
|
+
* 类名列表
|
95
|
+
*/
|
96
|
+
'class:list'?: any;
|
97
|
+
|
98
|
+
[key: string]: any;
|
99
|
+
}
|
100
|
+
|
101
|
+
const {
|
102
|
+
gap = 'md',
|
103
|
+
dividers = false,
|
104
|
+
dividerColor = 'gray',
|
105
|
+
align = 'stretch',
|
106
|
+
class: className = '',
|
107
|
+
'class:list': classList,
|
108
|
+
...rest
|
109
|
+
} = Astro.props;
|
110
|
+
|
111
|
+
// 间距映射
|
112
|
+
const gapClasses: Record<GapSize, string> = {
|
113
|
+
'none': 'stack-gap-none',
|
114
|
+
'xs': 'stack-gap-xs',
|
115
|
+
'sm': 'stack-gap-sm',
|
116
|
+
'md': 'stack-gap-md',
|
117
|
+
'lg': 'stack-gap-lg',
|
118
|
+
'xl': 'stack-gap-xl'
|
119
|
+
};
|
120
|
+
|
121
|
+
// 对齐方式映射
|
122
|
+
const alignClasses: Record<AlignType, string> = {
|
123
|
+
'start': 'stack-align-start',
|
124
|
+
'center': 'stack-align-center',
|
125
|
+
'end': 'stack-align-end',
|
126
|
+
'stretch': 'stack-align-stretch'
|
127
|
+
};
|
128
|
+
|
129
|
+
// 分隔线颜色映射
|
130
|
+
const dividerColorClasses: Record<DividerColor, string> = {
|
131
|
+
'gray': 'stack-divider-gray',
|
132
|
+
'light': 'stack-divider-light',
|
133
|
+
'dark': 'stack-divider-dark',
|
134
|
+
'primary': 'stack-divider-primary'
|
135
|
+
};
|
136
|
+
|
137
|
+
// 构建最终类名
|
138
|
+
const stackClasses = [
|
139
|
+
'stack',
|
140
|
+
alignClasses[align as AlignType],
|
141
|
+
gapClasses[gap as GapSize],
|
142
|
+
dividers ? `stack-divider ${dividerColorClasses[dividerColor as DividerColor]}` : '',
|
143
|
+
className
|
144
|
+
].filter(Boolean).join(' ');
|
145
|
+
---
|
146
|
+
|
147
|
+
<div class:list={[stackClasses, classList]} {...rest}>
|
148
|
+
<slot />
|
149
|
+
</div>
|