Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TOC on Desktop & Mobile #1129

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 188 additions & 87 deletions src/lib/components/TocNav.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { getTocCtx } from './TocRoot.svelte';
import TocTree from './TocTree.svelte';
import { cubicOut } from 'svelte/easing';
import { getTocCtx } from './TocRoot.svelte';

export let showToc = true;

Expand All @@ -12,94 +13,194 @@
} = getTocCtx();

$: progress = Math.max(...$activeHeadingIdxs) / ($headingsTree.length - 1);

function slideFade(
node: HTMLElement,
{
delay = 0,
duration = 400,
easing = cubicOut
}: { delay?: number; duration?: number; easing?: (t: number) => number } = {}
) {
const initialHeight = node.offsetHeight;

return {
delay,
duration,
easing,
css: (t: number) => {
return `
opacity: ${t};
height: ${t * initialHeight}px;
overflow: hidden;
`;
}
};
}
</script>

<aside class="web-grid-120-1fr-auto-side" class:web-is-mobile-closed={!showToc}>
<div class="web-page-steps">
<div
class="web-page-steps-location web-is-not-mobile"
style="--location:{progress * 100}%;"
<section class="web-mobile-header">
<div class="web-is-only-mobile">
<button
on:click={() => (showToc = !showToc)}
class="flex w-full items-center justify-between"
>
<span class="web-page-steps-location-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<g clip-path="url(#clip0_1684_10747)">
<g filter="url(#filter0_b_1684_10747)">
<circle
cx="8"
cy="8"
r="8"
fill="url(#paint0_linear_1684_10747)"
fill-opacity="0.32"
/>
<circle
cx="8"
cy="8"
r="7.75"
stroke="url(#paint1_linear_1684_10747)"
stroke-width="0.5"
/>
</g>
<circle cx="8" cy="7.99219" r="3" fill="white" />
</g>
<defs>
<filter
id="filter0_b_1684_10747"
x="-200"
y="-200"
width="416"
height="416"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="100" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_1684_10747"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_1684_10747"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_1684_10747"
x1="2.02105"
y1="1.10843"
x2="16.3872"
y2="17.2901"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.4" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_1684_10747"
x1="7.45643"
y1="-1.10615"
x2="5.53812"
y2="17.9973"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.16" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<clipPath id="clip0_1684_10747">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
<span class="flex w-full items-center justify-between">
<span class="text-description">Table of contents</span>
<span
aria-hidden="true"
class="toggle-icon {showToc ? 'web-icon-close' : 'icon-menu-alt-4'}"
></span>
</span>
</div>
<TocTree tree={$headingsTree} activeHeadingIdxs={$activeHeadingIdxs} {item} />
</button>
</div>
</aside>

{#if showToc}
<aside
class="web-grid-120-1fr-auto-side"
class:web-is-mobile-closed={!showToc}
transition:slideFade={{ duration: 300 }}
>
<div class="web-page-steps">
<div
class="web-page-steps-location web-is-not-mobile"
style="--location:{progress * 100}%;"
>
<span class="web-page-steps-location-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<g clip-path="url(#clip0_1684_10747)">
<g filter="url(#filter0_b_1684_10747)">
<circle
cx="8"
cy="8"
r="8"
fill="url(#paint0_linear_1684_10747)"
fill-opacity="0.32"
/>
<circle
cx="8"
cy="8"
r="7.75"
stroke="url(#paint1_linear_1684_10747)"
stroke-width="0.5"
/>
</g>
<circle cx="8" cy="7.99219" r="3" fill="white" />
</g>
<defs>
<filter
id="filter0_b_1684_10747"
x="-200"
y="-200"
width="416"
height="416"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="100" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur_1684_10747"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur_1684_10747"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_1684_10747"
x1="2.02105"
y1="1.10843"
x2="16.3872"
y2="17.2901"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.4" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_1684_10747"
x1="7.45643"
y1="-1.10615"
x2="5.53812"
y2="17.9973"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.16" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<clipPath id="clip0_1684_10747">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
</span>
</div>

<div class="toc-tree-holder">
<TocTree
tree={$headingsTree}
activeHeadingIdxs={$activeHeadingIdxs}
{item}
bind:showToc
/>
</div>
</div>
</aside>
{/if}
</section>

<style>
.web-mobile-header {
top: 5rem;
grid-area: side;
background: unset;
max-height: fit-content;
border-block-end: unset;
border-block-start: unset;
}

@media (max-width: 768px) {
.web-mobile-header {
top: 0;
margin: 1rem 0;
display: block;
position: sticky;
padding: 1.375rem 0;
align-content: center;
/** 1.5rem covers main header completely so fragments of it are not shown during scroll */
padding-block: 1.5rem;
padding-inline: 1.25rem;
background: hsl(var(--p-body-bg-color));
border-block-end: solid 1px var(--p-mobile-header-border-color);
border-block-start: solid 1px var(--p-mobile-header-border-color);
}

.toc-tree-holder {
margin-top: 1.5rem;
margin-left: 0.25rem;
}

.web-icon-close {
max-width: 20px;
max-height: 24px;
}
}

@media (min-width: 1280px) {
.web-mobile-header {
top: 7rem;
display: block;
}
}
</style>
3 changes: 3 additions & 0 deletions src/lib/components/TocTree.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
export let item: TableOfContentsElements['item'];
export let level = 1;

export let showToc = true;

const {
toc: {
helpers: { isActive }
Expand All @@ -22,6 +24,7 @@
class:is-selected={$isActive(heading.id)}
href="#{heading.id}"
use:melt={$item(heading.id)}
on:click|preventDefault={() => showToc = !showToc}
>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html heading.node.innerHTML}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils/tutorials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Tutorial } from '$markdoc/layouts/Tutorial.svelte';

export function globToTutorial(data: { tutorials: Record<string, unknown>; pathname: string }) {
let isFound = false;
let difficulty, readtime;
let difficulty: string | undefined, readtime: string | undefined;

return Object.entries(data.tutorials)
.map(([filepath, tutorial]) => {
Expand Down
40 changes: 22 additions & 18 deletions src/markdoc/layouts/Policy.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,14 @@
<header class="web-grid-120-1fr-auto-header">
<h1 class="text-title font-aeonik-pro text-primary">{title}</h1>
</header>
<button
class="toc-btn web-u-padding-20 web-u-margin-inline-20-negative text-primary web-is-only-mobile web-u-sep-block
web-u-filter-blur-8 sticky mt-6 flex
w-full items-center justify-between"
style:--inset-block-start="4.5rem"
style:inline-size="100vw"
style:background-color="hsl(var(--p-body-bg-color) / 0.1)"
style:translate="0 {$isHeaderHidden ? '-4.5rem' : '0'}"
style:z-index="1"
on:click={() => (showToc = !showToc)}
>
<span class="text-description">Table of contents</span>
<span class="icon-menu-alt-4" aria-hidden="true" />
</button>
<TocNav />

<TocNav bind:showToc />

<main class="web-grid-120-1fr-auto-main /web-is-mobile-closed" id="main">
<div class="web-content is-count-headers" class:web-is-mobile-closed={showToc}>
<div
class="web-content is-count-headers"
class:web-is-mobile-closed={showToc && !showToc}
>
<!-- svelte-ignore a11y-hidden -->
<h2 aria-hidden="true">Introduction</h2>
<slot />
Expand All @@ -107,7 +98,20 @@
opacity: 0;
}

.toc-btn {
transition: translate 0.3s ease;
@media (max-width: 768px) {
.container {
padding-left: 0 !important;
padding-right: 0 !important;
}

header {
padding-block-end: unset;
}

header,
main {
padding-left: var(--spacing-5, 1.25rem);
padding-right: var(--spacing-5, 1.25rem);
}
}
</style>
Loading
Loading