From ecc9f54f5efaacb0d635003c2546771289311edb Mon Sep 17 00:00:00 2001
From: Claas Augner <495429+caugner@users.noreply.github.com>
Date: Fri, 24 Nov 2023 14:28:34 +0100
Subject: [PATCH] feat(menu): highlight active items (#9940)
* fix(plus): add hash to Plus url
* feat(menu): highlight current item
* chore(ui): introduce --text-active + use for active menu items
* chore(submenu): revert highlighting active item
* feat(menu): add isActive prop to override state
* fix(plus-menu): exclude AI Help from isActive state
* fix(main-menu): remove trailing slash from AI Help
* fix(menu): use category color on hover/active
---
client/src/plus/utils.ts | 2 +-
client/src/ui/_vars.scss | 2 ++
client/src/ui/base/_themes.scss | 2 ++
client/src/ui/molecules/main-menu/index.tsx | 9 +++++++--
client/src/ui/molecules/menu/index.scss | 12 ++++++++++++
client/src/ui/molecules/menu/index.tsx | 19 +++++++++++++++++--
client/src/ui/molecules/plus-menu/index.tsx | 19 +++++++++++++++++--
7 files changed, 58 insertions(+), 7 deletions(-)
diff --git a/client/src/plus/utils.ts b/client/src/plus/utils.ts
index 52ed78429820..3682e07f418d 100644
--- a/client/src/plus/utils.ts
+++ b/client/src/plus/utils.ts
@@ -16,7 +16,7 @@ export function usePlusUrl(): string {
let target = `/${locale}/plus`;
if (normalizedUrl(target) === normalizedUrl(pathname)) {
- target = "#subscribe";
+ target += "#subscribe";
}
return target;
diff --git a/client/src/ui/_vars.scss b/client/src/ui/_vars.scss
index b65341b91088..bc2f8155f607 100644
--- a/client/src/ui/_vars.scss
+++ b/client/src/ui/_vars.scss
@@ -154,6 +154,7 @@ $mdn-color-ads: #00d0aa;
$mdn-theme-light-text-primary: $mdn-color-neutral-90;
$mdn-theme-light-text-secondary: $mdn-color-neutral-70;
+$mdn-theme-light-text-active: #{$mdn-color-neutral-50};
$mdn-theme-light-text-inactive: #{$mdn-color-neutral-40}a6;
$mdn-theme-light-text-link: $mdn-color-light-theme-blue-60;
$mdn-theme-light-text-invert: $mdn-color-white;
@@ -200,6 +201,7 @@ $mdn-theme-light-code-background-block: $mdn-color-neutral-light-80;
$mdn-theme-dark-text-primary: $mdn-color-white;
$mdn-theme-dark-text-secondary: $mdn-color-neutral-20;
+$mdn-theme-dark-text-active: #{$mdn-color-neutral-50};
$mdn-theme-dark-text-inactive: #{$mdn-color-neutral-20}a6;
$mdn-theme-dark-text-link: $mdn-color-dark-theme-blue-30;
$mdn-theme-dark-text-invert: $mdn-color-neutral-90;
diff --git a/client/src/ui/base/_themes.scss b/client/src/ui/base/_themes.scss
index 1f9bfb7db77c..223831842e37 100644
--- a/client/src/ui/base/_themes.scss
+++ b/client/src/ui/base/_themes.scss
@@ -5,6 +5,7 @@
@mixin light-theme {
--text-primary: #{$mdn-theme-light-text-primary};
--text-secondary: #{$mdn-theme-light-text-secondary};
+ --text-active: #{$mdn-theme-light-text-active};
--text-inactive: #{$mdn-theme-light-text-inactive};
--text-link: #{$mdn-theme-light-text-link};
--text-visited: #551a8b; // Source: https://searchfox.org/mozilla-central/rev/02841791400cf7cf5760c0cfaf31f5d772624253/modules/libpref/init/StaticPrefList.yaml#1787-1790
@@ -204,6 +205,7 @@
@mixin dark-theme {
--text-primary: #{$mdn-theme-dark-text-primary};
--text-secondary: #{$mdn-theme-dark-text-secondary};
+ --text-active: #{$mdn-theme-dark-text-active};
--text-inactive: #{$mdn-theme-dark-text-inactive};
--text-link: #{$mdn-theme-dark-text-link};
--text-visited: #ffadff; // Source: https://searchfox.org/mozilla-central/rev/02841791400cf7cf5760c0cfaf31f5d772624253/modules/libpref/init/StaticPrefList.yaml#1794-1797
diff --git a/client/src/ui/molecules/main-menu/index.tsx b/client/src/ui/molecules/main-menu/index.tsx
index 6af28855a65e..7a8b10e52e32 100644
--- a/client/src/ui/molecules/main-menu/index.tsx
+++ b/client/src/ui/molecules/main-menu/index.tsx
@@ -9,6 +9,7 @@ import { PLUS_IS_ENABLED } from "../../../env";
import { useLocale } from "../../../hooks";
import { useGleanClick } from "../../../telemetry/glean-context";
import { MENU } from "../../../telemetry/constants";
+import { useLocation } from "react-router";
export default function MainMenu({ isOpenOnMobile }) {
const locale = useLocale();
@@ -83,7 +84,7 @@ export default function MainMenu({ isOpenOnMobile }) {
)}
Blog
Play
-
+
AI Help Beta
@@ -98,9 +99,13 @@ function TopLevelMenuLink({
to: string;
children: React.ReactNode;
}) {
+ const { pathname } = useLocation();
const gleanClick = useGleanClick();
+
+ const isActive = pathname.startsWith(to.split("#", 2)[0]);
+
return (
-
+
a {
+ &:link,
+ &:visited {
+ color: var(--text-active);
+ }
+
+ &:hover,
+ &:active {
+ color: var(--category-color);
+ }
+ }
+
.top-level-entry-dot ~ .top-level-entry::after {
background: var(--text-primary-blue);
border: 1px solid var(--background-primary);
diff --git a/client/src/ui/molecules/menu/index.tsx b/client/src/ui/molecules/menu/index.tsx
index 86a39ea5447a..f89f90ec1440 100644
--- a/client/src/ui/molecules/menu/index.tsx
+++ b/client/src/ui/molecules/menu/index.tsx
@@ -1,3 +1,4 @@
+import { useLocation } from "react-router";
import { MENU } from "../../../telemetry/constants";
import { useGleanClick } from "../../../telemetry/glean-context";
import { MenuEntry, Submenu } from "../submenu";
@@ -5,20 +6,34 @@ import "./index.scss";
interface MenuProps {
menu: MenuEntry;
+ isActive?: boolean;
isOpen: boolean;
toggle: (id: string) => void;
}
-export const Menu = ({ menu, isOpen, toggle }: MenuProps) => {
+export const Menu = ({
+ menu,
+ isActive = undefined,
+ isOpen,
+ toggle,
+}: MenuProps) => {
+ const { pathname } = useLocation();
const gleanClick = useGleanClick();
const buttonId = `${menu.id}-button`;
const submenuId = `${menu.id}-menu`;
+ isActive =
+ isActive ??
+ (typeof menu.to === "string" &&
+ pathname.startsWith(menu.to.split("#", 2)[0]));
const hasAnyDot = menu.items.some((item) => item.dot);
return (
-
+
{hasAnyDot && (
)}
diff --git a/client/src/ui/molecules/plus-menu/index.tsx b/client/src/ui/molecules/plus-menu/index.tsx
index 9c3d4be8909a..9455dc5011cb 100644
--- a/client/src/ui/molecules/plus-menu/index.tsx
+++ b/client/src/ui/molecules/plus-menu/index.tsx
@@ -5,6 +5,7 @@ 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();
@@ -15,6 +16,13 @@ export const PlusMenu = ({ visibleSubMenuId, toggleMenu }) => {
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",
@@ -32,7 +40,7 @@ export const PlusMenu = ({ visibleSubMenuId, toggleMenu }) => {
hasIcon: true,
iconClasses: "submenu-icon",
label: "AI Help (beta)",
- url: `/${locale}/plus/ai-help`,
+ url: aiHelpUrl,
},
...(!isServer && isAuthenticated
? [
@@ -75,5 +83,12 @@ export const PlusMenu = ({ visibleSubMenuId, toggleMenu }) => {
};
const isOpen = visibleSubMenuId === plusMenu.id;
- return ;
+ return (
+
+ );
};