@izan-sanchez/izan-form
v1.0.1
Published
Un componente de formulario web component universal construido con Svelte. Compatible con React, Vue, Angular y JavaScript vanilla.
Maintainers
Readme
@izan-sanchez/izan-form 🚀
Un componente de formulario web moderno y accesible construido con Svelte que funciona en cualquier framework o HTML vanilla. Diseño glassmorphism, validación integrada, y totalmente personalizable.
✨ Características
- 🎨 Diseño Moderno: Estilo glassmorphism con animaciones suaves
- 🌙 Modo Oscuro/Claro: Soporte nativo para temas
- ♿ Accesible: Cumple con estándares WCAG
- 📱 Responsive: Optimizado para móviles y desktop
- 🔧 Flexible: Múltiples tipos de campos soportados
- ⚡ Ligero: Sin dependencias externas
- 🌐 Universal: Funciona en cualquier framework
- 🔒 Validación: Validación HTML5 integrada
- 🎭 Animaciones: Transiciones suaves y micro-interacciones
- 🎯 TypeScript: Incluye definiciones de tipos
📦 Instalación
npm install @izan-sanchez/izan-formyarn add @izan-sanchez/izan-formpnpm add @izan-sanchez/izan-form📋 Propiedades (Attributes)
| Propiedad | Tipo | Valor por defecto | Descripción |
|-----------|------|-------------------|-------------|
| title | string | '' | Título del formulario |
| submittext | string | 'Enviar' | Texto del botón de envío |
| loadingtext | string | 'Enviando...' | Texto mostrado durante el envío |
| theme | string | 'light' | Tema del formulario ('light' | 'dark') |
| fields | string | array | '[]' | Configuración de campos (JSON string o array) |
| privacypolicyurl | string | '' | URL de la política de privacidad |
🔧 Configuración de Campos
Tipos de Campo Soportados
Text Input
{
name: 'firstName',
type: 'text',
label: 'Nombre',
required: true,
minlength: 2,
maxlength: 50,
}{
name: 'email',
type: 'email',
label: 'Correo electrónico',
required: true,
}Password
{
name: 'password',
type: 'password',
label: 'Contraseña',
required: true,
minlength: 8,
maxlength: 128,}Number
{
name: 'age',
type: 'number',
label: 'Edad',
required: true,
min: 18,
max: 120,
}Tel
{
name: 'phone',
type: 'tel',
label: 'Teléfono',
}URL
{
name: 'website',
type: 'url',
label: 'Sitio web',
}Date
{
name: 'birthdate',
type: 'date',
label: 'Fecha de nacimiento',
required: true,
min: '1900-01-01',
max: '2024-12-31'
}Time
{
name: 'appointment',
type: 'time',
label: 'Hora de cita',
required: true,
min: '09:00',
max: '18:00'
}Textarea
{
name: 'message',
type: 'textarea',
label: 'Mensaje',
required: true,
rows: 5,
minlength: 10,
maxlength: 1000,
}Select
{
name: 'country',
type: 'select',
label: 'País',
required: true,
options: [
{ value: 'es', label: 'España' },
{ value: 'mx', label: 'México' },
{ value: 'ar', label: 'Argentina' },
{ value: 'co', label: 'Colombia', selected: true }
],
}Multiple Select
{
name: 'skills',
type: 'select',
label: 'Habilidades',
multiple: true,
options: [
{ value: 'js', label: 'JavaScript' },
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue.js' },
{ value: 'svelte', label: 'Svelte' }
]
}Radio Buttons
{
name: 'plan',
type: 'radio',
label: 'Plan de suscripción',
required: true,
options: [
{ value: 'basic', label: 'Básico - €9/mes' },
{ value: 'pro', label: 'Pro - €19/mes', selected: true },
{ value: 'premium', label: 'Premium - €39/mes' }
]
}Checkbox
{
name: 'newsletter',
type: 'checkbox',
label: 'Suscribirme al newsletter',
}Privacy Checkbox (especial)
{
name: 'privacy',
type: 'checkbox',
label: 'Acepto la política de privacidad',
required: true
}Propiedades Comunes de Campos
| Propiedad | Tipo | Descripción |
|-----------|------|-------------|
| name | string | Requerido. Nombre único del campo |
| type | string | Requerido. Tipo de campo |
| label | string | Requerido. Etiqueta visible del campo |
| required | boolean | Si el campo es obligatorio |
Propiedades Específicas por Tipo
Text, Email, Password, Tel, URL
minlength: Longitud mínimamaxlength: Longitud máxima
Number, Date, Time
min: Valor mínimomax: Valor máximo
Textarea
rows: Número de filasminlength: Longitud mínimamaxlength: Longitud máxima
Select
multiple: Permitir selección múltipleoptions: Array de opciones convalue,labelyselected
Radio
options: Array de opciones convalue,labelyselected
🎯 Uso Básico
HTML Vanilla
<!DOCTYPE html>
<html>
<head>
<script type="module" src="node_modules/@izan-sanchez/izan-form/dist/izan-form.js"></script>
</head>
<body>
<izan-form
title="Formulario de Contacto"
submittext="Enviar Mensaje"
theme="light"
fields='[
{
"name": "name",
"type": "text",
"label": "Nombre",
"required": true
},
{
"name": "email",
"type": "email",
"label": "Email",
"required": true
},
{
"name": "message",
"type": "textarea",
"label": "Mensaje",
"required": true
}
]'>
</izan-form>
<script>
document.querySelector('izan-form').addEventListener('formsubmit', (event) => {
console.log('Datos:', event.detail.values);
// Procesar datos aquí
});
</script>
</body>
</html>React
import React, { useEffect, useRef } from 'react';
import '@izan-sanchez/izan-form';
function ContactForm() {
const formRef = useRef(null);
const fields = [
{
name: 'name',
type: 'text',
label: 'Nombre',
required: true,
placeholder: 'Tu nombre completo'
},
{
name: 'email',
type: 'email',
label: 'Email',
required: true,
placeholder: '[email protected]'
},
{
name: 'phone',
type: 'tel',
label: 'Teléfono',
placeholder: '+34 600 000 000'
},
{
name: 'message',
type: 'textarea',
label: 'Mensaje',
required: true,
rows: 5,
placeholder: 'Escribe tu mensaje aquí...'
},
{
name: 'privacy',
type: 'checkbox',
label: 'Acepto la política de privacidad',
required: true
}
];
useEffect(() => {
const form = formRef.current;
if (form) {
const handleSubmit = (event) => {
console.log('Form data:', event.detail.values);
// Aquí puedes enviar los datos a tu API
fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.detail.values)
});
};
const handleReady = () => {
console.log('Form is ready');
};
form.addEventListener('formsubmit', handleSubmit);
form.addEventListener('ready', handleReady);
return () => {
form.removeEventListener('formsubmit', handleSubmit);
form.removeEventListener('ready', handleReady);
};
}
}, []);
return (
<izan-form
ref={formRef}
title="Formulario de Contacto"
submittext="Enviar Mensaje"
loadingtext="Enviando..."
theme="light"
fields={JSON.stringify(fields)}
successmessage="¡Mensaje enviado correctamente!"
errormessage="Error al enviar el mensaje. Inténtalo de nuevo."
privacypolicyurl="https://tu-sitio.com/privacy"
autocomplete="on"
/>
);
}
export default ContactForm;Vue 3
<template>
<izan-form
ref="formComponent"
:title="formConfig.title"
:submittext="formConfig.submitText"
:theme="currentTheme"
:fields="JSON.stringify(formConfig.fields)"
:successmessage="formConfig.successMessage"
:errormessage="formConfig.errorMessage"
:privacypolicyurl="formConfig.privacyUrl"
@formsubmit="handleFormSubmit"
@ready="onFormReady"
/>
<div class="form-controls">
<button @click="toggleTheme">
Cambiar a tema {{ currentTheme === 'light' ? 'oscuro' : 'claro' }}
</button>
<button @click="resetForm">Resetear formulario</button>
<button @click="validateForm">Validar formulario</button>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import '@izan-sanchez/izan-form';
const formComponent = ref(null);
const currentTheme = ref('light');
const formConfig = reactive({
title: 'Registro de Usuario',
submitText: 'Crear Cuenta',
successMessage: '¡Cuenta creada exitosamente!',
errorMessage: 'Error al crear la cuenta',
privacyUrl: 'https://tu-sitio.com/privacy',
fields: [
{
name: 'firstName',
type: 'text',
label: 'Nombre',
required: true,
minlength: 2
},
{
name: 'lastName',
type: 'text',
label: 'Apellidos',
required: true,
minlength: 2
},
{
name: 'email',
type: 'email',
label: 'Email',
required: true
},
{
name: 'password',
type: 'password',
label: 'Contraseña',
required: true,
minlength: 8
},
{
name: 'country',
type: 'select',
label: 'País',
required: true,
options: [
{ value: 'es', label: 'España' },
{ value: 'mx', label: 'México' },
{ value: 'ar', label: 'Argentina' },
{ value: 'co', label: 'Colombia' }
]
},
{
name: 'gender',
type: 'radio',
label: 'Género',
options: [
{ value: 'male', label: 'Masculino' },
{ value: 'female', label: 'Femenino' },
{ value: 'other', label: 'Otro' },
{ value: 'prefer-not-to-say', label: 'Prefiero no decir' }
]
},
{
name: 'newsletter',
type: 'checkbox',
label: 'Suscribirme al newsletter'
},
{
name: 'privacy',
type: 'checkbox',
label: 'Acepto la política de privacidad',
required: true
}
]
});
const handleFormSubmit = async (event) => {
console.log('Form submitted:', event.detail.values);
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.detail.values)
});
if (!response.ok) {
throw new Error('Registration failed');
}
const result = await response.json();
console.log('Registration successful:', result);
} catch (error) {
console.error('Registration error:', error);
}
};
const onFormReady = () => {
console.log('Form component is ready');
};
const toggleTheme = () => {
currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
};
const resetForm = () => {
if (formComponent.value) {
formComponent.value.reset();
}
};
const validateForm = () => {
if (formComponent.value) {
const isValid = formComponent.value.validate();
console.log('Form is valid:', isValid);
}
};
</script>
<style scoped>
.form-controls {
margin-top: 20px;
display: flex;
gap: 10px;
}
.form-controls button {
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
cursor: pointer;
}
</style>Angular
// app.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import '@izan-sanchez/izan-form';
@Component({
selector: 'app-contact',
template: `
<izan-form
#formElement
[attr.title]="formTitle"
[attr.submittext]="submitText"
[attr.theme]="theme"
[attr.fields]="fieldsJson"
[attr.successmessage]="successMessage"
[attr.errormessage]="errorMessage"
[attr.privacypolicyurl]="privacyUrl"
></izan-form>
<div class="controls">
<button (click)="toggleTheme()">Cambiar Tema</button>
<button (click)="resetForm()">Reset</button>
<button (click)="getFormValues()">Ver Valores</button>
</div>
`,
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
@ViewChild('formElement') formElement!: ElementRef;
formTitle = 'Formulario de Contacto';
submitText = 'Enviar';
theme = 'light';
successMessage = '¡Formulario enviado correctamente!';
errorMessage = 'Error al enviar el formulario';
privacyUrl = 'https://tu-sitio.com/privacy';
fields = [
{
name: 'company',
type: 'text',
label: 'Empresa',
required: true
},
{
name: 'contact_name',
type: 'text',
label: 'Nombre de contacto',
required: true
},
{
name: 'email',
type: 'email',
label: 'Email corporativo',
required: true
},
{
name: 'budget',
type: 'select',
label: 'Presupuesto',
required: true,
options: [
{ value: '1000-5000', label: '1.000€ - 5.000€' },
{ value: '5000-10000', label: '5.000€ - 10.000€' },
{ value: '10000-25000', label: '10.000€ - 25.000€' },
{ value: '25000+', label: 'Más de 25.000€' }
]
},
{
name: 'services',
type: 'checkbox',
label: 'Desarrollo web'
},
{
name: 'privacy',
type: 'checkbox',
label: 'Acepto la política de privacidad',
required: true
}
];
get fieldsJson(): string {
return JSON.stringify(this.fields);
}
ngAfterViewInit() {
const form = this.formElement.nativeElement;
form.addEventListener('formsubmit', (event: any) => {
console.log('Form submitted:', event.detail.values);
this.handleFormSubmit(event.detail.values);
});
form.addEventListener('ready', () => {
console.log('Form is ready');
});
}
handleFormSubmit(values: any) {
// Procesar los datos del formulario
console.log('Processing form data:', values);
}
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
resetForm() {
const form = this.formElement.nativeElement;
if (form && form.reset) {
form.reset();
}
}
getFormValues() {
const form = this.formElement.nativeElement;
if (form && form.getValues) {
const values = form.getValues();
console.log('Current form values:', values);
}
}
}Astro
---
// pages/contact.astro
const fields = [
{
name: 'name',
type: 'text',
label: 'Nombre',
required: true
},
{
name: 'email',
type: 'email',
label: 'Email',
required: true
},
{
name: 'subject',
type: 'select',
label: 'Asunto',
required: true,
options: [
{ value: 'general', label: 'Consulta general' },
{ value: 'support', label: 'Soporte técnico' },
{ value: 'sales', label: 'Ventas' },
{ value: 'partnership', label: 'Colaboración' }
]
},
{
name: 'priority',
type: 'radio',
label: 'Prioridad',
options: [
{ value: 'low', label: 'Baja' },
{ value: 'medium', label: 'Media', selected: true },
{ value: 'high', label: 'Alta' }
]
},
{
name: 'message',
type: 'textarea',
label: 'Mensaje',
required: true,
rows: 6
},
{
name: 'privacy',
type: 'checkbox',
label: 'Acepto la política de privacidad',
required: true
}
];
---
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Contacto - Mi Sitio</title>
</head>
<body>
<main>
<h1>Contacto</h1>
<izan-form
id="contact-form"
title="Envíanos un mensaje"
submittext="Enviar mensaje"
loadingtext="Enviando..."
theme="light"
fields={JSON.stringify(fields)}
successmessage="¡Mensaje enviado correctamente!"
errormessage="Error al enviar el mensaje"
privacypolicyurl="/privacy"
action="/api/contact"
method="POST"
autocomplete="on"
/>
</main>
<script>
import '@izan-sanchez/izan-form';
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
if (form) {
form.addEventListener('formsubmit', async (event) => {
console.log('Form data:', event.detail.values);
// Ejemplo de envío a API
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(event.detail.values)
});
if (response.ok) {
console.log('Message sent successfully');
} else {
console.error('Failed to send message');
}
} catch (error) {
console.error('Error:', error);
}
});
form.addEventListener('ready', () => {
console.log('Contact form is ready');
});
}
});
</script>
<style>
main {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
h1 {
text-align: center;
margin-bottom: 2rem;
color: #333;
}
</style>
</body>
</html>Svelte/SvelteKit
<script>
import { onMount } from 'svelte';
import '@izan-sanchez/izan-form';
let formElement;
let currentTheme = 'light';
const fields = [
{
name: 'username',
type: 'text',
label: 'Nombre de usuario',
required: true,
minlength: 3,
maxlength: 20,
pattern: '^[a-zA-Z0-9_]+$',
help: 'Solo letras, números y guiones bajos'
},
{
name: 'email',
type: 'email',
label: 'Email',
required: true
},
{
name: 'age',
type: 'number',
label: 'Edad',
min: 18,
max: 120,
required: true
},
{
name: 'website',
type: 'url',
label: 'Sitio web',
placeholder: 'https://tu-sitio.com'
},
{
name: 'bio',
type: 'textarea',
label: 'Biografía',
maxlength: 500,
rows: 4,
help: 'Cuéntanos un poco sobre ti'
},
{
name: 'notifications',
type: 'checkbox',
label: 'Recibir notificaciones por email'
},
{
name: 'privacy',
type: 'checkbox',
label: 'Acepto los términos y condiciones',
required: true
}
];
function handleFormSubmit(event) {
console.log('Form submitted:', event.detail.values);
// Ejemplo de validación personalizada
const { username, email, age } = event.detail.values;
if (username.toLowerCase().includes('admin')) {
alert('El nombre de usuario no puede contener "admin"');
return;
}
if (age < 18) {
alert('Debes ser mayor de edad');
return;
}
// Simular envío a servidor
setTimeout(() => {
alert('¡Registro completado exitosamente!');
formElement.reset();
}, 1000);
}
function toggleTheme() {
currentTheme = currentTheme === 'light' ? 'dark' : 'light';
}
function resetForm() {
if (formElement) {
formElement.reset();
}
}
function validateForm() {
if (formElement) {
const isValid = formElement.validate();
alert(`El formulario es ${isValid ? 'válido' : 'inválido'}`);
}
}
function getFormValues() {
if (formElement) {
const values = formElement.getValues();
console.log('Valores actuales:', values);
}
}
onMount(() => {
if (formElement) {
formElement.addEventListener('formsubmit', handleFormSubmit);
formElement.addEventListener('ready', () => {
console.log('Form is ready');
});
}
return () => {
if (formElement) {
formElement.removeEventListener('formsubmit', handleFormSubmit);
}
};
});
</script>
<div class="container">
<h1>Registro de Usuario</h1>
<izan-form
bind:this={formElement}
title="Crea tu cuenta"
submittext="Registrarse"
loadingtext="Creando cuenta..."
theme={currentTheme}
fields={JSON.stringify(fields)}
successmessage="¡Cuenta creada correctamente!"
errormessage="Error al crear la cuenta"
privacypolicyurl="/privacy"
novalidate={false}
autocomplete="on"
/>
<div class="controls">
<button on:click={toggleTheme}>
Cambiar a tema {currentTheme === 'light' ? 'oscuro' : 'claro'}
</button>
<button on:click={resetForm}>Resetear formulario</button>
<button on:click={validateForm}>Validar formulario</button>
<button on:click={getFormValues}>Ver valores actuales</button>
</div>
</div>
<style>
.container {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
}
h1 {
text-align: center;
margin-bottom: 2rem;
color: #333;
}
.controls {
margin-top: 2rem;
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.controls button {
padding: 0.5rem 1rem;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
cursor: pointer;
transition: background-color 0.2s;
}
.controls button:hover {
background: #f5f5f5;
}
</style>👨💻 Autor
Izan Sanchez
- Email: [email protected]
- GitHub: @izan-sanchez
📞 Soporte
¿Necesitas ayuda? Puedes:
- 📖 Revisar esta documentación
- 📧 Contactar al autor: [email protected]
