Native
This commit is contained in:
+101
-14
@@ -18,6 +18,7 @@ import Animated, {
|
|||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import RootSiblings from 'react-native-root-siblings'
|
import RootSiblings from 'react-native-root-siblings'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
import {Motion} from '@legendapp/motion'
|
||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
@@ -36,6 +37,8 @@ export {useToast} from '#/components/Toast/Context'
|
|||||||
export {Outlet} from '#/components/Toast/Portal'
|
export {Outlet} from '#/components/Toast/Portal'
|
||||||
|
|
||||||
const TOAST_ANIMATION_DURATION = 300
|
const TOAST_ANIMATION_DURATION = 300
|
||||||
|
const ANIMATION_DURATION = 300
|
||||||
|
const TOAST_GAP = 8 // Gap between toasts
|
||||||
|
|
||||||
export function ToastProvider({
|
export function ToastProvider({
|
||||||
children,
|
children,
|
||||||
@@ -48,24 +51,54 @@ export function ToastProvider({
|
|||||||
const [toasts, setToasts] = useState<
|
const [toasts, setToasts] = useState<
|
||||||
{
|
{
|
||||||
id: string
|
id: string
|
||||||
toast: ToastProps
|
props: ToastProps
|
||||||
timeout: NodeJS.Timeout
|
dimensions: {
|
||||||
|
width: number | undefined
|
||||||
|
height: number | undefined
|
||||||
|
}
|
||||||
|
setDimensions: (width: number, height: number) => void
|
||||||
|
exiting: boolean
|
||||||
|
timers: NodeJS.Timeout[]
|
||||||
}[]
|
}[]
|
||||||
>([])
|
>([])
|
||||||
|
|
||||||
const show = useCallback<ToastApi['show']>(props => {
|
const show = useCallback<ToastApi['show']>(props => {
|
||||||
AccessibilityInfo.announceForAccessibility(props.a11yLabel)
|
|
||||||
|
|
||||||
setToasts(prevToasts => {
|
setToasts(prevToasts => {
|
||||||
const id = nanoid()
|
const id = nanoid()
|
||||||
|
const duration = props.duration || DEFAULT_TOAST_DURATION
|
||||||
return [
|
return [
|
||||||
...prevToasts,
|
...prevToasts,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
toast: props,
|
props,
|
||||||
timeout: setTimeout(() => {
|
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))
|
setToasts(currentToasts => currentToasts.filter(t => t.id !== id))
|
||||||
}, props.duration || DEFAULT_TOAST_DURATION),
|
}, duration + ANIMATION_DURATION),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -74,8 +107,8 @@ export function ToastProvider({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setToasts(toasts => {
|
setToasts(toasts => {
|
||||||
toasts.map(({timeout}) => {
|
toasts.map(({timers}) => {
|
||||||
clearTimeout(timeout)
|
timers.map(clearTimeout)
|
||||||
})
|
})
|
||||||
return toasts
|
return toasts
|
||||||
})
|
})
|
||||||
@@ -99,17 +132,71 @@ export function ToastProvider({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
a.gap_sm,
|
|
||||||
{
|
{
|
||||||
flexDirection: 'column-reverse',
|
|
||||||
top: offsetTop ?? top,
|
top: offsetTop ?? top,
|
||||||
left: a.px_lg.paddingLeft,
|
left: a.px_lg.paddingLeft,
|
||||||
right: a.px_lg.paddingLeft,
|
right: a.px_lg.paddingLeft,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
{toasts.map(({id, toast}) => (
|
{toasts.map(toast => {
|
||||||
<Toast key={id} type={toast.type} content={toast.content} />
|
const isReady = !!toast.dimensions.width
|
||||||
))}
|
const toastComponent = (
|
||||||
|
<Toast
|
||||||
|
content={toast.props.content}
|
||||||
|
type={toast.props.type}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isReady) {
|
||||||
|
return (
|
||||||
|
<View key={toast.id} style={[{opacity: 0}]}>
|
||||||
|
<View
|
||||||
|
style={[{paddingBottom: 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={[
|
||||||
|
{
|
||||||
|
position: toast.exiting ? 'absolute' : 'relative',
|
||||||
|
width: toast.dimensions.width,
|
||||||
|
height: toast.dimensions.height,
|
||||||
|
paddingBottom: TOAST_GAP,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Toast
|
||||||
|
content={toast.props.content}
|
||||||
|
type={toast.props.type}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Motion.View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
</Portal.Portal>
|
</Portal.Portal>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Reference in New Issue
Block a user