📌 Visão Geral
O CourseCard
é um componente React que exibe um cartão com informações sobre um curso. Ele utiliza useState
para controlar a expansão da descrição e exibe:
- Imagem do curso 🖼️
- Título e descrição 📖
- Tags associadas 🏷️
- Criador do curso 👤
- Botão para acessar o curso 🎯
🏗️ Estrutura do Componente
O CourseCard
recebe um objeto course
como propriedade (prop) e utiliza vários subcomponentes da biblioteca shadcn/ui
para estruturar o layout.
Vamos analisar o código em partes.
🖼️ Exibição da Imagem
A primeira seção do componente exibe a imagem do curso dentro de um container responsivo usando o AspectRatio
.
<AspectRatio ratio={16 / 9}>
<Image
src={course.fotoBanner?.url}
alt={course.fotoBanner?.alt}
className="w-full h-full object-cover rounded-tl-lg rounded-tr-lg"
width={500}
height={300}
/>
</AspectRatio>
📌 Explicação:
AspectRatio ratio={16 / 9}
garante que a imagem sempre mantenha a proporção de 16:9.Image
carrega a foto do curso (course.fotoBanner.url
).alt={course.fotoBanner?.alt}
adiciona um texto alternativo para acessibilidade.- As classes Tailwind
w-full h-full object-cover
garantem que a imagem preencha o container corretamente.
📝 Título e Descrição
Depois da imagem, exibimos o título do curso e uma descrição que pode ser expandida.
<CardHeader className="px-4 pt-0 pb-0 space-y-4">
<CardTitle className="mt-4 mb-2">{course.nomeCurso}</CardTitle>
<CardDescription className="mt-4 mb-2">
{course.descricao.length > 200
? expanded
? course.descricao
: `${course.descricao.substring(0, 200)}...`
: course.descricao}
{course.descricao.length > 200 && (
<span onClick={toggleDescription} className="block mt-2 text-blue-500 cursor-pointer">
{expanded ? 'Veja menos' : 'Veja mais'}
</span>
)}
</CardDescription>
</CardHeader>
📌 Explicação:
CardTitle
exibe o nome do curso 📌CardDescription
exibe a descrição, que pode ser cortada em 200 caracteres e expandida ao clicar em "Veja mais" 📖useState
controla a expansão do texto 🛠️
🔀 Alternância entre Expandir e Recolher
Para controlar se a descrição está expandida ou recolhida, usamos useState
e a função toggleDescription
:
const [expanded, setExpanded] = useState(false);
const toggleDescription = () => setExpanded(!expanded);
🏷️ Exibição das Tags
Caso o curso tenha tags associadas, elas são exibidas logo abaixo da descrição.
<div className="mb-4 flex flex-wrap gap-2">
{tags.map((tag) => (
<TagBadge key={tag.id} tag={tag.nome} />
))}
</div>
📌 Explicação:
tags.map(...)
percorre todas as tags associadas ao curso.- Cada tag é renderizada com o componente
TagBadge
. - As classes Tailwind
flex-wrap gap-2
garantem um layout responsivo.
👤 Exibição do Criador do Curso
Os dados do criador do curso são mostrados dentro de um HoverCard, que exibe mais informações ao passar o mouse.
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex items-center space-x-2 sm:whitespace-nowrap">
<CollaboratorInfo
collaborator={{
photo: createdBy.foto?.url,
full_name: createdBy.nomeCompleto,
}}
/>
</div>
</HoverCardTrigger>
<HoverCardContent className="w-80">
<div className="flex justify-between space-x-4">
<Avatar>
<AvatarImage src={createdBy.foto?.url} />
<AvatarFallback>{createdBy.nomeCompleto}</AvatarFallback>
</Avatar>
<div className="space-y-1">
<h4 className="text-sm font-semibold">{createdBy.nomeCompleto}</h4>
<p className="text-sm">{createdBy.descricao}</p>
<div className="flex items-center pt-2">
<CalendarDays className="mr-2 h-4 w-4 opacity-70" />{' '}
<span className="text-xs text-muted-foreground">
Inscreveu-se em {new Date(createdBy.createdAt).toLocaleDateString()}
</span>
</div>
</div>
</div>
</HoverCardContent>
</HoverCard>
📌 Explicação:
- O
HoverCard
exibe detalhes sobre o criador ao passar o mouse. - O componente
CollaboratorInfo
exibe a foto e nome do criador. Avatar
eAvatarImage
mostram a foto do criador, com um fallback caso a imagem não carregue.
🎯 Botão "Começar"
Para permitir que o usuário acesse o curso, um botão com link é adicionado.
<Link href={`/cursos/${course.slug}`} passHref>
<Button className="w-full sm:w-auto mt-4 sm:mt-0">Começar</Button>
</Link>
📌 Explicação:
href={
/cursos/${course.slug}}
cria um link dinâmico para a página do curso.Button
estiliza o botão.w-full sm:w-auto
garante que o botão seja responsivo.
🔗 Código Completo
import React, { useState } from 'react'
import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Button } from '@/components/ui/button'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { AspectRatio } from '@/components/ui/aspect-ratio'
import TagBadge from '@/components/TagBadge'
import CollaboratorInfo from '@/components/CollaboratorInfo'
import { CalendarDays } from 'lucide-react'
import Image from 'next/image'
const CourseCard = ({ course }) => {
const [expanded, setExpanded] = useState(false)
const toggleDescription = () => setExpanded(!expanded)
const { createdBy, tags = [] } = course
return (
<Card>
<AspectRatio ratio={16 / 9}>
<Image src={course.fotoBanner?.url} alt={course.fotoBanner?.alt} width={500} height={300} />
</AspectRatio>
<CardHeader>
<CardTitle>{course.nomeCurso}</CardTitle>
<CardDescription>
{course.descricao.length > 200 ? expanded ? course.descricao : `${course.descricao.substring(0, 200)}...` : course.descricao}
{course.descricao.length > 200 && (
<span onClick={toggleDescription} className="text-blue-500 cursor-pointer">
{expanded ? 'Veja menos' : 'Veja mais'}
</span>
)}
</CardDescription>
</CardHeader>
<CardContent>
<Link href={`/cursos/${course.slug}`} passHref>
<Button>Começar</Button>
</Link>
</CardContent>
</Card>
)
}
export default CourseCard