learn and share notes on atproto (wip) 馃
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1import type { Heading } from "$lib/wikilink";
2import { A } from "@solidjs/router";
3import type { Component } from "solid-js";
4import { For, Show } from "solid-js";
5
6type OutlinePanelProps = { headings: Heading[]; onHeadingClick?: (id: string) => void };
7
8/**
9 * Table of contents panel showing document outline from markdown headings
10 */
11export const OutlinePanel: Component<OutlinePanelProps> = (props) => {
12 const handleClick = (id: string, e: MouseEvent) => {
13 e.preventDefault();
14 props.onHeadingClick?.(id);
15 const element = document.getElementById(id);
16 if (element) {
17 element.scrollIntoView({ behavior: "smooth", block: "start" });
18 }
19 };
20
21 return (
22 <div class="space-y-2">
23 <h3 class="text-sm font-semibold text-slate-400 uppercase tracking-wide">Outline</h3>
24 <Show when={props.headings.length > 0} fallback={<p class="text-sm text-slate-500 italic">No headings found</p>}>
25 <nav class="space-y-1">
26 <For each={props.headings}>
27 {(heading) => (
28 <A
29 href={`#${heading.id}`}
30 onClick={(e) => handleClick(heading.id, e)}
31 class="block text-sm text-slate-300 hover:text-blue-400 transition-colors truncate"
32 style={{ "padding-left": `${(heading.level - 1) * 12}px` }}>
33 {heading.text}
34 </A>
35 )}
36 </For>
37 </nav>
38 </Show>
39 </div>
40 );
41};
42
43export default OutlinePanel;