feat: multi-level sidebar

chore: support multiple search elements

chore: sidebar display toc on mobile view

chore: add hamburger menu to navbar on mobile

chore: add markdown link hook

for opening external link in new window

chore: add sidebar footer

- put search under params.type
- make navbar link aware of external link
This commit is contained in:
Xin
2023-08-06 15:23:37 +01:00
parent 7e37b73779
commit 2f34627da3
12 changed files with 304 additions and 77 deletions

View File

@ -0,0 +1,47 @@
nav {
.search-wrapper {
@apply hidden md:inline-block;
}
}
.hamburger-menu svg {
g {
@apply origin-center;
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1);
}
path {
opacity: 1;
transition:
transform 0.2s cubic-bezier(0.25, 1, 0.5, 1) 0.2s,
opacity 0.2s ease 0.2s;
}
&.open {
path {
transition:
transform 0.2s cubic-bezier(0.25, 1, 0.5, 1),
opacity 0s ease 0.2s;
}
g {
transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1) 0.2s;
}
}
&.open > {
path {
@apply opacity-0;
}
g:nth-of-type(1) {
@apply rotate-45;
path {
transform: translate3d(0, 6px, 0);
}
}
g:nth-of-type(2) {
@apply -rotate-45;
path {
transform: translate3d(0, -6px, 0);
}
}
}
}

View File

@ -1,7 +1,7 @@
@media (max-width: 767px) {
.sidebar-container {
@apply fixed pt-[calc(var(--navbar-height))] top-0 w-full bottom-0 z-[15] overscroll-contain bg-white dark:bg-dark;
/* transition: transform 0.8s cubic-bezier(0.52, 0.16, 0.04, 1); */
transition: transform 0.8s cubic-bezier(0.52, 0.16, 0.04, 1);
will-change: transform, opacity;
contain: layout style;
backface-visibility: hidden;

View File

@ -8,6 +8,7 @@
@import "components/steps.css";
@import "components/search.css";
@import "components/sidebar.css";
@import "components/navbar.css";
html {
@apply text-base antialiased;
@ -22,6 +23,7 @@ body {
:root {
--primary-hue: 212deg;
--navbar-height: 4rem;
--menu-height: 3.75rem;
}
.dark {

View File

@ -10,17 +10,33 @@
(function () {
const searchDataURL = '{{ $searchData.Permalink }}';
const inputElement = document.getElementById('search-input');
const resultsElement = document.getElementById('search-results');
const inputElements = document.querySelectorAll('.search-input');
for (const el of inputElements) {
el.addEventListener('focus', init);
el.addEventListener('keyup', search);
el.addEventListener('keydown', handleKeyDown);
}
inputElement.addEventListener('focus', init);
inputElement.addEventListener('keyup', search);
inputElement.addEventListener('keydown', handleKeyDown);
// Get the search wrapper, input, and results elements.
function getActiveSearchElement() {
const inputs = Array.from(document.querySelectorAll('.search-wrapper')).filter(el => el.clientHeight > 0);
if (inputs.length === 1) {
return {
wrapper: inputs[0],
inputElement: inputs[0].querySelector('.search-input'),
resultsElement: inputs[0].querySelector('.search-results')
};
}
return undefined;
}
const INPUTS = ['input', 'select', 'button', 'textarea']
// Focus the search input when pressing ctrl+k/cmd+k or /.
document.addEventListener('keydown', function (e) {
const { inputElement } = getActiveSearchElement();
if (!inputElement) return;
const activeElement = document.activeElement;
const tagName = activeElement && activeElement.tagName;
if (
@ -42,18 +58,36 @@
}
});
// Dismiss the search results when clicking outside the search box.
document.addEventListener('mousedown', function (e) {
const { inputElement, resultsElement } = getActiveSearchElement();
if (!inputElement || !resultsElement) return;
if (
e.target !== inputElement &&
e.target !== resultsElement &&
!resultsElement.contains(e.target)
) {
hideSearchResults();
}
});
// Get the currently active result and its index.
function getActiveResult() {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return { result: undefined, index: -1 };
const result = resultsElement.querySelector('.active');
if (result) {
const index = parseInt(result.getAttribute('data-index'));
return { result, index };
}
return { result: undefined, index: -1 };
if (!result) return { result: undefined, index: -1 };
const index = parseInt(result.getAttribute('data-index'));
return { result, index };
}
// Set the active result by index.
function setActiveResult(index) {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return;
const { result: activeResult } = getActiveResult();
activeResult && activeResult.classList.remove('active');
const result = resultsElement.querySelector(`[data-index="${index}"]`);
@ -65,22 +99,31 @@
// Get the number of search results from the DOM.
function getResultsLength() {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return 0;
return resultsElement.querySelectorAll('li').length;
}
// Finish the search by hiding the results and clearing the input.
function finishSearch() {
const { inputElement } = getActiveSearchElement();
if (!inputElement) return;
hideSearchResults();
inputElement.value = '';
inputElement.blur();
}
function hideSearchResults() {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return;
resultsElement.classList.add('hidden');
}
// Handle keyboard events.
function handleKeyDown(e) {
const { inputElement } = getActiveSearchElement();
if (!inputElement) return;
const resultsLength = getResultsLength();
const { result: activeResult, index: activeIndex } = getActiveResult();
@ -108,9 +151,11 @@
}
// Initializes the search.
function init() {
inputElement.removeEventListener('focus', init);
preloadIndex().then(search);
function init(e) {
e.target.removeEventListener('focus', init);
if (!(window.pageIndex && window.sectionIndex)) {
preloadIndex();
}
}
// Preload the search index.
@ -182,13 +227,14 @@
}
}
function search() {
const query = inputElement.value;
if (!inputElement.value) {
function search(e) {
const query = e.target.value;
if (!e.target.value) {
hideSearchResults();
return;
}
const { resultsElement } = getActiveSearchElement();
while (resultsElement.firstChild) {
resultsElement.removeChild(resultsElement.firstChild);
}
@ -249,9 +295,10 @@
displayResults(sortedResults, query);
}
function displayResults(results, query) {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return;
if (!results.length) {
resultsElement.innerHTML = `<span class="no-result">No results found.</span>`;
return;

17
assets/js/menu.js Normal file
View File

@ -0,0 +1,17 @@
const menu = document.querySelector('.hamburger-menu');
menu.addEventListener('click', (e) => {
e.preventDefault();
const sidebarContainer = document.querySelector('.sidebar-container');
// Toggle the hamburger menu
menu.querySelector('svg').classList.toggle('open');
// When the menu is open, we want to show the navigation sidebar
sidebarContainer.classList.toggle('max-md:[transform:translate3d(0,-100%,0)]');
sidebarContainer.classList.toggle('max-md:[transform:translate3d(0,0,0)]');
// When the menu is open, we want to prevent the body from scrolling
document.body.classList.toggle('overflow-hidden');
document.body.classList.toggle('md:overflow-auto');
});