Files
seguidorDeLotes/nuxt-ui-tips.md
josedario87 af8dc49209
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 16s
Fix: Corregir permisos de archivos de componentes (644)
- 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
2025-11-21 21:10:35 -06:00

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