@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,195 +1,248 @@
|
|
1
1
|
---
|
2
|
+
/**
|
3
|
+
* @component Blog
|
4
|
+
*
|
5
|
+
* @description
|
6
|
+
* Blog 组件是一个用于展示博客文章的容器组件,提供了标题、副标题、作者信息、日期、封面图片和标签等元素的布局。
|
7
|
+
* 组件内部使用了响应式设计,确保在不同屏幕尺寸下都有良好的显示效果。
|
8
|
+
*
|
9
|
+
* @design
|
10
|
+
* 设计理念:
|
11
|
+
* 1. 内容优先 - 突出展示文章的核心内容和相关元数据
|
12
|
+
* 2. 视觉层次 - 通过字体大小、间距和颜色建立清晰的视觉层次
|
13
|
+
* 3. 响应式布局 - 自适应不同屏幕尺寸,优化阅读体验
|
14
|
+
* 4. 视觉一致性 - 与整体设计系统保持一致的视觉风格
|
15
|
+
*
|
16
|
+
* @usage
|
17
|
+
* 基本用法:
|
18
|
+
* ```astro
|
19
|
+
* <Blog title="这是一篇博客文章">
|
20
|
+
* <p>这里是博客文章的内容...</p>
|
21
|
+
* </Blog>
|
22
|
+
* ```
|
23
|
+
*
|
24
|
+
* 完整用法:
|
25
|
+
* ```astro
|
26
|
+
* <Blog
|
27
|
+
* title="使用 Astro 构建现代网站"
|
28
|
+
* subtitle="探索 Astro 的强大功能和使用技巧"
|
29
|
+
* author="张三"
|
30
|
+
* date={new Date('2023-12-15')}
|
31
|
+
* cover={{
|
32
|
+
* src: "/images/blog/astro-cover.jpg",
|
33
|
+
* alt: "Astro 框架封面图"
|
34
|
+
* }}
|
35
|
+
* tags={["Astro", "前端", "教程"]}
|
36
|
+
* >
|
37
|
+
* <p>这里是博客文章的详细内容...</p>
|
38
|
+
* </Blog>
|
39
|
+
* ```
|
40
|
+
*/
|
41
|
+
|
2
42
|
import { UserIcon, CalendarIcon } from '../../index';
|
3
43
|
|
4
44
|
interface Props {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
45
|
+
/**
|
46
|
+
* 文章标题
|
47
|
+
*/
|
48
|
+
title: string;
|
49
|
+
/**
|
50
|
+
* 文章副标题
|
51
|
+
*/
|
52
|
+
subtitle?: string;
|
53
|
+
/**
|
54
|
+
* 文章作者
|
55
|
+
*/
|
56
|
+
author?: string;
|
57
|
+
/**
|
58
|
+
* 发布日期
|
59
|
+
*/
|
60
|
+
date?: Date;
|
61
|
+
/**
|
62
|
+
* 封面图片
|
63
|
+
*/
|
64
|
+
cover?: {
|
65
|
+
src: string;
|
66
|
+
alt?: string;
|
67
|
+
};
|
68
|
+
/**
|
69
|
+
* 文章标签
|
70
|
+
*/
|
71
|
+
tags?: string[];
|
14
72
|
}
|
15
73
|
|
16
|
-
const {
|
17
|
-
title,
|
18
|
-
subtitle,
|
19
|
-
author,
|
20
|
-
date,
|
21
|
-
cover,
|
22
|
-
tags = [],
|
23
|
-
} = Astro.props;
|
74
|
+
const { title, subtitle, author, date, cover, tags = [] } = Astro.props;
|
24
75
|
|
25
76
|
// 格式化日期
|
26
77
|
const formatDate = (date?: Date) => {
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
78
|
+
if (!date) return '';
|
79
|
+
return new Intl.DateTimeFormat('zh-CN', {
|
80
|
+
year: 'numeric',
|
81
|
+
month: 'long',
|
82
|
+
day: 'numeric',
|
83
|
+
}).format(date);
|
33
84
|
};
|
34
85
|
---
|
35
86
|
|
36
87
|
<article class="article">
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
88
|
+
<header class="article-header">
|
89
|
+
{
|
90
|
+
cover && (
|
91
|
+
<div class="cover-container">
|
92
|
+
<img src={cover.src} alt={cover.alt || title} class="cover-image" />
|
93
|
+
</div>
|
94
|
+
)
|
95
|
+
}
|
96
|
+
|
97
|
+
<div class="header-content">
|
98
|
+
<h1 class="article-title">{title}</h1>
|
99
|
+
{subtitle && <p class="article-subtitle">{subtitle}</p>}
|
100
|
+
|
101
|
+
<div class="article-meta">
|
102
|
+
{
|
103
|
+
author && (
|
104
|
+
<div class="meta-item">
|
105
|
+
<UserIcon size="16px" />
|
106
|
+
<span>{author}</span>
|
107
|
+
</div>
|
108
|
+
)
|
109
|
+
}
|
110
|
+
|
111
|
+
{
|
112
|
+
date && (
|
113
|
+
<div class="meta-item">
|
114
|
+
<CalendarIcon size="16px" />
|
115
|
+
<time datetime={date.toISOString()}>{formatDate(date)}</time>
|
116
|
+
</div>
|
117
|
+
)
|
118
|
+
}
|
119
|
+
</div>
|
120
|
+
|
121
|
+
{
|
122
|
+
tags.length > 0 && (
|
123
|
+
<div class="article-tags">
|
124
|
+
{tags.map((tag: string) => (
|
125
|
+
<span class="tag">{tag}</span>
|
126
|
+
))}
|
127
|
+
</div>
|
128
|
+
)
|
129
|
+
}
|
130
|
+
</div>
|
131
|
+
</header>
|
132
|
+
|
133
|
+
<div class="article-content">
|
134
|
+
<slot />
|
135
|
+
</div>
|
83
136
|
</article>
|
84
137
|
|
85
138
|
<style>
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
</style>
|
139
|
+
/* 基础样式 */
|
140
|
+
.article {
|
141
|
+
container-type: inline-size;
|
142
|
+
}
|
143
|
+
|
144
|
+
/* 文章头部 */
|
145
|
+
.article-header {
|
146
|
+
margin-bottom: 2rem;
|
147
|
+
}
|
148
|
+
|
149
|
+
/* 封面图片 */
|
150
|
+
.cover-container {
|
151
|
+
position: relative;
|
152
|
+
width: 100%;
|
153
|
+
height: 300px;
|
154
|
+
margin-bottom: 1.5rem;
|
155
|
+
border-radius: 0.5rem;
|
156
|
+
overflow: hidden;
|
157
|
+
}
|
158
|
+
|
159
|
+
.cover-image {
|
160
|
+
width: 100%;
|
161
|
+
height: 100%;
|
162
|
+
object-fit: cover;
|
163
|
+
}
|
164
|
+
|
165
|
+
/* 标题和副标题 */
|
166
|
+
.header-content {
|
167
|
+
display: flex;
|
168
|
+
flex-direction: column;
|
169
|
+
gap: 1rem;
|
170
|
+
}
|
171
|
+
|
172
|
+
.article-title {
|
173
|
+
font-size: 2.25rem;
|
174
|
+
font-weight: 700;
|
175
|
+
line-height: 1.2;
|
176
|
+
letter-spacing: -0.025em;
|
177
|
+
}
|
178
|
+
|
179
|
+
.article-subtitle {
|
180
|
+
font-size: 1.25rem;
|
181
|
+
color: var(--text-secondary, rgba(0, 0, 0, 0.7));
|
182
|
+
}
|
183
|
+
|
184
|
+
/* 元数据 */
|
185
|
+
.article-meta {
|
186
|
+
display: flex;
|
187
|
+
flex-wrap: wrap;
|
188
|
+
align-items: center;
|
189
|
+
gap: 1rem;
|
190
|
+
font-size: 0.875rem;
|
191
|
+
color: var(--text-secondary, rgba(0, 0, 0, 0.6));
|
192
|
+
}
|
193
|
+
|
194
|
+
.meta-item {
|
195
|
+
display: flex;
|
196
|
+
align-items: center;
|
197
|
+
gap: 0.5rem;
|
198
|
+
}
|
199
|
+
|
200
|
+
/* 标签 */
|
201
|
+
.article-tags {
|
202
|
+
display: flex;
|
203
|
+
flex-wrap: wrap;
|
204
|
+
gap: 0.5rem;
|
205
|
+
}
|
206
|
+
|
207
|
+
.tag {
|
208
|
+
display: inline-block;
|
209
|
+
padding: 0.25rem 0.75rem;
|
210
|
+
border: 1px solid var(--border-color, #e2e8f0);
|
211
|
+
border-radius: 9999px;
|
212
|
+
font-size: 0.75rem;
|
213
|
+
line-height: 1.5;
|
214
|
+
}
|
215
|
+
|
216
|
+
/* 文章内容 */
|
217
|
+
.article-content {
|
218
|
+
max-width: none;
|
219
|
+
font-size: 1.125rem;
|
220
|
+
line-height: 1.75;
|
221
|
+
}
|
222
|
+
|
223
|
+
/* 响应式调整 */
|
224
|
+
@media (min-width: 768px) {
|
225
|
+
.article-title {
|
226
|
+
font-size: 3rem;
|
227
|
+
}
|
228
|
+
|
229
|
+
.cover-container {
|
230
|
+
height: 400px;
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
/* 暗色主题适配 */
|
235
|
+
@media (prefers-color-scheme: dark) {
|
236
|
+
.article-subtitle {
|
237
|
+
color: var(--text-secondary, rgba(255, 255, 255, 0.7));
|
238
|
+
}
|
239
|
+
|
240
|
+
.article-meta {
|
241
|
+
color: var(--text-secondary, rgba(255, 255, 255, 0.6));
|
242
|
+
}
|
243
|
+
|
244
|
+
.tag {
|
245
|
+
border-color: var(--border-color, #2d3748);
|
246
|
+
}
|
247
|
+
}
|
248
|
+
</style>
|
@@ -1,18 +1,18 @@
|
|
1
1
|
---
|
2
2
|
/**
|
3
3
|
* @component TeamMember
|
4
|
-
*
|
4
|
+
*
|
5
5
|
* @description
|
6
6
|
* TeamMember 组件用于展示单个团队成员的信息,包括姓名、职位、头像、简介和社交媒体链接。
|
7
7
|
* 组件采用卡片式设计,支持悬停效果,并可以链接到成员的社交媒体账号。
|
8
|
-
*
|
8
|
+
*
|
9
9
|
* @design
|
10
10
|
* 设计理念:
|
11
11
|
* 1. 个人展示 - 突出展示团队成员的个人信息和形象
|
12
12
|
* 2. 社交连接 - 提供社交媒体链接,方便与团队成员建立联系
|
13
13
|
* 3. 视觉一致性 - 使用卡片组件确保与整体设计风格一致
|
14
14
|
* 4. 交互反馈 - 悬停时提供视觉反馈,增强用户体验
|
15
|
-
*
|
15
|
+
*
|
16
16
|
* @usage
|
17
17
|
* 基本用法:
|
18
18
|
* ```astro
|
@@ -23,7 +23,7 @@
|
|
23
23
|
* bio="拥有5年前端开发经验,专注于React和Vue生态系统。"
|
24
24
|
* />
|
25
25
|
* ```
|
26
|
-
*
|
26
|
+
*
|
27
27
|
* 包含社交媒体链接:
|
28
28
|
* ```astro
|
29
29
|
* <TeamMember
|
@@ -96,9 +96,13 @@ export interface Props {
|
|
96
96
|
const { name, role, avatar, bio, socialLinks, class: className = '' } = Astro.props;
|
97
97
|
---
|
98
98
|
|
99
|
-
<div
|
100
|
-
|
101
|
-
|
99
|
+
<div
|
100
|
+
class:list={[
|
101
|
+
'cosy:card cosy:bg-base-100 cosy:shadow-md cosy:hover:shadow-lg cosy:transition-shadow cosy:duration-300',
|
102
|
+
className,
|
103
|
+
]}>
|
104
|
+
<figure class="cosy:flex cosy:justify-center cosy:p-4">
|
105
|
+
<Image
|
102
106
|
src={avatar}
|
103
107
|
alt={`${name}'s avatar`}
|
104
108
|
width={192}
|
@@ -106,25 +110,25 @@ const { name, role, avatar, bio, socialLinks, class: className = '' } = Astro.pr
|
|
106
110
|
rounded="xl"
|
107
111
|
transition="fade"
|
108
112
|
hover="brightness"
|
109
|
-
class="
|
113
|
+
class="cosy:object-cover cosy:aspect-square"
|
110
114
|
/>
|
111
115
|
</figure>
|
112
|
-
<div class="
|
113
|
-
<h2 class="
|
114
|
-
<p class="
|
115
|
-
<p class="
|
116
|
+
<div class="cosy:px-6 cosy:py-4 cosy:card-body">
|
117
|
+
<h2 class="cosy:mb-1 cosy:font-semibold cosy:text-xl cosy:card-title">{name}</h2>
|
118
|
+
<p class="cosy:mb-3 cosy:text-sm cosy:text-base-content/70">{role}</p>
|
119
|
+
<p class="cosy:mb-4 cosy:text-base">{bio}</p>
|
116
120
|
|
117
121
|
{
|
118
122
|
socialLinks && socialLinks.length > 0 && (
|
119
|
-
<div class="
|
123
|
+
<div class="cosy:flex cosy:gap-3 cosy:mt-auto">
|
120
124
|
{socialLinks.map((link: SocialLink) => (
|
121
125
|
<Link
|
122
126
|
href={link.url}
|
123
127
|
external
|
124
128
|
variant="ghost"
|
125
|
-
class="
|
129
|
+
class="cosy:hover:bg-base-200 cosy:p-2 cosy:rounded-full"
|
126
130
|
aria-label={`Visit ${name}'s ${link.platform} profile`}>
|
127
|
-
<SocialIcon platform={link.platform} class="
|
131
|
+
<SocialIcon platform={link.platform} class="cosy:w-5 cosy:h-5" />
|
128
132
|
</Link>
|
129
133
|
))}
|
130
134
|
</div>
|
@@ -132,4 +136,3 @@ const { name, role, avatar, bio, socialLinks, class: className = '' } = Astro.pr
|
|
132
136
|
}
|
133
137
|
</div>
|
134
138
|
</div>
|
135
|
-
|
@@ -1,28 +1,28 @@
|
|
1
1
|
---
|
2
2
|
/**
|
3
3
|
* @component TeamMembers
|
4
|
-
*
|
4
|
+
*
|
5
5
|
* @description
|
6
6
|
* TeamMembers 组件用于展示团队成员列表,以网格布局呈现多个 TeamMember 组件。
|
7
7
|
* 支持响应式布局,可以根据屏幕大小自动调整列数。
|
8
|
-
*
|
8
|
+
*
|
9
9
|
* @design
|
10
10
|
* 设计理念:
|
11
11
|
* 1. 一致性展示 - 以统一的卡片形式展示团队成员信息
|
12
12
|
* 2. 响应式布局 - 在不同屏幕尺寸下自动调整列数
|
13
13
|
* 3. 灵活配置 - 支持自定义列数和样式
|
14
|
-
*
|
14
|
+
*
|
15
15
|
* @usage
|
16
16
|
* 基本用法:
|
17
17
|
* ```astro
|
18
18
|
* <TeamMembers members={teamData} />
|
19
19
|
* ```
|
20
|
-
*
|
20
|
+
*
|
21
21
|
* 自定义列数:
|
22
22
|
* ```astro
|
23
23
|
* <TeamMembers members={teamData} columns={2} />
|
24
24
|
* ```
|
25
|
-
*
|
25
|
+
*
|
26
26
|
* 添加自定义类名:
|
27
27
|
* ```astro
|
28
28
|
* <TeamMembers members={teamData} class="mt-8" />
|
@@ -36,66 +36,70 @@ import '../../app.css';
|
|
36
36
|
|
37
37
|
// 自定义图片元数据接口
|
38
38
|
interface ImageMetadata {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
src: string;
|
40
|
+
width: number;
|
41
|
+
height: number;
|
42
|
+
format: string;
|
43
43
|
}
|
44
44
|
|
45
45
|
interface SocialLink {
|
46
|
-
|
47
|
-
|
46
|
+
platform: 'github' | 'twitter' | 'linkedin' | 'website' | 'email';
|
47
|
+
url: string;
|
48
48
|
}
|
49
49
|
|
50
50
|
interface TeamMemberData {
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
name: string;
|
52
|
+
role: string;
|
53
|
+
avatar: ImageMetadata | string;
|
54
|
+
bio: string;
|
55
|
+
socialLinks?: SocialLink[];
|
56
56
|
}
|
57
57
|
|
58
58
|
interface Props {
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
59
|
+
/**
|
60
|
+
* 团队成员数据数组
|
61
|
+
*/
|
62
|
+
members: TeamMemberData[];
|
63
|
+
/**
|
64
|
+
* 网格列数
|
65
|
+
* @default 3
|
66
|
+
*/
|
67
|
+
columns?: 2 | 3 | 4;
|
68
|
+
/**
|
69
|
+
* 自定义类名
|
70
|
+
*/
|
71
|
+
class?: string;
|
72
72
|
}
|
73
73
|
|
74
|
-
const {
|
75
|
-
members,
|
76
|
-
columns = 3,
|
77
|
-
class: className = ''
|
78
|
-
} = Astro.props;
|
74
|
+
const { members, columns = 3, class: className = '' } = Astro.props;
|
79
75
|
|
80
76
|
// 使用类型断言确保 columns 是有效的键
|
81
77
|
const columnsValue = columns as 2 | 3 | 4;
|
82
78
|
|
83
79
|
// 根据列数生成对应的类名
|
84
80
|
const getGridClasses = (cols: 2 | 3 | 4) => {
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
81
|
+
const baseClass = 'cosy:grid-cols-1';
|
82
|
+
const mdClass = 'cosy:md:grid-cols-2';
|
83
|
+
|
84
|
+
let lgClass = 'cosy:lg:grid-cols-3';
|
85
|
+
if (cols === 2) {
|
86
|
+
lgClass = 'cosy:lg:grid-cols-2';
|
87
|
+
} else if (cols === 4) {
|
88
|
+
lgClass = 'cosy:lg:grid-cols-4';
|
89
|
+
}
|
90
|
+
|
91
|
+
return [baseClass, mdClass, lgClass];
|
90
92
|
};
|
91
93
|
---
|
92
94
|
|
93
|
-
<div class:list={[
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
95
|
+
<div class:list={['cosy:w-full cosy:mx-auto cosy:px-4', className]}>
|
96
|
+
<div class:list={['cosy:grid cosy:gap-6', ...getGridClasses(columnsValue)]}>
|
97
|
+
{
|
98
|
+
members.map((member: TeamMemberData, index: number) => (
|
99
|
+
<div class="cosy:transition-all cosy:hover:-translate-y-1 cosy:duration-300 cosy:transform">
|
100
|
+
<TeamMember {...member} />
|
101
|
+
</div>
|
102
|
+
))
|
103
|
+
}
|
104
|
+
</div>
|
105
|
+
</div>
|