feat: redesign match play and add broadcast tooling

This commit is contained in:
2026-09-29 02:52:56 +00:00
parent f417a831f7
commit 9b9aafefc1
348 changed files with 31588 additions and 11094 deletions

View File

@@ -16,13 +16,16 @@
meta,
columns,
row,
searchPlaceholder = 'Search...'
searchPlaceholder = 'Search...',
layout = 'table'
}: {
data: T[];
meta: TableMeta;
columns: Column[];
row: Snippet<[T, number]>;
searchPlaceholder?: string;
/** `list` renders rows as cards (the row snippet gives each card), with sort buttons instead of a header. */
layout?: 'table' | 'list';
} = $props();
let searchInput = $derived.by(() => meta.search);
@@ -115,72 +118,104 @@
>
</div>
<!-- table -->
<div class="overflow-x-auto rounded-lg border border-border">
<table class="w-full">
<thead>
<tr class="border-b border-border bg-surface-800">
{#each columns as col}
<th
class="{col.class ??
'px-4 text-left'} py-3 text-[11px] font-semibold tracking-wider text-text-secondary uppercase"
>
{#if col.sortable}
<button
class="inline-flex items-center gap-1 transition-colors hover:text-text-primary"
onclick={() => toggleSort(col.key)}
>
{col.label}
{#if meta.sortBy === col.key}
<svg
class="h-3 w-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="3"
>
{#if meta.sortDir === 'asc'}
<path stroke-linecap="round" stroke-linejoin="round" d="M5 15l7-7 7 7" />
{:else}
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
{/if}
</svg>
{:else}
<svg
class="h-3 w-3 opacity-30"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
/>
</svg>
{/if}
</button>
{:else}
{col.label}
{/if}
</th>
{/each}
</tr>
</thead>
<tbody class="divide-y divide-border/50">
{#each data as item, i}
{@render row(item, i)}
{:else}
<tr>
<td colspan={columns.length} class="px-4 py-8 text-center text-sm text-text-secondary">
{meta.search ? `No results for "${meta.search}"` : 'No data'}
</td>
</tr>
{#if layout === 'list'}
{#if columns.some((col) => col.sortable)}
<div class="mb-3 flex flex-wrap items-center gap-1.5 text-xs text-text-secondary">
<span>Sort by</span>
{#each columns.filter((col) => col.sortable) as col (col.key)}
<button
class="rounded-md border px-2 py-1 transition-colors {meta.sortBy === col.key
? 'border-white/20 bg-white/5 text-text-primary'
: 'border-transparent hover:text-text-primary'}"
onclick={() => toggleSort(col.key)}
>{col.label}{meta.sortBy === col.key
? meta.sortDir === 'asc'
? ' ↑'
: ' ↓'
: ''}</button
>
{/each}
</tbody>
</table>
</div>
</div>
{/if}
<div class="flex flex-col gap-2">
{#each data as item, i}
{@render row(item, i)}
{:else}
<p
class="rounded-xl border border-dashed border-white/10 px-4 py-8 text-center text-sm text-text-secondary"
>
{meta.search ? `No results for "${meta.search}"` : 'Nothing here yet'}
</p>
{/each}
</div>
{:else}
<!-- table -->
<div class="overflow-x-auto rounded-lg border border-border">
<table class="w-full">
<thead>
<tr class="border-b border-border bg-surface-800">
{#each columns as col}
<th
class="{col.class ??
'px-4 text-left'} py-3 text-[11px] font-semibold tracking-wider text-text-secondary uppercase"
>
{#if col.sortable}
<button
class="inline-flex items-center gap-1 transition-colors hover:text-text-primary"
onclick={() => toggleSort(col.key)}
>
{col.label}
{#if meta.sortBy === col.key}
<svg
class="h-3 w-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="3"
>
{#if meta.sortDir === 'asc'}
<path stroke-linecap="round" stroke-linejoin="round" d="M5 15l7-7 7 7" />
{:else}
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
{/if}
</svg>
{:else}
<svg
class="h-3 w-3 opacity-30"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
/>
</svg>
{/if}
</button>
{:else}
{col.label}
{/if}
</th>
{/each}
</tr>
</thead>
<tbody class="divide-y divide-border/50">
{#each data as item, i}
{@render row(item, i)}
{:else}
<tr>
<td colspan={columns.length} class="px-4 py-8 text-center text-sm text-text-secondary">
{meta.search ? `No results for "${meta.search}"` : 'No data'}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
<!-- pagination -->
{#if meta.totalPages > 1}