feat: add mobile sidebar

This commit is contained in:
gosangam
2026-04-17 23:20:59 +05:30
parent 0aab1d23ec
commit 9b5823146f
30 changed files with 2502 additions and 414 deletions
+69 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function BbdChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<BbdNav prev={prev} next={next} />
<BbdNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function BbdChapterPage({
</Card>
{/* Bottom navigation */}
<BbdNav prev={prev} next={next} />
<BbdNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,68 @@ function SidebarLink({
function BbdNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: BbdSidebarItem | null;
next: BbdSidebarItem | null;
sidebarItems: BbdSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/bbd/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/bbd/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/bbd/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Beyond Birth and Death"
/>
{prev ? (
<Link
href={`/books/bbd/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+16
View File
@@ -3,6 +3,7 @@ import { PageHeader } from "@/components/page-header";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
type BgMeta = {
book: string;
@@ -42,6 +43,19 @@ export default async function VersePage({
);
if (!verse) notFound();
// Build mobile nav groups — single flat list of chapters
const mobileNavGroups: MobileNavGroup[] = [
{
title: "Chapters",
items: bgMeta.chapters.map((ch) => ({
slug: `${ch.number}`,
label: `${ch.number}. ${ch.title}`,
href: `/books/bg/${ch.number}/1`,
type: "chapter",
})),
},
];
return (
<div className="min-h-screen bg-background">
<PageHeader sticky wide>
@@ -70,6 +84,8 @@ export default async function VersePage({
chapter={parseInt(chapter)}
basePath={`/books/bg/${chapter}`}
showBengali={false}
mobileNavGroups={mobileNavGroups}
mobileNavBookTitle="Bhagavad-gītā As It Is"
/>
</div>
</div>
@@ -3,6 +3,7 @@ import { PageHeader } from "@/components/page-header";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
type ChapterInfo = {
number: number;
@@ -49,6 +50,23 @@ export default async function CcVersePage({
);
if (!verseData) notFound();
// Build mobile nav groups from verse index
const mobileNavGroups: MobileNavGroup[] = [];
if (verseIndex.length > 0) {
mobileNavGroups.push({
title: "Verses",
items: verseIndex.map((v) => {
const label = v.verseEnd ? `${v.reference}` : v.reference;
return {
slug: String(v.verse),
label,
href: `/books/cc/${lila}/${chapter}/${v.verse}`,
type: "verse",
};
}),
});
}
return (
<div className="min-h-screen bg-background">
<PageHeader sticky wide>
@@ -77,6 +95,8 @@ export default async function CcVersePage({
chapter={parseInt(chapter)}
showBengali={true}
basePath={`/books/cc/${lila}/${chapter}`}
mobileNavGroups={mobileNavGroups}
mobileNavBookTitle={`${lilaInfo.name} — Chapter ${chapter}`}
/>
</div>
</div>
+38 -10
View File
@@ -6,6 +6,7 @@ import { Card, CardHeader, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Types
@@ -60,6 +61,10 @@ type ChapterViewerProps = {
showBengali?: boolean;
/** Base path for verse URLs, e.g. "/books/bg/1" — verse number is appended */
basePath: string;
/** Mobile nav groups for the drawer */
mobileNavGroups?: MobileNavGroup[];
/** Book title for the mobile nav drawer header */
mobileNavBookTitle?: string;
};
// ---------------------------------------------------------------------------
@@ -72,12 +77,18 @@ function VerseNav({
onPrev,
onNext,
verseIndex,
mobileNavGroups,
currentVerseSlug,
mobileNavBookTitle,
}: {
currentIdx: number;
total: number;
onPrev: () => void;
onNext: () => void;
verseIndex: VerseIndexEntry[];
mobileNavGroups?: MobileNavGroup[];
currentVerseSlug?: string;
mobileNavBookTitle?: string;
}) {
const hasPrev = currentIdx > 0;
const hasNext = currentIdx < total - 1;
@@ -90,16 +101,25 @@ function VerseNav({
return (
<div className="flex items-center justify-between gap-3">
<Button
variant="outline"
size="sm"
disabled={!hasPrev}
onClick={onPrev}
className="gap-1.5"
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline">Previous</span>
</Button>
<div className="flex items-center gap-2">
{mobileNavGroups && mobileNavGroups.length > 0 && (
<MobileNav
groups={mobileNavGroups}
currentSlug={currentVerseSlug ?? String(current?.verse ?? "")}
bookTitle={mobileNavBookTitle}
/>
)}
<Button
variant="outline"
size="sm"
disabled={!hasPrev}
onClick={onPrev}
className="gap-1.5"
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline">Previous</span>
</Button>
</div>
<span className="text-sm text-muted-foreground">
Verse {label} of {total}
@@ -220,6 +240,8 @@ export function ChapterViewer({
chapter,
showBengali = false,
basePath,
mobileNavGroups,
mobileNavBookTitle,
}: ChapterViewerProps) {
const router = useRouter();
@@ -409,6 +431,9 @@ export function ChapterViewer({
onPrev={goPrev}
onNext={goNext}
verseIndex={verseIndex}
mobileNavGroups={mobileNavGroups}
currentVerseSlug={String(currentVerseNum)}
mobileNavBookTitle={mobileNavBookTitle}
/>
{/* Verse content */}
@@ -432,6 +457,9 @@ export function ChapterViewer({
onPrev={goPrev}
onNext={goNext}
verseIndex={verseIndex}
mobileNavGroups={mobileNavGroups}
currentVerseSlug={String(currentVerseNum)}
mobileNavBookTitle={mobileNavBookTitle}
/>
</main>
</>
+73 -19
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -70,7 +71,12 @@ export default async function DsChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<DsNav prev={prev} next={next} />
<DsNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -88,7 +94,12 @@ export default async function DsChapterPage({
</Card>
{/* Bottom navigation */}
<DsNav prev={prev} next={next} />
<DsNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -204,29 +215,72 @@ function SidebarLink({
function DsNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: DsSidebarItem | null;
next: DsSidebarItem | null;
sidebarItems: DsSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ds/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter(
(i) => i.type === "parent" || i.type === "sub-chapter",
);
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ds/${i.slug}`,
type: i.type,
parentChapter: i.parentChapter,
isParent: i.type === "parent",
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/ds/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Dialectic Spiritualism"
/>
{prev ? (
<Link
href={`/books/ds/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function EjChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<EjNav prev={prev} next={next} />
<EjNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function EjChapterPage({
</Card>
{/* Bottom navigation */}
<EjNav prev={prev} next={next} />
<EjNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function EjNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: EjSidebarItem | null;
next: EjSidebarItem | null;
sidebarItems: EjSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ej/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ej/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/ej/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Easy Journey to Other Planets"
/>
{prev ? (
<Link
href={`/books/ej/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function EkChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<EkNav prev={prev} next={next} />
<EkNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function EkChapterPage({
</Card>
{/* Bottom navigation */}
<EkNav prev={prev} next={next} />
<EkNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function EkNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: EkSidebarItem | null;
next: EkSidebarItem | null;
sidebarItems: EkSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ek/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ek/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/ek/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Elevation to Kṛṣṇa Consciousness"
/>
{prev ? (
<Link
href={`/books/ek/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function HkhChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<HkhNav prev={prev} next={next} />
<HkhNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function HkhChapterPage({
</Card>
{/* Bottom navigation */}
<HkhNav prev={prev} next={next} />
<HkhNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function HkhNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: HkhSidebarItem | null;
next: HkhSidebarItem | null;
sidebarItems: HkhSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/hkh/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/hkh/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/hkh/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="The Kṛṣṇa Consciousness Handbook"
/>
{prev ? (
<Link
href={`/books/hkh/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+91 -21
View File
@@ -12,6 +12,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Type guards
@@ -79,14 +80,24 @@ export default async function IsoMantraPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<IsoNav prev={prev} next={next} />
<IsoNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
{verse && <VerseContent verse={verse} />}
{intro && <IntroContent intro={intro} />}
{/* Bottom navigation */}
<IsoNav prev={prev} next={next} />
<IsoNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -295,31 +306,90 @@ function SidebarLink({
function IsoNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: IsoSidebarItem | null;
next: IsoSidebarItem | null;
sidebarItems: IsoSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
// Front matter (front-pages + introduction)
const frontMatterItems = sidebarItems.filter(
(i) => i.slug === "intro-front-pages" || i.slug === "intro-introduction",
);
if (frontMatterItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: frontMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/iso/${i.slug}`,
type: i.type,
})),
});
}
// Mantras (invocation + 1-18)
const mantraItems = sidebarItems.filter((i) => i.type === "verse");
if (mantraItems.length > 0) {
mobileNavGroups.push({
title: "Mantras",
items: mantraItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/iso/${i.slug}`,
type: i.type,
})),
});
}
// Back matter (about-the-author)
const backMatterItems = sidebarItems.filter(
(i) => i.slug === "intro-about-the-author",
);
if (backMatterItems.length > 0) {
mobileNavGroups.push({
title: "Back Matter",
items: backMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/iso/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/iso/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "verse" && prev.mantra != null && prev.mantra > 0
? `Mantra ${prev.mantra}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Śrī Īśopaniṣad"
/>
{prev ? (
<Link
href={`/books/iso/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "verse" && prev.mantra != null && prev.mantra > 0
? `Mantra ${prev.mantra}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+73 -21
View File
@@ -6,6 +6,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
export default async function KbChapterPage({
params,
@@ -60,7 +61,13 @@ export default async function KbChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<ChapterNav prev={prev} next={next} volume={vol} />
<ChapterNav
prev={prev}
next={next}
volume={vol}
chapters={allChapters}
currentSlug={slug}
/>
{/* Chapter content */}
<Card>
@@ -78,7 +85,13 @@ export default async function KbChapterPage({
</Card>
{/* Bottom navigation */}
<ChapterNav prev={prev} next={next} volume={vol} />
<ChapterNav
prev={prev}
next={next}
volume={vol}
chapters={allChapters}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -154,32 +167,71 @@ function ChapterNav({
prev,
next,
volume,
chapters,
currentSlug,
}: {
prev: NavChapter | null;
next: NavChapter | null;
volume: number;
chapters: SidebarChapter[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = chapters.filter((i) => i.type !== "chapter");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.title,
href: `/books/kb/${volume}/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = chapters.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.chapter != null ? `${i.chapter}. ${i.title}` : i.title,
href: `/books/kb/${volume}/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/kb/${volume}/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.title}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle={`Kṛṣṇa Book — Volume ${volume}`}
/>
{prev ? (
<Link
href={`/books/kb/${volume}/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.title}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function MgChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<MgNav prev={prev} next={next} />
<MgNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function MgChapterPage({
</Card>
{/* Bottom navigation */}
<MgNav prev={prev} next={next} />
<MgNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function MgNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: MgSidebarItem | null;
next: MgSidebarItem | null;
sidebarItems: MgSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/mg/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/mg/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/mg/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Kṛṣṇa Consciousness: The Matchless Gift"
/>
{prev ? (
<Link
href={`/books/mg/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+80 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function NodChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<NodNav prev={prev} next={next} />
<NodNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function NodChapterPage({
</Card>
{/* Bottom navigation */}
<NodNav prev={prev} next={next} />
<NodNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -197,31 +208,79 @@ function SidebarLink({
function NodNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: NodSidebarItem | null;
next: NodSidebarItem | null;
sidebarItems: NodSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/nod/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
const conclusionItems = sidebarItems.filter((i) => i.type === "conclusion");
if (chapterItems.length > 0 || conclusionItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: [
...chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/nod/${i.slug}`,
type: i.type,
})),
...conclusionItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/nod/${i.slug}`,
type: i.type,
})),
],
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/nod/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="The Nectar of Devotion"
/>
{prev ? (
<Link
href={`/books/nod/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+92 -24
View File
@@ -12,6 +12,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Type guards
@@ -79,14 +80,24 @@ export default async function NoiTextPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<NoiNav prev={prev} next={next} />
<NoiNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
{verse && <VerseContent verse={verse} />}
{intro && <IntroContent intro={intro} />}
{/* Bottom navigation */}
<NoiNav prev={prev} next={next} />
<NoiNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -210,9 +221,7 @@ function NoiSidebar({
// Texts = 1-11
const textItems = items.filter((item) => item.type === "verse");
// Appendixes comes last
const appendixesItem = items.find(
(item) => item.slug === "intro-appendixes",
);
const appendixesItem = items.find((item) => item.slug === "intro-appendixes");
return (
<aside className="hidden lg:block w-56 shrink-0 sticky top-[89px] self-start max-h-[calc(100vh-89px)] overflow-y-auto py-8 pr-4">
@@ -295,31 +304,90 @@ function SidebarLink({
function NoiNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: NoiSidebarItem | null;
next: NoiSidebarItem | null;
sidebarItems: NoiSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
// Front matter (front-pages + preface)
const frontMatterItems = sidebarItems.filter(
(i) => i.slug === "intro-front-pages" || i.slug === "intro-preface",
);
if (frontMatterItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: frontMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/noi/${i.slug}`,
type: i.type,
})),
});
}
// Texts (1-11)
const textItems = sidebarItems.filter((i) => i.type === "verse");
if (textItems.length > 0) {
mobileNavGroups.push({
title: "Texts",
items: textItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/noi/${i.slug}`,
type: i.type,
})),
});
}
// Back matter (appendixes)
const backMatterItems = sidebarItems.filter(
(i) => i.slug === "intro-appendixes",
);
if (backMatterItems.length > 0) {
mobileNavGroups.push({
title: "Back Matter",
items: backMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/noi/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/noi/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "verse" && prev.text != null
? `Text ${prev.text}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="The Nectar of Instruction"
/>
{prev ? (
<Link
href={`/books/noi/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "verse" && prev.text != null
? `Text ${prev.text}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function OchkmChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<OchkmNav prev={prev} next={next} />
<OchkmNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function OchkmChapterPage({
</Card>
{/* Bottom navigation */}
<OchkmNav prev={prev} next={next} />
<OchkmNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function OchkmNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: OchkmSidebarItem | null;
next: OchkmSidebarItem | null;
sidebarItems: OchkmSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ochkm/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ochkm/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/ochkm/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="On Chanting the Hare Kṛṣṇa Mantra"
/>
{prev ? (
<Link
href={`/books/ochkm/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function OwkChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<OwkNav prev={prev} next={next} />
<OwkNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function OwkChapterPage({
</Card>
{/* Bottom navigation */}
<OwkNav prev={prev} next={next} />
<OwkNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function OwkNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: OwkSidebarItem | null;
next: OwkSidebarItem | null;
sidebarItems: OwkSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/owk/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/owk/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/owk/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="On the Way to Kṛṣṇa"
/>
{prev ? (
<Link
href={`/books/owk/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function PoyChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<PoyNav prev={prev} next={next} />
<PoyNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function PoyChapterPage({
</Card>
{/* Bottom navigation */}
<PoyNav prev={prev} next={next} />
<PoyNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function PoyNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: PoySidebarItem | null;
next: PoySidebarItem | null;
sidebarItems: PoySidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/poy/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/poy/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/poy/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="The Perfection of Yoga"
/>
{prev ? (
<Link
href={`/books/poy/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+109 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function PqpaChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<PqpaNav prev={prev} next={next} />
<PqpaNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function PqpaChapterPage({
</Card>
{/* Bottom navigation */}
<PqpaNav prev={prev} next={next} />
<PqpaNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -233,31 +244,108 @@ function SidebarLink({
function PqpaNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: PqpaSidebarItem | null;
next: PqpaSidebarItem | null;
sidebarItems: PqpaSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const frontMatterItems: PqpaSidebarItem[] = [];
const chapterItems: PqpaSidebarItem[] = [];
const conclusionItems: PqpaSidebarItem[] = [];
const backMatterItems: PqpaSidebarItem[] = [];
let pastChapters = false;
for (const item of sidebarItems) {
if (item.type === "chapter") {
pastChapters = true;
chapterItems.push(item);
} else if (item.type === "conclusion") {
pastChapters = true;
conclusionItems.push(item);
} else if (!pastChapters) {
frontMatterItems.push(item);
} else {
backMatterItems.push(item);
}
}
if (frontMatterItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: frontMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/pqpa/${i.slug}`,
type: i.type,
})),
});
}
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: [
...chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/pqpa/${i.slug}`,
type: i.type,
})),
...conclusionItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/pqpa/${i.slug}`,
type: i.type,
})),
],
});
}
if (backMatterItems.length > 0) {
mobileNavGroups.push({
title: "Back Matter",
items: backMatterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/pqpa/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/pqpa/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Perfect Questions Perfect Answers"
/>
{prev ? (
<Link
href={`/books/pqpa/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function RvChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<RvNav prev={prev} next={next} />
<RvNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function RvChapterPage({
</Card>
{/* Bottom navigation */}
<RvNav prev={prev} next={next} />
<RvNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function RvNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: RvSidebarItem | null;
next: RvSidebarItem | null;
sidebarItems: RvSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/rv/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/rv/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/rv/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Rāja-vidyā: The King of Knowledge"
/>
{prev ? (
<Link
href={`/books/rv/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
@@ -3,6 +3,7 @@ import { PageHeader } from "@/components/page-header";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
type ChapterInfo = {
number: number;
@@ -48,6 +49,20 @@ export default async function SbVersePage({
);
if (!verseData) notFound();
const mobileNavGroups: MobileNavGroup[] = [
{
title: "Verses",
items: verseIndex.map((v) => ({
slug: String(v.verse),
label: v.verseEnd
? `Texts ${v.verse}–${v.verseEnd}`
: `Text ${v.verse}`,
href: `/books/sb/${canto}/${chapter}/${v.verse}`,
type: "verse",
})),
},
];
return (
<div className="min-h-screen bg-background">
<PageHeader sticky wide>
@@ -76,6 +91,8 @@ export default async function SbVersePage({
chapter={parseInt(chapter)}
showBengali={false}
basePath={`/books/sb/${canto}/${chapter}`}
mobileNavGroups={mobileNavGroups}
mobileNavBookTitle={`SB ${canto}.${chapter}`}
/>
</div>
</div>
@@ -3,6 +3,7 @@ import { PageHeader } from "@/components/page-header";
import { notFound } from "next/navigation";
import { getBookMeta, getSingleVerse, getChapterVerseIndex } from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
type Sb1962Meta = {
book: string;
@@ -46,6 +47,23 @@ export default async function VersePage({
);
if (!verse) notFound();
const mobileNavGroups: MobileNavGroup[] = [
{
title: "Verses",
items: verseIndex.map((v) => {
const label = v.verseEnd
? `Verses ${v.verse}–${v.verseEnd}`
: `Verse ${v.verse}`;
return {
slug: String(v.verse),
label,
href: `/books/sb1962/${chapter}/${v.verse}`,
type: "verse",
};
}),
},
];
return (
<div className="min-h-screen bg-background">
<PageHeader sticky wide>
@@ -74,6 +92,8 @@ export default async function VersePage({
chapter={parseInt(chapter)}
basePath={`/books/sb1962/${chapter}`}
showBengali={false}
mobileNavGroups={mobileNavGroups}
mobileNavBookTitle={`Śrīmad-Bhāgavatam (1962) — Ch. ${chapter}`}
/>
</div>
</div>
+90 -19
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -70,7 +71,12 @@ export default async function SsrChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<SsrNav prev={prev} next={next} />
<SsrNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -88,7 +94,12 @@ export default async function SsrChapterPage({
</Card>
{/* Bottom navigation */}
<SsrNav prev={prev} next={next} />
<SsrNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -225,29 +236,89 @@ function SidebarLink({
function SsrNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: SsrSidebarItem | null;
next: SsrSidebarItem | null;
sidebarItems: SsrSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const frontMatter = sidebarItems.filter(
(i) => i.type === "intro-segment" && i.slug !== "intro-about-the-author",
);
if (frontMatter.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: frontMatter.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ssr/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter(
(i) => i.type === "parent" || i.type === "sub-chapter",
);
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ssr/${i.slug}`,
type: i.type,
parentChapter: i.parentChapter,
isParent: i.type === "parent",
})),
});
}
const backMatter = sidebarItems.filter(
(i) => i.slug === "intro-about-the-author",
);
if (backMatter.length > 0) {
mobileNavGroups.push({
title: "Back Matter",
items: backMatter.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ssr/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/ssr/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="The Science of Self-Realization"
/>
{prev ? (
<Link
href={`/books/ssr/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function TlcChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<TlcNav prev={prev} next={next} />
<TlcNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function TlcChapterPage({
</Card>
{/* Bottom navigation */}
<TlcNav prev={prev} next={next} />
<TlcNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -183,31 +194,70 @@ function SidebarLink({
function TlcNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: TlcSidebarItem | null;
next: TlcSidebarItem | null;
sidebarItems: TlcSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/tlc/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/tlc/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/tlc/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Teachings of Lord Caitanya"
/>
{prev ? (
<Link
href={`/books/tlc/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link
+71 -21
View File
@@ -11,6 +11,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { MobileNav, type MobileNavGroup } from "@/components/mobile-nav";
// ---------------------------------------------------------------------------
// Page
@@ -64,7 +65,12 @@ export default async function TtpChapterPage({
{/* Main content */}
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
{/* Top navigation */}
<TtpNav prev={prev} next={next} />
<TtpNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
{/* Content */}
<Card>
@@ -82,7 +88,12 @@ export default async function TtpChapterPage({
</Card>
{/* Bottom navigation */}
<TtpNav prev={prev} next={next} />
<TtpNav
prev={prev}
next={next}
sidebarItems={sidebarItems}
currentSlug={slug}
/>
</main>
</div>
</div>
@@ -174,31 +185,70 @@ function SidebarLink({
function TtpNav({
prev,
next,
sidebarItems,
currentSlug,
}: {
prev: TtpSidebarItem | null;
next: TtpSidebarItem | null;
sidebarItems: TtpSidebarItem[];
currentSlug: string;
}) {
const mobileNavGroups: MobileNavGroup[] = [];
const introItems = sidebarItems.filter((i) => i.type === "intro-segment");
if (introItems.length > 0) {
mobileNavGroups.push({
title: "Front Matter",
items: introItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ttp/${i.slug}`,
type: i.type,
})),
});
}
const chapterItems = sidebarItems.filter((i) => i.type === "chapter");
if (chapterItems.length > 0) {
mobileNavGroups.push({
title: "Chapters",
items: chapterItems.map((i) => ({
slug: i.slug,
label: i.label,
href: `/books/ttp/${i.slug}`,
type: i.type,
})),
});
}
return (
<div className="flex items-center justify-between gap-3">
{prev ? (
<Link
href={`/books/ttp/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div />
)}
<div className="flex items-center gap-2">
<MobileNav
groups={mobileNavGroups}
currentSlug={currentSlug}
bookTitle="Transcendental Teachings of Prahlāda Mahārāja"
/>
{prev ? (
<Link
href={`/books/ttp/${prev.slug}`}
className={cn(
buttonVariants({ variant: "outline", size: "sm" }),
"gap-1.5",
)}
>
<ChevronLeft className="size-4" />
<span className="hidden sm:inline truncate max-w-[180px]">
{prev.type === "chapter" && prev.chapter != null
? `Ch. ${prev.chapter}`
: prev.label}
</span>
<span className="sm:hidden">Previous</span>
</Link>
) : (
<div className="hidden lg:block" />
)}
</div>
{next ? (
<Link