Animate quantity steppers and make the nav bar sticky
- Add an AnimatedNumber component that slides a quantity stepper's count horizontally — left on increment, right on decrement — and wire it into both the menu and basket steppers. - Make the top nav bar position: sticky, with an explicit background so scrolled content doesn't show through it. - Reserve the scrollbar's gutter on every page (scrollable or not) so the sticky header doesn't shift width when navigating between short and tall pages. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'
|
||||
import { useState } from 'react'
|
||||
|
||||
import styles from './AnimatedNumber.module.scss'
|
||||
|
||||
type AnimatedNumberProps = {
|
||||
value: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
const variants = {
|
||||
initial: (direction: number) => ({ x: direction * 12, opacity: 0 }),
|
||||
animate: { x: 0, opacity: 1 },
|
||||
exit: (direction: number) => ({ x: direction * -12, opacity: 0 }),
|
||||
}
|
||||
|
||||
// A quantity stepper's count slides leftward when incrementing (the new
|
||||
// digit enters from the right, the old one exits to the left) and
|
||||
// rightward when decrementing.
|
||||
//
|
||||
// `direction` is derived from a prop change during render (the React-docs
|
||||
// "adjusting state when a prop changes" pattern) rather than mutating a ref
|
||||
// in the render body — mutating a ref here would already reflect the new
|
||||
// value by the time `exit`'s "custom" is resolved for the outgoing digit,
|
||||
// so every transition — increment or decrement — read as "forward".
|
||||
export const AnimatedNumber = ({ value, className }: AnimatedNumberProps) => {
|
||||
const reduceMotion = useReducedMotion()
|
||||
const [previousValue, setPreviousValue] = useState(value)
|
||||
const [direction, setDirection] = useState(1)
|
||||
|
||||
if (value !== previousValue) {
|
||||
setDirection(value > previousValue ? 1 : -1)
|
||||
setPreviousValue(value)
|
||||
}
|
||||
|
||||
if (reduceMotion) {
|
||||
return <span className={className}>{value}</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`${styles.wrapper} ${className ?? ''}`}>
|
||||
<AnimatePresence mode="popLayout" initial={false} custom={direction}>
|
||||
<motion.span
|
||||
key={value}
|
||||
custom={direction}
|
||||
variants={variants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
transition={{ duration: 0.15, ease: 'easeOut' }}
|
||||
className={styles.digit}
|
||||
>
|
||||
{value}
|
||||
</motion.span>
|
||||
</AnimatePresence>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user