12. Uso de iconos con Vue3 (Heroicons)
Uso de Iconos con Vue3 (Heroicons)
1. Introducción a Heroicons
1.1. ¿Qué son los Heroicons?
Heroicons es una librería de iconos gratuita creada por el equipo de Tailwind CSS.
Características principales:
✅ Diseño profesional - Iconos modernos y limpios
✅ Dos estilos - Outline (contorno) y Solid (relleno)
✅ Integración nativa con Vue.js y React
✅ Optimizados para Tailwind CSS
✅ MIT License - Uso gratuito
1.2. ¿Por qué usar Iconos?
🎯 Mejoran la UX - Comunicación visual más clara
🎨 Apariencia profesional - UI más atractiva
📱 Mejor usabilidad - Acciones identificables rápidamente
🌍 Internacionalización - Menos dependencia del texto
2. Instalación de Heroicons
2.1. Instalar el Paquete
bash
npm install @heroicons/vue@2 --save-dev
Nota: La versión @2 es la última disponible.
2.2. Verificar Instalación
json
// package.json
"devDependencies": {
"@heroicons/vue": "^2.0.0"
}
2.3. Estilos de Iconos
Heroicons ofrece dos estilos:
javascript
// Outline - Contorno (más ligero)
import { EyeIcon } from '@heroicons/vue/24/outline'
// Solid - Relleno (más llamativo)
import { EyeIcon } from '@heroicons/vue/24/solid'
// Mini - Tamaño 20px (para espacios pequeños)
import { EyeIcon } from '@heroicons/vue/20/solid'
3. Uso Básico de Iconos
3.1. Importar y Usar un Icono
En un componente Vue:
vue
<script setup>
import { EyeIcon } from '@heroicons/vue/24/solid'
</script>
<template>
<div>
<EyeIcon class="w-6 h-6 text-blue-500" />
<span>Ver Chatbot</span>
</div>
</template>
3.2. Tamaños Comunes
vue
<!-- Tamaño pequeño -->
<EyeIcon class="w-4 h-4" />
<!-- Tamaño mediano -->
<EyeIcon class="w-5 h-5" />
<!-- Tamaño grande -->
<EyeIcon class="w-6 h-6" />
<!-- Tamaño personalizado -->
<EyeIcon class="w-8 h-8" />
3.3. Colores con Tailwind
vue
<!-- Color primario -->
<EyeIcon class="w-5 h-5 text-blue-600" />
<!-- Color con hover -->
<EyeIcon class="w-5 h-5 text-gray-500 hover:text-blue-600" />
<!-- Color en modo oscuro -->
<EyeIcon class="w-5 h-5 text-gray-500 dark:text-gray-400" />
4. Ejemplo: ChatbotListItem con Iconos
4.1. Componente Actualizado
resources/js/Components/Chatbots/ChatbotListItem.vue:
vue
<script setup>
import { EyeIcon, PencilIcon, TrashIcon } from '@heroicons/vue/24/solid'
import { PrimaryButton, SecondaryButton, DangerButton } from '@/Components/Buttons'
const emit = defineEmits(['delete'])
defineProps({
chatbot: {
type: Object,
required: true
}
})
</script>
<template>
<div class="flex flex-col md:flex-row md:items-center md:justify-between py-4">
<div class="flex-1">
<div class="text-gray-800 dark:text-gray-200 font-medium">
{{ chatbot.name }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
Creado: {{ dayjs(chatbot.created_at).fromNow() }}
</div>
</div>
<div class="flex space-x-2 mt-4 md:mt-0">
<!-- Ver con icono -->
<PrimaryButton
:href="route('chatbots.show', chatbot.id)"
aria-label="Ver detalles del chatbot"
class="gap-2"
>
<EyeIcon class="w-4 h-4" />
<span>Ver</span>
</PrimaryButton>
<!-- Editar con icono -->
<SecondaryButton
:href="route('chatbots.edit', chatbot.id)"
aria-label="Editar chatbot"
class="gap-2"
>
<PencilIcon class="w-4 h-4" />
<span>Editar</span>
</SecondaryButton>
<!-- Eliminar con icono -->
<DangerButton
aria-label="Eliminar chatbot"
class="gap-2"
@click="$emit('delete', chatbot.id)"
>
<TrashIcon class="w-4 h-4" />
<span>Eliminar</span>
</DangerButton>
</div>
</div>
</template>
5. Configuración de Espaciado
5.1. Gap en el Componente Base
Modificar BaseButton.vue:
vue
<script setup>
import { computed } from 'vue'
import { twMerge } from 'tailwind-merge'
import { Link } from '@inertiajs/vue3'
const props = defineProps({
ariaLabel: {
type: String,
required: true
},
defaultClasses: {
type: String,
default: ''
},
class: {
type: String,
default: ''
},
href: {
type: String,
default: ''
}
})
// Combinar clases con gap por defecto
const mergedClasses = computed(() => {
return twMerge(
'inline-flex items-center gap-2', // 👈 Gap por defecto
props.defaultClasses,
props.class
)
})
</script>
<template>
<Link
v-if="href"
:href="href"
:class="mergedClasses"
:aria-label="ariaLabel"
>
<slot />
</Link>
<button
v-else
:class="mergedClasses"
:aria-label="ariaLabel"
>
<slot />
</button>
</template>
5.2. Prioridad en tailwind-merge
Orden de las clases en twMerge:
javascript
// ✅ Las clases a la derecha tienen mayor prioridad
twMerge(
'gap-2', // Baja prioridad
'gap-4' // Alta prioridad - Sobrescribe
)
// Resultado: 'gap-4'
// ✅ Las clases más específicas deben ir a la derecha
twMerge(
'p-4', // Baja prioridad
'p-6' // Alta prioridad
)
// Resultado: 'p-6'
// ❌ Las clases a la izquierda pueden ser sobrescritas
twMerge(
'text-red-500', // Se sobrescribe
'text-blue-500' // Prevalece
)
// Resultado: 'text-blue-500'
5.3. Uso con Diferentes Gaps
vue
<!-- Gap por defecto (2) -->
<PrimaryButton>Ver</PrimaryButton>
<!-- Gap más amplio -->
<PrimaryButton class="gap-4">
<EyeIcon class="w-4 h-4" />
<span>Ver</span>
</PrimaryButton>
<!-- Sin gap -->
<PrimaryButton class="gap-0">
<EyeIcon class="w-4 h-4" />
</PrimaryButton>
6. Ejemplos de Iconos por Categoría
6.1. Iconos de Acción
vue
<script setup>
import {
PlusIcon, // Crear
PencilIcon, // Editar
EyeIcon, // Ver
TrashIcon, // Eliminar
CheckIcon, // Aceptar/Confirmar
XMarkIcon, // Cancelar/Cerrar
ArrowPathIcon, // Recargar
ArrowLeftIcon, // Volver
ArrowRightIcon, // Siguiente
} from '@heroicons/vue/24/solid'
</script>
6.2. Iconos de Navegación
vue
<script setup>
import {
HomeIcon, // Inicio
Cog8ToothIcon, // Configuración
UserIcon, // Usuario
UsersIcon, // Usuarios
EnvelopeIcon, // Mensajes
BellIcon, // Notificaciones
MagnifyingGlassIcon, // Búsqueda
} from '@heroicons/vue/24/solid'
</script>
6.3. Iconos de Estado
vue
<script setup>
import {
CheckCircleIcon, // Éxito
ExclamationCircleIcon, // Advertencia
XCircleIcon, // Error
InformationCircleIcon, // Información
ClockIcon, // Tiempo/Pendiente
StarIcon, // Favorito
} from '@heroicons/vue/24/solid'
</script>
7. Componente de Icono Personalizado
7.1. IconWrapper para Reutilización
Crear resources/js/Components/IconWrapper.vue:
vue
<script setup>
import { computed } from 'vue'
import { twMerge } from 'tailwind-merge'
const props = defineProps({
icon: {
type: Object,
required: true
},
size: {
type: String,
default: '5'
},
color: {
type: String,
default: 'text-gray-500'
},
class: {
type: String,
default: ''
}
})
const sizeClasses = {
'3': 'w-3 h-3',
'4': 'w-4 h-4',
'5': 'w-5 h-5',
'6': 'w-6 h-6',
'8': 'w-8 h-8',
'10': 'w-10 h-10',
'12': 'w-12 h-12',
}
const iconClasses = computed(() => {
return twMerge(
sizeClasses[props.size] || 'w-5 h-5',
props.color,
props.class
)
})
</script>
<template>
<component
:is="icon"
:class="iconClasses"
aria-hidden="true"
/>
</template>
7.2. Uso del IconWrapper
vue
<script setup>
import IconWrapper from '@/Components/IconWrapper.vue'
import { EyeIcon, PencilIcon, TrashIcon } from '@heroicons/vue/24/solid'
</script>
<template>
<div class="flex gap-2">
<!-- Icono con tamaño personalizado -->
<IconWrapper :icon="EyeIcon" size="4" />
<!-- Icono con color personalizado -->
<IconWrapper
:icon="PencilIcon"
size="5"
color="text-blue-600"
/>
<!-- Icono con clases adicionales -->
<IconWrapper
:icon="TrashIcon"
size="6"
class="text-red-500 hover:text-red-700 transition-colors"
/>
</div>
</template>
8. Botones con Solo Icono
8.1. IconButton Component
Crear resources/js/Components/IconButton.vue:
vue
<script setup>
import { computed } from 'vue'
import BaseButton from './BaseButton.vue'
import { primaryClasses, secondaryClasses, dangerClasses } from '@/utils/buttonClasses'
import { twMerge } from 'tailwind-merge'
const props = defineProps({
icon: {
type: Object,
required: true
},
variant: {
type: String,
default: 'primary'
},
size: {
type: String,
default: 'md'
},
ariaLabel: {
type: String,
required: true
},
href: {
type: String,
default: ''
},
class: {
type: String,
default: ''
}
})
const variants = {
primary: primaryClasses,
secondary: secondaryClasses,
danger: dangerClasses,
}
const sizeClasses = {
sm: 'p-1.5',
md: 'p-2',
lg: 'p-2.5',
}
const iconSizes = {
sm: 'w-3.5 h-3.5',
md: 'w-4 h-4',
lg: 'w-5 h-5',
}
const defaultClasses = computed(() => {
return twMerge(
variants[props.variant],
sizeClasses[props.size],
props.class
)
})
</script>
<template>
<BaseButton
:default-classes="defaultClasses"
:aria-label="ariaLabel"
:href="href"
>
<component
:is="icon"
:class="iconSizes[size]"
aria-hidden="true"
/>
</BaseButton>
</template>
<!-- Uso -->
<IconButton
:icon="EyeIcon"
variant="primary"
size="md"
aria-label="Ver chatbot"
:href="route('chatbots.show', chatbot.id)"
/>
9. Lista de Iconos Más Usados
9.1. Iconos de Acción
9.2. Iconos de Navegación
9.3. Iconos de Estado
10. Buenas Prácticas
10.1. Accesibilidad
vue
<!-- ✅ Bueno: aria-hidden para iconos decorativos -->
<EyeIcon class="w-5 h-5" aria-hidden="true" />
<!-- ✅ Bueno: aria-label cuando no hay texto -->
<button aria-label="Ver detalles">
<EyeIcon class="w-5 h-5" aria-hidden="true" />
</button>
<!-- ✅ Bueno: texto con icono -->
<button>
<EyeIcon class="w-5 h-5" aria-hidden="true" />
<span>Ver</span>
</button>
10.2. Tamaños Consistentes
javascript
// ✅ Usar tamaños consistentes
const sizes = {
sm: 'w-4 h-4', // Iconos pequeños
md: 'w-5 h-5', // Iconos medianos
lg: 'w-6 h-6', // Iconos grandes
xl: 'w-8 h-8', // Iconos extra grandes
}
10.3. Rendimiento
javascript
// ✅ Mejor: Importar solo lo que usas
import { EyeIcon, PencilIcon } from '@heroicons/vue/24/solid'
// ❌ Peor: Importar todo
import * as Icons from '@heroicons/vue/24/solid'
11. Resumen
11.1. Instalación
bash
npm install @heroicons/vue@2 --save-dev
11.2. Uso Básico
vue
<script setup>
import { EyeIcon } from '@heroicons/vue/24/solid'
</script>
<template>
<EyeIcon class="w-5 h-5 text-blue-600" />
</template>
11.3. Componentes Creados
11.4. Beneficios
✅ Interfaz más visual y profesional
✅ Acciones identificables rápidamente
✅ Menos dependencia del texto
✅ Biblioteca extensa de iconos
✅ Integración perfecta con Tailwind
¡Los iconos mejoran significativamente la experiencia de usuario!
Comentarios
Publicar un comentario