diff --git a/src/app/input/page.tsx b/src/app/input/page.tsx index 9b98d51..77f1452 100644 --- a/src/app/input/page.tsx +++ b/src/app/input/page.tsx @@ -6,54 +6,117 @@ import Input from "@/components/input"; export default function Page() { return (
-

- Componente: Input +

+ 📦 Componente: Input

-

- Este componente encapsula un {``} con su respectiva - etiqueta y funcionalidad adicional para mostrar u ocultar contraseñas si - el tipo es password. Puedes personalizar las clases de cada - parte del input. +

+ El componente {``} 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.

-

🔹 Uso básico

- +
-

🔹 Campo de tipo contraseña

-

- Incluye un botón para mostrar u ocultar el contenido del campo. -

- - -

🔹 Input deshabilitado

- - -

🔹 Personalización de clases

- - -
+
+

🔹 Uso básico

+

Un campo de texto simple con etiqueta y placeholder.

-
+
+          {``}
+        
+ + +
+ +
+

🔐 Campo de tipo contraseña

+

+ Incluye un botón para mostrar u ocultar el contenido del campo. +

+ +
+          {``}
+        
+
+ +
+ +
+

🚫 Input deshabilitado

+ +
+          {``}
+        
+
+ +
+ +
+

🎨 Personalización de clases

+

+ Puedes personalizar el estilo de cada parte del input: contenedor, + etiqueta e input. +

+ +
+          {``}
+        
+
+ +
+ +
+

📏 Input en contenedor reducido

+

Se adapta a contenedores pequeños sin problemas.

+
+ +
+
+          {`
+ +
`}
+
+
); } diff --git a/src/app/table/page.tsx b/src/app/table/page.tsx index 0375257..5eec72e 100644 --- a/src/app/table/page.tsx +++ b/src/app/table/page.tsx @@ -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 (
-

Componente: Table

+

+ 📊 Componente: Table +

-

- El componente Table 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. +

+ El componente <Table /> permite mostrar datos + tabulares con soporte para ordenamiento, paginación, renderizado + personalizado, columnas clicables y estado de carga.

-

🔹 Uso básico

+
+ +

🔹 Uso básico

-

🔹 Con filas clicables

+
+ +

🖱️ Filas clicables

- Puedes pasar una función onRowClick para manejar clics en - filas. + Usa la propiedad onRowClick para manejar interacciones por + fila.

alert(`Seleccionaste a ${row.name}`)} /> -

🔹 Con paginación personalizada

+
+ +

📄 Paginación personalizada

- Puedes controlar cuántas filas se muestran por página usando{" "} - rowsPerPage. + Usa rowsPerPage para definir cuántas filas mostrar por + página.

-

🔹 Tabla en estado de carga

-

- El componente TablaSkeleton muestra una tabla con - placeholders mientras se cargan los datos reales. Puedes personalizar el - número de filas ycolumnas usando las - props rows y columns. -

+
- +

🎨 Render personalizado por celda

+

+ Puedes usar la propiedad render dentro de cada{" "} + header para personalizar cómo se muestra una columna. +

+
( + + ), + }, + ]} + data={data.map((d) => ({ ...d, acciones: null as null }))} + /> + +
+ +

⏳ Tabla en estado de carga

+

+ El componente TablaSkeleton muestra una tabla de ejemplo + mientras se cargan los datos. Puedes personalizar rows y{" "} + columns. +

+ ); } diff --git a/src/components/input.tsx b/src/components/input.tsx index bfa4ff8..87bb9d3 100644 --- a/src/components/input.tsx +++ b/src/components/input.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; export interface InputProps - extends Omit, 'className'> { + extends Omit, "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 (``) con una etiqueta opcional y + * Este componente renderiza un campo de entrada (``) 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} [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} [rest] - Atributos adicionales compatibles con ``. * * @example - * // Ejemplo de uso + * // Ejemplo de uso con contraseña: * + * + * @example + * // Ejemplo de input controlado: + * 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 ( -
-
- +
+ + {headers.map((header, index) => ( + + ))} + + {headers.map((header, index) => ( ))} - {paginatedData.map((row, rowIndex) => ( - onRowClick && onRowClick(row)} - className={onRowClick ? "clickable-row" : ""} - > - {headers.map((header, cellIndex) => ( - - ))} + {paginatedData.length === 0 ? ( + + - ))} + ) : ( + paginatedData.map((row, rowIndex) => ( + onRowClick?.(row)} + className={`${onRowClick ? "clickable-row" : ""} ${ + className?.row || "" + }`} + > + {headers.map((header, cellIndex) => ( + + ))} + + )) + )}
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" ? ( - - ) : ( - - ))} + {sortConfig?.key === header.key && ( + + )}
- {row[header.key] as string} -
+ No hay datos disponibles +
+ {header.render + ? header.render(row[header.key], row) + : (row[header.key] as React.ReactNode)} +
- {/* Paginación */} {totalPages > 1 && ( )} @@ -146,20 +165,11 @@ export default function Table({ } 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 ( @@ -185,5 +195,3 @@ function TablaSkeleton({ rows = 5, columns = 5 }: TablaSkeletonProps) {
); } - -export { TablaSkeleton };