Skip to content
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
31 changes: 30 additions & 1 deletion web/docs/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import { bfsFirstRouteKeyUnderDir } from "./lib/manifestBfsDefault";
import { persistExpandedPathForRouteKey } from "./lib/treeSession";
import { DocTreeNav } from "./lib/tree";
import { filterTree } from "./lib/filterTree";

const NAV_COLLAPSED_KEY = "fullsend-docs-nav-collapsed";
const WIDTH_STORAGE_KEY = "fullsend-docs-sidebar-width-px";
Expand All @@ -34,6 +35,8 @@
let narrowViewport = $state(false);
/** Bumps when outline session keys change outside the tree (e.g. directory hash); keeps DocTreeNav in sync with sessionStorage. */
let outlineSessionEpoch = $state(0);
let filterQuery = $state("");
let filteredManifest = $derived(filterTree(manifest, filterQuery));

let outlineExpanded = $derived(
narrowViewport ? mobileNavOpen : !navCollapsed,
Expand Down Expand Up @@ -422,11 +425,37 @@
</svg>
</button>
</div>
<div class="docs-tree-filter-wrap">
<svg class="docs-tree-filter-icon" width="14" height="14" viewBox="0 0 16 16" focusable="false" aria-hidden="true">
<path fill="currentColor" d="M11.5 7a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm-.82 4.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04Z"/>
</svg>
<input
class="docs-tree-filter"
type="text"
placeholder="Filter docs…"
aria-label="Filter documents"
bind:value={filterQuery}
/>
{#if filterQuery}
<button
type="button"
class="docs-tree-filter-clear"
aria-label="Clear filter"
onclick={() => filterQuery = ""}
>
<svg width="16" height="16" viewBox="0 0 16 16" focusable="false" aria-hidden="true">
<path fill="currentColor" d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/>
</svg>
</button>
{/if}
</div>
<nav class="docs-tree-wrap">
<DocTreeNav
nodes={manifest}
nodes={filteredManifest}
activeRouteKey={pageRouteKey}
outlineSessionEpoch={outlineSessionEpoch}
forceExpandAll={filterQuery.trim().length > 0}
{filterQuery}
/>
</nav>
</aside>
Expand Down
59 changes: 59 additions & 0 deletions web/docs/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,59 @@ body {
display: inline-flex;
}

.docs-tree-filter-wrap {
flex: 0 0 auto;
padding: 0.35rem 0.5rem 0;
position: relative;
}

.docs-tree-filter-icon {
position: absolute;
left: 0.85rem;
top: 56%;
transform: translateY(-50%);
color: var(--docs-muted, #888);
pointer-events: none;
}

.docs-tree-filter {
width: 100%;
padding: 0.3rem 0.5rem 0.3rem 1.8rem;
font: inherit;
font-size: 0.85rem;
border: 1px solid var(--docs-border);
border-radius: 0.3rem;
background: var(--docs-surface);
color: var(--docs-text);
outline: none;
}

.docs-tree-filter:focus {
border-color: var(--docs-link);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--docs-link) 25%, transparent);
}

.docs-tree-filter-clear {
position: absolute;
right: 0.75rem;
top: 56%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.15rem;
border: none;
border-radius: 0.2rem;
background: transparent;
color: var(--docs-muted, #888);
cursor: pointer;
}

.docs-tree-filter-clear:hover {
color: var(--docs-text);
background: var(--docs-border);
}

.docs-tree-wrap {
flex: 1;
min-height: 0;
Expand Down Expand Up @@ -384,6 +437,12 @@ body {
font-weight: 600;
}

.doc-tree-match {
background: color-mix(in srgb, var(--docs-link) 25%, transparent);
color: inherit;
border-radius: 0.15rem;
}

@media (max-width: 768px) {
.docs-shell-inner {
position: relative;
Expand Down
13 changes: 10 additions & 3 deletions web/docs/src/lib/DocTreeNav.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import type { ManifestNode } from "virtual:fullsend-docs";
import DocTreeNav from "./DocTreeNav.svelte";
import { highlightSegments } from "./filterTree";
import { navigateToRouteKey } from "./routing";

interface Props {
Expand All @@ -13,13 +14,17 @@
outlineSessionEpoch?: number;
/** POSIX path segments for this level’s parent (e.g. `guides/admin`). */
parentDirPath?: string;
forceExpandAll?: boolean;
filterQuery?: string;
}

let {
nodes,
activeRouteKey,
outlineSessionEpoch = 0,
parentDirPath = "",
forceExpandAll = false,
filterQuery = "",
}: Props = $props();

/** Bumps when a folder is toggled so `isExpanded` re-reads sessionStorage. */
Expand Down Expand Up @@ -57,7 +62,7 @@
<li class="doc-tree-item">
{#if node.type === "dir"}
{@const dirPath = parentDirPath ? `${parentDirPath}/${node.name}` : node.name}
{@const expanded = isExpanded(dirPath)}
{@const expanded = forceExpandAll || isExpanded(dirPath)}
{@const subId = childListId(dirPath)}
<div class="doc-tree-folder" data-doc-tree-dir={dirPath}>
<button
Expand Down Expand Up @@ -100,7 +105,7 @@
</svg>
{/if}
</span>
<span class="doc-tree-folder-label">{node.name}</span>
<span class="doc-tree-folder-label">{#each highlightSegments(node.name, filterQuery) as seg}{#if seg.highlight}<mark class="doc-tree-match">{seg.text}</mark>{:else}{seg.text}{/if}{/each}</span>
</button>
{#if expanded}
<div id={subId} class="doc-tree-folder-children">
Expand All @@ -109,6 +114,8 @@
{activeRouteKey}
{outlineSessionEpoch}
parentDirPath={dirPath}
{forceExpandAll}
{filterQuery}
/>
</div>
{/if}
Expand All @@ -130,7 +137,7 @@
/>
</svg>
</span>
<span class="doc-tree-link-text">{node.title}</span>
<span class="doc-tree-link-text">{#each highlightSegments(node.title, filterQuery) as seg}{#if seg.highlight}<mark class="doc-tree-match">{seg.text}</mark>{:else}{seg.text}{/if}{/each}</span>
</button>
{/if}
</li>
Expand Down
Loading
Loading