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 { Transition } from "@headlessui/react";
import { useState } from "react";
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 type = dropItem.type;
@ -20,13 +28,13 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"}
>
{type === "error"?
<div className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" key={dropItem.id}>
{type === "error" ? (
<div
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
key={dropItem.id}
>
<div className="flex-shrink-0">
<XCircleIcon
className="h-5 w-5 text-red-400"
aria-hidden="true"
/>
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
@ -49,7 +57,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
<button
type="button"
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"
>
@ -59,8 +70,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
</div>
</div>
</div>
:(type === "notice" ?
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}>
) : type === "notice" ? (
<div
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
key={dropItem.id}
>
<div className="flex-shrink-0">
<InformationCircleIcon
className="h-5 w-5 text-blue-400"
@ -87,7 +101,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
<button
type="button"
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"
>
@ -97,8 +114,11 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
</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">
<CheckCircleIcon
className="h-5 w-5 text-green-400"
@ -115,7 +135,10 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
<button
type="button"
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"
>
@ -125,10 +148,7 @@ export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponen
</div>
</div>
</div>
)
}
)}
</Transition>
)
}
);
}

View file

@ -5,29 +5,15 @@ import { TrashIcon } from "@heroicons/react/24/outline";
import SingleAlert from "./components/singleAlertComponent";
import { AlertDropdownType } from "../../types/alerts";
import { PopUpContext } from "../../contexts/popUpContext";
import { useOnClickOutside } from "../hooks/useOnClickOutside";
export default function AlertDropdown({}: AlertDropdownType) {
const { closePopUp } = useContext(PopUpContext);
const componentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
componentRef.current &&
!componentRef.current.contains(event.target as Node)
) {
// Use the custom hook
useOnClickOutside(componentRef, () => {
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 {
notificationList,
@ -38,13 +24,13 @@ export default function AlertDropdown({}: AlertDropdownType) {
return (
<div
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
<div className="flex gap-2 pr-3 ">
<button
className="hover:text-red-500"
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
onClick={() => {
closePopUp();
setTimeout(clearNotificationList, 100);
@ -52,12 +38,15 @@ export default function AlertDropdown({}: AlertDropdownType) {
>
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
</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" />
</button>
</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.map((alertItem, index) => (
<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
</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 { closePopUp } = useContext(PopUpContext);
const ref = useRef();
const {setErrorData}= useContext(alertContext)
const { setErrorData } = useContext(alertContext);
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
function setModalOpen(x: boolean) {
setOpen(x);
@ -25,8 +25,8 @@ export default function ExportModal() {
}, 300);
}
}
const [checked,setChecked] = useState(true)
const [name,setName] = useState(flows[tabIndex].name)
const [checked, setChecked] = useState(true);
const [name, setName] = useState(flows[tabIndex].name);
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
@ -98,14 +98,13 @@ export default function ExportModal() {
</label>
<input
onChange={(event) => {
if(event.target.value!=""){
if (event.target.value != "") {
let newFlow = flows[tabIndex];
newFlow.name = event.target.value;
setName(event.target.value)
setName(event.target.value);
updateFlow(newFlow);
}
else{
setName(event.target.value)
} else {
setName(event.target.value);
}
}}
type="text"
@ -113,7 +112,7 @@ export default function ExportModal() {
value={name ?? null}
placeholder="File 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 className="w-full">
@ -121,7 +120,11 @@ export default function ExportModal() {
htmlFor="description"
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>
<textarea
name="description"
@ -134,14 +137,15 @@ export default function ExportModal() {
value={flows[tabIndex].description ?? null}
placeholder="Flow description"
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>
</div>
<div>
<label htmlFor="checkbox" className="flex items-center">
<input
onChange={(event) => {
setChecked(event.target.checked)
setChecked(event.target.checked);
}}
checked={checked}
id="checkbox"
@ -156,8 +160,8 @@ export default function ExportModal() {
<div className="w-full flex justify-end">
<button
onClick={() => {
if(checked) downloadFlow(flows[tabIndex]);
else downloadFlow(removeApiKeys(flows[tabIndex]))
if (checked) downloadFlow(flows[tabIndex]);
else downloadFlow(removeApiKeys(flows[tabIndex]));
}}
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">
<button
onClick={() =>
openPopUp(
<ImportModal
/>
)
}
onClick={() => openPopUp(<ImportModal />)}
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" />
</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"
>
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 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 ErrorAlertType = {
title: string;
list: Array<string>;
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 AlertItemType = {
type: "notice" | "error" | "success";
@ -9,4 +26,4 @@ export type AlertItemType = {
link?: string;
list?: Array<string>;
id: string;
};
};