Notification modal refactor (#683)

This pull request refers to the Notification Center modal refactor,
deleting permanently the PopupContext way of showing the popup, and
utilizing the Popover component from ShadCN to display it.
This commit is contained in:
Lucas Oliveira 2023-07-26 07:13:35 -03:00 • committed by GitHub
commit d79f58cfb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 144 additions and 130 deletions

View file

@ -20,6 +20,7 @@
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3", "@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
@ -1463,6 +1464,43 @@
} }
} }
}, },
"node_modules/@radix-ui/react-popover": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz",
"integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-dismissable-layer": "1.0.4",
"@radix-ui/react-focus-guards": "1.0.1",
"@radix-ui/react-focus-scope": "1.0.3",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-popper": "1.1.2",
"@radix-ui/react-portal": "1.0.3",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-use-controllable-state": "1.0.1",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.5"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": { "node_modules/@radix-ui/react-popper": {
"version": "1.1.2", "version": "1.1.2",
"license": "MIT", "license": "MIT",

View file

@ -15,6 +15,7 @@
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3", "@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",

View file

@ -1,66 +1,72 @@
import { useContext, useRef } from "react"; import { useContext, useState } from "react";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "../../components/ui/popover";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { AlertDropdownType } from "../../types/alerts"; import { AlertDropdownType } from "../../types/alerts";
import { useOnClickOutside } from "../hooks/useOnClickOutside";
import SingleAlert from "./components/singleAlertComponent"; import SingleAlert from "./components/singleAlertComponent";
export default function AlertDropdown({}: AlertDropdownType) { export default function AlertDropdown({ children }: AlertDropdownType) {
const { closePopUp } = useContext(PopUpContext);
const componentRef = useRef<HTMLDivElement>(null);
// Use the custom hook
useOnClickOutside(componentRef, () => {
closePopUp();
});
const { const {
notificationList, notificationList,
clearNotificationList, clearNotificationList,
removeFromNotificationList, removeFromNotificationList,
setNotificationCenter,
} = useContext(alertContext); } = useContext(alertContext);
const [open, setOpen] = useState(false);
return ( return (
<div <Popover
ref={componentRef} open={open}
className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-muted px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" onOpenChange={(k) => {
setOpen(k);
if (k) setNotificationCenter(false);
}}
> >
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground"> <PopoverTrigger>{children}</PopoverTrigger>
Notifications <PopoverContent className="flex h-[500px] w-[500px] flex-col">
<div className="flex gap-3 pr-3 "> <div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
<button Notifications
className="text-foreground hover:text-status-red" <div className="flex gap-3 pr-3 ">
onClick={() => { <button
closePopUp(); className="text-foreground hover:text-status-red"
setTimeout(clearNotificationList, 100); onClick={() => {
}} setOpen(false);
> setTimeout(clearNotificationList, 100);
<IconComponent name="Trash2" className="h-[1.1rem] w-[1.1rem]" /> }}
</button> >
<button <IconComponent name="Trash2" className="h-[1.1rem] w-[1.1rem]" />
className="text-foreground hover:text-status-red" </button>
onClick={closePopUp} <button
> className="text-foreground hover:text-status-red"
<IconComponent name="X" className="h-5 w-5" /> onClick={() => {
</button> setOpen(false);
</div> }}
</div> >
<div className="text-high-foreground mt-3 flex h-full w-full flex-col overflow-y-scroll scrollbar-hide"> <IconComponent name="X" className="h-5 w-5" />
{notificationList.length !== 0 ? ( </button>
notificationList.map((alertItem, index) => (
<SingleAlert
key={alertItem.id}
dropItem={alertItem}
removeAlert={removeFromNotificationList}
/>
))
) : (
<div className="flex h-full w-full items-center justify-center pb-16 text-ring">
No new notifications
</div> </div>
)} </div>
</div> <div className="text-high-foreground mt-3 flex h-full w-full flex-col overflow-y-scroll scrollbar-hide">
</div> {notificationList.length !== 0 ? (
notificationList.map((alertItem, index) => (
<SingleAlert
key={alertItem.id}
dropItem={alertItem}
removeAlert={removeFromNotificationList}
/>
))
) : (
<div className="flex h-full w-full items-center justify-center pb-16 text-ring">
No new notifications
</div>
)}
</div>
</PopoverContent>
</Popover>
); );
} }

View file

@ -1,13 +1,11 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa"; import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
import { Link, useLocation, useParams } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import AlertDropdown from "../../alerts/alertDropDown"; import AlertDropdown from "../../alerts/alertDropDown";
import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import { getRepoStars } from "../../controllers/API"; import { getRepoStars } from "../../controllers/API";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
@ -15,14 +13,9 @@ import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar"; import MenuBar from "./components/menuBar";
export default function Header() { export default function Header() {
const { flows, addFlow, tabId } = useContext(TabsContext); const { flows, tabId } = useContext(TabsContext);
const { openPopUp } = useContext(PopUpContext);
const { templates } = useContext(typesContext);
const { id } = useParams();
const AlertWidth = 384;
const { dark, setDark } = useContext(darkContext); const { dark, setDark } = useContext(darkContext);
const { notificationCenter, setNotificationCenter, setErrorData } = const { notificationCenter } = useContext(alertContext);
useContext(alertContext);
const location = useLocation(); const location = useLocation();
const [stars, setStars] = useState(null); const [stars, setStars] = useState(null);
@ -111,33 +104,18 @@ export default function Header() {
<IconComponent name="MoonIcon" className="side-bar-button-size" /> <IconComponent name="MoonIcon" className="side-bar-button-size" />
)} )}
</button> </button>
<button <AlertDropdown>
className="extra-side-bar-save-disable relative" <div className="extra-side-bar-save-disable relative">
onClick={(event: React.MouseEvent<HTMLElement>) => { {notificationCenter && (
setNotificationCenter(false); <div className="header-notifications"></div>
const { top, left } = ( )}
event.target as Element <IconComponent
).getBoundingClientRect(); name="Bell"
openPopUp( className="side-bar-button-size"
<> aria-hidden="true"
<div />
className="absolute z-10" </div>
style={{ top: top + 40, left: left - AlertWidth }} </AlertDropdown>
>
<AlertDropdown />
</div>
<div className="header-notifications-box"></div>
</>
);
}}
>
{notificationCenter && <div className="header-notifications"></div>}
<IconComponent
name="Bell"
className="side-bar-button-size"
aria-hidden="true"
/>
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,30 @@
"use client";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react";
import { cn } from "../../utils/utils";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };

View file

@ -5,7 +5,6 @@ import { SSEProvider } from "./SSEContext";
import { AlertProvider } from "./alertContext"; import { AlertProvider } from "./alertContext";
import { DarkProvider } from "./darkContext"; import { DarkProvider } from "./darkContext";
import { LocationProvider } from "./locationContext"; import { LocationProvider } from "./locationContext";
import PopUpProvider from "./popUpContext";
import { TabsProvider } from "./tabsContext"; import { TabsProvider } from "./tabsContext";
import { TypesProvider } from "./typesContext"; import { TypesProvider } from "./typesContext";
import { UndoRedoProvider } from "./undoRedoContext"; import { UndoRedoProvider } from "./undoRedoContext";
@ -22,9 +21,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
<AlertProvider> <AlertProvider>
<SSEProvider> <SSEProvider>
<TabsProvider> <TabsProvider>
<UndoRedoProvider> <UndoRedoProvider>{children}</UndoRedoProvider>
<PopUpProvider>{children}</PopUpProvider>
</UndoRedoProvider>
</TabsProvider> </TabsProvider>
</SSEProvider> </SSEProvider>
</AlertProvider> </AlertProvider>

View file

@ -1,38 +0,0 @@
import React, { createContext, useState } from "react";
// context to set JSX element on the DOM
export const PopUpContext = createContext({
openPopUp: (popUpElement: JSX.Element) => {},
closePopUp: () => {},
setCloseEdit: (value: string) => {},
closeEdit: "",
});
interface PopUpProviderProps {
children: React.ReactNode;
}
const PopUpProvider = ({ children }: PopUpProviderProps) => {
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
const openPopUp = (element: JSX.Element) => {
setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
};
const closePopUp = () => {
setPopUpElements((prevPopUps) => prevPopUps.slice(1));
};
const [closeEdit, setCloseEdit] = useState("");
return (
<PopUpContext.Provider
value={{ openPopUp, closePopUp, closeEdit, setCloseEdit }}
>
{children}
{popUpElements[0]}
</PopUpContext.Provider>
);
};
export default PopUpProvider;

View file

@ -19,7 +19,9 @@ export type SingleAlertComponentType = {
dropItem: AlertItemType; dropItem: AlertItemType;
removeAlert: (index: string) => void; removeAlert: (index: string) => void;
}; };
export type AlertDropdownType = {}; export type AlertDropdownType = {
children: JSX.Element;
};
export type AlertItemType = { export type AlertItemType = {
type: "notice" | "error" | "success"; type: "notice" | "error" | "success";
title: string; title: string;