Good Practices
Svelte Tables
The @tanstack/svelte-table package offers a powerful asynchronous state management for your API calls in Svelte.
We recommend using it in all of Table view components.
# Usage Example
svelte
<script lang="ts">
import {
createSvelteTable,
flexRender,
getCoreRowModel,
getSortedRowModel,
getExpandedRowModel,
type ColumnDef,
type TableOptions
} from '@tanstack/svelte-table';
import {
Table,
TableBody,
TableBodyCell,
TableBodyRow,
TableHead,
TableHeadCell,
Card
} from 'flowbite-svelte';
import { writable } from 'svelte/store';
import { FontAwesomeIcon } from 'fontawesome-svelte';
import { goto } from '$app/navigation';
import type { PostsInterface } from '$lib/models/posts';
import { DateTime } from 'luxon';
import SvelteMarkdown from 'svelte-markdown';
export let posts: PostsInterface[] = [];
const columns: ColumnDef<{ id: string }>[] = [
{
accessorKey: 'status',
header: 'Status'
},
{
accessorKey: 'title',
header: 'Title'
},
{
accessorKey: 'body',
header: 'Description',
cell: ({ getValue }) => {
const val = getValue() as object;
return val ? (val.length > 55 ? val.slice(0, 55) : val) : '';
}
},
{
accessorKey: 'publish_at',
header: 'publish_at',
cell: ({ getValue }) => {
const val = getValue() as object[];
const valueString = DateTime.fromISO(val).toLocaleString(DateTime.DATE_MED);
return valueString;
}
},
{
accessorKey: 'author',
header: 'Author'
}
];
const options = writable<TableOptions<{ id: string }>>({
data: posts,
columns: columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getExpandedRowModel: getExpandedRowModel(),
manualSorting: true,
manualPagination: true,
onStateChange: () => {}
});
$: options.update((options) => ({
...options,
data: posts,
state: {
...options.state
}
}));
const table = createSvelteTable(options);
</script>
<Card size="100" class="">
<Table hoverable={true} striped={true}>
<TableHead>
{#each $table.getHeaderGroups() as headerGroup}
{#each headerGroup.headers as header}
<TableHeadCell>
{#if !header.isPlaceholder}
<a
role="button"
tabindex="0"
href="#/"
class:cursor-pointer={header.column.getCanSort()}
class:select-none={header.column.getCanSort()}
on:click|preventDefault={header.column.getToggleSortingHandler()}
>
<svelte:component
this={flexRender(header.column.columnDef.header, header.getContext())}
/>
{#if header.column.getIsSorted().toString() === 'asc'}
<FontAwesomeIcon
icon={['fal', 'chevron-up']}
size={'lg'}
class="text-secondary"
/>
{:else if header.column.getIsSorted().toString() === 'desc'}
<FontAwesomeIcon
icon={['fal', 'chevron-down']}
size={'lg'}
class="text-secondary"
/>
{/if}
</a>
{/if}
</TableHeadCell>
{/each}
{/each}
</TableHead>
<TableBody class="divide-y">
{#each $table.getRowModel().rows as row, index}
<TableBodyRow
class="cursor-pointer"
href=""
on:click={() => {
goto(`/posts/${posts[index].id}`);
}}
>
{#each row.getVisibleCells() as cell}
<TableBodyCell>
{#if cell.id.includes('status')}
{#if cell.row.original.status === 'published'}
<span class="">
<FontAwesomeIcon
icon={['fas', 'badge-check']}
size={'lg'}
class="text-lime-600"
/> Approved</span
>
{:else}
<span class="">
<FontAwesomeIcon
icon={['fas', 'seal-question']}
size={'lg'}
class="text-amber-500"
/> Pending</span
>
{/if}
{:else if cell.id.includes('body')}
<p class="line-clamp-2 w-52">
<SvelteMarkdown source={cell.row.original?.body || '--'} />
</p>
{:else}
<svelte:component
this={flexRender(cell.column.columnDef.cell, cell.getContext())}
/>
{/if}
</TableBodyCell>
{/each}
</TableBodyRow>
{/each}
</TableBody>
<tfoot>
{#each $table.getFooterGroups() as footerGroup}
<tr>
{#each footerGroup.headers as header}
<th>
{#if !header.isPlaceholder}
<svelte:component
this={flexRender(header.column.columnDef.footer, header.getContext())}
/>
{/if}
</th>
{/each}
</tr>
{/each}
</tfoot>
</Table>
</Card>