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">
|
<div className="flex flex-1">
|
||||||
<CourseSidebar course={course} completedLessonIds={completedLessonIds} />
|
<CourseSidebar course={course} completedLessonIds={completedLessonIds} />
|
||||||
<main className="flex-1 min-w-0 overflow-y-auto">
|
<main className="flex-1 min-w-0 overflow-y-auto">
|
||||||
|
<div className="h-12 lg:hidden" />
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Menu, X } from "lucide-react";
|
||||||
|
|
||||||
interface Lesson {
|
interface Lesson {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -29,7 +30,7 @@ export function CourseSidebar({
|
|||||||
completedLessonIds?: Set<string>;
|
completedLessonIds?: Set<string>;
|
||||||
}) {
|
}) {
|
||||||
const pathname = usePathname();
|
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 totalLessons = course.modules.reduce((s, m) => s + m.lessons.length, 0);
|
||||||
const completedCount = course.modules
|
const completedCount = course.modules
|
||||||
@@ -39,21 +40,39 @@ export function CourseSidebar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Mobile toggle */}
|
{/* Mobile hamburger */}
|
||||||
<button
|
<button
|
||||||
className="md:hidden fixed bottom-4 right-4 z-20 btn-aubade px-3 py-2 text-sm"
|
className="lg:hidden fixed top-3 left-3 z-50 p-2 rounded"
|
||||||
onClick={() => setOpen(!open)}
|
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>
|
</button>
|
||||||
|
|
||||||
|
{/* Overlay */}
|
||||||
|
{open && (
|
||||||
|
<div
|
||||||
|
className="lg:hidden fixed inset-0 bg-black/50 z-30"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<aside
|
<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={{
|
style={{
|
||||||
borderRight: "2px solid var(--border)",
|
borderRight: "2px solid var(--border)",
|
||||||
backgroundColor: "var(--background)",
|
backgroundColor: "var(--background)",
|
||||||
maxHeight: "calc(100vh - 53px)",
|
maxHeight: "calc(100vh - 53px)",
|
||||||
position: "sticky",
|
|
||||||
top: "53px",
|
top: "53px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -127,6 +146,7 @@ export function CourseSidebar({
|
|||||||
<Link
|
<Link
|
||||||
key={lesson.id}
|
key={lesson.id}
|
||||||
href={`/courses/${course.slug}/lessons/${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"
|
className="flex items-center gap-2 px-4 py-2 text-sm leading-snug border-l-2 transition-colors"
|
||||||
style={{
|
style={{
|
||||||
borderLeftColor: active ? "var(--foreground)" : "transparent",
|
borderLeftColor: active ? "var(--foreground)" : "transparent",
|
||||||
|
|||||||
Reference in New Issue
Block a user