Skip to content

Commit

Permalink
enhancement/issue 88 header navigation using content as data (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
thescientist13 authored Oct 31, 2024
1 parent b27f919 commit 0f6fdca
Show file tree
Hide file tree
Showing 9 changed files with 2,043 additions and 664 deletions.
52 changes: 33 additions & 19 deletions src/components/header/header.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getContentByCollection } from "@greenwood/cli/src/data/client.js";
import discordIcon from "../../assets/discord.svg?type=raw";
import githubIcon from "../../assets/github.svg?type=raw";
import twitterIcon from "../../assets/twitter-logo.svg?type=raw";
Expand All @@ -6,7 +7,12 @@ import greenwoodLogo from "../../assets/greenwood-logo-full.svg?type=raw";
import styles from "./header.module.css";

export default class Header extends HTMLElement {
connectedCallback() {
async connectedCallback() {
const currentRoute = this.getAttribute("current-route") ?? "";
const navItems = (await getContentByCollection("nav")).sort((a, b) =>
a.data.order > b.data.order ? 1 : -1,
);

this.innerHTML = `
<header class="${styles.container}">
<a href="/" title="Greenwood Home Page" class="${styles.logoLink}">
Expand All @@ -16,15 +22,18 @@ export default class Header extends HTMLElement {
<div class="${styles.navBar}">
<nav role="navigation" aria-label="Main">
<ul class="${styles.navBarMenu}">
<li class="${styles.navBarMenuItem}">
<a href="/docs/" title="Documentation">Docs</a>
</li>
<li class="${styles.navBarMenuItem}">
<a href="/guides/" title="Guides">Guides</a>
</li>
<li class="${styles.navBarMenuItem}">
<a href="/blog/" title="Blog">Blog</a>
</li>
${navItems
.map((item) => {
const { route, label } = item;
const isActiveClass = currentRoute.startsWith(item.route) ? 'class="active"' : "";
return `
<li class="${styles.navBarMenuItem}">
<a href="${route}" ${isActiveClass} title="${label}">${label}</a>
</li>
`;
})
.join("")}
</ul>
</nav>
Expand Down Expand Up @@ -64,15 +73,20 @@ export default class Header extends HTMLElement {
<nav role="navigation" aria-label="Mobile">
<ul class="${styles.mobileMenuList}">
<li class="${styles.mobileMenuListItem}">
<a href="/docs/" title="Documentation">Docs</a>
</li>
<li class="${styles.mobileMenuListItem}">
<a href="/guides/" title="Guides">Guides</a>
</li>
<li class="${styles.mobileMenuListItem}">
<a href="/blog/" title="Blog">Blog</a>
</li>
${navItems
.map((item) => {
const { route, label } = item;
const isActiveClass = currentRoute.startsWith(item.route)
? 'class="active"'
: "";
return `
<li class="${styles.mobileMenuListItem}">
<a href="${route}" ${isActiveClass} title="${label}">${label}</a>
</li>
`;
})
.join("")}
</ul>
</nav>
</div>
Expand Down
27 changes: 16 additions & 11 deletions src/components/header/header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
padding: 0;
}

.mobileMenuList {
text-align: left;
margin: var(--size-4) 0 0;
}

.navBarMenuItem a {
text-decoration: none;
color: var(--color-black);
Expand All @@ -50,11 +55,22 @@
text-decoration: none;
}

.navBarMenuItem a.active,
.navBarMenuItem a:hover,
.navBarMenuItem a:focus {
text-decoration: underline;
}

.mobileMenuListItem {
list-style-type: none;
margin: 10px 0;
font-size: var(--font-size-5);
}

.mobileMenuListItem a.active {
text-decoration: underline;
}

.socialTray {
display: flex;
gap: var(--size-3);
Expand Down Expand Up @@ -103,17 +119,6 @@
color: var(--color-black);
}

.mobileMenuList {
text-align: left;
margin: var(--size-4) 0 0;
}

.mobileMenuListItem {
list-style-type: none;
margin: 10px 0;
font-size: var(--font-size-5);
}

@media screen and (min-width: 480px) {
.container {
justify-content: space-between;
Expand Down
84 changes: 47 additions & 37 deletions src/components/header/header.spec.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,36 @@
import { expect } from "@esm-bundle/chai";
import "./header.js";
import pages from "../../stories/mocks/graph.json" with { type: "json" };

const CURRENT_ROUTE = "/guides/";
const ICONS = [
{
link: "https://github.com/ProjectEvergreen/greenwood",
title: "GitHub",
},
{
link: "https://discord.gg/bsy9jvWh",
title: "Discord",
},
{
link: "https://twitter.com/PrjEvergreen",
title: "Twitter",
},
];

window.fetch = function () {
return new Promise((resolve) => {
resolve(new Response(JSON.stringify(pages.filter((page) => page.data.collection === "nav"))));
});
};

describe("Components/Header", () => {
const NAV = [
{
title: "Documentation",
label: "Docs",
},
{
title: "Guides",
label: "Guides",
},
{
title: "Blog",
label: "Blog",
},
];
const ICONS = [
{
link: "https://github.com/ProjectEvergreen/greenwood",
title: "GitHub",
},
{
link: "https://discord.gg/bsy9jvWh",
title: "Discord",
},
{
link: "https://twitter.com/PrjEvergreen",
title: "Twitter",
},
];

let header;

before(async () => {
header = document.createElement("app-header");
header.setAttribute("current-route", CURRENT_ROUTE);

document.body.appendChild(header);

await header.updateComplete;
Expand Down Expand Up @@ -62,16 +58,23 @@ describe("Components/Header", () => {

it("should have the expected desktop navigation links", () => {
const links = header.querySelectorAll("nav[aria-label='Main'] ul li a");
let activeRoute = undefined;

Array.from(links).forEach((link) => {
const navItem = NAV.find(
(nav) => `/${nav.label.toLowerCase()}/` === link.getAttribute("href"),
);
Array.from(links).forEach((link, idx) => {
const navItem = pages.find((nav) => nav.route === link.getAttribute("href"));

expect(navItem).to.not.equal(undefined);
expect(navItem.data.order).to.equal((idx += 1));
expect(link.textContent).to.equal(navItem.label);
expect(link.getAttribute("title")).to.equal(navItem.title);

// current route should display as active
if (navItem.route === CURRENT_ROUTE && link.getAttribute("class").includes("active")) {
activeRoute = navItem;
}
});

expect(activeRoute.route).to.equal(CURRENT_ROUTE);
});

it("should have the expected social link icons", () => {
Expand Down Expand Up @@ -121,16 +124,23 @@ describe("Components/Header", () => {

it("should have the expected navigation links", () => {
const links = header.querySelectorAll("nav[aria-label='Mobile'] ul li a");
let activeRoute = undefined;

Array.from(links).forEach((link) => {
const navItem = NAV.find(
(nav) => `/${nav.label.toLowerCase()}/` === link.getAttribute("href"),
);
Array.from(links).forEach((link, idx) => {
const navItem = pages.find((nav) => nav.route === link.getAttribute("href"));

expect(navItem).to.not.equal(undefined);
expect(navItem.data.order).to.equal((idx += 1));
expect(link.textContent).to.equal(navItem.label);
expect(link.getAttribute("title")).to.equal(navItem.title);

// current route should display as active
if (navItem.route === CURRENT_ROUTE && link.getAttribute("class").includes("active")) {
activeRoute = navItem;
}
});

expect(activeRoute.route).to.equal(CURRENT_ROUTE);
});
});

Expand Down
17 changes: 16 additions & 1 deletion src/components/header/header.stories.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import "./header.js";
import pages from "../../stories/mocks/graph.json";

export default {
title: "Components/Header",
parameters: {
fetchMock: {
mocks: [
{
matcher: {
url: "http://localhost:1984/___graph.json",
response: {
body: pages.filter((page) => page.data.collection === "nav"),
},
},
},
],
},
},
};

const Template = () => "<app-header></app-header>";
const Template = () => "<app-header current-route='/guides/'></app-header>";

export const Primary = Template.bind({});
2 changes: 1 addition & 1 deletion src/layouts/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</head>

<body>
<app-header></app-header>
<app-header current-route="${globalThis.page.route}"></app-header>

<main class="page-content">
<page-outlet></page-outlet>
Expand Down
5 changes: 5 additions & 0 deletions src/pages/blog/index.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
collection: nav
order: 3
---

<html>
<head>
<title>Greenwood - ${globalThis.page.title}</title>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
title: Docs
layout: docs
collection: nav
order: 1
---

<app-heading-box heading="Docs">
Expand Down
2 changes: 2 additions & 0 deletions src/pages/guides/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
title: Guides
layout: guides
collection: nav
order: 2
---

<app-heading-box heading="Guides">
Expand Down
Loading

0 comments on commit 0f6fdca

Please sign in to comment.