Better animations for dialogs, animate web composer (#7703)
* animation atoms, use for modals * respect reduced motion * simplify animtions * fix atoms
This commit is contained in:
@@ -965,6 +965,26 @@ export const atoms = {
|
|||||||
transitionDelay: '50ms',
|
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}
|
* {@link Layout.SCROLLBAR_OFFSET}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {FocusScope} from '@radix-ui/react-focus-scope'
|
|||||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {useA11y} from '#/state/a11y'
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
@@ -152,6 +153,7 @@ export function Inner({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {close} = React.useContext(Context)
|
const {close} = React.useContext(Context)
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const {reduceMotionEnabled} = useA11y()
|
||||||
useFocusGuards()
|
useFocusGuards()
|
||||||
return (
|
return (
|
||||||
<FocusScope loop asChild trapped>
|
<FocusScope loop asChild trapped>
|
||||||
@@ -161,7 +163,7 @@ export function Inner({
|
|||||||
aria-label={label}
|
aria-label={label}
|
||||||
aria-labelledby={accessibilityLabelledBy}
|
aria-labelledby={accessibilityLabelledBy}
|
||||||
aria-describedby={accessibilityDescribedBy}
|
aria-describedby={accessibilityDescribedBy}
|
||||||
// @ts-ignore web only -prf
|
// @ts-expect-error web only -prf
|
||||||
onClick={stopPropagation}
|
onClick={stopPropagation}
|
||||||
onStartShouldSetResponder={_ => true}
|
onStartShouldSetResponder={_ => true}
|
||||||
onTouchEnd={stopPropagation}
|
onTouchEnd={stopPropagation}
|
||||||
@@ -177,10 +179,9 @@ export function Inner({
|
|||||||
shadowColor: t.palette.black,
|
shadowColor: t.palette.black,
|
||||||
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
||||||
shadowRadius: 30,
|
shadowRadius: 30,
|
||||||
// @ts-ignore web only
|
|
||||||
animation: 'fadeIn ease-out 0.1s',
|
|
||||||
},
|
},
|
||||||
flatten(style),
|
!reduceMotionEnabled && a.zoom_fade_in,
|
||||||
|
style,
|
||||||
])}>
|
])}>
|
||||||
<DismissableLayer
|
<DismissableLayer
|
||||||
onInteractOutside={preventDefault}
|
onInteractOutside={preventDefault}
|
||||||
@@ -216,7 +217,7 @@ export const InnerFlatList = React.forwardRef<
|
|||||||
style={[
|
style={[
|
||||||
a.overflow_hidden,
|
a.overflow_hidden,
|
||||||
a.px_0,
|
a.px_0,
|
||||||
// @ts-ignore web only -sfn
|
// @ts-expect-error web only -sfn
|
||||||
{maxHeight: 'calc(-36px + 100vh)'},
|
{maxHeight: 'calc(-36px + 100vh)'},
|
||||||
webInnerStyle,
|
webInnerStyle,
|
||||||
]}
|
]}
|
||||||
@@ -262,20 +263,15 @@ export function Handle() {
|
|||||||
|
|
||||||
function Backdrop() {
|
function Backdrop() {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const {reduceMotionEnabled} = useA11y()
|
||||||
return (
|
return (
|
||||||
<View
|
<View style={{opacity: 0.8}}>
|
||||||
style={{
|
|
||||||
opacity: 0.8,
|
|
||||||
}}>
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.fixed,
|
a.fixed,
|
||||||
a.inset_0,
|
a.inset_0,
|
||||||
{
|
{backgroundColor: t.palette.black},
|
||||||
backgroundColor: t.palette.black,
|
!reduceMotionEnabled && a.fade_in,
|
||||||
// @ts-ignore web only
|
|
||||||
animation: 'fadeIn ease-out 0.15s',
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -205,6 +205,15 @@ input:focus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes zoomIn {
|
||||||
|
from {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.force-no-clicks > *,
|
.force-no-clicks > *,
|
||||||
.force-no-clicks * {
|
.force-no-clicks * {
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
|||||||
import {FocusScope} from '@radix-ui/react-focus-scope'
|
import {FocusScope} from '@radix-ui/react-focus-scope'
|
||||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
|
import {useA11y} from '#/state/a11y'
|
||||||
import {useModals} from '#/state/modals'
|
import {useModals} from '#/state/modals'
|
||||||
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
|
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +13,7 @@ import {
|
|||||||
EmojiPickerPosition,
|
EmojiPickerPosition,
|
||||||
EmojiPickerState,
|
EmojiPickerState,
|
||||||
} from '#/view/com/composer/text-input/web/EmojiPicker.web'
|
} 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'
|
import {ComposePost, useComposerCancelRef} from '../com/composer/Composer'
|
||||||
|
|
||||||
const BOTTOM_BAR_HEIGHT = 61
|
const BOTTOM_BAR_HEIGHT = 61
|
||||||
@@ -41,6 +42,7 @@ function Inner({state}: {state: ComposerOpts}) {
|
|||||||
const {isModalActive} = useModals()
|
const {isModalActive} = useModals()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const {reduceMotionEnabled} = useA11y()
|
||||||
const [pickerState, setPickerState] = React.useState<EmojiPickerState>({
|
const [pickerState, setPickerState] = React.useState<EmojiPickerState>({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null},
|
pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null},
|
||||||
@@ -71,17 +73,15 @@ function Inner({state}: {state: ComposerOpts}) {
|
|||||||
<DismissableLayer
|
<DismissableLayer
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal
|
aria-modal
|
||||||
style={{
|
style={flatten([
|
||||||
position: 'fixed',
|
{position: 'fixed'},
|
||||||
top: 0,
|
a.inset_0,
|
||||||
left: 0,
|
{backgroundColor: '#000c'},
|
||||||
width: '100%',
|
a.flex,
|
||||||
height: '100%',
|
a.flex_col,
|
||||||
backgroundColor: '#000c',
|
a.align_center,
|
||||||
display: 'flex',
|
!reduceMotionEnabled && a.fade_in,
|
||||||
flexDirection: 'column',
|
])}
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
onFocusOutside={evt => evt.preventDefault()}
|
onFocusOutside={evt => evt.preventDefault()}
|
||||||
onInteractOutside={evt => evt.preventDefault()}
|
onInteractOutside={evt => evt.preventDefault()}
|
||||||
onDismiss={() => {
|
onDismiss={() => {
|
||||||
@@ -96,6 +96,11 @@ function Inner({state}: {state: ComposerOpts}) {
|
|||||||
!gtMobile && styles.containerMobile,
|
!gtMobile && styles.containerMobile,
|
||||||
t.atoms.bg,
|
t.atoms.bg,
|
||||||
t.atoms.border_contrast_medium,
|
t.atoms.border_contrast_medium,
|
||||||
|
!reduceMotionEnabled && [
|
||||||
|
a.zoom_fade_in,
|
||||||
|
{animationDelay: 0.1},
|
||||||
|
{animationFillMode: 'backwards'},
|
||||||
|
],
|
||||||
]}>
|
]}>
|
||||||
<ComposePost
|
<ComposePost
|
||||||
cancelRef={ref}
|
cancelRef={ref}
|
||||||
@@ -123,14 +128,14 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
// @ts-ignore web only
|
// @ts-expect-error web only
|
||||||
maxHeight: 'calc(100% - (40px * 2))',
|
maxHeight: 'calc(100% - (40px * 2))',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
containerMobile: {
|
containerMobile: {
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
marginBottom: BOTTOM_BAR_HEIGHT,
|
marginBottom: BOTTOM_BAR_HEIGHT,
|
||||||
// @ts-ignore web only
|
// @ts-expect-error web only
|
||||||
maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`,
|
maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user