feat: add managed learning paths
This commit is contained in:
38
components/LearningPathRow.tsx
Normal file
38
components/LearningPathRow.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ArrowRight, Check, Layers3, X } from 'lucide-react';
|
||||
import { Course } from '../types';
|
||||
import { PublicLearningPath } from '../services/db';
|
||||
import { CourseCoverImage } from './CourseCoverImage';
|
||||
|
||||
interface LearningPathRowProps {
|
||||
paths: PublicLearningPath[];
|
||||
courses: Course[];
|
||||
onCourseSelect?: (course: Course) => void;
|
||||
}
|
||||
|
||||
export const LearningPathRow: React.FC<LearningPathRowProps> = ({ paths, courses, onCourseSelect }) => {
|
||||
const [selectedPath, setSelectedPath] = useState<PublicLearningPath | null>(null);
|
||||
const courseById = new Map(courses.map((course) => [course.id, course]));
|
||||
|
||||
return <>
|
||||
<section className="mx-auto w-full max-w-[1440px] px-6 py-6 md:px-12">
|
||||
<div className="mb-4 flex items-baseline gap-3">
|
||||
<h2 className="font-display text-xl font-bold text-gray-100 md:text-2xl">Formações</h2>
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-gray-500">{paths.length} {paths.length === 1 ? 'formação' : 'formações'}</span>
|
||||
</div>
|
||||
<div className="flex gap-5 overflow-x-auto pb-4 hide-scrollbar snap-x snap-mandatory">
|
||||
{paths.map((path) => <button key={path.id} onClick={() => setSelectedPath(path)} className="group relative w-[300px] shrink-0 snap-start overflow-hidden rounded-xl border border-white/10 bg-zinc-950 text-left shadow-xl transition hover:-translate-y-1 hover:border-orange-500/60">
|
||||
<div className="relative aspect-[16/8] overflow-hidden bg-zinc-900">
|
||||
{path.coverImageUrl ? <CourseCoverImage source={path.coverImageUrl} alt={path.title} className="h-full w-full object-cover transition duration-500 group-hover:scale-105" style={{ transform: `scale(${path.coverImageZoom || 1})`, objectPosition: `${path.coverImagePositionX ?? 50}% ${path.coverImagePositionY ?? 50}%` }} /> : <div className="flex h-full items-center justify-center bg-gradient-to-br from-orange-500/30 to-zinc-900"><Layers3 className="h-9 w-9 text-orange-300" /></div>}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
|
||||
<span className="absolute bottom-3 left-3 rounded-full bg-black/60 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wider text-white backdrop-blur">{path.courses.length} {path.courses.length === 1 ? 'curso' : 'cursos'}</span>
|
||||
</div>
|
||||
<div className="p-4"><h3 className="truncate font-semibold text-white group-hover:text-orange-300">{path.title}</h3><p className="mt-1 line-clamp-2 min-h-10 text-sm leading-relaxed text-gray-500">{path.description || 'Uma trilha organizada para avançar passo a passo.'}</p><span className="mt-3 inline-flex items-center gap-1 text-sm font-semibold text-orange-400">Ver formação <ArrowRight className="h-4 w-4" /></span></div>
|
||||
</button>)}
|
||||
</div>
|
||||
</section>
|
||||
{selectedPath && <div className="fixed inset-0 z-[90] flex items-center justify-center bg-black/80 p-4 backdrop-blur-sm" role="dialog" aria-modal="true" aria-label={selectedPath.title}>
|
||||
<div className="max-h-[90vh] w-full max-w-2xl overflow-y-auto rounded-2xl border border-white/10 bg-zinc-950 shadow-2xl"><div className="flex items-start justify-between border-b border-white/10 p-6"><div><div className="flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-orange-400"><Layers3 className="h-4 w-4" /> Formação</div><h2 className="mt-2 text-2xl font-bold text-white">{selectedPath.title}</h2><p className="mt-2 text-sm leading-relaxed text-gray-400">{selectedPath.description}</p></div><button onClick={() => setSelectedPath(null)} className="rounded-full p-2 text-gray-400 hover:bg-white/10 hover:text-white" aria-label="Fechar"><X className="h-5 w-5" /></button></div><ol className="space-y-2 p-5">{selectedPath.courses.map((pathCourse, index) => { const course = courseById.get(pathCourse.id); return <li key={pathCourse.id}><button onClick={() => { if (course) { setSelectedPath(null); onCourseSelect?.(course); } }} disabled={!course} className="flex w-full items-center gap-4 rounded-xl bg-white/[0.03] px-4 py-3 text-left transition hover:bg-white/[0.07] disabled:cursor-default"><span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-orange-500/15 text-xs font-bold text-orange-400">{index + 1}</span><span className="min-w-0 flex-1"><span className="block truncate font-semibold text-white">{pathCourse.title}</span><span className="mt-0.5 block text-xs text-gray-500">{pathCourse.category}</span></span>{course && <span className="text-xs font-semibold text-orange-400">Abrir curso</span>}</button></li>; })}</ol><div className="border-t border-white/10 p-4 text-center text-xs text-gray-500"><Check className="mr-1 inline h-3.5 w-3.5 text-emerald-400" /> Siga os cursos nesta ordem.</div></div>
|
||||
</div>}
|
||||
</>;
|
||||
};
|
||||
Reference in New Issue
Block a user