Files
bibdle/src/lib/components/CollapsibleTable.svelte
George Powell 4a5aef5a3d refactor: extract CollapsibleTable component and fix show more
Replaces 7 inline collapsible tables in the global stats page with a
reusable CollapsibleTable component. Adds mode tab toggle (Rolling 30d /
Calendar) into the component. Fixes show more/less which was broken due
to mode-based expanded tracking when no modes were provided.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 20:17:30 -04:00

103 lines
2.3 KiB
Svelte

<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
interface Header {
label: string;
align?: 'left' | 'right';
width?: string;
}
interface Mode {
value: string;
label: string;
}
interface Props {
rows: T[];
headers: Header[];
row: Snippet<[item: T]>;
empty?: Snippet;
initialRows?: number;
modes?: Mode[];
mode?: string;
}
let {
rows,
headers,
row: rowSnippet,
empty,
initialRows = 3,
modes,
mode = $bindable(modes && modes.length > 0 ? modes[0].value : undefined),
}: Props = $props();
let expanded = $state(false);
// Reset expanded when mode changes (e.g. switching Rolling 30d ↔ Calendar)
$effect(() => {
mode;
expanded = false;
});
function toggleExpanded() {
expanded = !expanded;
}
const displayedRows = $derived(expanded ? rows : rows.slice(0, initialRows));
</script>
{#if modes && modes.length > 1}
<div class="flex gap-1 bg-white/5 rounded-lg p-1 w-fit ml-auto mb-3">
{#each modes as m (m.value)}
{@const active = mode === m.value}
<button
onclick={() => (mode = m.value)}
class="px-3 py-1 text-xs rounded-md transition-colors {active ? 'bg-white/10 text-gray-100' : 'text-gray-400 hover:text-gray-200'}"
>
{m.label}
</button>
{/each}
</div>
{/if}
{#if rows.length === 0}
{#if empty}
{@render empty()}
{:else}
<p class="text-gray-400 text-sm px-4 py-6">Not enough data yet.</p>
{/if}
{:else}
<div class="overflow-x-auto rounded-xl border border-white/10">
<table class="w-full text-sm">
<thead>
<tr class="bg-white/5 text-gray-400 text-xs uppercase tracking-wide">
{#each headers as header (header.label)}
<th
class="{header.align === 'right' ? 'text-right' : 'text-left'} px-4 py-3{header.width ? ' ' + header.width : ''}"
>
{header.label}
</th>
{/each}
</tr>
</thead>
<tbody>
{#each displayedRows as item, i (i)}
<tr class="border-t border-white/5 hover:bg-white/5 transition-colors">
{@render rowSnippet(item)}
</tr>
{/each}
</tbody>
</table>
</div>
{#if rows.length > initialRows}
<button
onclick={toggleExpanded}
class="mt-2 flex items-center gap-1 text-xs text-gray-500 hover:text-gray-300 transition-colors mx-auto"
>
<span>{expanded ? '▲ Show less' : '▼ Show more'}</span>
</button>
{/if}
{/if}