Rewrite nav bar
Fixes #31. Also resolves some small issues with incorrect margins and such.
This commit is contained in:
parent
537d8db0e2
commit
2d46bf1969
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@fwdekker/template",
|
"name": "@fwdekker/template",
|
||||||
"version": "3.3.0",
|
"version": "3.3.1",
|
||||||
"description": "The base template for pages on fwdekker.com.",
|
"description": "The base template for pages on fwdekker.com.",
|
||||||
"author": "Florine W. Dekker",
|
"author": "Florine W. Dekker",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
--primary-hover: rgb(0, 61, 245) !important;
|
--primary-hover: rgb(0, 61, 245) !important;
|
||||||
--primary-focus: rgba(0, 41, 163, 0.125) !important;
|
--primary-focus: rgba(0, 41, 163, 0.125) !important;
|
||||||
--primary-focus-opaque: rgb(0, 41, 163) !important;
|
--primary-focus-opaque: rgb(0, 41, 163) !important;
|
||||||
|
--primary-focus-dark: rgb(0, 29, 114) !important;
|
||||||
--primary-inverse: white !important;
|
--primary-inverse: white !important;
|
||||||
|
|
||||||
--form-element-active-border-color: var(--primary) !important;
|
--form-element-active-border-color: var(--primary) !important;
|
||||||
|
|
|
@ -1,107 +1,47 @@
|
||||||
/* Base elements */
|
/* Base elements */
|
||||||
nav.fwd-nav {
|
nav.fwd-nav {
|
||||||
display: block;
|
border-bottom: 1px solid #ccc;
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
background-color: var(--primary);
|
|
||||||
border-bottom: 1px solid var(--nav-border-color);
|
|
||||||
|
|
||||||
--padding: calc(2em / 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav * {
|
nav.fwd-nav > ul {
|
||||||
vertical-align: middle;
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav a,
|
nav.fwd-nav > ul > li {
|
||||||
nav.fwd-nav a:link,
|
flex-grow: 0;
|
||||||
nav.fwd-nav a:visited,
|
flex-basis: 0;
|
||||||
nav.fwd-nav a:hover,
|
|
||||||
nav.fwd-nav a:active {
|
|
||||||
/* Ensures whole li is clickable */
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav a,
|
nav.fwd-nav li {
|
||||||
nav.fwd-nav a:link,
|
|
||||||
nav.fwd-nav a:visited,
|
|
||||||
nav.fwd-nav a:hover,
|
|
||||||
nav.fwd-nav a:active,
|
|
||||||
nav.fwd-nav #nav-hamburger-label {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: calc(var(--padding)) calc(var(--padding));
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
color: var(--primary-inverse);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.fwd-nav #nav-hamburger-label {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.fwd-nav a[target="_blank"]::after {
|
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
background-color: var(--primary-inverse);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Logo */
|
|
||||||
nav.fwd-nav .logo {
|
|
||||||
width: calc(1em + var(--padding));
|
|
||||||
height: calc(1em + var(--padding));
|
|
||||||
|
|
||||||
vertical-align: middle;
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.fwd-nav div.logo {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: calc(1em / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* First level nesting */
|
|
||||||
nav.fwd-nav ul {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.fwd-nav ul li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--primary);
|
width: 100%;
|
||||||
|
padding: var(--nav-element-spacing-horizontal);
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav ul li:hover,
|
nav.fwd-nav li > :first-child {
|
||||||
nav.fwd-nav ul li:focus-within,
|
display: inline-block;
|
||||||
nav.fwd-nav #nav-hamburger-label:hover,
|
width: 100%;
|
||||||
nav.fwd-nav #nav-hamburger-label:focus-within {
|
margin: 0;
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--primary-hover);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav li.currentPage {
|
|
||||||
background-color: var(--primary-focus-opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Second level nesting */
|
|
||||||
nav.fwd-nav ul li ul {
|
nav.fwd-nav ul li ul {
|
||||||
z-index: 11;
|
|
||||||
|
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.fwd-nav ul li:where(:active, :focus-within, :hover) > ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav ul li ul li ul {
|
nav.fwd-nav ul li ul li ul {
|
||||||
|
@ -109,22 +49,74 @@ nav.fwd-nav ul li ul li ul {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav ul li:hover > ul,
|
/* z-index */
|
||||||
nav.fwd-nav ul li:focus-within > ul,
|
nav.fwd-nav a {
|
||||||
nav.fwd-nav ul li ul:hover {
|
position: relative;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.fwd-nav ul li ul li {
|
nav.fwd-nav > ul > li > ul {
|
||||||
min-width: 7em;
|
z-index: 10;
|
||||||
width: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Colors */
|
||||||
|
nav.fwd-nav,
|
||||||
|
nav.fwd-nav ul {
|
||||||
|
background-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide hamburger-related elements */
|
nav.fwd-nav ul li:where(:active, :focus-within, :hover) {
|
||||||
nav.fwd-nav input[type="checkbox"] {
|
background-color: var(--primary-focus-dark);
|
||||||
display: none;
|
}
|
||||||
|
|
||||||
|
nav.fwd-nav ul li.current-page:not(:where(:active, :focus-within, :hover)) {
|
||||||
|
background-color: var(--primary-focus-opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.fwd-nav a {
|
||||||
|
color: var(--primary-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.fwd-nav a::after {
|
||||||
|
background-color: var(--primary-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logo */
|
||||||
|
nav.fwd-nav #logo {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.fwd-nav #logo::before {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
width: calc(1em * var(--line-height));
|
||||||
|
height: calc(1em * var(--line-height));
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
--mask-image: url("https://fwdekker.com/favicon.png");
|
||||||
|
-webkit-mask-image: var(--mask-image);
|
||||||
|
-webkit-mask-size: cover;
|
||||||
|
mask-image: var(--mask-image);
|
||||||
|
mask-size: cover;
|
||||||
|
background-repeat: no-repeat no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: cover;
|
||||||
|
background-color: var(--primary-inverse);
|
||||||
|
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hamburger */
|
||||||
|
nav.fwd-nav #nav-hamburger-label {
|
||||||
|
height: fit-content;
|
||||||
|
margin: 0;
|
||||||
|
padding: calc(var(--nav-element-spacing-horizontal) + var(--nav-link-spacing-vertical));
|
||||||
|
|
||||||
|
color: var(--primary-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.fwd-nav #nav-hamburger-label:where(:active, :focus-within, :hover) {
|
||||||
|
background-color: var(--primary-focus-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 576px) {
|
@media (min-width: 576px) {
|
||||||
|
@ -134,7 +126,7 @@ nav.fwd-nav input[type="checkbox"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
nav.fwd-nav input[type="checkbox"]:not(:checked) ~ ul li:not(:first-child) {
|
nav.fwd-nav #nav-hamburger-checkbox:not(:checked) ~ ul li:not(:first-child) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
* Converts the given string to an HTML element.
|
* Converts the given string to an HTML element.
|
||||||
*
|
*
|
||||||
* @param string the string to convert to an HTML element
|
* @param string the string to convert to an HTML element
|
||||||
* @param query the type of element to return
|
* @param query the type of element to return, or `undefined` to return the first element
|
||||||
* @returns the HTML element described by the given string, or `null` if `query` did not match any element
|
* @returns the HTML element described by the given string
|
||||||
*/
|
*/
|
||||||
export function stringToHtml(string: string, query: string): HTMLElement | null {
|
export function stringToHtml(string: string, query: string = "*"): HTMLElement {
|
||||||
return (new DOMParser()).parseFromString(string, "text/html").body.querySelector(query);
|
return (new DOMParser()).parseFromString(string, "text/html").body.querySelector(query)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,13 +81,7 @@ export function getMetaProperty(name: string): string | null | undefined {
|
||||||
* @returns a base navigation element that will eventually be filled with contents
|
* @returns a base navigation element that will eventually be filled with contents
|
||||||
*/
|
*/
|
||||||
function nav(highlightPath?: string, cb?: (json: any) => void): HTMLElement {
|
function nav(highlightPath?: string, cb?: (json: any) => void): HTMLElement {
|
||||||
const base = stringToHtml(
|
const base = stringToHtml(`<ul><li><a id="logo" href="https://fwdekker.com/">FWDekker</a></span></li></ul>`);
|
||||||
`<ul><li><a href="https://fwdekker.com/">` +
|
|
||||||
`<div class="logo"><img class="logo" src="https://fwdekker.com/favicon.png" alt="FWDekker" /></div>` +
|
|
||||||
`<b>FWDekker</b>` +
|
|
||||||
`</a></li></ul>`,
|
|
||||||
"ul"
|
|
||||||
)!;
|
|
||||||
|
|
||||||
fetch("https://fwdekker.com/api/nav/")
|
fetch("https://fwdekker.com/api/nav/")
|
||||||
.then(it => it.json())
|
.then(it => it.json())
|
||||||
|
@ -96,20 +90,15 @@ function nav(highlightPath?: string, cb?: (json: any) => void): HTMLElement {
|
||||||
cb(json);
|
cb(json);
|
||||||
|
|
||||||
json.entries.forEach(
|
json.entries.forEach(
|
||||||
(entry: any) =>
|
(entry: any) => base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath)))
|
||||||
base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath), "li")!)
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(error => console.error("Failed to fetch navigation elements", error));
|
.catch(error => console.error("Failed to fetch navigation elements", error));
|
||||||
|
|
||||||
const nav = stringToHtml(
|
const nav = stringToHtml(`<nav class="fwd-nav"></nav>`);
|
||||||
`<nav class="fwd-nav">` +
|
nav.appendChild(stringToHtml(`<input id="nav-hamburger-checkbox" type="checkbox" hidden />`));
|
||||||
`<input id="nav-hamburger-checkbox" type="checkbox" hidden />` +
|
|
||||||
`<label id="nav-hamburger-label" for="nav-hamburger-checkbox">☰</label>` +
|
|
||||||
`</nav>`,
|
|
||||||
"nav"
|
|
||||||
)!;
|
|
||||||
nav.appendChild(base);
|
nav.appendChild(base);
|
||||||
|
nav.appendChild(stringToHtml(`<label id="nav-hamburger-label" for="nav-hamburger-checkbox">☰</label>`));
|
||||||
return nav;
|
return nav;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +117,7 @@ function unpackEntry(entry: any, path: string = "/", highlightPath?: string): st
|
||||||
|
|
||||||
if (entry.entries.length === 0)
|
if (entry.entries.length === 0)
|
||||||
return "" +
|
return "" +
|
||||||
`<li ${shouldHighlight ? "class=\"currentPage\"" : ""}>` +
|
`<li ${shouldHighlight ? "class=\"current-page\"" : ""}>` +
|
||||||
`<a href="${entry.link}" ${isExternalLink ? "target=\"_blank\"" : ""}>${entry.name}</a>` +
|
`<a href="${entry.link}" ${isExternalLink ? "target=\"_blank\"" : ""}>${entry.name}</a>` +
|
||||||
`</li>`;
|
`</li>`;
|
||||||
|
|
||||||
|
@ -136,7 +125,7 @@ function unpackEntry(entry: any, path: string = "/", highlightPath?: string): st
|
||||||
const arrow = depth === 0 ? "▾" : "▸";
|
const arrow = depth === 0 ? "▾" : "▸";
|
||||||
|
|
||||||
return "" +
|
return "" +
|
||||||
`<li class="${shouldHighlight ? "currentPage" : ""}">` +
|
`<li class="${shouldHighlight ? "current-page" : ""}">` +
|
||||||
`<a href="${entry.link}">${entry.name} ${arrow}</a>` +
|
`<a href="${entry.link}">${entry.name} ${arrow}</a>` +
|
||||||
`<ul>${entry.entries.map((it: any) => unpackEntry(it, `${path + entry.name}/`, highlightPath)).join("")}</ul>` +
|
`<ul>${entry.entries.map((it: any) => unpackEntry(it, `${path + entry.name}/`, highlightPath)).join("")}</ul>` +
|
||||||
`</li>`;
|
`</li>`;
|
||||||
|
@ -192,9 +181,8 @@ function footer(
|
||||||
footerLink("Licensed ", license, licenseURL, ". ") +
|
footerLink("Licensed ", license, licenseURL, ". ") +
|
||||||
footerLink("Source and support on ", vcs, vcsURL, ". ") +
|
footerLink("Source and support on ", vcs, vcsURL, ". ") +
|
||||||
footerLink("Read the ", privacyPolicyURL && "privacy policy", privacyPolicyURL, ". ") +
|
footerLink("Read the ", privacyPolicyURL && "privacy policy", privacyPolicyURL, ". ") +
|
||||||
`</small><div id="fwd-footer-version"><small>${version || ""}</small></div></footer>`,
|
`</small><div id="fwd-footer-version"><small>${version || ""}</small></div></footer>`
|
||||||
"footer"
|
);
|
||||||
)!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</p>
|
</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
<nav id="nav"></nav>
|
<nav id="nav"></nav>
|
||||||
<main class="container container-with-aside-right">
|
<main class="container">
|
||||||
<div role="document">
|
<div role="document">
|
||||||
<section>
|
<section>
|
||||||
<header class="fwd-header">
|
<header class="fwd-header">
|
||||||
|
@ -81,16 +81,6 @@
|
||||||
</section>
|
</section>
|
||||||
<footer id="footer"></footer>
|
<footer id="footer"></footer>
|
||||||
</div>
|
</div>
|
||||||
<aside>
|
|
||||||
<nav>
|
|
||||||
<b class="hidden-no-mobile">Table of contents</b>
|
|
||||||
<ul>
|
|
||||||
<li>Item 1</li>
|
|
||||||
<li>Item 2</li>
|
|
||||||
</ul>
|
|
||||||
<hr class="hidden-no-mobile" />
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!--suppress HtmlUnknownTarget -->
|
<!--suppress HtmlUnknownTarget -->
|
||||||
|
|
Loading…
Reference in New Issue