All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 16s
- Cambiar permisos de 600 (rw-------) a 644 (rw-r--r--) - Esto permite que el build de Docker lea los archivos correctamente - Agregar documentación completa de UTabs y auto-import en nuxt-ui-tips.md
379 lines
8.8 KiB
Markdown
379 lines
8.8 KiB
Markdown
# Nuxt UI v4 - Tips y Cambios de API
|
|
|
|
Este documento contiene los cambios más comunes al migrar o trabajar con Nuxt UI v4, basado en la experiencia práctica del proyecto.
|
|
|
|
## UModal - Componente Modal
|
|
|
|
### Cambios de API en v4
|
|
|
|
#### 1. v-model actualizado a v-model:open
|
|
|
|
**❌ Incorrecto (API antigua):**
|
|
```vue
|
|
<UModal v-model="showModal">
|
|
<!-- contenido -->
|
|
</UModal>
|
|
```
|
|
|
|
**✅ Correcto (Nuxt UI v4):**
|
|
```vue
|
|
<UModal v-model:open="showModal">
|
|
<!-- contenido -->
|
|
</UModal>
|
|
```
|
|
|
|
#### 2. Slot #content requerido
|
|
|
|
En v4, el contenido debe ir dentro del slot `#content`.
|
|
|
|
**❌ Incorrecto:**
|
|
```vue
|
|
<UModal v-model:open="showModal">
|
|
<MiComponente />
|
|
</UModal>
|
|
```
|
|
|
|
**✅ Correcto:**
|
|
```vue
|
|
<UModal v-model:open="showModal">
|
|
<template #content>
|
|
<MiComponente />
|
|
</template>
|
|
</UModal>
|
|
```
|
|
|
|
#### 3. Prop :ui para personalización
|
|
|
|
La prop `:ui` no tiene un slot `width`. Para personalizar el ancho, debes usar el slot `content`.
|
|
|
|
**❌ Incorrecto:**
|
|
```vue
|
|
<UModal
|
|
v-model:open="showModal"
|
|
:ui="{ width: 'max-w-4xl' }"
|
|
>
|
|
<!-- contenido -->
|
|
</UModal>
|
|
```
|
|
|
|
**✅ Correcto:**
|
|
```vue
|
|
<UModal
|
|
v-model:open="showModal"
|
|
:ui="{ content: 'w-[calc(100vw-2rem)] max-w-4xl rounded-lg shadow-lg ring ring-default' }"
|
|
>
|
|
<template #content>
|
|
<!-- contenido -->
|
|
</template>
|
|
</UModal>
|
|
```
|
|
|
|
**Nota:** Las clases del tema por defecto son:
|
|
- `w-[calc(100vw-2rem)]` - ancho responsive
|
|
- `rounded-lg` - bordes redondeados
|
|
- `shadow-lg` - sombra
|
|
- `ring ring-default` - borde/anillo
|
|
|
|
Luego agregas tu clase de ancho máximo personalizado: `max-w-3xl`, `max-w-4xl`, etc.
|
|
|
|
### Slots disponibles en UModal
|
|
|
|
Según la documentación oficial de Nuxt UI v4:
|
|
|
|
```typescript
|
|
interface ModalSlots {
|
|
default(): any; // Elemento trigger (opcional)
|
|
content(): any; // Contenido completo del modal
|
|
header(): any; // Solo el header
|
|
title(): any; // Solo el título
|
|
description(): any; // Solo la descripción
|
|
actions(): any; // Acciones del header
|
|
close(): any; // Botón de cerrar
|
|
body(): any; // Solo el cuerpo
|
|
footer(): any; // Solo el footer
|
|
}
|
|
```
|
|
|
|
### Props importantes
|
|
|
|
```typescript
|
|
interface ModalProps {
|
|
open?: boolean; // Estado del modal (usar con v-model:open)
|
|
defaultOpen?: boolean; // Estado inicial
|
|
modal?: boolean; // Bloquea interacción fuera (default: true)
|
|
overlay?: boolean; // Mostrar overlay (default: true)
|
|
dismissible?: boolean; // Cerrar al hacer clic fuera (default: true)
|
|
transition?: boolean; // Animación (default: true)
|
|
fullscreen?: boolean; // Pantalla completa
|
|
scrollable?: boolean; // Contenido scrollable (v4.2+)
|
|
title?: string; // Título del modal
|
|
description?: string; // Descripción del modal
|
|
close?: boolean | Partial<ButtonProps>; // Customizar botón cerrar
|
|
}
|
|
```
|
|
|
|
### Ejemplo completo
|
|
|
|
```vue
|
|
<template>
|
|
<!-- Modal básico con título y descripción -->
|
|
<UModal
|
|
v-model:open="showBasicModal"
|
|
title="Crear Nuevo Lote"
|
|
description="Complete el formulario para crear un lote"
|
|
>
|
|
<template #body>
|
|
<LoteForm @submit="handleSubmit" />
|
|
</template>
|
|
|
|
<template #footer>
|
|
<UButton @click="showBasicModal = false" variant="outline">
|
|
Cancelar
|
|
</UButton>
|
|
<UButton @click="handleSubmit">
|
|
Guardar
|
|
</UButton>
|
|
</template>
|
|
</UModal>
|
|
|
|
<!-- Modal con contenido personalizado y ancho custom -->
|
|
<UModal
|
|
v-model:open="showCustomModal"
|
|
:ui="{ content: 'w-[calc(100vw-2rem)] max-w-4xl rounded-lg shadow-lg ring ring-default' }"
|
|
>
|
|
<template #content>
|
|
<MiComponenteCustom />
|
|
</template>
|
|
</UModal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const showBasicModal = ref(false)
|
|
const showCustomModal = ref(false)
|
|
|
|
const handleSubmit = () => {
|
|
// lógica
|
|
showBasicModal.value = false
|
|
}
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## Resumen de Cambios Comunes
|
|
|
|
| Componente | Cambio | Desde | Hacia |
|
|
|------------|--------|-------|-------|
|
|
| UModal | v-model | `v-model="show"` | `v-model:open="show"` |
|
|
| UModal | Contenido | Directo | `<template #content>` |
|
|
| UModal | Ancho custom | `:ui="{ width: 'max-w-4xl' }"` | `:ui="{ content: 'w-[calc(100vw-2rem)] max-w-4xl ...' }"` |
|
|
|
|
---
|
|
|
|
## UTabs - Componente de Pestañas
|
|
|
|
### Cambios de API en v4
|
|
|
|
#### 1. v-model usa valores string
|
|
|
|
En Nuxt UI v4, los valores de los tabs deben ser strings, no números.
|
|
|
|
**❌ Incorrecto:**
|
|
```vue
|
|
<script setup>
|
|
const selectedTab = ref(0) // ❌ Número
|
|
const tabs = [
|
|
{ label: 'Lotes', icon: 'i-heroicons-cube', slot: 'lotes' },
|
|
{ label: 'Operaciones', icon: 'i-heroicons-beaker', slot: 'operaciones' }
|
|
]
|
|
</script>
|
|
|
|
<template>
|
|
<UTabs v-model="selectedTab" :items="tabs">
|
|
<template #lotes>
|
|
<!-- contenido -->
|
|
</template>
|
|
</UTabs>
|
|
</template>
|
|
```
|
|
|
|
**✅ Correcto:**
|
|
```vue
|
|
<script setup>
|
|
const selectedTab = ref('lotes') // ✅ String
|
|
const tabs = [
|
|
{ label: 'Lotes', icon: 'i-heroicons-cube', slot: 'lotes', value: 'lotes' },
|
|
{ label: 'Operaciones', icon: 'i-heroicons-beaker', slot: 'operaciones', value: 'operaciones' }
|
|
]
|
|
</script>
|
|
|
|
<template>
|
|
<UTabs v-model="selectedTab" :items="tabs">
|
|
<template #lotes>
|
|
<!-- contenido -->
|
|
</template>
|
|
</UTabs>
|
|
</template>
|
|
```
|
|
|
|
#### 2. Propiedad `value` en items
|
|
|
|
Cada item debe tener una propiedad `value` que coincida con el nombre del slot. Si no se especifica, Nuxt UI usa el índice como string ("0", "1", etc.).
|
|
|
|
**Props de TabsItem:**
|
|
```typescript
|
|
interface TabsItem {
|
|
label?: string
|
|
icon?: string
|
|
avatar?: AvatarProps
|
|
badge?: string | number | BadgeProps
|
|
content?: string
|
|
value?: string | number // ⚠️ Importante: debe coincidir con el nombre del slot
|
|
disabled?: boolean
|
|
slot?: string // Nombre del slot personalizado
|
|
class?: any
|
|
ui?: { ... }
|
|
}
|
|
```
|
|
|
|
#### 3. Uso de slots con nombres personalizados
|
|
|
|
Usa la propiedad `slot` en el item para definir un slot personalizado:
|
|
|
|
```vue
|
|
<script setup>
|
|
const tabs = [
|
|
{ label: 'Cuenta', slot: 'account', value: 'account' },
|
|
{ label: 'Contraseña', slot: 'password', value: 'password' }
|
|
]
|
|
</script>
|
|
|
|
<template>
|
|
<UTabs v-model="selectedTab" :items="tabs">
|
|
<template #account>
|
|
<!-- Contenido del tab de cuenta -->
|
|
</template>
|
|
|
|
<template #password>
|
|
<!-- Contenido del tab de contraseña -->
|
|
</template>
|
|
</UTabs>
|
|
</template>
|
|
```
|
|
|
|
### Ejemplo completo
|
|
|
|
```vue
|
|
<script setup lang="ts">
|
|
import type { TabsItem } from '@nuxt/ui'
|
|
|
|
const selectedTab = ref('lotes')
|
|
|
|
const tabs: TabsItem[] = [
|
|
{
|
|
label: 'Lotes',
|
|
icon: 'i-heroicons-cube',
|
|
slot: 'lotes',
|
|
value: 'lotes'
|
|
},
|
|
{
|
|
label: 'Operaciones',
|
|
icon: 'i-heroicons-beaker',
|
|
slot: 'operaciones',
|
|
value: 'operaciones'
|
|
}
|
|
]
|
|
</script>
|
|
|
|
<template>
|
|
<UTabs v-model="selectedTab" :items="tabs" class="mb-6">
|
|
<template #lotes>
|
|
<div class="py-4">
|
|
<!-- Contenido del tab de lotes -->
|
|
</div>
|
|
</template>
|
|
|
|
<template #operaciones>
|
|
<div class="py-4">
|
|
<!-- Contenido del tab de operaciones -->
|
|
</div>
|
|
</template>
|
|
</UTabs>
|
|
</template>
|
|
```
|
|
|
|
---
|
|
|
|
## Auto-import de Componentes en Nuxt
|
|
|
|
### Convención de nombres para subdirectorios
|
|
|
|
Cuando organizas componentes en subdirectorios dentro de `app/components/`, Nuxt genera automáticamente nombres prefijados:
|
|
|
|
**Estructura de archivos:**
|
|
```
|
|
app/components/
|
|
├── lotes/
|
|
│ ├── LotesTable.vue
|
|
│ ├── LoteForm.vue
|
|
│ └── LoteCard.vue
|
|
└── operaciones/
|
|
├── OperacionesTable.vue
|
|
└── OperacionForm.vue
|
|
```
|
|
|
|
**Nombres de componentes generados:**
|
|
```vue
|
|
<!-- ❌ INCORRECTO -->
|
|
<LotesTable /> <!-- No funciona -->
|
|
<LoteForm /> <!-- No funciona -->
|
|
|
|
<!-- ✅ CORRECTO -->
|
|
<LotesLotesTable /> <!-- app/components/lotes/LotesTable.vue -->
|
|
<LotesLoteForm /> <!-- app/components/lotes/LoteForm.vue -->
|
|
<LotesLoteCard /> <!-- app/components/lotes/LoteCard.vue -->
|
|
<OperacionesOperacionesTable /> <!-- app/components/operaciones/OperacionesTable.vue -->
|
|
<OperacionesOperacionForm /> <!-- app/components/operaciones/OperacionForm.vue -->
|
|
```
|
|
|
|
**Patrón:**
|
|
```
|
|
<DirectorioNombreArchivo />
|
|
```
|
|
|
|
### NO se requieren imports explícitos
|
|
|
|
Nuxt hace auto-import automáticamente. **NO** necesitas importar componentes manualmente:
|
|
|
|
**❌ Incorrecto:**
|
|
```vue
|
|
<script setup>
|
|
import LotesTable from '~/app/components/lotes/LotesTable.vue' // ❌ Innecesario
|
|
</script>
|
|
|
|
<template>
|
|
<LotesLotesTable />
|
|
</template>
|
|
```
|
|
|
|
**✅ Correcto:**
|
|
```vue
|
|
<script setup>
|
|
// No se requiere import
|
|
</script>
|
|
|
|
<template>
|
|
<LotesLotesTable /> <!-- Auto-import automático -->
|
|
</template>
|
|
```
|
|
|
|
### Verificación con analiticaNucleo
|
|
|
|
Verificado en el proyecto analiticaNucleo que funciona correctamente:
|
|
- `app/components/comercios/TablaComerciosResumen.vue` → `<ComerciosTablaComerciosResumen />`
|
|
- `app/components/comercios/TotalesMonetariosComercio.vue` → `<ComerciosTotalesMonetariosComercio />`
|
|
|
|
---
|
|
|
|
**Última actualización:** 2025-11-21
|