-
Notifications
You must be signed in to change notification settings - Fork 41
Add reusable dashboard animation components #126
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import type { ReactNode } from "react"; | ||
| import AnimationWrapper from "./AnimationWrapper"; | ||
|
|
||
| interface AnimatedCardProps { | ||
| children: ReactNode; | ||
| delay?: number; | ||
| className?: string; | ||
| } | ||
|
|
||
| export default function AnimatedCard(props: AnimatedCardProps) { | ||
| return <AnimationWrapper {...props} type="scale" />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import type { ReactNode } from "react"; | ||
| import { motion } from "framer-motion"; | ||
|
|
||
| import { fadeIn, slideUp, slideLeft, scaleIn } from "@/utils/animationConfig"; | ||
| import { useInViewAnimation } from "@/hooks/useInViewAnimation"; | ||
|
|
||
| type AnimationType = "fade" | "slideUp" | "slideLeft" | "scale"; | ||
|
|
||
| interface AnimationWrapperProps { | ||
| children: ReactNode; | ||
| type?: AnimationType; | ||
| delay?: number; | ||
| duration?: number; | ||
| className?: string; | ||
| once?: boolean; | ||
| } | ||
|
|
||
| const variantsMap = { | ||
| fade: fadeIn, | ||
| slideUp: slideUp, | ||
| slideLeft: slideLeft, | ||
| scale: scaleIn, | ||
| }; | ||
|
|
||
| export default function AnimationWrapper({ | ||
| children, | ||
| type = "fade", | ||
| delay = 0, | ||
| duration, | ||
| className, | ||
| once = true, | ||
| }: AnimationWrapperProps) { | ||
| const { ref, isInView, prefersReducedMotion } = | ||
| useInViewAnimation({ once }); | ||
|
Comment on lines
+33
to
+34
|
||
|
|
||
| if (prefersReducedMotion) { | ||
| return <div className={className}>{children}</div>; | ||
| } | ||
|
|
||
| const variants = variantsMap[type]; | ||
|
|
||
| return ( | ||
| <motion.div | ||
| ref={ref as React.Ref<HTMLDivElement>} | ||
| className={className} | ||
| variants={variants} | ||
| initial="hidden" | ||
| animate={isInView ? "visible" : "hidden"} | ||
| transition={{ | ||
| delay, | ||
| ...(duration ? { duration } : {}), | ||
|
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. Suggestion: The duration override uses a truthy check, so passing Severity Level: Major
|
||
| }} | ||
|
Comment on lines
+49
to
+52
|
||
| > | ||
| {children} | ||
| </motion.div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import type { ReactNode } from "react"; | ||
| import AnimationWrapper from "./AnimationWrapper"; | ||
|
|
||
| interface FadeInProps { | ||
| children: ReactNode; | ||
| delay?: number; | ||
| duration?: number; | ||
| className?: string; | ||
| } | ||
|
|
||
| export default function FadeIn(props: FadeInProps) { | ||
| return <AnimationWrapper {...props} type="fade" />; | ||
| } | ||
|
Comment on lines
+11
to
+13
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import type { ReactNode } from "react"; | ||
| import AnimationWrapper from "./AnimationWrapper"; | ||
|
|
||
| interface SlideInProps { | ||
| children: ReactNode; | ||
| delay?: number; | ||
| duration?: number; | ||
| className?: string; | ||
| } | ||
|
|
||
| export default function SlideIn(props: SlideInProps) { | ||
| return <AnimationWrapper {...props} type="slideUp" />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { useRef } from "react"; | ||
| import { useInView, useReducedMotion } from "framer-motion"; | ||
|
|
||
| interface UseInViewAnimationOptions { | ||
| once?: boolean; | ||
| amount?: "some" | "all" | number; | ||
| } | ||
|
|
||
| export const useInViewAnimation = ( | ||
| options: UseInViewAnimationOptions = {} | ||
| ) => { | ||
| const { once = true, amount = 0.2 } = options; | ||
|
|
||
| const ref = useRef<HTMLElement | null>(null); | ||
|
|
||
| const isInView = useInView(ref, { | ||
| once, | ||
| amount, | ||
| }); | ||
|
|
||
| const prefersReducedMotion = useReducedMotion(); | ||
|
|
||
| return { | ||
| ref, | ||
| isInView, | ||
| prefersReducedMotion, | ||
| }; | ||
| }; | ||
|
Comment on lines
+9
to
+28
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import type { Variants } from "framer-motion"; | ||
| export const fadeIn: Variants = { | ||
| hidden: { | ||
| opacity: 0, | ||
| }, | ||
| visible: { | ||
| opacity: 1, | ||
| transition: { | ||
| duration: 0.5, | ||
| ease: "easeOut", | ||
| }, | ||
| }, | ||
|
Comment on lines
+6
to
+12
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the referenced files and nearby lines.
for f in frontend/src/utils/animationConfig.ts frontend/src/components/AnimationWrapper.tsx frontend/src/pages/Dashboard.tsx; do
if [ -f "$f" ]; then
echo "===== $f ====="
wc -l "$f"
sed -n '1,220p' "$f"
echo
else
echo "MISSING: $f"
fi
doneRepository: 7-Blocks/Kepler Length of output: 10239 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the animation components referenced by Dashboard.tsx and inspect their implementations.
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
'export default|function FadeIn|const FadeIn|function SlideIn|const SlideIn|function AnimatedCard|const AnimatedCard|transition' \
frontend/src/components frontend/src/utils frontend/src/pages
echo "---- FILE LIST ----"
git ls-files 'frontend/src/components/**' 'frontend/src/utils/**' | sed -n '1,200p'Repository: 7-Blocks/Kepler Length of output: 17981 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the animation component implementations referenced in Dashboard.tsx.
for f in \
frontend/src/components/Animation/AnimatedCard.tsx \
frontend/src/components/Animation/FadeIn.tsx \
frontend/src/components/Animation/SlideIn.tsx \
frontend/src/utils/animationConfig.ts
do
if [ -f "$f" ]; then
echo "===== $f ====="
wc -l "$f"
sed -n '1,220p' "$f"
echo
else
echo "MISSING: $f"
fi
doneRepository: 7-Blocks/Kepler Length of output: 2334 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find and inspect the animation wrapper components used by Dashboard.tsx.
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
'AnimatedCard|FadeIn|SlideIn|variants|transition|delay' frontend/src/components
echo "---- candidate files ----"
fd -HI 'AnimatedCard.tsx|FadeIn.tsx|SlideIn.tsx|Animation.*\.tsx' frontend/src/componentsRepository: 7-Blocks/Kepler Length of output: 10115 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read the animation wrapper to determine how transition is composed.
f=frontend/src/components/Animation/AnimationWrapper.tsx
echo "===== $f ====="
wc -l "$f"
sed -n '1,220p' "$f"Repository: 7-Blocks/Kepler Length of output: 1536 🌐 Web query:
💡 Result: In Framer Motion, transition properties defined directly on a motion component as the Citations:
🌐 Web query:
💡 Result: In Framer Motion, the transition property defines the animation physics, such as duration, delay, easing, and spring properties [1]. When using both variants and the transition component prop, the transition prop defined directly on the motion component acts as the default animation configuration. However, if a transition is defined within a specific variant, it will take precedence for that variant state [1]. Regarding precedence and configuration: 1. Variant Precedence: If you define a transition inside a variant, it overrides the default transition defined on the component for that specific state [1]. 2. Property-Specific Overrides: The transition property allows you to define global settings for all animated properties, but you can override these for specific properties (e.g., separate settings for rotate versus scale) [1]. When you define transition settings for a specific property, the global values for that property are ignored [1]. 3. Duration and Delay: Duration and delay are standard transition settings. If omitted, duration defaults to 0.3 seconds [1]. When defining separate transition settings for individual properties, you must explicitly include these settings, such as delay and duration, because the global defaults will no longer apply to those specific properties [1][2]. Essentially, the closer a transition definition is to the animated property (i.e., within the variant vs. the component prop), the higher its priority [1]. If global settings are ignored due to a property-specific override, you must redefine all necessary timing parameters for that specific property [1]. Citations:
Move timing out of 🤖 Prompt for AI Agents |
||
| }; | ||
|
|
||
| export const slideUp: Variants = { | ||
| hidden: { | ||
| opacity: 0, | ||
| y: 20, | ||
| }, | ||
| visible: { | ||
| opacity: 1, | ||
| y: 0, | ||
| transition: { | ||
| duration: 0.5, | ||
| ease: "easeOut", | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| export const slideLeft: Variants = { | ||
| hidden: { | ||
| opacity: 0, | ||
| x: 20, | ||
| }, | ||
| visible: { | ||
| opacity: 1, | ||
| x: 0, | ||
| transition: { | ||
| duration: 0.5, | ||
| ease: "easeOut", | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| export const scaleIn: Variants = { | ||
| hidden: { | ||
| opacity: 0, | ||
| scale: 0.95, | ||
| }, | ||
| visible: { | ||
| opacity: 1, | ||
| scale: 1, | ||
| transition: { | ||
| duration: 0.45, | ||
| ease: "easeOut", | ||
| }, | ||
| }, | ||
| }; | ||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: 7-Blocks/Kepler
Length of output: 272
🏁 Script executed:
Repository: 7-Blocks/Kepler
Length of output: 228
🏁 Script executed:
Repository: 7-Blocks/Kepler
Length of output: 228
🏁 Script executed:
Repository: 7-Blocks/Kepler
Length of output: 1824
🏁 Script executed:
Repository: 7-Blocks/Kepler
Length of output: 2036
🏁 Script executed:
Repository: 7-Blocks/Kepler
Length of output: 8147
Import
Refinstead of usingReact.RefAnimationWrapper.tsxis a module and doesn’t import theReactnamespace, soref={ref as React.Ref<HTMLDivElement>}will fail type-checking under this tsconfig. Useimport type { Ref } from "react";and cast toRef<HTMLDivElement>instead.🤖 Prompt for AI Agents