Zum Hauptinhalt springen

Nav Komponente Navigation

Datei-Pfad: theme/components/Nav.js

Die Nav-Komponente ist für die Haupt-Navigation zuständig. Sie ist auf eine mobile Ansicht (SM-Breakpoint) angepasst. Die Komponente ist sehr umfangreich. Vielleicht ist also nicht an jeder Stelle alles ganz verständlich. Als Grundlage für die Komponente habe ich eine Navbar von Tailwind UI(die kostenlose Variante "Simple dark with menu button on left" mit dem React-Code). Diese Komponente nutzt schon von Haus einige Komponenten von Headless UI und Icons von heroicons. Ich habe den Beispiel-Code dann an einigen Stellen verändert und erweitert.

Navigation in der Desktop-Ansicht

Navigation in der Desktop-Ansicht

Navigation in der Mobile Ansicht

Navigation in der Mobile Ansicht

Daten für die Navigation

Die Daten für die Navigation (Labels, Pfade, Unterpunkte) kommen aus Strapi (Content-Type mainNav).

const { loading, error, data } = useQuery(QUERY, barer_authentication);

if (loading) {
return <Loader />;
}

if (error) {
if (
error.graphQLErrors[0]?.extensions?.code == "FORBIDDEN" ||
error.networkError.statusCode == 401
) {
window.location.reload();
}
}

Wenn die Daten aus Strapi noch geladen werden, wird die <Loader> Komponente gerendert. Es gab Probleme mit dem Loading weshalb ich die Schreife if(error) {...} eingebaut habe, den bei dem ersten Loading nach der Anmeldung können die Navigationspunkte warum auch immer noch nicht geladen werden. Die Navigation würde also so lange laden, bis sie Berechtigungen für mainNavbar erhält.

Meta Navigation

Die Meta-Navigation ist von der mainNavbar unabhängig. In der Desktop-Ansicht befindet sie sich oberhalb der eigentlichen Navigation. In der Navigation links befinden sich Links zu anderen Northware Apps.

<Menu as="div" className="appswitch-menu">
{process.env.NEXT_PUBLIC_CP_FRONT !== "current" ? (
<Menu.Button className="appswitch-item">
<Link
href={process.env.NEXT_PUBLIC_CP_FRONT}
className="appswitch-link hover:decoration-cockpit-500"
>
<span>
Northware <span className="text-cockpit-500">Cockpit</span>
</span>
</Link>
</Menu.Button>
) : (
""
)}
...
</Menu>

Welche Apps hier zu sehen sind, richtet sich nach den Environment-Variablen der jeweiligen App. In jeder App sollten die Environment-Variablen NEXT_PUBLIC_CP_FRONT(URL des Cockpit-Frontend), NEXT_PUBLIC_FI_FRONT(URL des Finance-Frontend), NEXT_PUBLIC_TRD_FRONT(URL des Trader Frontend) usw. definiert sein. In der App in der sich der User gerade befindet sollte die Environment-Variable current lauten. Wenn sich der User also im Northware Cockpit befindet lautet die Environment-Variable NEXT_PUBLIC_CP_FRONT = 'current', also wird der Link zu Northware Cockpit hier nicht angezeigt.

Auf der rechten Seite der Meta-Navigation wird der aktuell angemeldete User angezeigt und es gibt einen Link für den Logout.

<Menu as="div" className="metanav-right">
<p className="flex items-center">
<UserIcon className="h-4 mr-2" />
{user?.username || "Nicht angemeldet"}
</p>
<Menu.Button className="menuitem-meta" onClick={handleLogout}>
<LockClosedIcon className="h-4 mr-2" />
Abmelden
</Menu.Button>
</Menu>

Der Benutzername user wird oberhalb des return()-Blocks über useAuthContext() von Strapi gefetcht.

Der Button zum abmelden führt die Funktion handleLogout() aus. Diese Funktion löscht alle Daten, die den User beinhalten und führt den User zurück zum Root. In der Layout-Komponente wird dadurch das Login-Formular ausgegeben.

// User Auth und Logout

const { user, setUser } = useAuthContext();

function handleLogout() {
setUser(null);
Cookie.remove("token");
router.push("/");
}

Die Meta-Navigation wird von einem Div umgeben, der die Tailwind-Klassen .hidden und .md:flex besitzt. Das führt dazu, dass diese Meta-Navigation so nur in der Desktop-Ansicht zu sehen ist.

Disclosure .mainnav

Die Hauptnavigation wird von einem <Disclosure> umschlossen. Die Komponente ermöglicht eine Funktionalität die man zum Beispiel als Dropdown oder eben als Grundlage für ein Responsives Menü einsetzen kann.

Das Disclosure wird hier nur als Grundlage genutzt. Es gibt einen Disclosure.Button (ein Hamburger-Menü Toggle-Button) der aber von einem Div umgeben wird, der den Button auf größeren Bildschirmen mit .md:hidden versteckt. Auf Bildschirmen ab dem md-Breakpoint kann über den Disclosure.Button die mobile Navigation aufgerufen werden.

Die mobile Navigation selbst befindet sich im Disclosure.Panel also dem Wrapper der sichtbar wird, wenn der User auf den Disclosure.Button tippt.

Branding

Auf beiden Ansichten wird das Logo der App angezeigt. In den Desktop-Ansichten befindet sich das Logo links in der Navigation. In der mobilen Ansicht rutscht das Logo in die Mitte der Navigation.

Desktop only Navigation

Das Menü für die Desktop-Ansicht wird von einem Div umgeben, der die Klassen hidden md:flex besitzt, damit dieses Menü nur in Desktop-Ansichten zu sehen ist. Hier geht es nur um die Menüpunkte der Hauptnavigation.

Für jeden Eintrag im Array navcontent wird ein Menüpunkt gerendert.

Der Array navcontent wird oberhalb des return()-Bereichs definiert. Und beinhaltet die Menüpunkte für die jeweilige App.

let navcontent = data?.mainNavbar?.data?.attributes?.mainnav_cockpit;
if (service === "cockpit") {
navcontent = data?.mainNavbar?.data?.attributes?.mainnav_cockpit;
}
if (service === "finance") {
navcontent = data?.mainNavbar?.data?.attributes?.mainnav_finance;
}
if (service === "trader") {
navcontent = data?.mainNavbar?.data?.attributes?.mainnav_trader;
}
if (service === "pos") {
navcontent = data?.mainNavbar?.data?.attributes?.mainnav_pos;
}

Innerhalb des gerenderten Menüpunktes wird unterschieden zwischen Menüpunkten mit Unterpunkten (children-Array) und ohne Unterpunkte. Das funktioniert mit dieser Abfrage:

{item.children.length !== 0 ? (
// Menüpunkt mit Unterpunten
...
) : (
// Menüpunkt ohne Unterpunkte
)}

Menüpunkt mit Unterpunkten

Menüpunkte mit Unterpunkten sind <Menu> Komponenten. Der Menu.Button hat das Label des übergeordneten Menüpunktes. Es ist aber kein Link sondern togglet nur das Dropdown.

Die Komponente Transition umschließt das Dropdown-Menü um den onclick-Moment zu steuern und einen Effekt abzuspielen.

Die Komponente Menu.Items ist der Wrapper des Dropdowns und beinhaltet die einzelnen Menu.Item Komponenten.

Befindet sich der User auf einer untergeordneten Seite des Menüpunktes (muss nicht als Unterpunkt angelegt sein), erhät der Menüpunkt die Klasse .menuitem-active wenn die Funktion isMenuActive(item, pathsegments) true zurückgibt.

Der erste Menu.Item sieht anders aus als die weiteren Items, da es sich hier um den Link zur übergeordneten Seite zu ermöglichen.

Alle weiteren Menu.Item werden aus dem Array von item.children generiert.

Befindet sich der User auf der Seite, auf die der untergeordnete Menüpunkt verlinkt, erhält der Menüpunkt die Klasse .dropdown-menuitem-active wenn die Funktion isDropdownActive(childItem, pathname) true zurückgibt.

Menüpunkt ohne Unterpunkte

Wenn der Menüpunkt keine Unterpunkte hat, wird ebenfalls eine Menu Komponente genutzt. Sie beinhaltet jedoch nur einen Link zu item.href und den Text von item.label.

Befindet sich der User auf der Seite, auf die der Menüpunkt verlinkt oder auf einer untergeordneten Seite die nicht als Unterpunkt angelegt ist, erhält der Menüpunkt die Klasse .menuitem-active wenn die Funktion isMenuActive(item, pathsegments) true zurückgibt.

Mobile only Navigation

Das Disclosure.Panel beinhaltet die mobile Navigation. Grundsätzlich verhält sich diese Navigation ganz ähnlich wie die Navigation in der Desktop-Ansicht. Sie sieht jedoch anders aus und beinhaltet etwas andere Komponenten.

Das Disclosure.Panel ist bis zum md-Breakpoint nicht zu sehen.

Die Menüpunkte werden wieder aus dem Array navcontent gerendert. Dann wird wieder abgefragt, ob der Menüpunkt Unterpunkte hat oder nicht.

Menüpunkt hat Unterpunkte

Wenn der Menüpunkt Unterpunkte hat, wird für diesen Menüpunkt ein eigenes Disclosure gerendert. Der Disclosure.Button beinhaltet das item.label und togglet das Disclosure.

Befindet sich der User auf einer untergeordneten Seite des Menüpunktes (muss nicht als Unterpunkt angelegt sein), erhält der Menüpunkt die Klasse .menuitem-active wenn die Funktion isMenuActive(item, pathsegments) true zurückgibt.

Das Disclosure.Panel rendet als ersten Button einen Link zur übergeordneten Seite und weitere Links für jeden childItem.

Befindet sich der User auf der Seite, auf die der untergeordnete Menüpunkt verlinkt, erhält der Menüpunkt die Klasse .menuitem-active wenn die Funktion isDropdownActive(childItem, pathname) true zurückgibt.

Menüpunkt hat keine Unterpunkte

Wenn der Menüpunkt keine Unterpunkte hat, wird der Menüpunt als Link gerendert.

Befindet sich der User auf der Seite, auf die der Menüpunkt verlinkt oder auf einer untergeordneten Seite die nicht als Unterpunkt angelegt ist, erhält der Menüpunkt die Klasse .menuitem-active wenn die Funktion isMenuActive(item, pathsegments) true zurückgibt.

Meta Navigation in der Mobile-Ansicht

In der mobilen Ansicht ist die Meta Navigation unterhalb der Menüpunkte Teil des Menüs. Die Meta Navigation hat hier grundsätzlich die gleiche Funktionalität wie in der Desktop-Ansicht aber ein etwas anderes Styling.

isMenuActive()

Die Funktion isMenuActive() wird aufgerufen, um zu prüfen ob der betreffende Menüpunkt akiv ist. Die Funktion benötigt den betreffenden item und den pathname.

pathname ist eine Konstante die den aktuellen URL-Path als String zurückgibt.

Innerhalb der Funktion isMenuActive wird eine Let-Variable found mit dem Wert false initialisiert. Wenn der pathname mit dem String item.href,startet und nicht /, wird found zu true geändert.

Würde hier nicht item.href!=="/" ausgeschlossen werden, würde der Home Menüpunkt immer als active gelten.

Wenn found nach dem Durchlaufen dieser Schleife noch false ist, wird nochmal geprüft, ob der pathname und item.href nur / lauten, dann wird found zu true geändert. Das gewährleistet, das der Home Menüpunkt auf er Root Ebene doch wieder als aktive gilt.

const pathname = usePathname();

function isMenuActive(item, pathname) {
let found = false;
if (pathname.startsWith(item.href) && item.href !== "/") {
found = true;
}
if (found === false && item.href === "/" && pathname === "/") {
found = true;
}
return found;
}

Die Funktion gibt den Wert von found zurück. So kann mit diesem Wert an der Stelle weiter umgegegangen werden, wo die Funktion aufgerufen wurde.