UNPKG

@mui/material

Version:

Material UI is an open-source React component library that implements Google's Material Design. It's comprehensive and can be used in production out of the box.

110 lines (106 loc) 3.83 kB
'use client'; import * as React from 'react'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; const MEDIA_QUERY = '(prefers-reduced-motion: reduce)'; const REDUCED_MOTION_DURATION = 0; const REDUCED_MOTION_DELAY = '0ms'; const NOOP = () => {}; const getDefaultSnapshot = () => false; const getReducedMotionSnapshot = () => true; const subscribeNoop = () => NOOP; /** * Subscribes to the OS reduced-motion media query only when the theme mode needs it. * React 17 reads the media query after mount, matching useMediaQuery's fallback path. */ function useReducedMotionMediaQueryOld(enabled) { const [queryState, setQueryState] = React.useState(() => ({ enabled, matches: enabled ? null : false })); let matches = queryState.matches; if (queryState.enabled !== enabled) { matches = null; if (!enabled) { matches = false; } } useEnhancedEffect(() => { const setResolvedMatches = nextMatches => { setQueryState(previousState => { if (previousState.enabled === enabled && previousState.matches === nextMatches) { return previousState; } return { enabled, matches: nextMatches }; }); }; if (!enabled) { if (queryState.enabled) { setResolvedMatches(false); } return undefined; } if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { setResolvedMatches(false); return undefined; } const mediaQueryList = window.matchMedia(MEDIA_QUERY); const update = () => { setResolvedMatches(mediaQueryList.matches); }; update(); mediaQueryList.addEventListener('change', update); return () => { mediaQueryList.removeEventListener('change', update); }; }, [enabled, queryState.enabled]); return matches; } // See https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379 for why const safeReact = { ...React }; const maybeReactUseSyncExternalStore = safeReact.useSyncExternalStore; /** * React 18+ can read the media query during client renders, so newly mounted * transitions do not start from the SSR-safe reduced-motion default. */ function useReducedMotionMediaQueryNew(enabled) { const getServerSnapshot = enabled ? getReducedMotionSnapshot : getDefaultSnapshot; const [getSnapshot, subscribe] = React.useMemo(() => { if (!enabled || typeof window === 'undefined' || typeof window.matchMedia !== 'function') { return [getDefaultSnapshot, subscribeNoop]; } const mediaQueryList = window.matchMedia(MEDIA_QUERY); return [() => mediaQueryList.matches, notify => { mediaQueryList.addEventListener('change', notify); return () => { mediaQueryList.removeEventListener('change', notify); }; }]; }, [enabled]); return maybeReactUseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); } const useReducedMotionMediaQuery = maybeReactUseSyncExternalStore !== undefined ? useReducedMotionMediaQueryNew : useReducedMotionMediaQueryOld; /** * Resolves whether a Material UI transition should reduce motion and provides * adjusted CSS transition timing for MUI-owned duration/delay values. */ export default function useReducedMotion(mode, disablePrefersReducedMotion) { const prefersReducedMotion = useReducedMotionMediaQuery(!disablePrefersReducedMotion && mode === 'system'); const shouldReduceMotion = !disablePrefersReducedMotion && (mode === 'always' || mode === 'system' && prefersReducedMotion !== false); return React.useMemo(() => ({ shouldReduceMotion, getTransitionTiming(timing) { if (!shouldReduceMotion) { return timing; } return { duration: REDUCED_MOTION_DURATION, delay: REDUCED_MOTION_DELAY }; } }), [shouldReduceMotion]); }