Skip to content

Commit df631bf

Browse files
committed
feat: initial docs page + layout + components ✨
1 parent 08a0178 commit df631bf

13 files changed

Lines changed: 454 additions & 14 deletions

File tree

src/app/[slug]/page.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { Metadata } from "next";
2+
3+
import { allDocs } from "content-collections";
4+
import { notFound } from "next/navigation";
5+
6+
import { MDX } from "@/mdx/mdx";
7+
import Article from "@/components/article";
8+
import ToC from "@/components/layout/toc";
9+
import ArticleHeader from "@/components/articleHeader";
10+
11+
type DocPageProps = {
12+
params: Promise<{ slug: string }>;
13+
};
14+
15+
export async function generateMetadata({
16+
params,
17+
}: DocPageProps): Promise<Metadata> {
18+
const { slug } = await params;
19+
const document = allDocs.find((post) => post.slug === slug);
20+
21+
if (!document) {
22+
return notFound();
23+
}
24+
25+
return {
26+
title: document.title,
27+
description: document.description,
28+
};
29+
}
30+
31+
export default async function DocPage({ params }: DocPageProps) {
32+
const { slug } = await params;
33+
const document = allDocs.find((post) => post.slug === slug);
34+
35+
if (!document) {
36+
return notFound();
37+
}
38+
39+
return (
40+
<>
41+
<ArticleHeader data={document} />
42+
<Article>
43+
<MDX code={document.mdx} />
44+
</Article>
45+
<ToC tocData={document.toc} />
46+
</>
47+
);
48+
}

src/app/layout.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { cn } from "@/utils/cn";
88

99
// Providers:
1010
import { ThemeProvider } from "@/providers/themeProvider";
11+
import Sidebar from "@/components/layout/sidebar";
1112

1213
// Metadata:
1314
export const metadata: Metadata = {
@@ -26,9 +27,10 @@ export default function RootLayout({
2627
className={cn(
2728
`${fontSans.variable} ${fontMono.variable}`,
2829
"font-sans antialiased",
29-
"bg-neutral-100 dark:bg-neutral-900",
30-
"text-neutral-900 dark:text-neutral-50",
31-
"scroll-smooth"
30+
"bg-neutral-50 dark:bg-neutral-900",
31+
"text-black dark:text-white",
32+
"selection:bg-neutral-200 selection:text-neutral-900 dark:selection:bg-neutral-800 dark:selection:text-neutral-50",
33+
"scroll-smooth",
3234
)}
3335
>
3436
<ThemeProvider
@@ -37,7 +39,10 @@ export default function RootLayout({
3739
enableSystem
3840
disableTransitionOnChange
3941
>
40-
{children}
42+
<Sidebar />
43+
<main className={cn("container mx-auto max-w-3xl py-12")}>
44+
{children}
45+
</main>
4146
</ThemeProvider>
4247
</body>
4348
</html>

src/app/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { Metadata } from "next";
33
import { allDocs } from "content-collections";
44
import { notFound } from "next/navigation";
55
import { MDX } from "@/mdx/mdx";
6+
import Article from "@/components/article";
7+
import ToC from "@/components/layout/toc";
68

79
const indexPage = "index";
810

@@ -28,7 +30,10 @@ const Page = () => {
2830

2931
return (
3032
<>
31-
<MDX code={document.mdx} />
33+
<Article>
34+
<MDX code={document.mdx} />
35+
</Article>
36+
<ToC tocData={document.toc} />
3237
</>
3338
);
3439
};

src/components/article.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { cn } from "@/utils/cn";
2+
import type { ReactNode } from "react";
3+
4+
interface ArticleProps {
5+
children: ReactNode;
6+
className?: string;
7+
}
8+
9+
const Article = (props: ArticleProps) => {
10+
return (
11+
<article
12+
className={cn(
13+
"max-w-full",
14+
"prose prose-neutral dark:prose-invert",
15+
"prose-headings:font-bold prose-headings:font-headings prose-headings:tracking-tight prose-headings:scroll-mt-24",
16+
"prose-h1:mb-0 prose-h2:mb-5 prose-h2:font-semibold",
17+
"prose-a:underline-offset-[4px] prose-a:decoration-solid dark:prose-a:decoration-neutral-500 prose-a:decoration-neutral-400",
18+
"prose-figure:my-1 prose-p:mb-3 prose-p:text-pretty",
19+
"prose-ol:mb-2 prose-ul:mb-1",
20+
"prose-th:text-start",
21+
"prose-code:p-0",
22+
"prose-inline-code:rounded prose-inline-code:font-mono prose-inline-code:p-[2px] prose-inline-code:font-normal prose-inline-code:border prose-inline-code:border-neutral-300 prose-inline-code:bg-neutral-200/50 prose-inline-code:dark:border-neutral-800 prose-inline-code:dark:bg-neutral-800/50",
23+
"prose-quoteless prose-blockquote:not-italic prose-blockquote:text-neutral-700 dark:prose-blockquote:text-neutral-300 prose-blockquote:text-sm",
24+
props.className,
25+
)}
26+
>
27+
{props.children}
28+
</article>
29+
);
30+
};
31+
32+
export default Article;

src/components/articleHeader.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Doc } from "content-collections";
2+
3+
interface ArticleHeaderProps {
4+
data: Doc;
5+
}
6+
7+
const ArticleHeader = (props: ArticleHeaderProps) => {
8+
return (
9+
<header className="mb-8 flex flex-col space-y-3 border-b border-dashed border-neutral-200 py-4 dark:border-neutral-800">
10+
<h2 className="font-headings text-4xl font-semibold tracking-tight">
11+
{props.data.title}
12+
</h2>
13+
<p className="font-mono tracking-tight text-neutral-500 dark:text-neutral-400">
14+
{props.data.description}
15+
</p>
16+
</header>
17+
);
18+
};
19+
20+
export default ArticleHeader;

src/components/changeTheme.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { ChevronDown, ChevronUp, MoonIcon, SunIcon } from "lucide-react";
5+
import { useTheme } from "next-themes";
6+
7+
import { Button } from "@/components/ui/button";
8+
import {
9+
DropdownMenu,
10+
DropdownMenuContent,
11+
DropdownMenuItem,
12+
DropdownMenuTrigger,
13+
} from "@/components/ui/dropdown-menu";
14+
15+
const ChangeTheme = () => {
16+
const { theme, setTheme } = useTheme();
17+
const [isOpen, setIsOpen] = useState<boolean>(false);
18+
const [mounted, setMounted] = useState<boolean>(false);
19+
20+
useEffect(() => {
21+
setMounted(true);
22+
}, []);
23+
24+
return (
25+
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
26+
<DropdownMenuTrigger asChild>
27+
<Button
28+
variant="outline"
29+
className="flex w-full items-center justify-between px-3 shadow-none"
30+
>
31+
<div className="flex items-center space-x-[9px]">
32+
{mounted &&
33+
(theme === "light" ? (
34+
<MoonIcon
35+
size={16}
36+
strokeWidth={1.5}
37+
className="animate-in fade-in-20 duration-500"
38+
/>
39+
) : (
40+
<SunIcon
41+
size={16}
42+
strokeWidth={1.5}
43+
className="animate-in fade-in-20 duration-500"
44+
/>
45+
))}
46+
<span>Change Theme</span>
47+
</div>
48+
{isOpen ? (
49+
<ChevronUp
50+
size={14}
51+
className="text-neutral-400 dark:text-neutral-500"
52+
/>
53+
) : (
54+
<ChevronDown
55+
size={14}
56+
className="text-neutral-400 dark:text-neutral-500"
57+
/>
58+
)}
59+
<span className="sr-only">Change Theme</span>
60+
</Button>
61+
</DropdownMenuTrigger>
62+
<DropdownMenuContent align="end">
63+
<DropdownMenuItem onClick={() => setTheme("light")}>
64+
Light
65+
</DropdownMenuItem>
66+
<DropdownMenuItem onClick={() => setTheme("dark")}>
67+
Dark
68+
</DropdownMenuItem>
69+
<DropdownMenuItem onClick={() => setTheme("system")}>
70+
System
71+
</DropdownMenuItem>
72+
</DropdownMenuContent>
73+
</DropdownMenu>
74+
);
75+
};
76+
77+
export default ChangeTheme;

src/components/hero.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { ReactNode } from "react";
2+
import { SparkleCard } from "./ui/sparkleCard";
3+
4+
import { Reactjs, Tailwind } from "@react-symbols/icons";
5+
import ExternalLink from "./ui/externalLink";
6+
import { buttonVariants } from "./ui/button";
7+
import { Shiki } from "./ui/icons";
8+
9+
interface HeroProps {
10+
children: ReactNode;
11+
}
12+
13+
const technologies = [
14+
{
15+
name: "Shiki",
16+
icon: <Shiki width={25} height={25} aria-label="Shiki" />,
17+
url: "https://shiki.style/",
18+
},
19+
{
20+
name: "React",
21+
icon: <Reactjs width={32} height={32} aria-label="React" />,
22+
url: "https://react.dev/",
23+
},
24+
{
25+
name: "Tailwind CSS",
26+
icon: <Tailwind width={32} height={32} aria-label="Tailwind CSS" />,
27+
url: "https://tailwindcss.com/",
28+
},
29+
];
30+
31+
const Hero = (props: HeroProps) => {
32+
return (
33+
<SparkleCard
34+
className="not-prose"
35+
sparklePositions={["top-left", "bottom-right"]}
36+
>
37+
<div className="flex flex-col space-y-0.5">
38+
<h2 className="font-headings text-black dark:text-white text-3xl font-semibold tracking-tight">
39+
Codeblocks
40+
</h2>
41+
<p className="text-lg tracking-tighter text-neutral-600 dark:text-neutral-400">
42+
A beautifully code block component for React & MDX.
43+
</p>
44+
</div>
45+
<div className="my-3 flex items-center justify-start space-x-0.5">
46+
{technologies.map((tech) => (
47+
<ExternalLink
48+
key={tech.name}
49+
title={tech.name}
50+
href={tech.url}
51+
className={buttonVariants({
52+
variant: "ghost",
53+
size: "icon",
54+
})}
55+
>
56+
{tech.icon}
57+
</ExternalLink>
58+
))}
59+
</div>
60+
<div>{props.children}</div>
61+
</SparkleCard>
62+
);
63+
};
64+
65+
export default Hero;

src/components/layout/links.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { LinkProps } from "next/link";
2+
import { cn } from "@/utils/cn";
3+
import Link from "next/link";
4+
import type { ReactNode } from "react";
5+
6+
interface LayoutLinkProps extends LinkProps {
7+
children: ReactNode;
8+
activeLink?: boolean;
9+
}
10+
11+
const LayoutLink = (props: LayoutLinkProps) => {
12+
return (
13+
<Link
14+
className={cn(
15+
"py-1.5 pl-4 font-mono text-sm tracking-tight",
16+
"border-l-2",
17+
props.activeLink
18+
? "border-neutral-400 dark:text-neutral-50"
19+
: "border-neutral-200 text-neutral-600 hover:text-neutral-900 dark:border-neutral-800 dark:text-neutral-400",
20+
)}
21+
{...props}
22+
>
23+
{props.children}
24+
</Link>
25+
);
26+
};
27+
28+
export default LayoutLink;

0 commit comments

Comments
 (0)