feat: add mobile sidebar
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+55
-19
@@ -7,6 +7,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";
|
||||
import { prisma } from "@/lib/db";
|
||||
import type { JsonValue } from "@prisma/client/runtime/library";
|
||||
|
||||
@@ -127,7 +128,12 @@ export default async function EssayPage({
|
||||
{/* Main content */}
|
||||
<main className="flex-1 min-w-0 px-6 py-8 space-y-8">
|
||||
{/* Top navigation */}
|
||||
<EssayNav prev={prev} next={next} />
|
||||
<EssayNav
|
||||
prev={prev}
|
||||
next={next}
|
||||
allEssays={allEssays}
|
||||
currentSlug={slug}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<Card>
|
||||
@@ -145,7 +151,12 @@ export default async function EssayPage({
|
||||
</Card>
|
||||
|
||||
{/* Bottom navigation */}
|
||||
<EssayNav prev={prev} next={next} />
|
||||
<EssayNav
|
||||
prev={prev}
|
||||
next={next}
|
||||
allEssays={allEssays}
|
||||
currentSlug={slug}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,29 +207,54 @@ function EssaySidebar({
|
||||
function EssayNav({
|
||||
prev,
|
||||
next,
|
||||
allEssays,
|
||||
currentSlug,
|
||||
}: {
|
||||
prev: SidebarItem | null;
|
||||
next: SidebarItem | null;
|
||||
allEssays: SidebarItem[];
|
||||
currentSlug: string;
|
||||
}) {
|
||||
const mobileNavGroups: MobileNavGroup[] = [];
|
||||
|
||||
if (allEssays.length > 0) {
|
||||
mobileNavGroups.push({
|
||||
title: "Essays",
|
||||
items: allEssays.map((i) => ({
|
||||
slug: i.slug,
|
||||
label: i.title,
|
||||
href: `/essays/${i.slug}`,
|
||||
type: "chapter",
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
{prev ? (
|
||||
<Link
|
||||
href={`/essays/${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.title}
|
||||
</span>
|
||||
<span className="sm:hidden">Previous</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<MobileNav
|
||||
groups={mobileNavGroups}
|
||||
currentSlug={currentSlug}
|
||||
bookTitle="Essays"
|
||||
/>
|
||||
{prev ? (
|
||||
<Link
|
||||
href={`/essays/${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.title}
|
||||
</span>
|
||||
<span className="sm:hidden">Previous</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="hidden lg:block" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{next ? (
|
||||
<Link
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Menu } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type MobileNavItem = {
|
||||
slug: string;
|
||||
label: string;
|
||||
href: string;
|
||||
type: string;
|
||||
/** For hierarchical books (SSR, DS) — parent chapter number */
|
||||
parentChapter?: number | null;
|
||||
/** Whether this is a non-clickable parent heading */
|
||||
isParent?: boolean;
|
||||
};
|
||||
|
||||
export type MobileNavGroup = {
|
||||
title?: string;
|
||||
items: MobileNavItem[];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Mobile sidebar drawer triggered by a menu button.
|
||||
*
|
||||
* Usage: Place this component alongside the prev/next buttons in any
|
||||
* book reader page. It renders a menu icon button (visible only on
|
||||
* mobile / below `lg` breakpoint) that opens a bottom drawer with
|
||||
* the full sidebar navigation.
|
||||
*
|
||||
* Supports two modes:
|
||||
* 1. **Flat groups** — pass `groups` for simple grouped lists
|
||||
* (Front Matter, Chapters, etc.)
|
||||
* 2. **Hierarchical** — pass `groups` where some items have
|
||||
* `isParent: true` to render parent headings with indented children
|
||||
*/
|
||||
export function MobileNav({
|
||||
groups,
|
||||
currentSlug,
|
||||
bookTitle,
|
||||
}: {
|
||||
groups: MobileNavGroup[];
|
||||
currentSlug: string;
|
||||
bookTitle?: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={setOpen} direction="left">
|
||||
<DrawerTrigger
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline", size: "sm" }),
|
||||
"lg:hidden gap-1.5",
|
||||
)}
|
||||
>
|
||||
<Menu className="size-4" />
|
||||
<span className="hidden sm:inline">Contents</span>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent direction="left" className="h-full">
|
||||
<DrawerHeader className="text-left border-b border-border pb-4 mb-2">
|
||||
<DrawerTitle>{bookTitle ?? "Contents"}</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<nav className="overflow-y-auto px-4 pb-6 pt-2 space-y-4">
|
||||
{groups.map((group, gi) => (
|
||||
<div key={gi}>
|
||||
{group.title && (
|
||||
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-2 px-2">
|
||||
{group.title}
|
||||
</h3>
|
||||
)}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{group.items.map((item) => {
|
||||
if (item.isParent) {
|
||||
return (
|
||||
<div
|
||||
key={item.slug}
|
||||
className="px-2 py-1.5 text-xs font-bold text-muted-foreground mt-2"
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isActive = item.slug === currentSlug;
|
||||
const isChild = item.parentChapter != null && !item.isParent;
|
||||
|
||||
return (
|
||||
<DrawerClose key={item.slug} asChild>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"px-2 py-2 rounded-md text-sm transition-colors truncate block",
|
||||
isChild && "pl-4",
|
||||
isActive
|
||||
? "bg-primary/10 text-primary font-medium"
|
||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||
)}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</DrawerClose>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Drawer as DrawerPrimitive } from "vaul";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Drawer = ({
|
||||
shouldScaleBackground = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||
<DrawerPrimitive.Root
|
||||
shouldScaleBackground={shouldScaleBackground}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
Drawer.displayName = "Drawer";
|
||||
|
||||
const DrawerTrigger = DrawerPrimitive.Trigger;
|
||||
|
||||
const DrawerPortal = DrawerPrimitive.Portal;
|
||||
|
||||
const DrawerClose = DrawerPrimitive.Close;
|
||||
|
||||
const DrawerOverlay = React.forwardRef<
|
||||
React.ComponentRef<typeof DrawerPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
||||
|
||||
const DrawerContent = React.forwardRef<
|
||||
React.ComponentRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
|
||||
direction?: "top" | "bottom" | "left" | "right";
|
||||
}
|
||||
>(({ className, children, direction = "bottom", ...props }, ref) => (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 flex flex-col border bg-background",
|
||||
direction === "left" &&
|
||||
"inset-y-0 left-0 h-full w-[80%] max-w-sm rounded-r-[10px]",
|
||||
direction === "right" &&
|
||||
"inset-y-0 right-0 h-full w-[80%] max-w-sm rounded-l-[10px]",
|
||||
direction === "bottom" &&
|
||||
"inset-x-0 bottom-0 mt-24 h-auto rounded-t-[10px]",
|
||||
direction === "top" && "inset-x-0 top-0 mb-24 h-auto rounded-b-[10px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{(direction === "bottom" || direction === "top") && (
|
||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
)}
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
));
|
||||
DrawerContent.displayName = "DrawerContent";
|
||||
|
||||
const DrawerHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DrawerHeader.displayName = "DrawerHeader";
|
||||
|
||||
const DrawerFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DrawerFooter.displayName = "DrawerFooter";
|
||||
|
||||
const DrawerTitle = React.forwardRef<
|
||||
React.ComponentRef<typeof DrawerPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
||||
|
||||
const DrawerDescription = React.forwardRef<
|
||||
React.ComponentRef<typeof DrawerPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
};
|
||||
Generated
+486
-2
@@ -22,7 +22,8 @@
|
||||
"react-dom": "19.2.4",
|
||||
"shadcn": "^4.2.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vaul": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
@@ -2008,6 +2009,337 @@
|
||||
"@prisma/debug": "6.19.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
|
||||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
|
||||
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
|
||||
"integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-focus-guards": "1.1.3",
|
||||
"@radix-ui/react-focus-scope": "1.1.7",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"aria-hidden": "^1.2.4",
|
||||
"react-remove-scroll": "^2.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
|
||||
"integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
|
||||
"integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
|
||||
"integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
||||
"integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
|
||||
"integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
|
||||
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
|
||||
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-effect-event": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
|
||||
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
|
||||
"integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
|
||||
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rtsao/scc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@@ -2211,7 +2543,7 @@
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
@@ -2645,6 +2977,18 @@
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/aria-hidden": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
|
||||
"integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
@@ -3684,6 +4028,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
|
||||
@@ -5103,6 +5453,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-nonce": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-own-enumerable-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz",
|
||||
@@ -7724,6 +8083,75 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-remove-scroll": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
|
||||
"integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.7",
|
||||
"react-style-singleton": "^2.2.3",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.3",
|
||||
"use-sidecar": "^1.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-remove-scroll-bar": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
|
||||
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-style-singleton": "^2.2.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-nonce": "^1.0.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
@@ -9212,6 +9640,49 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-callback-ref": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
|
||||
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-node-es": "^1.1.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
@@ -9245,6 +9716,19 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vaul": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
|
||||
"integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
|
||||
+2
-1
@@ -23,7 +23,8 @@
|
||||
"react-dom": "19.2.4",
|
||||
"shadcn": "^4.2.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vaul": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
@@ -686,6 +686,135 @@
|
||||
dependencies:
|
||||
"@prisma/debug" "6.19.3"
|
||||
|
||||
"@radix-ui/primitive@1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz"
|
||||
integrity sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==
|
||||
|
||||
"@radix-ui/react-compose-refs@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz"
|
||||
integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
|
||||
|
||||
"@radix-ui/react-context@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz"
|
||||
integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
|
||||
|
||||
"@radix-ui/react-dialog@^1.1.1":
|
||||
version "1.1.15"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz"
|
||||
integrity sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==
|
||||
dependencies:
|
||||
"@radix-ui/primitive" "1.1.3"
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
"@radix-ui/react-context" "1.1.2"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.11"
|
||||
"@radix-ui/react-focus-guards" "1.1.3"
|
||||
"@radix-ui/react-focus-scope" "1.1.7"
|
||||
"@radix-ui/react-id" "1.1.1"
|
||||
"@radix-ui/react-portal" "1.1.9"
|
||||
"@radix-ui/react-presence" "1.1.5"
|
||||
"@radix-ui/react-primitive" "2.1.3"
|
||||
"@radix-ui/react-slot" "1.2.3"
|
||||
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||
aria-hidden "^1.2.4"
|
||||
react-remove-scroll "^2.6.3"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@1.1.11":
|
||||
version "1.1.11"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz"
|
||||
integrity sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==
|
||||
dependencies:
|
||||
"@radix-ui/primitive" "1.1.3"
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
"@radix-ui/react-primitive" "2.1.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.1"
|
||||
"@radix-ui/react-use-escape-keydown" "1.1.1"
|
||||
|
||||
"@radix-ui/react-focus-guards@1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz"
|
||||
integrity sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==
|
||||
|
||||
"@radix-ui/react-focus-scope@1.1.7":
|
||||
version "1.1.7"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz"
|
||||
integrity sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
"@radix-ui/react-primitive" "2.1.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.1"
|
||||
|
||||
"@radix-ui/react-id@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz"
|
||||
integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-layout-effect" "1.1.1"
|
||||
|
||||
"@radix-ui/react-portal@1.1.9":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz"
|
||||
integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==
|
||||
dependencies:
|
||||
"@radix-ui/react-primitive" "2.1.3"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.1"
|
||||
|
||||
"@radix-ui/react-presence@1.1.5":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz"
|
||||
integrity sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.1"
|
||||
|
||||
"@radix-ui/react-primitive@2.1.3":
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz"
|
||||
integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
|
||||
dependencies:
|
||||
"@radix-ui/react-slot" "1.2.3"
|
||||
|
||||
"@radix-ui/react-slot@1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz"
|
||||
integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz"
|
||||
integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==
|
||||
|
||||
"@radix-ui/react-use-controllable-state@1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz"
|
||||
integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-effect-event" "0.0.2"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.1"
|
||||
|
||||
"@radix-ui/react-use-effect-event@0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz"
|
||||
integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-layout-effect" "1.1.1"
|
||||
|
||||
"@radix-ui/react-use-escape-keydown@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz"
|
||||
integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==
|
||||
dependencies:
|
||||
"@radix-ui/react-use-callback-ref" "1.1.1"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz"
|
||||
integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
|
||||
|
||||
"@rtsao/scc@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz"
|
||||
@@ -791,12 +920,12 @@
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
"@types/react-dom@^19":
|
||||
"@types/react-dom@*", "@types/react-dom@^19":
|
||||
version "19.2.3"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"
|
||||
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
|
||||
|
||||
"@types/react@^17 || ^18 || ^19", "@types/react@^19", "@types/react@^19.2.0":
|
||||
"@types/react@*", "@types/react@^17 || ^18 || ^19", "@types/react@^19", "@types/react@^19.2.0":
|
||||
version "19.2.14"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz"
|
||||
integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==
|
||||
@@ -998,6 +1127,13 @@ argparse@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
aria-hidden@^1.2.4:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz"
|
||||
integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
aria-query@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz"
|
||||
@@ -1578,6 +1714,11 @@ detect-libc@^2.0.3, detect-libc@^2.1.2:
|
||||
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz"
|
||||
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
|
||||
|
||||
detect-node-es@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz"
|
||||
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
|
||||
|
||||
diff@^8.0.2:
|
||||
version "8.0.4"
|
||||
resolved "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz"
|
||||
@@ -2401,6 +2542,11 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@
|
||||
hasown "^2.0.2"
|
||||
math-intrinsics "^1.1.0"
|
||||
|
||||
get-nonce@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz"
|
||||
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
|
||||
|
||||
get-own-enumerable-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz"
|
||||
@@ -3791,7 +3937,7 @@ rc9@^2.1.2:
|
||||
defu "^6.1.4"
|
||||
destr "^2.0.3"
|
||||
|
||||
"react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^17 || ^18 || ^19", "react-dom@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react-dom@>=16.8.0, react-dom@19.2.4:
|
||||
"react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^17 || ^18 || ^19", "react-dom@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react-dom@>=16.8.0, react-dom@19.2.4:
|
||||
version "19.2.4"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz"
|
||||
integrity sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==
|
||||
@@ -3803,7 +3949,34 @@ react-is@^16.13.1:
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^17 || ^18 || ^19", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^19.2.4, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16.8.0, react@19.2.4:
|
||||
react-remove-scroll-bar@^2.3.7:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz"
|
||||
integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==
|
||||
dependencies:
|
||||
react-style-singleton "^2.2.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-remove-scroll@^2.6.3:
|
||||
version "2.7.2"
|
||||
resolved "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz"
|
||||
integrity sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==
|
||||
dependencies:
|
||||
react-remove-scroll-bar "^2.3.7"
|
||||
react-style-singleton "^2.2.3"
|
||||
tslib "^2.1.0"
|
||||
use-callback-ref "^1.3.3"
|
||||
use-sidecar "^1.1.3"
|
||||
|
||||
react-style-singleton@^2.2.2, react-style-singleton@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz"
|
||||
integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==
|
||||
dependencies:
|
||||
get-nonce "^1.0.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^17 || ^18 || ^19", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^19.2.4, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16.8.0, react@19.2.4:
|
||||
version "19.2.4"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.2.4.tgz"
|
||||
integrity sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==
|
||||
@@ -4499,7 +4672,7 @@ tsconfig-paths@^4.2.0:
|
||||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^2.0.1, tslib@^2.8.0:
|
||||
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.8.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
@@ -4679,6 +4852,21 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-callback-ref@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz"
|
||||
integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
use-sidecar@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz"
|
||||
integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==
|
||||
dependencies:
|
||||
detect-node-es "^1.1.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
use-sync-external-store@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz"
|
||||
@@ -4699,6 +4887,13 @@ vary@^1, vary@^1.1.2:
|
||||
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
|
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||
|
||||
vaul@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz"
|
||||
integrity sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==
|
||||
dependencies:
|
||||
"@radix-ui/react-dialog" "^1.1.1"
|
||||
|
||||
web-streams-polyfill@^3.0.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz"
|
||||
|
||||
Reference in New Issue
Block a user