14: Explora más componentes y agrega contenido a tu portafolio con Laravel
Introducción
En este tutorial vamos a explorar los componentes adicionales que Laravel Jetstream nos proporciona y los usaremos para agregar contenido y funcionalidad a nuestro portafolio. Aprenderemos a utilizar el componente Button, mejoraremos la presentación de nuestras secciones y añadiremos elementos interactivos que harán más atractiva nuestra página.
Paso 1: Explorar los componentes de Jetstream
Laravel Jetstream viene con una serie de componentes Vue predefinidos que podemos reutilizar. Vamos a revisar los más importantes:
1.1 Componentes disponibles en Jetstream
resources/js/Components/
├── ApplicationLogo.vue # Logo de la aplicación
├── AuthenticationCard.vue # Tarjeta para autenticación
├── Button.vue # Botón estilizado
├── Checkbox.vue # Checkbox personalizado
├── DangerButton.vue # Botón de peligro/eliminar
├── Dropdown.vue # Menú desplegable
├── DropdownLink.vue # Enlace dentro de dropdown
├── Input.vue # Campo de entrada
├── InputError.vue # Mensaje de error
├── Label.vue # Etiqueta de formulario
├── Modal.vue # Ventana modal
├── NavLink.vue # Enlace de navegación
├── ResponsiveNavLink.vue # Enlace responsive
├── SecondaryButton.vue # Botón secundario
├── SectionBorder.vue # Borde de sección
├── ValidationErrors.vue # Errores de validación
└── Welcome.vue # Página de bienvenidaPaso 2: Usar el componente Button
El componente Button de Jetstream ya tiene estilos predefinidos, pero podemos personalizarlos fácilmente.
2.1 Importar el componente
<script setup>
import { Head, Link } from '@inertiajs/vue3';
import Button from '@/Components/Button.vue';
// ... otros imports
</script>2.2 Usar el componente con estilos personalizados
<template>
<Button class="bg-green-400 rounded-full font-bold text-sm text-gray-800 hover:bg-green-500 transition-colors">
Let's Chat
</Button>
</template>2.3 Diferentes variantes de botones
Jetstream también proporciona otros tipos de botones:
<template>
<!-- Botón primario (indigo) -->
<Button>Guardar</Button>
<!-- Botón secundario (blanco con borde) -->
<SecondaryButton>Cancelar</SecondaryButton>
<!-- Botón de peligro (rojo) -->
<DangerButton @click="delete">Eliminar</DangerButton>
<!-- Botón personalizado -->
<Button class="bg-purple-500 hover:bg-purple-700 text-white">
Ver Proyectos
</Button>
</template>
<script setup>
import Button from '@/Components/Button.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import DangerButton from '@/Components/DangerButton.vue';
</script>Paso 3: Mejorar el Header con Call to Action
Vamos a mejorar la sección de cabecera agregando un llamado a la acción (CTA) atractivo.
3.1 Estructura del Header mejorado
<template>
<Section background="gray-800" min-height>
<div class="flex flex-col justify-between h-full py-8">
<!-- Contenido principal -->
<div>
<h1 class="border-b-2 border-gray-400 font-bold text-2xl uppercase text-gray-300 pb-3">
Hola, soy {{ user?.name || 'Juan' }}
</h1>
<p class="text-xl text-gray-400 mt-4">
Soy un Software Engineer y me encantaría trabajar contigo
</p>
</div>
<!-- Llamado a la acción -->
<div class="flex justify-end items-center border-b border-gray-500 pb-4">
<p class="font-bold text-gray-300 text-xl mr-5">
¿Quieres conocer más? ¡Hablemos!
</p>
<Button
class="bg-green-400 rounded-full font-bold text-sm text-gray-800 px-6 py-3 hover:bg-green-500 transition-colors"
>
Let's Chat
</Button>
</div>
<!-- Flecha hacia abajo -->
<div class="animate-bounce text-gray-300 text-center text-9xl mt-3">
<a href="#skills">⇣</a>
</div>
</div>
</Section>
</template>
<script setup>
import { usePage } from '@inertiajs/vue3';
import Section from '@/Components/Section.vue';
import Button from '@/Components/Button.vue';
const page = usePage();
const user = page.props.auth.user;
</script>Paso 4: Agregar CTA en la sección de Skills
Vamos a añadir un botón de "Contactar" en la sección de habilidades para que los visitantes puedan contactarnos fácilmente.
<template>
<Section background="gray-100" text="gray-800" id="skills">
<h2 class="text-4xl font-bold mt-8">
Mis Habilidades
</h2>
<!-- Grid de habilidades -->
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 mt-8">
<div v-for="skill in skills" :key="skill.id"
class="p-4 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
<div class="flex items-center justify-between">
<span class="font-medium">{{ skill.name }}</span>
<span class="text-sm text-gray-500">{{ skill.level }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 mt-2">
<div class="h-2 rounded-full transition-all duration-500"
:class="`bg-${skill.color}-500`"
:style="{ width: `${skill.level}%` }">
</div>
</div>
</div>
</div>
<!-- Call to Action -->
<div class="flex justify-center mt-10">
<Button
class="bg-indigo-500 hover:bg-indigo-700 text-white px-8 py-3 rounded-full font-bold transition-colors"
>
Get in Touch
</Button>
</div>
</Section>
</template>
<script setup>
import Section from '@/Components/Section.vue';
import Button from '@/Components/Button.vue';
defineProps({
skills: Array
});
</script>Paso 5: Agregar CTA en la sección de Proyectos
De manera similar, añadimos un botón de "Saber más" en la sección de proyectos.
<template>
<Section background="gray-800" text="white" id="projects">
<h2 class="text-4xl font-bold mt-8">
Proyectos Destacados
</h2>
<!-- Grid de proyectos -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-8">
<div v-for="project in projects" :key="project.id"
class="bg-white rounded-xl overflow-hidden shadow-lg hover:shadow-2xl transition-shadow">
<img :src="project.image_url" :alt="project.title" class="w-full h-48 object-cover">
<div class="p-6">
<h3 class="text-xl font-bold text-gray-800 mb-2">
{{ project.title }}
</h3>
<p class="text-gray-600 mb-4">{{ project.description }}</p>
<div class="flex flex-wrap gap-2 mb-4">
<span v-for="tech in project.technologies" :key="tech"
class="px-3 py-1 bg-gray-200 rounded-full text-sm text-gray-700">
{{ tech }}
</span>
</div>
<Link :href="route('projects.show', project.slug)"
class="inline-block px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
Ver más
</Link>
</div>
</div>
</div>
<!-- Call to Action -->
<div class="flex justify-center mt-10">
<Button
class="bg-purple-100 hover:bg-purple-800 text-gray-800 hover:text-white px-8 py-3 rounded-full font-bold transition-all duration-300"
>
Saber más al respecto
</Button>
</div>
</Section>
</template>
<script setup>
import { Link } from '@inertiajs/vue3';
import Section from '@/Components/Section.vue';
import Button from '@/Components/Button.vue';
defineProps({
projects: Array
});
</script>Paso 6: Usar el componente Dropdown para el menú de usuario
Cuando el usuario está autenticado, Jetstream proporciona un menú desplegable con opciones de perfil.
<template>
<div class="flex items-center space-x-4">
<!-- Botón de perfil con dropdown -->
<Dropdown align="right" width="48">
<template #trigger>
<button class="flex items-center text-sm text-gray-300 hover:text-white">
<img :src="user.profile_photo_url" :alt="user.name" class="w-8 h-8 rounded-full mr-2">
{{ user.name }}
<svg class="ml-2 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
</template>
<template #content>
<div class="py-1">
<DropdownLink :href="route('profile.show')">
Mi Perfil
</DropdownLink>
<DropdownLink :href="route('dashboard')">
Dashboard
</DropdownLink>
<DropdownLink :href="route('logout')" method="post" as="button">
Cerrar Sesión
</DropdownLink>
</div>
</template>
</Dropdown>
</div>
</template>
<script setup>
import Dropdown from '@/Components/Dropdown.vue';
import DropdownLink from '@/Components/DropdownLink.vue';
defineProps({
user: Object
});
</script>Paso 7: Usar el componente Modal para mostrar información
Los modales son útiles para mostrar información adicional sin navegar a otra página.
<template>
<div>
<!-- Botón para abrir modal -->
<Button @click="openModal = true">
Ver detalles
</Button>
<!-- Modal -->
<Modal :show="openModal" @close="openModal = false">
<div class="p-6">
<h2 class="text-2xl font-bold mb-4">
Detalles del Proyecto
</h2>
<p class="text-gray-600">
{{ projectContent }}
</p>
<div class="mt-6 flex justify-end">
<Button @click="openModal = false" class="bg-gray-200 text-gray-800 hover:bg-gray-300">
Cerrar
</Button>
</div>
</div>
</Modal>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Modal from '@/Components/Modal.vue';
import Button from '@/Components/Button.vue';
const openModal = ref(false);
const projectContent = 'Este es el contenido detallado del proyecto...';
</script>Paso 8: Crear un layout reutilizable
Para mantener consistencia en todas las páginas, vamos a crear un layout principal.
8.1 Crear AppLayout.vue
<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white border-b border-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="flex-shrink-0 flex items-center">
<Link :href="route('home')">
<ApplicationLogo class="block h-9 w-auto" />
</Link>
</div>
<!-- Enlaces de navegación -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<NavLink :href="route('home')" :active="route().current('home')">
Inicio
</NavLink>
<NavLink :href="route('projects.index')" :active="route().current('projects.*')">
Proyectos
</NavLink>
<NavLink :href="route('skills.index')" :active="route().current('skills.*')">
Habilidades
</NavLink>
</div>
</div>
<!-- Menú de usuario -->
<div class="flex items-center space-x-4">
<template v-if="$page.props.auth.user">
<Dropdown align="right" width="48">
<template #trigger>
<button class="flex items-center text-sm text-gray-700 hover:text-gray-900">
<img :src="$page.props.auth.user.profile_photo_url"
:alt="$page.props.auth.user.name"
class="w-8 h-8 rounded-full mr-2">
{{ $page.props.auth.user.name }}
</button>
</template>
<template #content>
<DropdownLink :href="route('profile.show')">
Mi Perfil
</DropdownLink>
<DropdownLink :href="route('logout')" method="post" as="button">
Cerrar Sesión
</DropdownLink>
</template>
</Dropdown>
</template>
<template v-else>
<Link :href="route('login')" class="text-gray-700 hover:text-gray-900">
Iniciar Sesión
</Link>
<Link :href="route('register')" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
Registrarse
</Link>
</template>
</div>
</div>
</div>
</nav>
<!-- Contenido de la página -->
<main>
<slot />
</main>
<!-- Footer -->
<footer class="bg-gray-900 text-gray-400 px-12 py-8">
<div class="flex justify-between items-center">
<p>© 2024 {{ $page.props.auth.user?.name || 'Juan' }}. Todos los derechos reservados.</p>
<div class="flex space-x-6">
<a href="https://github.com" target="_blank" class="hover:text-white transition-colors">
GitHub
</a>
<a href="https://twitter.com" target="_blank" class="hover:text-white transition-colors">
Twitter
</a>
<a href="https://linkedin.com" target="_blank" class="hover:text-white transition-colors">
LinkedIn
</a>
</div>
</div>
</footer>
</div>
</template>
<script setup>
import { Link } from '@inertiajs/vue3';
import ApplicationLogo from '@/Components/ApplicationLogo.vue';
import NavLink from '@/Components/NavLink.vue';
import Dropdown from '@/Components/Dropdown.vue';
import DropdownLink from '@/Components/DropdownLink.vue';
</script>8.2 Usar el layout en las páginas
<template>
<AppLayout>
<Head title="Inicio" />
<!-- Contenido de la página -->
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
<h1 class="text-3xl font-bold">Bienvenido</h1>
<p class="mt-4 text-gray-600">Este es el contenido de la página.</p>
</div>
</div>
</div>
</AppLayout>
</template>
<script setup>
import { Head } from '@inertiajs/vue3';
import AppLayout from '@/Layouts/AppLayout.vue';
</script>Solución de problemas comunes
El botón no muestra los estilos correctos
Asegúrate de que el componente Button esté importado y registrado correctamente.
Los colores no se aplican
Verifica que los colores estén definidos en tailwind.config.js y que la clase esté correctamente escrita.
El dropdown no funciona
Asegúrate de que el componente Dropdown esté importado y que el template tenga la estructura correcta.
Los enlaces no navegan correctamente
Verifica que estés usando el componente Link de Inertia y no una etiqueta <a> normal.
Resumen
✅ Exploramos los componentes disponibles en Laravel Jetstream
✅ Usamos el componente Button con estilos personalizados
✅ Agregamos Call to Action en diferentes secciones
✅ Implementamos el menú desplegable de usuario
✅ Creamos un layout reutilizable para todo el sitio
¡Excelente trabajo! Ahora tienes un portafolio más completo con componentes interactivos y llamados a la acción estratégicos. En las siguientes clases profundizaremos en funcionalidades específicas como la visualización detallada de proyectos y la implementación del formulario de contacto.
Recursos adicionales
¿Tienes dudas sobre cómo usar los componentes de Jetstream? Recuerda revisar la documentación oficial o consultar los videos del curso para ver ejemplos prácticos.
Comentarios
Publicar un comentario