-
Notifications
You must be signed in to change notification settings - Fork 0
Lottie Animation Integration for MotionForge #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |||||||||
| }, | ||||||||||
| "dependencies": { | ||||||||||
| "motionforge": "^1.2.0", | ||||||||||
| "lottie-web": "^5.13.0", | ||||||||||
|
Comment on lines
12
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Template references The template now includes ♻️ Suggested fix- "motionforge": "^1.2.0",
+ "motionforge": "^1.3.0",📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| "next": "15.1.0", | ||||||||||
| "react": "^19.0.0", | ||||||||||
| "react-dom": "^19.0.0", | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,18 @@ All notable changes to MotionForge will be documented in this file. | |||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||||
|
|
||||||
| ## [1.3.0] - 2024-03-20 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incorrect changelog date. The date -## [1.3.0] - 2024-03-20
+## [1.3.0] - 2026-02-15📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| ### Added | ||||||
| - Full Lottie animation integration with `<Lottie />` component | ||||||
| - Deterministic frame synchronization with MotionForge timeline | ||||||
| - Support for both remote JSON URLs and local JSON objects | ||||||
| - Advanced Lottie controls: `frameStart`, `frameEnd`, `playbackRate`, `loop` | ||||||
| - Automatic relative frame mapping within `<Sequence />` | ||||||
| - Production-grade performance with memoization and instance cleanup | ||||||
| - CLI template updates with Lottie support and examples | ||||||
| - Comprehensive Lottie documentation in README | ||||||
|
|
||||||
| ## [1.2.0] - 2024-01-15 | ||||||
|
|
||||||
| ### Added | ||||||
|
|
||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,182 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React, { useEffect, useRef, useState, useMemo } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lottie, { AnimationItem } from 'lottie-web'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useCurrentFrame } from '../core/context'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useRelativeCurrentFrame } from './Sequence'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export interface LottieProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Source of the Lottie animation. Can be a URL to a JSON file or an imported JSON object. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| src: string | object; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * The frame at which the Lottie animation should start playing. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Default is 0. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameStart?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * The frame at which the Lottie animation should end playing. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Default is the last frame of the Lottie animation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameEnd?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Playback rate of the Lottie animation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Default is 1. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| playbackRate?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Whether the Lottie animation should loop. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Default is false. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loop?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Width of the Lottie container. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width?: number | string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Height of the Lottie container. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height?: number | string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Style overrides for the Lottie container. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style?: React.CSSProperties; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * CSS class name for the Lottie container. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Lottie Component for MotionForge. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Provides production-grade Lottie support with deterministic frame synchronization. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Synchronizes with the MotionForge frame system instead of using time-based playback. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const Lottie: React.FC<LottieProps> = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| src, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameStart = 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameEnd, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| playbackRate = 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loop = false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const containerRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const animationRef = useRef<AnimationItem | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isLoaded, setIsLoaded] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [error, setError] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const absoluteFrame = useCurrentFrame(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const relativeFrame = useRelativeCurrentFrame(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use relative frame if inside a Sequence, otherwise use absolute frame | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentFrame = relativeFrame !== null ? relativeFrame : absoluteFrame; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle initialization | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof window === 'undefined' || !containerRef.current) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let isCancelled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const params: any = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| container: containerRef.current, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| renderer: 'svg', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| loop: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| autoplay: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rendererSettings: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| progressiveLoad: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hideOnTransparent: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof src === 'string') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params.path = src; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params.animationData = src; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const anim = lottie.loadAnimation(params); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| animationRef.current = anim; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const onLoaded = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isCancelled) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setIsLoaded(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Both events can be useful depending on how Lottie is loaded | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anim.addEventListener('DOMLoaded', onLoaded); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anim.addEventListener('data_ready', onLoaded); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isCancelled = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anim.destroy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| animationRef.current = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setError(err instanceof Error ? err.message : String(err)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => {}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [src]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Object When Additionally, the Proposed fix+ const srcRef = useRef(src);
+ const [srcKey, setSrcKey] = useState(0);
+
+ // Only re-trigger when src *actually* changes (deep-compare for objects)
+ useEffect(() => {
+ if (typeof src === 'string') {
+ if (src !== srcRef.current) {
+ srcRef.current = src;
+ setSrcKey(k => k + 1);
+ }
+ } else {
+ // For objects, JSON-serialize or use a stable reference from the caller
+ srcRef.current = src;
+ }
+ }, [src]);
// Handle initialization
useEffect(() => {
if (typeof window === 'undefined' || !containerRef.current) return;
let isCancelled = false;
+ setError(null);
+ setIsLoaded(false);
// ... rest unchanged
- }, [src]);
+ }, [srcKey]);Alternatively, document that + const srcStable = useMemo(
+ () => (typeof src === 'string' ? src : JSON.stringify(src)),
+ [src]
+ );
// ...
- }, [src]);
+ }, [srcStable]);But note 🧰 Tools🪛 ESLint[error] 122-122: Error: Calling setState synchronously within an effect can trigger cascading renders Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). /home/jailuser/git/packages/motionforge/src/components/Lottie.tsx:122:7
(react-hooks/set-state-in-effect) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle frame synchronization | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!animationRef.current || !isLoaded) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const anim = animationRef.current; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // totalFrames is available once loaded | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalLottieFrames = anim.totalFrames; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Determine the range of frames to play | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const start = frameStart; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const end = frameEnd ?? totalLottieFrames; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const duration = end - start; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (duration <= 0) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Map MotionForge currentFrame to Lottie frame | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // playbackRate affects how fast we move through the Lottie timeline | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let targetFrame = currentFrame * playbackRate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (loop) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetFrame = targetFrame % duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetFrame = Math.min(targetFrame, duration - 0.01); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // finalFrame is relative to the Lottie's original timeline | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const finalFrame = start + targetFrame; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // goToAndStop(value, isFrame) - second arg true means it's a frame number, not time | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| anim.goToAndStop(finalFrame, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+143
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Frame mapping doesn't clamp negative values, breaking with negative
Proposed fix let targetFrame = currentFrame * playbackRate;
if (loop) {
- targetFrame = targetFrame % duration;
+ targetFrame = ((targetFrame % duration) + duration) % duration;
} else {
- targetFrame = Math.min(targetFrame, duration - 0.01);
+ targetFrame = Math.max(0, Math.min(targetFrame, duration - 0.01));
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [currentFrame, isLoaded, frameStart, frameEnd, playbackRate, loop]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const containerStyle: React.CSSProperties = useMemo(() => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: width ?? '100%', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: height ?? '100%', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...style, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), [width, height, style]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div style={{ color: 'red', border: '1px solid red', padding: '10px' }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <strong>Lottie Error:</strong> {error} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref={containerRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={containerStyle} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={className} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data-lottie-loaded={isLoaded} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
External Lottie URL in a starter template is fragile and inconsistent with the demo.
This template hardcodes
https://assets.lottiefiles.com/...while the DemoLottie files usehttps://assets10.lottiefiles.com/...(different subdomain). If either URL goes stale, users see a broken hello-world experience on first run.Consider bundling a small inline JSON animation (like the
simpleLottieobject used inDemoLottie.tsx) instead of relying on an external URL for the starter template.🤖 Prompt for AI Agents