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:
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user