From 5d3e2e14679b3d8eafdf9a563727ec46a7a370ea Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 12 Feb 2025 21:18:26 +0000 Subject: Better animations for dialogs, animate web composer (#7703) * animation atoms, use for modals * respect reduced motion * simplify animtions * fix atoms --- src/alf/atoms.ts | 20 ++++++++++++++++++++ src/components/Dialog/index.web.tsx | 24 ++++++++++-------------- src/style.css | 9 +++++++++ src/view/shell/Composer.web.tsx | 33 +++++++++++++++++++-------------- 4 files changed, 58 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index a7cf6cb3f..6982de75f 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -965,6 +965,26 @@ export const atoms = { transitionDelay: '50ms', }), + /* + * Animaations + */ + fade_in: web({ + animation: 'fadeIn ease-out 0.15s', + }), + fade_out: web({ + animation: 'fadeOut ease-out 0.15s', + }), + zoom_in: web({ + animation: 'zoomIn ease-out 0.1s', + }), + zoom_out: web({ + animation: 'zoomOut ease-out 0.1s', + }), + // special composite animation for dialogs + zoom_fade_in: web({ + animation: 'zoomIn ease-out 0.1s, fadeIn ease-out 0.1s', + }), + /** * {@link Layout.SCROLLBAR_OFFSET} */ diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index e45133dc5..9f4f8bb3f 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -15,6 +15,7 @@ import {FocusScope} from '@radix-ui/react-focus-scope' import {RemoveScrollBar} from 'react-remove-scroll-bar' import {logger} from '#/logger' +import {useA11y} from '#/state/a11y' import {useDialogStateControlContext} from '#/state/dialogs' import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' @@ -152,6 +153,7 @@ export function Inner({ const t = useTheme() const {close} = React.useContext(Context) const {gtMobile} = useBreakpoints() + const {reduceMotionEnabled} = useA11y() useFocusGuards() return ( @@ -161,7 +163,7 @@ export function Inner({ aria-label={label} aria-labelledby={accessibilityLabelledBy} aria-describedby={accessibilityDescribedBy} - // @ts-ignore web only -prf + // @ts-expect-error web only -prf onClick={stopPropagation} onStartShouldSetResponder={_ => true} onTouchEnd={stopPropagation} @@ -177,10 +179,9 @@ export function Inner({ shadowColor: t.palette.black, shadowOpacity: t.name === 'light' ? 0.1 : 0.4, shadowRadius: 30, - // @ts-ignore web only - animation: 'fadeIn ease-out 0.1s', }, - flatten(style), + !reduceMotionEnabled && a.zoom_fade_in, + style, ])}> + diff --git a/src/style.css b/src/style.css index 96c51014f..770cb5e00 100644 --- a/src/style.css +++ b/src/style.css @@ -205,6 +205,15 @@ input:focus { } } +@keyframes zoomIn { + from { + transform: scale(0.95); + } + to { + transform: scale(1); + } +} + .force-no-clicks > *, .force-no-clicks * { pointer-events: none !important; diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index 80a112705..b76e88372 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -5,6 +5,7 @@ import {useFocusGuards} from '@radix-ui/react-focus-guards' import {FocusScope} from '@radix-ui/react-focus-scope' import {RemoveScrollBar} from 'react-remove-scroll-bar' +import {useA11y} from '#/state/a11y' import {useModals} from '#/state/modals' import {ComposerOpts, useComposerState} from '#/state/shell/composer' import { @@ -12,7 +13,7 @@ import { EmojiPickerPosition, EmojiPickerState, } from '#/view/com/composer/text-input/web/EmojiPicker.web' -import {useBreakpoints, useTheme} from '#/alf' +import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf' import {ComposePost, useComposerCancelRef} from '../com/composer/Composer' const BOTTOM_BAR_HEIGHT = 61 @@ -41,6 +42,7 @@ function Inner({state}: {state: ComposerOpts}) { const {isModalActive} = useModals() const t = useTheme() const {gtMobile} = useBreakpoints() + const {reduceMotionEnabled} = useA11y() const [pickerState, setPickerState] = React.useState({ isOpen: false, pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null}, @@ -71,17 +73,15 @@ function Inner({state}: {state: ComposerOpts}) { evt.preventDefault()} onInteractOutside={evt => evt.preventDefault()} onDismiss={() => { @@ -96,6 +96,11 @@ function Inner({state}: {state: ComposerOpts}) { !gtMobile && styles.containerMobile, t.atoms.bg, t.atoms.border_contrast_medium, + !reduceMotionEnabled && [ + a.zoom_fade_in, + {animationDelay: 0.1}, + {animationFillMode: 'backwards'}, + ], ]}>