import { Button } from 'expo-dev-client-components'; import * as React from 'react'; import { Animated, LayoutRectangle, useWindowDimensions, StyleSheet } from 'react-native'; import { createAsyncStack, StackItem, Status, StackItemComponent, useStackItems, } from '../functions/createAsyncStack'; export type ToastOptions = { durationMs?: number; }; export type ToastStackItem = { element: StackItemComponent; toastProps?: ToastOptions; }; export type ToastProps = { animatedValue: Animated.Value; pop: () => void; status: Status; }; const defaultDistanceFromBottom = 100; type ToastStackContextProps = { push: (element: StackItemComponent, options?: ToastOptions) => StackItem; pop: (amount?: number) => StackItem[]; getItems: () => StackItem[]; }; const ToastStackContext = React.createContext(null); export const useToastStack = () => { const context = React.useContext(ToastStackContext); if (!context) { throw new Error(`useToastStack() was called outside of a provider`); } return context; }; export function ToastStackProvider({ children }) { const toastStack = React.useRef(createAsyncStack()).current; const toasts = useStackItems(toastStack); function push(element: StackItemComponent, options?: ToastOptions): StackItem { return toastStack.push({ element, toastProps: options }); } function pop(amount: number = 1) { return toastStack.pop(amount); } function getItems() { return toastStack.getState().items; } return ( {children} {toasts.map((toast) => ( ))} ); } function ToastItem(props: StackItem) { const { status, data, onPopEnd, onPushEnd, pop, animatedValue } = props; const { toastProps, element: Element } = data; const { height } = useWindowDimensions(); const [layout, setLayout] = React.useState(null); const timerRef = React.useRef(null); React.useEffect(() => { if (status === 'pushing') { Animated.spring(animatedValue, { toValue: 1, useNativeDriver: true, }).start(onPushEnd); } if (status === 'popping') { Animated.spring(animatedValue, { toValue: 2, useNativeDriver: true, }).start(() => { onPopEnd(); if (timerRef.current != null) { clearTimeout(timerRef.current); timerRef.current = null; } }); } if (status === 'settled') { timerRef.current = setTimeout(() => { pop(); timerRef.current = null; }, toastProps?.durationMs || 2000); } return () => { if (timerRef.current != null) { clearTimeout(timerRef.current); } }; }, [status, pop, toastProps?.durationMs]); let distanceFromBottom = defaultDistanceFromBottom; if (layout != null) { distanceFromBottom = distanceFromBottom + layout.height; } const translateY = animatedValue.interpolate({ inputRange: [0, 1, 2], outputRange: [height, height - distanceFromBottom, height - distanceFromBottom], }); const opacity = animatedValue.interpolate({ inputRange: [0, 1, 2], outputRange: [1, 1, 0], }); const isPopping = status === 'popping' || status === 'popped'; return ( setLayout(layout)} pointerEvents={isPopping ? 'none' : 'box-none'} style={[ { position: 'absolute', left: 0, right: 0, opacity, transform: [{ translateY }], }, ]}> ); }