Skip to content

Commit

Permalink
example: simpleNavigationProjector.js make the side navigation show/h…
Browse files Browse the repository at this point in the history
…ide.
  • Loading branch information
Dierk Koenig committed Dec 8, 2024
1 parent d05210d commit e6ba50b
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 17 deletions.
2 changes: 1 addition & 1 deletion docs/css/kolibri-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ em { /* emphasis looks like highlighted with text marker */
&::before {
content: "";
position: absolute;
inset: -3px;
inset: 0; /* -3px leads to strange line-break issues */
background-color: var(--kolibri-color-select);
z-index: -10;
rotate: -2deg;
Expand Down
4 changes: 2 additions & 2 deletions docs/src/examples/navigation/simple/starter.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ siteController.registerPage(URI_HASH_HOME, HomePage());
siteController.registerPage(URI_HASH_UNSTYLED, UnstyledPage());
siteController.registerPage(URI_HASH_MASTER_DETAIL, MasterDetailPage());

SimpleNavigationProjector(siteController, siteProjector.sideNavigationElement);
SimpleNavigationProjector(siteController, siteProjector.topNavigationElement);
SimpleNavigationProjector(siteController, siteProjector.sideNavigationElement, true);
SimpleNavigationProjector(siteController, siteProjector.topNavigationElement, false);

siteController.gotoUriHash(window.location.hash);

Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import {select} from "../../../util/dom.js";

export { SimpleNavigationProjector }

const PAGE_CLASS = "simpleNavigationProjector";

const iconSVGStr = `
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M8 5L15.0368 11.9632L8 18.9263" stroke="url(#paint0_linear_1028_8530)"/>
<defs>
<linearGradient id="paint0_linear_1028_8530" x1="7.98915" y1="5" x2="18.7337" y2="14.9252" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FF2CA5"/>
<stop offset="1" stop-color="#6000FF"/>
</linearGradient>
</svg>
`;

/**
* A projector of anchors to all pages that are registered in the {@link SiteControllerType}.
* It binds each anchor to the "visited" state and highlights the currently selected page (uriHash).
* The highlighting is part of the style but layouting of the anchors is left to the parent
* such that the same projector can be used for horizontal and vertical display.
* @constructor
* @param { !SiteControllerType } siteController - the source of the information that we display
* @param { !HTMLDivElement } root - where to mount the view
* @param { !HTMLDivElement } root - where to mount the view
* @param { Boolean= } canHide - whether this navigation can hide itself, defaults to false
* @return { NavigationProjectorType }
* @example
* // set up
Expand All @@ -24,9 +37,9 @@ const PAGE_CLASS = "simpleNavigationProjector";
* SimpleNavigationProjector(siteController, siteProjector.topNavigationElement);
*/

const SimpleNavigationProjector = (siteController, root) => {
const SimpleNavigationProjector = (siteController, root, canHide=false) => {

root.innerHTML = ` <nav class="${PAGE_CLASS}"></nav> `;
root.innerHTML = `<nav class="${PAGE_CLASS}"></nav> `;

const projectNavigation = () => {

Expand All @@ -35,10 +48,10 @@ const SimpleNavigationProjector = (siteController, root) => {
document.head.innerHTML += projectorStyle;
}

const navigationDiv = root.querySelector(`nav.${PAGE_CLASS}`);
const [navigationEl] = select(root, `nav.${PAGE_CLASS}`);

// view is just so many anchors
navigationDiv.innerHTML =
navigationEl.innerHTML = (canHide ? `<div class="toggler">${iconSVGStr}</div>` : '') +
Object.entries(siteController.getAllPages())
.map( ([hash, page]) => `<a href="${hash}">${page.titleText}</a>`)
.join(" ");
Expand All @@ -49,13 +62,18 @@ const SimpleNavigationProjector = (siteController, root) => {
Object.entries(siteController.getAllPages())
.forEach( ([hash, page]) => page.onVisited( visited => {
if (!visited) return;
navigationDiv.querySelector(`a[href="${hash}"]`).classList.add("visited");
navigationEl.querySelector(`a[href="${hash}"]`)?.classList?.add("visited");
} ));
// update which anchor shows the current page
siteController.onUriHashChanged((newHash, oldHash) => {
navigationDiv.querySelector(`a[href="${oldHash}"]`)?.classList?.remove("current");
navigationDiv.querySelector(`a[href="${newHash}"]`)?.classList?.add ("current");
navigationEl.querySelector(`a[href="${oldHash}"]`)?.classList?.remove("current");
navigationEl.querySelector(`a[href="${newHash}"]`)?.classList?.add ("current");
});

if (canHide) {
navigationEl.classList.toggle("hide");
select(navigationEl, ".toggler").head().onclick = _evt => navigationEl.classList.toggle("hide");
}
};

projectNavigation();
Expand All @@ -65,18 +83,44 @@ const projectorStyle = `
<style data-style-id="${PAGE_CLASS}">
@layer navigationLayer {
.${PAGE_CLASS} {
&.hide {
.toggler {
rotate: 0deg;
}
a, a.current { /* hide the anchors */
width: 0;
color: transparent;
pointer-events: none;
}
}
.toggler { /* provide a box for the svg */
justify-self: center;
width: 2rem;
aspect-ratio: 1 / 1;
rotate: 180deg;
transition: rotate .3s ease-in-out;
}
svg {
fill: none;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
a {
text-wrap: nowrap;
font-family: system-ui;
color: revert;
pointer-events: revert;
user-select: none;
text-wrap: nowrap;
font-family: system-ui;
}
a.visited {
text-decoration: none;
text-decoration: none;
}
a.visited:not(.current) {
filter: brightness(150%) grayscale(60%);
filter: brightness(150%) grayscale(60%);
}
a.current {
color: var(--kolibri-color-accent, deeppink);
color: var(--kolibri-color-accent, deeppink);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion docs/src/kolibri/util/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ const fireChangeEvent = element => fireEvent(element, CHANGE);
/** @type InputTypeString */ const COLOR = "color";

/**
* Utility function that works like Element.querySelectorAll but logs a descriptive warning when
* Utility function that works like {@link Element.querySelectorAll} but logs a descriptive warning when
* the resulting NodeList is empty. Wraps the result in a {@link SequenceType } such that the
* Kolibri goodies become available.
* It is a suitable function when a result is **always** expected.
* @param { Element! } element - a DOM element (typically HTMLElement)
* @param { String! } selector - a CSS query selector, might contain operators
* @return { SequenceType<Node> }
Expand Down

0 comments on commit e6ba50b

Please sign in to comment.