Cambios
This commit is contained in:
+105
-42
@@ -6,54 +6,117 @@ import Input from "@/components/input";
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="container my-5">
|
||||
<h1 className="mb-4">
|
||||
Componente: <code>Input</code>
|
||||
<h1 className="mb-4 display-5">
|
||||
📦 Componente: <code>Input</code>
|
||||
</h1>
|
||||
|
||||
<p className="mb-4">
|
||||
Este componente encapsula un <code>{`<input>`}</code> con su respectiva
|
||||
etiqueta y funcionalidad adicional para mostrar u ocultar contraseñas si
|
||||
el tipo es <code>password</code>. Puedes personalizar las clases de cada
|
||||
parte del input.
|
||||
<p className="mb-4 text-muted">
|
||||
El componente <code>{`<Input />`}</code> encapsula un campo de entrada
|
||||
HTML estándar con mejoras en accesibilidad, estilo y experiencia de
|
||||
usuario. Soporta etiquetas, clases personalizadas, inputs
|
||||
deshabilitados, y funcionalidad para mostrar u ocultar contraseñas.
|
||||
</p>
|
||||
|
||||
<h2 className="h5 mt-4">🔹 Uso básico</h2>
|
||||
<Input label="Nombre" name="nombre" placeholder="Escribe tu nombre" />
|
||||
<hr className="my-5" />
|
||||
|
||||
<h2 className="h5 mt-4">🔹 Campo de tipo contraseña</h2>
|
||||
<p className="mb-2">
|
||||
Incluye un botón para mostrar u ocultar el contenido del campo.
|
||||
</p>
|
||||
<Input
|
||||
label="Contraseña"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
|
||||
<h2 className="h5 mt-4">🔹 Input deshabilitado</h2>
|
||||
<Input
|
||||
label="Correo electrónico"
|
||||
name="email"
|
||||
placeholder="email@ejemplo.com"
|
||||
disabled
|
||||
/>
|
||||
|
||||
<h2 className="h5 mt-4">🔹 Personalización de clases</h2>
|
||||
<Input
|
||||
label="Usuario"
|
||||
name="username"
|
||||
placeholder="ej: miguel_dev"
|
||||
className={{
|
||||
container: "mb-3 bg-light p-3 rounded",
|
||||
label: "fw-bold text-primary",
|
||||
input: "border border-primary",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div style={{width: '300px'}} className="mt-5 ms-5">
|
||||
<section>
|
||||
<h2 className="h5">🔹 Uso básico</h2>
|
||||
<p>Un campo de texto simple con etiqueta y placeholder.</p>
|
||||
<Input label="Nombre" name="nombre" placeholder="Escribe tu nombre" />
|
||||
</div>
|
||||
<pre className="mt-2 bg-light p-3 rounded">
|
||||
<code>{`<Input label="Nombre" name="nombre" placeholder="Escribe tu nombre" />`}</code>
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<section>
|
||||
<h2 className="h5">🔐 Campo de tipo contraseña</h2>
|
||||
<p className="mb-2">
|
||||
Incluye un botón para mostrar u ocultar el contenido del campo.
|
||||
</p>
|
||||
<Input
|
||||
label="Contraseña"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
<pre className="mt-2 bg-light p-3 rounded">
|
||||
<code>{`<Input
|
||||
label="Contraseña"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
/>`}</code>
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<section>
|
||||
<h2 className="h5">🚫 Input deshabilitado</h2>
|
||||
<Input
|
||||
label="Correo electrónico"
|
||||
name="email"
|
||||
placeholder="email@ejemplo.com"
|
||||
disabled
|
||||
/>
|
||||
<pre className="mt-2 bg-light p-3 rounded">
|
||||
<code>{`<Input
|
||||
label="Correo electrónico"
|
||||
name="email"
|
||||
placeholder="email@ejemplo.com"
|
||||
disabled
|
||||
/>`}</code>
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<section>
|
||||
<h2 className="h5">🎨 Personalización de clases</h2>
|
||||
<p>
|
||||
Puedes personalizar el estilo de cada parte del input: contenedor,
|
||||
etiqueta e input.
|
||||
</p>
|
||||
<Input
|
||||
label="Usuario"
|
||||
name="username"
|
||||
placeholder="ej: miguel_dev"
|
||||
className={{
|
||||
container: "mb-3 bg-light p-3 rounded",
|
||||
label: "fw-bold text-primary",
|
||||
input: "border border-primary",
|
||||
}}
|
||||
/>
|
||||
<pre className="mt-2 bg-light p-3 rounded">
|
||||
<code>{`<Input
|
||||
label="Usuario"
|
||||
name="username"
|
||||
placeholder="ej: miguel_dev"
|
||||
className={{
|
||||
container: "mb-3 bg-light p-3 rounded",
|
||||
label: "fw-bold text-primary",
|
||||
input: "border border-primary",
|
||||
}}
|
||||
/>`}</code>
|
||||
</pre>
|
||||
</section>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<section>
|
||||
<h2 className="h5">📏 Input en contenedor reducido</h2>
|
||||
<p>Se adapta a contenedores pequeños sin problemas.</p>
|
||||
<div style={{ width: "300px" }} className="mt-3 ms-1 border p-3">
|
||||
<Input label="Nombre" name="nombre" placeholder="Escribe tu nombre" />
|
||||
</div>
|
||||
<pre className="mt-2 bg-light p-3 rounded">
|
||||
<code>{`<div style={{ width: "300px" }}>
|
||||
<Input label="Nombre" name="nombre" placeholder="Escribe tu nombre" />
|
||||
</div>`}</code>
|
||||
</pre>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+61
-30
@@ -1,19 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import Table, { TablaSkeleton } from "@/components/table";
|
||||
import Table, { Header, TablaSkeleton } from "@/components/table";
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* Página de demostración del componente Table.
|
||||
* Incluye explicación de uso y ejemplos con distintas configuraciones.
|
||||
*/
|
||||
export default function Page() {
|
||||
const headers: { key: "name" | "age" | "email"; label: string }[] = [
|
||||
{ key: "name", label: "Nombre" },
|
||||
{ key: "age", label: "Edad" },
|
||||
{ key: "email", label: "Correo" },
|
||||
const headers: Header<{ name: string; age: number; email: string; acciones?: null }>[] = [
|
||||
{ key: "name", label: "Nombre", width: "30%" },
|
||||
{ key: "age", label: "Edad", width: "15%" },
|
||||
{ key: "email", label: "Correo", width: "40%" },
|
||||
];
|
||||
|
||||
|
||||
const data = [
|
||||
{ name: "Juan", age: 20, email: "juan@example.com" },
|
||||
{ name: "María", age: 25, email: "maria@example.com" },
|
||||
@@ -23,21 +19,27 @@ export default function Page() {
|
||||
|
||||
return (
|
||||
<div className="container my-5">
|
||||
<h1 className="mb-4">Componente: Table</h1>
|
||||
<h1 className="mb-4 display-5">
|
||||
📊 Componente: <code>Table</code>
|
||||
</h1>
|
||||
|
||||
<p className="mb-4">
|
||||
El componente <code>Table</code> permite renderizar tablas dinámicas con
|
||||
paginación y ordenamiento por columna. Acepta un arreglo de objetos como
|
||||
datos, y una lista de headers que indican qué propiedades mostrar.
|
||||
<p className="mb-4 text-muted">
|
||||
El componente <code><Table /></code> permite mostrar datos
|
||||
tabulares con soporte para ordenamiento, paginación, renderizado
|
||||
personalizado, columnas clicables y estado de carga.
|
||||
</p>
|
||||
|
||||
<h2 className="h5 mt-4">🔹 Uso básico</h2>
|
||||
<hr className="my-5" />
|
||||
|
||||
<h2 className="h5">🔹 Uso básico</h2>
|
||||
<Table headers={headers} data={data} />
|
||||
|
||||
<h2 className="h5 mt-5">🔹 Con filas clicables</h2>
|
||||
<hr className="my-5" />
|
||||
|
||||
<h2 className="h5">🖱️ Filas clicables</h2>
|
||||
<p className="mb-2">
|
||||
Puedes pasar una función <code>onRowClick</code> para manejar clics en
|
||||
filas.
|
||||
Usa la propiedad <code>onRowClick</code> para manejar interacciones por
|
||||
fila.
|
||||
</p>
|
||||
<Table
|
||||
headers={headers}
|
||||
@@ -45,22 +47,51 @@ export default function Page() {
|
||||
onRowClick={(row) => alert(`Seleccionaste a ${row.name}`)}
|
||||
/>
|
||||
|
||||
<h2 className="h5 mt-5">🔹 Con paginación personalizada</h2>
|
||||
<hr className="my-5" />
|
||||
|
||||
<h2 className="h5">📄 Paginación personalizada</h2>
|
||||
<p className="mb-2">
|
||||
Puedes controlar cuántas filas se muestran por página usando{" "}
|
||||
<code>rowsPerPage</code>.
|
||||
Usa <code>rowsPerPage</code> para definir cuántas filas mostrar por
|
||||
página.
|
||||
</p>
|
||||
<Table headers={headers} data={data} rowsPerPage={2} />
|
||||
|
||||
<h2 className="h5 mt-5">🔹 Tabla en estado de carga</h2>
|
||||
<p className="mb-2">
|
||||
El componente <code>TablaSkeleton</code> muestra una tabla con
|
||||
placeholders mientras se cargan los datos reales. Puedes personalizar el
|
||||
número de <strong>filas</strong> y<strong>columnas</strong> usando las
|
||||
props <code>rows</code> y <code>columns</code>.
|
||||
</p>
|
||||
<hr className="my-5" />
|
||||
|
||||
<TablaSkeleton rows={3} columns={5} />
|
||||
<h2 className="h5">🎨 Render personalizado por celda</h2>
|
||||
<p>
|
||||
Puedes usar la propiedad <code>render</code> dentro de cada{" "}
|
||||
<code>header</code> para personalizar cómo se muestra una columna.
|
||||
</p>
|
||||
<Table
|
||||
headers={[
|
||||
...headers,
|
||||
{
|
||||
key: "acciones",
|
||||
label: "Acciones",
|
||||
width: "15%",
|
||||
render: (_, row) => (
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => alert(row.email)}
|
||||
>
|
||||
Ver correo
|
||||
</button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
data={data.map((d) => ({ ...d, acciones: null as null }))}
|
||||
/>
|
||||
|
||||
<hr className="my-5" />
|
||||
|
||||
<h2 className="h5">⏳ Tabla en estado de carga</h2>
|
||||
<p className="mb-2">
|
||||
El componente <code>TablaSkeleton</code> muestra una tabla de ejemplo
|
||||
mientras se cargan los datos. Puedes personalizar <code>rows</code> y{" "}
|
||||
<code>columns</code>.
|
||||
</p>
|
||||
<TablaSkeleton rows={3} columns={4} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+50
-24
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState } from "react";
|
||||
|
||||
export interface InputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className'> {
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "className"> {
|
||||
label: string;
|
||||
className?: {
|
||||
container?: string;
|
||||
@@ -9,62 +9,88 @@ export interface InputProps
|
||||
input?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Componente de entrada (`Input`) reutilizable en React.
|
||||
*
|
||||
* Este componente renderiza un campo de entrada (`<input>`) con una etiqueta opcional y
|
||||
* Este componente renderiza un campo de entrada (`<input>`) con una etiqueta y
|
||||
* funcionalidad para mostrar u ocultar contraseñas cuando el tipo es `password`.
|
||||
* También permite personalizar clases CSS y es accesible mediante `aria-label` y `htmlFor`.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - Propiedades del componente.
|
||||
* @param {string} props.label - Etiqueta del campo de entrada.
|
||||
* @param {Object} [props.className] - Clases CSS personalizables.
|
||||
* @param {string} props.label - Etiqueta visible asociada al campo de entrada.
|
||||
* @param {Object} [props.className] - Clases CSS personalizables para distintos elementos.
|
||||
* @param {string} [props.className.container] - Clases para el contenedor del input.
|
||||
* @param {string} [props.className.label] - Clases para la etiqueta del input.
|
||||
* @param {string} [props.className.input] - Clases para el input en sí.
|
||||
* @param {string} [props.type] - Tipo del input (`text`, `password`, etc.).
|
||||
* @param {string} [props.name] - Nombre del input (para formularios).
|
||||
* @param {React.InputHTMLAttributes<HTMLInputElement>} [rest] - Atributos adicionales para el input.
|
||||
* @param {string} [props.type] - Tipo del input (`text`, `password`, `email`, etc.).
|
||||
* @param {string} [props.name] - Nombre del input (útil para formularios).
|
||||
* @param {string} [props.id] - ID personalizado para el input (para accesibilidad).
|
||||
* @param {string} [props.value] - Valor del input (para uso controlado).
|
||||
* @param {function} [props.onChange] - Manejador del cambio de valor del input.
|
||||
* @param {React.InputHTMLAttributes<HTMLInputElement>} [rest] - Atributos adicionales compatibles con `<input>`.
|
||||
*
|
||||
* @example
|
||||
* // Ejemplo de uso
|
||||
* // Ejemplo de uso con contraseña:
|
||||
* <Input
|
||||
* label="Contraseña"
|
||||
* name="password"
|
||||
* type="password"
|
||||
* className={{ container: "mb-4", label: "text-sm", input: "border" }}
|
||||
* className={{
|
||||
* container: "mb-4",
|
||||
* label: "text-sm font-bold",
|
||||
* input: "border"
|
||||
* }}
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Ejemplo de input controlado:
|
||||
* <Input
|
||||
* label="Correo electrónico"
|
||||
* name="email"
|
||||
* type="email"
|
||||
* value={email}
|
||||
* onChange={(e) => setEmail(e.target.value)}
|
||||
* />
|
||||
*/
|
||||
export default function Input(props: InputProps) {
|
||||
const { className, label, type, ...rest } = props;
|
||||
const { className, label, type, name, id, ...rest } = props;
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const togglePasswordVisibility = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
const isPassword = type === "password";
|
||||
const inputType = isPassword && showPassword ? "text" : type;
|
||||
const inputId = id || name || `input-${label.replace(/\s+/g, "-")}`;
|
||||
|
||||
const togglePasswordVisibility = () => setShowPassword(!showPassword);
|
||||
|
||||
return (
|
||||
<div className={`${className?.container}`}>
|
||||
<label htmlFor={props.name} className={`form-label ${className?.label}`}>
|
||||
<div className={className?.container || ""}>
|
||||
<label
|
||||
htmlFor={inputId}
|
||||
className={`form-label ${className?.label || ""}`}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<div className="input-group">
|
||||
<input
|
||||
type={showPassword ? 'text' : type}
|
||||
className={`form-control bg-white ${className?.input}`}
|
||||
id={inputId}
|
||||
name={name}
|
||||
type={inputType}
|
||||
className={`form-control bg-white ${className?.input || ""}`}
|
||||
{...rest}
|
||||
/>
|
||||
{props.type === 'password' && (
|
||||
{isPassword && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-light border"
|
||||
onClick={togglePasswordVisibility}
|
||||
aria-label={
|
||||
showPassword ? "Ocultar contraseña" : "Mostrar contraseña"
|
||||
}
|
||||
title={showPassword ? "Ocultar contraseña" : "Mostrar contraseña"}
|
||||
>
|
||||
{showPassword ? (
|
||||
<i className="bi bi-eye-slash"></i>
|
||||
) : (
|
||||
<i className="bi bi-eye"></i>
|
||||
)}
|
||||
<i className={`bi ${showPassword ? "bi-eye-slash" : "bi-eye"}`}></i>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+83
-75
@@ -1,59 +1,49 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import Pagination from "./pagination";
|
||||
|
||||
/**
|
||||
* Representa la definición de una columna para la tabla.
|
||||
*
|
||||
* @template T - Tipo genérico de los datos.
|
||||
*/
|
||||
export interface Header<T> {
|
||||
/** Clave del objeto a mostrar en esta columna. */
|
||||
key: keyof T;
|
||||
/** Etiqueta que se mostrará como encabezado. */
|
||||
label: string;
|
||||
render?: (value: T[keyof T], row: T) => React.ReactNode;
|
||||
width?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Propiedades del componente Table.
|
||||
*
|
||||
* @template T - Tipo genérico de los datos.
|
||||
*/
|
||||
interface TableProps<T> {
|
||||
/** Arreglo de encabezados de columna. */
|
||||
headers: Header<T>[];
|
||||
/** Datos que se mostrarán en la tabla. */
|
||||
data: T[];
|
||||
/** Número de filas por página (por defecto: 10). */
|
||||
rowsPerPage?: number;
|
||||
/** Callback que se ejecuta al hacer clic en una fila. */
|
||||
onRowClick?: (rowData: T) => void;
|
||||
rowKey?: (row: T) => string | number;
|
||||
className?: {
|
||||
table?: string;
|
||||
row?: string;
|
||||
cell?: string;
|
||||
header?: string;
|
||||
};
|
||||
controlledPage?: number;
|
||||
onPageChange?: (page: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Componente genérico de tabla con paginación y ordenamiento.
|
||||
*
|
||||
* @template T - Tipo de los objetos a renderizar en las filas.
|
||||
*
|
||||
* @param {TableProps<T>} props - Propiedades del componente.
|
||||
* @returns {JSX.Element} Tabla renderizada.
|
||||
*/
|
||||
export default function Table<T>({
|
||||
headers,
|
||||
data,
|
||||
rowsPerPage = 10,
|
||||
onRowClick,
|
||||
rowKey,
|
||||
className,
|
||||
controlledPage,
|
||||
onPageChange,
|
||||
}: TableProps<T>) {
|
||||
const [sortConfig, setSortConfig] = useState<{
|
||||
key: keyof T;
|
||||
direction: "asc" | "desc";
|
||||
} | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
/**
|
||||
* Maneja el ordenamiento al hacer clic en un encabezado.
|
||||
*
|
||||
* @param {keyof T} key - Clave del dato a ordenar.
|
||||
*/
|
||||
const [internalPage, setInternalPage] = useState(1);
|
||||
const currentPage = controlledPage ?? internalPage;
|
||||
const setPage = onPageChange ?? setInternalPage;
|
||||
|
||||
const handleSort = (key: keyof T) => {
|
||||
let direction: "asc" | "desc" = "asc";
|
||||
if (
|
||||
@@ -66,24 +56,17 @@ export default function Table<T>({
|
||||
setSortConfig({ key, direction });
|
||||
};
|
||||
|
||||
/**
|
||||
* Datos ordenados de acuerdo con `sortConfig`.
|
||||
*/
|
||||
const sortedData = useMemo(() => {
|
||||
if (!sortConfig) return data;
|
||||
|
||||
return [...data].sort((a, b) => {
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) {
|
||||
if (a[sortConfig.key]! < b[sortConfig.key]!)
|
||||
return sortConfig.direction === "asc" ? -1 : 1;
|
||||
}
|
||||
if (a[sortConfig.key] > b[sortConfig.key]) {
|
||||
if (a[sortConfig.key]! > b[sortConfig.key]!)
|
||||
return sortConfig.direction === "asc" ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}, [data, sortConfig]);
|
||||
|
||||
// Paginación
|
||||
const totalPages = Math.ceil(sortedData.length / rowsPerPage);
|
||||
const paginatedData = sortedData.slice(
|
||||
(currentPage - 1) * rowsPerPage,
|
||||
@@ -92,52 +75,88 @@ export default function Table<T>({
|
||||
|
||||
return (
|
||||
<div className="table-container">
|
||||
<table className="e-table">
|
||||
<thead style={{ userSelect: "none", cursor: "pointer" }}>
|
||||
<table className={`e-table ${className?.table || ""}`}>
|
||||
<colgroup>
|
||||
{headers.map((header, index) => (
|
||||
<col key={index} style={{ width: header.width }} />
|
||||
))}
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((header, index) => (
|
||||
<th
|
||||
key={index}
|
||||
onClick={() => handleSort(header.key)}
|
||||
className={
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-sort={
|
||||
sortConfig?.key === header.key
|
||||
? sortConfig.direction === "asc"
|
||||
? "ascending"
|
||||
: "descending"
|
||||
: "none"
|
||||
}
|
||||
style={{ width: header.width }}
|
||||
className={`sortable ${className?.header || ""} ${
|
||||
header.className || ""
|
||||
} ${
|
||||
sortConfig?.key === header.key
|
||||
? `sorted-${sortConfig.direction}`
|
||||
: ""
|
||||
}
|
||||
}`}
|
||||
>
|
||||
{header.label}
|
||||
{sortConfig?.key === header.key &&
|
||||
(sortConfig.direction === "asc" ? (
|
||||
<i className="bi bi-caret-up"></i>
|
||||
) : (
|
||||
<i className="bi bi-caret-down"></i>
|
||||
))}
|
||||
{sortConfig?.key === header.key && (
|
||||
<i
|
||||
className={`ms-1 bi bi-caret-${
|
||||
sortConfig.direction === "asc" ? "up" : "down"
|
||||
}`}
|
||||
></i>
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{paginatedData.map((row, rowIndex) => (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
onClick={() => onRowClick && onRowClick(row)}
|
||||
className={onRowClick ? "clickable-row" : ""}
|
||||
>
|
||||
{headers.map((header, cellIndex) => (
|
||||
<td key={cellIndex} data-titulo={header.label}>
|
||||
{row[header.key] as string}
|
||||
</td>
|
||||
))}
|
||||
{paginatedData.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={headers.length} className="text-center text-muted">
|
||||
No hay datos disponibles
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
) : (
|
||||
paginatedData.map((row, rowIndex) => (
|
||||
<tr
|
||||
key={rowKey ? rowKey(row) : rowIndex}
|
||||
onClick={() => onRowClick?.(row)}
|
||||
className={`${onRowClick ? "clickable-row" : ""} ${
|
||||
className?.row || ""
|
||||
}`}
|
||||
>
|
||||
{headers.map((header, cellIndex) => (
|
||||
<td
|
||||
key={cellIndex}
|
||||
className={`${className?.cell || ""} ${
|
||||
header.className || ""
|
||||
}`}
|
||||
style={{ width: header.width }}
|
||||
data-titulo={header.label}
|
||||
>
|
||||
{header.render
|
||||
? header.render(row[header.key], row)
|
||||
: (row[header.key] as React.ReactNode)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Paginación */}
|
||||
{totalPages > 1 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageChange={setPage}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
)}
|
||||
@@ -146,20 +165,11 @@ export default function Table<T>({
|
||||
}
|
||||
|
||||
interface TablaSkeletonProps {
|
||||
/** Número de renglones que se desean renderizar */
|
||||
rows?: number;
|
||||
/** Número de columnas por fila (default: 5) */
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Componente de tabla en estado de carga con placeholders.
|
||||
*
|
||||
* @param rows - Número de filas a mostrar (default: 5)
|
||||
* @param columns - Número de columnas por fila (default: 5)
|
||||
* @returns JSX.Element
|
||||
*/
|
||||
function TablaSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) {
|
||||
export function TablaSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) {
|
||||
return (
|
||||
<table className="e-table placeholder-glow">
|
||||
<thead>
|
||||
@@ -185,5 +195,3 @@ function TablaSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) {
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
export { TablaSkeleton };
|
||||
|
||||
Reference in New Issue
Block a user