@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,11 +1,11 @@
|
|
1
1
|
---
|
2
2
|
/**
|
3
3
|
* @component Image
|
4
|
-
*
|
4
|
+
*
|
5
5
|
* @description
|
6
6
|
* Image 组件是一个增强的图片组件,提供了丰富的功能,包括懒加载、加载状态指示、错误处理、
|
7
7
|
* 以及各种视觉效果。它旨在提供更好的用户体验和性能优化。
|
8
|
-
*
|
8
|
+
*
|
9
9
|
* @design
|
10
10
|
* 设计理念:
|
11
11
|
* 1. 用户体验优先 - 提供加载状态反馈,减少用户等待焦虑
|
@@ -13,141 +13,141 @@
|
|
13
13
|
* 3. 错误处理 - 优雅地处理图片加载失败的情况
|
14
14
|
* 4. 视觉一致性 - 提供统一的圆角、阴影和过渡效果
|
15
15
|
* 5. 灵活配置 - 支持多种自定义选项,适应不同场景
|
16
|
-
*
|
16
|
+
*
|
17
17
|
* @usage
|
18
18
|
* 基本用法:
|
19
19
|
* ```astro
|
20
20
|
* <Image src="/images/photo.jpg" alt="一张照片" />
|
21
21
|
* ```
|
22
|
-
*
|
22
|
+
*
|
23
23
|
* 带样式效果:
|
24
24
|
* ```astro
|
25
|
-
* <Image
|
26
|
-
* src="/images/photo.jpg"
|
27
|
-
* alt="一张照片"
|
28
|
-
* rounded="lg"
|
29
|
-
* shadow="md"
|
30
|
-
* hover="scale"
|
25
|
+
* <Image
|
26
|
+
* src="/images/photo.jpg"
|
27
|
+
* alt="一张照片"
|
28
|
+
* rounded="lg"
|
29
|
+
* shadow="md"
|
30
|
+
* hover="scale"
|
31
31
|
* />
|
32
32
|
* ```
|
33
|
-
*
|
33
|
+
*
|
34
34
|
* 自定义加载指示器:
|
35
35
|
* ```astro
|
36
|
-
* <Image
|
37
|
-
* src="https://example.com/large-image.jpg"
|
38
|
-
* alt="远程大图"
|
39
|
-
* loadingIndicator="progress"
|
36
|
+
* <Image
|
37
|
+
* src="https://example.com/large-image.jpg"
|
38
|
+
* alt="远程大图"
|
39
|
+
* loadingIndicator="progress"
|
40
40
|
* />
|
41
41
|
* ```
|
42
42
|
*/
|
43
43
|
|
44
|
-
import
|
44
|
+
import '../../app.css';
|
45
45
|
import { AlertTriangle } from '../../index';
|
46
46
|
|
47
47
|
// 自定义图片元数据接口
|
48
48
|
interface ImageMetadata {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
src: string;
|
50
|
+
width: number;
|
51
|
+
height: number;
|
52
|
+
format: string;
|
53
53
|
}
|
54
54
|
|
55
55
|
interface Props {
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
56
|
+
/**
|
57
|
+
* 图片源,可以是本地图片或远程URL
|
58
|
+
*/
|
59
|
+
src: ImageMetadata | string;
|
60
|
+
/**
|
61
|
+
* 图片的替代文本
|
62
|
+
*/
|
63
|
+
alt: string;
|
64
|
+
/**
|
65
|
+
* 图片的宽度
|
66
|
+
*/
|
67
|
+
width?: number;
|
68
|
+
/**
|
69
|
+
* 图片的高度
|
70
|
+
*/
|
71
|
+
height?: number;
|
72
|
+
/**
|
73
|
+
* 图片的加载方式
|
74
|
+
* @default "lazy"
|
75
|
+
*/
|
76
|
+
loading?: 'lazy' | 'eager';
|
77
|
+
/**
|
78
|
+
* 图片的填充方式
|
79
|
+
* @default "cover"
|
80
|
+
*/
|
81
|
+
objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
82
|
+
/**
|
83
|
+
* 图片的位置
|
84
|
+
* @default "center"
|
85
|
+
*/
|
86
|
+
objectPosition?: string;
|
87
|
+
/**
|
88
|
+
* 是否显示加载中的占位图
|
89
|
+
* @default true
|
90
|
+
*/
|
91
|
+
showPlaceholder?: boolean;
|
92
|
+
/**
|
93
|
+
* 是否显示加载失败的错误图
|
94
|
+
* @default true
|
95
|
+
*/
|
96
|
+
showError?: boolean;
|
97
|
+
/**
|
98
|
+
* 自定义类名
|
99
|
+
*/
|
100
|
+
class?: string;
|
101
|
+
/**
|
102
|
+
* 是否启用图片懒加载
|
103
|
+
* @default true
|
104
|
+
*/
|
105
|
+
lazy?: boolean;
|
106
|
+
/**
|
107
|
+
* 图片的圆角大小
|
108
|
+
* @default "none"
|
109
|
+
*/
|
110
|
+
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';
|
111
|
+
/**
|
112
|
+
* 图片的阴影效果
|
113
|
+
* @default "none"
|
114
|
+
*/
|
115
|
+
shadow?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
116
|
+
/**
|
117
|
+
* 图片的悬停效果
|
118
|
+
* @default "none"
|
119
|
+
*/
|
120
|
+
hover?: 'none' | 'scale' | 'brightness' | 'blur';
|
121
|
+
/**
|
122
|
+
* 图片的过渡动画
|
123
|
+
* @default "none"
|
124
|
+
*/
|
125
|
+
transition?: 'none' | 'fade' | 'slide' | 'zoom';
|
126
126
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
127
|
+
/**
|
128
|
+
* 加载指示器类型
|
129
|
+
* @default "skeleton"
|
130
|
+
*/
|
131
|
+
loadingIndicator?: 'pulse' | 'spinner' | 'progress' | 'skeleton';
|
132
132
|
}
|
133
133
|
|
134
134
|
const {
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
135
|
+
src,
|
136
|
+
alt,
|
137
|
+
width,
|
138
|
+
height,
|
139
|
+
loading = 'lazy',
|
140
|
+
objectFit = 'cover',
|
141
|
+
objectPosition = 'center',
|
142
|
+
showPlaceholder = true,
|
143
|
+
showError = true,
|
144
|
+
class: className = '',
|
145
|
+
lazy = true,
|
146
|
+
rounded = 'none',
|
147
|
+
shadow = 'none',
|
148
|
+
hover = 'none',
|
149
|
+
transition = 'none',
|
150
|
+
loadingIndicator = 'skeleton',
|
151
151
|
} = Astro.props;
|
152
152
|
|
153
153
|
// 判断是否为本地图片
|
@@ -159,172 +159,184 @@ const imgSrc = typeof src === 'string' ? src : src.src;
|
|
159
159
|
|
160
160
|
// 对象映射定义所有可能的类名
|
161
161
|
const objectFitClasses = {
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
162
|
+
contain: 'cosy:object-contain',
|
163
|
+
cover: 'cosy:object-cover',
|
164
|
+
fill: 'cosy:object-fill',
|
165
|
+
none: 'cosy:object-none',
|
166
|
+
'scale-down': 'cosy:object-scale-down',
|
167
167
|
};
|
168
168
|
|
169
169
|
const roundedClasses = {
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
170
|
+
none: '',
|
171
|
+
sm: 'cosy:rounded-sm',
|
172
|
+
md: 'cosy:rounded-md',
|
173
|
+
lg: 'cosy:rounded-lg',
|
174
|
+
xl: 'cosy:rounded-xl',
|
175
|
+
'2xl': 'cosy:rounded-2xl',
|
176
|
+
'3xl': 'cosy:rounded-3xl',
|
177
|
+
full: 'cosy:rounded-full',
|
178
178
|
};
|
179
179
|
|
180
180
|
const shadowClasses = {
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
181
|
+
none: '',
|
182
|
+
sm: 'cosy:shadow-sm',
|
183
|
+
md: 'cosy:shadow-md',
|
184
|
+
lg: 'cosy:shadow-lg',
|
185
|
+
xl: 'cosy:shadow-xl',
|
186
|
+
'2xl': 'cosy:shadow-2xl',
|
187
187
|
};
|
188
188
|
|
189
189
|
const hoverClasses = {
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
190
|
+
none: '',
|
191
|
+
scale: 'cosy:hover:scale-110',
|
192
|
+
brightness: 'cosy:hover:brightness-110',
|
193
|
+
blur: 'cosy:hover:blur-sm',
|
194
194
|
};
|
195
195
|
|
196
196
|
const transitionClasses = {
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
197
|
+
none: '',
|
198
|
+
fade: 'cosy:transition-opacity cosy:duration-300',
|
199
|
+
slide: 'cosy:transition-transform cosy:duration-300',
|
200
|
+
zoom: 'cosy:transition-all cosy:duration-300',
|
201
201
|
};
|
202
202
|
|
203
203
|
// 构建图片类名
|
204
204
|
const imgClasses = [
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
]
|
205
|
+
'cosy:object-center',
|
206
|
+
objectFitClasses[objectFit] || objectFitClasses['cover'],
|
207
|
+
roundedClasses[rounded],
|
208
|
+
shadowClasses[shadow],
|
209
|
+
hoverClasses[hover],
|
210
|
+
transitionClasses[transition],
|
211
|
+
className,
|
212
|
+
]
|
213
|
+
.filter(Boolean)
|
214
|
+
.join(' ');
|
213
215
|
|
214
216
|
// 构建占位图类名
|
215
217
|
const placeholderClasses = [
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
218
|
+
'cosy:absolute cosy:inset-0',
|
219
|
+
roundedClasses[rounded],
|
220
|
+
loadingIndicator === 'skeleton' || loadingIndicator === 'pulse'
|
221
|
+
? 'cosy:animate-pulse cosy:bg-base-300'
|
222
|
+
: '',
|
223
|
+
]
|
224
|
+
.filter(Boolean)
|
225
|
+
.join(' ');
|
220
226
|
|
221
227
|
// 构建错误占位图类名
|
222
228
|
const errorClasses = [
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
]
|
229
|
+
'cosy:absolute cosy:inset-0 cosy:flex cosy:items-center cosy:justify-center',
|
230
|
+
roundedClasses[rounded],
|
231
|
+
'cosy:bg-error cosy:bg-opacity-10',
|
232
|
+
]
|
233
|
+
.filter(Boolean)
|
234
|
+
.join(' ');
|
227
235
|
---
|
228
236
|
|
229
|
-
<
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
data-remote={isRemoteImage ? 'true' : 'false'}
|
239
|
-
/>
|
237
|
+
<script is:inline>
|
238
|
+
function handleImageLoad(img) {
|
239
|
+
if (!(img instanceof HTMLImageElement)) return;
|
240
|
+
const placeholder = img.parentElement?.querySelector('[data-placeholder]');
|
241
|
+
if (placeholder) {
|
242
|
+
placeholder.classList.add('opacity-0');
|
243
|
+
setTimeout(() => placeholder.remove(), 300);
|
244
|
+
}
|
245
|
+
}
|
240
246
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
</div>
|
253
|
-
)}
|
254
|
-
</div>
|
255
|
-
)}
|
247
|
+
function handleImageError(img) {
|
248
|
+
if (!(img instanceof HTMLImageElement)) return;
|
249
|
+
const placeholder = img.parentElement?.querySelector('[data-placeholder]');
|
250
|
+
if (placeholder) {
|
251
|
+
placeholder.remove();
|
252
|
+
}
|
253
|
+
const errorElement = img.parentElement?.querySelector('[data-error]');
|
254
|
+
if (errorElement) {
|
255
|
+
errorElement.classList.remove('hidden');
|
256
|
+
}
|
257
|
+
}
|
256
258
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
</div>
|
262
|
-
)}
|
263
|
-
</div>
|
259
|
+
function simulateLoadingProgress(img) {
|
260
|
+
if (!(img instanceof HTMLImageElement) || img.getAttribute('data-remote') !== 'true') return;
|
261
|
+
const progressBar = img.parentElement?.querySelector('progress');
|
262
|
+
if (!progressBar) return;
|
264
263
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
264
|
+
let progress = 0;
|
265
|
+
const interval = setInterval(() => {
|
266
|
+
progress += Math.random() * 15;
|
267
|
+
if (progress >= 100 || img.complete) {
|
268
|
+
clearInterval(interval);
|
269
|
+
progress = 100;
|
270
|
+
}
|
271
|
+
progressBar.value = Math.min(progress, 100);
|
272
|
+
}, 200);
|
274
273
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
placeholder.remove();
|
280
|
-
}
|
281
|
-
const errorElement = img.parentElement?.querySelector('[data-error]');
|
282
|
-
if (errorElement) {
|
283
|
-
errorElement.classList.remove('hidden');
|
284
|
-
}
|
285
|
-
}
|
274
|
+
img.addEventListener('load', () => {
|
275
|
+
clearInterval(interval);
|
276
|
+
progressBar.value = 100;
|
277
|
+
});
|
286
278
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
let progress = 0;
|
293
|
-
const interval = setInterval(() => {
|
294
|
-
progress += Math.random() * 15;
|
295
|
-
if (progress >= 100 || img.complete) {
|
296
|
-
clearInterval(interval);
|
297
|
-
progress = 100;
|
298
|
-
}
|
299
|
-
progressBar.value = Math.min(progress, 100);
|
300
|
-
}, 200);
|
301
|
-
|
302
|
-
img.addEventListener('load', () => {
|
303
|
-
clearInterval(interval);
|
304
|
-
progressBar.value = 100;
|
305
|
-
});
|
306
|
-
|
307
|
-
img.addEventListener('error', () => {
|
308
|
-
clearInterval(interval);
|
309
|
-
});
|
310
|
-
}
|
279
|
+
img.addEventListener('error', () => {
|
280
|
+
clearInterval(interval);
|
281
|
+
});
|
282
|
+
}
|
311
283
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
284
|
+
function initializeImageHandlers() {
|
285
|
+
document.querySelectorAll('img[data-remote]').forEach((img) => {
|
286
|
+
if (img instanceof HTMLImageElement) {
|
287
|
+
if (img.complete) {
|
288
|
+
handleImageLoad(img);
|
289
|
+
} else {
|
290
|
+
img.addEventListener('load', () => handleImageLoad(img));
|
291
|
+
img.addEventListener('error', () => handleImageError(img));
|
292
|
+
if (img.getAttribute('data-remote') === 'true') {
|
293
|
+
simulateLoadingProgress(img);
|
294
|
+
}
|
295
|
+
}
|
296
|
+
}
|
297
|
+
});
|
298
|
+
}
|
327
299
|
|
328
|
-
|
329
|
-
|
300
|
+
document.addEventListener('astro:page-load', initializeImageHandlers);
|
301
|
+
initializeImageHandlers();
|
330
302
|
</script>
|
303
|
+
|
304
|
+
<div class="cosy:relative cosy:w-full cosy:h-full">
|
305
|
+
<img
|
306
|
+
src={imgSrc}
|
307
|
+
alt={alt}
|
308
|
+
width={width}
|
309
|
+
height={height}
|
310
|
+
loading={loading}
|
311
|
+
class={imgClasses}
|
312
|
+
style={{ objectPosition }}
|
313
|
+
data-remote={isRemoteImage ? 'true' : 'false'}
|
314
|
+
/>
|
315
|
+
|
316
|
+
{/* 加载占位图 */}
|
317
|
+
{
|
318
|
+
showPlaceholder && (
|
319
|
+
<div class={placeholderClasses} data-placeholder>
|
320
|
+
{/* 远程图片加载指示器 */}
|
321
|
+
{isRemoteImage && loadingIndicator === 'spinner' && (
|
322
|
+
<div class="cosy:absolute cosy:inset-0 cosy:m-auto cosy:text-primary cosy:loading cosy:loading-spinner cosy:loading-lg" />
|
323
|
+
)}
|
324
|
+
|
325
|
+
{isRemoteImage && loadingIndicator === 'progress' && (
|
326
|
+
<div class="cosy:right-0 cosy:bottom-0 cosy:left-0 cosy:absolute">
|
327
|
+
<progress class="cosy:w-full cosy:progress cosy:progress-primary" />
|
328
|
+
</div>
|
329
|
+
)}
|
330
|
+
</div>
|
331
|
+
)
|
332
|
+
}
|
333
|
+
|
334
|
+
{/* 错误占位图 */}
|
335
|
+
{
|
336
|
+
showError && (
|
337
|
+
<div class={errorClasses} data-error hidden>
|
338
|
+
<AlertTriangle size="32px" class="cosy:text-error" />
|
339
|
+
</div>
|
340
|
+
)
|
341
|
+
}
|
342
|
+
</div>
|