feat: collapsible mobile sidebar for student course view

Hamburger button (top-left, lg:hidden), dark overlay, slide-in animation.
Sidebar closes on lesson link click. Spacer added to prevent content overlap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 05:07:29 +00:00
parent e3e6c713d2
commit 47840901c5
2 changed files with 28 additions and 7 deletions
@@ -62,6 +62,7 @@ export default async function CourseLayout({ children, params }: Props) {
<div className="flex flex-1">
<CourseSidebar course={course} completedLessonIds={completedLessonIds} />
<main className="flex-1 min-w-0 overflow-y-auto">
<div className="h-12 lg:hidden" />
{children}
</main>
</div>
+27 -7
View File
@@ -3,6 +3,7 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { Menu, X } from "lucide-react";
interface Lesson {
id: string;
@@ -29,7 +30,7 @@ export function CourseSidebar({
completedLessonIds?: Set<string>;
}) {
const pathname = usePathname();
const [open, setOpen] = useState(true);
const [open, setOpen] = useState(false);
const totalLessons = course.modules.reduce((s, m) => s + m.lessons.length, 0);
const completedCount = course.modules
@@ -39,21 +40,39 @@ export function CourseSidebar({
return (
<>
{/* Mobile toggle */}
{/* Mobile hamburger */}
<button
className="md:hidden fixed bottom-4 right-4 z-20 btn-aubade px-3 py-2 text-sm"
onClick={() => setOpen(!open)}
className="lg:hidden fixed top-3 left-3 z-50 p-2 rounded"
style={{
backgroundColor: "var(--background)",
border: "1.5px solid var(--border)",
color: "var(--foreground)",
}}
onClick={() => setOpen((v) => !v)}
aria-label="Навигация по курсу"
>
{open ? "✕" : "☰ Уроки"}
{open ? <X size={20} /> : <Menu size={20} />}
</button>
{/* Overlay */}
{open && (
<div
className="lg:hidden fixed inset-0 bg-black/50 z-30"
onClick={() => setOpen(false)}
/>
)}
<aside
className={`w-64 shrink-0 flex flex-col overflow-y-auto ${open ? "flex" : "hidden md:flex"}`}
className={[
"w-64 flex flex-col overflow-y-auto",
"fixed top-[53px] left-0 bottom-0 z-40 transition-transform duration-200",
"lg:sticky lg:shrink-0 lg:translate-x-0",
open ? "translate-x-0" : "-translate-x-full lg:translate-x-0",
].join(" ")}
style={{
borderRight: "2px solid var(--border)",
backgroundColor: "var(--background)",
maxHeight: "calc(100vh - 53px)",
position: "sticky",
top: "53px",
}}
>
@@ -127,6 +146,7 @@ export function CourseSidebar({
<Link
key={lesson.id}
href={`/courses/${course.slug}/lessons/${lesson.id}`}
onClick={() => setOpen(false)}
className="flex items-center gap-2 px-4 py-2 text-sm leading-snug border-l-2 transition-colors"
style={{
borderLeftColor: active ? "var(--foreground)" : "transparent",