feat: Add useEffect and NavigationSubItem component
All checks were successful
Update Memento Dev on VPS / deploy (push) Successful in 49s

This commit is contained in:
Gauthier Daniels 2025-04-17 19:53:58 +02:00
parent 965534659e
commit 6695c7c023
6 changed files with 110 additions and 35 deletions

View File

@ -2,7 +2,7 @@ import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/solid";
import { usePageContext } from "vike-react/usePageContext"; import { usePageContext } from "vike-react/usePageContext";
import { Link } from "@/components/common/Link"; import { Link } from "@/components/common/Link";
import { navigation } from "@/lib/navigation"; import { navigation } from "@/lib/navigation";
import { useState } from "react"; import { useEffect, useState } from "react";
import clsx from "clsx"; import clsx from "clsx";
type NavigationItemProps = { type NavigationItemProps = {
@ -50,33 +50,88 @@ function NavigationItem(props: NavigationItemProps) {
{isOpened && ( {isOpened && (
<ul <ul
role="list" role="list"
className="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800 mb-4" className="!mt-0 ml-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800 mb-4"
> >
{props.section.links.map((link) => ( {props.section.links.map((link) => (
<li key={link.href} className="relative"> <li key={link.href} className="relative">
<NavigationSubItem
link={link}
onLinkClick={props.onLinkClick}
isOpened={link.href === urlPathname || link.subitems?.some((subitem) => subitem.href === urlPathname)}
/>
</li>
))}
</ul>
)}
</>
);
}
type NavigationSubItemProps = {
link: (typeof navigation)[number]["links"][number];
onLinkClick?: React.MouseEventHandler<HTMLAnchorElement>;
isOpened?: boolean;
};
function NavigationSubItem(props: NavigationSubItemProps) {
const [isOpened, setIsOpened] = useState(props.isOpened);
const { urlPathname } = usePageContext();
useEffect(() => {
setIsOpened(
props.link.href === urlPathname || props.link.subitems?.some((subitem) => subitem.href === urlPathname),
);
}, [urlPathname, props.link]);
return (
<>
<span className="pl-2 flex cursor-pointer">
{props.link.subitems.length > 0 && (
<span
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
setIsOpened((prev) => !prev);
e.preventDefault();
}
}}
onClick={() => setIsOpened((prev) => !prev)}
>
{isOpened ? (
<ChevronUpIcon className="inline-block h-5 w-5 text-slate-400" />
) : (
<ChevronDownIcon className="inline-block h-5 w-5 text-slate-400" />
)}
<span className="sr-only">{isOpened ? "Masquer" : "Afficher"}</span>
</span>
)}
<Link <Link
href={link.href} href={props.link.href}
onClick={props.onLinkClick} onClick={props.onLinkClick}
className={clsx( className={clsx(
"block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:h-1.5 before:w-1.5 before:rounded-full", "block pl-2 w-full before:pointer-events-none before:absolute before:-left-1 before:h-1.5 before:w-1.5 before:rounded-full",
{ "before:top-1/2 before:-translate-y-1/2": !link.subitems }, { "before:top-1/2 before:-translate-y-1/2": !props.link.subitems },
{ "before:top-3 before:-translate-y-1/2": link.subitems }, { "before:top-3 before:-translate-y-1/2 font-semibold": props.link.subitems },
link.href === urlPathname || link.subitems?.some((subitem) => subitem.href === urlPathname) props.link.href !== urlPathname && "before:hidden",
? "font-semibold text-violet-500 before:bg-violet-500" isOpened
: "text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300", ? "text-violet-500 before:bg-violet-500"
: "text-slate-500 before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300",
)} )}
> >
{link.title} {props.link.title}
{link.subitems.length > 0 && ( {props.link.subitems.length > 0 && (
<span className="text-slate-400 dark:text-slate-500"> ({link.subitems.length})</span> <span className="text-slate-400 dark:text-slate-500"> ({props.link.subitems.length})</span>
)} )}
</Link> </Link>
{link.subitems && ( </span>
{props.link.subitems && isOpened && (
<ul <ul
role="list" role="list"
className="ml-4 border-l-2 border-slate-100 lg:space-y-1 lg:border-slate-200 dark:border-slate-800 mb-4" className="ml-4 border-l-2 border-slate-100 lg:space-y-1 lg:border-slate-200 dark:border-slate-800 mb-4"
> >
{link.subitems.map((subitem) => ( {props.link.subitems.map((subitem) => (
<li key={subitem.href} className="relative"> <li key={subitem.href} className="relative">
<Link <Link
href={subitem.href} href={subitem.href}
@ -94,10 +149,6 @@ function NavigationItem(props: NavigationItemProps) {
))} ))}
</ul> </ul>
)} )}
</li>
))}
</ul>
)}
</> </>
); );
} }

View File

@ -11,9 +11,14 @@ export type NavigationSection = {
links: NavigationLink[]; links: NavigationLink[];
}; };
export type NavigationOG = Partial<{
image: string;
}>;
export type NavigationLink = { export type NavigationLink = {
title: string; title: string;
href: string; href: string;
og?: NavigationOG;
subitems: NavigationSubItem[]; subitems: NavigationSubItem[];
}; };
@ -101,6 +106,7 @@ export const navigation: NavigationSection[] = [
{ {
title: "Merise", title: "Merise",
href: "/docs/merise", href: "/docs/merise",
og: { image: "/merise/og.webp" },
subitems: [ subitems: [
{ title: "Introduction", href: "/docs/merise" }, { title: "Introduction", href: "/docs/merise" },
{ title: "Dictionnaire de données", href: "/docs/merise/dictionnaire-de-donnees" }, { title: "Dictionnaire de données", href: "/docs/merise/dictionnaire-de-donnees" },
@ -112,3 +118,16 @@ export const navigation: NavigationSection[] = [
], ],
}, },
]; ];
export function findNavigationLink(namespace: string, href: string) {
const currentUrl = `/${namespace}/${href}`.replace(/\/+/g, "/").replace(/\/$/, "");
const links = navigation.flatMap((section) => section.links);
const subitems = links.flatMap((link) => link.subitems);
const allLinks = new Set([...links, ...subitems]);
const foundLink = Array.from(allLinks).find((link) => link.href === currentUrl);
console.log({ allLinks, currentUrl });
return foundLink;
}

View File

@ -1,4 +1,4 @@
import logoUrl from "../assets/logo.svg"; import logoUrl from "@/assets/logo.svg";
export default function HeadDefault() { export default function HeadDefault() {
return ( return (

View File

@ -23,6 +23,8 @@ export default {
class: "flex min-h-full bg-white dark:bg-slate-900", class: "flex min-h-full bg-white dark:bg-slate-900",
}, },
image: null,
// prerender: true, // prerender: true,
prefetchStaticAssets: "hover", prefetchStaticAssets: "hover",

View File

@ -1,6 +1,7 @@
import type { PageContext } from "vike/types"; import type { PageContext } from "vike/types";
import { snippetsService } from "@/services/SnippetsService"; import { snippetsService } from "@/services/SnippetsService";
import { findNavigationLink } from "@/lib/navigation";
import { docsService } from "@/services/DocsService"; import { docsService } from "@/services/DocsService";
import { readingTime } from "reading-time-estimator"; import { readingTime } from "reading-time-estimator";
import { useConfig } from "vike-react/useConfig"; import { useConfig } from "vike-react/useConfig";
@ -15,6 +16,7 @@ export async function data(pageContext: PageContext) {
const { key } = pageContext.routeParams; const { key } = pageContext.routeParams;
const doc = await docsService.getDoc("docs", key); const doc = await docsService.getDoc("docs", key);
const link = findNavigationLink("docs", key);
if (!doc) { if (!doc) {
throw render(404); throw render(404);
@ -25,6 +27,7 @@ export async function data(pageContext: PageContext) {
config({ config({
title: buildTitle(doc.title), title: buildTitle(doc.title),
description: doc.description, description: doc.description,
image: link?.og?.image || "notfound",
}); });
docsService.transform(doc); docsService.transform(doc);

BIN
app/public/merise/og.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB