Browse Source

Allow two ToC component variants

- A sidebar variant is meant for integration within a sidebar.  This is used in the current Publication component.
- An accordion variant is intended for standalone use.
master
buttercat1791 9 months ago
parent
commit
b07914f963
  1. 20
      src/lib/components/publications/Publication.svelte
  2. 53
      src/lib/components/publications/TableOfContents.svelte

20
src/lib/components/publications/Publication.svelte

@ -7,6 +7,7 @@
SidebarGroup, SidebarGroup,
SidebarWrapper, SidebarWrapper,
Heading, Heading,
CloseButton,
} from "flowbite-svelte"; } from "flowbite-svelte";
import { getContext, onDestroy, onMount } from "svelte"; import { getContext, onDestroy, onMount } from "svelte";
import { import {
@ -90,6 +91,10 @@
return currentBlog !== null && $publicationColumnVisibility.inner; return currentBlog !== null && $publicationColumnVisibility.inner;
} }
function closeToc() {
publicationColumnVisibility.update((v) => ({ ...v, toc: false }));
}
function closeDiscussion() { function closeDiscussion() {
publicationColumnVisibility.update((v) => ({ ...v, discussion: false })); publicationColumnVisibility.update((v) => ({ ...v, discussion: false }));
} }
@ -155,13 +160,20 @@
</script> </script>
<!-- Table of contents --> <!-- Table of contents -->
{#if publicationType !== "blog" || !isLeaf} {#if publicationType !== 'blog' || !isLeaf}
{#if $publicationColumnVisibility.toc}
<Sidebar class='sidebar-leather left-0 md:!pr-8'>
<CloseButton onclick={closeToc} class='btn-leather absolute top-0 right-0' />
<TableOfContents <TableOfContents
depth={0} displayMode='sidebar'
rootAddress={rootAddress}
depth={2}
onSectionFocused={(address: string) => { onSectionFocused={(address: string) => {
publicationTree.setBookmark(address); publicationTree.setBookmark(address);
}} }}
/> />
</Sidebar>
{/if}
{/if} {/if}
<!-- Default publications --> <!-- Default publications -->
@ -205,9 +217,7 @@
<!-- Blog list --> <!-- Blog list -->
{#if $publicationColumnVisibility.blog} {#if $publicationColumnVisibility.blog}
<div <div
class="flex flex-col p-4 space-y-4 overflow-auto max-w-xl flex-grow-1 class={`flex flex-col p-4 space-y-4 overflow-auto max-w-xl flex-grow-1 ${isInnerActive() ? 'discreet' : ''}`}
{isInnerActive() ? 'discreet' : ''}
"
> >
<div <div
class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border" class="card-leather bg-highlight dark:bg-primary-800 p-4 mb-4 rounded-lg border"

53
src/lib/components/publications/TableOfContents.svelte

@ -1,24 +1,36 @@
<script lang='ts'> <script lang='ts'>
import type { TableOfContents, TocEntry } from '$lib/components/publications/table_of_contents.svelte'; import { TableOfContents, type TocEntry } from '$lib/components/publications/table_of_contents.svelte';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { Accordion, AccordionItem, Card } from 'flowbite-svelte'; import { Accordion, AccordionItem, Card, SidebarDropdownWrapper, SidebarGroup, SidebarItem } from 'flowbite-svelte';
import Self from './TableOfContents.svelte'; import Self from './TableOfContents.svelte';
import type { SveltePublicationTree } from './svelte_publication_tree.svelte';
import { page } from '$app/state';
export type TocDisplayMode = 'accordion' | 'sidebar';
let { let {
displayMode = 'accordion',
rootAddress,
depth, depth,
onSectionFocused, onSectionFocused,
} = $props<{ } = $props<{
displayMode?: TocDisplayMode;
rootAddress: string;
depth: number; depth: number;
onSectionFocused?: (address: string) => void; onSectionFocused?: (address: string) => void;
}>(); }>();
let toc = getContext('toc') as TableOfContents; let publicationTree = getContext('publicationTree') as SveltePublicationTree;
let toc = new TableOfContents(rootAddress, publicationTree, page.url.pathname ?? "");
let entries = $derived( let entries = $derived.by(() => {
Array console.debug("[ToC] Filtering entries for depth", depth);
const entries = Array
.from(toc.addressMap.values()) .from(toc.addressMap.values())
.filter((entry) => entry.depth === depth) .filter((entry) => entry.depth === depth);
); console.debug("[ToC] Filtered entries", entries.map((e) => e.title));
return entries;
});
// Track the currently visible section for highlighting // Track the currently visible section for highlighting
let currentSection = $state<string | null>(null); let currentSection = $state<string | null>(null);
@ -51,13 +63,34 @@
} }
</script> </script>
<Accordion flush class='overflow-y-auto w-64 p-4'> {#if displayMode === 'accordion'}
<Accordion flush multiple>
{#each entries as entry} {#each entries as entry}
<AccordionItem open={entry.expanded}> <AccordionItem open={entry.expanded}>
<h3 class='text-lg font-bold'>{entry.title}</h3> {#snippet header()}
<span class="text-gray-800 dark:text-gray-300">{entry.title}</span>
{/snippet}
{#if entry.children.length > 0} {#if entry.children.length > 0}
<Self depth={depth + 1} onSectionFocused={onSectionFocused} /> <Self rootAddress={entry.address} depth={depth + 1} onSectionFocused={onSectionFocused} />
{/if} {/if}
</AccordionItem> </AccordionItem>
{/each} {/each}
</Accordion> </Accordion>
{:else}
<SidebarGroup>
{#each entries as entry}
{#if entry.children.length > 0}
<SidebarDropdownWrapper label={entry.title}>
<Self
displayMode={displayMode}
rootAddress={entry.address}
depth={depth + 1}
onSectionFocused={onSectionFocused}
/>
</SidebarDropdownWrapper>
{:else}
<SidebarItem label={entry.title} href={entry.href} />
{/if}
{/each}
</SidebarGroup>
{/if}
Loading…
Cancel
Save