@@ -0,0 +1,150 @@
|
||||
<template id="tesoft-nav-template">
|
||||
<style>
|
||||
@import "/src/styles/default.css";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--dark-gray);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
padding: var(--small-padding);
|
||||
}
|
||||
|
||||
nav > div {
|
||||
align-items: center;
|
||||
background-color: var(--gray);
|
||||
border-radius: 0.5rem;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-rows: auto;
|
||||
padding: var(--small-padding) var(--medium-padding) var(--small-padding) var(--medium-padding);
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding-right: var(--medium-padding);
|
||||
}
|
||||
|
||||
slot {
|
||||
display: grid;
|
||||
gap: var(--small-padding);
|
||||
grid-auto-columns: 14rem;
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: 4rem;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
tesoft-button.small-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
tesoft-button.small-menu::part(children) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tesoft-button.small-menu span {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
tesoft-button.small-menu svg {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
dialog {
|
||||
background-color: var(--gray);
|
||||
border-style: none;
|
||||
border-radius: var(--button-border-radius);
|
||||
}
|
||||
|
||||
dialog.small-menu tesoft-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
dialog.search {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
dialog.search > div {
|
||||
display: grid;
|
||||
gap: var(--medium-padding);
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
padding: var(--medium-padding);
|
||||
width: 90vw;
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
background-color: #222831D1;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
slot {
|
||||
display: none;
|
||||
}
|
||||
|
||||
tesoft-button.small-menu {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
dialog.search > div {
|
||||
width: 80rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav>
|
||||
<div>
|
||||
<img class="logo" src="/public/assets/logo.svg">
|
||||
<slot>
|
||||
</slot>
|
||||
<tesoft-button class="small-menu">
|
||||
<span></span>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
|
||||
<path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/>
|
||||
</svg>
|
||||
</tesoft-button>
|
||||
<tesoft-button class="search">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
|
||||
<path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/>
|
||||
</svg>
|
||||
</tesoft-button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<dialog class="small-menu" closedby="any">
|
||||
<div></div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="search" closedby="any">
|
||||
<div>
|
||||
<tesoft-input>
|
||||
<svg slot="left" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px">
|
||||
<path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/>
|
||||
</svg>
|
||||
</tesoft-input>
|
||||
<div class="results">
|
||||
<!-- TODO -->
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</template>
|
||||
|
||||
<script src="/src/components/nav.ts" type="module"></script>
|
||||
{% includeOnce 'components/button.njk' %}
|
||||
{% includeOnce 'components/input.njk' %}
|
||||
@@ -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<TesoftButton>("tesoft-button.small-menu")!;
|
||||
this.smallMenuButtonSpan = shadowRoot.querySelector<HTMLSpanElement>("tesoft-button.small-menu span")!;
|
||||
this.smallMenuDialog = shadowRoot.querySelector<HTMLDialogElement>("dialog.small-menu")!;
|
||||
this.smallMenuDiv = shadowRoot.querySelector<HTMLDivElement>("dialog.small-menu > div")!;
|
||||
this.searchButton = shadowRoot.querySelector<TesoftButton>("tesoft-button.search")!;
|
||||
this.searchDialog = shadowRoot.querySelector<HTMLDialogElement>("dialog.search")!;
|
||||
this.searchInput = shadowRoot.querySelector<TesoftInput>("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);
|
||||
@@ -3,17 +3,28 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ title }}</title>
|
||||
<title>tesoft - {{ title }}</title>
|
||||
<link rel="stylesheet" href="/src/styles/main.css">
|
||||
{% block styles %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
<div>
|
||||
<tesoft-nav>
|
||||
{% for item in [{title: "Blog", href: "/blog"}, {title: "About me", href: "/about"}] %}
|
||||
<tesoft-button href="{{ item.href }}" {% if item.title == title %}selected{% endif %}>
|
||||
{{ item.title }}
|
||||
</tesoft-button>
|
||||
{% endfor %}
|
||||
</tesoft-nav>
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{% includeOnce 'components/button.njk' %}
|
||||
{% includeOnce 'components/nav.njk' %}
|
||||
{% block components %}
|
||||
{% endblock %}
|
||||
{% block sources %}
|
||||
|
||||
@@ -48,4 +48,5 @@ a:focus {
|
||||
outline-color: var(--gold);
|
||||
outline-offset: 0.2rem;
|
||||
outline-style: solid;
|
||||
outline-width: 0.1rem;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user