diff --git a/frontend/components/Breadcrumbs.tsx b/frontend/components/Breadcrumbs.tsx
index edf4e7cc6..7737ce2f9 100644
--- a/frontend/components/Breadcrumbs.tsx
+++ b/frontend/components/Breadcrumbs.tsx
@@ -13,6 +13,11 @@ import BreadcrumbsTranslations, {
Breadcrumbs as BreadcrumbsTranslationType,
} from "/translations/breadcrumbs"
+const BreadcrumbContainer = styled("nav")`
+ margin: 0 auto;
+ width: 100vw;
+ max-width: 1920px;
+`
const BreadcrumbList = styled("ul")`
list-style: none;
overflow: hidden;
@@ -131,7 +136,7 @@ export function Breadcrumbs() {
}
return (
-
+
)
}
diff --git a/frontend/components/NewLayout/Header/Header.tsx b/frontend/components/NewLayout/Header/Header.tsx
index 4ca63e73d..e1e7af4f0 100644
--- a/frontend/components/NewLayout/Header/Header.tsx
+++ b/frontend/components/NewLayout/Header/Header.tsx
@@ -2,7 +2,7 @@ import { AppBar, Slide, Toolbar, useScrollTrigger } from "@mui/material"
import { styled } from "@mui/material/styles"
import HyLogoIcon from "../Icons/HyLogo"
-import { DesktopNavigationMenu, MobileNavigationMenu } from "../Navigation"
+import { NavigationMenu } from "../Navigation"
import LanguageSwitch from "./LanguageSwitch"
import MoocLogo from "./MoocLogo"
import { useTranslator } from "/hooks/useTranslator"
@@ -139,8 +139,7 @@ function Header() {
-
-
+
diff --git a/frontend/components/NewLayout/Icons/CaretLeft.tsx b/frontend/components/NewLayout/Icons/CaretLeft.tsx
new file mode 100644
index 000000000..dc8f48b78
--- /dev/null
+++ b/frontend/components/NewLayout/Icons/CaretLeft.tsx
@@ -0,0 +1,10 @@
+import { createSvgIcon } from "@mui/material/utils"
+
+const CaretLeftIcon = createSvgIcon(
+ ,
+ "CaretLeft",
+)
+
+export default CaretLeftIcon
diff --git a/frontend/components/NewLayout/Navigation/BottomNavigation.tsx b/frontend/components/NewLayout/Navigation/BottomNavigation.tsx
deleted file mode 100644
index 83a0ce7d1..000000000
--- a/frontend/components/NewLayout/Navigation/BottomNavigation.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { AppBar, AppBarProps, Toolbar } from "@mui/material"
-import { styled } from "@mui/material/styles"
-
-import { NavigationLinks } from "/components/NewLayout/Navigation/NavigationLinks"
-
-const BottomNavigationContainer = styled((props: AppBarProps) => (
-
-))(
- ({ theme }) => `
- ${theme.breakpoints.up("md")} {
- display: none;
- }
- top: auto;
- margin: auto;
- bottom: 0;
-`,
-)
-
-export const BottomNavigation = () => {
- return (
-
-
-
-
-
- )
-}
diff --git a/frontend/components/NewLayout/Navigation/DesktopNavigationMenu.tsx b/frontend/components/NewLayout/Navigation/DesktopNavigationMenu.tsx
index c57c64880..af7ac2e1a 100644
--- a/frontend/components/NewLayout/Navigation/DesktopNavigationMenu.tsx
+++ b/frontend/components/NewLayout/Navigation/DesktopNavigationMenu.tsx
@@ -14,6 +14,7 @@ import {
} from "@mui/material"
import { styled, Theme } from "@mui/material/styles"
+import { NavigationMenuItem } from "."
import { NavigationLinks } from "./NavigationLinks"
import { useLoginStateContext } from "/contexts/LoginStateContext"
import { useTranslator } from "/hooks/useTranslator"
@@ -124,10 +125,14 @@ const UserOptionsMenu = () => {
)
}
-const DesktopNavigationMenu = () => {
+interface DesktopNavigationMenuProps {
+ items: Array
+}
+
+const DesktopNavigationMenu = ({ items }: DesktopNavigationMenuProps) => {
return (
<>
-
+
diff --git a/frontend/components/NewLayout/Navigation/MobileNavigationMenu.tsx b/frontend/components/NewLayout/Navigation/MobileNavigationMenu.tsx
index 75c7d619a..a57c20299 100644
--- a/frontend/components/NewLayout/Navigation/MobileNavigationMenu.tsx
+++ b/frontend/components/NewLayout/Navigation/MobileNavigationMenu.tsx
@@ -1,38 +1,27 @@
-import React, { useState } from "react"
+import React, { createContext, useMemo, useState } from "react"
import { useRouter } from "next/router"
-/*import { useApolloClient } from "@apollo/client"
-import ChalkboardTeacher from "@fortawesome/fontawesome-free/svgs/solid/chalkboard-user.svg?icon"
-import Dashboard from "@fortawesome/fontawesome-free/svgs/solid/gauge-high.svg?icon"
-import ListIcon from "@fortawesome/fontawesome-free/svgs/solid/list.svg?icon"
-import SignOut from "@fortawesome/fontawesome-free/svgs/solid/right-from-bracket.svg?icon"
-import SignIn from "@fortawesome/fontawesome-free/svgs/solid/right-to-bracket.svg?icon"
-import Register from "@fortawesome/fontawesome-free/svgs/solid/user-plus.svg?icon"
-import User from "@fortawesome/fontawesome-free/svgs/solid/user.svg?icon"*/
import {
Button,
- Collapse,
Drawer,
EnhancedListItemButton,
+ ExtendList,
IconButton,
List,
- ListItem,
ListItemButton,
- ListItemIcon,
ListItemText,
- SvgIcon,
+ ListTypeMap,
} from "@mui/material"
-import { styled } from "@mui/material/styles"
+import { css, styled } from "@mui/material/styles"
import { useEventCallback } from "@mui/material/utils"
-import CaretDownIcon from "../Icons/CaretDown"
-import CaretUpIcon from "../Icons/CaretUp"
+import { isSubmenuItem, NavigationMenuItem } from "."
+import CaretLeftIcon from "../Icons/CaretLeft"
+import CaretRightIcon from "../Icons/CaretRight"
import HamburgerIcon from "../Icons/Hamburger"
import RemoveIcon from "../Icons/Remove"
-import { useLoginStateContext } from "/contexts/LoginStateContext"
import { useTranslator } from "/hooks/useTranslator"
-// import { signOut } from "/lib/authentication"
import CommonTranslations from "/translations/common"
const MobileMenuContainer = styled("div")(
@@ -58,32 +47,175 @@ const MobileMenu = styled(Drawer)(
`,
) as typeof Drawer
+const MobileMenuButton = styled(Button)(
+ ({ theme }) => `
+ align-items: center;
+ display: inline-flex;
+ height: 100%;
+ justify-content: center;
+ width: 44px;
+ background-color: transparent;
+ border: 0;
+ padding: 0;
+ margin: 0;
+ text-transform: none;
+
+ &[aria-expanded=true] {
+ font-size: 1rem;
+ line-height: 18px;
+ font-weight: 700;
+ align-items: center;
+ background-color: transparent;
+ border: none;
+ color: ${theme.palette.common.brand.main};
+ display: inline-flex;
+ height: auto;
+ letter-spacing: -0.3px;
+ margin-left: auto;
+ margin-right: -16px;
+ padding: 16px;
+ position: fixed;
+ right: 16px;
+ top: 12px;
+ z-index: 1350;
+ width: auto;
+
+ svg {
+ margin-left: 8px;
+ fill: ${theme.palette.common.brand.main};
+ }
+ &:hover {
+ background-color: transparent;
+ cursor: pointer;
+ }
+ }
+`,
+) as typeof Button
+
const MobileMenuHeader = styled("section")`
display: grid;
grid-template-columns: 1fr auto;
padding: 12px 80px 12px 16px;
gap: 16px;
- min-height: 68px;
align-items: center;
`
+interface MobileMenuLevelListProps {
+ isOpen?: boolean
+ level?: number
+}
-const MobileMenuList = styled(List)`
- position: relative;
-` as typeof List
+const MobileMenuLevelList = styled(List, {
+ shouldForwardProp: (prop) => prop !== "isOpen" && prop !== "level",
+})(
+ ({ theme, isOpen, level = 0 }) => `
+ background-color: ${theme.palette.common.grayscale.white};
+ display: block;
+ height: 80vh;
+ left: 0;
+ position: ${level > 0 ? "absolute" : "relative"};
+ top: 0;
+ width: 100%;
+ z-index: ${1300 + level};
+ ${
+ level > 0
+ ? css`
+ transform: translateX(100%);
+ transition:
+ transform 0.3s ease-in-out,
+ visibility 0.3s ease-in-out,
+ opacity 0.3s ease-in-out,
+ max-height 0.3s ease-in-out;
+ `.styles
+ : "transform: none;"
+ }
+ visibility: hidden !important;
+ max-height: 100vh;
-const MobileMenuListItem = styled(ListItem)(
- ({ theme }) => `
+ ${
+ isOpen &&
+ css`
+ height: 80vh;
+ transform: none;
+ visibility: visible !important;
+ padding-bottom: 96px;
+ max-height: 100vh;
+ `.styles
+ }
+`,
+) as ExtendList>
+
+interface MobileMenuListItemProps {
+ isHidden?: boolean
+ isActive?: boolean
+ isSubmenu?: boolean
+}
+
+const MobileMenuListItem = styled("li", {
+ shouldForwardProp: (prop) => prop !== "isHidden" && prop !== "isActive",
+})(
+ ({ theme, isHidden, isActive, isSubmenu }) => `
margin: 0 0 4px;
display: flex;
padding: 0;
+ background-color: ${theme.palette.common.grayscale.white};
+ transition: transform 0.3s ease-in-out, visibility 0.3s ease-in-out, opacity 0.3s ease-in-out;
+ ${isHidden ? `visibility: hidden !important;` : "visibility: visible;"};
+
.MuiListItemText-primary {
+ svg {
+ color: ${
+ isActive
+ ? theme.palette.common.grayscale.black
+ : theme.palette.common.brand.main
+ };
+ }
font-size: 1.3125rem;
line-height: 28px;
font-weight: 700;
- color: ${theme.palette.common.brand.light};
+ color: ${
+ isActive
+ ? theme.palette.common.grayscale.black
+ : theme.palette.common.brand.light
+ };
letter-spacing: -0.42px;
padding: 16px 0 16px 16px;
+
+ ${
+ isSubmenu
+ ? css`
+ font-size: 1rem;
+ line-height: 20px;
+ font-weight: 600;
+ align-items: center;
+ display: flex;
+ letter-spacing: -0.5px;
+ text-decoration: none;
+ width: 100%;
+ padding: 12px 16px;
+ color: ${isActive
+ ? theme.palette.common.grayscale.black
+ : theme.palette.common.brand.main};
+ `.styles
+ : ""
+ }
+ }
+
+ ${
+ isActive
+ ? css`
+ position: relative;
+ &:before {
+ border-left: 3px solid ${theme.palette.common.grayscale.black};
+ content: "";
+ height: 75%;
+ left: 10px;
+ position: absolute;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ }
+ `.styles
+ : ""
}
&.Mui-selected {
@@ -95,76 +227,158 @@ const MobileMenuListItem = styled(ListItem)(
`,
)
-const MobileMenuListItemText = styled(ListItemText)(
- ({ theme }) => `
- /*.MuiListItemText-primary {
- font-size: 1.3125rem;
- font-weight: 700;
- color: ${theme.palette.common.brand.light};
- letter-spacing: -0.42px;
- padding: 16px 0 16px 16px;
- }*/
-`,
-) as typeof ListItemText
-
const MobileMenuListItemButton = styled(ListItemButton)`
+ padding-top: 0;
+ padding-bottom: 0;
&:hover {
background: transparent;
}
` as EnhancedListItemButton
-const MobileMenuListItemIcon = styled(ListItemIcon)`
- /* */
+const MobileMenuListItemSubmenuButton = styled(IconButton)(
+ ({ theme }) => `
+ &:before {
+ background-color: ${theme.palette.common.grayscale.medium};
+ width: 1px;
+ height: 32px;
+ content: '';
+ display: inline-block;
+ transform: translateY(-50%);
+ top: 50%;
+ left: 0;
+ position: absolute;
+ }
+`,
+)
+
+const MobileMenuBreadcrumbs = styled("div")`
+ min-height: 44px;
+`
+
+const MobileMenuBreadcrumbButton = styled(Button)(
+ ({ theme }) => `
+ text-transform: none;
+ font-size: .9375rem;
+ line-height: 22px;
+ color: ${theme.palette.common.brand.light};
+ align-items: center;
+ background-color: transparent;
+ border: 0 none;
+ cursor: pointer;
+ display: inline-flex;
+ text-decoration: none;
+ padding: 11px 0;
+ text-align: left;
+
+ &:hover {
+ background-color: transparent;
+ }
+
+ svg {
+ margin-right: 8px;
+ fill: ${theme.palette.common.grayscale.black};
+ }
+`,
+) as typeof Button
+
+const MobileMenuContentContainer = styled("section")`
+ overflow: hidden;
+ position: relative;
+ min-height: calc(100vh - 126px);
`
interface MobileMenuItemProps {
- Icon?: typeof SvgIcon
- href?: string
- text: string
- collapsable?: boolean
+ item: NavigationMenuItem
+ level?: number
}
-const MobileMenuItem = ({
- Icon,
- href,
- text,
- collapsable,
- children,
-}: React.PropsWithChildren) => {
+const MobileMenuItem = ({ item, level = 0 }: MobileMenuItemProps) => {
+ const { currentLevel, setCurrentLevel, setBreadcrumbs } =
+ useMobileMenuContext()
const { pathname } = useRouter()
- const [open, setOpen] = useState(false)
+ const hasSubmenu = isSubmenuItem(item)
+ const { href, label } = item
const onClick = useEventCallback(() => {
- if (collapsable) {
- setOpen((prevOpen) => !prevOpen)
- }
+ setBreadcrumbs((prev) => [...prev, item])
+ setCurrentLevel(level + 1)
})
return (
-
-
- {Icon && (
-
-
-
- )}
-
- {collapsable && (open ? : )}
+ 0}
+ >
+
+ {level > 0 && }
+
- {collapsable && {children}}
+ {hasSubmenu && (
+ <>
+
+
+
+ = level + 1}
+ >
+
+
+
+ {item.items.map((subItem) => (
+
+ ))}
+
+ >
+ )}
)
}
-// const MobileNavigationMenu = forwardRef(({}, ref) => {
-const MobileNavigationMenu = () => {
+interface MobileMenuLevelProps {
+ level?: number
+}
+
+const MobileMenuLevel = ({
+ level = 0,
+ children,
+}: React.PropsWithChildren) => {
+ const { currentLevel } = useMobileMenuContext()
+
+ return (
+
+ {children}
+
+ )
+}
+
+interface MobileNavigationMenuProps {
+ items: Array
+}
+
+const MobileNavigationMenu = ({ items }: MobileNavigationMenuProps) => {
const [open, setOpen] = useState(false)
const t = useTranslator(CommonTranslations)
- const { admin /*loggedIn, logInOrOut, currentUser*/ } = useLoginStateContext()
- //const apollo = useApolloClient()
+ const [currentLevel, setCurrentLevel] = useState(0)
+ const [breadcrumbs, setBreadcrumbs] = useState<
+ Array
+ >([undefined])
- const onClick = useEventCallback(() => {
+ const onSubmenuClick = useEventCallback(() => {
+ setCurrentLevel((level) => level + 1)
+ })
+ const onBackClick = useEventCallback(() => {
+ setCurrentLevel((level) => level - 1)
+ setBreadcrumbs((prev) => prev.slice(0, -1))
+ })
+
+ const onMenuToggle = useEventCallback(() => {
setOpen((prevOpen) => !prevOpen)
})
@@ -172,122 +386,75 @@ const MobileNavigationMenu = () => {
setOpen(false)
})
- /*const onSignOut = useCallback(() => {
- setOpen(false)
- signOut(apollo, logInOrOut)
- }, [apollo, signOut, logInOrOut])
-
- const userDisplayName = useMemo(() => {
- const name = currentUser?.full_name
-
- if (!name) {
- return t("myProfile")
- }
-
- return name
- }, [currentUser, t])*/
-
- /*const menuItems = useMemo(() => {
- const items = [
- ,
- ,
- ,
- ]
-
- if (admin) {
- items.push(
- ,
- ,
- )
- }
- if (loggedIn) {
- items.push(
- ,
- ,
- )
- } else {
- items.push(
-
- {t("loginShort")}
- ,
-
- {t("signUp")}
- ,
- )
- }
-
- return items
- }, [loggedIn, onClose, t, admin, MobileMenuItemOld])*/
+ const menuContextValue = useMemo(
+ () => ({
+ currentLevel,
+ setCurrentLevel,
+ onSubmenuClick,
+ onBackClick,
+ breadcrumbs,
+ setBreadcrumbs,
+ }),
+ [currentLevel, breadcrumbs, setBreadcrumbs, onSubmenuClick, onBackClick],
+ )
return (
-
-
-
-
-
-
-
-
-
-
- {admin && }
-
+
+
+
+
+
+
+
+ {currentLevel > 0 && (
+
+
+ {breadcrumbs.slice(-2)[0]?.label ?? "Main menu"}
+
+ )}
+
+
+ {t("close")}
+
+
+
+
+
+ {items.map((item) => (
+
+ ))}
+
+
+
)
}
-//})
+
+interface MobileMenuContextType {
+ currentLevel: number
+ setCurrentLevel: React.Dispatch>
+ onSubmenuClick: () => void
+ onBackClick: () => void
+ breadcrumbs: Array
+ setBreadcrumbs: React.Dispatch<
+ React.SetStateAction>
+ >
+}
+
+const MobileMenuContext = createContext(
+ {} as MobileMenuContextType,
+)
+const useMobileMenuContext = () => React.useContext(MobileMenuContext)
export default MobileNavigationMenu
diff --git a/frontend/components/NewLayout/Navigation/NavigationDropdown.tsx b/frontend/components/NewLayout/Navigation/NavigationDropdown.tsx
new file mode 100644
index 000000000..0ff2cc717
--- /dev/null
+++ b/frontend/components/NewLayout/Navigation/NavigationDropdown.tsx
@@ -0,0 +1,279 @@
+import { useState } from "react"
+
+import {
+ Button,
+ EnhancedButton,
+ EnhancedLink,
+ EnhancedLinkProps,
+ Link,
+ MenuItem,
+ MenuList,
+ Popover,
+} from "@mui/material"
+import { css, styled } from "@mui/material/styles"
+import { useEventCallback } from "@mui/material/utils"
+
+import { NavigationLinkStyle } from "."
+import ArrowRight from "../Icons/ArrowRight"
+import CaretDownIcon from "../Icons/CaretDown"
+import CaretRight from "../Icons/CaretRight"
+import CaretUpIcon from "../Icons/CaretUp"
+
+interface NavigationDropdownButtonProps {
+ expanded?: boolean
+}
+
+const NavigationDropdownButton = styled(Button, {
+ shouldForwardProp: (prop) => prop !== "expanded",
+})(
+ ({ theme, expanded }) => `
+ ${NavigationLinkStyle.styles}
+ position: relative;
+ margin: 0;
+ right: unset;
+ top: unset;
+ min-height: unset;
+ border-radius: 0;
+ color: ${theme.palette.common.brand.nearlyBlack};
+ svg {
+ fill: ${theme.palette.common.brand.nearlyBlack};
+ }
+ ${theme.breakpoints.up("xl")} {
+ font-size: 1rem;
+ line-height: 16px;
+ }
+ &:hover {
+ cursor: pointer;
+ color: ${theme.palette.common.brand.main};
+ svg {
+ fill: ${theme.palette.common.brand.main};
+ }
+ }
+ ${
+ expanded
+ ? css`
+ &::after,
+ &::before {
+ background-color: ${theme.palette.common.grayscale.white};
+ content: "";
+ height: 5px;
+ width: 16px;
+ position: absolute;
+ bottom: -3px;
+ z-index: 3000 !important;
+ }
+
+ &::before {
+ left: -16px;
+ }
+
+ &::after {
+ right: -16px;
+ }
+
+ box-shadow: inset 0 -4px 0 0 ${theme.palette.common.grayscale.black};
+ `.styles
+ : ""
+ }
+`,
+) as EnhancedButton<"button", NavigationDropdownButtonProps>
+
+const NavigationDropdownMenu = styled(Popover)(
+ ({ theme }) => `
+ left: -17px;
+ top: 2px;
+
+ ${theme.breakpoints.up("lg")} {
+ top: 1px;
+ }
+
+ .MuiPopover-paper {
+ width: max-content;
+ max-width: 120rem;
+ padding: 24px 16px 40px;
+ border: 1px solid ${theme.palette.common.grayscale.black};
+ border-top: none;
+ border-radius: 0;
+ transition: none;
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
+ justify-items: center;
+ align-items: flex-start;
+ }
+`,
+) as typeof Popover
+
+const NavigationDropdownMenuPanelContainer = styled("div")`
+ align-items: baseline;
+ display: inline-flex;
+ flex-direction: column;
+ grid-column-start: 2;
+`
+const NavigationDropdownMenuList = styled(MenuList)`
+ /**/
+`
+
+const NavigationDropdownMenuHeader = styled("div")(
+ ({ theme }) => `
+ display: inline-grid;
+ gap: 8px 16px;
+ grid-template-areas: 'icon link' 'icon description';
+ grid-template-columns: auto;
+ grid-template-rows: auto;
+ margin-bottom: 16px;
+ align-items: flex-start;
+
+ svg {
+ fill: ${theme.palette.common.grayscale.white};
+ }
+`,
+)
+
+const NavigationDropdownContent = styled("div")`
+ padding-left: 56px;
+ display: inline-grid;
+ grid-template-columns: 310px;
+`
+
+const NavigationDropdownMenuItem = styled(MenuItem)`
+ padding: 0;
+ :hover {
+ background: transparent;
+ }
+`
+
+const NavigationDropdownMenuItemLink = styled(Link)(
+ ({ theme }) => `
+ font-size: 1rem;
+ line-height: 24px;
+ font-weight: 600;
+ align-items: flex-start;
+ color: ${theme.palette.common.brand.main};
+ display: inline-flex;
+ letter-spacing: -0.5px;
+ overflow: hidden;
+ text-decoration: none;
+
+ :hover {
+ text-decoration: underline;
+ }
+
+ svg {
+ margin-right: 12px;
+ margin-top: 8px;
+ fill: ${theme.palette.common.grayscale.black};
+ }
+`,
+) as EnhancedLink
+
+const NavigationDropdownHeaderLink = styled(Link)`
+ font-size: 25px;
+ line-height: 32px;
+ font-weight: 700;
+ grid-area: link;
+ letter-spacing: -0.5px;
+ margin-top: 4px;
+ text-decoration: none;
+ width: max-content;
+
+ :hover {
+ text-decoration: underline;
+ }
+` as EnhancedLink
+
+const NavigationIconWrapper = styled("div")(
+ ({ theme }) => `
+ background-color: ${theme.palette.common.grayscale.black};
+ display: flex;
+ grid-area: icon;
+ height: 40px;
+ justify-content: center;
+ align-items: center;
+ width: 40px;
+`,
+)
+
+export const NavigationDropdownMenuLink = ({
+ children,
+ ...props
+}: EnhancedLinkProps) => (
+
+
+
+ {children}
+
+
+)
+
+interface NavigationDropdownProps {
+ href?: string
+ label?: string
+ name?: string
+}
+
+export const NavigationDropdownLink = ({
+ name,
+ href,
+ label,
+ children,
+}: React.PropsWithChildren) => {
+ const [anchorEl, setAnchorEl] = useState(null)
+ const open = Boolean(anchorEl)
+
+ const onClick = useEventCallback(
+ (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget)
+ },
+ )
+ const onClose = useEventCallback(() => {
+ setAnchorEl(null)
+ })
+
+ const buttonName = name ?? label ?? "dropdown"
+ const menuName = `${buttonName}-menu`
+
+ return (
+ <>
+
+ {label}
+ {open ? : }
+
+
+ >
+ )
+}
diff --git a/frontend/components/NewLayout/Navigation/NavigationLinks.tsx b/frontend/components/NewLayout/Navigation/NavigationLinks.tsx
index 8aed7cbcf..1bd1f7882 100644
--- a/frontend/components/NewLayout/Navigation/NavigationLinks.tsx
+++ b/frontend/components/NewLayout/Navigation/NavigationLinks.tsx
@@ -1,26 +1,11 @@
-import { useState } from "react"
-
-import {
- Button,
- EnhancedButton,
- EnhancedLink,
- EnhancedLinkProps,
- Link,
- MenuItem,
- MenuList,
- Paper,
- Popover,
-} from "@mui/material"
+import { EnhancedLink, Link } from "@mui/material"
import { css, styled } from "@mui/material/styles"
-import { useEventCallback } from "@mui/material/utils"
-import ArrowRightIcon from "../Icons/ArrowRight"
-import CaretDownIcon from "../Icons/CaretDown"
-import CaretRight from "../Icons/CaretRight"
-import CaretUpIcon from "../Icons/CaretUp"
-import { useLoginStateContext } from "/contexts/LoginStateContext"
-import { useTranslator } from "/hooks/useTranslator"
-import CommonTranslations from "/translations/common"
+import { isSubmenuItem, NavigationMenuItem } from "."
+import {
+ NavigationDropdownLink,
+ NavigationDropdownMenuLink,
+} from "./NavigationDropdown"
const NavigationLinkStyle = css`
font-size: 0.875rem;
@@ -50,6 +35,7 @@ const NavigationLinkStyle = css`
font-size: 10px;
}
`
+
const NavigationLink = styled(Link)(
({ theme }) => `
${NavigationLinkStyle.styles}
@@ -71,99 +57,6 @@ const NavigationLink = styled(Link)(
`,
) as EnhancedLink
-interface NavigationDropdownButtonProps {
- expanded?: boolean
-}
-
-const NavigationDropdownButton = styled(Button, {
- shouldForwardProp: (prop) => prop !== "expanded",
-})(
- ({ theme, expanded }) => `
- ${NavigationLinkStyle.styles}
- position: relative;
- margin: 0;
- right: unset;
- top: unset;
- min-height: unset;
- color: ${theme.palette.common.brand.nearlyBlack};
- svg {
- fill: ${theme.palette.common.brand.nearlyBlack};
- }
- ${theme.breakpoints.up("xl")} {
- font-size: 1rem;
- line-height: 16px;
- }
- &:hover {
- cursor: pointer;
- color: ${theme.palette.common.brand.main};
- svg {
- fill: ${theme.palette.common.brand.main};
- }
- }
- ${
- expanded
- ? css`
- &::after,
- &::before {
- background-color: ${theme.palette.common.grayscale.white};
- content: "";
- height: 5px;
- width: 16px;
- position: absolute;
- bottom: -1px;
- z-index: 3000 !important;
- }
-
- &::before {
- left: -16px;
- }
-
- &::after {
- right: -16px;
- }
-
- box-shadow: inset 0 -4px 0 0 ${theme.palette.common.grayscale.black};
- `.styles
- : ""
- }
-`,
-) as EnhancedButton<"button", NavigationDropdownButtonProps>
-
-const NavigationDropdownMenu = styled(Popover)`
- left: -17px;
- top: 1px;
-` as typeof Popover
-
-const NavigationDropdownMenuPanel = styled(Paper)(
- ({ theme }) => `
- position: relative;
- width: max-content;
- max-width: 120rem;
- padding: 24px 16px 40px;
- border-radius: 0;
- background-color: ${theme.palette.common.grayscale.white};
- transition: none;
- top: 1px;
- display: grid;
- grid-template-columns: 1fr auto 1fr;
- justify-items: center;
- align-items: flex-start;
- border: 1px solid ${theme.palette.common.grayscale.black};
- border-top: none;
- margin: 0 auto;
-`,
-)
-
-const NavigationDropdownMenuPanelContainer = styled("div")`
- align-items: baseline;
- display: inline-flex;
- flex-direction: column;
- grid-column-start: 2;
-`
-const NavigationDropdownMenuList = styled(MenuList)`
- /**/
-`
-
const NavigationLinksContainer = styled("nav")(
({ theme }) => `
height: 100%;
@@ -190,203 +83,40 @@ const NavigationLinkItem = styled("li")`
position: relative;
`
-const NavigationDropdownMenuHeader = styled("div")(
- ({ theme }) => `
- display: inline-grid;
- gap: 8px 16px;
- grid-template-areas: 'icon link' 'icon description';
- grid-template-columns: auto;
- grid-template-rows: auto;
- margin-bottom: 16px;
- align-items: flex-start;
-
- svg {
- fill: ${theme.palette.common.grayscale.white};
- }
-`,
-)
-
-const NavigationIconWrapper = styled("div")(
- ({ theme }) => `
- background-color: ${theme.palette.common.grayscale.black};
- display: flex;
- grid-area: icon;
- height: 40px;
- justify-content: center;
- align-items: center;
- width: 40px;
-`,
-)
-
-const NavigationDropdownHeaderLink = styled(Link)`
- font-size: 25px;
- line-height: 32px;
- font-weight: 700;
- grid-area: link;
- letter-spacing: -0.5px;
- margin-top: 4px;
- text-decoration: none;
- width: max-content;
-
- :hover {
- text-decoration: underline;
- }
-` as EnhancedLink
-
-const NavigationDropdownContent = styled("div")`
- padding-left: 56px;
- display: inline-grid;
- grid-template-columns: 310px;
-`
-
-const NavigationDropdownMenuItem = styled(MenuItem)`
- padding: 0;
-`
-
-const NavigationDropdownMenuItemLink = styled(Link)(
- ({ theme }) => `
- font-size: 1rem;
- line-height: 24px;
- font-weight: 600;
- align-items: flex-start;
- color: ${theme.palette.common.brand.main};
- display: inline-flex;
- letter-spacing: -0.5px;
- overflow: hidden;
- text-decoration: none;
-
- :hover {
- text-decoration: underline;
- }
-
- svg {
- margin-right: 12px;
- margin-top: 8px;
- fill: ${theme.palette.common.grayscale.black};
- }
-`,
-) as EnhancedLink
-
-const NavigationDropdownMenuLink = ({
- children,
- ...props
-}: EnhancedLinkProps) => (
-
-
-
- {children}
-
-
-)
-interface NavigationDropdownProps {
- href?: string
- label?: string
- name?: string
+interface NavigationItemProps {
+ item: NavigationMenuItem
}
-const NavigationDropdownLink = ({
- name,
- href,
- label,
- children,
-}: React.PropsWithChildren) => {
- const [anchorEl, setAnchorEl] = useState(null)
- const open = Boolean(anchorEl)
-
- const onClick = useEventCallback(
- (event: React.MouseEvent) => {
- setAnchorEl(event.currentTarget)
- },
- )
- const onClose = useEventCallback(() => {
- setAnchorEl(null)
- })
-
- const buttonName = name ?? label ?? "dropdown"
- const menuName = `${buttonName}-menu`
-
+const NavigationItem = ({ item }: NavigationItemProps) => {
+ const { name, label, href } = item
return (
- <>
-
- {label}
- {open ? : }
-
-
- >
+
+ {isSubmenuItem(item) ? (
+
+ {item.items.map(({ name, label, href }) => (
+
+ {label}
+
+ ))}
+
+ ) : (
+ {label}
+ )}
+
)
}
-export const NavigationLinks = () => {
- const { admin } = useLoginStateContext()
- const t = useTranslator(CommonTranslations)
+interface NavigationLinksProps {
+ items: Array
+}
+
+export const NavigationLinks = ({ items }: NavigationLinksProps) => {
return (
-
- {t("courses")}
-
-
-
-
- {t("modules")}
-
-
-
- {admin && (
-
-
-
- {t("courses")}
-
-
- {t("modules")}
-
-
- {t("emailTemplates")}
-
-
- {t("userSearch")}
-
-
-
- )}
+ {items.map((item) => (
+
+ ))}
)
diff --git a/frontend/components/NewLayout/Navigation/NavigationMenu.tsx b/frontend/components/NewLayout/Navigation/NavigationMenu.tsx
new file mode 100644
index 000000000..9e1d88e06
--- /dev/null
+++ b/frontend/components/NewLayout/Navigation/NavigationMenu.tsx
@@ -0,0 +1,66 @@
+import { useMemo } from "react"
+
+import { isDefined } from "remeda"
+
+import {
+ DesktopNavigationMenu,
+ MobileNavigationMenu,
+ NavigationMenuItem,
+} from "."
+import { useLoginStateContext } from "/contexts/LoginStateContext"
+import { useTranslator } from "/hooks/useTranslator"
+import CommonTranslations from "/translations/common"
+
+const NavigationMenu = () => {
+ const t = useTranslator(CommonTranslations)
+ const { admin } = useLoginStateContext()
+
+ const items =
+ useMemo>(
+ () =>
+ [
+ {
+ label: t("courses"),
+ href: "/_new/courses",
+ },
+ {
+ label: t("modules"),
+ href: "/_new/study-modules",
+ },
+ admin
+ ? {
+ label: "Admin",
+ href: "/_new/admin",
+ items: [
+ {
+ href: "/_new/admin/courses",
+ label: t("courses"),
+ },
+ {
+ href: "/_new/admin/study-modules",
+ label: t("modules"),
+ },
+ {
+ href: "/_new/admin/email-templates",
+ label: t("emailTemplates"),
+ },
+ {
+ href: "/_new/admin/users/search",
+ label: t("userSearch"),
+ },
+ ],
+ }
+ : null,
+ ].filter(isDefined),
+ [t, admin],
+ ) ?? []
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default NavigationMenu
diff --git a/frontend/components/NewLayout/Navigation/index.tsx b/frontend/components/NewLayout/Navigation/index.tsx
index c572424bb..5757f5e0d 100644
--- a/frontend/components/NewLayout/Navigation/index.tsx
+++ b/frontend/components/NewLayout/Navigation/index.tsx
@@ -1,5 +1,35 @@
import { useRouter } from "next/router"
+import { css } from "@mui/material/styles"
+
+export const NavigationLinkStyle = css`
+ font-size: 0.875rem;
+ line-height: 16px;
+ font-weight: 700;
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+ letter-spacing: -0.7px;
+ padding: 26px 10px;
+ width: auto;
+ text-transform: uppercase;
+ text-decoration: none;
+ text-align: left;
+ align-items: center;
+ justify-content: center;
+ transition: 0.1s;
+
+ svg {
+ pointer-events: none;
+ margin-left: 4px;
+ height: 10px;
+ width: 10px;
+ font-size: 10px;
+ }
+`
export function useActiveTab() {
const { pathname } = useRouter()
@@ -8,6 +38,28 @@ export function useActiveTab() {
)?.[1]
}
+export type NavigationMenuLinkItem = {
+ href: string
+ label: string
+ name?: string
+ description?: string
+ level?: number
+ parent?: NavigationMenuItem
+}
+
+export type NavigationMenuSubmenuItem = NavigationMenuLinkItem & {
+ items: Array
+}
+
+export type NavigationMenuItem =
+ | NavigationMenuLinkItem
+ | NavigationMenuSubmenuItem
+
+export const isSubmenuItem = (
+ item: NavigationMenuItem,
+): item is NavigationMenuSubmenuItem =>
+ Boolean((item as NavigationMenuSubmenuItem).items)
+
export { default as MobileNavigationMenu } from "./MobileNavigationMenu"
export { default as DesktopNavigationMenu } from "./DesktopNavigationMenu"
-export * from "./BottomNavigation"
+export { default as NavigationMenu } from "./NavigationMenu"
diff --git a/frontend/components/SkipLink.tsx b/frontend/components/SkipLink.tsx
index 902ff5672..a6e2a4c16 100644
--- a/frontend/components/SkipLink.tsx
+++ b/frontend/components/SkipLink.tsx
@@ -5,11 +5,11 @@ import { useTranslator } from "/hooks/useTranslator"
import CommonTranslations from "/translations/common"
const SkipLinkContainer = styled("a")`
- left: -999;
+ left: -999px;
position: absolute;
top: auto;
- height: 1;
- width: 1;
+ height: 1px;
+ width: 1px;
overflow: hidden;
z-index: -999;
diff --git a/frontend/pages/_new/_layout.tsx b/frontend/pages/_new/_layout.tsx
index 4cd3b5602..58acea060 100644
--- a/frontend/pages/_new/_layout.tsx
+++ b/frontend/pages/_new/_layout.tsx
@@ -8,7 +8,6 @@ import { Breadcrumbs } from "/components/Breadcrumbs"
import Footer from "/components/Footer"
import Alerts from "/components/HeaderBar/Alerts"
import Header from "/components/NewLayout/Header"
-import { BottomNavigation } from "/components/NewLayout/Navigation/BottomNavigation"
import PageLoadingIndicators from "/components/PageLoadingIndicators"
import SkipLink from "/components/SkipLink"
@@ -21,6 +20,11 @@ const FooterDownPusherWrapper = styled("div")`
const MainContent = styled("main")`
position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ max-width: 1920px;
+ margin: 0 auto;
`
const Layout: React.FunctionComponent = ({
@@ -44,7 +48,6 @@ const Layout: React.FunctionComponent = ({
{children}
-
>
)
diff --git a/frontend/src/newTheme/components.tsx b/frontend/src/newTheme/components.tsx
index c607caf56..cf7535439 100644
--- a/frontend/src/newTheme/components.tsx
+++ b/frontend/src/newTheme/components.tsx
@@ -159,37 +159,6 @@ export const withComponents = (theme: Theme) =>
},
},
},
- text: {
- textTransform: "none",
- backgroundColor: "transparent",
- fontSize: "1rem",
- fontWeight: "700",
- letterSpacing: "-0.3px",
- padding: "16px",
- position: "fixed",
- right: "16px",
- top: "12px",
- marginLeft: "auto",
- marginRight: "-16px",
- alignItems: "center",
- display: "inline-flex",
- borderRadius: "0",
- svg: {
- marginLeft: "8px",
- },
- "&::after": {
- border: "none",
- },
- "&:hover": {
- backgroundColor: "transparent",
- },
- },
- textPrimary: {
- color: theme.palette.common.brand.main,
- svg: {
- fill: theme.palette.common.brand.main,
- },
- },
},
},
MuiAppBar: {