npm package: @luis-angel-martin-dzul/input-styles
Description: Librería de inputs y componentes Vue para desarrollos
Latest version: 0.0.3

# 📦 Input Style

**Librería de componentes y formularios Vue 3**

Incluye componentes de inputs, selects, botones, paginación y más, estilizados con TailwindCSS y listos para producción.

------

![Última actualización](https://img.shields.io/badge/Actualizado-31%20de%20Marzo%20del%2026-blue)

------

# 📦 Instalación

La librería se distribuye mediante **npm**:

```
npm install @luis-angel-martin-dzul/input-styles
```

### Dependencias peer requeridas

```
npm install vue@^3.3.0 v-calendar@^3.0.0
```

------

# 🚀 Uso en Vue + Vite

En `main.js` o `main.ts`:

```js
import { createApp } from 'vue'
import App from './App.vue'

import 'v-calendar/style.css'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import {
    faCircleCheck, faCircleXmark, faEye, faEyeSlash,
    faCalendar, faClock, faChevronDown,
    faCloudArrowUp, faCloudArrowDown, faSpinner, faCheck
} from '@fortawesome/free-solid-svg-icons'
import VCalendar from 'v-calendar'

library.add(
    faCircleCheck, faCircleXmark, faEye, faEyeSlash,
    faCalendar, faClock, faChevronDown,
    faCloudArrowUp, faCloudArrowDown, faSpinner, faCheck
)

const app = createApp(App)

app.component('font-awesome-icon', FontAwesomeIcon)
app.use(VCalendar, { locale: 'es', firstDayOfWeek: 1 })
app.mount('#app')
```

------

# 🎨 Estilos personalizados (Style Registry)

La librería expone un sistema de **tokens de estilo configurables** que permite cambiar la apariencia de todos los componentes desde un solo lugar, sin necesidad de modificar el código fuente.

Los estilos se configuran **antes de montar la app**, importando `configureStyles` directamente.

```js
import { configureStyles } from '@luis-angel-martin-dzul/input-styles'

configureStyles({
    LOADER_ARC_STROKE: '#3B82F6',
    REQUIRED_ASTERISK: 'text-red-500',
    // solo los tokens que quieras cambiar
})
```

> ⚠️ `configureStyles()` debe llamarse **antes** de que los componentes se monten. Los cambios no son reactivos después del montaje inicial.

------

## 📋 Referencia completa de tokens

### Tokens auxiliares de label
_Afecta: todos los componentes que tienen `label` o `description`_

Estos tokens controlan partes individuales del label y del texto descriptivo. Al sobrescribir uno, las funciones `inputLabelClass`, `inputCheckLabelClass` e `inputDescriptionClass` lo recogen automáticamente **sin necesidad de sobreescribir la función entera**.

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `INPUT_BASE_TITLE` | `string` | `'block text-sm font-medium'` | Clases estructurales del `<label>` de inputs de texto. |
| `INPUT_CHECK_BASE_TITLE` | `string` | `'block text-sm font-medium'` | Clases estructurales del `<label>` de checkbox, radio y switch. |
| `LABEL_DISABLED_STATIC` | `string` | `'text-gray-400'` | Color del label y descripción cuando el campo está deshabilitado. |
| `LABEL_ENABLED_STATIC` | `string` | `'text-gray-700'` | Color del label cuando el campo está habilitado. |

```js
configureStyles({
    LABEL_DISABLED_STATIC: 'text-gray-400',
    LABEL_ENABLED_STATIC:  'text-gray-900',
    INPUT_BASE_TITLE:      'block font-semibold mb-1 text-sm',
})
```

---

### Inputs de texto
_Afecta: `InputTextStyle`, `InputNumberStyle`, `InputPasswordStyle`, `TextAreaStyle`, `InputDateStyle`, `InputTimeStyle`, `SelectStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `INPUT_BASE` | `string` | `'w-full text-gray-800 bg-white px-3 py-1.5 rounded border ...'` | Clases base del `<input>`. No incluye borde ni ring (eso lo maneja `inputValidateClass`). |
| `inputValidateClass` | `function(validate)` | Ver abajo | Retorna las clases de borde y ring según el estado de validación. `validate`: `true` / `false` / `null`. |
| `inputLabelClass` | `function(disabled)` | Ver abajo | Retorna las clases del `<label>`. Internamente usa `INPUT_BASE_TITLE`, `LABEL_DISABLED_STATIC` y `LABEL_ENABLED_STATIC`. |
| `inputDescriptionClass` | `function(validate, alignRight?)` | Ver abajo | Retorna las clases del texto de descripción. Internamente usa `ICON_VALID`, `ICON_INVALID` y `LABEL_DISABLED_STATIC`. |

> 💡 Si solo quieres cambiar un color de validación, basta con sobrescribir `ICON_INVALID` o `ICON_VALID` — `inputDescriptionClass` los recoge automáticamente.

```js
configureStyles({
    ICON_INVALID: 'text-red-500',
    ICON_VALID:   'text-emerald-500',
})

// Override completo de las funciones (cuando necesitas control total)
configureStyles({
    inputValidateClass: (validate) => {
        if (validate === false) return 'border-red-500 focus:ring-red-200'
        if (validate === true)  return 'border-emerald-500 focus:ring-emerald-200'
        return 'border-gray-200 focus:ring-indigo-200'
    },
    inputLabelClass: (disabled) => ['block font-bold mb-2', disabled ? 'text-gray-400' : 'text-gray-900'],
    inputDescriptionClass: (validate, alignRight = true) => [
        'block w-full text-sm',
        alignRight && 'text-right',
        validate === false ? 'text-red-500' : validate === true ? 'text-emerald-500' : 'text-gray-500',
    ],
})
```

---

### Select
_Afecta: `SelectStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `SELECT_DISABLED` | `string` | `'w-full px-3 py-1.5 rounded bg-gray-100 ...'` | Clases del input en estado disabled. |
| `SELECT_DROPDOWN` | `string` | `'absolute z-20 w-full bg-white border ...'` | Clases del contenedor `<ul>` del dropdown. |
| `SELECT_OPTION_BASE` | `string` | `'px-3 py-1 cursor-pointer text-md'` | Clases base de cada `<li>` del dropdown. |
| `selectOptionClass` | `function(isSelected, isHighlighted)` | Ver abajo | Retorna clases dinámicas por opción según su estado. |
| `SELECT_EMPTY` | `string` | `'px-3 py-1 text-gray-400 cursor-not-allowed'` | Clases del mensaje "Sin registros". |

```js
configureStyles({
    selectOptionClass: (isSelected, isHighlighted) => {
        if (isSelected)    return 'bg-indigo-100 text-indigo-900 font-medium'
        if (isHighlighted) return 'bg-indigo-50 text-gray-900'
        return 'hover:bg-indigo-50 text-gray-800'
    },
})
```

---

### Checkbox, Radio y Switch
_Afecta: `InputCheckStyle`, `InputRadioStyle`, `InputSwitchStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `inputCheckLabelClass` | `function(disabled)` | Ver abajo | Clases del label de checks. |
| `CHECK_BOX_BASE` | `string` | `'w-5 h-5 flex items-center justify-center rounded border-2 transition'` | Estructura base del cuadro del checkbox. |
| `RADIO_BASE` | `string` | `'w-5 h-5 flex items-center justify-center rounded-full transition'` | Estructura base del círculo del radio. |
| `SWITCH_BASE` | `string` | `'w-10 h-5 relative flex items-center rounded-full transition border-2'` | Estructura base de la pista del switch. |
| `checkBoxClass` | `function(model, disabled)` | Ver abajo | Clases de color del checkbox según estado activo/disabled. |
| `radioClass` | `function(model, disabled)` | Ver abajo | Clases de color del radio button según estado. |
| `switchClass` | `function(model, disabled)` | Ver abajo | Clases de color de la pista del switch. |
| `switchDotClass` | `function(model, disabled)` | Ver abajo | Clases de color de la bola interior del switch. |

```js
configureStyles({
    checkBoxClass: (model, disabled) => {
        if (disabled) return model
            ? 'bg-indigo-200 border-indigo-200 cursor-not-allowed'
            : 'bg-gray-100 border-gray-200 cursor-not-allowed'
        return model
            ? 'bg-indigo-500 border-indigo-500 peer-focus:ring-2 peer-focus:ring-indigo-200'
            : 'bg-white border-gray-300 peer-focus:ring-2 peer-focus:ring-indigo-200'
    },
})
```

---

### Paginación
_Afecta: `PaginationStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `PAGINATION_NAV_PREV` | `string` | `'bg-white hover:bg-indigo-50 border ...'` | Clases del botón de página anterior. |
| `PAGINATION_NAV_NEXT` | `string` | `'bg-white hover:bg-indigo-50 border ...'` | Clases del botón de página siguiente. |
| `PAGINATION_PAGE_BASE` | `string` | `'border px-2.5 py-2 cursor-pointer w-8 relative'` | Clases base de cada botón de página. |
| `paginationPageClass` | `function(page, actualPage, index)` | Ver abajo | Retorna clases dinámicas por botón de página. |
| `PAGINATION_SUMMARY` | `string` | `'text-sm text-gray-700'` | Clases del texto de resumen de registros. |

---

### Loader
_Afecta: `LoaderStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `LOADER_ARC_STROKE` | `string` | `'#6366F1'` | Color hex del arco animado del loader (indigo-500). |
| `LOADER_MESSAGE` | `string` | `'text-gray-600 text-lg font-medium'` | Clases del mensaje de carga. |

---

### Iconos y asterisco de requerido
_Afecta: todos los componentes con `required` o iconos de validación_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `REQUIRED_ASTERISK` | `string` | `'text-red-500'` | Clase de color del asterisco `*` en campos requeridos. |
| `ICON_VALID` | `string` | `'text-emerald-500'` | Clase de color del ícono de validación correcta. |
| `ICON_INVALID` | `string` | `'text-red-500'` | Clase de color del ícono de validación errónea. |
| `ICON_STATIC` | `string` | `'text-indigo-400'` | Clase de color de los íconos estáticos (reloj, calendario, ojo, chevron). |
| `ICON_SIZE_STATIC` | `string` | `'w-5 h-5'` | Clases de tamaño de todos los íconos. |

---

### File Upload y Download
_Afecta: `InputFileUploadStyle`, `InputFileDownloadStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `FILE_BUTTON_BASE` | `string` | `'flex items-center justify-center gap-3 px-4 py-2 border-2 rounded-xl ...'` | Clases estructurales del botón (compartido entre upload y download). |
| `FILE_BUTTON_ACTIVE` | `string` | `'border-gray-200 bg-white text-gray-700 hover:border-indigo-400 ...'` | Clases del botón en estado normal/activo. |
| `FILE_BUTTON_DISABLED` | `string` | `'border-gray-200 bg-gray-100 text-gray-400'` | Clases del botón en estado disabled. |
| `FILE_ICON_BG` | `string` | `'bg-indigo-300'` | Fondo del círculo de ícono en estado activo. |
| `FILE_ICON_BG_COMPLETE` | `string` | `'bg-emerald-500'` | Fondo del círculo de ícono cuando la descarga está completa. |
| `FILE_ICON_BG_DISABLED` | `string` | `'bg-gray-300'` | Fondo del círculo de ícono en estado disabled. |

---

### Time Picker
_Afecta: `InputTimeStyle`_

| Token | Tipo | Default | Descripción |
| ----- | ---- | ------- | ----------- |
| `TIME_PICKER_WRAPPER` | `string` | `'absolute z-20 left-0 top-full rounded-lg bg-white'` | Contenedor flotante del picker. |
| `TIME_PICKER_CONTAINER` | `string` | `'border border-gray-300 rounded-lg p-3'` | Borde interior del picker. |
| `TIME_PICKER_COLUMN` | `string` | `'h-48 overflow-y-auto border border-gray-200 rounded-md ...'` | Columna scrollable de horas/minutos. |
| `TIME_PICKER_AMPM_COLUMN` | `string` | `'h-48 overflow-y-auto border border-gray-200 rounded-md ...'` | Columna scrollable AM/PM. |
| `TIME_PICKER_COLUMN_LABEL` | `string` | `'block text-xs font-medium text-gray-500 mb-1 text-center'` | Label de cada columna ("Hora", "Minuto", "AM/PM"). |
| `TIME_PICKER_ITEM` | `string` | `'px-3 py-1 text-center cursor-pointer hover:bg-indigo-50 ...'` | Fila base de cada ítem del picker. |
| `timePickerItemClass` | `function(isActive)` | Ver abajo | Clases dinámicas de cada ítem según si está seleccionado. |

------

# 🧩 Componentes

## Props comunes

La mayoría de los componentes comparten las siguientes props:

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String \| Number \| Boolean` | — | Valor del campo (v-model). |
| `label` | `String` | `''` | Texto del label del campo. |
| `description` | `String` | `''` | Texto de ayuda debajo del campo. Cambia de color según el estado de validación. |
| `placeholder` | `String` | `''` | Placeholder del input. |
| `disabled` | `Boolean` | `false` | Deshabilita el campo. |
| `required` | `Boolean` | `false` | Marca el campo como requerido (muestra asterisco `*`). |
| `validate` | `Boolean \| null` | `null` | Estado de validación: `true` = válido, `false` = inválido, `null` = sin estado. |
| `mclass` | `String` | `null` | Clases CSS adicionales aplicadas directamente al elemento del input. |

## Eventos comunes

| Evento | Payload | Descripción |
| ------ | ------- | ----------- |
| `update:modelValue` | `value` | Se dispara al cambiar el valor (permite `v-model`). |
| `change` | `value` | Se dispara al cambiar el valor. |
| `blur` | `event` | Se dispara al perder el foco. |

------

## InputTextStyle

Input de texto genérico. Soporta tipos `text`, `email`, `search`, `tel`, `url` y `number`.

```vue
<InputTextStyle
    v-model="nombre"
    label="Nombre"
    placeholder="Escribe tu nombre"
    :required="true"
    :validate="validate"
    description="Campo requerido"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String \| Number` | `''` | Valor del campo. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `placeholder` | `String` | `''` | |
| `type` | `String` | `'text'` | Tipo del input: `text`, `email`, `search`, `tel`, `url`, `number`. |
| `maxlength` | `String` | `null` | Longitud máxima de caracteres. |
| `minlength` | `String` | `null` | Longitud mínima de caracteres. |
| `min` | `String` | `null` | Valor mínimo (solo cuando `type="number"`, aplicado al perder el foco). |
| `max` | `String` | `null` | Valor máximo (solo cuando `type="number"`, aplicado al perder el foco). |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

------

## InputNumberStyle

Input numérico con control preciso de dígitos, decimales, rango y formato de visualización. El valor se emite siempre como string numérico limpio (ej: `"1234.56"`). Al perder el foco se formatea con `toLocaleString`.

```vue
<InputNumberStyle
    v-model="monto"
    label="Monto"
    numeric="10,2"
    min="0"
    max="9999999999"
    :required="true"
    :validate="validate"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String \| Number` | `''` | Valor del campo. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `placeholder` | `String` | `''` | |
| `numeric` | `String` | `null` | Formato tipo SQL: `"10,2"` → 10 dígitos enteros, 2 decimales. Si se omite, usa `10,2` por defecto. |
| `min` | `String` | `null` | Valor mínimo (aplicado al perder el foco). Si es `null` o negativo, permite negativos. |
| `max` | `String` | `null` | Valor máximo (aplicado al perder el foco). |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

------

## InputPasswordStyle

Input de contraseña con toggle para mostrar/ocultar el texto.

```vue
<InputPasswordStyle
    v-model="password"
    label="Contraseña"
    placeholder="Mínimo 8 caracteres"
    :required="true"
    :validate="validate"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String \| Number` | `''` | Valor del campo. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `placeholder` | `String` | `''` | |
| `maxlength` | `String` | `null` | Longitud máxima. |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

------

## TextAreaStyle

Área de texto multi-línea.

```vue
<TextAreaStyle
    v-model="comentario"
    label="Comentario"
    placeholder="Escribe aquí..."
    row="5"
    :maxlength="500"
    :validate="validate"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String` | `''` | Valor del campo. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `placeholder` | `String` | `''` | |
| `row` | `String` | `'3'` | Número de filas visibles del textarea. |
| `maxlength` | `String` | `null` | Longitud máxima de caracteres. |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

------

## SelectStyle

Select personalizado con dropdown, navegación por teclado y búsqueda por tipeo. El dropdown se posiciona automáticamente arriba o abajo según el espacio disponible.

```vue
<SelectStyle
    v-model="pais"
    label="País"
    :list="paises"
    return-value="value"
    :required="true"
    :validate="validate"
/>
```

```js
// Formato requerido del array `list`
const paises = [
    { value: 1, text: 'México' },
    { value: 2, text: 'España' },
    { value: 3, text: 'Argentina' },
]
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String \| Number \| Object` | — | Valor seleccionado. |
| `list` | `Array` | `[]` | Array de opciones con formato `{ value, text }`. |
| `returnValue` | `String` | `'value'` | Qué se emite al seleccionar: `'value'` (item.value), `'text'` (item.text) u `'object'` (el item completo). |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

**Navegación por teclado:**
- `↑` / `↓` — navegar opciones
- `Enter` — seleccionar la opción resaltada
- `Escape` — cerrar el dropdown
- Tipear letras — búsqueda inmediata en la lista

------

## InputCheckStyle

Checkbox estilizado con label y descripción.

```vue
<InputCheckStyle
    v-model="aceptaTerminos"
    label="Acepto los términos y condiciones"
    :required="true"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `Boolean` | `false` | Estado del checkbox. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | Clases adicionales del cuadro del checkbox. |

------

## InputRadioStyle

Radio button estilizado con label y descripción.

```vue
<InputRadioStyle
    v-model="opcionA"
    label="Opción A"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `Boolean` | `false` | Estado del radio. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | Clases adicionales del círculo del radio. |

------

## InputSwitchStyle

Toggle switch estilizado.

```vue
<InputSwitchStyle
    v-model="activo"
    label="Activar notificaciones"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `Boolean` | `false` | Estado del switch. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | Clases adicionales de la pista del switch. |

------

## InputDateStyle

Selector de fecha con máscara `dd/mm/aaaa`, navegación por teclado entre secciones y calendario visual (v-calendar). El valor se emite en formato `YYYY-MM-DD`.

```vue
<InputDateStyle
    v-model="fechaNacimiento"
    label="Fecha de nacimiento"
    min="1900-01-01"
    max="2100-12-31"
    :validate="validate"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String \| Date` | — | Valor de la fecha en formato `YYYY-MM-DD` o `Date`. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `min` | `String` | `'1900-01-01'` | Fecha mínima seleccionable en el calendario (formato `YYYY-MM-DD`). |
| `max` | `String` | `'2100-12-31'` | Fecha máxima seleccionable en el calendario (formato `YYYY-MM-DD`). |
| `disabledDates` | `Array \| Object` | `[]` | Fechas deshabilitadas en el calendario. Acepta el formato de `v-calendar`. |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

**Valor emitido:** `'YYYY-MM-DD'` (string) o `''` si la fecha es inválida o está vacía.

**Navegación por teclado:**
- Escribe los dígitos y el foco avanza automáticamente entre día → mes → año
- `←` / `→` — mover entre secciones
- `Backspace` / `Delete` — limpiar la sección activa
- Clic en el ícono 📅 — abre/cierra el calendario visual

------

## InputTimeStyle

Selector de hora con picker de columnas scrollables. Soporta formato 12h (AM/PM) o 24h. El valor se emite en formato `HH:mm`.

```vue
<InputTimeStyle
    v-model="horaInicio"
    label="Hora de inicio"
    min="08:00"
    max="18:00"
    :is24h="true"
    :validate="validate"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `String` | — | Valor de la hora en formato `HH:mm`. |
| `label` | `String` | `''` | |
| `description` | `String` | `''` | |
| `min` | `String` | `null` | Hora mínima (ej: `'08:00'`). |
| `max` | `String` | `null` | Hora máxima (ej: `'18:00'`). |
| `is24h` | `Boolean` | `false` | `true` = formato 24h, `false` = formato 12h con selector AM/PM. |
| `disabled` | `Boolean` | `false` | |
| `required` | `Boolean` | `false` | |
| `validate` | `Boolean \| null` | `null` | |
| `mclass` | `String` | `null` | |

**Valor emitido:** `'HH:mm'` en formato 24h siempre, independientemente del modo de visualización.

------

## InputFileUploadStyle

Botón para adjuntar archivos con validación de tamaño y formato.

```vue
<InputFileUploadStyle
    v-model="archivo"
    :formats="['pdf', 'docx']"
    max-size-m-b="10"
    :state="uploadState"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `modelValue` | `File` | `null` | Archivo seleccionado. |
| `disabled` | `Boolean` | `false` | Deshabilita el botón. |
| `formats` | `Array` | `[]` | Extensiones permitidas (ej: `['pdf', 'xlsx', 'png']`). Array vacío = todos los formatos. |
| `maxSizeMB` | `String \| Number` | `5` | Tamaño máximo del archivo en MB. |
| `state` | `String` | `'uphold'` | Estado del botón: `'uphold'` (normal) o `'loader'` (cargando, muestra spinner). |
| `delay` | `String` | `'1.5'` | Segundos de bloqueo del botón tras un clic, para evitar doble envío. |

**Eventos:**
- `update:modelValue` — emite el `File` seleccionado, o `null` si el archivo no es válido.
- `change` — igual que `update:modelValue`.

El componente valida automáticamente el tamaño y el formato, mostrando un mensaje de error inline si la validación falla.

------

## InputFileDownloadStyle

Botón para descargar archivos con tres estados visuales.

```vue
<InputFileDownloadStyle
    :state="downloadState"
    :disabled="false"
    @click="descargarArchivo"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `disabled` | `Boolean` | `false` | Deshabilita el botón. |
| `state` | `String` | `'download'` | Estado: `'download'` (normal), `'loader'` (descargando, spinner), `'complete'` (completado, check verde). |
| `delay` | `String` | `'1.5'` | Segundos de bloqueo del botón tras un clic. |

**Eventos:**
- `click` — se dispara al hacer clic (solo si no está deshabilitado).

------

## ButtonStyle

Botón genérico con bloqueo automático anti-doble-clic.

```vue
<ButtonStyle
    class="px-4 py-2 bg-indigo-500 text-white rounded"
    :disabled="cargando"
    @click="guardar"
>
    Guardar
</ButtonStyle>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `type` | `String` | `'button'` | Tipo del botón: `'button'` o `'submit'`. |
| `class` | `String` | `''` | Clases CSS del botón (TailwindCSS). |
| `disabled` | `Boolean` | `false` | Deshabilita el botón. |
| `delay` | `String` | `'1.5'` | Segundos de bloqueo automático tras hacer clic (anti-doble-clic). |

**Eventos:**
- `click` — se dispara al hacer clic (solo si no está deshabilitado ni en periodo de bloqueo).

Acepta contenido mediante `<slot>` (texto, íconos, etc.).

------

## LoaderStyle

Indicador de carga circular animado.

```vue
<LoaderStyle message="Procesando datos..." />
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `message` | `String` | `'Cargando...'` | Texto que se muestra debajo del arco animado. |

El color del arco se controla con el token `LOADER_ARC_STROKE`.

------

## PaginationStyle

Paginador con navegación anterior/siguiente, botones de página y resumen de registros.

```vue
<PaginationStyle
    :total-pages="totalPaginas"
    :actual-page="paginaActual"
    :records="totalRegistros"
    :limit="registrosPorPagina"
    :summary="true"
    @page="cambiarPagina"
/>
```

| Prop | Tipo | Default | Descripción |
| ---- | ---- | ------- | ----------- |
| `totalPages` | `Number` | `0` | Total de páginas. |
| `actualPage` | `Number` | `1` | Página actualmente activa. |
| `records` | `Number` | `0` | Total de registros (para el resumen). |
| `limit` | `Number` | `0` | Registros por página (para calcular el rango del resumen). |
| `summary` | `Boolean` | `false` | `true` = muestra el texto "Mostrando X - Y de Z". |

**Eventos:**
- `page` — emite el número de página a la que se desea navegar.

El paginador muestra hasta 7 botones con elipsis (`...`) inteligentes cuando hay más páginas.

------

## Ejemplo de uso completo

```vue
<template>
    <div class="p-6 space-y-4 max-w-md">

        <InputTextStyle
            v-model="form.nombre"
            label="Nombre completo"
            placeholder="Ej: Juan Pérez"
            :required="true"
            :validate="validaciones.nombre"
        />

        <InputNumberStyle
            v-model="form.monto"
            label="Monto"
            numeric="10,2"
            min="0"
            :validate="validaciones.monto"
        />

        <InputPasswordStyle
            v-model="form.password"
            label="Contraseña"
            :required="true"
            :validate="validaciones.password"
        />

        <SelectStyle
            v-model="form.pais"
            label="País"
            :list="paises"
            return-value="value"
            :validate="validaciones.pais"
        />

        <InputDateStyle
            v-model="form.fecha"
            label="Fecha"
            :validate="validaciones.fecha"
        />

        <InputTimeStyle
            v-model="form.hora"
            label="Hora"
            :is24h="true"
        />

        <InputCheckStyle
            v-model="form.acepta"
            label="Acepto los términos"
            :required="true"
        />

        <ButtonStyle
            class="w-full px-4 py-2 bg-indigo-500 text-white rounded hover:bg-indigo-600"
            @click="enviar"
        >
            Enviar
        </ButtonStyle>

    </div>
</template>

<script setup>
import { ref } from 'vue'
import {
    InputTextStyle, InputNumberStyle, InputPasswordStyle,
    SelectStyle, InputDateStyle, InputTimeStyle,
    InputCheckStyle, ButtonStyle
} from '@luis-angel-martin-dzul/input-styles'

const form = ref({
    nombre: '', monto: '', password: '',
    pais: null, fecha: '', hora: '', acepta: false
})

const validaciones = ref({
    nombre: null, monto: null, password: null,
    pais: null, fecha: null
})

const paises = [
    { value: 1, text: 'México' },
    { value: 2, text: 'España' },
]

function enviar() {
    validaciones.value.nombre = form.value.nombre.length > 0
    // ... resto de validaciones
}
</script>
```