Detect active nav item via route handle instead of path matching

This commit is contained in:
Remmy Cat Stock 2022-10-21 18:29:23 +02:00
parent 85fda204c0
commit 3ce7c00d5b
No known key found for this signature in database
GPG Key ID: E3F847E89FAC01C7
9 changed files with 48 additions and 10 deletions

View File

@ -1,7 +1,8 @@
import { Link, useLocation } from "@remix-run/react";
import { Link, useMatches } from "@remix-run/react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import type { RootLoaderData } from "~/root";
import { type SendouRouteHandle } from "~/utils/remix";
import { LOGO_PATH, navIconUrl } from "~/utils/urls";
import { Image } from "../Image";
import { ColorModeToggle } from "./ColorModeToggle";
@ -12,6 +13,25 @@ import { Menu } from "./Menu";
import navItems from "./nav-items.json";
import { UserItem } from "./UserItem";
function useActiveNavItem() {
const matches = useMatches();
return React.useMemo(() => {
let activeItem: { name: string; url: string } | undefined = undefined;
for (const match of matches.reverse()) {
const handle = match.handle as SendouRouteHandle | undefined;
if (handle?.navItemName) {
activeItem = navItems.find(({ name }) => name === handle.navItemName);
break;
}
}
return activeItem;
}, [matches]);
}
export const Layout = React.memo(function Layout({
children,
patrons,
@ -22,12 +42,8 @@ export const Layout = React.memo(function Layout({
isCatchBoundary?: boolean;
}) {
const { t } = useTranslation();
const location = useLocation();
const [menuOpen, setMenuOpen] = React.useState(false);
const currentPagesNavItem = navItems.find((navItem) =>
location.pathname.includes(navItem.name)
);
const activeNavItem = useActiveNavItem();
return (
<div className="layout__container">
@ -51,15 +67,15 @@ export const Layout = React.memo(function Layout({
</div>
</header>
<Menu expanded={menuOpen} closeMenu={() => setMenuOpen(false)} />
{currentPagesNavItem && (
{activeNavItem && (
<div className="layout__page-title-header">
<Image
path={navIconUrl(currentPagesNavItem.name)}
path={navIconUrl(activeNavItem.name)}
width={28}
height={28}
alt=""
/>
{t(`pages.${currentPagesNavItem.name}` as any)}
{t(`pages.${activeNavItem.name}` as any)}
</div>
)}
{children}

View File

@ -18,7 +18,11 @@ import { Main } from "~/components/Main";
import { requireUser } from "~/modules/auth";
import { getUser, isImpersonating } from "~/modules/auth/user.server";
import { canPerformAdminActions } from "~/permissions";
import { parseRequestFormData, validate } from "~/utils/remix";
import {
parseRequestFormData,
type SendouRouteHandle,
validate,
} from "~/utils/remix";
import { makeTitle } from "~/utils/strings";
import { impersonateUrl, SEED_URL, STOP_IMPERSONATING_URL } from "~/utils/urls";
import { db } from "~/db";
@ -69,6 +73,10 @@ export const loader: LoaderFunction = async ({ request }) => {
});
};
export const handle: SendouRouteHandle = {
navItemName: "admin",
};
export default function AdminPage() {
return (
<Main className="stack lg">

View File

@ -52,6 +52,7 @@ export const links: LinksFunction = () => {
export const handle: SendouRouteHandle = {
i18n: ["weapons", "analyzer"],
navItemName: "analyzer",
};
export default function BuildAnalyzerPage() {

View File

@ -21,6 +21,7 @@ export interface BadgesLoaderData {
export const handle: SendouRouteHandle = {
i18n: "badges",
navItemName: "badges",
};
export const loader: LoaderFunction = () => {

View File

@ -16,6 +16,7 @@ export const links: LinksFunction = () => {
export const handle: SendouRouteHandle = {
i18n: ["weapons", "builds"],
breadcrumb: ({ t }) => t("pages.builds"),
navItemName: "builds",
};
export default function BuildsLayoutPage() {

View File

@ -48,6 +48,7 @@ export const meta: MetaFunction = (args) => {
export const handle: SendouRouteHandle = {
i18n: "calendar",
navItemName: "calendar",
};
const loaderSearchParamsSchema = z.object({

View File

@ -66,6 +66,7 @@ export const meta: MetaFunction = (args) => {
export const handle: SendouRouteHandle = {
i18n: "game-misc",
navItemName: "maps",
};
export const loader = async ({ request }: LoaderArgs) => {

View File

@ -3,11 +3,16 @@ import { Outlet } from "@remix-run/react";
import { Main } from "~/components/Main";
import { SubNav, SubNavLink } from "~/components/SubNav";
import styles from "~/styles/plus.css";
import { type SendouRouteHandle } from "~/utils/remix";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
export const handle: SendouRouteHandle = {
navItemName: "plus",
};
export default function PlusPageLayout() {
return (
<>

View File

@ -102,6 +102,7 @@ export function validate(condition: any, status = 400): asserts condition {
export type SendouRouteHandle = {
/** The i18n translation files used for this route, via remix-i18next */
i18n?: Namespace;
/**
* A function that returns the breadcrumb text that should be displayed in
* the <Breadcrumb> component
@ -110,4 +111,7 @@ export type SendouRouteHandle = {
match: RouteMatch;
t: TFunction<"common", undefined>;
}) => string | undefined;
/** The name of a navItem that is active on this route. See nav-items.json */
navItemName?: string;
};