📌 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.Imagecarrega 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-covergarantem 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:
CardTitleexibe o nome do curso 📌CardDescriptionexibe a descrição, que pode ser cortada em 200 caracteres e expandida ao clicar em "Veja mais" 📖useStatecontrola 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-2garantem 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
HoverCardexibe detalhes sobre o criador ao passar o mouse. - O componente
CollaboratorInfoexibe a foto e nome do criador. AvatareAvatarImagemostram 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.Buttonestiliza o botão.w-full sm:w-autogarante 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