Spaces:
Running
Running
| <script lang="ts" module> | |
| import type { Snippet } from 'svelte'; | |
| export type TableColumn<Key extends string = string> = { | |
| key: Key; | |
| label: string | (() => string); | |
| headClass?: string | (() => string | undefined); | |
| cellClass?: string | (() => string | undefined); | |
| ariaSort?: () => 'ascending' | 'descending' | 'none'; | |
| }; | |
| </script> | |
| <script lang="ts" generics="Row, Column extends TableColumn"> | |
| import * as UITable from '$lib/components/ui/table'; | |
| import { cn } from '$lib/utils.js'; | |
| type Props = { | |
| title?: string; | |
| data: Row[]; | |
| columns: Column[]; | |
| getRowKey: (row: Row, index: number) => string | number; | |
| rowLabel?: (row: Row, index: number) => string; | |
| rowClass?: string | ((row: Row, index: number) => string); | |
| containerClass?: string; | |
| tableClass?: string; | |
| headerClass?: string; | |
| onrowclick?: (row: Row, index: number) => void; | |
| header?: Snippet<[Column]>; | |
| cell: Snippet<[Row, Column, number]>; | |
| }; | |
| let { | |
| title, | |
| data, | |
| columns, | |
| getRowKey, | |
| rowLabel, | |
| rowClass, | |
| containerClass = 'mt-4 overflow-hidden rounded-2xl border bg-card', | |
| tableClass = 'min-w-[980px]', | |
| headerClass = 'bg-muted/50 hover:bg-muted/50 [&_th]:text-muted-foreground', | |
| onrowclick, | |
| header, | |
| cell | |
| }: Props = $props(); | |
| function labelText(label: string | (() => string)) { | |
| return typeof label === 'function' ? label() : label; | |
| } | |
| function resolvedRowClass(row: Row, index: number) { | |
| return typeof rowClass === 'function' ? rowClass(row, index) : rowClass; | |
| } | |
| function resolvedColumnClass(className: string | (() => string | undefined) | undefined) { | |
| return typeof className === 'function' ? className() : className; | |
| } | |
| function handleRowKeydown(event: KeyboardEvent, row: Row, index: number) { | |
| if (!onrowclick || (event.key !== 'Enter' && event.key !== ' ')) return; | |
| event.preventDefault(); | |
| onrowclick(row, index); | |
| } | |
| </script> | |
| <section class={containerClass}> | |
| {#if title} | |
| <h2 class="sr-only">{title}</h2> | |
| {/if} | |
| <UITable.Root class={tableClass}> | |
| <UITable.Header> | |
| <UITable.Row class={headerClass}> | |
| {#each columns as column (column.key)} | |
| <UITable.Head | |
| class={cn('text-muted-foreground', resolvedColumnClass(column.headClass))} | |
| aria-sort={column.ariaSort?.() ?? 'none'} | |
| > | |
| {#if header} | |
| {@render header(column)} | |
| {:else} | |
| {labelText(column.label)} | |
| {/if} | |
| </UITable.Head> | |
| {/each} | |
| </UITable.Row> | |
| </UITable.Header> | |
| <UITable.Body> | |
| {#each data as row, index (getRowKey(row, index))} | |
| <UITable.Row | |
| class={resolvedRowClass(row, index)} | |
| role={onrowclick ? 'button' : undefined} | |
| tabindex={onrowclick ? 0 : undefined} | |
| aria-label={rowLabel?.(row, index)} | |
| onclick={() => onrowclick?.(row, index)} | |
| onkeydown={(event) => handleRowKeydown(event, row, index)} | |
| > | |
| {#each columns as column (column.key)} | |
| <UITable.Cell class={resolvedColumnClass(column.cellClass)}> | |
| {@render cell(row, column, index)} | |
| </UITable.Cell> | |
| {/each} | |
| </UITable.Row> | |
| {/each} | |
| </UITable.Body> | |
| </UITable.Root> | |
| </section> | |