next-recomponents
v2.0.104
Published
description nueva
Readme
next-recomponents
Componentes y hooks de React para aplicaciones Next.js: tablas, formularios, modales, popups, calendario, visor de documentos y utilidades para consumir APIs, fechas y Excel.
Contenido
- Instalación y configuración
- Componentes
- Hooks y utilidades
Instalación y configuración
npm install next-recomponentsDependencias que debe tener tu proyecto
La librería no las incluye; tu aplicación debe tenerlas instaladas:
| Paquete | Para qué se usa |
| ------------------ | ------------------------------------------------ |
| react, react-dom | Todo |
| next (13 o superior, App Router) | Container (next/link, usePathname) |
| axios | useResources |
| moment | useDates |
| react-datepicker | MyCalendar |
npm install axios moment react-datepickerTailwind CSS
Los componentes usan clases de Tailwind. Agrega la librería a content en tailwind.config para que Tailwind genere esas clases:
// tailwind.config.js
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/next-recomponents/dist/**/*.{js,mjs}",
],
};Estilos del calendario
MyCalendar necesita la hoja de estilos del paquete. Impórtala una vez, por ejemplo en app/layout.tsx:
import "next-recomponents/dist/index.css";Componentes de cliente
Los componentes usan estado y efectos, así que deben usarse desde un componente de cliente. Agrega "use client" al inicio del archivo que los importa:
"use client";
import { Button, Table } from "next-recomponents";Escala de colores compartida
Button, Alert, Modal y usePopup aceptan la misma propiedad color:
"primary" (por defecto en la mayoría) · "secondary" · "info" · "danger" · "warning" · "success" · "white"
Table / TableAdvanced
Table y TableAdvanced son el mismo componente. Según lo que reciba en data muestra:
- Un arreglo de filas → tabla con encabezados, orden, filtros, búsqueda, edición, selección, totales, paginación y modal por fila. Cada fila debe tener
id. - Un solo objeto
{ campo: valor }→ ficha vertical de dos columnas (campo | valor). - Un arreglo vacío u otro valor → no muestra nada.
import { Table } from "next-recomponents";
const usuarios = [
{ id: 1, nombre: "Ana López", correo: "[email protected]", saldo: 1250.5 },
{ id: 2, nombre: "Juan Pérez", correo: "[email protected]", saldo: 980 },
];
<Table data={usuarios} header="Usuarios" searchable exportName="usuarios" />;Comportamiento automático
- Columnas: se toman de las claves de todas las filas. Las que empiezan con
_no se muestran (útil para datos internos como_meta). - Ordenar: clic en el nombre de la columna alterna ascendente / descendente. Los valores vacíos siempre quedan al final.
- Menú de columna (ícono junto al encabezado): ordenar de A a Z / Z a A y filtrar por valores. Solo puede haber un filtro activo a la vez.
- Redimensionar: arrastra el borde derecho de un encabezado.
- Paginación: con más de 100 filas visibles aparece una barra de páginas de 100 en 100.
- Vista de tarjetas: si el contenedor de la tabla mide menos de 640 px de ancho (celular o panel angosto), cada fila se muestra como una tarjeta, con selector de orden y "seleccionar todo".
- Formato de valores:
- Fechas
AAAA-MM-DD(con o sin hora) se muestran comoDD/MM/AAAA. - Números con decimales se muestran con 2 decimales y separador de miles (
1,250.50), precedidos decurrentCoinsi se indica. - Elementos de React se muestran tal cual; objetos y arreglos, como JSON.
- Fechas
Props de la tabla (arreglo de filas)
| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| data | object[] | — | Filas. Todas deben tener id. |
| header | ReactNode | — | Título sobre la tabla. |
| searchable | boolean | false | Muestra el buscador. Busca por palabras: una fila coincide si cada palabra aparece en alguno de sus campos ("juan perez" encuentra nombre y apellido en columnas distintas). Resalta las celdas que coinciden. |
| exportName | string | — | Muestra el botón Exportar y descarga exportName.xlsx con todas las filas (sin columnas ocultas ni campos que empiezan con _). |
| hideColumns | string[] | [] | Columnas que no se muestran. |
| editableFields | string[] | — | Columnas editables en línea. El cambio se aplica al salir del campo. Si el valor original era número y lo escrito es un número válido, se guarda como número. |
| onSave | (rows) => void \| Promise | — | Muestra el botón Guardar cambios (con el número de filas editadas). Recibe todas las filas con sus ediciones. Mientras la promesa no se resuelve, la tabla muestra "Guardando…". |
| onSaveLabel | string | "Guardar cambios" | Texto del botón de guardar. |
| onSelect | (rows) => void \| Promise | — | Agrega una columna de casillas y el botón Guardar selección; recibe las filas marcadas. No se puede usar junto con onSave. |
| onSelectLabel | string | "Guardar selección" | Texto del botón de selección. |
| buttons | Record<columna, ReactElement> | — | Reemplaza el contenido de una columna por un control (botón, input, select, componente propio). Ver Controles en celdas. |
| modal | ReactElement | — | Agrega una columna con botón de edición que abre este contenido a pantalla completa. Ver Modal por fila. |
| modalButton | ReactElement | ícono de lápiz | Botón propio para abrir el modal. Su onClick se sigue ejecutando. |
| onCloseModal | (row) => boolean \| Promise<boolean> | — | Se llama al intentar cerrar el modal; devolver false lo mantiene abierto. |
| footer | Record<columna, "sum" \| "avg" \| "count"> | — | Fila de totales fija al pie. Se calcula sobre las filas visibles (con filtros y búsqueda). Los vacíos cuentan como 0 y los valores no numéricos se ignoran; count cuenta los valores numéricos. |
| currentCoin | string | "" | Símbolo antepuesto a los números con decimales ("$" → $ 1,250.50). |
| sortBy | { [columna]: "ASC" \| "DESC" } | — | Orden inicial. Solo se usa la primera clave. |
| height | number | 560 | Altura máxima (px) del área con scroll. Encabezados y totales quedan fijos. |
| autoHeight | boolean | false | Sin altura máxima: la tabla crece con sus filas. |
| width | number \| string | "100%" | Ancho de la tabla. |
| colSize | Record<columna, number> | — | Ancho fijo (px) por columna. Las demás se reparten el espacio restante. |
| density | "compact" \| "standard" \| "comfortable" | "standard" | Alto de fila: 30, 53 u 80 px. |
| rowHeight | number | — | Alto de fila en px (tiene prioridad sobre density). |
| wrapText | boolean | false | Permite que el texto ocupe varias líneas en lugar de cortarse con "…". |
| fontSize | string | "12px" | Tamaño de letra de las celdas. |
| className | string | — | Clases extra para el contenedor. |
| disabled | boolean | false | Desactiva ordenar, filtrar y redimensionar desde los encabezados. |
Edición y guardado
<Table
data={productos}
editableFields={["precio", "existencias"]}
onSave={async (rows) => {
await api.guardar(rows); // todas las filas, con las ediciones aplicadas
}}
/>Las filas editadas siguen visibles aunque un filtro de columna las excluya, para que no "desaparezcan" mientras se trabaja.
Selección
<Table
data={pedidos}
onSelect={async (seleccionadas) => {
await api.autorizar(seleccionadas.map((p) => p.id));
}}
onSelectLabel="Autorizar"
/>Totales
<Table
data={ventas}
currentCoin="$"
footer={{ importe: "sum", descuento: "avg", folio: "count" }}
/>Controles en celdas (buttons)
Cada clave de buttons es el nombre de una columna; el valor es el control que se pinta en cada celda de esa columna. La tabla clona el control por fila y le pasa:
valueychildrencon el valor de la celda (salvo a<input>,<select>y<textarea>, que conservan sus propios hijos);rowcon la fila completa, si el control es un componente propio (no a etiquetas HTML como<button>);onClickyonChangeenvueltos (ver abajo).
Lo que reciben onClick y onChange. Reciben el evento original con la fila agregada. Funcionan las dos formas:
import { Button, Table, TableButtonProps } from "next-recomponents";
<Table
data={facturas}
buttons={{
// Forma 1: el evento con la fila (e.row, e.currentTarget, e.preventDefault()).
folio: (
<Button onClick={(e: TableButtonProps) => abrirFactura(e.row.id)} />
),
// Forma 2: desestructurar { e, row }.
estado: (
<Button
color="success"
onClick={async ({ row }: TableButtonProps) => {
await api.pagar(row.id);
return { estado: "Pagada" }; // se aplica a la fila
}}
/>
),
// Input editable: onChange también recibe e.row; e.target.value es el valor escrito.
cantidad: (
<input
type="number"
onChange={(e: any) => {
const cantidad = Number(e.target.value);
return { total: cantidad * e.row.precio }; // actualiza otra columna de la fila
}}
/>
),
}}
/>;Lo que devuelve el handler.
- En
onClick: si devuelve (o resuelve) un objeto, sus campos se aplican a la fila:return { estado: "Pagada" }. Cualquier otro valor se ignora. - En
onChange: la columna del control se actualiza cone.target.value. Además, si devuelve un objeto, se aplican los campos que ya existen en la fila. - Si el control es
Buttony el handler devuelve una promesa, el botón muestra un indicador de carga hasta que termine.
TableEventProps es un alias de TableButtonProps.
Modal por fila
modal recibe un elemento; la tabla lo clona con dos props:
row: la fila abierta.hide(): cierra el modal sin pasar poronCloseModal.
function DetalleCliente({ row, hide }: { row?: any; hide?: () => void }) {
return (
<div>
<h2>{row?.nombre}</h2>
<Button onClick={hide}>Listo</Button>
</div>
);
}
<Table
data={clientes}
modal={<DetalleCliente />}
onCloseModal={async (row) => confirm(`¿Cerrar ${row.nombre}?`)}
/>;- Los botones Anterior / Siguiente recorren las filas visibles (con filtros, orden y búsqueda), no solo la página actual.
- Al cambiar de fila, el contenido del modal se monta de nuevo: no arrastra el estado de la fila anterior.
- El modal sigue la fila por su
id, aunque cambie de posición por un orden o una edición. Si un filtro la oculta, el modal lo indica. - En la vista de tarjetas, tocar una tarjeta abre su modal.
Cuándo se reinician los datos
La tabla copia data a su estado interno para aplicar ediciones. Ese estado (ediciones, cambios de buttons) se reemplaza solo cuando el contenido de data cambia. Pasar un arreglo nuevo con el mismo contenido en cada render, como data={rows.map(...)}, no borra las ediciones.
Ficha vertical (un solo objeto)
<Table
data={{ nombre: "Ana López", correo: "[email protected]", activo: true, saldo: 1250.5 }}
header="Detalle del cliente"
editableFields={["correo"]}
onSave={async (valores) => api.actualizar(valores)}
/>Acepta header, hideColumns (y oculta los campos que empiezan con _), editableFields, buttons, searchable (busca por campo o valor), exportName (exporta pares Campo/Valor), onSave, onSaveLabel, currentCoin, fontSize y className.
onSaverecibe el objeto con sus ediciones (no un arreglo).- Los booleanos se muestran como "Sí" / "No".
- Los objetos y arreglos anidados se muestran con
Pre.
Form y useFormValues
Formulario que conecta automáticamente sus campos con un estado de valores, por su name. Los campos pueden estar a cualquier profundidad (dentro de div, grillas, etc.).
import { Form, Input, Select, TextArea, Button, regularExpresions } from "next-recomponents";
<Form
onSubmit={async (e) => {
await api.crearUsuario(e.values); // { nombre, correo, rol, acepto }
}}
>
<Input name="nombre" label="Nombre" required />
<Input name="correo" label="Correo" regex={regularExpresions.email} />
<Select name="rol" label="Rol">
<option value="admin">Administrador</option>
<option value="user">Usuario</option>
</Select>
<Input name="acepto" type="checkbox" label="Acepto los términos" />
<Button type="submit">Guardar</Button>
</Form>;Props
| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| onSubmit | (e) => void \| Promise | — | Recibe el evento con e.values (los valores del formulario). Mientras la promesa no se resuelve se muestra loader a pantalla completa. |
| state | [valores, setValores] | estado interno | Estado externo, por ejemplo de useFormValues o useState. Permite leer o cambiar los valores desde fuera. |
| variant | "card" \| "plain" | "card" | card: tarjeta con franja de color y etiqueta. plain: sin estilos (para filtros, barras, menús). |
| badge | ReactNode \| false | "Formulario" | Texto de la etiqueta en la variante card; false la oculta. |
| invalidMessage | string | "Existen valores inválidos en el formulario" | Mensaje (alert) si se intenta enviar con campos inválidos. |
| loader | ReactNode | "Loading..." | Contenido mostrado mientras se ejecuta onSubmit. |
| className y demás | — | — | Se pasan al <form>. |
Cómo se conectan los campos
- Texto, select, calendario, textarea:
valueyonChange→ guardae.target.value. type="checkbox": guardatrue/false.type="radio": varios radios con el mismoname; guarda elvaluedel elegido.- Los campos sin
nameno se conectan. - Si el
onChangepropio de un campo existe, también se ejecuta.
Validación
Un Input con regex que no se cumple muestra su mensaje de error. Mientras haya alguno, el formulario no se envía y muestra invalidMessage.
useFormValues
Estado de formulario que fusiona cada actualización con la anterior. Úsalo para valores iniciales o para leer y modificar los valores desde fuera del formulario:
import { Form, Input, useFormValues } from "next-recomponents";
const [valores, setValores] = useFormValues<{ nombre: string; edad: number }>({
nombre: "Ana",
});
<Form state={[valores, setValores]} onSubmit={(e) => console.log(e.values)}>
<Input name="nombre" label="Nombre" />
<Input name="edad" type="number" label="Edad" />
</Form>;
setValores({ edad: 30 }); // conserva `nombre`Input
Campo de texto con etiqueta sobre el borde, validación por expresión regular y botón para mostrar contraseñas. Con type="checkbox" o type="radio" se muestra como casilla con la etiqueta al lado.
import { Input, regularExpresions } from "next-recomponents";
<Input
label="Correo"
name="correo"
type="email"
regex={regularExpresions.email}
invalidMessage="Escribe un correo válido"
required
/>
<Input label="Contraseña" type="password" regex={regularExpresions.password} />
<Input label="Recordarme" type="checkbox" name="recordar" />
<Input label="Mensual" type="radio" name="plan" value="mensual" />
<Input label="Anual" type="radio" name="plan" value="anual" />| Prop | Tipo | Descripción |
| --- | --- | --- |
| label | ReactNode | Obligatoria. Etiqueta del campo. Con required agrega un *. |
| regex | RegExp | El valor debe cumplirla. Con valor válido el borde se pone verde; con inválido, rojo y aparece el mensaje. Un campo vacío no se valida. |
| invalidMessage | ReactNode | Mensaje si no cumple regex. Por defecto "Valor no válido". |
| icon | ReactNode | Ícono decorativo a la derecha (se ignora en contraseñas). |
| Atributos de <input> | — | name, type, value, defaultValue, onChange, placeholder, disabled, required, etc. |
Funciona controlado (value + onChange) o no controlado (defaultValue).
TextArea
Área de texto con etiqueta y contador opcional de caracteres.
import { TextArea } from "next-recomponents";
<TextArea label="Comentarios" name="comentarios" maxLength={500} rows={6} />;| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| label | ReactNode | — | Etiqueta del campo. |
| maxLength | number | — | Límite de caracteres. Muestra un contador que cambia a ámbar al 90 % y a rojo al llegar al límite. |
| rows | number | 4 | Alto inicial en líneas (se puede estirar). |
| Atributos de <textarea> | — | — | name, value, defaultValue, onChange, disabled, required, etc. |
Select
Selector con búsqueda: se escribe para filtrar las opciones (sin importar acentos ni mayúsculas) y se navega con el teclado (↑ ↓ Enter Esc).
import { Select } from "next-recomponents";
<Select
label="Estado"
name="estado"
value={estado}
onChange={(e) => setEstado(e.target.value)}
>
<option value="AGS">Aguascalientes</option>
<option value="JAL">Jalisco</option>
<option value="NL">Nuevo León</option>
</Select>;| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| children | <option> | — | Opciones. Si una opción no tiene value, se usa su texto. Pueden venir de un .map(...). |
| label | string | — | Etiqueta del campo. |
| placeholder | string | "Selecciona..." | Texto con el campo vacío. |
| value | string | — | Valor (el value de la opción) seleccionado. |
| onChange | (e) => void | — | Recibe { target: { value } } con el value de la opción elegida (o "" al borrar). |
| strictMode | boolean | true | Solo acepta opciones de la lista: si al salir del campo el texto no coincide con una, se borra y el campo queda en rojo. |
| Atributos de <input> | — | — | name, disabled, required, onBlur, onFocus, etc. |
- Mientras no haya opciones, muestra "Cargando…" (útil cuando las opciones llegan de una API).
- La lista se abre hacia arriba si no cabe abajo y se muestra por encima de modales.
MyCalendar
Selector de fecha en el que solo se pueden elegir las fechas indicadas. Muestra la fecha en formato legible (Mié, 7 oct 2026) y entrega AAAA-MM-DD.
Requiere importar next-recomponents/dist/index.css (ver Instalación).
import { MyCalendar } from "next-recomponents";
<MyCalendar
label="Fecha de la cita"
name="fecha"
enabledDates={["2026-10-20", "2026-10-21", "2026-10-23"]}
value={fecha}
onChange={(e) => setFecha(e.target.value)} // "2026-10-21"
/>;| Prop | Tipo | Descripción |
| --- | --- | --- |
| label | ReactNode | Obligatoria. Etiqueta del campo. |
| enabledDates | (string \| Date)[] | Obligatoria. Únicas fechas elegibles ("AAAA-MM-DD" o Date). Si la lista está vacía, el componente no se muestra. |
| value / defaultValue | string \| Date | Fecha seleccionada. |
| onChange | (e) => void | Recibe { target: { value: "AAAA-MM-DD" } }. |
| name | string | Nombre del campo oculto que envía AAAA-MM-DD en el formulario. |
| placeholder | string | Por defecto "Selecciona una fecha". |
| disabled, required | boolean | Como en un input. |
Al abrir, el calendario muestra el mes de la fecha elegida o, si no hay, de la próxima fecha disponible.
Button
Botón con colores predefinidos, ícono y estado de carga automático.
import { Button } from "next-recomponents";
<Button onClick={() => console.log("clic")}>Aceptar</Button>
<Button color="danger" icon={<TrashIcon />} onClick={async () => await api.borrar(id)}>
Eliminar
</Button>
<Button color="white" full>Cancelar</Button>| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| color | ver colores | "primary" | Color del botón. |
| icon | ReactNode | — | Ícono antes del texto. Sin children, el botón queda cuadrado (solo ícono). |
| size | "small" \| "full" | "small" | full ocupa todo el ancho. |
| full | boolean | false | Atajo de size="full". |
| loading | boolean | false | Fuerza el estado de carga. |
| onClick | (e) => unknown | — | Si devuelve una promesa, el botón se deshabilita y muestra un indicador hasta que se resuelva (mínimo 400 ms para que se alcance a ver). |
| ref | Ref<HTMLButtonElement> | — | Soportado. |
| Atributos de <button> | — | — | type, disabled, className, etc. |
Dentro de buttons de Table, onClick recibe además la fila (ver Controles en celdas).
Modal
Ventana modal que se abre al hacer clic en un botón.
import { Modal, Button } from "next-recomponents";
<Modal
button={<Button>Nuevo cliente</Button>}
title="Nuevo cliente"
size="md"
onClose={async () => confirm("¿Descartar los cambios?")}
>
<FormularioCliente />
</Modal>;
// Dentro del contenido, `hide` cierra el modal sin pasar por onClose:
function FormularioCliente({ hide }: { hide?: () => void }) {
return <Button onClick={async () => { await guardar(); hide?.(); }}>Guardar</Button>;
}| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| button | ReactElement | — | Obligatoria. Elemento que abre el modal. Su propio onClick también se ejecuta. |
| children | ReactNode | — | Contenido. Si es un componente propio, recibe hide(). A etiquetas HTML y fragmentos no se les pasa. |
| title | ReactNode | — | Título en la barra superior. Sin título, solo aparece la X. |
| color | ver colores | "primary" | Color de la barra del título. white la deja sin color. |
| size | "sm" \| "md" \| "lg" \| "xl" \| "full" | "lg" | Ancho máximo. full ocupa toda la ventana. |
| onClose | () => boolean \| void \| Promise | — | Se llama al intentar cerrar (X, clic fuera o Escape). Devolver false lo mantiene abierto. |
El modal bloquea el scroll de la página, mantiene el foco del teclado dentro y lo devuelve al botón al cerrar.
Alert
Mensaje destacado en la página.
import { Alert } from "next-recomponents";
<Alert color="success" title="Guardado">Los cambios se guardaron correctamente.</Alert>
<Alert color="danger" onClose={() => setError(null)}>{error}</Alert>
<Alert color="info" icon={false}>Sin ícono.</Alert>| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| children | ReactNode | — | Mensaje. |
| color | ver colores | "primary" | Color e ícono. |
| title | ReactNode | — | Título en negrita. |
| icon | ReactNode \| false | según el color | Ícono propio; false lo oculta. |
| onClose | () => void | — | Muestra una X para cerrar. El componente no se oculta solo: hazlo desde tu estado. |
| Atributos de <div> | — | — | className, role, etc. Con danger y warning el role por defecto es alert (los lectores de pantalla lo anuncian); con el resto, status. |
Container
Estructura de página: menú lateral, barra superior con migas de pan, panel izquierdo opcional y pie plegable. Requiere Next.js con App Router.
"use client";
import { Container } from "next-recomponents";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<Container
appName="Sistema QC"
menuList={[
{ location: "/", name: "Inicio", icon: <HomeIcon /> },
{ location: "/clientes", name: "Clientes", icon: <UsersIcon /> },
]}
navItems={[{ location: "/clientes", name: "Clientes" }]}
leftPanel={<Filtros />}
footPanel={<Resumen />}
expandedMenu
>
{children}
</Container>
);
}| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| children | ReactNode | — | Contenido principal. |
| appName | ReactNode | — | Nombre de la aplicación (marca del menú y título en celular). Si es texto, su inicial se usa como logo. |
| menuList | { location, name, icon? }[] | [] | Opciones del menú lateral. La opción de la ruta actual (o de una subruta) queda resaltada. Sin ícono, se muestra la inicial del nombre. |
| navItems | { location, name, icon? }[] | [] | Migas de pan. El último elemento es la página actual y define el título de la pestaña del navegador y, si su ícono es un SVG, el favicon. |
| leftPanel | ReactNode | — | Panel a la izquierda del contenido (arriba, en celular). |
| footPanel | ReactNode | — | Pie plegable con "Mostrar / Ocultar detalles". |
| expandedMenu | boolean | false | En escritorio, el menú inicia expandido (con nombres). Si no, muestra solo íconos y se expande al pasar el mouse. |
| expandedFooter | boolean | false | El pie inicia abierto. |
En celular, el menú se abre como cajón lateral con el botón ☰ y se cierra al navegar, con Escape o tocando fuera.
TabContainer y TabItem
Pestañas.
import { TabContainer, TabItem } from "next-recomponents";
<TabContainer currentIndex={0} labelMaxWidth={180}>
<TabItem label="General" icon={<InfoIcon />}>
<DatosGenerales />
</TabItem>
<TabItem label="Documentos">
<Documentos />
</TabItem>
<TabItem label="Historial" disabled>
<Historial />
</TabItem>
</TabContainer>;TabContainer
| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| children | TabItem[] | — | Pestañas. |
| currentIndex | number | 0 | Pestaña activa. Si cambia desde fuera, la pestaña activa cambia. |
| labelMaxWidth | number | — | Ancho máximo (px) de cada etiqueta; el texto largo se corta con "…". |
TabItem
| Prop | Tipo | Descripción |
| --- | --- | --- |
| label | string | Obligatoria. Texto de la pestaña. Usa etiquetas distintas en cada pestaña. |
| icon | ReactNode | Ícono junto al texto. |
| disabled | boolean | No se puede abrir con clic. |
| children | ReactNode | Contenido. |
Con el foco en una pestaña, las flechas ← → cambian de pestaña.
Pre
Visor de JSON con colores, números de línea y nodos plegables. Acepta cualquier valor y no se rompe con referencias circulares.
import { Pre } from "next-recomponents";
<Pre data={respuestaApi} title="Respuesta" defaultExpandDepth={1} maxHeight={400} />;| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| data | any | — | Valor a mostrar. |
| defaultExpandDepth | number | todo abierto | Niveles abiertos al inicio; 0 = todo plegado. |
| maxHeight | number \| string | crece con el contenido | Alto máximo con scroll (400, "60vh"). |
| title | ReactNode | — | Texto en la barra superior. |
DocumentViewer
Miniatura de un archivo (imagen, PDF, Word/Excel/PowerPoint u otro) con acciones de descargar, abrir y eliminar. Al abrirlo se muestra a pantalla completa.
import { DocumentViewer } from "next-recomponents";
const archivos = [
{ url: "https://cdn.ejemplo.com/foto.jpg", name: "foto.jpg", contentType: "image/jpeg" },
{ url: "https://cdn.ejemplo.com/contrato.pdf", name: "contrato.pdf", contentType: "application/pdf" },
];
<div className="flex gap-3">
{archivos.map((a) => (
<DocumentViewer
key={a.url}
item={a}
width="200px"
height="200px"
groupId={1}
onDelete={(item) => quitar(item.url)}
/>
))}
</div>;| Prop | Tipo | Por defecto | Descripción |
| --- | --- | --- | --- |
| item | { url, name, contentType } | — | Obligatoria. Archivo. contentType es el tipo MIME y decide cómo se muestra. |
| width / height | string | "100%" | Tamaño de la miniatura. |
| onDelete | (item) => void | — | Muestra el botón de eliminar. |
| groupId | number | 1 | Los visores con el mismo groupId se recorren juntos (← →) en pantalla completa, en el orden en que aparecen en la página. |
Cómo se muestra cada tipo:
- Imágenes (
image/*) y PDF: directamente. - Office (Word, Excel, PowerPoint): con el visor de Google Docs, así que la
urldebe ser pública. - Otros: ícono de archivo con su extensión ("Este tipo de archivo no tiene vista previa"); se pueden descargar.
Table3 (heredada)
Tabla anterior basada en <table>. Se mantiene por compatibilidad; para código nuevo usa Table.
import { Table3 } from "next-recomponents";
<Table3
data={filas} // cada fila con id o _id
header="Inventario"
selectItems
maxItems={20} // filas por página
footer={{ cantidad: "sum" }}
symbols={{ importe: "$" }}
exportName="inventario"
onSave={(rows) => console.log(rows)}
/>;Props: data (con id o _id), selectItems, maxItems, onSave, exportName, colSizes, modal, footer (sum / count / avg), header, onChange, symbols (prefijo por columna en totales), sortBy ({ columna: "asc" | "desc" }), más los atributos de <table>.
usePopup
Reemplazo de alert, confirm y prompt del navegador, con promesas. También abre contenido propio en un modal.
Importante: renderiza PopupComponent en el componente que usa el hook.
import { usePopup, Button } from "next-recomponents";
function Acciones() {
const popup = usePopup();
const borrar = async () => {
if (!(await popup.confirm("¿Eliminar el registro?", "danger"))) return;
await api.borrar();
await popup.alert("Registro eliminado", "success");
};
const renombrar = async () => {
const nombre = await popup.prompt("Nuevo nombre"); // null si se cancela
if (nombre) await api.renombrar(nombre);
};
return (
<>
<Button color="danger" onClick={borrar}>Eliminar</Button>
<Button onClick={renombrar}>Renombrar</Button>
{popup.PopupComponent}
</>
);
}| Función / valor | Devuelve | Descripción |
| --- | --- | --- |
| alert(mensaje, color?) | Promise<void> | Mensaje con botón Aceptar. |
| confirm(mensaje, color?) | Promise<boolean> | true con Aceptar; false con Cancelar o Escape. |
| prompt(mensaje, color?) | Promise<string \| null> | Texto escrito con Aceptar (Enter también acepta); null con Cancelar. |
| modal(contenido, color?, icons?, full?, onClose?) | Promise<void> | Abre contenido propio en un modal. full ocupa toda la ventana. onClose debe resolver true para permitir el cierre. Con icons = true se muestra el ícono del color en lugar de la X (se cierra con Escape). |
| close(confirmado, valor?) | — | Cierra el popup abierto desde código. |
| updateMessage(contenido) | — | Cambia el contenido del popup abierto (por ejemplo, si el contenido del modal depende de un estado). |
| isOpen | boolean | Si hay un popup abierto. |
| PopupComponent | ReactNode | Debe renderizarse para que el popup se vea. |
- El popup aparece por encima de cualquier
Modalabierto. - Si se abre un popup mientras otro está abierto, el anterior se resuelve como cancelado.
- Hacer clic en el fondo no lo cierra.
useResources
Hook para consumir una API REST con un recurso por endpoint: listar con paginación, buscar por id, crear, actualizar y eliminar. Mantiene en estado los datos, la página actual y el estado de cada petición.
import { useResources } from "next-recomponents";
type Cliente = { id: number; nombre: string; correo: string };
const recursos = useResources({
baseURI: "https://api.ejemplo.com",
endpoints: {
clientes: { typeof: {} as Cliente, defaultParams: { limit: 20, page: 1 } },
perfil: { typeof: {} as { id: number; nombre: string }, id: "me" },
},
onError: ({ errorMessage }) => alert(errorMessage), // solo errores 403
});
const { clientes } = recursos;
clientes.data; // filas cargadas
clientes.state; // "loading" | "success" | "error"
await clientes.show({ page: 2, limit: 20, activo: true });
await clientes.find(15); // GET /clientes/15 → clientes.selectedItem
await clientes.create({ nombre: "Ana" }); // POST /clientes
await clientes.update(15, { correo: "[email protected]" }); // PATCH /clientes/15
await clientes.remove(15); // DELETE /clientes/15Configuración
| Opción | Descripción |
| --- | --- |
| baseURI | URL base. Cada endpoint se consulta en baseURI/<nombre>. Sin baseURI no se hacen peticiones (respuestas vacías, útil para pruebas). |
| endpoints | Objeto { nombre: { typeof, id?, defaultParams? } }. typeof solo da el tipo de las filas. |
| endpoints.x.id | Al montar se llama find(id, defaultParams). |
| endpoints.x.defaultParams | Al montar (si no hay id) se llama show(defaultParams). |
| onError | Se llama con { error, errorMessage } cuando la API responde 403. |
Autenticación. Todas las peticiones envían Authorization: <token>, con el valor guardado en localStorage bajo la clave "token". Se lee al montar el hook.
Por cada endpoint
| Miembro | Descripción |
| --- | --- |
| show({ page?, limit?, merge?, ...filtros }) | GET /nombre con page (1), limit (10) y los filtros como query. Espera { data, totalItems, totalPages, currentPage }. Con merge (por defecto true), las páginas posteriores a la 1 se agregan a los datos ya cargados. |
| find(id, query?) | GET /nombre/id. Guarda el resultado en selectedItem y lo reemplaza (o agrega) en data. |
| create(datos \| datos[]) | POST /nombre. Si algún valor es un File, se envía como multipart/form-data. El resultado se agrega a data. |
| update(id, datos) | PATCH /nombre/id. Fusiona la respuesta con la fila en data. |
| remove(id) | DELETE /nombre/id. Quita la fila de data. |
| getAllPages(limit?) | Recorre todas las páginas con show y devuelve todas las filas. |
| data, selectedItem | Datos cargados y último elemento consultado o creado. |
| state, errorMessage | Estado de la última petición y mensaje de error. |
| currentPage, totalPages, params | Paginación y filtros de la última consulta. |
| loaded, setLoaded() | Si el recurso ya se cargó. |
Si una petición falla, la función devuelve el error (no lo lanza) y deja state = "error".
useDates
Utilidades para fechas de calendario (AAAA-MM-DD) sin errores de zona horaria: "2026-10-07T00:00:00Z" es el 7 de octubre, aunque en México ese instante aún sea el día 6.
import { useDates } from "next-recomponents";
const { now, dateAddDays, dateSubDays, isValid, addMinutes } = useDates();
now(); // "2026-10-14" (hoy)
now("07/10/2026"); // "2026-10-07"
dateAddDays(30, "2026-10-07"); // "2026-11-06"
dateSubDays(1); // ayer
isValid("2026-10-20"); // true si el 20 de octubre aún no termina
isValid("2026-10-20", "2026-10-21"); // false
addMinutes("23:50", 20); // "00:10"| Función | Descripción |
| --- | --- |
| now(fecha?) | Hoy, o la fecha recibida normalizada, como "AAAA-MM-DD". |
| dateAddDays(días, fecha?) | Suma días (desde hoy si no se indica fecha). |
| dateSubDays(días, fecha?) | Resta días. |
| isValid(fecha, referencia?) | true si el final de ese día (23:59:59) es posterior a referencia (por defecto, ahora). Útil para vigencias. |
| addMinutes("HH:mm", minutos) | Suma minutos a una hora y da la vuelta al pasar la medianoche. |
Formatos aceptados: "AAAA-MM-DD" (con o sin hora, que se ignora), "DD/MM/AAAA", "D-M-AAAA", Date y Moment. Una fecha no válida devuelve "" (o false en isValid).
useExcel
Importar y exportar archivos de Excel.
import { useExcel } from "next-recomponents";
const excel = useExcel();
// Exportar
await excel.export(clientes, "clientes.xlsx");
await excel.exportAllPages({ Clientes: clientes, Pedidos: pedidos }, "reporte.xlsx");
// Importar desde un <input type="file">
const filas = await excel.import(file); // filas de la primera hoja
const hojas = await excel.importAllPages(file); // { NombreHoja: filas[] }| Función | Descripción |
| --- | --- |
| export(filas, nombre?) | Descarga un .xlsx de una hoja. Los encabezados son las claves de la primera fila. Nombre por defecto: "archivo.xlsx". |
| exportAllPages({ hoja: filas }, nombre?) | Descarga un .xlsx con una hoja por clave. |
| import(file) | Lee la primera hoja; cada fila es un objeto con los encabezados como claves. |
| importAllPages(file) | Lee todas las hojas. |
regularExpresions
Expresiones regulares listas para usar con Input (regex={regularExpresions.email}). Validan el formato, no la existencia (un RFC con formato correcto puede no existir).
| Clave | Valida | Ejemplo válido |
| --- | --- | --- |
| email | Correo | [email protected] |
| phone | 10 dígitos | 4611234567 |
| phoneIntl | Teléfono internacional (E.164) | +524611234567 |
| url | URL http(s) | https://sitio.com/ruta?x=1 |
| number | Solo dígitos | 007 |
| integer | Entero con signo | -15 |
| decimal | Decimal con punto | -0.5 |
| currency | Importe, hasta 2 decimales, comas de miles opcionales | 1,234.50 |
| percent | 0 a 100, hasta 2 decimales | 99.9 |
| alpha | Letras (con acentos y ñ) y espacios | José Peña |
| alphanumeric | Letras, números y espacios | Calle 5 Sur |
| name | Nombre de persona (2 a 60) | María-José O'Neil |
| username | 3 a 20, empieza con letra | ana.lopez_1 |
| slug | Slug para URL | mi-articulo-2026 |
| password | 8+ con mayúscula, minúscula y número | Clave2026 |
| strongPassword | 10+ y además un símbolo | Clave#2026x |
| date | AAAA-MM-DD | 2026-10-07 |
| dateDMY | DD/MM/AAAA | 07/10/2026 |
| time | HH:MM 24 h | 23:59 |
| zipMX | Código postal (5 dígitos) | 38000 |
| rfc | RFC persona física o moral | GODE561231GR8 |
| curp | CURP | GODE561231HDFRRN09 |
| clabe | CLABE (18 dígitos) | 012180001234567891 |
| rut | RUT chileno | 12.345.678-5 |
| creditCard | Tarjeta (13 a 19 dígitos) | 4111 1111 1111 1111 |
| hexColor | Color hexadecimal | #4F8CFF |
| ipv4 | Dirección IPv4 | 192.168.0.1 |
| uuid | UUID | 123e4567-e89b-12d3-a456-426614174000 |
Ninguna lleva la bandera g, así que se pueden reutilizar con .test() sin resultados alternados.
