feat: Add useEffect and NavigationSubItem component
All checks were successful
Update Memento Dev on VPS / deploy (push) Successful in 49s
All checks were successful
Update Memento Dev on VPS / deploy (push) Successful in 49s
This commit is contained in:
parent
965534659e
commit
6695c7c023
@ -2,7 +2,7 @@ import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/solid";
|
||||
import { usePageContext } from "vike-react/usePageContext";
|
||||
import { Link } from "@/components/common/Link";
|
||||
import { navigation } from "@/lib/navigation";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
type NavigationItemProps = {
|
||||
@ -50,33 +50,88 @@ function NavigationItem(props: NavigationItemProps) {
|
||||
{isOpened && (
|
||||
<ul
|
||||
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) => (
|
||||
<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
|
||||
href={link.href}
|
||||
href={props.link.href}
|
||||
onClick={props.onLinkClick}
|
||||
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",
|
||||
{ "before:top-1/2 before:-translate-y-1/2": !link.subitems },
|
||||
{ "before:top-3 before:-translate-y-1/2": link.subitems },
|
||||
link.href === urlPathname || link.subitems?.some((subitem) => subitem.href === urlPathname)
|
||||
? "font-semibold text-violet-500 before:bg-violet-500"
|
||||
: "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",
|
||||
"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": !props.link.subitems },
|
||||
{ "before:top-3 before:-translate-y-1/2 font-semibold": props.link.subitems },
|
||||
props.link.href !== urlPathname && "before:hidden",
|
||||
isOpened
|
||||
? "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}
|
||||
{link.subitems.length > 0 && (
|
||||
<span className="text-slate-400 dark:text-slate-500"> ({link.subitems.length})</span>
|
||||
{props.link.title}
|
||||
{props.link.subitems.length > 0 && (
|
||||
<span className="text-slate-400 dark:text-slate-500"> ({props.link.subitems.length})</span>
|
||||
)}
|
||||
</Link>
|
||||
{link.subitems && (
|
||||
</span>
|
||||
{props.link.subitems && isOpened && (
|
||||
<ul
|
||||
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"
|
||||
>
|
||||
{link.subitems.map((subitem) => (
|
||||
{props.link.subitems.map((subitem) => (
|
||||
<li key={subitem.href} className="relative">
|
||||
<Link
|
||||
href={subitem.href}
|
||||
@ -94,10 +149,6 @@ function NavigationItem(props: NavigationItemProps) {
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,9 +11,14 @@ export type NavigationSection = {
|
||||
links: NavigationLink[];
|
||||
};
|
||||
|
||||
export type NavigationOG = Partial<{
|
||||
image: string;
|
||||
}>;
|
||||
|
||||
export type NavigationLink = {
|
||||
title: string;
|
||||
href: string;
|
||||
og?: NavigationOG;
|
||||
subitems: NavigationSubItem[];
|
||||
};
|
||||
|
||||
@ -101,6 +106,7 @@ export const navigation: NavigationSection[] = [
|
||||
{
|
||||
title: "Merise",
|
||||
href: "/docs/merise",
|
||||
og: { image: "/merise/og.webp" },
|
||||
subitems: [
|
||||
{ title: "Introduction", href: "/docs/merise" },
|
||||
{ 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;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import logoUrl from "../assets/logo.svg";
|
||||
import logoUrl from "@/assets/logo.svg";
|
||||
|
||||
export default function HeadDefault() {
|
||||
return (
|
||||
|
||||
@ -23,6 +23,8 @@ export default {
|
||||
class: "flex min-h-full bg-white dark:bg-slate-900",
|
||||
},
|
||||
|
||||
image: null,
|
||||
|
||||
// prerender: true,
|
||||
prefetchStaticAssets: "hover",
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { PageContext } from "vike/types";
|
||||
|
||||
import { snippetsService } from "@/services/SnippetsService";
|
||||
import { findNavigationLink } from "@/lib/navigation";
|
||||
import { docsService } from "@/services/DocsService";
|
||||
import { readingTime } from "reading-time-estimator";
|
||||
import { useConfig } from "vike-react/useConfig";
|
||||
@ -15,6 +16,7 @@ export async function data(pageContext: PageContext) {
|
||||
const { key } = pageContext.routeParams;
|
||||
|
||||
const doc = await docsService.getDoc("docs", key);
|
||||
const link = findNavigationLink("docs", key);
|
||||
|
||||
if (!doc) {
|
||||
throw render(404);
|
||||
@ -25,6 +27,7 @@ export async function data(pageContext: PageContext) {
|
||||
config({
|
||||
title: buildTitle(doc.title),
|
||||
description: doc.description,
|
||||
image: link?.og?.image || "notfound",
|
||||
});
|
||||
|
||||
docsService.transform(doc);
|
||||
|
||||
BIN
app/public/merise/og.webp
Normal file
BIN
app/public/merise/og.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Loading…
Reference in New Issue
Block a user