@djangocfg/ui-nextjs 1.4.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/LICENSE +21 -0
- package/README.md +152 -0
- package/package.json +110 -0
- package/src/animations/AnimatedBackground.tsx +645 -0
- package/src/animations/index.ts +2 -0
- package/src/blocks/ArticleCard.tsx +94 -0
- package/src/blocks/ArticleList.tsx +95 -0
- package/src/blocks/CTASection.tsx +136 -0
- package/src/blocks/FeatureSection.tsx +104 -0
- package/src/blocks/Hero.tsx +102 -0
- package/src/blocks/NewsletterSection.tsx +119 -0
- package/src/blocks/StatsSection.tsx +103 -0
- package/src/blocks/SuperHero.tsx +328 -0
- package/src/blocks/TestimonialSection.tsx +122 -0
- package/src/blocks/index.ts +9 -0
- package/src/components/README.md +2018 -0
- package/src/components/breadcrumb-navigation.tsx +127 -0
- package/src/components/breadcrumb.tsx +132 -0
- package/src/components/button-download.tsx +275 -0
- package/src/components/dropdown-menu.tsx +219 -0
- package/src/components/index.ts +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +338 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/components/menubar.tsx +274 -0
- package/src/components/multi-select-pro/async.tsx +608 -0
- package/src/components/multi-select-pro/helpers.tsx +84 -0
- package/src/components/multi-select-pro/index.tsx +622 -0
- package/src/components/navigation-menu.tsx +153 -0
- package/src/components/pagination-static.tsx +348 -0
- package/src/components/pagination.tsx +138 -0
- package/src/components/phone-input.tsx +276 -0
- package/src/components/sidebar.tsx +866 -0
- package/src/components/sonner.tsx +31 -0
- package/src/components/ssr-pagination.tsx +237 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/useCfgRouter.ts +153 -0
- package/src/hooks/useLocalStorage.ts +221 -0
- package/src/hooks/useQueryParams.ts +73 -0
- package/src/hooks/useSessionStorage.ts +188 -0
- package/src/hooks/useTheme.ts +57 -0
- package/src/index.ts +24 -0
- package/src/lib/index.ts +2 -0
- package/src/styles/index.css +2 -0
- package/src/theme/ForceTheme.tsx +115 -0
- package/src/theme/ThemeProvider.tsx +82 -0
- package/src/theme/ThemeToggle.tsx +52 -0
- package/src/theme/index.ts +3 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +212 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
- package/src/tools/JsonForm/widgets/index.ts +12 -0
- package/src/tools/JsonTree/index.tsx +252 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
- package/src/tools/LottiePlayer/index.tsx +54 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +163 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
- package/src/tools/Mermaid/index.tsx +40 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
- package/src/tools/OpenapiViewer/components/index.ts +14 -0
- package/src/tools/OpenapiViewer/constants.ts +39 -0
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
- package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
- package/src/tools/OpenapiViewer/index.tsx +36 -0
- package/src/tools/OpenapiViewer/types.ts +152 -0
- package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
- package/src/tools/OpenapiViewer/utils/index.ts +9 -0
- package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
- package/src/tools/PrettyCode/index.tsx +43 -0
- package/src/tools/VideoPlayer/README.md +239 -0
- package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
- package/src/tools/VideoPlayer/index.ts +9 -0
- package/src/tools/VideoPlayer/types.ts +62 -0
- package/src/tools/index.ts +43 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LottiePlayer Component
|
|
3
|
+
*
|
|
4
|
+
* Universal Lottie animation player component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import Lottie from 'react-lottie-player';
|
|
11
|
+
import { LottiePlayerProps } from './types';
|
|
12
|
+
import { useLottie } from './useLottie';
|
|
13
|
+
|
|
14
|
+
// Size presets mapping
|
|
15
|
+
const SIZE_PRESETS = {
|
|
16
|
+
xs: { width: 64, height: 64 },
|
|
17
|
+
sm: { width: 128, height: 128 },
|
|
18
|
+
md: { width: 256, height: 256 },
|
|
19
|
+
lg: { width: 384, height: 384 },
|
|
20
|
+
xl: { width: 512, height: 512 },
|
|
21
|
+
full: { width: '100%', height: '100%' },
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* LottiePlayer component for displaying Lottie animations
|
|
26
|
+
*
|
|
27
|
+
* Features:
|
|
28
|
+
* - Loads animations from URLs or objects
|
|
29
|
+
* - Size presets or custom dimensions
|
|
30
|
+
* - Playback controls (speed, direction, loop)
|
|
31
|
+
* - Loading and error states
|
|
32
|
+
* - Event callbacks
|
|
33
|
+
*
|
|
34
|
+
* Usage:
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // From URL with size preset
|
|
37
|
+
* <LottiePlayer
|
|
38
|
+
* src="https://example.com/animation.json"
|
|
39
|
+
* size="md"
|
|
40
|
+
* autoplay
|
|
41
|
+
* loop
|
|
42
|
+
* />
|
|
43
|
+
*
|
|
44
|
+
* // From object with custom size
|
|
45
|
+
* <LottiePlayer
|
|
46
|
+
* src={animationData}
|
|
47
|
+
* width={300}
|
|
48
|
+
* height={300}
|
|
49
|
+
* speed={1.5}
|
|
50
|
+
* controls
|
|
51
|
+
* />
|
|
52
|
+
*
|
|
53
|
+
* // With callbacks
|
|
54
|
+
* <LottiePlayer
|
|
55
|
+
* src="https://example.com/animation.json"
|
|
56
|
+
* onLoad={() => console.log('Animation loaded')}
|
|
57
|
+
* onComplete={() => console.log('Animation completed')}
|
|
58
|
+
* onError={(err) => console.error('Error:', err)}
|
|
59
|
+
* />
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function LottiePlayer({
|
|
63
|
+
src,
|
|
64
|
+
size = 'md',
|
|
65
|
+
width,
|
|
66
|
+
height,
|
|
67
|
+
autoplay = true,
|
|
68
|
+
loop = true,
|
|
69
|
+
speed = 1,
|
|
70
|
+
direction = 1,
|
|
71
|
+
controls = false,
|
|
72
|
+
background,
|
|
73
|
+
className,
|
|
74
|
+
showLoading = true,
|
|
75
|
+
onComplete,
|
|
76
|
+
onLoad,
|
|
77
|
+
onError,
|
|
78
|
+
}: LottiePlayerProps) {
|
|
79
|
+
// Load animation data using our custom hook
|
|
80
|
+
const { animationData, isLoading, error, retry } = useLottie({ src });
|
|
81
|
+
|
|
82
|
+
// Notify parent about load state
|
|
83
|
+
React.useEffect(() => {
|
|
84
|
+
if (animationData && onLoad) {
|
|
85
|
+
onLoad();
|
|
86
|
+
}
|
|
87
|
+
}, [animationData, onLoad]);
|
|
88
|
+
|
|
89
|
+
// Notify parent about errors
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
if (error && onError) {
|
|
92
|
+
onError(error);
|
|
93
|
+
}
|
|
94
|
+
}, [error, onError]);
|
|
95
|
+
|
|
96
|
+
// Determine dimensions
|
|
97
|
+
const dimensions = React.useMemo(() => {
|
|
98
|
+
// Custom dimensions override size preset
|
|
99
|
+
if (width !== undefined || height !== undefined) {
|
|
100
|
+
return {
|
|
101
|
+
width: width ?? SIZE_PRESETS[size].width,
|
|
102
|
+
height: height ?? SIZE_PRESETS[size].height,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Use size preset
|
|
107
|
+
return SIZE_PRESETS[size];
|
|
108
|
+
}, [size, width, height]);
|
|
109
|
+
|
|
110
|
+
// Handle complete event
|
|
111
|
+
const handleComplete = React.useCallback(() => {
|
|
112
|
+
if (onComplete) {
|
|
113
|
+
onComplete();
|
|
114
|
+
}
|
|
115
|
+
}, [onComplete]);
|
|
116
|
+
|
|
117
|
+
// Loading state
|
|
118
|
+
if (isLoading && showLoading) {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
className={`flex items-center justify-center ${className || ''}`}
|
|
122
|
+
style={{
|
|
123
|
+
width: dimensions.width,
|
|
124
|
+
height: dimensions.height,
|
|
125
|
+
background: background || 'transparent',
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<div className="flex flex-col items-center gap-2">
|
|
129
|
+
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-gray-900" />
|
|
130
|
+
<span className="text-sm text-gray-500">Loading animation...</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Error state
|
|
137
|
+
if (error) {
|
|
138
|
+
return (
|
|
139
|
+
<div
|
|
140
|
+
className={`flex items-center justify-center ${className || ''}`}
|
|
141
|
+
style={{
|
|
142
|
+
width: dimensions.width,
|
|
143
|
+
height: dimensions.height,
|
|
144
|
+
background: background || 'transparent',
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
<div className="flex flex-col items-center gap-2 p-4 text-center">
|
|
148
|
+
<svg
|
|
149
|
+
className="h-8 w-8 text-red-500"
|
|
150
|
+
fill="none"
|
|
151
|
+
stroke="currentColor"
|
|
152
|
+
viewBox="0 0 24 24"
|
|
153
|
+
>
|
|
154
|
+
<path
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
strokeLinejoin="round"
|
|
157
|
+
strokeWidth={2}
|
|
158
|
+
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
159
|
+
/>
|
|
160
|
+
</svg>
|
|
161
|
+
<div className="text-sm text-red-600">{error.message}</div>
|
|
162
|
+
<button
|
|
163
|
+
onClick={retry}
|
|
164
|
+
className="rounded bg-red-100 px-3 py-1 text-sm text-red-700 hover:bg-red-200"
|
|
165
|
+
>
|
|
166
|
+
Retry
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// No animation data
|
|
174
|
+
if (!animationData) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Render the Lottie player
|
|
179
|
+
return (
|
|
180
|
+
<div
|
|
181
|
+
className={className}
|
|
182
|
+
style={{
|
|
183
|
+
width: dimensions.width,
|
|
184
|
+
height: dimensions.height,
|
|
185
|
+
background: background || 'transparent',
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<Lottie
|
|
189
|
+
animationData={animationData}
|
|
190
|
+
play={autoplay}
|
|
191
|
+
loop={loop}
|
|
192
|
+
speed={speed}
|
|
193
|
+
direction={direction}
|
|
194
|
+
style={{
|
|
195
|
+
width: '100%',
|
|
196
|
+
height: '100%',
|
|
197
|
+
}}
|
|
198
|
+
onComplete={handleComplete}
|
|
199
|
+
rendererSettings={{
|
|
200
|
+
preserveAspectRatio: 'xMidYMid meet',
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
{controls && (
|
|
204
|
+
<div className="mt-2 flex items-center justify-center gap-2">
|
|
205
|
+
<span className="text-xs text-gray-500">
|
|
206
|
+
Speed: {speed}x | Direction: {direction === 1 ? 'Forward' : 'Reverse'}
|
|
207
|
+
</span>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LottiePlayer - Dynamic Import Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Lazy loads the LottiePlayer component for optimal bundle size
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import dynamic from 'next/dynamic';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { LottiePlayerProps } from './types';
|
|
12
|
+
|
|
13
|
+
// Dynamically import the LottiePlayer component to reduce initial bundle size
|
|
14
|
+
const LottiePlayerClient = dynamic(
|
|
15
|
+
() => import('./LottiePlayer.client').then((mod) => ({ default: mod.LottiePlayer })),
|
|
16
|
+
{
|
|
17
|
+
ssr: false,
|
|
18
|
+
loading: () => (
|
|
19
|
+
<div className="flex items-center justify-center p-8">
|
|
20
|
+
<div className="flex flex-col items-center gap-2">
|
|
21
|
+
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-gray-900" />
|
|
22
|
+
<span className="text-sm text-gray-500">Loading player...</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
),
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* LottiePlayer component wrapper with dynamic import
|
|
31
|
+
*
|
|
32
|
+
* This component automatically handles code splitting and lazy loading
|
|
33
|
+
* of the Lottie player to optimize bundle size.
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { LottiePlayer } from '@djangocfg/ui-nextjs/tools';
|
|
38
|
+
*
|
|
39
|
+
* <LottiePlayer
|
|
40
|
+
* src="https://example.com/animation.json"
|
|
41
|
+
* size="md"
|
|
42
|
+
* autoplay
|
|
43
|
+
* loop
|
|
44
|
+
* />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function LottiePlayer(props: LottiePlayerProps) {
|
|
48
|
+
return <LottiePlayerClient {...props} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Re-export types for convenience
|
|
52
|
+
export type { LottiePlayerProps, LottieSize, LottieSpeed, LottieDirection } from './types';
|
|
53
|
+
export { useLottie } from './useLottie';
|
|
54
|
+
export type { UseLottieOptions, UseLottieReturn } from './useLottie';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LottiePlayer Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the Lottie animation player component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type LottieSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
8
|
+
|
|
9
|
+
export type LottieSpeed = 0.5 | 1 | 1.5 | 2;
|
|
10
|
+
|
|
11
|
+
export type LottieDirection = 1 | -1;
|
|
12
|
+
|
|
13
|
+
export interface LottiePlayerProps {
|
|
14
|
+
/**
|
|
15
|
+
* Animation data (JSON object) or URL to load from
|
|
16
|
+
*/
|
|
17
|
+
src: string | object;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Size preset for the player
|
|
21
|
+
* @default 'md'
|
|
22
|
+
*/
|
|
23
|
+
size?: LottieSize;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Custom width (overrides size preset)
|
|
27
|
+
*/
|
|
28
|
+
width?: number | string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Custom height (overrides size preset)
|
|
32
|
+
*/
|
|
33
|
+
height?: number | string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Autoplay animation
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
autoplay?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Loop animation
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
loop?: boolean | number;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Playback speed
|
|
49
|
+
* @default 1
|
|
50
|
+
*/
|
|
51
|
+
speed?: LottieSpeed;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Direction (1 = forward, -1 = reverse)
|
|
55
|
+
* @default 1
|
|
56
|
+
*/
|
|
57
|
+
direction?: LottieDirection;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Show playback controls
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
controls?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Background color
|
|
67
|
+
*/
|
|
68
|
+
background?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* CSS class name
|
|
72
|
+
*/
|
|
73
|
+
className?: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Show loading state
|
|
77
|
+
* @default true
|
|
78
|
+
*/
|
|
79
|
+
showLoading?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Callback when animation completes
|
|
83
|
+
*/
|
|
84
|
+
onComplete?: () => void;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Callback when animation loads
|
|
88
|
+
*/
|
|
89
|
+
onLoad?: () => void;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Callback on error
|
|
93
|
+
*/
|
|
94
|
+
onError?: (error: Error) => void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LottieAnimationData {
|
|
98
|
+
v: string;
|
|
99
|
+
fr: number;
|
|
100
|
+
ip: number;
|
|
101
|
+
op: number;
|
|
102
|
+
w: number;
|
|
103
|
+
h: number;
|
|
104
|
+
nm: string;
|
|
105
|
+
ddd: number;
|
|
106
|
+
assets: any[];
|
|
107
|
+
layers: any[];
|
|
108
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLottie Hook
|
|
3
|
+
*
|
|
4
|
+
* Hook for loading and managing Lottie animation data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import { useState, useEffect, useRef } from 'react';
|
|
10
|
+
import { LottieAnimationData } from './types';
|
|
11
|
+
|
|
12
|
+
export interface UseLottieOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Animation data (JSON object) or URL to load from
|
|
15
|
+
*/
|
|
16
|
+
src: string | object;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enable caching of loaded animations
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
cache?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseLottieReturn {
|
|
26
|
+
/**
|
|
27
|
+
* Loaded animation data
|
|
28
|
+
*/
|
|
29
|
+
animationData: object | null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Loading state
|
|
33
|
+
*/
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Error state
|
|
38
|
+
*/
|
|
39
|
+
error: Error | null;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Retry loading the animation
|
|
43
|
+
*/
|
|
44
|
+
retry: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Simple in-memory cache for loaded animations
|
|
48
|
+
const animationCache = new Map<string, object>();
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Hook for loading Lottie animations from URLs or objects
|
|
52
|
+
*
|
|
53
|
+
* Features:
|
|
54
|
+
* - Loads animations from URLs or accepts animation objects directly
|
|
55
|
+
* - Caching support to prevent re-fetching the same animation
|
|
56
|
+
* - Error handling with retry capability
|
|
57
|
+
* - Loading states
|
|
58
|
+
*
|
|
59
|
+
* Usage:
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const { animationData, isLoading, error, retry } = useLottie({
|
|
62
|
+
* src: 'https://example.com/animation.json'
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
66
|
+
* if (error) return <div>Error: {error.message} <button onClick={retry}>Retry</button></div>;
|
|
67
|
+
* if (!animationData) return null;
|
|
68
|
+
*
|
|
69
|
+
* return <LottiePlayer animationData={animationData} />;
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function useLottie(options: UseLottieOptions): UseLottieReturn {
|
|
73
|
+
const { src, cache = true } = options;
|
|
74
|
+
|
|
75
|
+
const [animationData, setAnimationData] = useState<object | null>(null);
|
|
76
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
77
|
+
const [error, setError] = useState<Error | null>(null);
|
|
78
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
79
|
+
|
|
80
|
+
// Track if component is mounted to prevent state updates on unmounted component
|
|
81
|
+
const isMountedRef = useRef(true);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
isMountedRef.current = true;
|
|
85
|
+
return () => {
|
|
86
|
+
isMountedRef.current = false;
|
|
87
|
+
};
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
// If src is already an object, use it directly
|
|
92
|
+
if (typeof src === 'object' && src !== null) {
|
|
93
|
+
setAnimationData(src);
|
|
94
|
+
setIsLoading(false);
|
|
95
|
+
setError(null);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If src is a string (URL), fetch it
|
|
100
|
+
if (typeof src === 'string') {
|
|
101
|
+
const loadAnimation = async () => {
|
|
102
|
+
// Check cache first
|
|
103
|
+
if (cache && animationCache.has(src)) {
|
|
104
|
+
if (isMountedRef.current) {
|
|
105
|
+
setAnimationData(animationCache.get(src)!);
|
|
106
|
+
setIsLoading(false);
|
|
107
|
+
setError(null);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Load from URL
|
|
113
|
+
if (isMountedRef.current) {
|
|
114
|
+
setIsLoading(true);
|
|
115
|
+
setError(null);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch(src);
|
|
120
|
+
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(`Failed to load animation: ${response.status} ${response.statusText}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const data = await response.json();
|
|
126
|
+
|
|
127
|
+
// Validate that it's a valid Lottie animation
|
|
128
|
+
if (!data || typeof data !== 'object' || !data.v || !data.layers) {
|
|
129
|
+
throw new Error('Invalid Lottie animation data');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Cache the loaded animation
|
|
133
|
+
if (cache) {
|
|
134
|
+
animationCache.set(src, data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (isMountedRef.current) {
|
|
138
|
+
setAnimationData(data);
|
|
139
|
+
setIsLoading(false);
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (isMountedRef.current) {
|
|
143
|
+
setError(err instanceof Error ? err : new Error('Failed to load animation'));
|
|
144
|
+
setIsLoading(false);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
loadAnimation();
|
|
150
|
+
}
|
|
151
|
+
}, [src, cache, retryCount]);
|
|
152
|
+
|
|
153
|
+
const retry = () => {
|
|
154
|
+
setRetryCount((prev) => prev + 1);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
animationData,
|
|
159
|
+
isLoading,
|
|
160
|
+
error,
|
|
161
|
+
retry,
|
|
162
|
+
};
|
|
163
|
+
}
|