Skip to content

Commit

Permalink
client:Design a Toast Component
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ritwik-69 committed Jun 7, 2024
1 parent bc535e7 commit ead0e8c
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 1 deletion.
127 changes: 127 additions & 0 deletions frontend/src/library/toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { 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 (
<div className={`toast ${position}-animation ${color}`}>
<p>{message}</p>
<button className="close-button" onClick={close}>
{'x'}
</button>
</div>
);
}

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<ToastType[]>([]);
const [position, setPosition] = useState('top-left');
type Options = {
message?: string;
duration?: number;
position?: string;
color?: "info" | "warning" | "error" | "success";
};
const openToast = (
{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);
};

function closeToast(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,
}),
[]

Check warning on line 103 in frontend/src/library/toast/Toast.tsx

View workflow job for this annotation

GitHub Actions / eslint-frontend

React Hook useMemo has a missing dependency: 'openToast'. Either include it or remove the dependency array
);
return (
<ToastContext.Provider value={contextValue}>
{children}
<div className={`toasts ${position}`}>
{toasts &&
toasts.map((toast) => {
return (
<Toast
key={toast.id}
message={toast.message}
close={() => {
closeToast(toast.id);
}}
duration={toast.duration}
position={toast.position}
color={toast.color}
/>
);
})}
</div>
</ToastContext.Provider>
);
}
20 changes: 20 additions & 0 deletions frontend/src/library/toast/toast-context.ts
Original file line number Diff line number Diff line change
@@ -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<ToastContextValue>({
open: () => {},
close: () => {},
});

export const useToast = () => useContext(ToastContext);
181 changes: 181 additions & 0 deletions frontend/src/library/toast/toast.css
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 4 additions & 1 deletion frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<React.StrictMode>
<App />
<ToastProvider>
<App />
</ToastProvider>
</React.StrictMode>
);

0 comments on commit ead0e8c

Please sign in to comment.