- 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
8.8 KiB
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):
<UModal v-model="showModal">
<!-- contenido -->
</UModal>
✅ Correcto (Nuxt UI v4):
<UModal v-model:open="showModal">
<!-- contenido -->
</UModal>
2. Slot #content requerido
En v4, el contenido debe ir dentro del slot #content.
❌ Incorrecto:
<UModal v-model:open="showModal">
<MiComponente />
</UModal>
✅ Correcto:
<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:
<UModal
v-model:open="showModal"
:ui="{ width: 'max-w-4xl' }"
>
<!-- contenido -->
</UModal>
✅ Correcto:
<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 responsiverounded-lg- bordes redondeadosshadow-lg- sombraring 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:
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
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
<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:
<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:
<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:
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:
<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
<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:
<!-- ❌ 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:
<script setup>
import LotesTable from '~/app/components/lotes/LotesTable.vue' // ❌ Innecesario
</script>
<template>
<LotesLotesTable />
</template>
✅ Correcto:
<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