diff --git a/client/src/ui/atoms/signup-link/index.tsx b/client/src/ui/atoms/signup-link/index.tsx index b01b87349bc1..51a6a3bcf372 100644 --- a/client/src/ui/atoms/signup-link/index.tsx +++ b/client/src/ui/atoms/signup-link/index.tsx @@ -10,7 +10,7 @@ export const SignUpLink = ({ toPlans = false, gleanContext = "" }) => { const loginUrl = useLoginUrl(); const href = toPlans ? plansUrl : loginUrl; - const label = toPlans ? "Upgrade Now" : "Sign up"; + const label = toPlans ? "Upgrade Now" : "Sign up for free"; const rel = toPlans ? undefined : "nofollow"; return ( diff --git a/client/src/ui/molecules/guides-menu/index.scss b/client/src/ui/molecules/guides-menu/index.scss new file mode 100644 index 000000000000..42a234d2f98a --- /dev/null +++ b/client/src/ui/molecules/guides-menu/index.scss @@ -0,0 +1,23 @@ +@use "sass:color"; +@use "../../vars" as *; + +.guides { + .submenu .submenu-item-heading { + font-size: var(--type-smaller-font-size); + font-weight: initial; + } + + .desktop-only { + display: none; + } + + @media (min-width: $screen-lg) { + .desktop-only { + display: inherit; + } + + .mobile-only { + display: none; + } + } +} diff --git a/client/src/ui/molecules/guides-menu/index.tsx b/client/src/ui/molecules/guides-menu/index.tsx new file mode 100644 index 000000000000..402f11600180 --- /dev/null +++ b/client/src/ui/molecules/guides-menu/index.tsx @@ -0,0 +1,66 @@ +import { useLocale } from "../../../hooks"; +import { Menu } from "../menu"; + +import "./index.scss"; + +export const GuidesMenu = ({ visibleSubMenuId, toggleMenu }) => { + const locale = useLocale(); + + const menu = { + id: "guides", + label: "Guides", + to: `/${locale}/docs/Learn`, + items: [ + { + description: "Learn web development", + hasIcon: true, + extraClasses: "apis-link-container mobile-only", + iconClasses: "submenu-icon learn", + label: "Overview / MDN Learning Area", + url: `/${locale}/docs/Learn`, + }, + { + description: "Learn web development", + extraClasses: "apis-link-container desktop-only", + hasIcon: true, + iconClasses: "submenu-icon learn", + label: "MDN Learning Area", + url: `/${locale}/docs/Learn`, + }, + { + description: "Learn to structure web content with HTML", + extraClasses: "html-link-container", + hasIcon: true, + iconClasses: "submenu-icon html", + label: "HTML", + url: `/${locale}/docs/Learn/HTML`, + }, + { + description: "Learn to style content using CSS", + extraClasses: "css-link-container", + hasIcon: true, + iconClasses: "submenu-icon css", + label: "CSS", + url: `/${locale}/docs/Learn/CSS`, + }, + { + description: "Learn to run scripts in the browser", + extraClasses: "javascript-link-container", + hasIcon: true, + iconClasses: "submenu-icon javascript", + label: "JavaScript", + url: `/${locale}/docs/Learn/JavaScript`, + }, + { + description: "Learn to make the web accessible to all", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Accessibility", + url: `/${locale}/docs/Web/Accessibility`, + }, + ], + }; + const isOpen = visibleSubMenuId === menu.id; + + return ; +}; diff --git a/client/src/ui/molecules/main-menu/index.scss b/client/src/ui/molecules/main-menu/index.scss index 459e5e18ec30..29913f4dbe63 100644 --- a/client/src/ui/molecules/main-menu/index.scss +++ b/client/src/ui/molecules/main-menu/index.scss @@ -40,22 +40,8 @@ ul.main-menu { } } - .top-level-entry .long { - display: none; - } - @media (min-width: ($screen-xl)) { gap: 1rem; - - .top-level-entry { - .long { - display: unset; - } - - .short { - display: none; - } - } } } @@ -92,55 +78,3 @@ ul.main-menu { } } } - -@media screen and (min-width: $screen-lg) { - #more-button { - display: flex; - - &::after { - display: none; - } - } -} - -.submenu-icon { - &.html { - background-color: var(--html-accent-color); - } - - &.css { - background-color: var(--css-accent-color); - } - - &.javascript { - background-color: var(--js-accent-color); - } - - &.http { - background-color: var(--http-accent-color); - } - - &.apis { - background-color: var(--apis-accent-color); - } - - &.learn { - background-color: var(--learn-accent-color); - } - - &.plus { - background-color: var(--plus-accent-color); - } - - &.curriculum { - background-color: var(--curriculum-color); - } - - &.blog { - background-color: var(--apis-accent-color); - } - - &.observatory { - background-color: var(--observatory-accent); - } -} diff --git a/client/src/ui/molecules/main-menu/index.tsx b/client/src/ui/molecules/main-menu/index.tsx index 25a4ca9c9254..a61dc3e1e28d 100644 --- a/client/src/ui/molecules/main-menu/index.tsx +++ b/client/src/ui/molecules/main-menu/index.tsx @@ -1,19 +1,15 @@ import { useEffect, useRef, useState } from "react"; -import { Menu } from "../menu"; +import { GuidesMenu } from "../guides-menu"; +import { ReferenceMenu } from "../reference-menu"; +import { PlusMenu } from "../plus-menu"; import "./index.scss"; -import "./tools.scss"; - import { PLUS_IS_ENABLED } from "../../../env"; import { useGleanClick } from "../../../telemetry/glean-context"; import { MENU } from "../../../telemetry/constants"; import { useLocation } from "react-router"; -import { useIsServer, useLocale } from "../../../hooks"; -import { usePlusUrl } from "../../../plus/utils"; -import { MenuEntry } from "../submenu"; -import { useUserData } from "../../../user-context"; -import { OBSERVATORY_TITLE } from "../../../../../libs/constants"; +import { ToolsMenu } from "../tools-menu"; export default function MainMenu({ isOpenOnMobile }) { const previousActiveElement = useRef(null); @@ -68,269 +64,34 @@ export default function MainMenu({ isOpenOnMobile }) { } } - const locale = useLocale(); - - // Plus menu. - const plusUrl = usePlusUrl(); - const isServer = useIsServer(); - const userData = useUserData(); - const isAuthenticated = userData && userData.isAuthenticated; - const { pathname } = useLocation(); - - const menus = [ - { - id: "html", - label: "HTML", - to: `/${locale}/docs/Web/HTML`, - isActive: - pathname.startsWith(`/${locale}/docs/Learn/HTML`) || - pathname.startsWith(`/${locale}/docs/Web/HTML`), - items: [ - { - description: "Learn to structure web content with HTML", - hasIcon: true, - iconClasses: "submenu-icon html", - label: "Learn HTML", - url: `/${locale}/docs/Learn/HTML`, - }, - { - description: "Look up elements, attributes, and more", - hasIcon: true, - iconClasses: "submenu-icon html", - label: "HTML references", - url: `/${locale}/docs/Web/HTML`, - }, - ], - }, - { - id: "css", - label: "CSS", - to: `/${locale}/docs/Web/CSS`, - isActive: - pathname.startsWith(`/${locale}/docs/Learn/CSS`) || - pathname.startsWith(`/${locale}/docs/Web/CSS`), - items: [ - { - description: "Learn to style content using CSS", - hasIcon: true, - iconClasses: "submenu-icon css", - label: "Learn CSS", - url: `/${locale}/docs/Learn/CSS`, - }, - { - description: "Look up properties, selectors, and more", - hasIcon: true, - iconClasses: "submenu-icon css", - label: "CSS references", - url: `/${locale}/docs/Web/CSS`, - }, - ], - }, - { - id: "js", - label: ( - <> - JS - JavaScript - - ), - to: `/${locale}/docs/Web/JavaScript`, - isActive: - pathname.startsWith(`/${locale}/docs/Learn/JavaScript`) || - pathname.startsWith(`/${locale}/docs/Web/JavaScript`), - items: [ - { - description: "Learn to run scripts in the browser", - hasIcon: true, - iconClasses: "submenu-icon javascript", - label: "Learn JavaScript", - url: `/${locale}/docs/Learn/JavaScript`, - }, - { - description: "Look up objects, expressions, and more", - hasIcon: true, - iconClasses: "submenu-icon javascript", - label: "JavaScript references", - url: `/${locale}/docs/Web/JavaScript`, - }, - ], - }, - { - id: "apis", - label: ( - <> - APIs - Web APIs - - ), - to: `/${locale}/docs/Web/API`, - isActive: pathname.startsWith(`/${locale}/docs/Web/API`), - items: [ - { - description: "Look up all the APIs and interfaces", - hasIcon: true, - iconClasses: "submenu-icon apis", - label: "Web API references", - url: `/${locale}/docs/Web/API`, - }, - ], - }, - { - id: "http", - label: "HTTP", - to: `/${locale}/docs/Web/HTTP`, - isActive: pathname.startsWith(`/${locale}/docs/Web/HTTP`), - items: [ - { - description: "Look up status codes, headers, and more", - hasIcon: true, - iconClasses: "submenu-icon http", - label: "HTTP references", - url: `/${locale}/docs/Web/HTTP`, - }, - ], - }, - { - id: "more", - label: "More", - items: [ - { - description: "Learn to make the web accessible to all", - hasIcon: true, - iconClasses: "submenu-icon", - label: "Accessibility", - url: `/${locale}/docs/Web/Accessibility`, - }, - { - description: "Develop extensions for web browsers", - hasIcon: true, - iconClasses: "submenu-icon", - label: "Web Extensions", - url: `/${locale}/docs/Mozilla/Add-ons/WebExtensions`, - }, - ], - }, - { - id: "learn", - label: "Learn", - to: `/${locale}/docs/Learn`, - isActive: - pathname.startsWith("/en-US/curriculum/") || - pathname.startsWith(`/${locale}/docs/Learn`), - items: [ - { - description: "Essential skills for front-end developers", - hasIcon: true, - iconClasses: "submenu-icon curriculum", - label: "MDN Curriculum", - url: "/en-US/curriculum/", - }, - { - description: "Learn web development", - hasIcon: true, - iconClasses: "submenu-icon learn", - label: "MDN Learning Area", - url: `/${locale}/docs/Learn`, - }, - ], - }, - { - id: "blog", - label: "Blog", - to: "/en-US/blog/", - isActive: pathname.startsWith("/en-US/blog/"), - items: [ - { - description: "Learn about web features, and MDN", - hasIcon: true, - iconClasses: "submenu-icon blog", - label: "MDN Blog", - url: "/en-US/blog/", - }, - ], - }, - { - id: "tools", - label: "Tools", - items: [ - { - description: "Write, test and share your code", - hasIcon: true, - iconClasses: "submenu-icon", - label: "Playground", - url: `/${locale}/play`, - }, - { - description: "Scan a website for free", - hasIcon: true, - iconClasses: "submenu-icon observatory", - label: OBSERVATORY_TITLE, - url: `/en-US/observatory`, - }, - ...(PLUS_IS_ENABLED - ? [ - { - description: "A customized MDN experience", - hasIcon: true, - iconClasses: "submenu-icon plus", - label: "MDN Plus", - url: plusUrl, - }, - { - description: "Get real-time assistance and support", - hasIcon: true, - iconClasses: "submenu-icon plus", - label: "AI Help", - url: `/en-US/plus/ai-help`, - }, - ...(!isServer && isAuthenticated - ? [ - { - description: "Your saved articles from across MDN", - hasIcon: true, - iconClasses: "submenu-icon plus", - label: "Collections", - url: `/${locale}/plus/collections`, - }, - ] - : []), - { - description: "All browser compatibility updates at a glance", - hasIcon: true, - iconClasses: "submenu-icon plus", - label: "Updates", - url: `/${locale}/plus/updates`, - }, - ] - : []), - ], - }, - ].filter(Boolean) as (MenuEntry | React.ReactElement)[]; - return ( ); } -function isMenuEntry(menu: any): menu is MenuEntry { - return typeof menu.id === "string"; -} - function TopLevelMenuLink({ to, children, diff --git a/client/src/ui/molecules/plus-menu/index.scss b/client/src/ui/molecules/plus-menu/index.scss new file mode 100644 index 000000000000..bdc55163ef3c --- /dev/null +++ b/client/src/ui/molecules/plus-menu/index.scss @@ -0,0 +1,23 @@ +@use "sass:color"; +@use "../../vars" as *; + +.mdn-plus { + .submenu-icon { + background-color: var(--plus-accent-color); + } + + .note { + background-color: var(--background-information); + + .submenu-item-description { + display: block; + margin: 0.125rem; + } + } + + @media (min-width: $screen-lg) { + .mobile-only { + display: none; + } + } +} diff --git a/client/src/ui/molecules/plus-menu/index.tsx b/client/src/ui/molecules/plus-menu/index.tsx new file mode 100644 index 000000000000..5aa5138688c7 --- /dev/null +++ b/client/src/ui/molecules/plus-menu/index.tsx @@ -0,0 +1,94 @@ +import "./index.scss"; +import { usePlusUrl } from "../../../plus/utils"; +import { Menu } from "../menu"; +import { useIsServer, useLocale, useViewedState } from "../../../hooks"; +import { useUserData } from "../../../user-context"; +import { MenuEntry } from "../submenu"; +import { FeatureId } from "../../../constants"; +import { useLocation } from "react-router"; + +export const PlusMenu = ({ visibleSubMenuId, toggleMenu }) => { + const plusUrl = usePlusUrl(); + const locale = useLocale(); + const isServer = useIsServer(); + const userData = useUserData(); + const isAuthenticated = userData && userData.isAuthenticated; + + const { isViewed } = useViewedState(); + + // Avoid that "Plus" and "AI Help" are both active. + const { pathname } = useLocation(); + const aiHelpUrl = `/${locale}/plus/ai-help`; + const isActive = + pathname.startsWith(plusUrl.split("#", 2)[0]) && + !pathname.startsWith(aiHelpUrl); + + const plusMenu: MenuEntry = { + label: "Plus", + id: "mdn-plus", + to: plusUrl, + items: [ + { + description: "A customized MDN experience", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Overview", + url: plusUrl, + }, + { + description: "Get real-time assistance and support", + hasIcon: true, + iconClasses: "submenu-icon", + label: "AI Help", + url: aiHelpUrl, + }, + ...(!isServer && isAuthenticated + ? [ + { + description: "Your saved articles from across MDN", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Collections", + url: `/${locale}/plus/collections`, + }, + ] + : []), + { + description: "All browser compatibility updates at a glance", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Updates", + dot: + Date.now() < 1675209600000 && // new Date("2023-02-01 00:00:00Z").getTime() + !isViewed(FeatureId.PLUS_UPDATES_V2) + ? "New feature" + : undefined, + url: `/${locale}/plus/updates`, + }, + { + description: "Learn how to use MDN Plus", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Documentation", + url: `/en-US/plus/docs/features/overview`, + }, + { + description: "Frequently asked questions about MDN Plus", + hasIcon: true, + iconClasses: "submenu-icon", + label: "FAQ", + url: `/en-US/plus/docs/faq`, + }, + ], + }; + const isOpen = visibleSubMenuId === plusMenu.id; + + return ( + + ); +}; diff --git a/client/src/ui/molecules/reference-menu/index.scss b/client/src/ui/molecules/reference-menu/index.scss new file mode 100644 index 000000000000..1420a142333f --- /dev/null +++ b/client/src/ui/molecules/reference-menu/index.scss @@ -0,0 +1,110 @@ +@use "sass:color"; +@use "../../vars" as *; + +.references { + .desktop-only { + display: none; + } + + @media (min-width: $screen-lg) { + .desktop-only { + display: inherit; + } + + .mobile-only { + display: none; + } + } +} + +.html-link-container { + a:hover, + a:focus { + .submenu-icon { + &.html { + background: var(--html-accent-color) !important; + } + } + } +} + +.css-link-container { + a:hover, + a:focus { + .submenu-icon { + &.css { + background-color: var(--css-accent-color) !important; + } + } + } +} + +.javascript-link-container { + a:hover, + a:focus { + .submenu-icon { + &.javascript { + background-color: var(--js-accent-color) !important; + } + } + } +} + +.http-link-container { + a:hover, + a:focus { + .submenu-icon { + &.http { + background-color: var(--http-accent-color) !important; + } + } + } +} + +.apis-link-container { + a:hover, + a:focus { + .submenu-icon { + &.apis { + background-color: var(--apis-accent-color) !important; + } + } + } +} + +.learn-link-container { + a:hover, + a:focus { + .submenu-icon { + &.learn { + background-color: var(--learn-accent-color) !important; + } + } + } +} + +.submenu-icon { + &.html { + background-color: var(--html-accent-engage); + } + + &.css { + background-color: var(--css-accent-engage); + } + + &.javascript { + background-color: var(--js-accent-engage); + } + + &.http { + background-color: var(--http-accent-engage); + } + + &.apis { + background-color: var(--apis-accent-engage); + } + + &.learn { + background-color: var(--learn-accent-engage); + } +} diff --git a/client/src/ui/molecules/reference-menu/index.tsx b/client/src/ui/molecules/reference-menu/index.tsx new file mode 100644 index 000000000000..35c550dfa9e3 --- /dev/null +++ b/client/src/ui/molecules/reference-menu/index.tsx @@ -0,0 +1,84 @@ +import { useLocale } from "../../../hooks"; +import { Menu } from "../menu"; + +import "./index.scss"; + +export const ReferenceMenu = ({ visibleSubMenuId, toggleMenu }) => { + const locale = useLocale(); + + const menu = { + id: "references", + label: "References", + to: `/${locale}/docs/Web`, + items: [ + { + description: "Web technology reference for developers", + hasIcon: true, + extraClasses: "apis-link-container mobile-only", + iconClasses: "submenu-icon", + label: "Overview / Web Technology", + url: `/${locale}/docs/Web`, + }, + { + description: "Structure of content on the web", + extraClasses: "html-link-container", + hasIcon: true, + iconClasses: "submenu-icon html", + label: "HTML", + url: `/${locale}/docs/Web/HTML`, + }, + { + description: "Code used to describe document style", + extraClasses: "css-link-container", + hasIcon: true, + iconClasses: "submenu-icon css", + label: "CSS", + url: `/${locale}/docs/Web/CSS`, + }, + { + description: "General-purpose scripting language", + extraClasses: "javascript-link-container", + hasIcon: true, + iconClasses: "submenu-icon javascript", + label: "JavaScript", + url: `/${locale}/docs/Web/JavaScript`, + }, + { + description: "Protocol for transmitting web resources", + extraClasses: "http-link-container", + hasIcon: true, + iconClasses: "submenu-icon http", + label: "HTTP", + url: `/${locale}/docs/Web/HTTP`, + }, + { + description: "Interfaces for building web applications", + extraClasses: "apis-link-container", + hasIcon: true, + iconClasses: "submenu-icon apis", + label: "Web APIs", + url: `/${locale}/docs/Web/API`, + }, + { + description: "Developing extensions for web browsers", + extraClasses: "apis-link-container", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Web Extensions", + url: `/${locale}/docs/Mozilla/Add-ons/WebExtensions`, + }, + { + description: "Web technology reference for developers", + extraClasses: "apis-link-container desktop-only", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Web Technology", + url: `/${locale}/docs/Web`, + }, + ], + }; + + const isOpen = visibleSubMenuId === menu.id; + + return ; +}; diff --git a/client/src/ui/molecules/submenu/index.tsx b/client/src/ui/molecules/submenu/index.tsx index caf480f0f7c8..1931e98db100 100644 --- a/client/src/ui/molecules/submenu/index.tsx +++ b/client/src/ui/molecules/submenu/index.tsx @@ -17,7 +17,6 @@ export type SubmenuItem = { export type MenuEntry = { id: string; - isActive?: boolean; items: SubmenuItem[]; label: string | ReactNode; to?: string; diff --git a/client/src/ui/molecules/theme-switcher/index.tsx b/client/src/ui/molecules/theme-switcher/index.tsx index add18e2e3627..91e143ccb6f0 100644 --- a/client/src/ui/molecules/theme-switcher/index.tsx +++ b/client/src/ui/molecules/theme-switcher/index.tsx @@ -82,7 +82,7 @@ export const ThemeSwitcher = () => { setIsOpen(!isOpen); }} > - Theme + Theme diff --git a/client/src/ui/molecules/main-menu/tools.scss b/client/src/ui/molecules/tools-menu/index.scss similarity index 100% rename from client/src/ui/molecules/main-menu/tools.scss rename to client/src/ui/molecules/tools-menu/index.scss diff --git a/client/src/ui/molecules/tools-menu/index.tsx b/client/src/ui/molecules/tools-menu/index.tsx new file mode 100644 index 000000000000..ce1a83b316e2 --- /dev/null +++ b/client/src/ui/molecules/tools-menu/index.tsx @@ -0,0 +1,40 @@ +import { OBSERVATORY_TITLE } from "../../../../../libs/constants"; +import { useLocale } from "../../../hooks"; +import { Menu } from "../menu"; + +import "./index.scss"; + +export const ToolsMenu = ({ visibleSubMenuId, toggleMenu }) => { + const locale = useLocale(); + + const menu = { + id: "tools", + label: "Tools", + items: [ + { + description: "Write, test and share your code", + hasIcon: true, + iconClasses: "submenu-icon", + label: "Playground", + url: `/${locale}/play`, + }, + { + description: "Scan a website for free", + hasIcon: true, + iconClasses: "submenu-icon", + label: OBSERVATORY_TITLE, + url: `/en-US/observatory`, + }, + { + description: "Get real-time assistance and support", + hasIcon: true, + iconClasses: "submenu-icon", + label: "AI Help", + url: `/en-US/plus/ai-help`, + }, + ], + }; + const isOpen = visibleSubMenuId === menu.id; + + return ; +};