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(
-
+
+
+
);