This commit is contained in:
David Arranz 2026-06-04 18:37:24 +02:00
parent 6e83958e30
commit 402b8300b1
4 changed files with 86 additions and 85 deletions

View File

@ -75,10 +75,7 @@ export function DataTableToolbar<TData>({
// Render principal
return (
<div
className={cn(
"flex items-center justify-between gap-2 py-4 bg-transparent border-b",
className
)}
className={cn("flex items-center justify-between gap-2 px-2 py-2 bg-transparent", className)}
>
{/* IZQUIERDA: acciones + contador */}
<div className="flex flex-1 items-center gap-3 flex-wrap">
@ -187,7 +184,7 @@ export function DataTableToolbar<TData>({
)}
{/* Contador de selección */}
<div aria-live="polite" className="text-sm text-muted-foreground ml-2">
<div aria-live="polite" className="text-sm text-muted-foreground">
{hasSelection
? t("components.datatable.selection_summary", {
count: selectedCount,

View File

@ -258,15 +258,18 @@ export function DataTable<TData, TValue>({
// Render principal
return (
<div className="transition-[max-height] duration-300 ease-in-out">
<div className="flex flex-col gap-0">
<div className="flex flex-col rounded-lg border overflow-clip">
<DataTableToolbar showViewOptions={!readOnly} table={table} />
<div className="overflow-hidden rounded-md border">
<div className="overflow-hidden rounded-md border-t">
<TableComp className="min-w-full sm:min-w-[820px] w-full">
{/* CABECERA */}
<TableHeader>
{table.getHeaderGroups().map((hg) => (
<TableRow className="bg-muted/50 hover:bg-muted/50 text-xs sm:text-sm" key={hg.id}>
<TableRow
className="bg-background hover:bg-muted/50 text-xs sm:text-sm"
key={hg.id}
>
{hg.headers.map((h) => {
/*
const w = h.getSize();
@ -286,7 +289,7 @@ export function DataTable<TData, TValue>({
return (
<TableHead
className={cn("whitespace-nowrap", headerClassName)}
className={cn("whitespace-nowrap font-semibold", headerClassName)}
colSpan={h.colSpan}
key={h.id}
>

View File

@ -2,68 +2,68 @@
/** Skeleton del footer de paginación, imita DataTablePagination */
export function SkeletonDataTableFooter({
pageIndex = 0,
pageSize = 10,
totalItems = 0,
pageIndex = 0,
pageSize = 10,
totalItems = 0,
}: {
pageIndex?: number; // 0-based
pageSize?: number;
totalItems?: number;
pageIndex?: number; // 0-based
pageSize?: number;
totalItems?: number;
}) {
// Cálculo de rango (1-based visual)
const start = totalItems > 0 ? pageIndex * pageSize + 1 : 0;
const end = totalItems > 0 ? Math.min(start + pageSize - 1, totalItems) : 0;
// Cálculo de rango (1-based visual)
const start = totalItems > 0 ? pageIndex * pageSize + 1 : 0;
const end = totalItems > 0 ? Math.min(start + pageSize - 1, totalItems) : 0;
return (
<div
role="status"
aria-busy="true"
className="flex items-center justify-between border-t border-border px-4 py-3 bg-background"
>
{/* Izquierda: rango visible */}
<div className="flex flex-col sm:flex-row items-center gap-4 flex-1 text-sm text-muted-foreground">
<span aria-live="polite">
{/* Texto real + shimmer leve para coherencia visual */}
{`Mostrando ${start}${end} de ${totalItems} registros`}
</span>
return (
<div
aria-busy="true"
className="flex items-center justify-between border-t border-border px-2 py-2 bg-background"
role="status"
>
{/* Izquierda: rango visible */}
<div className="flex flex-col sm:flex-row items-center gap-4 flex-1 text-sm text-muted-foreground">
<span aria-live="polite">
{/* Texto real + shimmer leve para coherencia visual */}
{`Mostrando ${start}${end} de ${totalItems} registros`}
</span>
{/* 'Filas por página' + trigger del select simulado */}
<div className="flex items-center gap-2">
<span>Filas por página</span>
<div
className="h-8 w-20 rounded bg-foreground/10 animate-pulse motion-reduce:animate-none"
aria-hidden
/>
</div>
</div>
{/* Derecha: controles de paginación simulados */}
<div className="flex items-center gap-2">
{/* Primera */}
<div
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
aria-hidden
/>
{/* Anterior */}
<div
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
aria-hidden
/>
{/* Indicador de página */}
<div className="h-5 w-28 rounded bg-foreground/10 animate-pulse motion-reduce:animate-none" />
{/* Siguiente */}
<div
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
aria-hidden
/>
{/* Última */}
<div
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
aria-hidden
/>
</div>
<span className="sr-only">Loading pagination</span>
{/* 'Filas por página' + trigger del select simulado */}
<div className="flex items-center gap-2">
<span>Filas por página</span>
<div
aria-hidden
className="h-8 w-20 rounded bg-foreground/10 animate-pulse motion-reduce:animate-none"
/>
</div>
);
</div>
{/* Derecha: controles de paginación simulados */}
<div className="flex items-center gap-2">
{/* Primera */}
<div
aria-hidden
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
/>
{/* Anterior */}
<div
aria-hidden
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
/>
{/* Indicador de página */}
<div className="h-5 w-28 rounded bg-foreground/10 animate-pulse motion-reduce:animate-none" />
{/* Siguiente */}
<div
aria-hidden
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
/>
{/* Última */}
<div
aria-hidden
className="h-8 w-8 rounded-md bg-foreground/10 animate-pulse motion-reduce:animate-none"
/>
</div>
<span className="sr-only">Loading pagination</span>
</div>
);
}

View File

@ -69,23 +69,6 @@ export const SwitchField = <TFormValues extends FieldValues>({
data-invalid={hasError}
orientation={orientation}
>
<Switch
aria-describedby={descriptionId}
aria-invalid={hasError || undefined}
aria-required={required || undefined}
checked={field.value === true}
className={cn(inputClassName)}
disabled={isDisabled}
id={inputId}
name={field.name}
onBlur={field.onBlur}
onCheckedChange={(checked) =>
onCheckedChange ? onCheckedChange(checked) : field.onChange(checked)
}
ref={field.ref}
required={required}
/>
<FieldContent className="gap-1">
<FormFieldLabel className="font-normal" htmlFor={inputId} required={required}>
{label}
@ -99,6 +82,24 @@ export const SwitchField = <TFormValues extends FieldValues>({
<FieldError errors={[fieldState.error]} />
</FieldContent>
<Switch
aria-describedby={descriptionId}
aria-invalid={hasError || undefined}
aria-required={required || undefined}
checked={field.value === true}
className={cn(inputClassName)}
disabled={isDisabled}
id={inputId}
name={field.name}
onBlur={field.onBlur}
onCheckedChange={(checked) => {
field.onChange(checked);
onCheckedChange?.(checked);
}}
ref={field.ref}
required={required}
/>
</Field>
);
}}