From 8dc54494fd76fee451499363023341257273cba1 Mon Sep 17 00:00:00 2001 From: ritwik-69 <72665321+ritwik-69@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:22:30 +0530 Subject: [PATCH] client:Design a Toast Component This commit creates the toast component with customizable position,duration and color. In main.tsx wrapped the app component in toastcontextprovider so that toast can be used everywhere in the app. fixes:#68 --- frontend/src/library/toast/Toast.tsx | 138 +++++++++++++++ frontend/src/library/toast/toast-context.ts | 20 +++ frontend/src/library/toast/toast.css | 181 ++++++++++++++++++++ frontend/src/main.tsx | 5 +- 4 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 frontend/src/library/toast/Toast.tsx create mode 100644 frontend/src/library/toast/toast-context.ts create mode 100644 frontend/src/library/toast/toast.css diff --git a/frontend/src/library/toast/Toast.tsx b/frontend/src/library/toast/Toast.tsx new file mode 100644 index 0000000..90e69cc --- /dev/null +++ b/frontend/src/library/toast/Toast.tsx @@ -0,0 +1,138 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { ToastContext } from './toast-context'; +import './toast.css'; + +function useTimeout(callback: () => void, duration: number) { + const savedCallback = useRef(callback); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + const functionId = setTimeout(() => savedCallback.current(), duration); + + return () => { + clearTimeout(functionId); + }; + }, [duration]); +} +type toastProperties = { + message: string; + close: () => void; + duration: number; + position: string; + color: string; +}; + +export function Toast({ + message, + close, + duration, + position, + color, +}: toastProperties) { + useTimeout(() => { + close(); + }, duration); + return ( +
+

{message}

+ +
+ ); +} + +type ToastProviderProperties = { + children: React.ReactElement; +}; +type ToastType = { + message: string; + id: number; + duration: number; + position: string; + color: string; +}; + +export function ToastProvider({ children }: ToastProviderProperties) { + const [toasts, setToasts] = useState([]); + const [position, setPosition] = useState('top-left'); + type Options = { + message?: string; + duration?: number; + position?: string; + color?: 'info' | 'warning' | 'error' | 'success'; + }; + const openToast = useCallback( + ({ + message = '', + duration = 5000, + position = 'top-center', + color = 'info', + }: Options = {}) => { + const newToast = { + message: message, + id: Date.now(), + duration: duration, + position: position, + color: color, + }; + setToasts((prevToast) => [...prevToast, newToast]); + setPosition(position); + }, + [] + ); + + const closeToast = useCallback((id: number) => { + setTimeout(() => { + setToasts((prevToasts) => + prevToasts.filter((toast) => toast.id !== id) + ); + }, 300); + + setToasts((toasts) => { + return toasts.map((toast) => { + if (toast.id === id) { + if (toast.position == 'top-left') + toast.position = 'fade-out-left'; + else if (toast.position == 'top-right') + toast.position = 'fade-out-right'; + else if (toast.position == 'top-center') + toast.position = 'fade-out-center'; + } + return toast; + }); + }); + }, []); + const contextValue = useMemo( + () => ({ + open: openToast, + close: closeToast, + }), + [openToast, closeToast] + ); + return ( + + {children} +
+ {toasts && + toasts.map((toast) => { + return ( + { + closeToast(toast.id); + }} + duration={toast.duration} + position={toast.position} + color={toast.color} + /> + ); + })} +
+
+ ); +} diff --git a/frontend/src/library/toast/toast-context.ts b/frontend/src/library/toast/toast-context.ts new file mode 100644 index 0000000..625bc97 --- /dev/null +++ b/frontend/src/library/toast/toast-context.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +type Options = { + message?: string; + duration?: number; + position?: string; + color?: 'info' | 'warning' | 'error' | 'success'; +}; + +type ToastContextValue = { + open: (options?: Options) => void; + close: (id: number) => void; +}; + +export const ToastContext = createContext({ + open: () => {}, + close: () => {}, +}); + +export const useToast = () => useContext(ToastContext); diff --git a/frontend/src/library/toast/toast.css b/frontend/src/library/toast/toast.css new file mode 100644 index 0000000..42cf727 --- /dev/null +++ b/frontend/src/library/toast/toast.css @@ -0,0 +1,181 @@ +.toasts { + display: flex; + flex-direction: column; + gap: 10px; +} + +.toast { + color: black; + border-radius: 5px; + padding: 10px 10px; + width: 300px; + position: relative; + display: flex; +} + +.top-right { + position: fixed; + top: 10px; + right: 10px; +} + +.top-left { + position: fixed; + top: 10px; + left: 10px; +} +.top-center { + position: fixed; + top: 10px; + left: 38%; +} + +.top-center-animation { + animation-name: slideinCenter; + animation-duration: 0.35s; +} + +.top-right-animation { + animation-name: slideinRight; + animation-duration: 0.35s; +} + +.top-left-animation { + animation-name: slideinLeft; + animation-duration: 0.35s; +} + +@keyframes slideinRight { + 0% { + transform: translateX(100%); + } + 60% { + transform: translateX(-15%); + } + 80% { + transform: translateX(5%); + } + 80% { + transform: translateX(0); + } +} +@keyframes slideinCenter { + 0% { + transform: translateY(-100%); + } + 60% { + transform: translateY(15%); + } + 80% { + transform: translateY(-5%); + } + 80% { + transform: translateY(0); + } +} + +@keyframes slideinLeft { + 0% { + transform: translateX(-100%); + } + 60% { + transform: translateX(15%); + } + 80% { + transform: translateX(-5%); + } + 80% { + transform: translateX(0); + } +} + +.fade-out-left-animation { + animation-name: fade-out-left; + animation-duration: 0.35s; +} + +.fade-out-right-animation { + animation-name: fade-out-right; + animation-duration: 0.35s; +} + +.fade-out-center-animation { + animation-name: fade-out-center; + animation-duration: 0.35s; +} + +@keyframes fade-out-left { + 0% { + transform: translateX(0%); + } + + 60% { + transform: translateX(-100%); + } + + 80% { + transform: translateX(-195%); + } + + 100% { + transform: translateX(-200%); + } +} +@keyframes fade-out-right { + 0% { + transform: translateX(0%); + } + + 60% { + transform: translateX(100%); + } + + 80% { + transform: translateX(195%); + } + + 100% { + transform: translateX(200%); + } +} + +@keyframes fade-out-center { + 0% { + transform: translateY(-100%); + } + 30% { + transform: translateY(-300%); + } + 80% { + transform: translateY(-700%); + } + 100% { + transform: translateY(-1000%); + } +} + +.info { + background-color: cyan; +} + +.success { + background-color: #5cb85c; +} + +.error { + background-color: #d9534f; +} +.warning { + background-color: #f0ad4e; +} + +.close-button { + position: absolute; + right: 0px; + top: 0px; + padding: 0px 5px; + background: none; + cursor: pointer; + border: transparent; + color: black; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 12afe2b..a3323d8 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,12 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './index.css'; +import { ToastProvider } from './library/toast/Toast.tsx'; ReactDOM.createRoot(document.getElementById('root')!).render( - + + + );