import { OverlayContainer } from '@react-native-aria/overlays'; import { PresenceTransition } from '../Transitions'; import VStack from '../../primitives/Stack/VStack'; import { Alert } from '../../composites/Alert'; import React, { createContext, useState } from 'react'; import { AccessibilityInfo, Easing, Platform, SafeAreaView, } from 'react-native'; import IconButton from '../IconButton'; import Box from '../../primitives/Box'; import { useThemeProps } from '../../../hooks'; import { CloseIcon } from '../../primitives/Icon/Icons'; import type { IToastContext, IToastInfo, IToast, IToastProps } from './types'; let INSET = 50; const POSITIONS = { 'top': { top: INSET, left: 0, right: 0, }, 'top-right': { top: INSET, right: 0, }, 'top-left': { top: INSET, left: 0, }, 'bottom': { bottom: INSET, left: 0, right: 0, }, 'bottom-left': { bottom: INSET, left: 0, }, 'bottom-right': { bottom: INSET, right: 0, }, }; const initialAnimationOffset = 24; const transitionConfig: any = { 'bottom': initialAnimationOffset, 'top': -initialAnimationOffset, 'top-right': -initialAnimationOffset, 'top-left': -initialAnimationOffset, 'bottom-left': initialAnimationOffset, 'bottom-right': initialAnimationOffset, }; const ToastContext = createContext({ toastInfo: {}, setToastInfo: () => {}, setToast: () => {}, removeToast: () => {}, hideAll: () => {}, isActive: () => false, visibleToasts: {}, setVisibleToasts: () => {}, hideToast: () => {}, }); const CustomToast = () => { const { toastInfo, visibleToasts, removeToast } = React.useContext( ToastContext ); const getPositions = () => { return Object.keys(toastInfo); }; return getPositions().length > 0 ? ( {getPositions().map((position: string) => { if (Object.keys(POSITIONS).includes(position)) return ( { // @ts-ignore toastInfo[position].map((toast: IToast) => ( { if (status === 'exited') { removeToast(toast.id); toast.config?.onCloseComplete && toast.config?.onCloseComplete(); } }} initial={{ opacity: 0, translateY: transitionConfig[position], }} animate={{ opacity: 1, transition: { easing: Easing.ease, duration: 250 }, }} exit={{ opacity: 0, scale: 0.85, transition: { easing: Easing.ease, duration: 100 }, }} > {toast.component} )) } ); else return null; })} ) : null; }; export const ToastProvider = ({ children }: { children: any }) => { const [toastInfo, setToastInfo] = useState({}); const [visibleToasts, setVisibleToasts] = useState< { [key in string]: boolean } >({}); const themeProps = useThemeProps('Toast', {}); let toastIndex = React.useRef(1); const hideAll = () => { setVisibleToasts({}); }; const hideToast = (id: any) => { setVisibleToasts((prevVisibleToasts) => ({ ...prevVisibleToasts, [id]: false, })); }; const isActive = (id: any) => { for (let toastPosition of Object.keys(toastInfo)) { // @ts-ignore let positionArray: Array = toastInfo[toastPosition]; return positionArray.findIndex((toastData) => toastData.id === id) > -1; } return false; }; const removeToast = (id: any) => { setToastInfo((prev) => { for (let toastPosition of Object.keys(prev)) { // @ts-ignore let positionArray: Array = prev[toastPosition]; const isToastPresent = positionArray.findIndex((toastData) => toastData.id === id) > -1; if (isToastPresent) { let newPositionArray = positionArray.filter((item) => item.id !== id); let temp: any = {}; temp[toastPosition] = newPositionArray; let newToastInfo = { ...prev, ...temp }; return newToastInfo; } } return prev; }); }; const setToast = (props: IToastProps): number => { const { placement = 'bottom', title, render, status, id = toastIndex.current++, description, isClosable = true, duration = 5000, variant, accessibilityAnnouncement, accessibilityLiveRegion = 'polite', ...rest } = props; let positionToastArray = toastInfo[placement]; if (!positionToastArray) positionToastArray = []; let component = null; if (render) { component = render({ id: toastIndex.current }); } else if (!status && !variant) { component = ( {title} {description && ( {description} )} ); } else if (status || variant) { component = ( { hideToast(id); }} icon={} /> ) : undefined } {...rest} > {title} {description ? ( {description} ) : null} ); } toastInfo[placement] = [ ...positionToastArray, { component, id, config: props }, ]; setToastInfo({ ...toastInfo }); setVisibleToasts({ ...visibleToasts, [id]: true }); if (duration !== null) { setTimeout(function () { hideToast(id); }, duration); } // iOS doesn't support accessibilityLiveRegion if (accessibilityAnnouncement && Platform.OS === 'ios') { AccessibilityInfo.announceForAccessibility(accessibilityAnnouncement); } return id; }; return ( {children} ); }; export const useToast = () => { const { setToast, hideAll, isActive, hideToast } = React.useContext( ToastContext ); const toast = { show: setToast, close: hideToast, closeAll: hideAll, isActive, }; return toast; };