@enlolab/forms
v0.1.2
Published
Sistema de formularios dinámicos para React + Zod + React Hook Form
Downloads
37
Maintainers
Readme
1
@enlolab/forms
Sistema de formularios dinámicos para React con Zod, React Hook Form y componentes UI modernos.
✨ Características
- 🎯 Formularios dinámicos basados en esquemas
- 🔒 Validación robusta con Zod
- 🎨 Componentes UI integrados con @enlolab/ui
- 📱 Responsive y accesible por defecto
- 🚀 TypeScript nativo con tipos completos
- 🎭 Múltiples tipos de campos (text, select, radio, checkbox, etc.)
- 🔄 Formularios multi-paso con navegación
- 🎨 Temas personalizables con CSS variables
- ♿ Accesibilidad integrada
📦 Instalación
npm install @enlolab/formsDependencias requeridas
npm install react react-dom
npm install react-hook-form @hookform/resolvers
npm install zod
npm install @enlolab/ui
npm install tailwindcss@^4.0.0🎨 Configuración de estilos
1. Configurar Tailwind CSS v4
Crea o actualiza tu archivo src/styles/globals.css:
@import "tailwindcss";
/* Importar estilos de @enlolab/ui
@source "../../../node_modules/@enlolab/ui
/* Importar estilos de @enlolab/forms */
@source "../../../node_modules/@enlolab/forms/dist";
/* Variables CSS personalizadas (opcional) */
:root {
--primary: oklch(0.723 0.219 149.579);
--primary-foreground: oklch(0.982 0.018 155.826);
--ring: oklch(0.723 0.219 149.579);
}
.dark {
--primary: oklch(0.696 0.17 162.48);
--primary-foreground: oklch(0.393 0.095 152.535);
--ring: oklch(0.527 0.154 150.069);
}2. Configurar Astro (si usas Astro)
/* En src/styles/global.css */
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:where(.dark, .dark *));
/* Importar componentes de @enlolab/ui
@source "../../../node_modules/@enlolab/ui
/* Importar componentes de @enlolab/forms */
@source "../../../node_modules/@enlolab/forms/dist";
/* Variables CSS específicas de la aplicación */
:root {
--primary: oklch(0.723 0.219 149.579);
--primary-foreground: oklch(0.982 0.018 155.826);
--ring: oklch(0.723 0.219 149.579);
}
.dark {
--primary: oklch(0.696 0.17 162.48);
--primary-foreground: oklch(0.393 0.095 152.535);
--ring: oklch(0.527 0.154 150.069);
}3. Configurar Next.js
/* En app/globals.css o styles/globals.css */
@import "tailwindcss";
/* Importar estilos de @enlolab/ui
@source "../../node_modules/@enlolab/ui
/* Importar estilos de @enlolab/forms */
@source "../../node_modules/@enlolab/forms/dist";
/* Variables CSS personalizadas */
:root {
--primary: oklch(0.723 0.219 149.579);
--primary-foreground: oklch(0.982 0.018 155.826);
--ring: oklch(0.723 0.219 149.579);
}🚀 Uso básico
Formulario simple
import { z } from "zod";
import { useForm } from "@enlolab/forms";
const schema = z.object({
name: z.string().min(1, "El nombre es requerido"),
email: z.string().email("Email inválido"),
age: z.number().min(18, "Debes ser mayor de edad"),
});
function SimpleForm() {
const form = useForm({ schema });
const onSubmit = (data: z.infer<typeof schema>) => {
console.log(data);
};
return (
<form.Form onSubmit={onSubmit}>
<form.Field name="name" label="Nombre" />
<form.Field name="email" label="Email" type="email" />
<form.Field name="age" label="Edad" type="number" />
<form.Submit>Enviar</form.Submit>
</form.Form>
);
}Formulario con configuración avanzada
import { z } from "zod";
import { useForm } from "@enlolab/forms";
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
country: z.string().min(1),
interests: z.array(z.string()).min(1),
newsletter: z.boolean(),
});
function AdvancedForm() {
const form = useForm({
schema,
defaultValues: {
newsletter: true,
},
});
return (
<form.Form onSubmit={console.log}>
<form.Field
name="name"
label="Nombre completo"
placeholder="Tu nombre completo"
helperText="Ingresa tu nombre completo"
/>
<form.Field
name="email"
label="Email"
type="email"
placeholder="[email protected]"
/>
<form.Field
name="country"
label="País"
type="select"
options={[
{ value: "es", label: "España" },
{ value: "mx", label: "México" },
{ value: "ar", label: "Argentina" },
]}
/>
<form.Field
name="interests"
label="Intereses"
type="checkbox-group"
options={[
{ value: "tech", label: "Tecnología" },
{ value: "sports", label: "Deportes" },
{ value: "music", label: "Música" },
]}
/>
<form.Field
name="newsletter"
label="Suscribirse al newsletter"
type="switch"
/>
<form.Submit>Guardar</form.Submit>
</form.Form>
);
}🎭 Tipos de campos disponibles
Campos de texto
// Campo de texto básico
<form.Field name="name" label="Nombre" />
// Campo de email
<form.Field name="email" label="Email" type="email" />
// Campo de teléfono
<form.Field name="phone" label="Teléfono" type="tel" />
// Área de texto
<form.Field name="message" label="Mensaje" type="textarea" rows={4} />Campos de selección
// Select simple
<form.Field
name="country"
label="País"
type="select"
options={[
{ value: "es", label: "España" },
{ value: "mx", label: "México" }
]}
/>
// Radio buttons
<form.Field
name="gender"
label="Género"
type="radio"
options={[
{ value: "male", label: "Masculino" },
{ value: "female", label: "Femenino" },
{ value: "other", label: "Otro" }
]}
/>
// Checkbox group
<form.Field
name="interests"
label="Intereses"
type="checkbox-group"
options={[
{ value: "tech", label: "Tecnología" },
{ value: "sports", label: "Deportes" }
]}
/>Campos especiales
// Switch
<form.Field
name="newsletter"
label="Newsletter"
type="switch"
/>
// Slider
<form.Field
name="age"
label="Edad"
type="slider"
min={0}
max={100}
step={1}
/>
// Fecha
<form.Field
name="birthdate"
label="Fecha de nacimiento"
type="date"
dateType="popover"
/>🔄 Formularios multi-paso
import { z } from "zod";
import { useForm } from "@enlolab/forms";
const step1Schema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
const step2Schema = z.object({
country: z.string().min(1),
interests: z.array(z.string()).min(1),
});
const fullSchema = step1Schema.merge(step2Schema);
function MultiStepForm() {
const form = useForm({
schema: fullSchema,
steps: [
{
title: "Información personal",
fields: ["name", "email"],
},
{
title: "Preferencias",
fields: ["country", "interests"],
},
],
});
return (
<form.Form onSubmit={console.log}>
<form.Steps>
<form.Step>
<form.Field name="name" label="Nombre" />
<form.Field name="email" label="Email" type="email" />
<form.Next>Siguiente</form.Next>
</form.Step>
<form.Step>
<form.Field
name="country"
label="País"
type="select"
options={[
{ value: "es", label: "España" },
{ value: "mx", label: "México" },
]}
/>
<form.Field
name="interests"
label="Intereses"
type="checkbox-group"
options={[
{ value: "tech", label: "Tecnología" },
{ value: "sports", label: "Deportes" },
]}
/>
<form.Back>Anterior</form.Back>
<form.Submit>Enviar</form.Submit>
</form.Step>
</form.Steps>
</form.Form>
);
}🎨 Personalización
Variables CSS
Puedes personalizar los colores y estilos modificando las variables CSS:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.723 0.219 149.579);
--primary-foreground: oklch(0.982 0.018 155.826);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.723 0.219 149.579);
--radius: 0.65rem;
}Configuración de campos
<form.Field
name="email"
label="Email"
type="email"
placeholder="[email protected]"
helperText="Nunca compartiremos tu email"
error_className="text-red-500"
input_className="border-2 border-blue-300"
label_className="font-bold text-gray-700"
wrapper_className="mb-6"
colSpan={{ default: 1, md: 2 }}
disabled={(values) => values.isLoading}
hidden={(values) => values.hideEmail}
/>🔧 Configuración avanzada
Monorepo con workspaces
Si usas un monorepo, puedes configurar los estilos así:
/* Para desarrollo (paquetes locales) */
@source "../../../packages/enlolab-ui/dist";
@source "../../../packages/enlolab-forms/dist";
/* Para producción (paquetes publicados) */
@source "../../../node_modules/@enlolab/ui
@source "../../../node_modules/@enlolab/forms/dist";Safelist de clases críticas
Para asegurar que las clases críticas se generen:
@source inline("bg-popover", "text-popover-foreground", "border", "shadow-md", "rounded-md", "z-50");📚 Ejemplos completos
Formulario de contacto completo
import { z } from "zod";
import { useForm } from "@enlolab/forms";
const contactSchema = z.object({
name: z.string().min(1, "El nombre es requerido"),
email: z.string().email("Email inválido"),
phone: z.string().optional(),
subject: z.string().min(1, "El asunto es requerido"),
message: z.string().min(10, "El mensaje debe tener al menos 10 caracteres"),
newsletter: z.boolean().default(false),
priority: z.enum(["low", "medium", "high"]).default("medium"),
});
function ContactForm() {
const form = useForm({
schema: contactSchema,
defaultValues: {
priority: "medium",
newsletter: false,
},
});
const onSubmit = async (data: z.infer<typeof contactSchema>) => {
try {
await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
alert("Mensaje enviado correctamente");
} catch (error) {
alert("Error al enviar el mensaje");
}
};
return (
<form.Form onSubmit={onSubmit} className="max-w-2xl mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">Formulario de contacto</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<form.Field
name="name"
label="Nombre completo"
placeholder="Tu nombre completo"
/>
<form.Field
name="email"
label="Email"
type="email"
placeholder="[email protected]"
/>
</div>
<form.Field
name="phone"
label="Teléfono (opcional)"
type="tel"
placeholder="+34 600 000 000"
/>
<form.Field
name="subject"
label="Asunto"
placeholder="¿En qué podemos ayudarte?"
/>
<form.Field
name="priority"
label="Prioridad"
type="radio"
options={[
{ value: "low", label: "Baja" },
{ value: "medium", label: "Media" },
{ value: "high", label: "Alta" },
]}
/>
<form.Field
name="message"
label="Mensaje"
type="textarea"
rows={5}
placeholder="Describe tu consulta..."
/>
<form.Field
name="newsletter"
label="Suscribirse al newsletter"
type="switch"
/>
<div className="flex gap-4 mt-6">
<form.Submit className="flex-1">Enviar mensaje</form.Submit>
<button type="button" className="px-6 py-2 border rounded-md">
Cancelar
</button>
</div>
</form.Form>
);
}Formulario de registro multi-paso
import { z } from "zod";
import { useForm } from "@enlolab/forms";
const personalSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
birthdate: z.date(),
});
const accountSchema = z
.object({
username: z.string().min(3),
password: z.string().min(8),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Las contraseñas no coinciden",
path: ["confirmPassword"],
});
const preferencesSchema = z.object({
interests: z.array(z.string()).min(1),
newsletter: z.boolean(),
terms: z.boolean().refine((val) => val, "Debes aceptar los términos"),
});
const fullSchema = personalSchema.merge(accountSchema).merge(preferencesSchema);
function RegistrationForm() {
const form = useForm({
schema: fullSchema,
steps: [
{
title: "Información personal",
fields: ["firstName", "lastName", "email", "birthdate"],
},
{ title: "Cuenta", fields: ["username", "password", "confirmPassword"] },
{ title: "Preferencias", fields: ["interests", "newsletter", "terms"] },
],
});
return (
<form.Form onSubmit={console.log} className="max-w-2xl mx-auto">
<form.Steps>
<form.Step>
<h3 className="text-xl font-bold mb-4">Información personal</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<form.Field name="firstName" label="Nombre" />
<form.Field name="lastName" label="Apellidos" />
</div>
<form.Field name="email" label="Email" type="email" />
<form.Field
name="birthdate"
label="Fecha de nacimiento"
type="date"
/>
<form.Next>Siguiente</form.Next>
</form.Step>
<form.Step>
<h3 className="text-xl font-bold mb-4">Cuenta</h3>
<form.Field name="username" label="Nombre de usuario" />
<form.Field name="password" label="Contraseña" type="password" />
<form.Field
name="confirmPassword"
label="Confirmar contraseña"
type="password"
/>
<form.Back>Anterior</form.Back>
<form.Next>Siguiente</form.Next>
</form.Step>
<form.Step>
<h3 className="text-xl font-bold mb-4">Preferencias</h3>
<form.Field
name="interests"
label="Intereses"
type="checkbox-group"
options={[
{ value: "tech", label: "Tecnología" },
{ value: "sports", label: "Deportes" },
{ value: "music", label: "Música" },
{ value: "travel", label: "Viajes" },
]}
/>
<form.Field name="newsletter" label="Newsletter" type="switch" />
<form.Field
name="terms"
label="Acepto los términos y condiciones"
type="checkbox"
/>
<form.Back>Anterior</form.Back>
<form.Submit>Crear cuenta</form.Submit>
</form.Step>
</form.Steps>
</form.Form>
);
}🤝 Contribuir
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
📄 Licencia
MIT License - ver LICENSE para más detalles.
🔗 Enlaces
Hecho con ❤️ por el equipo de Enlolab
