Refactor alertDropDown components

Adjust singleAlertComponent's formatting for better readability and add a custom hook to handle click outside functionality in alertDropdown component.
This commit is contained in:
Gabriel Almeida 2023-04-27 17:01:51 -03:00
commit 917cc51148
6 changed files with 509 additions and 451 deletions

View file

@ -1,10 +1,18 @@
import { XCircleIcon, XMarkIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/24/outline"; import {
XCircleIcon,
XMarkIcon,
InformationCircleIcon,
CheckCircleIcon,
} from "@heroicons/react/24/outline";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { useState } from "react"; import { useState } from "react";
import { SingleAlertComponentType } from "../../../../types/alerts"; import { SingleAlertComponentType } from "../../../../types/alerts";
export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponentType) { export default function SingleAlert({
dropItem,
removeAlert,
}: SingleAlertComponentType) {
const [show, setShow] = useState(true); const [show, setShow] = useState(true);
const type = dropItem.type; const type = dropItem.type;
@ -20,13 +28,13 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
leaveFrom={"transform translate-x-0"} leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"} leaveTo={"transform translate-x-[-100%]"}
> >
{type === "error"? {type === "error" ? (
<div className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" key={dropItem.id}> <div
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
key={dropItem.id}
>
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<XCircleIcon <XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
className="h-5 w-5 text-red-400"
aria-hidden="true"
/>
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="text-sm font-medium text-red-800"> <h3 className="text-sm font-medium text-red-800">
@ -49,7 +57,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500); setShow(false);
setTimeout(() => {
removeAlert(dropItem.id);
}, 500);
}} }}
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500" className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
> >
@ -59,8 +70,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
</div> </div>
</div> </div>
</div> </div>
:(type === "notice" ? ) : type === "notice" ? (
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}> <div
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
key={dropItem.id}
>
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<InformationCircleIcon <InformationCircleIcon
className="h-5 w-5 text-blue-400" className="h-5 w-5 text-blue-400"
@ -87,7 +101,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500); setShow(false);
setTimeout(() => {
removeAlert(dropItem.id);
}, 500);
}} }}
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500" className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
> >
@ -97,8 +114,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
</div> </div>
</div> </div>
</div> </div>
: ) : (
<div className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md" key={dropItem.id}> <div
className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md"
key={dropItem.id}
>
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<CheckCircleIcon <CheckCircleIcon
className="h-5 w-5 text-green-400" className="h-5 w-5 text-green-400"
@ -115,7 +135,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500); setShow(false);
setTimeout(() => {
removeAlert(dropItem.id);
}, 500);
}} }}
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500" className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
> >
@ -125,10 +148,7 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
</div> </div>
</div> </div>
</div> </div>
) )}
}
</Transition> </Transition>
) );
}
}

View file

@ -5,29 +5,15 @@ import { TrashIcon } from "@heroicons/react/24/outline";
import SingleAlert from "./components/singleAlertComponent"; import SingleAlert from "./components/singleAlertComponent";
import { AlertDropdownType } from "../../types/alerts"; import { AlertDropdownType } from "../../types/alerts";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { useOnClickOutside } from "../hooks/useOnClickOutside";
export default function AlertDropdown({}: AlertDropdownType) { export default function AlertDropdown({}: AlertDropdownType) {
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
const componentRef = useRef<HTMLDivElement>(null); const componentRef = useRef<HTMLDivElement>(null);
useEffect(() => { // Use the custom hook
function handleClickOutside(event: MouseEvent) { useOnClickOutside(componentRef, () => {
if (
componentRef.current &&
!componentRef.current.contains(event.target as Node)
) {
closePopUp(); closePopUp();
} });
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
// Cleanup the event listener when the component is unmounted
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [componentRef]);
const { const {
notificationList, notificationList,
@ -38,13 +24,13 @@ export default function AlertDropdown({}: AlertDropdownType) {
return ( return (
<div <div
ref={componentRef} ref={componentRef}
className="z-10 py-3 pb-4 rounded-md bg-white ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col" className="z-10 py-3 pb-4 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col"
> >
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800"> <div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
Notifications Notifications
<div className="flex gap-2 pr-3 "> <div className="flex gap-2 pr-3 ">
<button <button
className="hover:text-red-500" className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
onClick={() => { onClick={() => {
closePopUp(); closePopUp();
setTimeout(clearNotificationList, 100); setTimeout(clearNotificationList, 100);
@ -52,12 +38,15 @@ export default function AlertDropdown({}: AlertDropdownType) {
> >
<TrashIcon className="w-[1.1rem] h-[1.1rem]" /> <TrashIcon className="w-[1.1rem] h-[1.1rem]" />
</button> </button>
<button className="hover:text-red-500" onClick={closePopUp}> <button
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
onClick={closePopUp}
>
<XMarkIcon className="h-5 w-5" /> <XMarkIcon className="h-5 w-5" />
</button> </button>
</div> </div>
</div> </div>
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide"> <div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
{notificationList.length !== 0 ? ( {notificationList.length !== 0 ? (
notificationList.map((alertItem, index) => ( notificationList.map((alertItem, index) => (
<SingleAlert <SingleAlert
@ -67,7 +56,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
/> />
)) ))
) : ( ) : (
<div className="h-full w-full pb-16 text-gray-500 flex justify-center items-center"> <div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
No new notifications No new notifications
</div> </div>
)} )}

View file

@ -0,0 +1,33 @@
import { useEffect } from "react";
export function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
// Do nothing if clicking ref's element or its children
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
// Attach the listener to the document
document.addEventListener("mousedown", listener, { passive: true });
// Attach the listener to the react-flow instance
const reactFlowContainer = document.querySelector(".react-flow");
if (reactFlowContainer) {
reactFlowContainer.addEventListener("mousedown", listener, {
passive: true,
});
}
// Clean up the listener when the component is unmounted
return () => {
document.removeEventListener("mousedown", listener);
if (reactFlowContainer) {
reactFlowContainer.removeEventListener("mousedown", listener);
}
};
}, [ref, handler]); // Rerun only if ref or handler changes
}

View file

@ -15,7 +15,7 @@ export default function ExportModal() {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
const ref = useRef(); const ref = useRef();
const {setErrorData}= useContext(alertContext) const { setErrorData } = useContext(alertContext);
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext); const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
function setModalOpen(x: boolean) { function setModalOpen(x: boolean) {
setOpen(x); setOpen(x);
@ -25,8 +25,8 @@ export default function ExportModal() {
}, 300); }, 300);
} }
} }
const [checked,setChecked] = useState(true) const [checked, setChecked] = useState(true);
const [name,setName] = useState(flows[tabIndex].name) const [name, setName] = useState(flows[tabIndex].name);
return ( return (
<Transition.Root show={open} appear={true} as={Fragment}> <Transition.Root show={open} appear={true} as={Fragment}>
<Dialog <Dialog
@ -98,14 +98,13 @@ export default function ExportModal() {
</label> </label>
<input <input
onChange={(event) => { onChange={(event) => {
if(event.target.value!=""){ if (event.target.value != "") {
let newFlow = flows[tabIndex]; let newFlow = flows[tabIndex];
newFlow.name = event.target.value; newFlow.name = event.target.value;
setName(event.target.value) setName(event.target.value);
updateFlow(newFlow); updateFlow(newFlow);
} } else {
else{ setName(event.target.value);
setName(event.target.value)
} }
}} }}
type="text" type="text"
@ -113,7 +112,7 @@ export default function ExportModal() {
value={name ?? null} value={name ?? null}
placeholder="File name" placeholder="File name"
id="name" id="name"
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500" className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100"
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
@ -121,7 +120,11 @@ export default function ExportModal() {
htmlFor="description" htmlFor="description"
className="block mb-2 font-medium text-gray-700 dark:text-white" className="block mb-2 font-medium text-gray-700 dark:text-white"
> >
Description <span className="text-gray-400 text-sm"> (optional)</span> Description{" "}
<span className="text-gray-400 text-sm">
{" "}
(optional)
</span>
</label> </label>
<textarea <textarea
name="description" name="description"
@ -134,14 +137,15 @@ export default function ExportModal() {
value={flows[tabIndex].description ?? null} value={flows[tabIndex].description ?? null}
placeholder="Flow description" placeholder="Flow description"
rows={3} rows={3}
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500" className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
></textarea> ></textarea>
</div> </div>
<div> <div>
<label htmlFor="checkbox" className="flex items-center"> <label htmlFor="checkbox" className="flex items-center">
<input <input
onChange={(event) => { onChange={(event) => {
setChecked(event.target.checked) setChecked(event.target.checked);
}} }}
checked={checked} checked={checked}
id="checkbox" id="checkbox"
@ -156,8 +160,8 @@ export default function ExportModal() {
<div className="w-full flex justify-end"> <div className="w-full flex justify-end">
<button <button
onClick={() => { onClick={() => {
if(checked) downloadFlow(flows[tabIndex]); if (checked) downloadFlow(flows[tabIndex]);
else downloadFlow(removeApiKeys(flows[tabIndex])) else downloadFlow(removeApiKeys(flows[tabIndex]));
}} }}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
> >

View file

@ -54,18 +54,13 @@ export default function TabsManagerComponent() {
/> />
<div className="ml-auto mr-2 flex gap-3"> <div className="ml-auto mr-2 flex gap-3">
<button <button
onClick={() => onClick={() => openPopUp(<ImportModal />)}
openPopUp(
<ImportModal
/>
)
}
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500" className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500"
> >
Import <ArrowUpTrayIcon className="w-5 h-5" /> Import <ArrowUpTrayIcon className="w-5 h-5" />
</button> </button>
<button <button
onClick={() =>openPopUp(<ExportModal/>)} onClick={() => openPopUp(<ExportModal />)}
className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500" className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500"
> >
Export <ArrowDownTrayIcon className="h-5 w-5" /> Export <ArrowDownTrayIcon className="h-5 w-5" />

View file

@ -1,7 +1,24 @@
export type ErrorAlertType = {title:string,list:Array<string>,id:string,removeAlert:(id:string)=>void} export type ErrorAlertType = {
export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void} title: string;
export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void} list: Array<string>;
export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void} id: string;
removeAlert: (id: string) => void;
};
export type NoticeAlertType = {
title: string;
link: string;
id: string;
removeAlert: (id: string) => void;
};
export type SuccessAlertType = {
title: string;
id: string;
removeAlert: (id: string) => void;
};
export type SingleAlertComponentType = {
dropItem: AlertItemType;
removeAlert: (index: string) => void;
};
export type AlertDropdownType = {}; export type AlertDropdownType = {};
export type AlertItemType = { export type AlertItemType = {
type: "notice" | "error" | "success"; type: "notice" | "error" | "success";
@ -9,4 +26,4 @@ export type AlertItemType = {
link?: string; link?: string;
list?: Array<string>; list?: Array<string>;
id: string; id: string;
}; };