BillTracker/client/contexts/MotionPreferenceContext.tsx

54 lines
1.5 KiB
TypeScript
Raw Normal View History

import { createContext, useContext, useState, type ReactNode } from 'react';
import { MotionConfig } from 'framer-motion';
/**
* "Reduce motion" device preference, persisted to localStorage under
* 'bt-reduce-motion'. When ON, framer-motion animations are forced off
* (`reducedMotion="always"`); when OFF we pass "user" so the OS
* `prefers-reduced-motion` setting is still honored.
*/
const STORAGE_KEY = 'bt-reduce-motion';
function loadPref(): boolean {
try {
return localStorage.getItem(STORAGE_KEY) === 'true';
} catch {
return false;
}
}
interface MotionPreferenceValue {
reduceMotion: boolean;
setReduceMotion: (v: boolean) => void;
}
const MotionPreferenceContext = createContext<MotionPreferenceValue | null>(null);
export function MotionPreferenceProvider({ children }: { children: ReactNode }) {
const [reduceMotion, setReduceMotionState] = useState<boolean>(loadPref);
const setReduceMotion = (v: boolean) => {
setReduceMotionState(v);
try {
localStorage.setItem(STORAGE_KEY, String(v));
} catch {
// localStorage unavailable
}
};
return (
<MotionPreferenceContext.Provider value={{ reduceMotion, setReduceMotion }}>
<MotionConfig reducedMotion={reduceMotion ? 'always' : 'user'}>{children}</MotionConfig>
</MotionPreferenceContext.Provider>
);
}
export function useMotionPreference() {
const ctx = useContext(MotionPreferenceContext);
if (!ctx) {
throw new Error('useMotionPreference must be used within a MotionPreferenceProvider');
}
return ctx;
}