38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import type { Variants } from 'framer-motion';
|
|
|
|
// EaseOutExpo — smooth editorial deceleration
|
|
export const easeOutExpo = [0.22, 1, 0.36, 1] as const;
|
|
|
|
export const fadeInUp: Variants = {
|
|
hidden: { opacity: 0, y: 20 },
|
|
visible: { opacity: 1, y: 0, transition: { duration: 0.5, ease: easeOutExpo } },
|
|
};
|
|
|
|
export const staggerContainer: Variants = {
|
|
hidden: {},
|
|
visible: { transition: { staggerChildren: 0.1 } },
|
|
};
|
|
|
|
export const viewportOnce = { once: true, amount: 0.3 } as const;
|
|
|
|
// Hook: returns true if user prefers reduced motion. Components use this to
|
|
// disable Framer Motion animations (per spec, a11y requirement).
|
|
export function usePrefersReducedMotion() {
|
|
const [reduced, setReduced] = useState(false);
|
|
useEffect(() => {
|
|
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
setReduced(mq.matches);
|
|
const handler = () => setReduced(mq.matches);
|
|
mq.addEventListener('change', handler);
|
|
return () => mq.removeEventListener('change', handler);
|
|
}, []);
|
|
return reduced;
|
|
}
|
|
|
|
// Pass to <motion.*> as motion-reduced guard. When reduced, variants resolve to visible immediately.
|
|
export const motionProps = (reduced: boolean) =>
|
|
reduced
|
|
? { initial: 'visible' as const, animate: 'visible' as const, whileInView: undefined, viewport: undefined }
|
|
: {};
|