feat(table): add header icon support and enhance cell rendering options

This commit is contained in:
Sam Chau
2025-11-05 07:29:55 +10:30
parent 4bcbdd77c8
commit bba8590b07
2 changed files with 74 additions and 4 deletions

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import type { ComponentType } from 'svelte';
interface Tab {
label: string;
href: string;
active?: boolean;
icon?: ComponentType;
}
export let tabs: Tab[] = [];
</script>
<div class="border-b border-neutral-200 dark:border-neutral-800">
<nav class="-mb-px flex gap-2" aria-label="Tabs">
{#each tabs as tab (tab.href)}
<a
href={tab.href}
class="flex items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium transition-colors {tab.active
? 'border-blue-600 text-blue-600 dark:border-blue-500 dark:text-blue-500'
: 'border-transparent text-neutral-600 hover:border-neutral-300 hover:text-neutral-900 dark:text-neutral-400 dark:hover:border-neutral-700 dark:hover:text-neutral-50'}"
>
{#if tab.icon}
<svelte:component this={tab.icon} size={16} />
{/if}
{tab.label}
</a>
{/each}
<!-- Actions slot for custom action tabs (like Add Instance) -->
<slot name="actions" />
</nav>
</div>

View File

@@ -9,6 +9,8 @@
key: string;
/** Header text to display */
header: string;
/** Optional icon component to display before header text */
headerIcon?: ComponentType;
/** Optional width class (e.g., 'w-32', 'w-1/4') */
width?: string;
/** Text alignment */
@@ -16,7 +18,7 @@
/** Whether column is sortable */
sortable?: boolean;
/** Custom cell renderer - receives the full row object */
cell?: (row: T) => string | ComponentType;
cell?: (row: T) => string | ComponentType | { html: string };
}
/**
@@ -27,6 +29,7 @@
export let hoverable: boolean = true;
export let compact: boolean = false;
export let emptyMessage: string = 'No data available';
export let onRowClick: ((row: T) => void) | undefined = undefined;
/**
* Get cell value by key path (supports nested properties like 'user.name')
@@ -61,7 +64,12 @@
<th
class={`${compact ? 'px-4 py-2' : 'px-6 py-3'} text-xs font-medium uppercase tracking-wider text-neutral-700 dark:text-neutral-300 ${getAlignClass(column.align)} ${column.width || ''}`}
>
{column.header}
<div class="flex items-center gap-1.5 {column.align === 'center' ? 'justify-center' : column.align === 'right' ? 'justify-end' : ''}">
{#if column.headerIcon}
<svelte:component this={column.headerIcon} size={14} />
{/if}
{column.header}
</div>
</th>
{/each}
<!-- Actions column slot -->
@@ -89,9 +97,10 @@
{:else}
{#each data as row, rowIndex}
<tr
class={hoverable
class="{hoverable
? 'transition-colors hover:bg-neutral-50 dark:hover:bg-neutral-900'
: ''}
: ''} {onRowClick ? 'cursor-pointer' : ''}"
on:click={() => onRowClick && onRowClick(row)}
>
{#each columns as column}
<td
@@ -101,6 +110,8 @@
{@const rendered = column.cell(row)}
{#if typeof rendered === 'string'}
{rendered}
{:else if typeof rendered === 'object' && 'html' in rendered}
{@html rendered.html}
{:else}
<svelte:component this={rendered} {row} />
{/if}
@@ -124,3 +135,29 @@
</tbody>
</table>
</div>
<style>
td :global(ul) {
list-style-type: disc;
padding-left: 1.5rem;
margin: 0.5rem 0;
}
td :global(ol) {
list-style-type: decimal;
padding-left: 1.5rem;
margin: 0.5rem 0;
}
td :global(li) {
margin: 0.25rem 0;
}
td :global(p) {
margin: 0.5rem 0;
}
td :global(strong) {
font-weight: 600;
}
</style>