Last active
October 10, 2022 03:43
-
-
Save erenkulaksiz/1f29492bc960072a37d811c17696078a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
createContext, | |
PropsWithChildren, | |
ReactNode, | |
useContext, | |
useEffect, | |
useState, | |
} from "react"; | |
import { AnimateSharedLayout, motion } from "framer-motion"; | |
import { Toast as ToastComponent, AlertModal } from "@components"; | |
import { CreatePortal as Portal } from "@components"; | |
import { isClient } from "@utils/isClient"; | |
import { Log } from "@utils"; | |
import { WorkboxInit } from "@utils/workboxInit"; | |
import type { ToastProps } from "components/Toast/Toast.d"; | |
import type { AlertProps } from "components/Alert/Alert.d"; | |
function HOC({ children }: { children: ReactNode }) { | |
const NotalUI = useNotalUI(); | |
useEffect(() => WorkboxInit(NotalUI), []); | |
return <>{children}</>; | |
} | |
export interface NotalUIContextProps { | |
Toast: { | |
show: (arg: ToastProps) => void; | |
showMultiple?: ([]: ToastProps[]) => void; | |
close: (index: number) => void; | |
closeAll?: () => void; | |
render?: (index: number) => void; | |
onClick?: ({ toast, index }: { toast: ToastProps; index: number }) => void; | |
}; | |
Alert: { | |
show: (arg: AlertProps) => void; | |
close: () => void; | |
onClose?: () => void; | |
}; | |
AlertState: AlertProps; | |
} | |
const notalUIContext = createContext<NotalUIContextProps>( | |
{} as NotalUIContextProps | |
); | |
export default function useNotalUI() { | |
return useContext(notalUIContext); | |
} | |
export function NotalUIProvider(props: PropsWithChildren) { | |
const [alert, setAlert] = useState<AlertProps>({ | |
visible: false, | |
}); | |
const [showToastPortal, setShowToastPortal] = useState<boolean>(false); | |
const [toastBuffer, setToastBuffer] = useState<ToastProps[]>([]); | |
useEffect(() => { | |
if (toastBuffer.length > 0) { | |
setShowToastPortal(true); | |
} else { | |
// remove toast portal after delay | |
setTimeout(() => setShowToastPortal(false), 500); | |
} | |
let interval: NodeJS.Timer | null = null; | |
if (toastBuffer.filter((el) => el.timeEnabled).length > 0) { | |
const filterToasts = toastBuffer.filter((el) => el.timeEnabled); | |
if (filterToasts.length == 0) return; | |
const lastToast = filterToasts[filterToasts.length - 1]; | |
const toastTimeEnabled = toastBuffer.findIndex( | |
(el) => el.id == lastToast.id | |
); | |
if (interval) return; | |
if (toastTimeEnabled == -1) return; | |
interval = setInterval( | |
() => Toast?.close && Toast?.close(toastTimeEnabled), | |
toastBuffer[toastTimeEnabled].duration || 1000 | |
); | |
} else { | |
if (interval) clearInterval(interval); | |
} | |
return () => { | |
if (interval) clearInterval(interval); | |
}; | |
}, [toastBuffer]); | |
/** | |
* @description - toast control | |
* @param {ToastProps} toast - toast props | |
* @returns {id} | |
*/ | |
const Toast: NotalUIContextProps["Toast"] = { | |
/** | |
* @description - show toast | |
* @param {ToastProps} - toast props | |
* @returns {id} | |
*/ | |
show: ({ | |
title, | |
desc, | |
icon, | |
closeable = true, | |
timeEnabled = true, | |
duration = 3500, | |
className = "dark:bg-neutral-800 bg-neutral-100 border-2 border-solid border-neutral-300 dark:border-neutral-800", | |
buttons, | |
showClose = true, | |
type = "default", | |
id, | |
once = false, | |
}: ToastProps) => { | |
if (once) { | |
const findToast = toastBuffer.findIndex((el) => el.id == id); | |
if (findToast != -1) return; | |
} | |
let btns; | |
/** | |
* Sending index to buttons function to know which index this toast is | |
*/ | |
if (typeof buttons == "function") { | |
btns = buttons(toastBuffer.length + 1); // run the function | |
} else if (Array.isArray(buttons)) { | |
btns = buttons; | |
} | |
const toastId = id || Date.now(); | |
setToastBuffer([ | |
...toastBuffer, | |
{ | |
title, | |
desc, | |
icon, | |
closeable, | |
timeEnabled, | |
className, | |
buttons: btns, | |
duration, | |
id: toastId, | |
showClose, | |
type, | |
}, | |
]); | |
return { id: toastId }; | |
}, | |
showMultiple: (multipleToasts: ToastProps[]) => { | |
const allToasts = [...toastBuffer]; | |
multipleToasts.map((element, index) => { | |
let btns; | |
if (typeof element.buttons == "function") { | |
btns = element.buttons(toastBuffer.length + 1); | |
} else if (Array.isArray(element.buttons)) { | |
btns = element.buttons; | |
} | |
allToasts.push({ | |
title: element?.title, | |
desc: element?.desc, | |
icon: element?.icon, | |
closeable: element?.closeable, | |
timeEnabled: element?.timeEnabled, | |
className: element?.className, | |
buttons: btns, | |
duration: element?.duration, | |
id: Date.now() + index, | |
showClose: element?.showClose, | |
type: element?.type, | |
}); | |
}); | |
setToastBuffer([...allToasts]); | |
}, | |
close: (index: number) => { | |
const newToastArr = [...toastBuffer]; | |
newToastArr.splice(index, 1); | |
setToastBuffer(newToastArr); | |
}, | |
closeAll: () => { | |
setToastBuffer([]); | |
}, | |
render: (index: number) => { | |
const toasts = [...toastBuffer]; | |
toasts[index]["rendered"] = true; | |
setToastBuffer([...toasts]); | |
}, | |
}; | |
const Alert: NotalUIContextProps["Alert"] = { | |
show: ({ | |
title, | |
desc, | |
showCloseButton = true, | |
closeable = true, | |
titleIcon = false, | |
className, | |
blur = false, | |
buttons = false, | |
animate = true, | |
customContent = false, | |
}: AlertProps) => { | |
setAlert({ | |
visible: true, | |
title, | |
desc, | |
closeable, | |
titleIcon, | |
blur, | |
buttons, | |
className, | |
animate, | |
showCloseButton: buttons == false ? showCloseButton : false, | |
customContent, | |
}); | |
}, | |
close: () => { | |
setAlert({ ...alert, visible: false }); | |
}, | |
}; | |
const value = { Toast, Alert, AlertState: alert }; | |
return ( | |
<notalUIContext.Provider value={value} {...props}> | |
{props.children} | |
<HOC> | |
{isClient() && showToastPortal && ( | |
<Portal portalName="notal-toast"> | |
{/* @ts-ignore */} | |
<AnimateSharedLayout> | |
<motion.div | |
layout | |
variants={{ | |
show: { opacity: 1, y: 0 }, | |
hidden: { opacity: 0, y: 70 }, | |
}} | |
initial="hidden" | |
animate="show" | |
exit="hidden" | |
className="absolute pointer-events-none left-0 right-0 top-0 bottom-0 z-50 flex flex-col justify-end items-end" | |
> | |
{toastBuffer.map((toast, index) => ( | |
<ToastComponent | |
toast={toast} | |
key={toast.id} | |
onClick={() => { | |
if (!toast?.closeable) return; | |
if (typeof Toast?.onClick == "function") | |
Toast?.onClick({ toast, index }); | |
else Toast?.close(index); | |
}} | |
onRender={() => Toast.render && Toast?.render(index)} | |
/> | |
))} | |
</motion.div> | |
</AnimateSharedLayout> | |
</Portal> | |
)} | |
<AlertModal | |
alert={alert} | |
onClose={() => { | |
if (!alert.closeable) return; | |
if (typeof Alert?.onClose == "function") Alert?.onClose(); | |
else Alert?.close(); | |
}} | |
/> | |
</HOC> | |
</notalUIContext.Provider> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment