Skip to content

Commit

Permalink
changes v1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
SkidGod4444 committed Jan 13, 2025
1 parent 2e762da commit 5d4e143
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 132 deletions.
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions content/guide/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Introduction
title: Plura Guide
description: A sleek and modern template for building documentation and blogs with Next.js 15, MDX, Velite, and Shadcn UI components
published: true
toc:
Expand Down Expand Up @@ -41,7 +41,7 @@ date: 2024-03-14
</Card>
</div>

## Setup Guide
### Setup Guide

This is a ready-to-use template that comes with everything you need to build a modern documentation site or blog using Next.js 15, MDX, Velite, and Shadcn UI components.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(routes)/guide/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export default async function GuidePage({ params }: { params: Promise<DocPagePro
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] pt-4">
{doc.toc.visible && (
<DashboardTableOfContents toc={doc.toc.content} />
<DashboardTableOfContents toc={doc.toc.content} github={doc.github} />
)}
</div>
</div>
Expand Down
143 changes: 93 additions & 50 deletions src/components/mdx/toc.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { useMounted } from "@/hooks/use-mounted";
import { ListTree, PencilLine } from "lucide-react";
import Link from "next/link";
import { Button } from "../ui/button";
import Newsletter from "../sidebar/newsletter";

interface TocEntry {
Expand All @@ -14,9 +15,10 @@ interface TocEntry {

interface TocProps {
toc: TocEntry[];
github?: string;
}

export function DashboardTableOfContents({ toc }: TocProps) {
export function DashboardTableOfContents({ toc, github }: TocProps) {
const itemIds = React.useMemo(
() =>
toc
Expand All @@ -28,45 +30,29 @@ export function DashboardTableOfContents({ toc }: TocProps) {
: [],
[toc]
);
const activeHeading = useActiveItem(itemIds);
const mounted = useMounted();

return mounted ? (
<div className="space-y-2">
<p className="font-medium">On this page</p>
<div className="flex flex-col space-x-1 relative">
<div className="w-1 flex h-full bg-neutral-900 absolute"></div>
<Tree tree={toc} activeItem={activeHeading} />
</div>

{/* Newsletter component remains at bottom */}
<div className="mt-4">
<Newsletter />
</div>
</div>
) : null;
}

function useActiveItem(itemIds: (string | undefined)[]) {
const [activeId, setActiveId] = React.useState<string>("");


const [activeHeading, setActiveHeading] = React.useState<string>("");

React.useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const id = entry.target.id;
if (entry.isIntersecting) {
setActiveId(entry.target.id);
setActiveHeading(id);
// Update URL hash without scrolling
window.history.replaceState(null, '', `#${id}`);
}
});
},
{ rootMargin: `0% 0% -40% 0%` }
{
rootMargin: '-0% 0% -25% 0%',
threshold: 1.0
}
);

itemIds?.forEach((id) => {
if (!id) {
return;
}

if (!id) return;
const element = document.getElementById(id);
if (element) {
observer.observe(element);
Expand All @@ -75,10 +61,7 @@ function useActiveItem(itemIds: (string | undefined)[]) {

return () => {
itemIds?.forEach((id) => {
if (!id) {
return;
}

if (!id) return;
const element = document.getElementById(id);
if (element) {
observer.unobserve(element);
Expand All @@ -87,40 +70,98 @@ function useActiveItem(itemIds: (string | undefined)[]) {
};
}, [itemIds]);

return activeId;
const mounted = useMounted();

const handleClick = React.useCallback((url: string) => {
const id = url.slice(1);
const element = document.getElementById(id);
if (element) {
// Prevent default scroll behavior
const topOffset = element.getBoundingClientRect().top + window.scrollY - 100;
window.scrollTo({
top: topOffset,
behavior: 'smooth'
});
setActiveHeading(id);
}
}, []);

return mounted ? (
<div className="space-y-2">
<div className="flex flex-row gap-2 text-muted-foreground">
<ListTree className="size-4" />
<p className="font-medium">On this page</p>
</div>
<div className="relative">
<div className="absolute left-0 top-0 h-full w-0.5 bg-muted" />
<Tree
tree={toc}
activeItem={activeHeading}
onItemClick={handleClick}
/>
</div>

<div className="flex flex-col border-t py-5 w-full gap-10">
<Link href={github || "#"}>
<Button size={"sm"} variant={"secondary"}>
<span>Edit this page on GitHub</span>
<PencilLine className="h-4 w-4" />
</Button>
</Link>

<Newsletter/>
</div>
</div>
) : null;
}

interface TreeProps {
tree: TocEntry[];
level?: number;
activeItem?: string | null;
activeItem?: string;
onItemClick: (url: string) => void;
}

function Tree({ tree, level = 1, activeItem }: TreeProps) {
function Tree({ tree, level = 1, activeItem, onItemClick }: TreeProps) {
return tree.length && level < 5 ? (
<ul className={cn("m-0 list-none text-sm")}>
<ul className={cn("m-0 list-none text-sm", level !== 1 && "ml-4")}>
{tree.map((item, index) => {
const isActive = item.url === `#${activeItem}`;
const hasChildren = item.items?.length ?? 0 > 0;
const isParentActive = item.items?.some(
(child) => `#${activeItem}` === child.url
);
return (
<li key={index} className={cn("mt-0")}>
<a
href={item.url}
<li key={index} className={cn("mt-0 relative")}>
{level !== 1 && (
<div
className={cn(
"absolute w-3 h-full border-l-2 border-b-2 rounded-bl-xl -left-4 top-0",
(isActive || isParentActive) ? "border-primary" : "border-muted"
)}
style={{
height: hasChildren ? '20%' : '50%'
}}
/>
)}
<button
onClick={() => onItemClick(item.url)}
className={cn(
"inline-block no-underline py-2 p-2 relative rounded hover:text-neutral-200 transition-all duration-300 ease-in-out",
item.url === `#${activeItem}`
? "text-primary font-medium"
: "text-muted-foreground"
"w-full text-left py-2 px-4 relative rounded hover:text-primary transition-all duration-300 ease-in-out",
isActive ? "text-primary font-medium" : "text-muted-foreground"
)}
>
{item.url === `#${activeItem}` && (
<span className="bg-neutral-400 rounded h-full w-1 ml-[-3px] absolute left-0 top-0"></span>
{isActive && (
<span className="bg-primary rounded-xl h-[30px] w-0.5 absolute left-0 top-0" />
)}
{item.title}
</a>
</button>
{item.items?.length ? (
<Tree
<Tree
tree={item.items}
level={level + 1}
activeItem={activeItem}
onItemClick={onItemClick}
/>
) : null}
</li>
Expand All @@ -129,3 +170,5 @@ function Tree({ tree, level = 1, activeItem }: TreeProps) {
</ul>
) : null;
}

export default DashboardTableOfContents;
38 changes: 24 additions & 14 deletions src/components/sidebar/app-header.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
"use client"
import { Ellipsis } from "lucide-react"
import { Ellipsis, MessageCircleQuestion } from "lucide-react"
import Image from 'next/image';
import { useState } from "react";
import { EllipsisLinks } from "./ellipsis-links";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Button } from "../ui/button";
import { GitHubLogoIcon } from "@radix-ui/react-icons";

export function AppHeader() {

const [isOpen, setIsOpen] = useState(false);
export function AppHeader() {

return (
<div className="flex items-center justify-between p-2 border-solid border-b-[1px] border-sidebar-border">
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 select-none">
<Image src="/plura-logo.png" alt="plura-logo" className="invert dark:invert-0" width={ 32 } height={ 32 }/>
<h3 className="font-bold text-xl tracking-wide">PLURA</h3>
<h3 className="text-lg font-semibold">PLURA-AI</h3>
</div>
{/* flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground */}
<div className="relative">
<button className="hover:rounded-sm px-1 py-1 hover:bg-sidebar-hover" onClick={ () => setIsOpen(!isOpen) }>
<Ellipsis className="hover:invert dark:invert-0"/>
</button>
<Popover>
<PopoverTrigger>
<Ellipsis className="size-5"/>
</PopoverTrigger>
<PopoverContent className="flex flex-col w-60 items-start justify-center rounded-xl p-2">
<Button variant={"ghost"} className="w-full justify-start">
<GitHubLogoIcon /> GitHub
</Button>

<div className="absolute z-50 -left-24">
{ isOpen ? <EllipsisLinks /> : null }
</div>
<Button variant={"ghost"} className="w-full justify-start">
<MessageCircleQuestion /> Support
</Button>
</PopoverContent>
</Popover>
</div>
</div>
)
Expand Down
12 changes: 8 additions & 4 deletions src/components/sidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import {
SidebarFooter,
SidebarHeader,
} from "@/components/ui/sidebar";
import { NavGuide } from "./nav-guide";
import { SideBarNav } from "./sidebar-nav";
import { sidebarConfig } from "@/config/sidebar.config";
import { ProjectSwitcher } from "./project-switcher";
import DialogDemo from "../searchbar/Searchbar";
import ThemeToggler from "../theme/toggler";
import { Separator } from "../ui/separator";
import { AppHeader } from "./app-header";
import SearchBar from "./search-bar";
import { usePathname } from "next/navigation";


export function AppSidebar() {
const pathname = usePathname();
const sidebarItem = pathname.includes("guide") ? sidebarConfig.guide : pathname.includes("ai-sdk") ? sidebarConfig.aiSdk : sidebarConfig.plura;

return (
<Sidebar>
<SidebarHeader>
Expand All @@ -24,10 +28,10 @@ export function AppSidebar() {
options={sidebarConfig.projectOptions}
defaultOption={sidebarConfig.projectOptions[0]}
/>
<DialogDemo />
<SearchBar />
</SidebarHeader>
<SidebarContent>
<NavGuide items={sidebarConfig.guide} />
<SideBarNav items={sidebarItem} />
</SidebarContent>
<SidebarFooter >
<Separator />
Expand Down
15 changes: 0 additions & 15 deletions src/components/sidebar/ellipsis-links.tsx

This file was deleted.

11 changes: 4 additions & 7 deletions src/components/sidebar/newsletter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@ import {
import { SidebarInput } from "@/components/ui/sidebar";
export default function Newsletter() {
return (
<Card className="shadow-none">
<form>
<CardHeader className="p-4 pb-0">
<CardTitle className="text-sm">Subscribe to our newsletter</CardTitle>
<Card className="shadow-none p-2">
<CardHeader className="p-2">
<CardTitle className="text-sm">Get quick updates!</CardTitle>
<CardDescription>
Opt-in to receive updates and news about the Plura.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-2.5 p-4">
<CardContent className="grid gap-2.5 p-2">
<SidebarInput type="email" placeholder="Email" />
<Button
className="w-full bg-sidebar-primary text-sidebar-primary-foreground shadow-none"
size="sm"
>
Subscribe
</Button>
</CardContent>
</form>
</Card>
);
}
Loading

0 comments on commit 5d4e143

Please sign in to comment.