From 2d46bf19697339105b82619eeb764c66c5f88ef8 Mon Sep 17 00:00:00 2001 From: "Florine W. Dekker" Date: Wed, 23 Nov 2022 00:58:13 +0100 Subject: [PATCH] Rewrite nav bar Fixes #31. Also resolves some small issues with incorrect margins and such. --- package.json | 2 +- src/main/css/snippets/colors.css | 1 + src/main/css/snippets/nav.css | 190 +++++++++++++++---------------- src/main/js/Template.ts | 38 +++---- src/test/index.html | 12 +- 5 files changed, 107 insertions(+), 136 deletions(-) diff --git a/package.json b/package.json index a7d4f29..3cb8576 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fwdekker/template", - "version": "3.3.0", + "version": "3.3.1", "description": "The base template for pages on fwdekker.com.", "author": "Florine W. Dekker", "license": "MIT", diff --git a/src/main/css/snippets/colors.css b/src/main/css/snippets/colors.css index d4dfca9..853b925 100644 --- a/src/main/css/snippets/colors.css +++ b/src/main/css/snippets/colors.css @@ -4,6 +4,7 @@ --primary-hover: rgb(0, 61, 245) !important; --primary-focus: rgba(0, 41, 163, 0.125) !important; --primary-focus-opaque: rgb(0, 41, 163) !important; + --primary-focus-dark: rgb(0, 29, 114) !important; --primary-inverse: white !important; --form-element-active-border-color: var(--primary) !important; diff --git a/src/main/css/snippets/nav.css b/src/main/css/snippets/nav.css index 012eed7..a68402a 100644 --- a/src/main/css/snippets/nav.css +++ b/src/main/css/snippets/nav.css @@ -1,107 +1,47 @@ /* Base elements */ nav.fwd-nav { - display: block; - z-index: 10; - - margin: 0; - width: 100%; - - background-color: var(--primary); - border-bottom: 1px solid var(--nav-border-color); - - --padding: calc(2em / 3); + border-bottom: 1px solid #ccc; } -nav.fwd-nav * { - vertical-align: middle; +nav.fwd-nav > ul { + display: flex; + align-items: start; + flex-wrap: wrap; } -nav.fwd-nav a, -nav.fwd-nav a:link, -nav.fwd-nav a:visited, -nav.fwd-nav a:hover, -nav.fwd-nav a:active { - /* Ensures whole li is clickable */ - width: 100%; +nav.fwd-nav > ul > li { + flex-grow: 0; + flex-basis: 0; } -nav.fwd-nav a, -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; - +nav.fwd-nav li { 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 ul li:focus-within, -nav.fwd-nav #nav-hamburger-label:hover, -nav.fwd-nav #nav-hamburger-label:focus-within { - cursor: pointer; - background-color: var(--primary-hover); +nav.fwd-nav li > :first-child { + display: inline-block; + width: 100%; + margin: 0; } -nav.fwd-nav li.currentPage { - background-color: var(--primary-focus-opaque); -} - - -/* Second level nesting */ nav.fwd-nav ul li ul { - z-index: 11; - display: none; + position: absolute; + top: 100%; 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 { @@ -109,22 +49,74 @@ nav.fwd-nav ul li ul li ul { top: 0; } -nav.fwd-nav ul li:hover > ul, -nav.fwd-nav ul li:focus-within > ul, -nav.fwd-nav ul li ul:hover { - display: block; +/* z-index */ +nav.fwd-nav a { + position: relative; } -nav.fwd-nav ul li ul li { - min-width: 7em; - width: 100%; - white-space: nowrap; +nav.fwd-nav > ul > li > ul { + z-index: 10; } +/* Colors */ +nav.fwd-nav, +nav.fwd-nav ul { + background-color: var(--primary); +} -/* Hide hamburger-related elements */ -nav.fwd-nav input[type="checkbox"] { - display: none; +nav.fwd-nav ul li:where(:active, :focus-within, :hover) { + background-color: var(--primary-focus-dark); +} + +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) { @@ -134,7 +126,7 @@ nav.fwd-nav input[type="checkbox"] { } @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; } } diff --git a/src/main/js/Template.ts b/src/main/js/Template.ts index 3beb98e..c468f3d 100644 --- a/src/main/js/Template.ts +++ b/src/main/js/Template.ts @@ -2,11 +2,11 @@ * Converts the given string to an HTML element. * * @param string the string to convert to an HTML element - * @param query the type of element to return - * @returns the HTML element described by the given string, or `null` if `query` did not match any element + * @param query the type of element to return, or `undefined` to return the first element + * @returns the HTML element described by the given string */ -export function stringToHtml(string: string, query: string): HTMLElement | null { - return (new DOMParser()).parseFromString(string, "text/html").body.querySelector(query); +export function stringToHtml(string: string, query: string = "*"): HTMLElement { + 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 */ function nav(highlightPath?: string, cb?: (json: any) => void): HTMLElement { - const base = stringToHtml( - ``, - "ul" - )!; + const base = stringToHtml(``); fetch("https://fwdekker.com/api/nav/") .then(it => it.json()) @@ -96,20 +90,15 @@ function nav(highlightPath?: string, cb?: (json: any) => void): HTMLElement { cb(json); json.entries.forEach( - (entry: any) => - base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath), "li")!) + (entry: any) => base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath))) ); }) .catch(error => console.error("Failed to fetch navigation elements", error)); - const nav = stringToHtml( - ``, - "nav" - )!; + const nav = stringToHtml(``); + nav.appendChild(stringToHtml(``)); nav.appendChild(base); + nav.appendChild(stringToHtml(``)); return nav; } @@ -128,7 +117,7 @@ function unpackEntry(entry: any, path: string = "/", highlightPath?: string): st if (entry.entries.length === 0) return "" + - `
  • ` + + `
  • ` + `${entry.name}` + `
  • `; @@ -136,7 +125,7 @@ function unpackEntry(entry: any, path: string = "/", highlightPath?: string): st const arrow = depth === 0 ? "▾" : "▸"; return "" + - `
  • ` + + `
  • ` + `${entry.name} ${arrow}` + `` + `
  • `; @@ -192,9 +181,8 @@ function footer( footerLink("Licensed ", license, licenseURL, ". ") + footerLink("Source and support on ", vcs, vcsURL, ". ") + footerLink("Read the ", privacyPolicyURL && "privacy policy", privacyPolicyURL, ". ") + - ``, - "footer" - )!; + `` + ); } /** diff --git a/src/test/index.html b/src/test/index.html index 3479c8d..cdcfb9d 100644 --- a/src/test/index.html +++ b/src/test/index.html @@ -29,7 +29,7 @@

    -
    +
    @@ -81,16 +81,6 @@
    -