Share reduced motion media query hook (#3287)

This commit is contained in:
Neil
2026-05-30 18:32:45 -07:00
committed by GitHub
parent f5382a187e
commit 2dbebd1f54
4 changed files with 33 additions and 54 deletions
+2 -8
View File
@@ -2,6 +2,7 @@
renderer surface. Keeping the state machine and its presentation variants together avoids
scattering tightly coupled update behavior across multiple files. */
import { useEffect, useRef, useState } from 'react'
import { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion'
import { useAppStore } from '../store'
import { Card } from './ui/card'
import { Button } from './ui/button'
@@ -197,14 +198,7 @@ export function UpdateCard() {
}, [status.state])
// ── Prefers-reduced-motion ──────────────────────────────────────────
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
setPrefersReducedMotion(mq.matches)
const handler = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches)
mq.addEventListener('change', handler)
return () => mq.removeEventListener('change', handler)
}, [])
const prefersReducedMotion = usePrefersReducedMotion()
useEffect(() => {
return () => {
@@ -1,7 +1,5 @@
import { useEffect, useState } from 'react'
import type { FeatureWallOpenSourceTelemetry } from '../../../../shared/telemetry-events'
const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)'
export { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion'
export function getFeatureWallOpenSource(
modalData: Record<string, unknown>
@@ -11,27 +9,3 @@ export function getFeatureWallOpenSource(
? source
: 'unknown'
}
export function usePrefersReducedMotion(): boolean {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => {
if (typeof window === 'undefined' || !window.matchMedia) {
return false
}
return window.matchMedia(REDUCED_MOTION_QUERY).matches
})
useEffect(() => {
if (typeof window === 'undefined' || !window.matchMedia) {
return
}
const media = window.matchMedia(REDUCED_MOTION_QUERY)
setPrefersReducedMotion(media.matches)
const onChange = (event: MediaQueryListEvent): void => {
setPrefersReducedMotion(event.matches)
}
media.addEventListener('change', onChange)
return () => media.removeEventListener('change', onChange)
}, [])
return prefersReducedMotion
}
+1 -19
View File
@@ -1,4 +1,5 @@
import { useEffect, useId, useRef, useState } from 'react'
import { usePrefersReducedMotion } from '@/hooks/usePrefersReducedMotion'
import { usePetUrl } from './usePetUrl'
import type { DetectedSpriteCacheEntry } from './pet-blob-cache'
import type { CustomPet } from '../../../../shared/types'
@@ -176,25 +177,6 @@ function useDocumentVisible(): boolean {
return visible
}
function usePrefersReducedMotion(): boolean {
const [reduced, setReduced] = useState(() => {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
return false
}
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
})
useEffect(() => {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
return
}
const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
const onChange = (event: MediaQueryListEvent): void => setReduced(event.matches)
mq.addEventListener('change', onChange)
return () => mq.removeEventListener('change', onChange)
}, [])
return reduced
}
// Why: keep a default for the cached helpers below; the live size now comes
// from the store so the user can resize from the status-bar menu.
const SIZE = 180
@@ -0,0 +1,29 @@
import { useEffect, useState } from 'react'
const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)'
function readPrefersReducedMotion(): boolean {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
return false
}
return window.matchMedia(REDUCED_MOTION_QUERY).matches
}
export function usePrefersReducedMotion(): boolean {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(readPrefersReducedMotion)
useEffect(() => {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
return
}
const media = window.matchMedia(REDUCED_MOTION_QUERY)
setPrefersReducedMotion(media.matches)
const onChange = (event: MediaQueryListEvent): void => {
setPrefersReducedMotion(event.matches)
}
media.addEventListener('change', onChange)
return () => media.removeEventListener('change', onChange)
}, [])
return prefersReducedMotion
}