diff --git a/src/components/toast-bar.tsx b/src/components/toast-bar.tsx index e38fd02..a626f59 100644 --- a/src/components/toast-bar.tsx +++ b/src/components/toast-bar.tsx @@ -3,6 +3,7 @@ import { styled, keyframes } from 'goober'; import { Toast, ToastPosition, resolveValue } from '../core/types'; import { ToastIcon } from './toast-icon'; +import { prefersReducedMotion } from '../core/utils'; const enterAnimation = (factor: number) => ` 0% {transform: translate3d(0,${factor * -200}%,0) scale(.6); opacity:.5;} @@ -38,7 +39,7 @@ const Message = styled('div')` interface ToastBarProps { toast: Toast; - position: ToastPosition; + position?: ToastPosition; style?: React.CSSProperties; } @@ -63,9 +64,13 @@ const getAnimationStyle = ( }; export const ToastBar: React.FC = React.memo( - ({ toast, position, style }) => { - const animationStyle = toast?.height - ? getAnimationStyle(position, toast.visible) + const animationStyle: React.CSSProperties = toast?.height + ? prefersReducedMotion() + ? {} + : getAnimationStyle( + toast.position || position || 'top-center', + toast.visible + ) : { opacity: 0 }; return ( diff --git a/src/components/toaster.tsx b/src/components/toaster.tsx index 9aa2e0a..493625d 100644 --- a/src/components/toaster.tsx +++ b/src/components/toaster.tsx @@ -9,7 +9,7 @@ import { Toast, resolveValue, } from '../core/types'; -import { createRectRef } from '../core/utils'; +import { createRectRef, prefersReducedMotion } from '../core/utils'; setup(React.createElement); @@ -36,7 +36,9 @@ const getPositionStyle = ( return { display: 'flex', position: 'absolute', - transition: 'all 230ms cubic-bezier(.21,1.02,.73,1)', + transition: `all ${ + prefersReducedMotion() ? 0 : 230 + }ms cubic-bezier(.21,1.02,.73,1)`, transform: `translateY(${offset * (top ? 1 : -1)}px)`, ...verticalStyle, ...horizontalStyle, diff --git a/src/core/store.ts b/src/core/store.ts index b488920..643f630 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { DefaultToastOptions, Toast, ToastType } from './types'; +import { prefersReducedMotion } from './utils'; const TOAST_LIMIT = 20; @@ -55,13 +56,16 @@ const addToRemoveQueue = (toastId: string) => { return; } - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: ActionType.REMOVE_TOAST, - toastId: toastId, - }); - }, 1000); + const timeout = setTimeout( + () => { + toastTimeouts.delete(toastId); + dispatch({ + type: ActionType.REMOVE_TOAST, + toastId: toastId, + }); + }, + prefersReducedMotion() ? 0 : 1000 + ); toastTimeouts.set(toastId, timeout); }; diff --git a/src/core/use-toaster.ts b/src/core/use-toaster.ts index 73cf841..ceb9d32 100644 --- a/src/core/use-toaster.ts +++ b/src/core/use-toaster.ts @@ -66,7 +66,7 @@ export const useToaster = (toastOptions?: DefaultToastOptions) => { const relevantToasts = toasts.filter( (t) => (t.position || defaultPosition) === - (toast.position || defaultPosition) + (toast.position || defaultPosition) && t.height ); const toastIndex = relevantToasts.findIndex((t) => t.id === toast.id); const toastsBefore = relevantToasts.filter( diff --git a/src/core/utils.ts b/src/core/utils.ts index 5393032..098159a 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -15,3 +15,16 @@ export const createRectRef = (onRect: (rect: DOMRect) => void) => ( }); } }; + +export const prefersReducedMotion = (() => { + // Cache result + let shouldReduceMotion: boolean | undefined = undefined; + + return () => { + if (shouldReduceMotion === undefined) { + const mediaQuery = matchMedia('(prefers-reduced-motion: reduce)'); + shouldReduceMotion = !mediaQuery || mediaQuery.matches; + } + return shouldReduceMotion; + }; +})();