feat: Update navigation structure and add subitems

This commit is contained in:
Gauthier Daniels 2025-04-13 21:16:47 +02:00
parent 82258addeb
commit 717ca15a83
3 changed files with 108 additions and 43 deletions

View File

@ -50,7 +50,7 @@ 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-9" 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"
> >
{props.section.links.map((link) => ( {props.section.links.map((link) => (
<li key={link.href} className="relative"> <li key={link.href} className="relative">
@ -67,7 +67,9 @@ function NavigationItem(props: NavigationItemProps) {
)} )}
> >
{link.title} {link.title}
{link.subitems && <span className="text-slate-400 dark:text-slate-500"> ({link.subitems.length})</span>} {link.subitems.length > 0 && (
<span className="text-slate-400 dark:text-slate-500"> ({link.subitems.length})</span>
)}
</Link> </Link>
{link.subitems && ( {link.subitems && (
<ul <ul
@ -125,11 +127,12 @@ export function Navigation({
return ( return (
<nav className={clsx("text-base lg:text-sm", className)}> <nav className={clsx("text-base lg:text-sm", className)}>
<ul role="list" className="space-y-4"> <ul role="list" className="space-y-4">
{firstSections.map((section) => ( <li>
<li key={section.title}> <h2 className="font-display font-bold text-base text-slate-900 dark:text-white">{firstSections[0]?.type}</h2>
<NavigationItem section={section} onLinkClick={onLinkClick} /> {firstSections.map((section) => (
</li> <NavigationItem key={section.title} section={section} onLinkClick={onLinkClick} />
))} ))}
</li>
{Object.entries(filteredSections).map(([type, sections]) => ( {Object.entries(filteredSections).map(([type, sections]) => (
<li key={type}> <li key={type}>
<h2 className="font-display font-bold text-base text-slate-900 dark:text-white">{type}</h2> <h2 className="font-display font-bold text-base text-slate-900 dark:text-white">{type}</h2>

View File

@ -1,9 +1,8 @@
import { navigation, NavigationLink, type NavigationSubItem } from "@/lib/navigation";
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 clsx from "clsx"; import clsx from "clsx";
import { navigation } from "@/lib/navigation";
function ArrowIcon(props: React.ComponentPropsWithoutRef<"svg">) { function ArrowIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return ( return (
<svg viewBox="0 0 16 16" aria-hidden="true" {...props}> <svg viewBox="0 0 16 16" aria-hidden="true" {...props}>
@ -23,8 +22,8 @@ function PageLink({
dir?: "previous" | "next"; dir?: "previous" | "next";
}) { }) {
const pageCategory = navigation.find((section) => { const pageCategory = navigation.find((section) => {
return section.links.some((link) => link.href === href); return section.links.some((link) => link.href === href || link.subitems.some((subitem) => subitem.href === href));
})!; });
return ( return (
<div {...props}> <div {...props}>
@ -40,7 +39,9 @@ function PageLink({
)} )}
> >
<p className="flex flex-col gap-0"> <p className="flex flex-col gap-0">
<span className="text-violet-600 dark:text-violet-400 text-sm -mb-3">{pageCategory.title}</span> {pageCategory && (
<span className="text-violet-600 dark:text-violet-400 text-sm -mb-3">{pageCategory.title}</span>
)}
<span>{title}</span> <span>{title}</span>
</p> </p>
<ArrowIcon className={clsx("h-6 w-6 flex-none fill-current", dir === "previous" && "-scale-x-100")} /> <ArrowIcon className={clsx("h-6 w-6 flex-none fill-current", dir === "previous" && "-scale-x-100")} />
@ -51,12 +52,45 @@ function PageLink({
} }
export function PrevNextLinks() { export function PrevNextLinks() {
const allLinks = navigation.flatMap((section) => section.links);
const { urlPathname } = usePageContext(); const { urlPathname } = usePageContext();
const allLinks = navigation.flatMap((section) => section.links); let subItemElement: undefined | NavigationSubItem;
const linkIndex = allLinks.findIndex((link) => link.href === urlPathname);
const previousPage = linkIndex > -1 ? allLinks[linkIndex - 1] : null; const findLinkIndex = (pathname = urlPathname) => {
const nextPage = linkIndex > -1 ? allLinks[linkIndex + 1] : null; for (let i = 0; i < allLinks.length; i++) {
const link = allLinks[i];
if (link.href === urlPathname) {
return i;
}
if (link.subitems) {
const subitemIndex = link.subitems.findIndex((subitem) => subitem.href === urlPathname);
if (subitemIndex !== -1) {
subItemElement = link.subitems[subitemIndex];
return i;
}
}
}
};
const linkIndex = findLinkIndex();
if (linkIndex === undefined) return null;
let previousPage: NavigationSubItem | NavigationLink | null = linkIndex > -1 ? allLinks[linkIndex - 1] : null;
let nextPage: NavigationSubItem | NavigationLink | null = linkIndex > -1 ? allLinks[linkIndex + 1] : null;
if (subItemElement !== undefined) {
const subItemIndex = findLinkIndex(subItemElement.href)!;
const currentPage = allLinks[subItemIndex];
const subItemIndexInLink = currentPage.subitems?.findIndex((subitem) => subitem.href === urlPathname);
if (subItemIndexInLink !== undefined && subItemIndexInLink > -1) {
previousPage = currentPage.subitems[subItemIndexInLink - 1];
nextPage = currentPage.subitems[subItemIndexInLink + 1];
}
}
if (!nextPage && !previousPage) { if (!nextPage && !previousPage) {
return null; return null;

View File

@ -4,15 +4,42 @@ const navigationsTypes = {
DOCUMENTATIONS: "📚 Documentations", DOCUMENTATIONS: "📚 Documentations",
}; };
export const navigation = [ export type NavigationSection = {
title: string;
type: (typeof navigationsTypes)[keyof typeof navigationsTypes];
position: "start" | "auto";
links: NavigationLink[];
};
export type NavigationLink = {
title: string;
href: string;
subitems: NavigationSubItem[];
};
export type NavigationSubItem = {
title: string;
href: string;
};
export const navigation: NavigationSection[] = [
{ {
title: "Préambule", title: "Préambule",
type: navigationsTypes.GLOBAL, type: navigationsTypes.GLOBAL,
position: "start", position: "start",
links: [ links: [
{ title: "Memento Dev", href: "/" }, { title: "Memento Dev", href: "/", subitems: [] },
{ title: "Certifications", href: "/certifications" }, { title: "Certifications", href: "/certifications", subitems: [] },
{ title: "Documentations", href: "/docs" }, { title: "Documentations", href: "/docs", subitems: [] },
],
},
{
title: "Communauté",
type: navigationsTypes.GLOBAL,
position: "start",
links: [
{ title: "Influenceurs", href: "/docs/communaute/influenceurs", subitems: [] },
{ title: "Partages et réutilisations", href: "/docs/communaute/partages", subitems: [] },
], ],
}, },
{ {
@ -20,7 +47,7 @@ export const navigation = [
type: navigationsTypes.CERTIFICATIONS, type: navigationsTypes.CERTIFICATIONS,
position: "auto", position: "auto",
links: [ links: [
{ title: "Résumé", href: "/certifications/dwwm" }, { title: "Résumé", href: "/certifications/dwwm", subitems: [] },
{ {
title: "Activité Type 1", title: "Activité Type 1",
href: "/certifications/dwwm/at1", href: "/certifications/dwwm/at1",
@ -44,37 +71,38 @@ export const navigation = [
], ],
}, },
{ {
title: "React", title: "Front-end",
type: navigationsTypes.DOCUMENTATIONS, type: navigationsTypes.DOCUMENTATIONS,
position: "auto", position: "auto",
links: [ links: [
{ title: "Introduction", href: "/docs/react" }, {
{ title: "Initialisation", href: "/docs/react/initialisation" }, title: "React",
{ title: "Syntaxe JSX", href: "/docs/react/jsx" }, href: "/docs/react",
{ title: "Premier composant", href: "/docs/react/premier-composant" }, subitems: [
{ title: "State et cycle de vie", href: "/docs/react/state-et-cycle-de-vie" }, { title: "Initialisation", href: "/docs/react/initialisation" },
{ title: "Hooks", href: "/docs/react/hooks" }, { title: "Syntaxe JSX", href: "/docs/react/jsx" },
{ title: "Le hook useContext", href: "/docs/react/use-context" }, { title: "Premier composant", href: "/docs/react/premier-composant" },
{ title: "Le hook useReducer", href: "/docs/react/use-reducer" }, { title: "State et cycle de vie", href: "/docs/react/state-et-cycle-de-vie" },
{ title: "Hooks", href: "/docs/react/hooks" },
{ title: "Le hook useContext", href: "/docs/react/use-context" },
{ title: "Le hook useReducer", href: "/docs/react/use-reducer" },
],
},
], ],
}, },
{ {
title: "Merise", title: "Base de données",
type: navigationsTypes.DOCUMENTATIONS, type: navigationsTypes.DOCUMENTATIONS,
position: "auto", position: "auto",
links: [ links: [
{ title: "Introduction", href: "/docs/merise" }, {
{ title: "Dictionnaire de données", href: "/docs/merise/dictionnaire-de-donnees" }, title: "Merise",
{ title: "Modèle Conceptuel de Données", href: "/docs/merise/modele-conceptuel-de-donnees" }, href: "/docs/merise",
], subitems: [
}, { title: "Dictionnaire de données", href: "/docs/merise/dictionnaire-de-donnees" },
{ { title: "Modèle Conceptuel de Données", href: "/docs/merise/modele-conceptuel-de-donnees" },
title: "Communauté", ],
type: navigationsTypes.GLOBAL, },
position: "start",
links: [
{ title: "Influenceurs", href: "/docs/communaute/influenceurs" },
{ title: "Partages et réutilisations", href: "/docs/communaute/partages" },
], ],
}, },
]; ];