@windrun-huaiin/third-ui 29.2.1 → 30.1.0
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/fuma/fuma-page-genarator.d.ts +2 -6
- package/dist/fuma/fuma-page-genarator.js +3 -2
- package/dist/fuma/fuma-page-genarator.mjs +3 -2
- package/dist/fuma/mdx/cheet-table.d.ts +13 -0
- package/dist/fuma/mdx/cheet-table.js +295 -0
- package/dist/fuma/mdx/cheet-table.mjs +293 -0
- package/dist/fuma/mdx/index.d.ts +1 -0
- package/dist/fuma/mdx/index.js +2 -0
- package/dist/fuma/mdx/index.mjs +1 -0
- package/dist/fuma/server/features/widgets.js +2 -0
- package/dist/fuma/server/features/widgets.mjs +2 -0
- package/dist/lib/fuma-schema-check-util.d.ts +1 -1
- package/dist/main/404-page.d.ts +12 -0
- package/dist/main/404-page.js +66 -0
- package/dist/main/404-page.mjs +64 -0
- package/dist/main/alert-dialog/confirm-dialog.js +1 -1
- package/dist/main/alert-dialog/confirm-dialog.mjs +2 -2
- package/dist/main/alert-dialog/dialog-loading-action.js +5 -2
- package/dist/main/alert-dialog/dialog-loading-action.mjs +5 -2
- package/dist/main/alert-dialog/dialog-styles.d.ts +4 -2
- package/dist/main/alert-dialog/dialog-styles.js +8 -4
- package/dist/main/alert-dialog/dialog-styles.mjs +7 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.js +5 -5
- package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +6 -6
- package/dist/main/alert-dialog/info-dialog.js +1 -1
- package/dist/main/alert-dialog/info-dialog.mjs +2 -2
- package/dist/main/alert-dialog/undoable-confirm-dialog.js +2 -2
- package/dist/main/alert-dialog/undoable-confirm-dialog.mjs +3 -3
- package/dist/main/anime/anime-404-page.d.ts +14 -0
- package/dist/main/anime/anime-404-page.js +197 -0
- package/dist/main/anime/anime-404-page.mjs +195 -0
- package/dist/main/anime/anime-beam-frame.d.ts +3 -0
- package/dist/main/anime/anime-beam-frame.js +63 -0
- package/dist/main/anime/anime-beam-frame.mjs +61 -0
- package/dist/main/anime/anime-not-found-page.d.ts +7 -0
- package/dist/main/anime/anime-not-found-page.js +142 -0
- package/dist/main/anime/anime-not-found-page.mjs +140 -0
- package/dist/main/anime/anime-spiral-loading.d.ts +10 -0
- package/dist/main/anime/anime-spiral-loading.js +77 -0
- package/dist/main/anime/anime-spiral-loading.mjs +75 -0
- package/dist/main/anime/index.d.ts +3 -0
- package/dist/main/anime/index.js +12 -0
- package/dist/main/anime/index.mjs +4 -0
- package/dist/main/beam-frame/animate.d.ts +3 -0
- package/dist/main/beam-frame/animate.js +63 -0
- package/dist/main/beam-frame/animate.mjs +61 -0
- package/dist/main/beam-frame/beam-frame.d.ts +4 -0
- package/dist/main/beam-frame/beam-frame.js +262 -0
- package/dist/main/beam-frame/beam-frame.mjs +258 -0
- package/dist/main/beam-frame/index.d.ts +4 -0
- package/dist/main/beam-frame/index.js +11 -0
- package/dist/main/beam-frame/index.mjs +3 -0
- package/dist/main/beam-frame/motion.d.ts +3 -0
- package/dist/main/beam-frame/motion.js +61 -0
- package/dist/main/beam-frame/motion.mjs +59 -0
- package/dist/main/beam-frame/share-config.d.ts +54 -0
- package/dist/main/beam-frame/share-config.js +161 -0
- package/dist/main/beam-frame/share-config.mjs +152 -0
- package/dist/main/beam-frame-config.d.ts +54 -0
- package/dist/main/beam-frame-config.js +161 -0
- package/dist/main/beam-frame-config.mjs +152 -0
- package/dist/main/calendar/random-date-range-dialog.js +177 -51
- package/dist/main/calendar/random-date-range-dialog.mjs +178 -52
- package/dist/main/cta.js +17 -1
- package/dist/main/cta.mjs +18 -2
- package/dist/main/delayed-img.d.ts +1 -1
- package/dist/main/delayed-img.js +8 -5
- package/dist/main/delayed-img.mjs +8 -5
- package/dist/main/index.d.ts +1 -0
- package/dist/main/index.js +2 -0
- package/dist/main/index.mjs +1 -0
- package/dist/main/info-tooltip.js +70 -9
- package/dist/main/info-tooltip.mjs +70 -9
- package/dist/main/loading-frame/index.d.ts +1 -0
- package/dist/main/loading.d.ts +2 -1
- package/dist/main/loading.js +64 -26
- package/dist/main/loading.mjs +64 -26
- package/dist/main/motion/creative-left-panel.d.ts +7 -0
- package/dist/main/motion/creative-left-panel.js +11 -0
- package/dist/main/motion/creative-left-panel.mjs +9 -0
- package/dist/main/motion/creative-right-panel.d.ts +7 -0
- package/dist/main/motion/creative-right-panel.js +11 -0
- package/dist/main/motion/creative-right-panel.mjs +9 -0
- package/dist/main/motion/index.d.ts +1 -0
- package/dist/main/motion/index.js +9 -0
- package/dist/main/motion/index.mjs +2 -0
- package/dist/main/motion/motion-beam-frame.d.ts +3 -0
- package/dist/main/motion/motion-beam-frame.js +61 -0
- package/dist/main/motion/motion-beam-frame.mjs +59 -0
- package/dist/main/snake-loading-frame.d.ts +7 -3
- package/dist/main/snake-loading-frame.js +45 -252
- package/dist/main/snake-loading-frame.mjs +47 -254
- package/package.json +16 -5
- package/src/fuma/fuma-page-genarator.tsx +2 -22
- package/src/fuma/mdx/cheet-table.tsx +650 -0
- package/src/fuma/mdx/index.ts +1 -0
- package/src/fuma/server/features/widgets.tsx +2 -0
- package/src/main/404-page.tsx +162 -0
- package/src/main/alert-dialog/confirm-dialog.tsx +2 -1
- package/src/main/alert-dialog/dialog-loading-action.tsx +7 -5
- package/src/main/alert-dialog/dialog-styles.ts +13 -3
- package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +26 -23
- package/src/main/alert-dialog/info-dialog.tsx +2 -1
- package/src/main/alert-dialog/undoable-confirm-dialog.tsx +18 -17
- package/src/main/anime/anime-404-page.tsx +344 -0
- package/src/main/anime/anime-beam-frame.tsx +128 -0
- package/src/main/anime/anime-spiral-loading.tsx +123 -0
- package/src/main/anime/index.ts +10 -0
- package/src/main/beam-frame-config.tsx +341 -0
- package/src/main/calendar/random-date-range-dialog.tsx +225 -69
- package/src/main/cta.tsx +50 -21
- package/src/main/delayed-img.tsx +9 -4
- package/src/main/index.ts +1 -0
- package/src/main/info-tooltip.tsx +116 -20
- package/src/main/loading-frame/index.ts +4 -0
- package/src/main/loading.tsx +75 -24
- package/src/main/motion/index.ts +8 -0
- package/src/main/motion/motion-beam-frame.tsx +137 -0
- package/src/main/snake-loading-frame.tsx +95 -496
- package/src/styles/cta.css +21 -4
- package/src/styles/third-ui.css +0 -20
|
@@ -1,43 +1,42 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type { ReactNode
|
|
4
|
-
import { useEffect,
|
|
5
|
-
import { motion } from '
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { motion } from 'motion/react';
|
|
6
6
|
import { cn } from '@windrun-huaiin/lib/utils';
|
|
7
|
+
import { AnimeBeamFrame } from './anime';
|
|
8
|
+
import type { BeamFrameTone } from './beam-frame-config';
|
|
7
9
|
|
|
8
10
|
type SnakeShape = 'circle' | 'rounded-rect';
|
|
9
11
|
|
|
10
|
-
interface SnakeLoadingFrameProps {
|
|
12
|
+
export interface SnakeLoadingFrameProps {
|
|
11
13
|
shape: SnakeShape;
|
|
12
14
|
loading: boolean;
|
|
13
15
|
children: ReactNode;
|
|
16
|
+
paused?: boolean;
|
|
14
17
|
className?: string;
|
|
15
18
|
themeColor?: string;
|
|
19
|
+
tone?: BeamFrameTone;
|
|
16
20
|
strokeWidth?: number;
|
|
17
21
|
contentClassName?: string;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
interface SnakeLoadingPreviewProps {
|
|
24
|
+
export interface SnakeLoadingPreviewProps {
|
|
21
25
|
shape: SnakeShape;
|
|
22
26
|
children: ReactNode;
|
|
23
27
|
className?: string;
|
|
24
28
|
themeColor?: string;
|
|
29
|
+
tone?: BeamFrameTone;
|
|
25
30
|
defaultProgress?: number;
|
|
26
31
|
strokeWidth?: number;
|
|
27
32
|
contentClassName?: string;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const DEFAULT_THEME_COLOR = '#3b82f6';
|
|
31
|
-
const TRACK_COLOR = 'rgba(148, 163, 184, 0.22)';
|
|
32
|
-
const BODY_LENGTH_RATIO = 0.26;
|
|
33
36
|
const EXIT_DURATION_MS = 260;
|
|
34
37
|
const LOOP_DURATION_SECONDS = 1.85;
|
|
35
38
|
const DEFAULT_CIRCLE_STROKE = 0.5;
|
|
36
39
|
const DEFAULT_RECT_STROKE = 1;
|
|
37
|
-
const MIN_FRAME_SIZE = 2;
|
|
38
|
-
const MIN_BODY_LENGTH = 24;
|
|
39
|
-
const MAX_BODY_LENGTH_RATIO = 0.36;
|
|
40
|
-
const RECT_MIN_STROKE_WIDTH = 1;
|
|
41
40
|
|
|
42
41
|
function clampProgress(progress: number): number {
|
|
43
42
|
if (!Number.isFinite(progress)) {
|
|
@@ -71,488 +70,34 @@ function createBodyTailColor(themeColor: string): string {
|
|
|
71
70
|
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.18)`;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
function
|
|
75
|
-
const rgb = hexToRgb(themeColor);
|
|
76
|
-
|
|
77
|
-
if (!rgb) {
|
|
78
|
-
return 'rgba(59, 130, 246, 0.94)';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.94)`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function createSweepTailColor(themeColor: string): string {
|
|
85
|
-
const rgb = hexToRgb(themeColor);
|
|
86
|
-
|
|
87
|
-
if (!rgb) {
|
|
88
|
-
return 'rgba(59, 130, 246, 0.32)';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.32)`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
interface CornerRadius {
|
|
95
|
-
x: number;
|
|
96
|
-
y: number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
interface RectRadii {
|
|
100
|
-
topLeft: CornerRadius;
|
|
101
|
-
topRight: CornerRadius;
|
|
102
|
-
bottomRight: CornerRadius;
|
|
103
|
-
bottomLeft: CornerRadius;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
interface RingGeometry {
|
|
107
|
-
viewBox: string;
|
|
108
|
-
path: string;
|
|
109
|
-
length: number;
|
|
110
|
-
strokeWidth: number;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function parseRadiusValue(value: string): CornerRadius {
|
|
114
|
-
const parts = value
|
|
115
|
-
.split(/\s+/)
|
|
116
|
-
.map((part) => Number.parseFloat(part))
|
|
117
|
-
.filter((part) => Number.isFinite(part));
|
|
118
|
-
|
|
119
|
-
if (parts.length === 0) {
|
|
120
|
-
return { x: 0, y: 0 };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (parts.length === 1) {
|
|
124
|
-
return { x: parts[0], y: parts[0] };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { x: parts[0], y: parts[1] };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function clampCornerRadius(
|
|
131
|
-
radius: CornerRadius,
|
|
132
|
-
maxX: number,
|
|
133
|
-
maxY: number,
|
|
134
|
-
): CornerRadius {
|
|
135
|
-
return {
|
|
136
|
-
x: Math.max(0, Math.min(radius.x, maxX)),
|
|
137
|
-
y: Math.max(0, Math.min(radius.y, maxY)),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function scaleHorizontalPair(
|
|
142
|
-
start: CornerRadius,
|
|
143
|
-
end: CornerRadius,
|
|
144
|
-
limit: number,
|
|
145
|
-
): [CornerRadius, CornerRadius] {
|
|
146
|
-
const sum = start.x + end.x;
|
|
147
|
-
|
|
148
|
-
if (sum <= limit || sum === 0) {
|
|
149
|
-
return [start, end];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const scale = limit / sum;
|
|
153
|
-
|
|
154
|
-
return [
|
|
155
|
-
{ ...start, x: start.x * scale },
|
|
156
|
-
{ ...end, x: end.x * scale },
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function scaleVerticalPair(
|
|
161
|
-
start: CornerRadius,
|
|
162
|
-
end: CornerRadius,
|
|
163
|
-
limit: number,
|
|
164
|
-
): [CornerRadius, CornerRadius] {
|
|
165
|
-
const sum = start.y + end.y;
|
|
166
|
-
|
|
167
|
-
if (sum <= limit || sum === 0) {
|
|
168
|
-
return [start, end];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const scale = limit / sum;
|
|
172
|
-
|
|
173
|
-
return [
|
|
174
|
-
{ ...start, y: start.y * scale },
|
|
175
|
-
{ ...end, y: end.y * scale },
|
|
176
|
-
];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function normalizeRectRadii(
|
|
180
|
-
width: number,
|
|
181
|
-
height: number,
|
|
182
|
-
input: RectRadii,
|
|
183
|
-
): RectRadii {
|
|
184
|
-
let topLeft = clampCornerRadius(input.topLeft, width / 2, height / 2);
|
|
185
|
-
let topRight = clampCornerRadius(input.topRight, width / 2, height / 2);
|
|
186
|
-
let bottomRight = clampCornerRadius(input.bottomRight, width / 2, height / 2);
|
|
187
|
-
let bottomLeft = clampCornerRadius(input.bottomLeft, width / 2, height / 2);
|
|
188
|
-
|
|
189
|
-
[topLeft, topRight] = scaleHorizontalPair(topLeft, topRight, width);
|
|
190
|
-
[bottomLeft, bottomRight] = scaleHorizontalPair(bottomLeft, bottomRight, width);
|
|
191
|
-
[topLeft, bottomLeft] = scaleVerticalPair(topLeft, bottomLeft, height);
|
|
192
|
-
[topRight, bottomRight] = scaleVerticalPair(topRight, bottomRight, height);
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
topLeft,
|
|
196
|
-
topRight,
|
|
197
|
-
bottomRight,
|
|
198
|
-
bottomLeft,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function buildRoundedRectPath(
|
|
203
|
-
x: number,
|
|
204
|
-
y: number,
|
|
205
|
-
width: number,
|
|
206
|
-
height: number,
|
|
207
|
-
radii: RectRadii,
|
|
208
|
-
): string {
|
|
209
|
-
const { topLeft, topRight, bottomRight, bottomLeft } = normalizeRectRadii(
|
|
210
|
-
width,
|
|
211
|
-
height,
|
|
212
|
-
radii,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
return [
|
|
216
|
-
`M ${x + topLeft.x} ${y}`,
|
|
217
|
-
`H ${x + Math.max(topLeft.x, width - topRight.x)}`,
|
|
218
|
-
topRight.x > 0 || topRight.y > 0
|
|
219
|
-
? `A ${topRight.x} ${topRight.y} 0 0 1 ${x + width} ${y + topRight.y}`
|
|
220
|
-
: `L ${x + width} ${y}`,
|
|
221
|
-
`V ${y + Math.max(topRight.y, height - bottomRight.y)}`,
|
|
222
|
-
bottomRight.x > 0 || bottomRight.y > 0
|
|
223
|
-
? `A ${bottomRight.x} ${bottomRight.y} 0 0 1 ${x + width - bottomRight.x} ${y + height}`
|
|
224
|
-
: `L ${x + width} ${y + height}`,
|
|
225
|
-
`H ${x + Math.min(width - bottomRight.x, bottomLeft.x)}`,
|
|
226
|
-
bottomLeft.x > 0 || bottomLeft.y > 0
|
|
227
|
-
? `A ${bottomLeft.x} ${bottomLeft.y} 0 0 1 ${x} ${y + height - bottomLeft.y}`
|
|
228
|
-
: `L ${x} ${y + height}`,
|
|
229
|
-
`V ${y + Math.min(height - bottomLeft.y, topLeft.y)}`,
|
|
230
|
-
topLeft.x > 0 || topLeft.y > 0
|
|
231
|
-
? `A ${topLeft.x} ${topLeft.y} 0 0 1 ${x + topLeft.x} ${y}`
|
|
232
|
-
: `L ${x} ${y}`,
|
|
233
|
-
'Z',
|
|
234
|
-
].join(' ');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function createCircleGeometry(
|
|
238
|
-
width: number,
|
|
239
|
-
height: number,
|
|
240
|
-
strokeWidth: number,
|
|
241
|
-
): RingGeometry {
|
|
242
|
-
const safeWidth = Math.max(width, MIN_FRAME_SIZE);
|
|
243
|
-
const safeHeight = Math.max(height, MIN_FRAME_SIZE);
|
|
244
|
-
const radius = Math.max(0, Math.min(safeWidth, safeHeight) / 2 - strokeWidth / 2);
|
|
245
|
-
const centerX = safeWidth / 2;
|
|
246
|
-
const centerY = safeHeight / 2;
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
viewBox: `0 0 ${safeWidth} ${safeHeight}`,
|
|
250
|
-
path: `M ${centerX} ${centerY - radius} A ${radius} ${radius} 0 1 1 ${centerX} ${
|
|
251
|
-
centerY + radius
|
|
252
|
-
} A ${radius} ${radius} 0 1 1 ${centerX} ${centerY - radius}`,
|
|
253
|
-
length: Math.max(0, 2 * Math.PI * radius),
|
|
254
|
-
strokeWidth,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function createRoundedRectGeometry(
|
|
259
|
-
width: number,
|
|
260
|
-
height: number,
|
|
261
|
-
strokeWidth: number,
|
|
262
|
-
radii: RectRadii,
|
|
263
|
-
): RingGeometry {
|
|
264
|
-
const safeWidth = Math.max(width, MIN_FRAME_SIZE);
|
|
265
|
-
const safeHeight = Math.max(height, MIN_FRAME_SIZE);
|
|
266
|
-
const inset = strokeWidth / 2;
|
|
267
|
-
const innerWidth = Math.max(safeWidth - strokeWidth, MIN_FRAME_SIZE);
|
|
268
|
-
const innerHeight = Math.max(safeHeight - strokeWidth, MIN_FRAME_SIZE);
|
|
269
|
-
const adjustedRadii = normalizeRectRadii(innerWidth, innerHeight, {
|
|
270
|
-
topLeft: {
|
|
271
|
-
x: Math.max(0, radii.topLeft.x - inset),
|
|
272
|
-
y: Math.max(0, radii.topLeft.y - inset),
|
|
273
|
-
},
|
|
274
|
-
topRight: {
|
|
275
|
-
x: Math.max(0, radii.topRight.x - inset),
|
|
276
|
-
y: Math.max(0, radii.topRight.y - inset),
|
|
277
|
-
},
|
|
278
|
-
bottomRight: {
|
|
279
|
-
x: Math.max(0, radii.bottomRight.x - inset),
|
|
280
|
-
y: Math.max(0, radii.bottomRight.y - inset),
|
|
281
|
-
},
|
|
282
|
-
bottomLeft: {
|
|
283
|
-
x: Math.max(0, radii.bottomLeft.x - inset),
|
|
284
|
-
y: Math.max(0, radii.bottomLeft.y - inset),
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
viewBox: `0 0 ${safeWidth} ${safeHeight}`,
|
|
290
|
-
path: buildRoundedRectPath(inset, inset, innerWidth, innerHeight, adjustedRadii),
|
|
291
|
-
length: 0,
|
|
292
|
-
strokeWidth,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function readRectRadii(element: HTMLElement): RectRadii {
|
|
297
|
-
const computedStyle = window.getComputedStyle(element);
|
|
298
|
-
|
|
299
|
-
return {
|
|
300
|
-
topLeft: parseRadiusValue(computedStyle.borderTopLeftRadius),
|
|
301
|
-
topRight: parseRadiusValue(computedStyle.borderTopRightRadius),
|
|
302
|
-
bottomRight: parseRadiusValue(computedStyle.borderBottomRightRadius),
|
|
303
|
-
bottomLeft: parseRadiusValue(computedStyle.borderBottomLeftRadius),
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function SnakeRingSvg({
|
|
308
|
-
containerRef,
|
|
73
|
+
function StaticProgressFrame({
|
|
309
74
|
shape,
|
|
75
|
+
progress,
|
|
310
76
|
themeColor,
|
|
311
|
-
progressRatio,
|
|
312
|
-
animate,
|
|
313
|
-
strokeWidth,
|
|
314
77
|
}: {
|
|
315
|
-
containerRef: RefObject<HTMLDivElement | null>;
|
|
316
78
|
shape: SnakeShape;
|
|
79
|
+
progress: number;
|
|
317
80
|
themeColor: string;
|
|
318
|
-
progressRatio: number;
|
|
319
|
-
animate: boolean;
|
|
320
|
-
strokeWidth?: number;
|
|
321
81
|
}) {
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
const headGlowColor = createHeadGlowColor(themeColor);
|
|
325
|
-
const sweepTailColor = createSweepTailColor(themeColor);
|
|
326
|
-
const [geometry, setGeometry] = useState<RingGeometry | null>(null);
|
|
327
|
-
const pathMeasureRef = useRef<SVGPathElement | null>(null);
|
|
328
|
-
const measuredLengthRef = useRef(0);
|
|
329
|
-
|
|
330
|
-
useEffect(() => {
|
|
331
|
-
const container = containerRef.current;
|
|
332
|
-
|
|
333
|
-
if (!container) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const updateGeometry = () => {
|
|
338
|
-
const rect = container.getBoundingClientRect();
|
|
339
|
-
const preferredStrokeWidth =
|
|
340
|
-
strokeWidth ?? (shape === 'circle' ? DEFAULT_CIRCLE_STROKE : DEFAULT_RECT_STROKE);
|
|
341
|
-
const resolvedStrokeWidth =
|
|
342
|
-
shape === 'rounded-rect'
|
|
343
|
-
? Math.max(RECT_MIN_STROKE_WIDTH, preferredStrokeWidth)
|
|
344
|
-
: preferredStrokeWidth;
|
|
345
|
-
const nextGeometry =
|
|
346
|
-
shape === 'circle'
|
|
347
|
-
? createCircleGeometry(rect.width, rect.height, resolvedStrokeWidth)
|
|
348
|
-
: createRoundedRectGeometry(
|
|
349
|
-
rect.width,
|
|
350
|
-
rect.height,
|
|
351
|
-
resolvedStrokeWidth,
|
|
352
|
-
readRectRadii(container),
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
measuredLengthRef.current = 0;
|
|
356
|
-
setGeometry(nextGeometry);
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
updateGeometry();
|
|
360
|
-
|
|
361
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
362
|
-
updateGeometry();
|
|
363
|
-
});
|
|
364
|
-
const mutationObserver = new MutationObserver(() => {
|
|
365
|
-
updateGeometry();
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
resizeObserver.observe(container);
|
|
369
|
-
mutationObserver.observe(container, {
|
|
370
|
-
attributes: true,
|
|
371
|
-
attributeFilter: ['class', 'style'],
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
return () => {
|
|
375
|
-
resizeObserver.disconnect();
|
|
376
|
-
mutationObserver.disconnect();
|
|
377
|
-
};
|
|
378
|
-
}, [containerRef, shape, strokeWidth]);
|
|
379
|
-
|
|
380
|
-
useEffect(() => {
|
|
381
|
-
if (!geometry || shape === 'circle' || !pathMeasureRef.current) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const measuredLength = pathMeasureRef.current.getTotalLength();
|
|
386
|
-
const normalizedLength = Number.isFinite(measuredLength)
|
|
387
|
-
? Math.max(0, measuredLength)
|
|
388
|
-
: 0;
|
|
389
|
-
|
|
390
|
-
if (normalizedLength <= 0) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (Math.abs(measuredLengthRef.current - normalizedLength) < 0.1) {
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
measuredLengthRef.current = normalizedLength;
|
|
399
|
-
|
|
400
|
-
setGeometry((current) => {
|
|
401
|
-
if (!current || current.path !== geometry.path) {
|
|
402
|
-
return current;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (Math.abs(current.length - normalizedLength) < 0.1) {
|
|
406
|
-
return current;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
...current,
|
|
411
|
-
length: normalizedLength,
|
|
412
|
-
};
|
|
413
|
-
});
|
|
414
|
-
}, [geometry, shape]);
|
|
415
|
-
|
|
416
|
-
const resolvedGeometry = useMemo(() => geometry, [geometry]);
|
|
417
|
-
|
|
418
|
-
if (!resolvedGeometry) {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const effectiveLength =
|
|
423
|
-
resolvedGeometry.length > 0
|
|
424
|
-
? resolvedGeometry.length
|
|
425
|
-
: Math.max(
|
|
426
|
-
1,
|
|
427
|
-
2 * Math.max(0, resolvedGeometry.strokeWidth),
|
|
428
|
-
2 *
|
|
429
|
-
(Math.max(0, Number.parseFloat(resolvedGeometry.viewBox.split(' ')[2] ?? '0')) +
|
|
430
|
-
Math.max(0, Number.parseFloat(resolvedGeometry.viewBox.split(' ')[3] ?? '0'))),
|
|
431
|
-
);
|
|
432
|
-
const [, , viewBoxWidthRaw, viewBoxHeightRaw] = resolvedGeometry.viewBox.split(' ');
|
|
433
|
-
const viewBoxWidth = Math.max(0, Number.parseFloat(viewBoxWidthRaw ?? '0'));
|
|
434
|
-
const viewBoxHeight = Math.max(0, Number.parseFloat(viewBoxHeightRaw ?? '0'));
|
|
435
|
-
const centerX = viewBoxWidth / 2;
|
|
436
|
-
const centerY = viewBoxHeight / 2;
|
|
437
|
-
const isCircle = shape === 'circle';
|
|
438
|
-
const tailTransparentStart = isCircle ? '18%' : '26%';
|
|
439
|
-
const tailColorStart = isCircle ? '39%' : '46%';
|
|
440
|
-
const tailTransparentEnd = isCircle ? '90%' : '82%';
|
|
441
|
-
const headTransparentStart = isCircle ? '32%' : '40%';
|
|
442
|
-
const headColorStart = isCircle ? '48%' : '53%';
|
|
443
|
-
const headTransparentEnd = isCircle ? '82%' : '73%';
|
|
444
|
-
const bodyLength = Math.min(
|
|
445
|
-
effectiveLength * MAX_BODY_LENGTH_RATIO,
|
|
446
|
-
Math.max(MIN_BODY_LENGTH, effectiveLength * BODY_LENGTH_RATIO),
|
|
447
|
-
);
|
|
448
|
-
const bodyDashArray = `${bodyLength} ${effectiveLength}`;
|
|
449
|
-
const staticDashOffset = effectiveLength * (1 - progressRatio);
|
|
450
|
-
const tailStrokeWidth =
|
|
451
|
-
shape === 'circle'
|
|
452
|
-
? resolvedGeometry.strokeWidth + 1.2
|
|
453
|
-
: resolvedGeometry.strokeWidth + Math.min(1.2, resolvedGeometry.strokeWidth * 0.32);
|
|
82
|
+
const trackColor = 'rgba(148, 163, 184, 0.22)';
|
|
83
|
+
const progressRatio = clampProgress(progress);
|
|
454
84
|
|
|
455
85
|
return (
|
|
456
|
-
<
|
|
457
|
-
className="pointer-events-none absolute inset-0 h-full w-full"
|
|
458
|
-
viewBox={resolvedGeometry.viewBox}
|
|
459
|
-
preserveAspectRatio="none"
|
|
86
|
+
<div
|
|
460
87
|
aria-hidden="true"
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)
|
|
465
|
-
{
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
>
|
|
475
|
-
<stop offset="0%" stopColor="transparent" />
|
|
476
|
-
<stop offset={tailTransparentStart} stopColor="transparent" />
|
|
477
|
-
<stop offset={tailColorStart} stopColor={sweepTailColor} />
|
|
478
|
-
<stop offset="64%" stopColor={headGlowColor} />
|
|
479
|
-
<stop offset={tailTransparentEnd} stopColor="transparent" />
|
|
480
|
-
<stop offset="100%" stopColor="transparent" />
|
|
481
|
-
<animateTransform
|
|
482
|
-
attributeName="gradientTransform"
|
|
483
|
-
type="rotate"
|
|
484
|
-
from={`0 ${centerX} ${centerY}`}
|
|
485
|
-
to={`360 ${centerX} ${centerY}`}
|
|
486
|
-
dur={`${LOOP_DURATION_SECONDS}s`}
|
|
487
|
-
repeatCount="indefinite"
|
|
488
|
-
/>
|
|
489
|
-
</linearGradient>
|
|
490
|
-
<linearGradient
|
|
491
|
-
id={`${gradientId}-sweep-head`}
|
|
492
|
-
x1="0"
|
|
493
|
-
y1="0"
|
|
494
|
-
x2={String(viewBoxWidth)}
|
|
495
|
-
y2="0"
|
|
496
|
-
gradientUnits="userSpaceOnUse"
|
|
497
|
-
>
|
|
498
|
-
<stop offset="0%" stopColor="transparent" />
|
|
499
|
-
<stop offset={headTransparentStart} stopColor="transparent" />
|
|
500
|
-
<stop offset={headColorStart} stopColor={themeColor} />
|
|
501
|
-
<stop offset="63%" stopColor={headGlowColor} />
|
|
502
|
-
<stop offset={headTransparentEnd} stopColor="transparent" />
|
|
503
|
-
<stop offset="100%" stopColor="transparent" />
|
|
504
|
-
<animateTransform
|
|
505
|
-
attributeName="gradientTransform"
|
|
506
|
-
type="rotate"
|
|
507
|
-
from={`0 ${centerX} ${centerY}`}
|
|
508
|
-
to={`360 ${centerX} ${centerY}`}
|
|
509
|
-
dur={`${LOOP_DURATION_SECONDS}s`}
|
|
510
|
-
repeatCount="indefinite"
|
|
511
|
-
/>
|
|
512
|
-
</linearGradient>
|
|
513
|
-
</defs>
|
|
514
|
-
) : null}
|
|
515
|
-
<path
|
|
516
|
-
d={resolvedGeometry.path}
|
|
517
|
-
fill="none"
|
|
518
|
-
stroke={TRACK_COLOR}
|
|
519
|
-
strokeWidth={resolvedGeometry.strokeWidth}
|
|
520
|
-
strokeLinecap="round"
|
|
521
|
-
strokeLinejoin="round"
|
|
522
|
-
/>
|
|
523
|
-
{animate ? (
|
|
524
|
-
<>
|
|
525
|
-
<path
|
|
526
|
-
d={resolvedGeometry.path}
|
|
527
|
-
fill="none"
|
|
528
|
-
stroke={`url(#${gradientId}-sweep-tail)`}
|
|
529
|
-
strokeWidth={tailStrokeWidth}
|
|
530
|
-
strokeLinecap="round"
|
|
531
|
-
strokeLinejoin="round"
|
|
532
|
-
/>
|
|
533
|
-
<path
|
|
534
|
-
d={resolvedGeometry.path}
|
|
535
|
-
fill="none"
|
|
536
|
-
stroke={`url(#${gradientId}-sweep-head)`}
|
|
537
|
-
strokeWidth={resolvedGeometry.strokeWidth}
|
|
538
|
-
strokeLinecap="round"
|
|
539
|
-
strokeLinejoin="round"
|
|
540
|
-
/>
|
|
541
|
-
</>
|
|
542
|
-
) : null}
|
|
543
|
-
{!animate ? (
|
|
544
|
-
<path
|
|
545
|
-
d={resolvedGeometry.path}
|
|
546
|
-
fill="none"
|
|
547
|
-
stroke={themeColor}
|
|
548
|
-
strokeWidth={resolvedGeometry.strokeWidth}
|
|
549
|
-
strokeLinecap="round"
|
|
550
|
-
strokeLinejoin="round"
|
|
551
|
-
strokeDasharray={bodyDashArray}
|
|
552
|
-
strokeDashoffset={staticDashOffset}
|
|
553
|
-
/>
|
|
554
|
-
) : null}
|
|
555
|
-
</svg>
|
|
88
|
+
className={cn(
|
|
89
|
+
'pointer-events-none absolute inset-0 p-px',
|
|
90
|
+
shape === 'circle' ? 'rounded-full' : 'rounded-[inherit]',
|
|
91
|
+
)}
|
|
92
|
+
style={{
|
|
93
|
+
background: `conic-gradient(${themeColor} 0% ${progressRatio}%, ${trackColor} ${progressRatio}% 100%)`,
|
|
94
|
+
mask: 'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
|
|
95
|
+
maskComposite: 'exclude',
|
|
96
|
+
WebkitMask:
|
|
97
|
+
'linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)',
|
|
98
|
+
WebkitMaskComposite: 'xor',
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
556
101
|
);
|
|
557
102
|
}
|
|
558
103
|
|
|
@@ -560,23 +105,65 @@ function SnakeFrameBase({
|
|
|
560
105
|
shape,
|
|
561
106
|
loading,
|
|
562
107
|
children,
|
|
108
|
+
paused = false,
|
|
563
109
|
className,
|
|
564
110
|
themeColor = DEFAULT_THEME_COLOR,
|
|
111
|
+
tone = 'rainbow',
|
|
565
112
|
previewProgress,
|
|
566
113
|
strokeWidth,
|
|
567
114
|
contentClassName,
|
|
568
115
|
}: SnakeLoadingFrameProps & { previewProgress?: number }) {
|
|
569
116
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
117
|
+
const [frameRadius, setFrameRadius] = useState<number | undefined>(
|
|
118
|
+
shape === 'circle' ? 999 : undefined,
|
|
119
|
+
);
|
|
570
120
|
const [showOverlay, setShowOverlay] = useState(loading || previewProgress !== undefined);
|
|
571
121
|
const exitTimerRef = useRef<number | null>(null);
|
|
572
122
|
const resolvedStrokeWidth =
|
|
573
123
|
strokeWidth ?? (shape === 'circle' ? DEFAULT_CIRCLE_STROKE : DEFAULT_RECT_STROKE);
|
|
574
124
|
const circleContentInset =
|
|
575
125
|
shape === 'circle' ? Math.max(6, resolvedStrokeWidth + 4) : 0;
|
|
576
|
-
const
|
|
126
|
+
const isPreview = previewProgress !== undefined;
|
|
127
|
+
const isActivelyLoading = loading && !paused && !isPreview;
|
|
577
128
|
|
|
578
129
|
useEffect(() => {
|
|
579
|
-
if (
|
|
130
|
+
if (shape === 'circle') {
|
|
131
|
+
setFrameRadius(999);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const node = containerRef.current;
|
|
136
|
+
|
|
137
|
+
if (!node) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const updateRadius = () => {
|
|
142
|
+
const computedStyle = window.getComputedStyle(node);
|
|
143
|
+
const nextRadius = Number.parseFloat(computedStyle.borderTopLeftRadius);
|
|
144
|
+
|
|
145
|
+
setFrameRadius(Number.isFinite(nextRadius) ? nextRadius : undefined);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
updateRadius();
|
|
149
|
+
|
|
150
|
+
const resizeObserver = new ResizeObserver(updateRadius);
|
|
151
|
+
const mutationObserver = new MutationObserver(updateRadius);
|
|
152
|
+
|
|
153
|
+
resizeObserver.observe(node);
|
|
154
|
+
mutationObserver.observe(node, {
|
|
155
|
+
attributes: true,
|
|
156
|
+
attributeFilter: ['class', 'style'],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return () => {
|
|
160
|
+
resizeObserver.disconnect();
|
|
161
|
+
mutationObserver.disconnect();
|
|
162
|
+
};
|
|
163
|
+
}, [shape]);
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (isPreview) {
|
|
580
167
|
setShowOverlay(true);
|
|
581
168
|
return;
|
|
582
169
|
}
|
|
@@ -602,7 +189,7 @@ function SnakeFrameBase({
|
|
|
602
189
|
exitTimerRef.current = null;
|
|
603
190
|
}
|
|
604
191
|
};
|
|
605
|
-
}, [
|
|
192
|
+
}, [isPreview, loading]);
|
|
606
193
|
|
|
607
194
|
return (
|
|
608
195
|
<div
|
|
@@ -635,17 +222,27 @@ function SnakeFrameBase({
|
|
|
635
222
|
<motion.div
|
|
636
223
|
className="pointer-events-none absolute inset-0 overflow-visible"
|
|
637
224
|
initial={false}
|
|
638
|
-
animate={{ opacity: loading ||
|
|
225
|
+
animate={{ opacity: loading || isPreview ? 1 : 0 }}
|
|
639
226
|
transition={{ duration: EXIT_DURATION_MS / 1000, ease: 'easeOut' }}
|
|
640
227
|
>
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
228
|
+
{isPreview ? (
|
|
229
|
+
<StaticProgressFrame
|
|
230
|
+
shape={shape}
|
|
231
|
+
progress={previewProgress}
|
|
232
|
+
themeColor={themeColor}
|
|
233
|
+
/>
|
|
234
|
+
) : (
|
|
235
|
+
<AnimeBeamFrame
|
|
236
|
+
active={isActivelyLoading}
|
|
237
|
+
interactive={false}
|
|
238
|
+
tone={tone}
|
|
239
|
+
duration={LOOP_DURATION_SECONDS}
|
|
240
|
+
radius={frameRadius}
|
|
241
|
+
className="absolute inset-0 h-full w-full"
|
|
242
|
+
>
|
|
243
|
+
<div className="h-full w-full" />
|
|
244
|
+
</AnimeBeamFrame>
|
|
245
|
+
)}
|
|
649
246
|
</motion.div>
|
|
650
247
|
) : null}
|
|
651
248
|
</div>
|
|
@@ -661,6 +258,7 @@ export function SnakeLoadingPreview({
|
|
|
661
258
|
children,
|
|
662
259
|
className,
|
|
663
260
|
themeColor = DEFAULT_THEME_COLOR,
|
|
261
|
+
tone = 'rainbow',
|
|
664
262
|
defaultProgress = 32,
|
|
665
263
|
strokeWidth,
|
|
666
264
|
contentClassName,
|
|
@@ -678,6 +276,7 @@ export function SnakeLoadingPreview({
|
|
|
678
276
|
previewProgress={progress}
|
|
679
277
|
className={className}
|
|
680
278
|
themeColor={themeColor}
|
|
279
|
+
tone={tone}
|
|
681
280
|
strokeWidth={strokeWidth}
|
|
682
281
|
contentClassName={contentClassName}
|
|
683
282
|
>
|