Refactor data table components for improved layout and functionality
Publish telemt-api gateway Docker image / test (push) Successful in 27s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m22s

- Made the `title` prop optional in `DataTableCard`, allowing for more flexible usage without a title.
- Updated the rendering logic in `DataTableCard` to conditionally display the title and description.
- Enhanced the styling of table headers across various pages for a more consistent appearance, removing unnecessary backdrop blur effects.
- Replaced button elements with native button tags in `SortableTh` for better accessibility and semantic HTML.
This commit is contained in:
Denozordec
2026-03-31 19:07:04 +07:00
parent 8e616cd1c4
commit 9533241624
7 changed files with 46 additions and 37 deletions
@@ -10,7 +10,8 @@
toolbar, toolbar,
children children
}: { }: {
title: string; /** Без title — как в shadcn dashboard-01: сразу toolbar и таблица */
title?: string;
description?: string; description?: string;
toolbar?: Snippet; toolbar?: Snippet;
children: Snippet; children: Snippet;
@@ -19,12 +20,14 @@
</script> </script>
<Card.Root class={cn('overflow-hidden rounded-lg border border-border shadow-sm', className)}> <Card.Root class={cn('overflow-hidden rounded-lg border border-border shadow-sm', className)}>
<div class="border-b border-border bg-muted/40 px-4 py-3"> {#if title}
<Card.Title class="text-base font-semibold">{title}</Card.Title> <div class="border-b border-border bg-muted/40 px-4 py-3">
{#if description} <Card.Title class="text-base font-semibold">{title}</Card.Title>
<Card.Description class="text-xs sm:text-sm">{description}</Card.Description> {#if description}
{/if} <Card.Description class="text-xs sm:text-sm">{description}</Card.Description>
</div> {/if}
</div>
{/if}
{#if toolbar} {#if toolbar}
{@render toolbar()} {@render toolbar()}
{/if} {/if}
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import * as Table from '$lib/components/ui/table/index.js'; import * as Table from '$lib/components/ui/table/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '$lib/utils.js';
import ArrowUpDownIcon from '@lucide/svelte/icons/arrow-up-down'; import ArrowUpDownIcon from '@lucide/svelte/icons/arrow-up-down';
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up'; import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
@@ -25,23 +24,31 @@
} = $props(); } = $props();
</script> </script>
<Table.Head class={cn(numeric && 'text-right', className)}> <Table.Head
<Button class={cn(
variant="ghost" 'px-3 py-2 text-left align-middle text-sm font-medium text-foreground',
size="sm" numeric && 'text-right',
className
)}
>
<button
type="button"
class={cn( class={cn(
'-ml-2 h-8 gap-1 px-2 font-medium text-muted-foreground hover:text-foreground', 'inline-flex max-w-full items-center gap-1.5 rounded-md',
'text-foreground/90 transition-colors',
'hover:bg-accent/60 hover:text-foreground',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/45',
numeric && 'ms-auto' numeric && 'ms-auto'
)} )}
onclick={onToggle} onclick={onToggle}
> >
{@render children()} {@render children()}
{#if active && dir === 'asc'} {#if active && dir === 'asc'}
<ArrowUpIcon class="size-3.5 opacity-70" aria-hidden="true" /> <ArrowUpIcon class="size-3 shrink-0 opacity-70" aria-hidden="true" />
{:else if active && dir === 'desc'} {:else if active && dir === 'desc'}
<ArrowDownIcon class="size-3.5 opacity-70" aria-hidden="true" /> <ArrowDownIcon class="size-3 shrink-0 opacity-70" aria-hidden="true" />
{:else} {:else}
<ArrowUpDownIcon class="size-3.5 opacity-50" aria-hidden="true" /> <ArrowUpDownIcon class="size-3 shrink-0 opacity-40" aria-hidden="true" />
{/if} {/if}
</Button> </button>
</Table.Head> </Table.Head>
+6 -6
View File
@@ -666,8 +666,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(70vh,520px)] w-full" orientation="both"> <ScrollArea class="max-h-[min(70vh,520px)] w-full" orientation="both">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<SortableTh <SortableTh
active={ipSortKey === 'ip'} active={ipSortKey === 'ip'}
dir={ipSortDir} dir={ipSortDir}
@@ -819,8 +819,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(70vh,480px)] w-full" orientation="both"> <ScrollArea class="max-h-[min(70vh,480px)] w-full" orientation="both">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<SortableTh <SortableTh
active={nodeSortKey === 'alias'} active={nodeSortKey === 'alias'}
dir={nodeSortDir} dir={nodeSortDir}
@@ -953,8 +953,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(60vh,400px)] w-full" orientation="vertical"> <ScrollArea class="max-h-[min(60vh,400px)] w-full" orientation="vertical">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<SortableTh <SortableTh
active={topSortKey === 'user'} active={topSortKey === 'user'}
dir={topSortDir} dir={topSortDir}
+2 -2
View File
@@ -378,8 +378,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(75vh,640px)] w-full" orientation="both"> <ScrollArea class="max-h-[min(75vh,640px)] w-full" orientation="both">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<SortableTh <SortableTh
active={incSortKey === 'sev'} active={incSortKey === 'sev'}
dir={incSortDir} dir={incSortDir}
+2 -2
View File
@@ -610,8 +610,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(75vh,560px)] w-full" orientation="both"> <ScrollArea class="max-h-[min(75vh,560px)] w-full" orientation="both">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<SortableTh <SortableTh
active={ipSortKey === 'user'} active={ipSortKey === 'user'}
dir={ipSortDir} dir={ipSortDir}
+2 -2
View File
@@ -404,8 +404,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(70vh,520px)] w-full" orientation="both"> <ScrollArea class="max-h-[min(70vh,520px)] w-full" orientation="both">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<SortableTh <SortableTh
active={liveSortKey === 'sev'} active={liveSortKey === 'sev'}
dir={liveSortDir} dir={liveSortDir}
+7 -8
View File
@@ -389,10 +389,7 @@
</Card.Root> </Card.Root>
{/snippet} {/snippet}
{#snippet children()} {#snippet children()}
<DataTableCard <DataTableCard>
title="Список пользователей"
description="Слияние по имени между серверами; клик по строке — карточка пользователя"
>
{#snippet toolbar()} {#snippet toolbar()}
<div class="border-b border-border"> <div class="border-b border-border">
<div <div
@@ -467,8 +464,8 @@
{/snippet} {/snippet}
<ScrollArea class="max-h-[min(70vh,560px)] w-full" orientation="both"> <ScrollArea class="max-h-[min(70vh,560px)] w-full" orientation="both">
<Table.Root> <Table.Root>
<Table.Header class="sticky top-0 z-10 border-b border-border bg-muted/95 backdrop-blur-sm"> <Table.Header class="sticky top-0 z-10 bg-muted">
<Table.Row class="border-0 hover:bg-transparent"> <Table.Row class="border-b border-border/80 hover:bg-transparent">
<Table.Head class="w-10 px-2"> <Table.Head class="w-10 px-2">
{#if pageUsernames.length > 0} {#if pageUsernames.length > 0}
<Checkbox <Checkbox
@@ -487,7 +484,7 @@
>Имя</SortableTh> >Имя</SortableTh>
<Table.Head <Table.Head
class={cn( class={cn(
'text-muted-foreground text-xs font-medium uppercase tracking-wide', 'px-3 py-2 text-left align-middle text-sm font-medium text-foreground',
!showColLinks && 'hidden', !showColLinks && 'hidden',
showColLinks && 'hidden sm:table-cell' showColLinks && 'hidden sm:table-cell'
)} )}
@@ -515,7 +512,9 @@
showColExpires && 'hidden sm:table-cell' showColExpires && 'hidden sm:table-cell'
)} )}
>Истекает</SortableTh> >Истекает</SortableTh>
<Table.Head class="w-12 px-2 text-right" aria-hidden="true"></Table.Head> <Table.Head class="w-12 px-2 text-right">
<span class="sr-only">Действия</span>
</Table.Head>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>