Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 51 additions & 33 deletions app/(docs)/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { format } from "date-fns";
import MDX from "@/components/MDX";
import { Text } from "@/components/retroui/Text";
import { Metadata } from "next";
import { MoveRightIcon, MoveUpRightIcon } from "lucide-react";
import { MoveUpRightIcon } from "lucide-react";
import { generateToc } from "@/lib/toc";
import TableOfContents from "@/components/TableOfContents";
import SideNav from "@/components/SideNav";
import { Button } from "@/components/retroui/Button";

interface IProps {
params: { slug: string[] };
Expand Down Expand Up @@ -37,47 +41,61 @@ export async function generateMetadata({ params }: IProps): Promise<Metadata> {
};
}

export default function page({ params }: IProps) {
export default async function page({ params }: IProps) {
const doc = getDocParams({ params });

if (!doc) {
return notFound();
}

const toc = await generateToc(doc.body.raw);
return (
<div className="space-y-12 py-8">
<div className="border-b border-black pb-6">
<Text as="h1" className="text-4xl">{doc.title}</Text>
<p className="text-lg text-muted-foreground mt-2">{doc.description}</p>
{doc.links && (
<div className="flex space-x-4 text-sm mt-4 text-black">
{doc.links?.api_reference && (
<a
className="flex items-center bg-gray-200 px-1.5 py-.5"
href={doc.links.api_reference}
target="_blank"
>
API Reference <MoveUpRightIcon className="h-3 w-3 ml-1" />
</a>
)}
{doc.links && doc.links?.source && (
<a
className="flex items-center bg-gray-200 px-1.5 py-.5"
href={doc.links.source}
target="_blank"
>
Source <MoveUpRightIcon className="h-3 w-3 ml-1" />
</a>
)}
</div>
)}
<div className="flex lg:gap-20 items-start">
{/* Sidebar */}
<div className="hidden lg:block w-60 flex-shrink-0 sticky top-28 self-start">
<SideNav />
</div>
<div>
<MDX code={doc.body.code} />

{/* Main Content */}
<div className="flex-1 space-y-12 py-12 px-4">
<div className="border-b border-black pb-6">
<Text as="h1" className="text-4xl">{doc.title}</Text>
<p className="text-lg text-muted-foreground mt-2">{doc.description}</p>
{doc.links && (
<div className="flex space-x-4 text-sm mt-4 text-black">
{doc.links?.api_reference && (
<a
className="flex items-center bg-gray-200 px-1.5 py-.5"
href={doc.links.api_reference}
target="_blank"
>
API Reference <MoveUpRightIcon className="h-3 w-3 ml-1" />
</a>
)}
{doc.links && doc.links?.source && (
<a
className="flex items-center bg-gray-200 px-1.5 py-.5"
href={doc.links.source}
target="_blank"
>
Source <MoveUpRightIcon className="h-3 w-3 ml-1" />
</a>
)}
</div>
)}
</div>
<div>
<MDX code={doc.body.code} />
</div>
<p className="text-right">
Last Updated: {format(doc.lastUpdated, "dd MMM, yyy")}
</p>
</div>

{/* Table of Contents */}
<div className="hidden lg:block lg:w-60 flex-shrink-0 sticky top-36 self-start space-y-6">
<TableOfContents toc={toc} />
</div>
<p className="text-right">
Last Updated: {format(doc.lastUpdated, "dd MMM, yyy")}
</p>
</div>
);
}
7 changes: 2 additions & 5 deletions app/(docs)/docs/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ export default function ComponentLayout({
children: React.ReactNode;
}>) {
return (
<div className="relative max-w-6xl mx-auto">
<div className="hidden lg:block">
<SideNav />
</div>
<div className="lg:ml-72 mt-16 md:mt-10 px-4 lg:px-0">{children}</div>
<div className="relative max-w-7xl mx-auto">
<div className="max-lg:px-4">{children}</div>
</div>
);
}
27 changes: 27 additions & 0 deletions app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,30 @@ code.hljs {
.hljs-emphasis {
font-style: italic;
}

/* Custom scrollbar for sidebar */
.sidebar-scroll {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
transition: scrollbar-color 0.3s ease;
}

.sidebar-scroll:hover {
scrollbar-color: var(--color-primary) transparent;
}

/* Webkit browsers (Chrome, Safari, Edge) */
.sidebar-scroll::-webkit-scrollbar {
width: 2px;
height: 2px;
}

.sidebar-scroll::-webkit-scrollbar-track {
background: transparent;
}

.sidebar-scroll::-webkit-scrollbar-thumb {
background: transparent;
border-radius: 2px;
transition: background 0.3s ease;
}
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function RootLayout({
<body
className={`${head.variable} ${sans.variable} ${mono.variable} bg-background text-foreground`}
>
<div className="relative z-40 pb-24">
<div className="relative z-10 pb-24">
<TopNav />
</div>
{children}
Expand Down
12 changes: 6 additions & 6 deletions components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ export default function SideNav() {

return (
<div
className={`lg:fixed top-0 bottom-0 border-r-2 h-screen overflow-y-scroll transition-transform transform md:translate-x-0 w-60 bg-background flex flex-col justify-start md:justify-start py-14 md:py-8`}
// className={`border-r-2 h-screen overflow-y-scroll w-60 bg-background flex flex-col justify-start py-8`}
className="sidebar-scroll border-r-2 max-h-screen overflow-y-auto transition-transform transform md:translate-x-0 w-full bg-background flex flex-col justify-start md:justify-start py-8"
>
<nav className="flex flex-col items-start max-lg:px-6 lg:pr-6 lg:pt-26 space-y-4" aria-label="Main navigation">
<nav className="flex flex-col items-start max-lg:px-6 space-y-4" aria-label="Main navigation">
{navConfig.sideNavItems.map((item) => (
<div key={item.title} className="w-full">
<Text as="h6">{item.title}</Text>
<Text as="h5">{item.title}</Text>
<div className="flex flex-col w-full">
{item.children.map((child) => (
<Link
key={child.title}
href={child.href}
className={`px-2 py-1 w-full border border-transparent text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors ${
target={child.href.startsWith("http") ? "_blank" : "_self"}
className={`px-2 py-1 w-full border border-transparent text-muted-foreground flex items-center justify-between hover:text-foreground hover:bg-muted/50 transition-colors ${
pathname === child.href && "bg-primary text-primary-foreground"
}`}
>
{child.title}
{child.tag && (
<Badge size="sm" className="ml-2">
<Badge size="sm" className="py-0.5 px-1.5 border text-xs border-secondary text-muted-foreground bg-secondary/10">
{child.tag}
</Badge>
)}
Expand Down
41 changes: 41 additions & 0 deletions components/TableOfContents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { TableOfContents as TOCType } from '@/lib/toc';

interface TableOfContentsProps {
toc: TOCType;
}

function renderTOCItems(items: any[], level = 0) {
if (!items || items.length === 0) return null;

return (
<ul className={`space-y-1 ${level > 0 ? 'ml-4 mt-1' : ''}`}>
{items.map((item, index) => (
<li key={index}>
<a
href={item.url}
className="text-sm hover:text-black transition-colors block py-1 border-l-2 border-transparent hover:border-black pl-2"
>
{item.title}
</a>
{item.items && renderTOCItems(item.items, level + 1)}
</li>
))}
</ul>
);
}

export default function TableOfContents({ toc }: TableOfContentsProps) {
if (!toc.items || toc.items.length === 0) {
return null;
}

return (
<div className="border border-black p-4 rounded-sm max-h-60 overflow-y-auto sidebar-scroll">
<h3 className="mb-3 border-b border-black pb-2">
On this Page
</h3>
{renderTOCItems(toc.items)}
</div>
);
}
23 changes: 21 additions & 2 deletions config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ const utilsRoute = "/docs/utils";

export const navConfig: INavigationConfig = {
topNavItems: [
{ title: "Documentation", href: "/docs" },
{ title: "Docs", href: "/docs" },
{ title: "Components", href: `${componentsRoute}/button` },
{ title: "Blogs", href: "/blogs" },
{ title: "Blog", href: "/blogs" },
],
sideNavItems: [
{
Expand All @@ -19,6 +19,25 @@ export const navConfig: INavigationConfig = {
title: "Installation",
href: "/docs/install",
},
// {
// title: "Changelog",
// href: "https://pro.retroui.dev",
// },
{
title: "Blocks",
href: "https://pro.retroui.dev/blocks",
tag: "Pro",
},
{
title: "Templates",
href: "https://pro.retroui.dev/templates",
tag: "Pro",
},
{
title: "Figma Kit",
href: "https://pro.retroui.dev/figma",
tag: "Pro",
}
],
},
{
Expand Down
7 changes: 5 additions & 2 deletions contentlayer.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { visit } from "unist-util-visit";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeSlug from "rehype-slug";
import remarkToc from 'remark-toc'
import { u } from "unist-builder";
import { UnistNode } from "./types/unist";
import { componentConfig } from "./config";
Expand Down Expand Up @@ -40,7 +41,7 @@ export const Doc = defineDocumentType(() => ({
url: {
type: "string",
resolve: (doc) => `/${doc._raw.flattenedPath}`,
},
}
},
}));

Expand Down Expand Up @@ -77,7 +78,9 @@ export const Blog = defineDocumentType(() => ({

export default makeSource({
mdx: {
remarkPlugins: [],
remarkPlugins: [
remarkToc
],
rehypePlugins: [
rehypeSlug,

Expand Down
79 changes: 79 additions & 0 deletions lib/toc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { toc } from "mdast-util-toc"
import { remark } from "remark"
import { visit } from "unist-util-visit"
import type { VFile } from "vfile"

const textTypes = ["text", "emphasis", "strong", "inlineCode"] as const

function flattenNode(node: any): string {
const p: string[] = []
visit(node as any, (node: any) => {
if (!textTypes.includes(node.type as any)) return
if ('value' in node && typeof node.value === 'string') {
p.push(node.value)
}
})
return p.join('')
}

interface Item {
title: string
url: string
items?: Item[]
}

interface Items {
items?: Item[]
}

function getItems(node: any, current: any): Items {
if (!node) {
return {}
}

if (node.type === "paragraph") {
visit(node, (item: any) => {
if (item.type === "link") {
current.url = item.url
current.title = flattenNode(node)
}

if (item.type === "text") {
current.title = flattenNode(node)
}
})

return current
}

if (node.type === "list") {
current.items = node.children.map((i: any) => getItems(i, {}))

return current
} else if (node.type === "listItem") {
const heading = getItems(node.children[0], {})

if (node.children.length > 1) {
getItems(node.children[1], heading)
}

return heading
}

return {}
}

const getToc = () => (node: any, file: VFile) => {
const table = toc(node, { maxDepth: 3 })
const items = getItems(table.map, {})

file.data = { ...file.data, ...items }
}

export type TableOfContents = Items

export const generateToc = async (content: string): Promise<TableOfContents> => {
const result = await remark().use(getToc).process(content)

return result.data as TableOfContents
}
Loading