Add web animations
This commit is contained in:
@@ -88,6 +88,7 @@
|
||||
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
||||
"@haileyok/bluesky-video": "0.3.2",
|
||||
"@ipld/dag-cbor": "^9.2.0",
|
||||
"@legendapp/motion": "^2.4.0",
|
||||
"@lingui/react": "^4.14.1",
|
||||
"@mattermost/react-native-paste-input": "mattermost/react-native-paste-input",
|
||||
"@miblanchard/react-native-slider": "^2.6.0",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {AccessibilityInfo, Pressable, View} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {Motion} from '@legendapp/motion'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
@@ -23,6 +24,9 @@ import {
|
||||
export {useToast} from '#/components/Toast/Context'
|
||||
export {Outlet} from '#/components/Toast/Portal'
|
||||
|
||||
const ANIMATION_DURATION = 300
|
||||
const TOAST_GAP = 8 // Gap between toasts
|
||||
|
||||
const TOAST_ANIMATION_STYLES = {
|
||||
entering: {
|
||||
animation: 'toastFadeIn 0.3s ease-out forwards',
|
||||
@@ -48,22 +52,54 @@ export function ToastProvider({children}: {children: React.ReactNode}) {
|
||||
const [toasts, setToasts] = useState<
|
||||
{
|
||||
id: string
|
||||
toast: ToastProps
|
||||
timeout: NodeJS.Timeout
|
||||
props: ToastProps
|
||||
dimensions: {
|
||||
width: number | undefined
|
||||
height: number | undefined
|
||||
}
|
||||
setDimensions: (width: number, height: number) => void
|
||||
exiting: boolean
|
||||
timers: NodeJS.Timeout[]
|
||||
}[]
|
||||
>([])
|
||||
|
||||
const show = useCallback<ToastApi['show']>(props => {
|
||||
setToasts(prevToasts => {
|
||||
const id = nanoid()
|
||||
const duration = props.duration || DEFAULT_TOAST_DURATION
|
||||
return [
|
||||
...prevToasts,
|
||||
{
|
||||
id,
|
||||
toast: props,
|
||||
timeout: setTimeout(() => {
|
||||
setToasts(currentToasts => currentToasts.filter(t => t.id !== id))
|
||||
}, props.duration || DEFAULT_TOAST_DURATION),
|
||||
props,
|
||||
dimensions: {width: undefined, height: undefined},
|
||||
exiting: false,
|
||||
setDimensions: (width: number, height: number) => {
|
||||
setToasts(currentToasts =>
|
||||
currentToasts.map(t => {
|
||||
if (t.id === id) {
|
||||
t.dimensions.width = width
|
||||
t.dimensions.height = height
|
||||
}
|
||||
return t
|
||||
}),
|
||||
)
|
||||
},
|
||||
timers: [
|
||||
setTimeout(() => {
|
||||
setToasts(currentToasts =>
|
||||
currentToasts.map(t => {
|
||||
if (t.id === id) {
|
||||
t.exiting = true
|
||||
}
|
||||
return t
|
||||
}),
|
||||
)
|
||||
}, duration),
|
||||
setTimeout(() => {
|
||||
setToasts(currentToasts => currentToasts.filter(t => t.id !== id))
|
||||
}, duration + ANIMATION_DURATION),
|
||||
],
|
||||
},
|
||||
]
|
||||
})
|
||||
@@ -72,8 +108,8 @@ export function ToastProvider({children}: {children: React.ReactNode}) {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setToasts(toasts => {
|
||||
toasts.map(({timeout}) => {
|
||||
clearTimeout(timeout)
|
||||
toasts.map(({timers}) => {
|
||||
timers.map(clearTimeout)
|
||||
})
|
||||
return toasts
|
||||
})
|
||||
@@ -97,7 +133,6 @@ export function ToastProvider({children}: {children: React.ReactNode}) {
|
||||
<View
|
||||
style={[
|
||||
a.fixed,
|
||||
a.gap_sm,
|
||||
{
|
||||
flexDirection: 'column-reverse',
|
||||
left: a.px_xl.paddingLeft,
|
||||
@@ -110,9 +145,68 @@ export function ToastProvider({children}: {children: React.ReactNode}) {
|
||||
},
|
||||
],
|
||||
]}>
|
||||
{toasts.map(({id, toast}) => (
|
||||
<Toast key={id} content={toast.content} type={toast.type} />
|
||||
))}
|
||||
{toasts.map(toast => {
|
||||
const isReady = !!toast.dimensions.width
|
||||
const toastComponent = (
|
||||
<Toast
|
||||
content={toast.props.content}
|
||||
type={toast.props.type}
|
||||
/>
|
||||
)
|
||||
|
||||
if (!isReady) {
|
||||
return (
|
||||
<View
|
||||
key={toast.id}
|
||||
style={[a.absolute, a.inset_0, {opacity: 0}]}>
|
||||
<View
|
||||
style={[a.justify_end, {paddingTop: TOAST_GAP}]}
|
||||
onLayout={e => {
|
||||
if (toast.dimensions.width) return
|
||||
toast.setDimensions(
|
||||
e.nativeEvent.layout.width,
|
||||
e.nativeEvent.layout.height,
|
||||
)
|
||||
}}>
|
||||
{toastComponent}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Motion.View
|
||||
key={toast.id}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{
|
||||
y: 0,
|
||||
opacity: toast.exiting ? 0 : 1,
|
||||
height: toast.exiting ? 0 : toast.dimensions.height,
|
||||
}}
|
||||
transition={{
|
||||
type: 'timing',
|
||||
easing: 'circOut',
|
||||
duration: ANIMATION_DURATION,
|
||||
}}>
|
||||
<View
|
||||
key={toast.id}
|
||||
style={[
|
||||
a.justify_end,
|
||||
{
|
||||
position: toast.exiting ? 'absolute' : 'relative',
|
||||
width: toast.dimensions.width,
|
||||
height: toast.dimensions.height,
|
||||
paddingTop: TOAST_GAP,
|
||||
},
|
||||
]}>
|
||||
<Toast
|
||||
content={toast.props.content}
|
||||
type={toast.props.type}
|
||||
/>
|
||||
</View>
|
||||
</Motion.View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</Portal.Portal>
|
||||
)}
|
||||
|
||||
@@ -5013,6 +5013,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
|
||||
integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
|
||||
|
||||
"@legendapp/motion@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@legendapp/motion/-/motion-2.4.0.tgz#eef6f934b784e07fe94631d5648d412f34fa0c6b"
|
||||
integrity sha512-AAYpRLGvxGD5hIGl9sVHyoUufr66zoH82PuxYcKiPSMdCBI3jwZFWh6CuHjV1leRKVIRk2py1rSvIVabG8eqcw==
|
||||
dependencies:
|
||||
"@legendapp/tools" "2.0.1"
|
||||
|
||||
"@legendapp/tools@2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@legendapp/tools/-/tools-2.0.1.tgz#995fe6cb3e2398b939f645505aa8e1abc84bd07f"
|
||||
integrity sha512-Kxt0HWvWElRK6oybHRzcYxdgaKGwuaiRNreS7usW7QuHXRIHaH4yMcW2YNRG4DHE5fpefv+Bno/BohQcCE4FaA==
|
||||
|
||||
"@leichtgewicht/ip-codec@^2.0.1":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
||||
|
||||
Reference in New Issue
Block a user