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

View File

@ -258,15 +258,18 @@ export function DataTable<TData, TValue>({
// Render principal // Render principal
return ( return (
<div className="transition-[max-height] duration-300 ease-in-out"> <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} /> <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"> <TableComp className="min-w-full sm:min-w-[820px] w-full">
{/* CABECERA */} {/* CABECERA */}
<TableHeader> <TableHeader>
{table.getHeaderGroups().map((hg) => ( {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) => { {hg.headers.map((h) => {
/* /*
const w = h.getSize(); const w = h.getSize();
@ -286,7 +289,7 @@ export function DataTable<TData, TValue>({
return ( return (
<TableHead <TableHead
className={cn("whitespace-nowrap", headerClassName)} className={cn("whitespace-nowrap font-semibold", headerClassName)}
colSpan={h.colSpan} colSpan={h.colSpan}
key={h.id} key={h.id}
> >

View File

@ -2,68 +2,68 @@
/** Skeleton del footer de paginación, imita DataTablePagination */ /** Skeleton del footer de paginación, imita DataTablePagination */
export function SkeletonDataTableFooter({ export function SkeletonDataTableFooter({
pageIndex = 0, pageIndex = 0,
pageSize = 10, pageSize = 10,
totalItems = 0, totalItems = 0,
}: { }: {
pageIndex?: number; // 0-based pageIndex?: number; // 0-based
pageSize?: number; pageSize?: number;
totalItems?: number; totalItems?: number;
}) { }) {
// Cálculo de rango (1-based visual) // Cálculo de rango (1-based visual)
const start = totalItems > 0 ? pageIndex * pageSize + 1 : 0; const start = totalItems > 0 ? pageIndex * pageSize + 1 : 0;
const end = totalItems > 0 ? Math.min(start + pageSize - 1, totalItems) : 0; const end = totalItems > 0 ? Math.min(start + pageSize - 1, totalItems) : 0;
return ( return (
<div <div
role="status" aria-busy="true"
aria-busy="true" className="flex items-center justify-between border-t border-border px-2 py-2 bg-background"
className="flex items-center justify-between border-t border-border px-4 py-3 bg-background" role="status"
> >
{/* Izquierda: rango visible */} {/* Izquierda: rango visible */}
<div className="flex flex-col sm:flex-row items-center gap-4 flex-1 text-sm text-muted-foreground"> <div className="flex flex-col sm:flex-row items-center gap-4 flex-1 text-sm text-muted-foreground">
<span aria-live="polite"> <span aria-live="polite">
{/* Texto real + shimmer leve para coherencia visual */} {/* Texto real + shimmer leve para coherencia visual */}
{`Mostrando ${start}${end} de ${totalItems} registros`} {`Mostrando ${start}${end} de ${totalItems} registros`}
</span> </span>
{/* 'Filas por página' + trigger del select simulado */} {/* 'Filas por página' + trigger del select simulado */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span>Filas por página</span> <span>Filas por página</span>
<div <div
className="h-8 w-20 rounded bg-foreground/10 animate-pulse motion-reduce:animate-none" aria-hidden
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
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>
</div> </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} data-invalid={hasError}
orientation={orientation} 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"> <FieldContent className="gap-1">
<FormFieldLabel className="font-normal" htmlFor={inputId} required={required}> <FormFieldLabel className="font-normal" htmlFor={inputId} required={required}>
{label} {label}
@ -99,6 +82,24 @@ export const SwitchField = <TFormValues extends FieldValues>({
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
</FieldContent> </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> </Field>
); );
}} }}