From 126fde7151ca4f2881bd138c91a28a24ddde845d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Erbsh=C3=A4u=C3=9Fer?= Date: Sun, 24 May 2026 09:31:13 +0200 Subject: [PATCH] add nav bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Erbshäußer --- frontend/src/components/nav.njk | 150 ++++++++++++++++++++++++++++++++ frontend/src/components/nav.ts | 73 ++++++++++++++++ frontend/src/layouts/main.njk | 21 +++-- frontend/src/styles/default.css | 1 + frontend/src/styles/main.css | 17 ++++ 5 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/nav.njk create mode 100644 frontend/src/components/nav.ts diff --git a/frontend/src/components/nav.njk b/frontend/src/components/nav.njk new file mode 100644 index 0000000..266e3aa --- /dev/null +++ b/frontend/src/components/nav.njk @@ -0,0 +1,150 @@ + + + +{% includeOnce 'components/button.njk' %} +{% includeOnce 'components/input.njk' %} diff --git a/frontend/src/components/nav.ts b/frontend/src/components/nav.ts new file mode 100644 index 0000000..23fe09d --- /dev/null +++ b/frontend/src/components/nav.ts @@ -0,0 +1,73 @@ +import {TesoftComponent} from "../scripts/main.ts"; +import {TesoftButton} from "./button.ts"; +import {TesoftInput} from "./input.ts"; + +export class TesoftNav extends TesoftComponent { + private readonly smallMenuButton: TesoftButton; + private readonly smallMenuButtonSpan: HTMLSpanElement; + private readonly smallMenuDialog: HTMLDialogElement; + private readonly smallMenuDiv: HTMLDivElement; + private readonly searchButton: TesoftButton; + private readonly searchDialog: HTMLDialogElement; + private readonly searchInput: TesoftInput; + + constructor() { + super(); + + const template = document.getElementById("tesoft-nav-template") as HTMLTemplateElement; + const templateContent = template.content; + + const shadowRoot = this.attachShadow({mode: "open"}); + shadowRoot.appendChild(templateContent.cloneNode(true)); + + this.smallMenuButton = shadowRoot.querySelector("tesoft-button.small-menu")!; + this.smallMenuButtonSpan = shadowRoot.querySelector("tesoft-button.small-menu span")!; + this.smallMenuDialog = shadowRoot.querySelector("dialog.small-menu")!; + this.smallMenuDiv = shadowRoot.querySelector("dialog.small-menu > div")!; + this.searchButton = shadowRoot.querySelector("tesoft-button.search")!; + this.searchDialog = shadowRoot.querySelector("dialog.search")!; + this.searchInput = shadowRoot.querySelector("dialog.search tesoft-input")!; + + this.smallMenuButton.addEventListener("click", () => { + this.updateSmallMenu(); + this.smallMenuDialog.showModal(); + this.smallMenuDialog.style.left = `${this.smallMenuButton.offsetLeft}px`; + this.smallMenuDialog.style.top = `${this.smallMenuButton.offsetTop}px`; + this.smallMenuDialog.style.width = `${this.smallMenuButton.offsetWidth}px`; + }); + + this.searchButton.addEventListener("click", () => { + this.searchDialog.showModal(); + this.searchInput.value = ""; + this.searchInput.focus(); + }); + + this.searchInput.addEventListener("input", () => { + // TODO + }); + + shadowRoot.addEventListener("slotchange", () => { + const selected = this.querySelector("[selected]"); + if (selected !== null) { + this.smallMenuButtonSpan.textContent = selected.textContent; + } + }); + } + + private updateSmallMenu() { + this.smallMenuDiv.innerHTML = ""; + + for (const child of this.children) { + const button = document.createElement("tesoft-button") as TesoftButton; + button.innerText = child.textContent.trim(); + this.smallMenuDiv.appendChild(button); + + button.href = (child as HTMLAnchorElement).href; + if (child.hasAttribute("selected")) { + button.selected = "1"; + } + } + } +} + +customElements.define("tesoft-nav", TesoftNav); diff --git a/frontend/src/layouts/main.njk b/frontend/src/layouts/main.njk index 0147a01..faf86d0 100644 --- a/frontend/src/layouts/main.njk +++ b/frontend/src/layouts/main.njk @@ -3,17 +3,28 @@ - {{ title }} + tesoft - {{ title }} {% block styles %} {% endblock %} -
- {% block content %} - {% endblock %} -
+
+ + {% for item in [{title: "Blog", href: "/blog"}, {title: "About me", href: "/about"}] %} + + {{ item.title }} + + {% endfor %} + +
+ {% block content %} + {% endblock %} +
+
+{% includeOnce 'components/button.njk' %} +{% includeOnce 'components/nav.njk' %} {% block components %} {% endblock %} {% block sources %} diff --git a/frontend/src/styles/default.css b/frontend/src/styles/default.css index a2aa16c..d7e20c8 100644 --- a/frontend/src/styles/default.css +++ b/frontend/src/styles/default.css @@ -48,4 +48,5 @@ a:focus { outline-color: var(--gold); outline-offset: 0.2rem; outline-style: solid; + outline-width: 0.1rem; } diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css index c8fa32d..542aa1a 100644 --- a/frontend/src/styles/main.css +++ b/frontend/src/styles/main.css @@ -13,4 +13,21 @@ body { background-color: var(--dark-gray); color: var(--light-gray); + display: grid; + grid-template-columns: 1fr auto 1fr; + grid-template-rows: auto; +} + +body > div { + grid-column: 2; + max-width: 144rem; +} + +tesoft-nav { + position: sticky; + top: 0; +} + +main { + padding: 0 0.5rem 0 0.5rem; }