HomePilot / frontend /src /ui /phone /primitives /useReducedMotion.js
HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
921d377
Raw
History Blame Contribute Delete
1.72 kB
/**
* useReducedMotion — subscribe to the OS-level
* ``prefers-reduced-motion`` preference.
*
* Every animated component under ``phone/`` reads this hook so a
* single user preference disables animation app-wide. The hook also
* tracks runtime changes (macOS + iOS let users toggle the setting
* without reloading), so an animation doesn't silently persist when
* the user enables the preference mid-call.
*
* SSR-safe: returns ``false`` when ``window`` is undefined. Callers
* don't need to null-guard the DOM.
*/
import { useEffect, useState } from 'react';
const QUERY = '(prefers-reduced-motion: reduce)';
export function useReducedMotion() {
const [reduced, setReduced] = useState(() => {
if (typeof window === 'undefined')
return false;
try {
return window.matchMedia(QUERY).matches;
}
catch {
return false;
}
});
useEffect(() => {
if (typeof window === 'undefined')
return;
let mq;
try {
mq = window.matchMedia(QUERY);
}
catch {
return;
}
const onChange = (e) => setReduced(e.matches);
// Both APIs exist in the wild; prefer addEventListener on modern
// browsers, fall back to the deprecated addListener for older ones
// (Safari < 14). Same pattern at teardown.
if ('addEventListener' in mq) {
mq.addEventListener('change', onChange);
return () => mq.removeEventListener('change', onChange);
}
const legacy = mq;
legacy.addListener?.(onChange);
return () => legacy.removeListener?.(onChange);
}, []);
return reduced;
}