@coffic/cosy-ui 0.1.27 → 0.1.28
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/index.ts +1 -0
- package/package.json +1 -1
- package/src/components/Alert.astro +1 -1
- package/src/components/Footer.astro +4 -1
- package/src/components/Image.astro +251 -0
- package/src/env.d.ts +0 -1
- package/src/types/footer.ts +33 -0
- package/src/utils/social.ts +12 -5
package/index.ts
CHANGED
@@ -9,6 +9,7 @@ export { default as CodeBlock } from './src/components/CodeBlock.astro';
|
|
9
9
|
export { default as Footer } from './src/components/Footer.astro';
|
10
10
|
export { default as Header } from './src/components/Header.astro';
|
11
11
|
export { default as Hero } from './src/components/Hero.astro';
|
12
|
+
export { default as Image } from './src/components/Image.astro';
|
12
13
|
export { default as Link } from './src/components/Link.astro';
|
13
14
|
export { default as Modal } from './src/components/Modal.astro';
|
14
15
|
export { default as SocialIcon } from './src/components/SocialIcon.astro';
|
package/package.json
CHANGED
@@ -40,7 +40,7 @@ const icons = {
|
|
40
40
|
|
41
41
|
<div class={`alert ${alertClass} ${className}`} role="alert">
|
42
42
|
<div class="flex items-center gap-2">
|
43
|
-
<
|
43
|
+
<div set:html={icons[type]} />
|
44
44
|
<div class="flex-1">
|
45
45
|
{title && <h3 class="font-bold">{title}</h3>}
|
46
46
|
<div><slot /></div>
|
@@ -3,6 +3,9 @@ import { processSocialLink } from '../utils/social';
|
|
3
3
|
import Link from './Link.astro';
|
4
4
|
import Banner from './Banner.astro';
|
5
5
|
import SocialIcon from './SocialIcon.astro';
|
6
|
+
import type { FooterProps } from '../types/footer';
|
7
|
+
|
8
|
+
interface Props extends FooterProps {}
|
6
9
|
|
7
10
|
const {
|
8
11
|
siteName,
|
@@ -19,7 +22,7 @@ const {
|
|
19
22
|
termsLink,
|
20
23
|
privacyLink,
|
21
24
|
socialLinks = [],
|
22
|
-
} = Astro.props;
|
25
|
+
}: Props = Astro.props;
|
23
26
|
|
24
27
|
const currentYear = new Date().getFullYear();
|
25
28
|
|
@@ -0,0 +1,251 @@
|
|
1
|
+
---
|
2
|
+
import { Image as AstroImage } from 'astro:assets';
|
3
|
+
import type { ImageMetadata } from 'astro';
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
/**
|
7
|
+
* 图片源,可以是本地图片或远程URL
|
8
|
+
*/
|
9
|
+
src: ImageMetadata | string;
|
10
|
+
/**
|
11
|
+
* 图片的替代文本
|
12
|
+
*/
|
13
|
+
alt: string;
|
14
|
+
/**
|
15
|
+
* 图片的宽度
|
16
|
+
*/
|
17
|
+
width?: number;
|
18
|
+
/**
|
19
|
+
* 图片的高度
|
20
|
+
*/
|
21
|
+
height?: number;
|
22
|
+
/**
|
23
|
+
* 图片的加载方式
|
24
|
+
* @default "lazy"
|
25
|
+
*/
|
26
|
+
loading?: 'lazy' | 'eager';
|
27
|
+
/**
|
28
|
+
* 图片的填充方式
|
29
|
+
* @default "cover"
|
30
|
+
*/
|
31
|
+
objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
32
|
+
/**
|
33
|
+
* 图片的位置
|
34
|
+
* @default "center"
|
35
|
+
*/
|
36
|
+
objectPosition?: string;
|
37
|
+
/**
|
38
|
+
* 是否显示加载中的占位图
|
39
|
+
* @default true
|
40
|
+
*/
|
41
|
+
showPlaceholder?: boolean;
|
42
|
+
/**
|
43
|
+
* 是否显示加载失败的错误图
|
44
|
+
* @default true
|
45
|
+
*/
|
46
|
+
showError?: boolean;
|
47
|
+
/**
|
48
|
+
* 自定义类名
|
49
|
+
*/
|
50
|
+
class?: string;
|
51
|
+
/**
|
52
|
+
* 是否启用图片懒加载
|
53
|
+
* @default true
|
54
|
+
*/
|
55
|
+
lazy?: boolean;
|
56
|
+
/**
|
57
|
+
* 图片的圆角大小
|
58
|
+
* @default "none"
|
59
|
+
*/
|
60
|
+
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';
|
61
|
+
/**
|
62
|
+
* 图片的阴影效果
|
63
|
+
* @default "none"
|
64
|
+
*/
|
65
|
+
shadow?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
66
|
+
/**
|
67
|
+
* 图片的悬停效果
|
68
|
+
* @default "none"
|
69
|
+
*/
|
70
|
+
hover?: 'none' | 'scale' | 'brightness' | 'blur';
|
71
|
+
/**
|
72
|
+
* 图片的过渡动画
|
73
|
+
* @default "none"
|
74
|
+
*/
|
75
|
+
transition?: 'none' | 'fade' | 'slide' | 'zoom';
|
76
|
+
}
|
77
|
+
|
78
|
+
const {
|
79
|
+
src,
|
80
|
+
alt,
|
81
|
+
width,
|
82
|
+
height,
|
83
|
+
loading = 'lazy',
|
84
|
+
objectFit = 'cover',
|
85
|
+
objectPosition = 'center',
|
86
|
+
showPlaceholder = true,
|
87
|
+
showError = true,
|
88
|
+
class: className = '',
|
89
|
+
lazy = true,
|
90
|
+
rounded = 'none',
|
91
|
+
shadow = 'none',
|
92
|
+
hover = 'none',
|
93
|
+
transition = 'none',
|
94
|
+
} = Astro.props;
|
95
|
+
|
96
|
+
// 处理类名
|
97
|
+
const classes = [
|
98
|
+
'relative',
|
99
|
+
// 圆角
|
100
|
+
rounded !== 'none' && `rounded-${rounded}`,
|
101
|
+
// 阴影
|
102
|
+
shadow !== 'none' && `shadow-${shadow}`,
|
103
|
+
// 悬停效果
|
104
|
+
hover !== 'none' && {
|
105
|
+
'scale': 'hover:scale-105',
|
106
|
+
'brightness': 'hover:brightness-110',
|
107
|
+
'blur': 'hover:blur-sm',
|
108
|
+
}[hover],
|
109
|
+
// 过渡动画
|
110
|
+
transition !== 'none' && {
|
111
|
+
'fade': 'transition-opacity duration-300',
|
112
|
+
'slide': 'transition-transform duration-300',
|
113
|
+
'zoom': 'transition-all duration-300',
|
114
|
+
}[transition],
|
115
|
+
className,
|
116
|
+
].filter(Boolean).join(' ');
|
117
|
+
|
118
|
+
// 处理图片样式
|
119
|
+
const imageStyles = {
|
120
|
+
objectFit,
|
121
|
+
objectPosition,
|
122
|
+
};
|
123
|
+
|
124
|
+
// 判断是否为本地图片
|
125
|
+
const isLocalImage = typeof src !== 'string' && 'src' in src;
|
126
|
+
---
|
127
|
+
|
128
|
+
<div class={classes}>
|
129
|
+
<div class="relative w-full h-full">
|
130
|
+
{isLocalImage ? (
|
131
|
+
<AstroImage
|
132
|
+
src={src}
|
133
|
+
alt={alt}
|
134
|
+
width={width}
|
135
|
+
height={height}
|
136
|
+
loading={loading}
|
137
|
+
class="w-full h-full opacity-0 transition-opacity duration-300"
|
138
|
+
style={imageStyles}
|
139
|
+
/>
|
140
|
+
) : (
|
141
|
+
<img
|
142
|
+
src={src}
|
143
|
+
alt={alt}
|
144
|
+
width={width}
|
145
|
+
height={height}
|
146
|
+
loading={loading}
|
147
|
+
class="w-full h-full opacity-0 transition-opacity duration-300"
|
148
|
+
style={imageStyles}
|
149
|
+
/>
|
150
|
+
)}
|
151
|
+
|
152
|
+
{/* 加载占位图 */}
|
153
|
+
{showPlaceholder && (
|
154
|
+
<div class="absolute inset-0 bg-base-200 animate-pulse" />
|
155
|
+
)}
|
156
|
+
|
157
|
+
{/* 错误占位图 */}
|
158
|
+
{showError && (
|
159
|
+
<div class="absolute inset-0 bg-error/10 flex items-center justify-center hidden">
|
160
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-error" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
161
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
162
|
+
</svg>
|
163
|
+
</div>
|
164
|
+
)}
|
165
|
+
</div>
|
166
|
+
</div>
|
167
|
+
|
168
|
+
<script>
|
169
|
+
// 处理图片加载状态
|
170
|
+
function handleImageLoad(img) {
|
171
|
+
// 移除加载占位图
|
172
|
+
const placeholder = img.parentElement?.querySelector('.animate-pulse');
|
173
|
+
if (placeholder) {
|
174
|
+
placeholder.classList.add('opacity-0');
|
175
|
+
setTimeout(() => {
|
176
|
+
placeholder.remove();
|
177
|
+
}, 300);
|
178
|
+
}
|
179
|
+
|
180
|
+
// 添加加载完成动画
|
181
|
+
img.classList.add('opacity-100');
|
182
|
+
}
|
183
|
+
|
184
|
+
// 处理图片加载错误
|
185
|
+
function handleImageError(img) {
|
186
|
+
// 移除加载占位图
|
187
|
+
const placeholder = img.parentElement?.querySelector('.animate-pulse');
|
188
|
+
if (placeholder) {
|
189
|
+
placeholder.classList.add('opacity-0');
|
190
|
+
setTimeout(() => {
|
191
|
+
placeholder.remove();
|
192
|
+
}, 300);
|
193
|
+
}
|
194
|
+
|
195
|
+
// 显示错误占位图
|
196
|
+
const errorPlaceholder = img.parentElement?.querySelector('.bg-error\\/10');
|
197
|
+
if (errorPlaceholder) {
|
198
|
+
errorPlaceholder.classList.remove('hidden');
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
// 初始化图片加载处理
|
203
|
+
function initializeImageHandlers() {
|
204
|
+
const images = document.querySelectorAll('img[loading="lazy"]');
|
205
|
+
images.forEach(img => {
|
206
|
+
if (img instanceof HTMLImageElement) {
|
207
|
+
// 如果图片已经加载完成
|
208
|
+
if (img.complete) {
|
209
|
+
handleImageLoad(img);
|
210
|
+
} else {
|
211
|
+
// 添加加载事件监听
|
212
|
+
img.addEventListener('load', () => handleImageLoad(img));
|
213
|
+
img.addEventListener('error', () => handleImageError(img));
|
214
|
+
}
|
215
|
+
}
|
216
|
+
});
|
217
|
+
}
|
218
|
+
|
219
|
+
// 页面加载时初始化
|
220
|
+
document.addEventListener('astro:page-load', initializeImageHandlers);
|
221
|
+
// 初始加载时也初始化
|
222
|
+
initializeImageHandlers();
|
223
|
+
</script>
|
224
|
+
|
225
|
+
<style>
|
226
|
+
/* 过渡动画 */
|
227
|
+
.transition-opacity {
|
228
|
+
transition-property: opacity;
|
229
|
+
}
|
230
|
+
|
231
|
+
.transition-transform {
|
232
|
+
transition-property: transform;
|
233
|
+
}
|
234
|
+
|
235
|
+
.transition-all {
|
236
|
+
transition-property: all;
|
237
|
+
}
|
238
|
+
|
239
|
+
/* 悬停效果 */
|
240
|
+
.hover\:scale-105:hover {
|
241
|
+
transform: scale(1.05);
|
242
|
+
}
|
243
|
+
|
244
|
+
.hover\:brightness-110:hover {
|
245
|
+
filter: brightness(1.1);
|
246
|
+
}
|
247
|
+
|
248
|
+
.hover\:blur-sm:hover {
|
249
|
+
filter: blur(4px);
|
250
|
+
}
|
251
|
+
</style>
|
package/src/env.d.ts
CHANGED
@@ -1 +0,0 @@
|
|
1
|
-
/// <reference types="astro/client" />
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export interface Logo {
|
2
|
+
src: string;
|
3
|
+
alt: string;
|
4
|
+
}
|
5
|
+
|
6
|
+
export interface Product {
|
7
|
+
name: string;
|
8
|
+
href: string;
|
9
|
+
external?: boolean;
|
10
|
+
}
|
11
|
+
|
12
|
+
export interface SocialLink {
|
13
|
+
name: string;
|
14
|
+
url: string;
|
15
|
+
platform: string;
|
16
|
+
}
|
17
|
+
|
18
|
+
export interface FooterProps {
|
19
|
+
siteName: string;
|
20
|
+
homeLink: string;
|
21
|
+
slogan: string;
|
22
|
+
company: string;
|
23
|
+
copyright: string;
|
24
|
+
inspirationalSlogan: string;
|
25
|
+
icp?: string;
|
26
|
+
logo?: Logo;
|
27
|
+
products?: Product[];
|
28
|
+
aboutLink?: string;
|
29
|
+
contactLink?: string;
|
30
|
+
termsLink?: string;
|
31
|
+
privacyLink?: string;
|
32
|
+
socialLinks?: SocialLink[];
|
33
|
+
}
|
package/src/utils/social.ts
CHANGED
@@ -4,6 +4,13 @@ interface PlatformConfig {
|
|
4
4
|
domains: string[];
|
5
5
|
}
|
6
6
|
|
7
|
+
// 社交链接类型
|
8
|
+
export interface SocialLink {
|
9
|
+
name: string;
|
10
|
+
url: string;
|
11
|
+
platform: string;
|
12
|
+
}
|
13
|
+
|
7
14
|
// 处理后的社交链接类型
|
8
15
|
export interface ProcessedSocialLink {
|
9
16
|
url: string;
|
@@ -69,21 +76,21 @@ function detectPlatform(url: string): [string, PlatformConfig] | null {
|
|
69
76
|
}
|
70
77
|
|
71
78
|
// 处理社交链接
|
72
|
-
export function processSocialLink(
|
73
|
-
const platformInfo = detectPlatform(url);
|
79
|
+
export function processSocialLink(link: SocialLink): ProcessedSocialLink {
|
80
|
+
const platformInfo = detectPlatform(link.url);
|
74
81
|
|
75
82
|
if (!platformInfo) {
|
76
83
|
// 如果无法识别平台,返回默认值
|
77
84
|
return {
|
78
|
-
url,
|
79
|
-
name:
|
85
|
+
url: link.url,
|
86
|
+
name: link.name,
|
80
87
|
platform: 'default',
|
81
88
|
};
|
82
89
|
}
|
83
90
|
|
84
91
|
const [platform, config] = platformInfo;
|
85
92
|
return {
|
86
|
-
url,
|
93
|
+
url: link.url,
|
87
94
|
name: config.name,
|
88
95
|
platform,
|
89
96
|
};
|