diff --git a/frontend/components/NewLayout/Header/Header.tsx b/frontend/components/NewLayout/Header/Header.tsx index 05bd370cd..b0e5944a5 100644 --- a/frontend/components/NewLayout/Header/Header.tsx +++ b/frontend/components/NewLayout/Header/Header.tsx @@ -1,8 +1,12 @@ 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 LanguageSwitch from "./LanguageSwitch" import MoocLogo from "./MoocLogo" +import { useTranslator } from "/hooks/useTranslator" +import CommonTranslations from "/translations/common" interface HideOnScrollProps { window?: () => Window @@ -19,31 +23,108 @@ function HideOnScroll({ window, children }: HideOnScrollProps) { ) } -const StyledToolbar = styled(Toolbar)` +const HyToolbar = styled(Toolbar)( + ({ theme }) => ` + background-color: ${theme.palette.common.grayscale.black}; + border-bottom: 2px solid rgb(0, 0, 0, 0.7); + padding: 0; + margin: 0; + position: relative; + flex-wrap: wrap; + display: flex; + justify-content: space-between; +`, +) + +const GroupToolbar = styled(Toolbar)` display: flex; flex-direction: row; flex-shrink: 0; overflow: hidden; ` -const MenuContainer = styled("div")` +const HyLogo = styled(HyLogoIcon)( + ({ theme }) => ` + fill: ${theme.palette.common.grayscale.white}; + font-size: 32; + ${theme.breakpoints.up("lg")} { + font-size: 64; + } +`, +) + +const HyLabel = styled("span")( + ({ theme }) => ` + font-size: 0.75rem; + line-height: 14px; + font-weight: 700; + color: ${theme.palette.common.grayscale.white}; + letter-spacing: -0.7px; + margin-left: 8px; + max-width: 90px; + text-transform: uppercase; + ${theme.breakpoints.down("xs")} { + font-size: 0.75rem; + line-height: 14px; + letter-spacing: -0.6px; + margin-left: 3px; + } + ${theme.breakpoints.up("md")} { + font-size: 0.875rem; + } + ${theme.breakpoints.up("lg")} { + letter-spacing: -0.6px; + margin-left: 4px; + } + ${theme.breakpoints.up("xl")} { + font-size: 0.875rem; + margin-left: 8px; + } +`, +) + +const HyLogoContainer = styled("div")( + ({ theme }) => ` + padding: 8px 0; display: flex; align-items: center; - justify-content: flex-end; - flex: 1; -` + justify-content: space-between; + ${theme.breakpoints.down("xs")} { + min-width: min-content; + max-width: max-content; + white-space: nowrap; + } + ${theme.breakpoints.up("lg")} { + min-width: min-content; + max-width: 90px; + white-space: initial; + } + ${theme.breakpoints.up("xl")} { + min-width: min-content; + max-width: max-content; + white-space: nowrap; + } +`, +) function Header() { + const t = useTranslator(CommonTranslations) + return ( - - + + + + + {t("hy")} + + + + - - - - - + + + ) diff --git a/frontend/components/NewLayout/Header/LanguageSwitch.tsx b/frontend/components/NewLayout/Header/LanguageSwitch.tsx index 4c643bbc6..eba3e79b3 100644 --- a/frontend/components/NewLayout/Header/LanguageSwitch.tsx +++ b/frontend/components/NewLayout/Header/LanguageSwitch.tsx @@ -1,80 +1,238 @@ -import React from "react" +import React, { useState } from "react" +import Link from "next/link" import { useRouter } from "next/router" -import LanguageIcon from "@mui/icons-material/Language" import { Button, - ButtonGroup, - ButtonGroupProps, - ButtonProps, EnhancedButton, + EnhancedMenuItem, + Menu, + MenuItem, } from "@mui/material" import { styled } from "@mui/material/styles" +import { useEventCallback } from "@mui/material/utils" +import CaretDownIcon from "../Icons/CaretDown" +import CaretUpIcon from "../Icons/CaretUp" +import GlobeIcon from "../Icons/Globe" import { useTranslator } from "/hooks/useTranslator" import { KeyOfTranslationDictionary } from "/translations" import CommonTranslations from "/translations/common" -const LanguageSwitchButton = (buttonProps: ButtonProps<"div">) => ( - + + + + + {admin && } + + ) -}) +} +//}) export default MobileNavigationMenu diff --git a/frontend/components/NewLayout/Navigation/NavigationLinks.tsx b/frontend/components/NewLayout/Navigation/NavigationLinks.tsx index b446f16fd..e179c97b6 100644 --- a/frontend/components/NewLayout/Navigation/NavigationLinks.tsx +++ b/frontend/components/NewLayout/Navigation/NavigationLinks.tsx @@ -1,5 +1,5 @@ import { EnhancedLink, Link } from "@mui/material" -import { css, styled } from "@mui/material/styles" +import { styled } from "@mui/material/styles" import { useActiveTab } from "/components/NewLayout/Navigation" import { useLoginStateContext } from "/contexts/LoginStateContext" @@ -12,60 +12,90 @@ interface NavigationLinkProps { const NavigationLink = styled(Link, { shouldForwardProp: (prop) => prop !== "active", -})` +})( + ({ theme, active }) => ` + font-size: 0.875rem; + line-height: 16px; + font-weight: 700; + background-color: transparent; + border: none; + color: ${theme.palette.common.brand.nearlyBlack}; + cursor: pointer; + display: flex; + flex-direction: row; + height: 100%; + letter-spacing: -0.7px; + padding: 12px 10px 13px; + text-transform: uppercase; text-decoration: none; - color: inherit; - font-size: 1rem; - padding: 0.2rem; - ${({ active }) => - active - ? css` - border-bottom: 2px solid rgba(200, 100, 0, 0.25); - font-weight: 600; - ` - : css` - &:hover { - text-shadow: 0px 0px 1px black; - } - `} - + text-align: left; + align-items: flex-start; transition: 0.1s; -` as EnhancedLink<"a", NavigationLinkProps> + ${active && `border-bottom: 2px solid ${theme.palette.common.brand.active};`} + &:hover { + color: ${theme.palette.common.brand.main}; + } +`, +) as EnhancedLink<"a", NavigationLinkProps> -const NavigationLinkContainer = styled("div")` +const NavigationContainer = styled("nav")` display: flex; - justify-content: space-between; - gap: 2rem; + margin: 0 32px; + align-items: center; + flex-flow: row; + justify-content: center; + padding: 0; +` + +const NavigationLinkList = styled("ul")` + display: flex; + height: 100%; + list-style: none; + margin: 0; + padding: 0; width: 100%; ` +const NavigationLinkItem = styled("li")` + list-style: none; + height: 100%; +` + export const NavigationLinks = () => { const { admin } = useLoginStateContext() const t = useTranslator(CommonTranslations) const active = useActiveTab() return ( - - - {t("courses")} - + + + + + {t("courses")} + + - - {t("modules")} - + + + {t("modules")} + + - {admin && ( - - Admin - - )} - + {admin && ( + + + Admin + + + )} + + ) } diff --git a/frontend/components/NewLayout/Navigation/NavigationMenu.tsx b/frontend/components/NewLayout/Navigation/NavigationMenu.tsx deleted file mode 100644 index 8f66591c1..000000000 --- a/frontend/components/NewLayout/Navigation/NavigationMenu.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import React, { - forwardRef, - MouseEventHandler, - useCallback, - useEffect, - useRef, - useState, -} from "react" - -import { useRouter } from "next/router" - -import { useApolloClient } from "@apollo/client" -import ChalkboardTeacherIcon from "@fortawesome/fontawesome-free/svgs/solid/chalkboard-teacher.svg?icon" -import DashboardIcon from "@fortawesome/fontawesome-free/svgs/solid/dashboard.svg?icon" -import ListIcon from "@fortawesome/fontawesome-free/svgs/solid/list.svg?icon" -import SignOutIcon from "@fortawesome/fontawesome-free/svgs/solid/sign-out.svg?icon" -import UserIcon from "@fortawesome/fontawesome-free/svgs/solid/user.svg?icon" -import MenuIcon from "@mui/icons-material/Menu" -import { - Button, - Divider, - EnhancedMenuItem, - IconButton, - ListItemIcon, - ListItemText, - Menu, - MenuItemProps, - MenuItem as MUIMenuItem, -} from "@mui/material" -import { styled } from "@mui/material/styles" -import { useEventCallback } from "@mui/material/utils" - -import { NavigationLinks } from "./NavigationLinks" -import LanguageSwitch from "/components/NewLayout/Header/LanguageSwitch" -import { useLoginStateContext } from "/contexts/LoginStateContext" -import { useTranslator } from "/hooks/useTranslator" -import { signOut } from "/lib/authentication" -import CommonTranslations from "/translations/common" - -const MenuItem = MUIMenuItem as EnhancedMenuItem - -const NavigationMenuContainer = styled("nav")( - ({ theme }) => ` - width: 100%; - height: 100%; - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.5rem; - - ${theme.breakpoints.down("xs")} { - display: none; - } -`, -) - -const MobileMenuContainer = styled("div")( - ({ theme }) => ` - display: flex; - justify-content: flex-end; - - ${theme.breakpoints.down("xs")} { - display: none; - } -`, -) - -const NavigationRightContainer = styled("div")` - display: flex; - justify-content: flex-end; - gap: 0.5rem; - flex-grow: 1; -` - -const NavigationLinksWrapper = styled("div")( - ({ theme }) => ` - display: flex; - flex-grow: 1; - - ${theme.breakpoints.down("sm")} { - display: none; - } -`, -) - -const MenuButton = styled(Button)` - display: flex; - max-height: 10vh; - white-space: nowrap; - font-size: clamp(12px, 1.5vw, 16px); -` - -const UserOptionsMenu = () => { - const apollo = useApolloClient() - const { pathname } = useRouter() - const { loggedIn, logInOrOut, currentUser } = useLoginStateContext() - const t = useTranslator(CommonTranslations) - - const userDisplayName = currentUser?.first_name - ? `${currentUser.first_name} ${currentUser.last_name}` - : t("myProfile") - - const onLogOut = useCallback( - () => signOut(apollo, logInOrOut), - [apollo, logInOrOut], - ) - - if (loggedIn) { - return ( - <> - {userDisplayName} - - {t("logout")} - - - ) - } - return ( - <> - {t("loginShort")} - {t("signUp")} - - ) -} - -const DesktopNavigationMenu = () => { - return ( - - - - - - - - - - ) -} - -interface MobileMenuItemProps extends MenuItemProps { - icon: React.ElementType - text: string - onClick?: React.MouseEventHandler -} - -const Nop = () => { - /* */ -} - -const MobileMenuItem = forwardRef( - ({ icon: Icon, text, onClick = Nop, ...props }, ref) => { - return ( - - - - - {text} - - ) - }, -) as EnhancedMenuItem<"li", MobileMenuItemProps> - -const MobileNavigationMenu = forwardRef(({}, ref) => { - const [isOpen, setIsOpen] = useState(false) - const anchor = useRef<(EventTarget & HTMLButtonElement) | null>(null) - - const t = useTranslator(CommonTranslations) - const { admin, loggedIn, logInOrOut, currentUser } = useLoginStateContext() - const apollo = useApolloClient() - - const onClick: MouseEventHandler = useEventCallback( - (event) => { - setIsOpen((value) => !value) - anchor.current = event.currentTarget - }, - ) - - const onClose = useEventCallback(() => { - setIsOpen(false) - anchor.current = null - }) - - useEffect(() => { - const resizeListener = () => { - setIsOpen(false) - } - window?.addEventListener("resize", resizeListener) - - return () => window?.removeEventListener("resize", resizeListener) - }, []) - - const userDisplayName = currentUser?.first_name - ? `${currentUser.first_name} ${currentUser.last_name}` - : t("myProfile") - - const onLogOut = useCallback( - () => signOut(apollo, logInOrOut), - [apollo, logInOrOut], - ) - - return ( - - - - - - - - - - - - {admin && [ - , - , - ]} - {loggedIn - ? [ - , - , - ] - : [ - - {t("loginShort")} - , - - {t("signUp")} - , - ]} - - - ) -}) - -export { DesktopNavigationMenu, MobileNavigationMenu } diff --git a/frontend/next.config.js b/frontend/next.config.js index 46d35f50b..57c838763 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -56,7 +56,7 @@ const nextConfiguration = (_phase) => ({ emotion: { // would label things with [local] or something; will break styling if not set to never // autoLabel: "never", - autoLabel: "never", + // autoLabel: "never", // labelFormat: "[dirname]--[filename]--[local]", importMap: { "@mui/system": { diff --git a/frontend/src/newTheme/components.tsx b/frontend/src/newTheme/components.tsx index 97f056f9c..652a5e52d 100644 --- a/frontend/src/newTheme/components.tsx +++ b/frontend/src/newTheme/components.tsx @@ -3,6 +3,7 @@ import { ButtonProps, EnhancedLinkProps, FormControlProps, + ListItemButtonProps, MenuItemProps, TextFieldProps, } from "@mui/material" @@ -44,21 +45,179 @@ export const withComponents = (theme: Theme) => MuiMenuItem: { defaultProps: { LinkComponent: LinkBehavior, + disableRipple: true, } as MenuItemProps, }, + MuiListItemButton: { + defaultProps: { + LinkComponent: LinkBehavior, + disableRipple: true, + } as ListItemButtonProps, + }, MuiButton: { defaultProps: { variant: "outlined", color: "primary", + disableRipple: true, LinkComponent: LinkBehavior, } as ButtonProps, styleOverrides: { root: { ...bodyFontDeclaration, - textTransform: "uppercase", - borderRadius: "20px", + display: "flex", + alignItems: "center", + border: "none", + cursor: "pointer", + fontSize: "0.9375rem", + justifyContent: "center", + lineHeight: "0.9375rem", + margin: 0, + padding: 0, + transitionDuration: "0.1s", + transitionProperty: "all", + minHeight: "44px", + position: "relative", + textDecoration: "none", + }, + contained: { + backgroundColor: theme.palette.common.brand.main, + color: theme.palette.common.grayscale.white, + "&::after": { + content: '""', + display: "block", + borderBottom: `2px solid ${theme.palette.common.additional.skyblue}`, + position: "absolute", + bottom: "-3.5px", + left: "3px", + right: "3px", + zIndex: 0, + }, + "&:disabled": { + cursor: "not-allowed", + "&::after": { + borderBottom: `2px solid ${theme.palette.common.grayscale.mediumDark}`, + bottom: "-3.5px", + }, + }, + }, + containedPrimary: { + backgroundColor: theme.palette.common.brand.main, + color: theme.palette.common.grayscale.white, + svg: { + fill: theme.palette.common.grayscale.white, + }, + "&:hover": { + backgroundColor: theme.palette.common.brand.active, + }, + "&:active": { + backgroundColor: theme.palette.common.brand.dark, + }, + "&:disabled": { + backgroundColor: theme.palette.common.grayscale.medium, + color: theme.palette.common.grayscale.dark, + svg: { + fill: theme.palette.common.grayscale.dark, + }, + "&::after": { + bottom: "-2px", + }, + }, + "&:focus": { + outline: `solid 2px ${theme.palette.common.additional.yellow.main}`, + outlineOffset: "2px", + }, + "&::after": { + bottom: "-2px", + }, + }, + containedSecondary: { + backgroundColor: "transparent", + border: `solid 2px ${theme.palette.common.brand.main}`, + color: theme.palette.common.brand.main, + ".svg": { + fill: theme.palette.common.brand.main, + }, + "&:hover": { + border: `solid 2px ${theme.palette.common.brand.active}`, + color: theme.palette.common.brand.active, + svg: { + fill: theme.palette.common.brand.active, + }, + }, + "&:active": { + border: `solid 2px ${theme.palette.common.brand.dark}`, + color: theme.palette.common.brand.dark, + svg: { + fill: theme.palette.common.brand.dark, + }, + }, + "&:disabled": { + border: `solid 2px ${theme.palette.common.grayscale.mediumDark}`, + color: theme.palette.common.grayscale.mediumDark, + svg: { + fill: theme.palette.common.grayscale.mediumDark, + }, + }, + }, + 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", + + svg: { + marginLeft: "8px", + }, + "&::after": { + border: "none", + }, + "&:hover": { + backgroundColor: "transparent", + }, + }, + textPrimary: { + color: theme.palette.common.brand.main, + svg: { + fill: theme.palette.common.brand.main, + }, + }, + }, + }, + MuiAppBar: { + styleOverrides: { + root: { + backgroundColor: theme.palette.common.grayscale.white, + zIndex: 100, + borderBottom: `2px solid ${theme.palette.common.grayscale.dark}`, + transform: "none", + [theme.breakpoints.up("lg")]: { + borderBottom: `1px solid ${theme.palette.common.grayscale.black}`, + margin: "0 auto", + maxWidth: "1920px", + }, + }, + }, + }, + MuiToolbar: { + styleOverrides: { + root: { + padding: "0 8px", + display: "flex", + justifyContent: "space-between", + [theme.breakpoints.up("lg")]: { + padding: "0 32px", + }, }, }, }, }, - }) + } as Partial) diff --git a/frontend/src/newTheme/fonts.tsx b/frontend/src/newTheme/fonts.tsx index b4847ea32..0cf483c70 100644 --- a/frontend/src/newTheme/fonts.tsx +++ b/frontend/src/newTheme/fonts.tsx @@ -11,7 +11,7 @@ export const headerFont = Open_Sans({ }) export const bodyFont = Open_Sans({ - weight: ["400", "700"], + weight: ["400", "600", "700"], style: ["normal", "italic"], fallback: ["Helvetica", "Arial", "sans-serif"], display: "swap", diff --git a/frontend/src/newTheme/index.tsx b/frontend/src/newTheme/index.tsx index 68c7205ec..c38cb8ac5 100644 --- a/frontend/src/newTheme/index.tsx +++ b/frontend/src/newTheme/index.tsx @@ -16,7 +16,7 @@ let theme = createTheme({ xxs: 360, xs: 480, sm: 640, - md: 900, + md: 960, desktop: 1024, lg: 1200, xl: 1536, diff --git a/frontend/src/newTheme/palette.tsx b/frontend/src/newTheme/palette.tsx index 3f3c53685..51e14c853 100644 --- a/frontend/src/newTheme/palette.tsx +++ b/frontend/src/newTheme/palette.tsx @@ -1,4 +1,3 @@ -import { amber } from "@mui/material/colors" import { createTheme, Theme } from "@mui/material/styles" export const withPalette = (theme: Theme) => @@ -6,10 +5,60 @@ export const withPalette = (theme: Theme) => ...theme, palette: { primary: { - main: "#378170", + main: "#0e688b", }, secondary: { - main: amber[500], + main: "#fff", + }, + common: { + brand: { + main: "#0e688b", + soft: "#b1e7ff", + bright: "#48c5f8", + light: "#107eab", + active: "#005379", + dark: "#003146", + nearlyBlack: "#000222", + }, + grayscale: { + white: "#fff", + slightlyGray: "#fefefe", + light: "#f8f8f8", + medium: "#d2d2d2", + backgroundBox: "#f5f5f5", + tabsBorder: "#e6e6e6", + backgroundArrow: "#dfdfdf", + mediumDark: "#979797", + dark: "#555555", + darkText: "#222222", + black: "#000", + }, + additional: { + red: { + light: "#e5053a", + dark: "#a31621", + }, + purple: { + light: "#420039", + }, + yellow: { + light: "#f9a21a", + main: "#c47f1b", + }, + skyblue: "#48c5f8", + orange: "#d14600", + green: { + light: "#96ba3c", + dark: "#006400", + }, + }, + link: { + blue: "#0479a4", + disabled: "#767676", + }, + hover: { + gray: "#eaeaea", + }, }, blue: { light3: "#DAE3EB", diff --git a/frontend/translations/common/en.ts b/frontend/translations/common/en.ts index b6c3e605b..020eb316f 100644 --- a/frontend/translations/common/en.ts +++ b/frontend/translations/common/en.ts @@ -77,8 +77,8 @@ export default { ukraineHyText: "Support students and researchers affected by the invasion of Ukraine", ukraineHyLinkText: "University of Helsinki Ukraine appeal", - fi: "Suomeksi", - en: "In English", + fi: "Suomi", + en: "English", selectAll: "Select all", unscheduled: "Indefinitely open", showCourse: "Show course", @@ -108,4 +108,5 @@ export default { difficulty: "Difficulty", module: "Module", courseOtherLanguages: "Also available in", + hy: "University of Helsinki", } as const diff --git a/frontend/translations/common/fi.ts b/frontend/translations/common/fi.ts index c311f6a6d..2ec9b0f2e 100644 --- a/frontend/translations/common/fi.ts +++ b/frontend/translations/common/fi.ts @@ -77,8 +77,8 @@ export default { "https://www.helsinki.fi/fi/yhteistyo/lahjoittajille/ukraina-kerays", ukraineHyLinkText: "Lahjoita Ukraina‑keräykseen opiskelijoiden ja tutkijoiden tueksi", - fi: "Suomeksi", - en: "In English", + fi: "Suomi", + en: "English", selectAll: "Valitse kaikki", unscheduled: "Aikatauluton", showCourse: "Näytä kurssi", @@ -108,4 +108,5 @@ export default { difficulty: "Vaikeustaso", module: "Kokonaisuus", courseOtherLanguages: "Saatavilla myös kielillä", + hy: "Helsingin yliopisto", } as const diff --git a/frontend/translations/common/se.ts b/frontend/translations/common/se.ts index 28fec9484..0bac7ff54 100644 --- a/frontend/translations/common/se.ts +++ b/frontend/translations/common/se.ts @@ -109,4 +109,5 @@ export default { difficulty: "Difficulty", module: "Module", courseOtherLanguages: "Also available in", + hy: "Helsingfors universitet", } as const diff --git a/frontend/types/mui.d.ts b/frontend/types/mui.d.ts index 7cbbf5321..3940ebad3 100644 --- a/frontend/types/mui.d.ts +++ b/frontend/types/mui.d.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import { LinkProps as NextLinkProps } from "next/link" +import { ListItemButtonTypeMap } from "@mui/material" import { ButtonTypeMap } from "@mui/material/Button" import { ButtonBaseTypeMap, ExtendButtonBase } from "@mui/material/ButtonBase" import { LinkTypeMap } from "@mui/material/Link" @@ -84,6 +85,27 @@ declare module "@mui/material/MenuItem" { > } +declare module "@mui/material/ListItemButton" { + export type EnhancedListItemButtonProps< + RootComponent extends + React.ElementType = ListItemButtonTypeMap["defaultComponent"], + AdditionalProps = {}, + > = OverrideProps< + ListItemButtonTypeMap< + AdditionalProps & Partial, + RootComponent + >, + RootComponent + > & { component?: React.ElementType } + export type EnhancedListItemButton< + D extends React.ElementType = ButtonTypeMap["defaultComponent"], + P = {}, + > = ExtendButtonBase, D>> + const ListItemButton: ExtendButtonBase< + ListItemButtonTypeMap> + > +} + declare module "@mui/material/Typography" { // add typography variants - also needs to be declared in "styles" interface TypographyPropsVariantOverrides { @@ -164,7 +186,6 @@ declare module "@mui/material/styles" { dark2?: string dark3?: string } - // interface ComponentNameToClassKey { @@ -219,3 +240,56 @@ declare module "@mui/material/styles" { } } } + +declare module "@mui/material/styles/createPalette" { + interface CommonColors { + brand: { + main: string + soft: string + bright: string + light: string + active: string + dark: string + nearlyBlack: string + } + link: { + blue: string + disabled: string + } + grayscale: { + white: string + slightlyGray: string + light: string + medium: string + backgroundBox: string + tabsBorder: string + backgroundArrow: string + mediumDark: string + dark: string + darkText: string + black: string + } + additional: { + red: { + light: string + dark: string + } + purple: { + light: string + } + yellow: { + light: string + main: string + } + skyblue: string + orange: string + green: { + light: string + dark: string + } + } + hover: { + gray: string + } + } +}