Repository: CBIIT/crdc-datahub-ui
Pull request #710: CRDCDH-2601 Add column support to header navigation 
Author: login: Alejandro-Vega
Created: 2025-05-13T12:51:47Z | Merged: 2025-05-13T18:55:41Z

### Overview

Updated the header navigation to support 4 column design.

### Change Details (Specifics)

- Updated header to 4 column design using a grid system
- Adjusted spacing and alignment to match edges of header 
- Updated header config design to avoid having to define some of the config within the navigation components for both desktop and tablet/mobile. Instead, relying on action mappings and handlers.
- When a column in the navigation requires permissions and the entire column is hidden due to insufficient permissions, then it will not render the column and shift the next column over to take its place. Only case right now is the Admin manage permissions, can be seen in storybook
- Added additional Storybook support for the header navigation and dropdown from the navigation
- Replaced old GenericAlert within navigation to use Notistack
- Forced newline to "Data Submission Instructions" to avoid it looking weird within the 4 column grid 
- Updated tablet/mobile navigation to use new header config, but intention was to leave it looking like it did before
- Already behind on this ticket, so will skip adding tests for now. Will create tests for this when time allows

> [!NOTE]
> There is currently a weird in-between where the navigation gets crowded due to screen size before breaking to the tablet/mobile navigation. This already existed before these changes. I'm assuming the solution would be to just break sooner, but leaving as-is for now until we decide to address this.

### Related Ticket(s)

[CRDCDH-2601](https://tracker.nci.nih.gov/browse/CRDCDH-2601)



Diff:
diff --git a/src/components/Header/components/HeaderDesktop.tsx b/src/components/Header/components/HeaderDesktop.tsx
index aa2b5bae4..41f004be8 100644
--- a/src/components/Header/components/HeaderDesktop.tsx
+++ b/src/components/Header/components/HeaderDesktop.tsx
@@ -9,6 +9,7 @@ const HeaderBanner = styled("div")({
 const HeaderContainer = styled("div")({
   margin: "0 auto",
   paddingLeft: "32px",
+  paddingRight: "32px",
   maxWidth: "1400px",
   display: "flex",
 });
diff --git a/src/components/Header/components/HeaderTabletAndMobile.tsx b/src/components/Header/components/HeaderTabletAndMobile.tsx
index 077fec21d..bb211ba0d 100644
--- a/src/components/Header/components/HeaderTabletAndMobile.tsx
+++ b/src/components/Header/components/HeaderTabletAndMobile.tsx
@@ -1,16 +1,18 @@
-import React, { HTMLProps, useEffect, useState } from "react";
+import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
+import { flatMap } from "lodash";
 import { styled } from "@mui/material";
+import { useSnackbar } from "notistack";
 import Logo from "./LogoMobile";
 import menuClearIcon from "../../../assets/header/Menu_Cancel_Icon.svg";
 import rightArrowIcon from "../../../assets/header/Right_Arrow.svg";
 import leftArrowIcon from "../../../assets/header/Left_Arrow.svg";
-import { HeaderLinks, HeaderSubLinks } from "../../../config/HeaderConfig";
+import { ActionHandlers, ActionId, HeaderLinks } from "../../../config/HeaderConfig";
 import { useAuthContext } from "../../Contexts/AuthContext";
-import GenericAlert from "../../GenericAlert";
 import APITokenDialog from "../../APITokenDialog";
 import UploaderToolDialog from "../../UploaderToolDialog";
 import { hasPermission, Permissions } from "../../../config/AuthPermissions";
+import { Logger } from "../../../utils";
 
 const HeaderBanner = styled("div")({
   width: "100%",
@@ -128,12 +130,12 @@ const Header = () => {
   const { isLoggedIn, user, logout } = useAuthContext();
   const navigate = useNavigate();
   const location = useLocation();
+  const { enqueueSnackbar } = useSnackbar();
 
   const [navMobileDisplay, setNavMobileDisplay] = useState("none");
   const [openAPITokenDialog, setOpenAPITokenDialog] = useState<boolean>(false);
   const [uploaderToolOpen, setUploaderToolOpen] = useState<boolean>(false);
   const [selectedList, setSelectedList] = useState<NavBarItem[] | NavBarSubItem[]>(HeaderLinks);
-  const [showLogoutAlert, setShowLogoutAlert] = useState<boolean>(false);
   const [restorePath, setRestorePath] = useState<string | null>(null);
 
   const displayName = user?.firstName || "N/A";
@@ -142,70 +144,33 @@ const Header = () => {
     const logoutStatus = await logout?.();
     if (logoutStatus) {
       navigate("/");
-      setShowLogoutAlert(true);
-      setTimeout(() => setShowLogoutAlert(false), 10000);
+      enqueueSnackbar("You have been logged out.", { variant: "success" });
     }
   };
 
-  HeaderSubLinks[displayName] = [
-    {
-      name: "User Profile",
-      link: `/profile/${user?._id}`,
-      id: "navbar-dropdown-item-user-profile",
-      className: "navMobileSubItem",
-    },
-    {
-      name: "Uploader CLI Tool",
-      onClick: () => setUploaderToolOpen(true),
-      id: "navbar-dropdown-item-uploader-tool",
-      className: "navMobileSubItem action",
-    },
-    {
-      name: "API Token",
-      onClick: () => setOpenAPITokenDialog(true),
-      id: "navbar-dropdown-item-api-token",
-      className: "navMobileSubItem action",
-      permissions: ["data_submission:create"],
-    },
-    {
-      name: "Manage Studies",
-      link: "/studies",
-      id: "navbar-dropdown-item-studies-manage",
-      className: "navMobileSubItem",
-      permissions: ["study:manage"],
-    },
-    {
-      name: "Manage Programs",
-      link: "/programs",
-      id: "navbar-dropdown-item-program-manage",
-      className: "navMobileSubItem",
-      permissions: ["program:manage"],
-    },
-    {
-      name: "Manage Institutions",
-      link: "/institutions",
-      id: "navbar-dropdown-item-institution-manage",
-      className: "navMobileSubItem",
-      permissions: ["institution:manage"],
-    },
-    {
-      name: "Manage Users",
-      link: "/users",
-      id: "navbar-dropdown-item-user-manage",
-      className: "navMobileSubItem",
-      permissions: ["user:manage"],
-    },
-    {
-      name: "Logout",
-      link: "/logout",
-      id: "navbar-dropdown-item-logout",
-      className: "navMobileSubItem",
-    },
-  ];
+  const actionHandlers: ActionHandlers = useMemo(
+    () => ({
+      logout: handleLogout,
+      openAPITokenDialog: () => setOpenAPITokenDialog(true),
+      openCLIToolDialog: () => setUploaderToolOpen(true),
+    }),
+    [logout]
+  );
+
+  const handleItemClick = (item: ActionId) => {
+    if (!item) {
+      Logger.error(`HeaderTabletAndMobile.tsx: No action found for actionId '${item}'`);
+      return;
+    }
+
+    actionHandlers[item]?.();
+  };
 
-  const clickNavItem = (e) => {
-    const clickTitle = e.target.textContent;
-    setSelectedList(HeaderSubLinks[clickTitle]);
+  const clickNavItem = (clickTitle: string) => {
+    const list: NavBarItem = HeaderLinks?.find(
+      (link) => link.name === clickTitle && "columns" in link
+    );
+    setSelectedList(flatMap(list?.columns));
   };
 
   const checkPermissions = (permissions: AuthPermissions[]) => {
@@ -238,9 +203,6 @@ const Header = () => {
 
   return (
     <>
-      <GenericAlert open={showLogoutAlert}>
-        <span>You have been logged out.</span>
-      </GenericAlert>
       <HeaderBanner data-testid="navigation-header-mobile">
         <HeaderContainer>
           <Logo />
@@ -296,100 +258,102 @@ const Header = () => {
               </div>
             )}
             <div className="navMobileContainer">
-              {selectedList?.map((navMobileItem: NavBarItem | NavBarSubItem) => {
-                const hasEveryPermission = checkPermissions(navMobileItem?.permissions);
-                if (!hasEveryPermission) {
-                  return null;
-                }
+              {selectedList
+                ?.filter((headerLinks) => headerLinks.name !== "User")
+                ?.map((navMobileItem: NavBarItem | NavBarSubItem) => {
+                  const hasEveryPermission = checkPermissions(navMobileItem?.permissions);
+                  if (!hasEveryPermission) {
+                    return null;
+                  }
 
-                return (
-                  <React.Fragment key={`mobile_${navMobileItem.id}`}>
-                    {navMobileItem.className === "navMobileItem" && (
-                      <NavLink
-                        id={navMobileItem.id}
-                        to={navMobileItem.link}
-                        target={navMobileItem.link.startsWith("https://") ? "_blank" : "_self"}
-                        onClick={() => setNavMobileDisplay("none")}
-                      >
-                        <div className="navMobileItem">{navMobileItem.name}</div>
-                      </NavLink>
-                    )}
-                    {navMobileItem.className === "navMobileItem clickable" && (
-                      <div
-                        id={navMobileItem.id}
-                        role="button"
-                        tabIndex={0}
-                        className="navMobileItem clickable"
-                        onKeyDown={(e) => {
-                          if (e.key === "Enter") {
-                            clickNavItem(e);
-                          }
-                        }}
-                        onClick={clickNavItem}
-                      >
-                        {navMobileItem.name}
-                      </div>
-                    )}
-                    {navMobileItem.className === "navMobileSubItem action" &&
-                    "onClick" in navMobileItem &&
-                    typeof navMobileItem.onClick === "function" ? (
-                      <div
-                        id={navMobileItem.id}
-                        role="button"
-                        tabIndex={0}
-                        className="navMobileItem SubItem action"
-                        onKeyDown={(e) => {
-                          if (e.key === "Enter") {
-                            navMobileItem.onClick();
-                          }
-                        }}
-                        onClick={() => navMobileItem.onClick()}
-                      >
-                        {navMobileItem.name}
-                      </div>
-                    ) : null}
-                    {navMobileItem.className === "navMobileSubItem" && (
-                      <Link
-                        id={navMobileItem.id}
-                        to={navMobileItem.link}
-                        target={
-                          navMobileItem.link.startsWith("https://") ||
-                          navMobileItem.link.endsWith(".pdf")
-                            ? "_blank"
-                            : "_self"
-                        }
-                      >
+                  return (
+                    <React.Fragment key={`mobile_${navMobileItem.id}`}>
+                      {navMobileItem.className === "navMobileItem" && (
+                        <NavLink
+                          id={navMobileItem.id}
+                          to={navMobileItem.link}
+                          target={navMobileItem.link.startsWith("https://") ? "_blank" : "_self"}
+                          onClick={() => setNavMobileDisplay("none")}
+                        >
+                          <div className="navMobileItem">{navMobileItem.name}</div>
+                        </NavLink>
+                      )}
+                      {navMobileItem.className === "navMobileItem clickable" && (
                         <div
+                          id={navMobileItem.id}
                           role="button"
                           tabIndex={0}
-                          className="navMobileItem SubItem"
+                          className="navMobileItem clickable"
                           onKeyDown={(e) => {
                             if (e.key === "Enter") {
-                              setNavMobileDisplay("none");
-                              if (navMobileItem.name === "Logout") {
-                                handleLogout();
-                                setSelectedList(HeaderLinks);
-                              }
+                              clickNavItem(navMobileItem.name);
                             }
                           }}
-                          onClick={() => {
-                            setNavMobileDisplay("none");
-                            if (navMobileItem.name === "Logout") {
-                              handleLogout();
-                              setSelectedList(HeaderLinks);
+                          onClick={() => clickNavItem(navMobileItem.name)}
+                        >
+                          {navMobileItem.name}
+                        </div>
+                      )}
+                      {navMobileItem.className === "navMobileSubItem action" &&
+                      "actionId" in navMobileItem &&
+                      typeof navMobileItem.actionId === "string" ? (
+                        <div
+                          id={navMobileItem.id}
+                          role="button"
+                          tabIndex={0}
+                          className="navMobileItem SubItem action"
+                          onKeyDown={(e) => {
+                            if (e.key === "Enter") {
+                              handleItemClick(navMobileItem.actionId as ActionId);
                             }
                           }}
+                          onClick={() => handleItemClick(navMobileItem.actionId as ActionId)}
                         >
                           {navMobileItem.name}
                         </div>
-                      </Link>
-                    )}
-                    {navMobileItem.className === "navMobileSubTitle" && (
-                      <div className="navMobileItem">{navMobileItem.name}</div>
-                    )}
-                  </React.Fragment>
-                );
-              })}
+                      ) : null}
+                      {navMobileItem.className === "navMobileSubItem" && (
+                        <Link
+                          id={navMobileItem.id}
+                          to={navMobileItem.link}
+                          target={
+                            navMobileItem.link.startsWith("https://") ||
+                            navMobileItem.link.endsWith(".pdf")
+                              ? "_blank"
+                              : "_self"
+                          }
+                        >
+                          <div
+                            role="button"
+                            tabIndex={0}
+                            className="navMobileItem SubItem"
+                            onKeyDown={(e) => {
+                              if (e.key === "Enter") {
+                                setNavMobileDisplay("none");
+                                if (navMobileItem.name === "Logout") {
+                                  handleLogout();
+                                  setSelectedList(HeaderLinks);
+                                }
+                              }
+                            }}
+                            onClick={() => {
+                              setNavMobileDisplay("none");
+                              if (navMobileItem.name === "Logout") {
+                                handleLogout();
+                                setSelectedList(HeaderLinks);
+                              }
+                            }}
+                          >
+                            {navMobileItem.name}
+                          </div>
+                        </Link>
+                      )}
+                      {navMobileItem.className === "navMobileSubTitle" && (
+                        <div className="navMobileItem">{navMobileItem.name}</div>
+                      )}
+                    </React.Fragment>
+                  );
+                })}
               {/* eslint-disable-next-line no-nested-ternary */}
               {selectedList === HeaderLinks ? (
                 isLoggedIn ? (
@@ -400,10 +364,10 @@ const Header = () => {
                     className="navMobileItem clickable"
                     onKeyDown={(e) => {
                       if (e.key === "Enter") {
-                        clickNavItem(e);
+                        clickNavItem("User");
                       }
                     }}
-                    onClick={clickNavItem}
+                    onClick={() => clickNavItem("User")}
                   >
                     {displayName}
                   </div>
diff --git a/src/components/Header/components/NavbarDesktop.tsx b/src/components/Header/components/NavbarDesktop.tsx
index 04e0d54ef..ab49b9e6f 100644
--- a/src/components/Header/components/NavbarDesktop.tsx
+++ b/src/components/Header/components/NavbarDesktop.tsx
@@ -1,11 +1,14 @@
-import React, { useEffect, useState, useRef } from "react";
+import React, { useEffect, useState, useRef, useMemo } from "react";
 import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
-import { Button, styled } from "@mui/material";
+import { ClickAwayListener, styled } from "@mui/material";
+import { flatMap } from "lodash";
+import { useSnackbar } from "notistack";
 import { useAuthContext } from "../../Contexts/AuthContext";
-import GenericAlert from "../../GenericAlert";
-import { HeaderLinks, HeaderSubLinks } from "../../../config/HeaderConfig";
+import { ActionHandlers, ActionId, HeaderLinks } from "../../../config/HeaderConfig";
 import APITokenDialog from "../../APITokenDialog";
 import UploaderToolDialog from "../../UploaderToolDialog";
+import NavbarDesktopDropdown from "./NavbarDesktopDropdown";
+import { Logger } from "../../../utils";
 import { hasPermission, Permissions } from "../../../config/AuthPermissions";
 
 const Nav = styled("div")({
@@ -84,6 +87,7 @@ const LiSection = styled("li")({
   },
   "&.name-dropdown-li": {
     marginLeft: "auto",
+    marginRight: "12px",
   },
   "&.login-button": {
     lineHeight: "48px",
@@ -103,6 +107,7 @@ const LiSection = styled("li")({
     borderTop: "4px solid transparent",
     borderLeft: "4px solid transparent",
     borderRight: "4px solid transparent",
+    height: "100%",
     "&:hover": {
       cursor: "pointer",
     },
@@ -110,6 +115,7 @@ const LiSection = styled("li")({
   "& .navText": {
     borderBottom: "4px solid transparent",
     width: "fit-content",
+    height: "100%",
     margin: "auto",
     "&:hover": {
       cursor: "pointer",
@@ -192,60 +198,16 @@ const LiSection = styled("li")({
     borderTop: "4px solid #5786FF",
     borderLeft: "4px solid #5786FF",
     borderRight: "4px solid #5786FF",
+    height: "100%",
+    "& .shouldBeUnderlined": {
+      borderBottom: "0 !important",
+    },
   },
   "& .invisible": {
     visibility: "hidden",
   },
 });
 
-const Dropdown = styled("div")({
-  top: "60.5px",
-  left: 0,
-  width: "100%",
-  background: "#1F4671",
-  zIndex: 1100,
-  position: "absolute",
-});
-
-const NameDropdownContainer = styled("div")({
-  margin: "0 auto",
-  textAlign: "left",
-  position: "relative",
-  maxWidth: "1400px",
-  "& .dropdownList": {
-    background: "#1F4671",
-    display: "inline-flex",
-    gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
-    padding: "32px 32px 0 32px",
-  },
-  "& .dropdownItem": {
-    padding: "0 10px 52px 10px",
-    textAlign: "left",
-    fontFamily: "'Poppins', sans-serif",
-    fontStyle: "normal",
-    fontWeight: 600,
-    fontSize: "20px",
-    lineHeight: "110%",
-    color: "#FFFFFF",
-    textDecoration: "none",
-    cursor: "pointer",
-    "&:hover": {
-      textDecoration: "underline",
-    },
-  },
-  "& .dropdownItemButton": {
-    textTransform: "none",
-    paddingLeft: "20px",
-    paddingRight: "20px",
-    "&:hover": {
-      background: "transparent",
-    },
-  },
-  "#navbar-dropdown-item-name-logout": {
-    maxWidth: "200px",
-  },
-});
-
 const StyledLoginLink = styled(Link)({
   color: "#007BBD !important",
   textAlign: "right",
@@ -290,86 +252,47 @@ const NavBar = () => {
   const { isLoggedIn, user, logout } = useAuthContext();
   const navigate = useNavigate();
   const location = useLocation();
+  const { enqueueSnackbar } = useSnackbar();
 
   const [clickedTitle, setClickedTitle] = useState("");
   const [openAPITokenDialog, setOpenAPITokenDialog] = useState<boolean>(false);
   const [uploaderToolOpen, setUploaderToolOpen] = useState<boolean>(false);
-  const [showLogoutAlert, setShowLogoutAlert] = useState<boolean>(false);
   const [restorePath, setRestorePath] = useState<string>(null);
   const dropdownSelection = useRef<HTMLDivElement>(null);
 
   const clickableObject = HeaderLinks.filter(
     (item) => item.className === "navMobileItem clickable"
   );
-  const clickableTitle = clickableObject.map((item) => item.name);
+  const clickableTitle = clickableObject.map((item) => item.name) as string[];
   const displayName = user?.firstName?.toUpperCase() || "N/A";
 
   clickableTitle.push(displayName);
 
-  HeaderSubLinks[displayName] = [
-    {
-      name: "User Profile",
-      link: `/profile/${user?._id}`,
-      id: "navbar-dropdown-item-user-profile",
-      className: "navMobileSubItem",
-    },
-    {
-      name: "Uploader CLI Tool",
-      onClick: () => setUploaderToolOpen(true),
-      id: "navbar-dropdown-item-uploader-tool",
-      className: "navMobileSubItem action",
-    },
-    {
-      name: "API Token",
-      onClick: () => setOpenAPITokenDialog(true),
-      id: "navbar-dropdown-item-api-token",
-      className: "navMobileSubItem action",
-      permissions: ["data_submission:create"],
-    },
-    {
-      name: "Manage Studies",
-      link: "/studies",
-      id: "navbar-dropdown-item-studies-manage",
-      className: "navMobileSubItem",
-      permissions: ["study:manage"],
-    },
-    {
-      name: "Manage Programs",
-      link: "/programs",
-      id: "navbar-dropdown-item-program-manage",
-      className: "navMobileSubItem",
-      permissions: ["program:manage"],
-    },
-    {
-      name: "Manage Institutions",
-      link: "/institutions",
-      id: "navbar-dropdown-item-institution-manage",
-      className: "navMobileSubItem",
-      permissions: ["institution:manage"],
-    },
-    {
-      name: "Manage Users",
-      link: "/users",
-      id: "navbar-dropdown-item-user-manage",
-      className: "navMobileSubItem",
-      permissions: ["user:manage"],
-    },
-    {
-      name: "Logout",
-      onClick: () => handleLogout(),
-      id: "navbar-dropdown-item-logout",
-      className: "navMobileSubItem action",
-    },
-  ];
-
   const handleLogout = async () => {
     setClickedTitle("");
     const logoutStatus = await logout();
     if (logoutStatus) {
       navigate("/");
-      setShowLogoutAlert(true);
-      setTimeout(() => setShowLogoutAlert(false), 10000);
+      enqueueSnackbar("You have been logged out.", { variant: "success" });
+    }
+  };
+
+  const actionHandlers: ActionHandlers = useMemo(
+    () => ({
+      logout: handleLogout,
+      openAPITokenDialog: () => setOpenAPITokenDialog(true),
+      openCLIToolDialog: () => setUploaderToolOpen(true),
+    }),
+    [logout]
+  );
+
+  const handleItemClick = (item: ActionId) => {
+    if (!item) {
+      Logger.error(`NavbarDesktop.tsx: No action found for actionId '${item}'`);
+      return;
     }
+
+    actionHandlers[item]?.();
   };
 
   const handleMenuClick = (e) => {
@@ -380,6 +303,10 @@ const NavBar = () => {
     }
   };
 
+  const handleUserClick = () => {
+    setClickedTitle("User");
+  };
+
   const onKeyPressHandler = (e) => {
     if (e.key === "Enter") {
       handleMenuClick(e);
@@ -392,11 +319,18 @@ const NavBar = () => {
     if (item.className === "navMobileItem") {
       return correctPath === item.link;
     }
-    if (HeaderSubLinks[linkName] === undefined) {
+    const tab = HeaderLinks.find((link) => link.name === linkName);
+    if (!tab) {
       return false;
     }
-    const linkNames = Object.values(HeaderSubLinks[linkName]).map((e: NavBarSubItem) => e.link);
-    return linkNames.includes(correctPath);
+
+    if ("columns" in tab) {
+      // Current path is within sub-navigation links
+      const linkNames = flatMap(tab.columns).map((item) => item.link);
+      return linkNames.includes(correctPath);
+    }
+
+    return false;
   };
 
   const checkPermissions = (permissions: AuthPermissions[]) => {
@@ -439,146 +373,105 @@ const NavBar = () => {
     setRestorePath(location?.pathname);
   }, [location]);
 
+  const headerLinksWithoutUser = useMemo(
+    () => HeaderLinks.filter((headerLinks) => headerLinks.name !== "User"),
+    [HeaderLinks]
+  );
+
   return (
-    <Nav>
-      <GenericAlert open={showLogoutAlert}>
-        <span>You have been logged out.</span>
-      </GenericAlert>
-      <NavContainer>
-        <UlContainer>
-          {HeaderLinks.map((navItem: NavBarItem) => {
-            const hasEveryPermission = checkPermissions(navItem.permissions);
-            if (!hasEveryPermission) {
-              return null;
-            }
-
-            return (
-              <LiSection key={navItem.id}>
-                {navItem.className === "navMobileItem" ? (
-                  <div className="navTitle directLink">
-                    <NavLink
-                      to={navItem.link}
-                      target={navItem.link.startsWith("https://") ? "_blank" : "_self"}
-                    >
+    <ClickAwayListener onClickAway={() => setClickedTitle("")}>
+      <Nav>
+        <NavContainer>
+          <UlContainer>
+            {headerLinksWithoutUser?.map((navItem: NavBarItem) => {
+              const hasEveryPermission = checkPermissions(navItem.permissions);
+              if (!hasEveryPermission) {
+                return null;
+              }
+
+              return (
+                <LiSection key={navItem.id}>
+                  {navItem.className === "navMobileItem" ? (
+                    <div className="navTitle directLink">
+                      <NavLink
+                        to={navItem.link}
+                        target={navItem.link.startsWith("https://") ? "_blank" : "_self"}
+                      >
+                        <div
+                          id={navItem.id}
+                          role="button"
+                          tabIndex={0}
+                          className={`navText directLink ${
+                            shouldBeUnderlined(navItem) ? "shouldBeUnderlined" : ""
+                          }`}
+                          onKeyDown={onKeyPressHandler}
+                          onClick={handleMenuClick}
+                        >
+                          {navItem.name}
+                        </div>
+                      </NavLink>
+                    </div>
+                  ) : (
+                    <div className={clickedTitle === navItem.name ? "navTitleClicked" : "navTitle"}>
                       <div
                         id={navItem.id}
                         role="button"
                         tabIndex={0}
-                        className={`navText directLink ${
-                          shouldBeUnderlined(navItem) ? "shouldBeUnderlined" : ""
-                        }`}
+                        className={`${
+                          clickedTitle === navItem.name ? "navText clicked" : "navText"
+                        } ${shouldBeUnderlined(navItem) ? "shouldBeUnderlined" : ""}`}
                         onKeyDown={onKeyPressHandler}
                         onClick={handleMenuClick}
                       >
                         {navItem.name}
                       </div>
-                    </NavLink>
-                  </div>
-                ) : (
-                  <div className={clickedTitle === navItem.name ? "navTitleClicked" : "navTitle"}>
-                    <div
-                      id={navItem.id}
-                      role="button"
-                      tabIndex={0}
-                      className={`${
-                        clickedTitle === navItem.name ? "navText clicked" : "navText"
-                      } ${shouldBeUnderlined(navItem) ? "shouldBeUnderlined" : ""}`}
-                      onKeyDown={onKeyPressHandler}
-                      onClick={handleMenuClick}
-                    >
-                      {navItem.name}
                     </div>
-                  </div>
-                )}
-              </LiSection>
-            );
-          })}
-          <LiSection className={`name-dropdown-li${isLoggedIn ? "" : " login-button"}`}>
-            {isLoggedIn ? (
-              <div
-                id="navbar-dropdown-name-container"
-                className={clickedTitle === displayName ? "navTitleClicked" : "navTitle"}
-              >
+                  )}
+                </LiSection>
+              );
+            })}
+            <LiSection className={`name-dropdown-li${isLoggedIn ? "" : " login-button"}`}>
+              {isLoggedIn ? (
                 <div
-                  id="navbar-dropdown-name"
-                  role="button"
-                  tabIndex={0}
-                  className={
-                    clickedTitle === displayName
-                      ? "navText displayName clicked"
-                      : "navText displayName"
-                  }
-                  onKeyDown={onKeyPressHandler}
-                  onClick={handleMenuClick}
+                  id="navbar-dropdown-name-container"
+                  className={clickedTitle === "User" ? "navTitleClicked" : "navTitle"}
                 >
-                  {displayName}
+                  <div
+                    id="navbar-dropdown-name"
+                    role="button"
+                    tabIndex={0}
+                    className={
+                      clickedTitle === "User"
+                        ? "navText displayName clicked"
+                        : "navText displayName"
+                    }
+                    onKeyDown={onKeyPressHandler}
+                    onClick={handleUserClick}
+                  >
+                    {displayName}
+                  </div>
                 </div>
-              </div>
-            ) : (
-              <StyledLoginLink
-                id="header-navbar-login-button"
-                to="/login"
-                state={{ redirectState: restorePath }}
-              >
-                Login
-              </StyledLoginLink>
-            )}
-          </LiSection>
-        </UlContainer>
-      </NavContainer>
-      <Dropdown ref={dropdownSelection} className={clickedTitle === "" ? "invisible" : ""}>
-        <NameDropdownContainer>
-          <div className="dropdownList">
-            {clickedTitle !== ""
-              ? HeaderSubLinks[clickedTitle]?.map((dropItem) => {
-                  const hasEveryPermission = checkPermissions(dropItem?.permissions);
-                  if (!hasEveryPermission) {
-                    return null;
-                  }
-
-                  if (dropItem.link) {
-                    return (
-                      <span className="dropdownItem" key={dropItem.id}>
-                        <Link
-                          target={
-                            dropItem.link.startsWith("https://") || dropItem.link.endsWith(".pdf")
-                              ? "_blank"
-                              : "_self"
-                          }
-                          id={dropItem.id}
-                          to={dropItem.link}
-                          className="dropdownItem"
-                          onClick={() => setClickedTitle("")}
-                        >
-                          {dropItem.name}
-                          {dropItem.text && <div className="dropdownItemText">{dropItem.text}</div>}
-                        </Link>
-                      </span>
-                    );
-                  }
-
-                  if (dropItem.onClick) {
-                    return (
-                      <Button
-                        id={dropItem.id}
-                        key={dropItem.id}
-                        className="dropdownItem dropdownItemButton"
-                        onClick={dropItem.onClick}
-                      >
-                        {dropItem.name}
-                      </Button>
-                    );
-                  }
-
-                  return null;
-                })
-              : null}
-          </div>
-        </NameDropdownContainer>
-      </Dropdown>
-      <APITokenDialog open={openAPITokenDialog} onClose={() => setOpenAPITokenDialog(false)} />
-      <UploaderToolDialog open={uploaderToolOpen} onClose={() => setUploaderToolOpen(false)} />
-    </Nav>
+              ) : (
+                <StyledLoginLink
+                  id="header-navbar-login-button"
+                  to="/login"
+                  state={{ redirectState: restorePath }}
+                >
+                  Login
+                </StyledLoginLink>
+              )}
+            </LiSection>
+          </UlContainer>
+        </NavContainer>
+        <NavbarDesktopDropdown
+          clickedTitle={clickedTitle}
+          onTitleClick={(title) => setClickedTitle(title)}
+          onItemClick={(item) => handleItemClick(item)}
+        />
+        <APITokenDialog open={openAPITokenDialog} onClose={() => setOpenAPITokenDialog(false)} />
+        <UploaderToolDialog open={uploaderToolOpen} onClose={() => setUploaderToolOpen(false)} />
+      </Nav>
+    </ClickAwayListener>
   );
 };
 
diff --git a/src/components/Header/components/NavbarDesktopDropdown.stories.tsx b/src/components/Header/components/NavbarDesktopDropdown.stories.tsx
new file mode 100644
index 000000000..dec8399e3
--- /dev/null
+++ b/src/components/Header/components/NavbarDesktopDropdown.stories.tsx
@@ -0,0 +1,109 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { Context as AuthContext, ContextState as AuthCtxState } from "../../Contexts/AuthContext";
+import NavbarDesktopDropdown from "./NavbarDesktopDropdown";
+import { Roles } from "../../../config/AuthRoles";
+import { HeaderLinks } from "../../../config/HeaderConfig";
+
+const HeaderTitles =
+  HeaderLinks?.filter((link) => "columns" in link).map((link) => link.name) || [];
+
+const meta: Meta<typeof NavbarDesktopDropdown> = {
+  title: "Navigation / Header / Dropdown",
+  component: NavbarDesktopDropdown,
+  parameters: {
+    layout: "fullscreen",
+    docs: {
+      story: {
+        inline: false,
+        iframeHeight: 300,
+      },
+    },
+  },
+  tags: ["autodocs"],
+} satisfies Meta<typeof NavbarDesktopDropdown>;
+
+type Story = StoryObj<typeof meta>;
+
+export const Empty: Story = {
+  name: "Without links",
+  args: {},
+  decorators: [
+    (Story) => (
+      <AuthContext.Provider value={{ isLoggedIn: false } as AuthCtxState}>
+        <Story />
+      </AuthContext.Provider>
+    ),
+  ],
+};
+
+export const DropdownExpanded: Story = {
+  name: "Expanded",
+  args: {
+    role: "Admin",
+    permissions: [
+      "dashboard:view",
+      "data_submission:create",
+      "user:manage",
+      "program:manage",
+      "study:manage",
+      "institution:manage",
+    ],
+    clickedTitle: HeaderTitles[0],
+  },
+  argTypes: {
+    role: {
+      name: "Role",
+      options: Roles,
+      control: {
+        type: "radio",
+      },
+    },
+    permissions: {
+      name: "Permissions",
+      options: [
+        "dashboard:view",
+        "data_submission:create",
+        "user:manage",
+        "program:manage",
+        "study:manage",
+        "institution:manage",
+      ],
+      control: {
+        type: "check",
+      },
+    },
+    HeaderLinks: {
+      control: false,
+      description: "The header sub-navigation links",
+    },
+    clickedTitle: {
+      name: "Clicked Title",
+      options: HeaderTitles,
+      control: {
+        type: "radio",
+      },
+      description: "The title of the navigation item clicked",
+    },
+  },
+  decorators: [
+    (Story, context) => (
+      <AuthContext.Provider
+        value={
+          {
+            isLoggedIn: true,
+            user: {
+              _id: "example-user",
+              firstName: "Example",
+              role: context.args.role,
+              permissions: context.args.permissions,
+            } as User,
+          } as AuthCtxState
+        }
+      >
+        <Story />
+      </AuthContext.Provider>
+    ),
+  ],
+};
+
+export default meta;
diff --git a/src/components/Header/components/NavbarDesktopDropdown.tsx b/src/components/Header/components/NavbarDesktopDropdown.tsx
new file mode 100644
index 000000000..d0b1d1f79
--- /dev/null
+++ b/src/components/Header/components/NavbarDesktopDropdown.tsx
@@ -0,0 +1,193 @@
+import { Button, styled } from "@mui/material";
+import Grid from "@mui/material/Unstable_Grid2";
+import { Link } from "react-router-dom";
+import { Status, useAuthContext } from "../../Contexts/AuthContext";
+import { ActionId, HeaderLinks } from "../../../config/HeaderConfig";
+import SuspenseLoader from "../../SuspenseLoader";
+import { hasPermission, Permissions } from "../../../config/AuthPermissions";
+
+const Dropdown = styled("div")({
+  left: 0,
+  right: 0,
+  width: "100%",
+  background: "#1F4671",
+  zIndex: 1100,
+  display: "block",
+  position: "absolute",
+  paddingTop: "35px",
+  paddingBottom: "12px",
+  "&.invisible": {
+    visibility: "hidden",
+  },
+});
+
+const StyledGridContainer = styled(Grid)({
+  margin: "0 auto",
+  textAlign: "left",
+  position: "relative",
+  maxWidth: "1400px",
+  padding: "0 16px",
+  "& .dropdownList": {
+    padding: 0,
+    marginTop: 0,
+    marginBottom: "45px",
+    listStyle: "none",
+  },
+  "& .gridItem": {
+    paddingLeft: "12px",
+    paddingRight: "12px",
+  },
+  "& .dropdownListItem": {
+    padding: 0,
+    margin: 0,
+    lineHeight: "22px",
+    height: "22px",
+  },
+  "& .dropdownItem": {
+    padding: "0",
+    textAlign: "left",
+    fontFamily: "'Poppins', sans-serif",
+    fontStyle: "normal",
+    fontWeight: 600,
+    fontSize: "20px",
+    lineHeight: "22px",
+    color: "#FFFFFF",
+    textDecoration: "none",
+    cursor: "pointer",
+    whiteSpace: "pre-line",
+    "&:hover": {
+      textDecoration: "underline",
+    },
+  },
+  "& a.dropdownItem": {
+    display: "inline-block",
+    padding: "0",
+  },
+  "& .dropdownItemButton": {
+    textTransform: "none",
+    paddingLeft: 0,
+    "&:hover": {
+      background: "transparent",
+    },
+  },
+  "#navbar-dropdown-item-name-logout": {
+    maxWidth: "200px",
+  },
+});
+
+type Props = {
+  clickedTitle: string;
+  onTitleClick?: (title: string) => void;
+  onItemClick?: (item: ActionId) => void;
+};
+
+const NavbarDesktopDropdown = ({ clickedTitle, onTitleClick, onItemClick }: Props) => {
+  const { user, status: AuthStatus } = useAuthContext();
+
+  const checkPermissions = (permissions: AuthPermissions[]) => {
+    if (!permissions?.length) {
+      return true; // No permissions required
+    }
+
+    return permissions.every((permission) => {
+      const [entityRaw, actionRaw] = permission.split(":", 2);
+
+      if (!entityRaw || !actionRaw) {
+        return false;
+      }
+
+      const entity = entityRaw as keyof Permissions;
+      const action = actionRaw as Permissions[keyof Permissions]["action"];
+
+      return hasPermission(user, entity, action, null, true);
+    });
+  };
+
+  if (AuthStatus === Status.LOADING) {
+    return <SuspenseLoader />;
+  }
+
+  const dropdownLinks: NavBarItem | undefined = HeaderLinks.find(
+    (link) => link.name === clickedTitle
+  );
+
+  // Get completely empty columns due to insufficient permissions
+  const emptyColumns =
+    dropdownLinks?.columns?.filter(
+      (c) => c.some((r) => r.permissions) && c.every((r) => !checkPermissions(r.permissions || []))
+    ) || [];
+
+  return (
+    <Dropdown className={clickedTitle === "" ? "invisible" : ""}>
+      <StyledGridContainer container>
+        {clickedTitle !== ""
+          ? dropdownLinks?.columns?.map((column, columnIdx) => {
+              if (!column?.length || emptyColumns.includes(column)) {
+                return null;
+              }
+
+              return (
+                // eslint-disable-next-line react/no-array-index-key
+                <Grid xs={3} key={`column-${columnIdx}`}>
+                  {column.map((dropItem, rowIdx) => {
+                    const hasEveryPermission = checkPermissions(dropItem?.permissions);
+                    if (!hasEveryPermission) {
+                      // eslint-disable-next-line react/no-array-index-key
+                      return <Grid xs={3} key={`empty-${columnIdx}-${rowIdx}`} />;
+                    }
+
+                    if (dropItem.link) {
+                      return (
+                        <Grid xs={3} key={dropItem.id} className="gridItem">
+                          <ul className="dropdownList">
+                            <li className="dropdownListItem">
+                              <Link
+                                target={
+                                  dropItem.link.startsWith("https://") ||
+                                  dropItem.link.endsWith(".pdf")
+                                    ? "_blank"
+                                    : "_self"
+                                }
+                                id={dropItem.id}
+                                to={dropItem.link}
+                                className="dropdownItem"
+                                onClick={() => onTitleClick("")}
+                              >
+                                {dropItem.name}
+                                {dropItem.text && (
+                                  <div className="dropdownItemText">{dropItem.text}</div>
+                                )}
+                              </Link>
+                            </li>
+                          </ul>
+                        </Grid>
+                      );
+                    }
+
+                    return (
+                      <Grid xs={3} key={dropItem.id} className="gridItem">
+                        <ul className="dropdownList">
+                          <li className="dropdownListItem">
+                            <Button
+                              id={dropItem.id}
+                              key={dropItem.id}
+                              className="dropdownItem dropdownItemButton"
+                              onClick={() => onItemClick?.(dropItem.actionId as ActionId)}
+                            >
+                              {dropItem.name}
+                            </Button>
+                          </li>
+                        </ul>
+                      </Grid>
+                    );
+                  })}
+                </Grid>
+              );
+            })
+          : null}
+      </StyledGridContainer>
+    </Dropdown>
+  );
+};
+
+export default NavbarDesktopDropdown;
diff --git a/src/components/Header/index.stories.tsx b/src/components/Header/index.stories.tsx
index c45b0883c..ad5109807 100644
--- a/src/components/Header/index.stories.tsx
+++ b/src/components/Header/index.stories.tsx
@@ -1,4 +1,6 @@
 import type { Meta, StoryObj } from "@storybook/react";
+import { screen, within } from "@storybook/testing-library";
+import { expect, userEvent, waitFor } from "@storybook/test";
 import { Context as AuthContext, ContextState as AuthCtxState } from "../Contexts/AuthContext";
 import Header from "./index";
 import { Roles } from "../../config/AuthRoles";
@@ -68,4 +70,71 @@ export const Authenticated: Story = {
   ],
 };
 
+export const DropdownExpanded: Story = {
+  name: "Expanded",
+  args: {
+    role: "Admin",
+    permissions: [
+      "dashboard:view",
+      "data_submission:create",
+      "user:manage",
+      "program:manage",
+      "study:manage",
+      "institution:manage",
+    ],
+  },
+  argTypes: {
+    role: {
+      name: "Role",
+      options: Roles,
+      control: {
+        type: "radio",
+      },
+    },
+    permissions: {
+      name: "Permissions",
+      options: [
+        "dashboard:view",
+        "data_submission:create",
+        "user:manage",
+        "program:manage",
+        "study:manage",
+        "institution:manage",
+      ],
+      control: {
+        type: "check",
+      },
+    },
+  },
+  play: async ({ canvasElement }) => {
+    const canvas = within(canvasElement);
+
+    const button = canvas.getByRole("button", { name: /Example/i });
+    await userEvent.click(button);
+
+    await waitFor(() => {
+      expect(screen.getByText("User Profile")).toBeInTheDocument();
+    });
+  },
+  decorators: [
+    (Story, context) => (
+      <AuthContext.Provider
+        value={
+          {
+            isLoggedIn: true,
+            user: {
+              _id: "example-user",
+              firstName: "Example",
+              role: context.args.role,
+              permissions: context.args.permissions,
+            } as User,
+          } as AuthCtxState
+        }
+      >
+        <Story />
+      </AuthContext.Provider>
+    ),
+  ],
+};
+
 export default meta;
diff --git a/src/config/HeaderConfig.ts b/src/config/HeaderConfig.ts
index 28cba5146..96beea584 100644
--- a/src/config/HeaderConfig.ts
+++ b/src/config/HeaderConfig.ts
@@ -18,7 +18,7 @@ export const headerData = {
   usaFlagSmallAltText: "usaFlagSmall",
 };
 
-export const HeaderLinks: NavBarItem[] = [
+export const HeaderLinks = [
   {
     name: "Back to CRDC",
     link: "https://datacommons.cancer.gov/submit",
@@ -42,12 +42,46 @@ export const HeaderLinks: NavBarItem[] = [
     link: "#",
     id: "navbar-dropdown-documentation",
     className: "navMobileItem clickable",
+    columns: [
+      [
+        {
+          name: "Submission Request Instructions",
+          link: "https://datacommons.cancer.gov/submission-request-instructions",
+          id: "submission-request-instructions",
+          className: "navMobileSubItem",
+        },
+      ],
+      [
+        {
+          name: "Data Submission\nInstructions",
+          link: DataSubmissionInstructionsLink,
+          id: "data-submission-instructions",
+          className: "navMobileSubItem",
+        },
+      ],
+      [
+        {
+          name: "API Instructions",
+          link: ApiInstructions,
+          id: "api-instructions",
+          className: "navMobileSubItem",
+        },
+      ],
+    ],
   },
   {
     name: "Model Navigator",
     link: "#",
     id: "navbar-dropdown-model-navigator",
     className: "navMobileItem clickable",
+    columns: DataCommons.map((dc) => [
+      {
+        id: `model-navigator-${dc.name}`,
+        name: `${dc.displayName}${dc.displayName.indexOf("Model") === -1 ? " Model" : ""}`,
+        link: `/model-navigator/${dc.displayName}/latest`,
+        className: "navMobileSubItem",
+      },
+    ]),
   },
   {
     name: "Operation Dashboard",
@@ -56,34 +90,90 @@ export const HeaderLinks: NavBarItem[] = [
     className: "navMobileItem",
     permissions: ["dashboard:view"],
   },
-];
 
-export const HeaderSubLinks: Record<string, NavBarSubItem[]> = {
-  "Model Navigator": DataCommons.map((dc) => ({
-    id: `model-navigator-${dc.name}`,
-    name: `${dc.displayName}${dc.displayName.indexOf("Model") === -1 ? " Model" : ""}`,
-    link: `/model-navigator/${dc.displayName}/latest`,
-    className: "navMobileSubItem",
-  })),
+  {
+    name: "User",
+    id: "navbar-dropdown-user",
+    className: "navMobileItem clickable",
+    columns: [
+      [
+        {
+          name: "User Profile",
+          link: "/profile/:userId",
+          id: "navbar-dropdown-item-user-profile",
+          className: "navMobileSubItem",
+        },
+      ],
+      [
+        {
+          name: "Uploader CLI Tool",
+          id: "navbar-dropdown-item-uploader-tool",
+          className: "navMobileSubItem action",
+          actionId: "openCLIToolDialog",
+        },
+        {
+          name: "API Token",
+          id: "navbar-dropdown-item-api-token",
+          className: "navMobileSubItem action",
+          permissions: ["data_submission:create"],
+          actionId: "openAPITokenDialog",
+        },
+      ],
+      [
+        {
+          name: "Manage Studies",
+          link: "/studies",
+          id: "navbar-dropdown-item-studies-manage",
+          className: "navMobileSubItem",
+          permissions: ["study:manage"],
+        },
+        {
+          name: "Manage Programs",
+          link: "/programs",
+          id: "navbar-dropdown-item-program-manage",
+          className: "navMobileSubItem",
+          permissions: ["program:manage"],
+        },
+        {
+          name: "Manage Institutions",
+          link: "/institutions",
+          id: "navbar-dropdown-item-institution-manage",
+          className: "navMobileSubItem",
+          permissions: ["institution:manage"],
+        },
+        {
+          name: "Manage Users",
+          link: "/users",
+          id: "navbar-dropdown-item-user-manage",
+          className: "navMobileSubItem",
+          permissions: ["user:manage"],
+        },
+      ],
+      [
+        {
+          name: "Logout",
+          id: "navbar-dropdown-item-logout",
+          className: "navMobileSubItem action",
+          actionId: "logout",
+        },
+      ],
+    ],
+  },
+] as const satisfies NavBarItem[];
 
-  Documentation: [
-    {
-      name: "Submission Request Instructions",
-      link: "https://datacommons.cancer.gov/submission-request-instructions",
-      id: "submission-request-instructions",
-      className: "navMobileSubItem",
-    },
-    {
-      name: "Data Submission Instructions",
-      link: DataSubmissionInstructionsLink,
-      id: "data-submission-instructions",
-      className: "navMobileSubItem",
-    },
-    {
-      name: "API Instructions",
-      link: ApiInstructions,
-      id: "api-instructions",
-      className: "navMobileSubItem",
-    },
-  ],
-};
+/**
+ * Type defining the actionId's defined within HeaderLinks
+ *
+ * @see HeaderLinks
+ */
+export type ActionId = Extract<
+  Extract<(typeof HeaderLinks)[number], { columns: NavBarSubItem[][] }>["columns"][number][number],
+  { actionId: string }
+>["actionId"];
+
+/**
+ * Provides structure for the action handlers
+ *
+ * @see HeaderLinks
+ */
+export type ActionHandlers = { [key in ActionId]?: (...args) => unknown };
diff --git a/src/types/Navigation.d.ts b/src/types/Navigation.d.ts
index de400c53f..2b850bb73 100644
--- a/src/types/Navigation.d.ts
+++ b/src/types/Navigation.d.ts
@@ -12,7 +12,7 @@ type NavBarItem = {
   /**
    * The link to navigate to when the Navigation Item is clicked
    */
-  link: string;
+  link?: string;
   /**
    * The class name of the Navigation Item
    *
@@ -29,6 +29,10 @@ type NavBarItem = {
    *   otherwise it will be hidden
    */
   permissions?: AuthPermissions[];
+  /**
+   * Defines the columns of sub-navigation items
+   */
+  columns?: NavBarSubItem[][];
 };
 
 type NavBarSubItem = {
@@ -63,12 +67,6 @@ type NavBarSubItem = {
    * @note Only works if the className is `navMobileSubTitle`
    */
   text?: string;
-  /**
-   * The onClick function to be called when the Navigation Sub Item is clicked
-   *
-   * @note Only works if the className is `navMobileSubItem action`
-   */
-  onClick?: () => void;
   /**
    * Defines a list of permissions necessary for the Navigation Sub Item to be shown
    *
@@ -77,6 +75,10 @@ type NavBarSubItem = {
    *   otherwise it will be hidden
    */
   permissions?: AuthPermissions[];
+  /**
+   * A unique identifier to define an action to take place when thhe item is clicked
+   */
+  actionId?: string;
 };
 
 type FooterConfiguration = {


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR enhances the header navigation by introducing a 4-column grid layout, refactors configuration to use inline column definitions, and updates both desktop and mobile components (including Storybook stories) to leverage the new design and action handlers.

- Refactor `HeaderConfig.ts` to embed sub-navigation as `columns` and introduce `ActionId`/`ActionHandlers`.
- Implement a new `NavbarDesktopDropdown` component and update desktop/mobile headers to delegate dropdown rendering and actions.
- Add and update Storybook stories with interactive “Expanded” scenarios.

### Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

<details>
<summary>Show a summary per file</summary>

| File                                                        | Description                                                   |
|-------------------------------------------------------------|---------------------------------------------------------------|
| src/config/HeaderConfig.ts                                  | Migrated to column-based nav definitions and added type helpers |
| src/components/Header/index.stories.tsx                     | Added an “Expanded” story with play function for dropdown     |
| src/components/Header/components/NavbarDesktopDropdown.tsx  | New dropdown component using grid, permissions, and actions   |
| src/components/Header/components/NavbarDesktop.tsx          | Swapped out old dropdown logic for new component and hooks    |
| src/components/Header/components/HeaderTabletAndMobile.tsx  | Updated mobile header to use action handlers and new config   |
| src/components/Header/components/HeaderDesktop.tsx          | Minor styling tweak (paddingRight)                            |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/components/Header/index.stories.tsx:116**
* In Storybook play functions, prefer using `canvas.getByText` instead of `screen.getByText` to scope queries to the story's DOM and avoid false positives.
```
expect(screen.getByText("User Profile")).toBeInTheDocument();
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T13:31:48Z
user: login: amattu2
body: This looks great and works super well (especially the storybook changes). Nice job!

I left one suggestion for memoizing a computation that's performed every render, but I'll leave it up to you whether to address it or not. Same goes for the Copilot suggestions, take a look and evaluate if they're legitimate things or can be dismissed.


state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T17:48:30Z
user: login: Alejandro-Vega
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T18:18:51Z
user: login: Alejandro-Vega
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T18:45:19Z
user: login: Alejandro-Vega
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T18:46:08Z
user: login: Alejandro-Vega
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T18:46:51Z
user: login: amattu2
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T18:55:03Z

Review comments:
diff_hunk: @@ -0,0 +1,191 @@
+import { Button, styled } from "@mui/material";
+import Grid from "@mui/material/Unstable_Grid2";
+import { Link } from "react-router-dom";
+import { Status, useAuthContext } from "../../Contexts/AuthContext";
+import { ActionId, HeaderLinks } from "../../../config/HeaderConfig";
+import SuspenseLoader from "../../SuspenseLoader";
+import { hasPermission, Permissions } from "../../../config/AuthPermissions";
+
+const Dropdown = styled("div")({
+  left: 0,
+  right: 0,
+  width: "100%",
+  background: "#1F4671",
+  zIndex: 1100,
+  display: "block",
+  position: "absolute",
+  paddingTop: "35px",
+  paddingBottom: "12px",
+  "&.invisible": {
+    visibility: "hidden",
+  },
+});
+
+const StyledGridContainer = styled(Grid)({
+  margin: "0 auto",
+  textAlign: "left",
+  position: "relative",
+  maxWidth: "1400px",
+  padding: "0 16px",
+  "& .dropdownList": {
+    padding: 0,
+    marginTop: 0,
+    marginBottom: "45px",
+    listStyle: "none",
+  },
+  "& .gridItem": {
+    paddingLeft: "12px",
+    paddingRight: "12px",
+  },
+  "& .dropdownListItem": {
+    padding: 0,
+    margin: 0,
+    lineHeight: "22px",
+    height: "22px",
+  },
+  "& .dropdownItem": {
+    padding: "0",
+    textAlign: "left",
+    fontFamily: "'Poppins', sans-serif",
+    fontStyle: "normal",
+    fontWeight: 600,
+    fontSize: "20px",
+    lineHeight: "22px",
+    color: "#FFFFFF",
+    textDecoration: "none",
+    cursor: "pointer",
+    whiteSpace: "pre-line",
+    "&:hover": {
+      textDecoration: "underline",
+    },
+  },
+  "& a.dropdownItem": {
+    display: "inline-block",
+    padding: "0",
+  },
+  "& .dropdownItemButton": {
+    textTransform: "none",
+    paddingLeft: 0,
+    "&:hover": {
+      background: "transparent",
+    },
+  },
+  "#navbar-dropdown-item-name-logout": {
+    maxWidth: "200px",
+  },
+});
+
+type Props = {
+  clickedTitle: string;
+  onTitleClick?: (title: string) => void;
+  onItemClick?: (item: ActionId) => void;
+};
+
+const NavbarDesktopDropdown = ({ clickedTitle, onTitleClick, onItemClick }: Props) => {
+  const { user, status: AuthStatus } = useAuthContext();
+
+  const checkPermissions = (permissions: AuthPermissions[]) => {
+    if (!permissions?.length) {
+      return true; // No permissions required
+    }
+
+    return permissions.every((permission) => {
+      const [entityRaw, actionRaw] = permission.split(":", 2);
+
+      if (!entityRaw || !actionRaw) {
+        return false;
+      }
+
+      const entity = entityRaw as keyof Permissions;
+      const action = actionRaw as Permissions[keyof Permissions]["action"];
+
+      return hasPermission(user, entity, action, null, true);
+    });
+  };
+
+  if (AuthStatus === Status.LOADING) {
+    return <SuspenseLoader />;
+  }
+
+  const dropdownLinks: NavBarItem = HeaderLinks.find((link) => link.name === clickedTitle);
+
+  // Get completely empty columns due to insufficient permissions
+  const emptyColumns =
+    dropdownLinks?.columns?.filter(
path: src/components/Header/components/NavbarDesktopDropdown.tsx
user: login: Copilot
body: `find` can return `undefined`, but `dropdownLinks` is typed as `NavBarItem`. Consider typing it as `NavBarItem | undefined` and adding a guard to handle the undefined case to avoid runtime errors.
```suggestion
  const dropdownLinks: NavBarItem | undefined = HeaderLinks.find((link) => link.name === clickedTitle);

  if (!dropdownLinks) {
    return null; // Or handle the undefined case appropriately
  }

  // Get completely empty columns due to insufficient permissions
  const emptyColumns =
    dropdownLinks.columns?.filter(
```
created_at: 2025-05-13T13:31:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -392,10 +315,10 @@ const NavBar = () => {
     if (item.className === "navMobileItem") {
       return correctPath === item.link;
     }
-    if (HeaderSubLinks[linkName] === undefined) {
+    if (HeaderLinks[linkName] === undefined) {
       return false;
     }
-    const linkNames = Object.values(HeaderSubLinks[linkName]).map((e: NavBarSubItem) => e.link);
+    const linkNames = Object.values(HeaderLinks[linkName]).map((e: NavBarSubItem) => e.link);
path: src/components/Header/components/NavbarDesktop.tsx
user: login: Copilot
body: `HeaderLinks` is an array, so indexing by a string `linkName` always yields `undefined`. This breaks the active-state check. Use `HeaderLinks.find(link => link.name === linkName)` instead of array indexing.
created_at: 2025-05-13T13:31:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,16 +1,18 @@
-import React, { HTMLProps, useEffect, useState } from "react";
+import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
+import { flatMap } from "lodash";
path: src/components/Header/components/HeaderTabletAndMobile.tsx
user: login: Copilot
body: [nitpick] The lodash `flatMap` import may be unnecessary or could be replaced with the native `Array.prototype.flatMap` to reduce bundle size. Please remove the unused import or switch to the native method.
```suggestion
// Removed unused import for flatMap from lodash
```
created_at: 2025-05-13T13:31:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -439,146 +362,102 @@ const NavBar = () => {
     setRestorePath(location?.pathname);
   }, [location]);
 
+  const headerLinksWithoutUser = HeaderLinks.filter((headerLinks) => headerLinks.name !== "User");
path: src/components/Header/components/NavbarDesktop.tsx
user: login: amattu2
body: Should we need to memoise this? 
created_at: 2025-05-13T17:36:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -439,146 +362,102 @@ const NavBar = () => {
     setRestorePath(location?.pathname);
   }, [location]);
 
+  const headerLinksWithoutUser = HeaderLinks.filter((headerLinks) => headerLinks.name !== "User");
path: src/components/Header/components/NavbarDesktop.tsx
user: login: Alejandro-Vega
body: Added in [ec0de2a](https://github.com/CBIIT/crdc-datahub-ui/pull/710/commits/ec0de2aa175d6ff1d5673b44f132f6db982b3c66).
created_at: 2025-05-13T18:18:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,191 @@
+import { Button, styled } from "@mui/material";
+import Grid from "@mui/material/Unstable_Grid2";
+import { Link } from "react-router-dom";
+import { Status, useAuthContext } from "../../Contexts/AuthContext";
+import { ActionId, HeaderLinks } from "../../../config/HeaderConfig";
+import SuspenseLoader from "../../SuspenseLoader";
+import { hasPermission, Permissions } from "../../../config/AuthPermissions";
+
+const Dropdown = styled("div")({
+  left: 0,
+  right: 0,
+  width: "100%",
+  background: "#1F4671",
+  zIndex: 1100,
+  display: "block",
+  position: "absolute",
+  paddingTop: "35px",
+  paddingBottom: "12px",
+  "&.invisible": {
+    visibility: "hidden",
+  },
+});
+
+const StyledGridContainer = styled(Grid)({
+  margin: "0 auto",
+  textAlign: "left",
+  position: "relative",
+  maxWidth: "1400px",
+  padding: "0 16px",
+  "& .dropdownList": {
+    padding: 0,
+    marginTop: 0,
+    marginBottom: "45px",
+    listStyle: "none",
+  },
+  "& .gridItem": {
+    paddingLeft: "12px",
+    paddingRight: "12px",
+  },
+  "& .dropdownListItem": {
+    padding: 0,
+    margin: 0,
+    lineHeight: "22px",
+    height: "22px",
+  },
+  "& .dropdownItem": {
+    padding: "0",
+    textAlign: "left",
+    fontFamily: "'Poppins', sans-serif",
+    fontStyle: "normal",
+    fontWeight: 600,
+    fontSize: "20px",
+    lineHeight: "22px",
+    color: "#FFFFFF",
+    textDecoration: "none",
+    cursor: "pointer",
+    whiteSpace: "pre-line",
+    "&:hover": {
+      textDecoration: "underline",
+    },
+  },
+  "& a.dropdownItem": {
+    display: "inline-block",
+    padding: "0",
+  },
+  "& .dropdownItemButton": {
+    textTransform: "none",
+    paddingLeft: 0,
+    "&:hover": {
+      background: "transparent",
+    },
+  },
+  "#navbar-dropdown-item-name-logout": {
+    maxWidth: "200px",
+  },
+});
+
+type Props = {
+  clickedTitle: string;
+  onTitleClick?: (title: string) => void;
+  onItemClick?: (item: ActionId) => void;
+};
+
+const NavbarDesktopDropdown = ({ clickedTitle, onTitleClick, onItemClick }: Props) => {
+  const { user, status: AuthStatus } = useAuthContext();
+
+  const checkPermissions = (permissions: AuthPermissions[]) => {
+    if (!permissions?.length) {
+      return true; // No permissions required
+    }
+
+    return permissions.every((permission) => {
+      const [entityRaw, actionRaw] = permission.split(":", 2);
+
+      if (!entityRaw || !actionRaw) {
+        return false;
+      }
+
+      const entity = entityRaw as keyof Permissions;
+      const action = actionRaw as Permissions[keyof Permissions]["action"];
+
+      return hasPermission(user, entity, action, null, true);
+    });
+  };
+
+  if (AuthStatus === Status.LOADING) {
+    return <SuspenseLoader />;
+  }
+
+  const dropdownLinks: NavBarItem = HeaderLinks.find((link) => link.name === clickedTitle);
+
+  // Get completely empty columns due to insufficient permissions
+  const emptyColumns =
+    dropdownLinks?.columns?.filter(
path: src/components/Header/components/NavbarDesktopDropdown.tsx
user: login: Alejandro-Vega
body: Usage already considers possibly undefined so it wouldn't break anything, but added the possible "undefined" type as suggested in [2e1b72b](https://github.com/CBIIT/crdc-datahub-ui/pull/710/commits/2e1b72b019504eb3cfaf584d01db4e68329bf620).
created_at: 2025-05-13T18:45:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -392,10 +315,10 @@ const NavBar = () => {
     if (item.className === "navMobileItem") {
       return correctPath === item.link;
     }
-    if (HeaderSubLinks[linkName] === undefined) {
+    if (HeaderLinks[linkName] === undefined) {
       return false;
     }
-    const linkNames = Object.values(HeaderSubLinks[linkName]).map((e: NavBarSubItem) => e.link);
+    const linkNames = Object.values(HeaderLinks[linkName]).map((e: NavBarSubItem) => e.link);
path: src/components/Header/components/NavbarDesktop.tsx
user: login: Alejandro-Vega
body: Good robot. This is a genuine bug, fixed in [9b1ca71](https://github.com/CBIIT/crdc-datahub-ui/pull/710/commits/9b1ca71883ccbec50cc9e2fd1122081df0b5b8f3).
created_at: 2025-05-13T18:46:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,16 +1,18 @@
-import React, { HTMLProps, useEffect, useState } from "react";
+import React, { HTMLProps, useEffect, useMemo, useState } from "react";
 import { NavLink, Link, useNavigate, useLocation } from "react-router-dom";
+import { flatMap } from "lodash";
path: src/components/Header/components/HeaderTabletAndMobile.tsx
user: login: Alejandro-Vega
body: We already have lodash, so might as well use it. Leaving as-is.
created_at: 2025-05-13T18:46:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: coveralls
created_at: 2025-05-13T12:56:39Z
body: ## Pull Request Test Coverage Report for [Build 15004306216](https://coveralls.io/builds/73651507)


### Details

* **39** of **102**   **(38.24%)**  changed or added relevant lines in **4** files are covered.
* **2** unchanged lines in **1** file lost coverage.
* Overall coverage decreased (**-0.2%**) to **62.252%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/components/Header/components/NavbarDesktop.tsx](https://coveralls.io/builds/73651507/source?filename=src%2Fcomponents%2FHeader%2Fcomponents%2FNavbarDesktop.tsx#L276) | 17 | 31 | 54.84%
| [src/components/Header/components/NavbarDesktopDropdown.tsx](https://coveralls.io/builds/73651507/source?filename=src%2Fcomponents%2FHeader%2Fcomponents%2FNavbarDesktopDropdown.tsx#L88) | 10 | 32 | 31.25%
| [src/components/Header/components/HeaderTabletAndMobile.tsx](https://coveralls.io/builds/73651507/source?filename=src%2Fcomponents%2FHeader%2Fcomponents%2FHeaderTabletAndMobile.tsx#L147) | 10 | 37 | 27.03%
<!-- | **Total:** | **39** | **102** | **38.24%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/components/Header/components/NavbarDesktop.tsx](https://coveralls.io/builds/73651507/source?filename=src%2Fcomponents%2FHeader%2Fcomponents%2FNavbarDesktop.tsx#L272) | 2 | 62.29% |
<!-- | **Total:** | **2** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/73651507/badge)](https://coveralls.io/builds/73651507) |
| :-- | --: |
| Change from base [Build 15003193877](https://coveralls.io/builds/73650481): |  -0.2% |
| Covered Lines: | 4800 |
| Relevant Lines: | 7301 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE
user: login: amattu2
created_at: 2025-05-13T18:55:33Z
body: Everything is resolved, merging this.
author_association: CONTRIBUTOR