mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
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>
103 lines
2.3 KiB
Svelte
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}
|