-
Notifications
You must be signed in to change notification settings - Fork 23
ux: Add animated success celebration when settled up #270
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?
ux: Add animated success celebration when settled up #270
Conversation
Co-authored-by: Devasy <110348311+Devasy@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
✅ Deploy Preview for split-but-wiser ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughIntroduces new reusable UI components for displaying settled states: a SettledUpState component with Framer Motion animations and confetti effects, plus a Confetti component for animated particles. Updates GroupDetails page to use the new SettledUpState component instead of inline UI code. Changes
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@web/components/SettledUpState.tsx`:
- Around line 1-49: The component ignores user "prefers-reduced-motion" and must
respect reduced-motion settings: use framer-motion's useReducedMotion() inside
SettledUpState to detect reduced motion, pass a flag into Confetti (e.g.,
reducedMotion or disableAnimation) and conditionally render Confetti (skip or
set count=0) when reduced; for the motion.div elements (the outer icon wrapper,
inner check, and text container) use the reducedMotion flag to replace animate
with initial (or set animate equal to initial/disable transitions) so no
entrance animations run when reducedMotion is true; update the Confetti
component similarly to accept and honor the prop so it avoids rendering or
animating when reducedMotion is true.
In `@web/components/ui/Confetti.tsx`:
- Around line 9-18: The Particle interface and Confetti rendering use
Math.random() during render which causes particle positions and border radii to
change on re-renders; modify the Particle type to include stable precomputed
fields (e.g., initialX, initialY, borderRadius, initialRotation, duration,
delay) and generate these random values once when creating particles (inside a
createInitialParticles function, useEffect, or useRef in the Confetti component)
so the render uses particle.initialX / particle.borderRadius etc. instead of
calling Math.random() inline; update any places referencing x, y, rotation or
border radius to use the new stored properties on Particle (and remove runtime
Math.random calls from the JSX) to prevent jitter.
| import { motion } from 'framer-motion'; | ||
| import { Check } from 'lucide-react'; | ||
| import React from 'react'; | ||
| import { THEMES } from '../constants'; | ||
| import { useTheme } from '../contexts/ThemeContext'; | ||
| import { Confetti } from './ui/Confetti'; | ||
|
|
||
| export const SettledUpState = () => { | ||
| const { style } = useTheme(); | ||
|
|
||
| return ( | ||
| <div className="col-span-full flex flex-col items-center justify-center py-20 relative"> | ||
| <Confetti count={100} /> | ||
|
|
||
| <motion.div | ||
| initial={{ scale: 0, rotate: -180 }} | ||
| animate={{ scale: 1, rotate: 0 }} | ||
| transition={{ | ||
| type: "spring", | ||
| stiffness: 260, | ||
| damping: 20, | ||
| delay: 0.1 | ||
| }} | ||
| className={`w-24 h-24 flex items-center justify-center mx-auto mb-6 ${ | ||
| style === THEMES.NEOBRUTALISM | ||
| ? 'bg-emerald-100 border-2 border-black rounded-none shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]' | ||
| : 'bg-emerald-100 dark:bg-emerald-900/20 rounded-full ring-4 ring-emerald-500/20' | ||
| }`} | ||
| > | ||
| <motion.div | ||
| initial={{ opacity: 0, scale: 0.5 }} | ||
| animate={{ opacity: 1, scale: 1 }} | ||
| transition={{ delay: 0.2 }} | ||
| > | ||
| <Check size={48} strokeWidth={3} className={style === THEMES.NEOBRUTALISM ? 'text-black' : 'text-emerald-500'} /> | ||
| </motion.div> | ||
| </motion.div> | ||
|
|
||
| <motion.div | ||
| initial={{ opacity: 0, y: 20 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| transition={{ delay: 0.3 }} | ||
| className="text-center" | ||
| > | ||
| <h3 className={`text-3xl font-black mb-2 ${style === THEMES.NEOBRUTALISM ? 'text-black' : 'text-emerald-500'}`}> | ||
| All Settled Up! | ||
| </h3> | ||
| <p className="opacity-60 text-lg">No outstanding balances in this group.</p> | ||
| </motion.div> |
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.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
find . -name "package.json" -type f | head -5 | xargs grep -l "framer-motion" 2>/dev/nullRepository: Devasy/splitwiser
Length of output: 79
🏁 Script executed:
cat package.json | grep -A 2 -B 2 "framer-motion"Repository: Devasy/splitwiser
Length of output: 105
🏁 Script executed:
find . -name "Confetti*" -o -name "confetti*" | head -10Repository: Devasy/splitwiser
Length of output: 93
🌐 Web query:
framer-motion v11 useReducedMotion API prefers-reduced-motion
💡 Result:
In Framer Motion v11, reduced-motion support is built around the user’s OS/browser setting exposed via the CSS media query (prefers-reduced-motion: reduce). [3]
1) Manual: useReducedMotion()
useReducedMotion() is a hook that returns a boolean indicating whether Reduced Motion is enabled, and it will re-render when the setting changes. [2]
import { useReducedMotion, motion } from "framer-motion" // v11
export function Sidebar({ isOpen }: { isOpen: boolean }) {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={
shouldReduceMotion
? { opacity: isOpen ? 1 : 0 } // reduce transforms
: { x: isOpen ? 0 : "-100%" }
}
/>
)
}[1][2]
2) Automatic (site-wide): <MotionConfig reducedMotion="user" />
If you want Motion to automatically comply across your app, set:
import { MotionConfig } from "framer-motion" // v11
<MotionConfig reducedMotion="user">{children}</MotionConfig>With "user", Motion will disable transform and layout animations while preserving other animations like opacity. [1]
Sources: Motion accessibility guide + API docs [1][2], MDN prefers-reduced-motion [3].
[1] https://motion.dev/docs/react-accessibility
[2] https://motion.dev/motion/use-reduced-motion/
[3] https://developer.mozilla.org/en-US/docs/Web/CSS/%40media/prefers-reduced-motion
🏁 Script executed:
cat ./web/package.jsonRepository: Devasy/splitwiser
Length of output: 768
🏁 Script executed:
cat -n ./web/components/ui/Confetti.tsxRepository: Devasy/splitwiser
Length of output: 2251
Honor prefers-reduced-motion to improve accessibility for motion-sensitive users.
This component is animation-heavy with entrance animations and confetti effects. Please respect user reduced-motion settings by gating confetti and disabling animations when prefers-reduced-motion is active.
Example approach using `useReducedMotion()` from framer-motion v11
-import { motion } from 'framer-motion';
+import { motion, useReducedMotion } from 'framer-motion';
@@
export const SettledUpState = () => {
const { style } = useTheme();
+ const reduceMotion = useReducedMotion();
@@
- <Confetti count={100} />
+ {!reduceMotion && <Confetti count={100} />}
@@
<motion.div
- initial={{ scale: 0, rotate: -180 }}
- animate={{ scale: 1, rotate: 0 }}
- transition={{
- type: "spring",
- stiffness: 260,
- damping: 20,
- delay: 0.1
- }}
+ initial={reduceMotion ? false : { scale: 0, rotate: -180 }}
+ animate={{ scale: 1, rotate: 0 }}
+ transition={
+ reduceMotion
+ ? { duration: 0 }
+ : { type: "spring", stiffness: 260, damping: 20, delay: 0.1 }
+ }
@@
<motion.div
- initial={{ opacity: 0, scale: 0.5 }}
- animate={{ opacity: 1, scale: 1 }}
- transition={{ delay: 0.2 }}
+ initial={reduceMotion ? false : { opacity: 0, scale: 0.5 }}
+ animate={{ opacity: 1, scale: 1 }}
+ transition={reduceMotion ? { duration: 0 } : { delay: 0.2 }}
@@
<motion.div
- initial={{ opacity: 0, y: 20 }}
- animate={{ opacity: 1, y: 0 }}
- transition={{ delay: 0.3 }}
+ initial={reduceMotion ? false : { opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={reduceMotion ? { duration: 0 } : { delay: 0.3 }}Also consider adding reduced-motion support to the Confetti component itself so it respects user preferences when used elsewhere.
🤖 Prompt for AI Agents
In `@web/components/SettledUpState.tsx` around lines 1 - 49, The component ignores
user "prefers-reduced-motion" and must respect reduced-motion settings: use
framer-motion's useReducedMotion() inside SettledUpState to detect reduced
motion, pass a flag into Confetti (e.g., reducedMotion or disableAnimation) and
conditionally render Confetti (skip or set count=0) when reduced; for the
motion.div elements (the outer icon wrapper, inner check, and text container)
use the reducedMotion flag to replace animate with initial (or set animate equal
to initial/disable transitions) so no entrance animations run when reducedMotion
is true; update the Confetti component similarly to accept and honor the prop so
it avoids rendering or animating when reducedMotion is true.
| interface Particle { | ||
| id: number; | ||
| x: number; | ||
| y: number; | ||
| color: string; | ||
| size: number; | ||
| rotation: number; | ||
| duration: number; | ||
| delay: number; | ||
| } |
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.
Stabilize particle randomness to avoid jitter on re-renders.
Math.random() in render (x drift and borderRadius) changes on every re-render, so particles can jump or re‑animate unexpectedly. Store those values in Particle so they’re stable for the component’s lifetime.
💡 Proposed fix
interface Particle {
id: number;
x: number;
y: number;
color: string;
size: number;
rotation: number;
duration: number;
delay: number;
+ drift: number;
+ isCircle: boolean;
}
@@
const newParticles: Particle[] = Array.from({ length: count }).map((_, i) => ({
id: i,
x: Math.random() * 100, // percent
y: -10, // start above
color: COLORS[Math.floor(Math.random() * COLORS.length)],
size: Math.random() * 8 + 4,
rotation: Math.random() * 360,
duration: Math.random() * 2 + 2,
delay: Math.random() * 0.5,
+ drift: Math.random() * 20 - 10,
+ isCircle: Math.random() > 0.5,
}));
@@
animate={{
y: '105vh',
rotate: p.rotation + 720,
- x: `${p.x + (Math.random() * 20 - 10)}vw`,
+ x: `${p.x + p.drift}vw`,
opacity: 0,
}}
@@
style={{
position: 'absolute',
width: p.size,
height: p.size,
backgroundColor: p.color,
- borderRadius: Math.random() > 0.5 ? '50%' : '0%',
+ borderRadius: p.isCircle ? '50%' : '0%',
}}Also applies to: 24-60
🤖 Prompt for AI Agents
In `@web/components/ui/Confetti.tsx` around lines 9 - 18, The Particle interface
and Confetti rendering use Math.random() during render which causes particle
positions and border radii to change on re-renders; modify the Particle type to
include stable precomputed fields (e.g., initialX, initialY, borderRadius,
initialRotation, duration, delay) and generate these random values once when
creating particles (inside a createInitialParticles function, useEffect, or
useRef in the Confetti component) so the render uses particle.initialX /
particle.borderRadius etc. instead of calling Math.random() inline; update any
places referencing x, y, rotation or border radius to use the new stored
properties on Particle (and remove runtime Math.random calls from the JSX) to
prevent jitter.
Implemented a new
SettledUpStatecomponent with a confetti animation and Framer Motion entrance effects. Replaced the static "All Settled Up!" text inGroupDetails.tsx.Key changes:
web/components/ui/Confetti.tsx: Reusable confetti particle system.web/components/SettledUpState.tsx: Themed success state with animations.web/pages/GroupDetails.tsx: Integrated the new component.PR created automatically by Jules for task 4830318606363542076 started by @Devasy23
Summary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.