Configure eslint & prettier

This commit is contained in:
SonMooSans
2023-07-30 15:46:09 +08:00
parent b14fd37e3b
commit 2160bd0d32
140 changed files with 7580 additions and 5068 deletions
+9 -9
View File
@@ -1,11 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["next-docs-zeta", "next-docs-ui"]],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["website", "docs"]
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["next-docs-zeta", "next-docs-ui"]],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["website", "docs"]
}
+121
View File
@@ -0,0 +1,121 @@
const TAILWIND_CONFIG = {
extends: ['plugin:tailwindcss/recommended'],
rules: {
// by prettier-plugin-tailwindcss
'tailwindcss/classnames-order': 'off',
'tailwindcss/enforces-negative-arbitrary-values': 'error',
'tailwindcss/enforces-shorthand': 'error',
'tailwindcss/migration-from-tailwind-2': 'error',
'tailwindcss/no-custom-classname': 'error'
}
}
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
reportUnusedDisableDirectives: true,
ignorePatterns: ['next-env.d.ts'],
overrides: [
// Rules for all files
{
files: '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
rules: {
'prefer-object-has-own': 'error',
'logical-assignment-operators': [
'error',
'always',
{ enforceForIfStatements: true }
],
'no-negated-condition': 'off',
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
'object-shorthand': ['error', 'always'],
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/consistent-type-imports': 'error'
}
},
// React.js rules
{
files: '{packages,examples,apps}/**',
extends: [
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:@next/next/recommended'
],
rules: {
// it breaks the use of component libraries
'react/prop-types': 'off',
'react/no-unknown-property': 'off',
'react/jsx-curly-brace-presence': 'error',
'react/jsx-boolean-value': 'error'
},
settings: {
react: { version: 'detect' }
}
},
{
...TAILWIND_CONFIG,
files: 'packages/next-docs-ui/**',
settings: {
tailwindcss: {
config: 'packages/next-docs-ui/tailwind.config.js',
callees: ['cn', 'clsx']
}
}
},
{
...TAILWIND_CONFIG,
files: 'apps/docs/**',
settings: {
tailwindcss: {
config: 'apps/docs/tailwind.config.js',
callees: ['cn', 'clsx', 'cva'],
whitelist: ['nd-not-prose']
},
next: { rootDir: 'apps/docs' }
}
},
{
...TAILWIND_CONFIG,
files: 'examples/website/**',
settings: {
tailwindcss: {
config: 'examples/website/tailwind.config.js'
},
next: { rootDir: 'examples/website' }
}
},
{
files: [
'prettier.config.js',
'postcss.config.js',
'tailwind.config.js',
'next.config.js',
'.eslintrc.cjs'
],
env: {
node: true
},
rules: {
'@typescript-eslint/no-var-requires': 'off'
}
},
{
files: 'packages/{next-docs-ui,next-docs}/**',
rules: {
'@next/next/no-html-link-for-pages': 'off'
}
},
{
files: ['**/*.d.ts'],
rules: {
'no-var': 'off'
}
}
]
}
+1
View File
@@ -14,6 +14,7 @@ node_modules
out
dist
.contentlayer
.eslintcache
# production
/build
+2
View File
@@ -0,0 +1,2 @@
pnpm-lock.yaml
.changeset/*.md
+1 -1
View File
@@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
"typescript.tsdk": "node_modules\\typescript\\lib"
}
+4 -2
View File
@@ -20,7 +20,8 @@ npm install next-docs-zeta
### Next Docs UI
The framework built on top of Next Docs Zeta. It offers many out-of-the-box features along with a well-designed user interface.
The framework built on top of Next Docs Zeta. It offers many out-of-the-box
features along with a well-designed user interface.
```bash
npm install next-docs-ui
@@ -30,7 +31,8 @@ npm install next-docs-ui
[![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?style=flat-square&logo=codesandbox)](https://githubbox.com/SonMooSans/next-docs-ui-template)
View the [Template](https://github.com/SonMooSans/next-docs-ui-template) repository on Github.
View the [Template](https://github.com/SonMooSans/next-docs-ui-template)
repository on Github.
### Sources
+8 -8
View File
@@ -1,10 +1,10 @@
import { allDocs } from "contentlayer/generated";
import { initSearchAPI } from "next-docs-zeta/server";
import { allDocs } from 'contentlayer/generated'
import { initSearchAPI } from 'next-docs-zeta/server'
export const { GET } = initSearchAPI(
allDocs.map((docs) => ({
title: docs.title,
content: docs.body.raw,
url: "/docs/" + docs.slug,
}))
);
allDocs.map(docs => ({
title: docs.title,
content: docs.body.raw,
url: '/docs/' + docs.slug
}))
)
@@ -1 +1 @@
export { default } from "next-docs-ui/not-found";
export { default } from 'next-docs-ui/not-found'
+121 -130
View File
@@ -1,150 +1,141 @@
import { allDocs } from "contentlayer/generated";
import { notFound, redirect } from "next/navigation";
import { getTree } from "@/utils/page-tree";
import { findNeighbour, getTableOfContents } from "next-docs-zeta/server";
import { getMDXComponent } from "next-contentlayer/hooks";
import React from "react";
import { DocsPage } from "next-docs-ui/page";
import {
Link,
Pre,
Heading,
Card,
Cards,
MDXContent,
Table,
Image,
} from "next-docs-ui/mdx";
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger
} from '@/components/ui/accordion'
import { getTree } from '@/utils/page-tree'
import { allDocs } from 'contentlayer/generated'
import type { Metadata } from 'next'
import { getMDXComponent } from 'next-contentlayer/hooks'
import { RollButton } from 'next-docs-ui/components'
import {
Accordion,
AccordionTrigger,
AccordionItem,
AccordionContent,
} from "@/components/ui/accordion";
import { RollButton } from "next-docs-ui/components";
import { getPageUrl } from "next-docs-zeta/contentlayer";
import type { Metadata } from "next";
Card,
Cards,
Heading,
Image,
Link,
MDXContent,
Pre,
Table
} from 'next-docs-ui/mdx'
import { DocsPage } from 'next-docs-ui/page'
import { getPageUrl } from 'next-docs-zeta/contentlayer'
import { findNeighbour, getTableOfContents } from 'next-docs-zeta/server'
import { notFound, redirect } from 'next/navigation'
type Param = {
mode: string;
slug?: string[];
};
mode: string
slug?: string[]
}
export default async function Page({ params }: { params: Param }) {
const tree = getTree(params.mode);
const path = [params.mode, ...(params.slug ?? [])].join("/");
const page = allDocs.find((page) => page.slug === path);
const tree = getTree(params.mode)
const path = [params.mode, ...(params.slug ?? [])].join('/')
const page = allDocs.find(page => page.slug === path)
if (params.mode !== "ui" && params.mode !== "headless") {
redirect(`/docs/headless/${path}`);
}
if (params.mode !== 'ui' && params.mode !== 'headless') {
redirect(`/docs/headless/${path}`)
}
if (page == null) {
notFound();
}
if (page == null) {
notFound()
}
const toc = await getTableOfContents(page.body.raw);
const MDX = getMDXComponent(page.body.code);
const url = getPageUrl(page.slug.split("/"), "/docs");
const neighbours = findNeighbour(tree, url);
const toc = await getTableOfContents(page.body.raw)
const MDX = getMDXComponent(page.body.code)
const url = getPageUrl(page.slug.split('/'), '/docs')
const neighbours = findNeighbour(tree, url)
return (
<DocsPage
toc={toc}
tree={tree}
footer={neighbours}
tocContent={
<div className="pt-4 mt-4 border-t">
<a
href={`https://github.com/SonMooSans/next-docs/blob/main/apps/docs/content/${page._raw.sourceFilePath}`}
target="_blank"
rel="noreferrer noopener"
className="text-xs text-muted-foreground font-medium hover:text-foreground"
>
Edit this Page -&gt;
</a>
</div>
}
>
<MDXContent>
<div className="nd-not-prose">
<h1 className="text-3xl sm:text-4xl mb-8 font-bold">
{page.title}
</h1>
</div>
<MDX
components={{
Card: (props) => <Card {...props} />,
Cards: (props) => <Cards {...props} />,
a: (props) => <Link {...props} />,
pre: (props) => <Pre {...props} />,
img: (props) => <Image {...props} />,
h1: (props) => <Heading as="h1" {...props} />,
h2: (props) => <Heading as="h2" {...props} />,
h3: (props) => <Heading as="h3" {...props} />,
h4: (props) => <Heading as="h4" {...props} />,
h5: (props) => <Heading as="h5" {...props} />,
h6: (props) => <Heading as="h6" {...props} />,
table: (props) => <Table {...props} />,
Accordion: (props) => <Accordion {...props} />,
AccordionTrigger: (props) => (
<AccordionTrigger {...props} />
),
AccordionItem: (props) => <AccordionItem {...props} />,
AccordionContent: (props) => (
<AccordionContent {...props} />
),
blockquote: (props) => (
<div className="border rounded-lg p-3 nd-not-prose my-4 text-sm">
{props.children}
</div>
),
RollButton: (props) => <RollButton {...props} />,
}}
/>
</MDXContent>
</DocsPage>
);
return (
<DocsPage
toc={toc}
tree={tree}
footer={neighbours}
tocContent={
<div className="mt-4 border-t pt-4">
<a
href={`https://github.com/SonMooSans/next-docs/blob/main/apps/docs/content/${page._raw.sourceFilePath}`}
target="_blank"
rel="noreferrer noopener"
className="text-muted-foreground hover:text-foreground text-xs font-medium"
>
Edit this Page -&gt;
</a>
</div>
}
>
<MDXContent>
<div className="nd-not-prose">
<h1 className="mb-8 text-3xl font-bold sm:text-4xl">{page.title}</h1>
</div>
<MDX
components={{
Card: props => <Card {...props} />,
Cards: props => <Cards {...props} />,
a: props => <Link {...props} />,
pre: props => <Pre {...props} />,
img: props => <Image {...props} />,
h1: props => <Heading as="h1" {...props} />,
h2: props => <Heading as="h2" {...props} />,
h3: props => <Heading as="h3" {...props} />,
h4: props => <Heading as="h4" {...props} />,
h5: props => <Heading as="h5" {...props} />,
h6: props => <Heading as="h6" {...props} />,
table: props => <Table {...props} />,
Accordion: props => <Accordion {...props} />,
AccordionTrigger: props => <AccordionTrigger {...props} />,
AccordionItem: props => <AccordionItem {...props} />,
AccordionContent: props => <AccordionContent {...props} />,
blockquote: props => (
<div className="nd-not-prose my-4 rounded-lg border p-3 text-sm">
{props.children}
</div>
),
RollButton: props => <RollButton {...props} />
}}
/>
</MDXContent>
</DocsPage>
)
}
export function generateMetadata({ params }: { params: Param }): Metadata {
const path = [params.mode, ...(params.slug ?? [])].join("/");
const page = allDocs.find((page) => page.slug === path);
const path = [params.mode, ...(params.slug ?? [])].join('/')
const page = allDocs.find(page => page.slug === path)
if (page == null) return {};
if (page == null) return {}
const description =
page.description ??
"The headless ui library for building documentation websites";
const description =
page.description ??
'The headless ui library for building documentation websites'
return {
title: page.title,
description: description,
openGraph: {
url: "https://next-docs-zeta.vercel.app",
title: page.title,
description: description,
images: "/banner.png",
siteName: "Next Docs",
},
twitter: {
card: "summary_large_image",
creator: "@money_is_shark",
title: page.title,
description: description,
images: "/banner.png",
},
};
return {
title: page.title,
description,
openGraph: {
url: 'https://next-docs-zeta.vercel.app',
title: page.title,
description,
images: '/banner.png',
siteName: 'Next Docs'
},
twitter: {
card: 'summary_large_image',
creator: '@money_is_shark',
title: page.title,
description,
images: '/banner.png'
}
}
}
export function generateStaticParams() {
return allDocs.map((docs) => {
const [mode, ...slugs] = docs.slug.split("/");
return allDocs.map(docs => {
const [mode, ...slugs] = docs.slug.split('/')
return {
slug: slugs,
mode,
};
});
return {
slug: slugs,
mode
}
})
}
+35 -35
View File
@@ -1,46 +1,46 @@
import { ReactNode } from "react";
import { getTree } from "@/utils/page-tree";
import { DocsLayout } from "next-docs-ui/layout";
import { cn } from "@/utils/cn";
import { cn } from '@/utils/cn'
import { getTree } from '@/utils/page-tree'
import { DocsLayout } from 'next-docs-ui/layout'
import type { ReactNode } from 'react'
export default function Layout({
params,
children,
params,
children
}: {
params: { mode: string };
children: ReactNode;
params: { mode: string }
children: ReactNode
}) {
const filteredTree = getTree(params.mode);
const filteredTree = getTree(params.mode)
return (
<main
return (
<main
className={cn(
params.mode === 'ui' && '[--primary:213_94%_68%]',
params.mode === 'headless' && '[--primary:270_95%_75%]'
)}
>
<DocsLayout tree={filteredTree} nav={false}>
<div className="absolute inset-0 z-[-1] overflow-hidden">
<div
className={cn(
params.mode === "ui" && "[--primary:213_94%_68%]",
params.mode === "headless" && "[--primary:270_95%_75%]"
'to-background absolute left-0 top-0 h-[500px] w-full bg-gradient-to-br from-purple-400/20 to-50%',
params.mode === 'ui' && 'from-blue-400/30'
)}
>
<DocsLayout tree={filteredTree} nav={false}>
<div className="absolute inset-0 -z-[1] overflow-hidden">
<div
className={cn(
"absolute top-0 left-0 w-full h-[500px] bg-gradient-to-br from-purple-400/20 to-background to-50%",
params.mode === "ui" && "from-blue-400/30"
)}
/>
</div>
{children}
</DocsLayout>
</main>
);
/>
</div>
{children}
</DocsLayout>
</main>
)
}
export function generateStaticParams() {
return [
{
mode: "ui",
},
{
mode: "headless",
},
];
return [
{
mode: 'ui'
},
{
mode: 'headless'
}
]
}
+47 -52
View File
@@ -1,58 +1,53 @@
import { cn } from "@/utils/cn";
import { cva } from "class-variance-authority";
import { LayoutIcon, LibraryIcon } from "lucide-react";
import Link from "next/link";
import { cn } from '@/utils/cn'
import { cva } from 'class-variance-authority'
import { LayoutIcon, LibraryIcon } from 'lucide-react'
import Link from 'next/link'
const item = cva(
"group relative overflow-hidden rounded-xl z-[2] p-px after:absolute after:-inset-px after:-z-[1] after:duration-300 after:transition-rotate-angle after:[--rotate-angle:-20deg] hover:after:[--rotate-angle:135deg]"
);
'group relative overflow-hidden rounded-xl z-[2] p-px after:absolute after:-inset-px after:z-[-1] after:duration-300 after:transition-rotate-angle after:[--rotate-angle:-20deg] hover:after:[--rotate-angle:135deg]'
)
export default function DocsRoot() {
return (
<main className="container py-20">
<div className="absolute top-0 right-0 -translate-x-[50%] -translate-y-[50%] -z-[1] w-full max-w-[1000px] h-[500px] blur-3xl">
<div className="[mask-image:linear-gradient(to_bottom,white,transparent)] bg-gradient-to-r from-purple-400 to-blue-400 w-full h-full" />
</div>
<h1 className="text-3xl sm:text-4xl font-bold">Choose One.</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8">
<Link
href="/docs/headless"
className={cn(
item(),
"after:bg-gradient-to-animated after:from-blue-500/30 after:to-purple-400"
)}
>
<div className="rounded-xl h-full bg-background p-6 bg-gradient-to-br from-purple-400/20 group-hover:from-purple-400/10">
<LibraryIcon className="text-purple-400 dark:text-purple-200 w-9 h-9 mb-2" />
<p className="font-semibold text-lg mb-2">
Next Docs Zeta
</p>
<p className="text-sm text-muted-foreground">
The Headless UI Library for building documentation
websites.
</p>
</div>
</Link>
return (
<main className="container py-20">
<div className="absolute right-0 top-0 z-[-1] h-[500px] w-full max-w-[1000px] translate-x-[-50%] translate-y-[-50%] blur-3xl">
<div className="h-full w-full bg-gradient-to-r from-purple-400 to-blue-400 [mask-image:linear-gradient(to_bottom,white,transparent)]" />
</div>
<h1 className="text-3xl font-bold sm:text-4xl">Choose One.</h1>
<div className="mt-8 grid grid-cols-1 gap-8 md:grid-cols-2">
<Link
href="/docs/headless"
className={cn(
item(),
'after:bg-gradient-to-animated after:from-blue-500/30 after:to-purple-400'
)}
>
<div className="bg-background h-full rounded-xl bg-gradient-to-br from-purple-400/20 p-6 group-hover:from-purple-400/10">
<LibraryIcon className="mb-2 h-9 w-9 text-purple-400 dark:text-purple-200" />
<p className="mb-2 text-lg font-semibold">Next Docs Zeta</p>
<p className="text-muted-foreground text-sm">
The Headless UI Library for building documentation websites.
</p>
</div>
</Link>
<Link
href="/docs/ui"
className={cn(
item(),
"after:bg-gradient-to-animated after:from-pink-500/20 after:to-blue-400"
)}
>
<div className="rounded-xl bg-background p-6 h-full bg-gradient-to-br from-blue-400/20 group-hover:from-blue-400/10">
<LayoutIcon className="text-cyan-400 dark:text-cyan-200 w-9 h-9 mb-2" />
<p className="font-semibold text-lg mb-2">
Next Docs UI
</p>
<p className="text-sm text-muted-foreground">
The Framework for building documentation websites
with well designed UI.
</p>
</div>
</Link>
</div>
</main>
);
<Link
href="/docs/ui"
className={cn(
item(),
'after:bg-gradient-to-animated after:from-pink-500/20 after:to-blue-400'
)}
>
<div className="bg-background h-full rounded-xl bg-gradient-to-br from-blue-400/20 p-6 group-hover:from-blue-400/10">
<LayoutIcon className="mb-2 h-9 w-9 text-cyan-400 dark:text-cyan-200" />
<p className="mb-2 text-lg font-semibold">Next Docs UI</p>
<p className="text-muted-foreground text-sm">
The Framework for building documentation websites with well
designed UI.
</p>
</div>
</Link>
</div>
</main>
)
}
+84 -87
View File
@@ -1,103 +1,100 @@
import { Inter } from "next/font/google";
import { ExternalLinkIcon, Star } from "lucide-react";
import { RootProvider } from "next-docs-ui/provider";
import { Nav } from "@/components/nav";
import "next-docs-ui/style.css";
import "./style.css";
import { Nav } from '@/components/nav'
import { ExternalLinkIcon, Star } from 'lucide-react'
import { RootProvider } from 'next-docs-ui/provider'
import { Inter } from 'next/font/google'
import 'next-docs-ui/style.css'
import './style.css'
export const metadata = {
title: {
template: '%s | Next Docs',
default: 'Next Docs'
},
description: 'The headless ui library for building a documentation website',
openGraph: {
url: 'https://next-docs-zeta.vercel.app',
title: {
template: "%s | Next Docs",
default: "Next Docs",
template: '%s | Next Docs',
default: 'Next Docs'
},
description: "The headless ui library for building a documentation website",
openGraph: {
url: "https://next-docs-zeta.vercel.app",
title: {
template: "%s | Next Docs",
default: "Next Docs",
},
description:
"The headless ui library for building a documentation website",
images: "/banner.png",
siteName: "Next Docs",
description: 'The headless ui library for building a documentation website',
images: '/banner.png',
siteName: 'Next Docs'
},
twitter: {
card: 'summary_large_image',
creator: '@money_is_shark',
title: {
template: '%s | Next Docs',
default: 'Next Docs'
},
twitter: {
card: "summary_large_image",
creator: "@money_is_shark",
title: {
template: "%s | Next Docs",
default: "Next Docs",
},
description:
"The headless ui library for building a documentation website",
images: "/banner.png",
},
metadataBase:
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: `https://${process.env.VERCEL_URL}`,
};
description: 'The headless ui library for building a documentation website',
images: '/banner.png'
},
metadataBase:
process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: `https://${process.env.VERCEL_URL}`
}
const inter = Inter({
subsets: ["latin"],
});
subsets: ['latin']
})
export default function RootLayout({
children,
children
}: {
children: React.ReactNode;
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body className="relative flex flex-col min-h-screen">
<RootProvider
search={{
links: [
["Home", "/"],
["UI Docs", "/docs/ui"],
["Headless Docs", "/docs/headless"],
],
}}
>
<Nav />
{children}
<Footer />
</RootProvider>
</body>
</html>
);
return (
<html lang="en" className={inter.className}>
<body className="relative flex min-h-screen flex-col">
<RootProvider
search={{
links: [
['Home', '/'],
['UI Docs', '/docs/ui'],
['Headless Docs', '/docs/headless']
]
}}
>
<Nav />
{children}
<Footer />
</RootProvider>
</body>
</html>
)
}
function Footer() {
return (
<footer className="mt-auto border-t py-12 bg-secondary text-secondary-foreground">
<div className="container flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<p className="text-sm font-semibold mb-1">NEXT DOCS</p>
<p className="text-xs">Built with by Fuma</p>
</div>
return (
<footer className="bg-secondary text-secondary-foreground mt-auto border-t py-12">
<div className="container flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<p className="mb-1 text-sm font-semibold">NEXT DOCS</p>
<p className="text-xs">Built with by Fuma</p>
</div>
<div className="flex flex-row gap-20 items-center">
<a
href="https://github.com/SonMooSans/next-docs"
rel="noreferrer noopener"
className="flex flex-row items-center text-sm"
>
<Star className="w-4 h-4 mr-2" />
Give us a star
</a>
<a
href="https://www.npmjs.com/package/next-docs-zeta"
rel="noreferrer noopener"
className="flex flex-row items-center text-sm"
>
<ExternalLinkIcon className="w-4 h-4 mr-2" />
NPM registry
</a>
</div>
</div>
</footer>
);
<div className="flex flex-row items-center gap-20">
<a
href="https://github.com/SonMooSans/next-docs"
rel="noreferrer noopener"
className="flex flex-row items-center text-sm"
>
<Star className="mr-2 h-4 w-4" />
Give us a star
</a>
<a
href="https://www.npmjs.com/package/next-docs-zeta"
rel="noreferrer noopener"
className="flex flex-row items-center text-sm"
>
<ExternalLinkIcon className="mr-2 h-4 w-4" />
NPM registry
</a>
</div>
</div>
</footer>
)
}
+186 -202
View File
@@ -1,206 +1,190 @@
import { cn } from "@/utils/cn";
import { CopyIcon } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { ComponentPropsWithRef, ComponentPropsWithoutRef } from "react";
import { cn } from '@/utils/cn'
import { CopyIcon } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import type { ComponentPropsWithoutRef, ComponentPropsWithRef } from 'react'
export default function HomePage() {
return (
<main
className={cn(
"flex flex-col text-muted-foreground",
"[--foreground:186_93%_30%] dark:[--foreground:186_93%_81%] [--muted-foreground:202_57%_49%] dark:[--muted-foreground:202_57%_69%]"
)}
return (
<main
className={cn(
'text-muted-foreground flex flex-col',
'[--foreground:186_93%_30%] [--muted-foreground:202_57%_49%] dark:[--foreground:186_93%_81%] dark:[--muted-foreground:202_57%_69%]'
)}
>
<div className="absolute inset-x-0 top-0 flex">
<div className="bg-gradient-radial-top mx-auto h-[300px] w-[1500px] max-w-[100vw] from-blue-500/10 to-80%" />
</div>
<Image
src="/stars.png"
alt="stars"
width={650 / 1.2}
height={627 / 1.2}
className="absolute right-0 top-0 hidden dark:block"
priority
/>
<div className="absolute left-0 top-0 max-xl:hidden">
<div className="ml-72 h-[500px] w-6 -rotate-45 rounded-full bg-gradient-to-b from-transparent via-purple-400/50 via-60% to-cyan-200" />
</div>
<Star className="animate-star absolute left-[10%] top-40 text-cyan-100 delay-200 max-lg:hidden" />
<Star className="animate-star absolute left-[30%] top-72 scale-[.25] text-cyan-100 delay-700" />
<Star className="animate-star absolute right-[10%] top-64 scale-50 text-pink-200 md:top-20" />
<Star className="animate-star absolute right-[15%] top-96 text-pink-200 delay-1000 max-lg:hidden" />
<Star className="animate-star absolute right-[30%] top-64 scale-50 text-pink-200 max-lg:hidden" />
<div className="container z-[2] flex flex-col gap-4 pt-40 text-center">
<div className="relative mx-auto">
<div
className="from-foreground/30 absolute -inset-x-20 top-0 z-[-1] h-full max-w-[100vw] bg-gradient-to-l to-purple-400/30 blur-3xl"
aria-hidden
/>
<h1 className="bg-gradient-to-b from-purple-400 from-20% to-cyan-300 bg-clip-text text-3xl font-bold text-transparent dark:from-purple-200 dark:from-20% dark:to-cyan-300 sm:text-5xl sm:leading-snug">
Build Next.js Docs
<br /> With Speed
</h1>
</div>
<p>
Next Docs is a framework built for building documentation websites in
Next.js
</p>
<div className="mt-4 flex flex-row justify-center">
<Link
href="/docs"
className="rounded-full bg-gradient-to-b from-cyan-200 to-cyan-300 px-8 py-2 font-medium text-cyan-950"
>
Get Started -&gt;
</Link>
</div>
</div>
<div className="container mt-24 grid grid-cols-1 gap-10 md:grid-cols-5">
<div className="bg-background relative z-[2] flex flex-col overflow-hidden rounded-2xl border p-8 md:col-span-3">
<div className="z-[-1] mx-auto mb-28 flex rounded-3xl border bg-gradient-to-b from-transparent to-cyan-500/30 px-24 shadow-2xl shadow-cyan-400/30">
<Heart className="mx-auto" />
</div>
<div className="from-background/30 absolute inset-0 flex flex-col bg-gradient-to-b from-10% to-blue-500/30 p-8">
<div className="mt-auto text-center">
<p className="text-foreground mb-2 text-xl font-medium">
First class Developer Experience
</p>
<p className="text-sm">Install, Code, Deploy within a minute</p>
</div>
</div>
</div>
<div className="bg-background relative z-[2] flex flex-col overflow-hidden rounded-2xl border p-8 md:col-span-2">
<div className="relative z-[-1] mx-auto mb-20">
<div className="absolute inset-8 z-[-1] animate-pulse bg-cyan-300/50 blur-3xl" />
<Rocket className="text-foreground mx-auto [mask-image:linear-gradient(to_bottom,white_50%,transparent_90%)]" />
</div>
<div className="from-background/30 absolute inset-0 flex flex-col bg-gradient-to-b from-10% to-blue-500/30 p-8">
<div className="mt-auto text-center">
<p className="text-foreground mb-2 text-xl font-medium">
Lightning Fast
</p>
<p className="text-sm">
Built for App Router and work with Pages Router
</p>
</div>
</div>
</div>
</div>
<div className="mt-[-300px] h-[400px] bg-gradient-to-b from-transparent via-blue-500/10 to-transparent" />
<div className="container mb-40 mt-20 flex flex-col items-center gap-2 text-center">
<h2 className="text-foreground text-3xl font-semibold">Install Now</h2>
<p className="text-sm">Stop waiting, go type it.</p>
<pre className="text-foreground relative mt-4 rounded-full border border-blue-200/20 bg-blue-200/30 py-1.5 pl-4 pr-20 dark:bg-blue-500/20">
npm install next-docs-zeta
<button className="absolute inset-y-2 right-2 rounded-full">
<CopyIcon className="h-4 w-4" />
</button>
</pre>
</div>
</main>
)
}
function Star(props: ComponentPropsWithoutRef<'svg'>) {
return (
<svg width="91" height="268" viewBox="0 0 91 268" fill="none" {...props}>
<path
d="M91 134.177C58.7846 134.177 47.4533 48.4036 45.5 0.560455C43.5467 48.4036 32.2154 134.177 0 134.177C32.2154 134.177 43.5467 219.949 45.5 267.793C47.4533 219.949 58.7846 134.177 91 134.177Z"
fill="currentColor"
/>
</svg>
)
}
function Rocket(props: ComponentPropsWithRef<'svg'>) {
return (
<svg width="200" height="200" viewBox="0 0 512 512" {...props}>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="6"
d="M461.81 53.81a4.4 4.4 0 0 0-3.3-3.39c-54.38-13.3-180 34.09-248.13 102.17a294.9 294.9 0 0 0-33.09 39.08c-21-1.9-42-.3-59.88 7.5c-50.49 22.2-65.18 80.18-69.28 105.07a9 9 0 0 0 9.8 10.4l81.07-8.9a180.29 180.29 0 0 0 1.1 18.3a18.15 18.15 0 0 0 5.3 11.09l31.39 31.39a18.15 18.15 0 0 0 11.1 5.3a179.91 179.91 0 0 0 18.19 1.1l-8.89 81a9 9 0 0 0 10.39 9.79c24.9-4 83-18.69 105.07-69.17c7.8-17.9 9.4-38.79 7.6-59.69a293.91 293.91 0 0 0 39.19-33.09c68.38-68 115.47-190.86 102.37-247.95ZM298.66 213.67a42.7 42.7 0 1 1 60.38 0a42.65 42.65 0 0 1-60.38 0Z"
/>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="6"
d="M109.64 352a45.06 45.06 0 0 0-26.35 12.84C65.67 382.52 64 448 64 448s65.52-1.67 83.15-19.31A44.73 44.73 0 0 0 160 402.32"
/>
</svg>
)
}
function Heart(props: ComponentPropsWithoutRef<'svg'>) {
return (
<svg width="178" height="159" viewBox="0 0 178 159" fill="none" {...props}>
<g filter="url(#filter0_d_2_135)">
<path
d="M52 39H76M46 45.5L80 45.5M44 51.5L84 51.5M43 57.5H135M44 63.5H134M45 69.5H133M49 75.5H129M53 81.5H125M57 87.5H121M62 93.5H116M68 99.5H110M75 105.5H103M82.5 111.5H97.5M126 39H102M132 45.5L98 45.5M134 51.5L94 51.5"
strokeWidth="2"
strokeLinecap="round"
className="animate-heart stroke-foreground"
strokeDasharray={200}
/>
</g>
<defs>
<filter
id="filter0_d_2_135"
x="0"
y="0"
width="178"
height="158.5"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<div className="flex absolute top-0 inset-x-0">
<div className="w-[1500px] max-w-[100vw] h-[300px] mx-auto bg-gradient-radial-top from-blue-500/10 to-80%" />
</div>
<Image
src="/stars.png"
alt="stars"
width={650 / 1.2}
height={627 / 1.2}
className="absolute right-0 top-0 hidden dark:block"
priority
/>
<div className="absolute top-0 left-0 max-xl:hidden">
<div className="w-6 ml-72 h-[500px] -rotate-45 rounded-full bg-gradient-to-b from-transparent via-purple-400/50 via-60% to-cyan-200" />
</div>
<Star className="absolute top-40 left-[10%] text-cyan-100 animate-star delay-200 max-lg:hidden" />
<Star className="absolute top-72 left-[30%] scale-[.25] text-cyan-100 delay-700 animate-star" />
<Star className="absolute top-64 right-[10%] scale-50 text-pink-200 animate-star md:top-20" />
<Star className="absolute top-96 right-[15%] text-pink-200 delay-1000 animate-star max-lg:hidden" />
<Star className="absolute top-64 right-[30%] scale-50 animate-star text-pink-200 max-lg:hidden" />
<div className="pt-40 z-[2] flex flex-col gap-4 text-center container">
<div className="relative mx-auto">
<div
className="absolute top-0 -left-20 -right-20 h-full bg-gradient-to-l from-foreground/30 to-purple-400/30 blur-3xl -z-[1] max-w-[100vw]"
aria-hidden
/>
<h1 className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-b from-purple-400 from-20% to-cyan-300 dark:from-purple-200 dark:from-20% dark:to-cyan-300 sm:text-5xl sm:leading-snug">
Build Next.js Docs
<br /> With Speed
</h1>
</div>
<p>
Next Docs is a framework built for building documentation
websites in Next.js
</p>
<div className="flex flex-row justify-center mt-4">
<Link
href="/docs"
className="rounded-full bg-gradient-to-b from-cyan-200 to-cyan-300 px-8 py-2 text-cyan-950 font-medium"
>
Get Started -&gt;
</Link>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-5 container mt-24 gap-10">
<div className="relative md:col-span-3 p-8 border rounded-2xl flex flex-col overflow-hidden bg-background z-[2]">
<div className="-z-[1] mx-auto mb-28 px-24 bg-gradient-to-b from-transparent to-cyan-500/30 rounded-3xl border flex shadow-2xl shadow-cyan-400/30">
<Heart className="mx-auto" />
</div>
<div className="absolute inset-0 flex flex-col bg-gradient-to-b from-background/30 to-blue-500/30 p-8 from-10%">
<div className="mt-auto text-center">
<p className="text-xl font-medium text-foreground mb-2">
First class Developer Experience
</p>
<p className="text-sm">
Install, Code, Deploy within a minute
</p>
</div>
</div>
</div>
<div className="relative md:col-span-2 p-8 border rounded-2xl flex flex-col overflow-hidden bg-background z-[2]">
<div className="-z-[1] mx-auto mb-20 relative">
<div className="bg-cyan-300/50 inset-8 absolute blur-3xl -z-[1] animate-pulse" />
<Rocket className="mx-auto text-foreground [mask-image:linear-gradient(to_bottom,white_50%,transparent_90%)]" />
</div>
<div className="absolute inset-0 flex flex-col bg-gradient-to-b from-background/30 to-blue-500/30 p-8 from-10%">
<div className="mt-auto text-center">
<p className="text-xl font-medium text-foreground mb-2">
Lightning Fast
</p>
<p className="text-sm">
Built for App Router and work with Pages Router
</p>
</div>
</div>
</div>
</div>
<div className="bg-gradient-to-b from-transparent via-blue-500/10 to-transparent h-[400px] -mt-[300px]" />
<div className="container flex flex-col items-center text-center gap-2 mt-20 mb-40">
<h2 className="text-3xl font-semibold text-foreground">
Install Now
</h2>
<p className="text-sm">Stop waiting, go type it.</p>
<pre className="relative pl-4 pr-20 py-1.5 rounded-full border text-foreground bg-blue-200/30 dark:bg-blue-500/20 border-blue-200/20 mt-4">
npm install next-docs-zeta
<button className="absolute top-2 bottom-2 right-2 rounded-full">
<CopyIcon className="w-4 h-4" />
</button>
</pre>
</div>
</main>
);
}
function Star(props: ComponentPropsWithoutRef<"svg">) {
return (
<svg
width="91"
height="268"
viewBox="0 0 91 268"
fill="none"
{...props}
>
<path
d="M91 134.177C58.7846 134.177 47.4533 48.4036 45.5 0.560455C43.5467 48.4036 32.2154 134.177 0 134.177C32.2154 134.177 43.5467 219.949 45.5 267.793C47.4533 219.949 58.7846 134.177 91 134.177Z"
fill="currentColor"
/>
</svg>
);
}
function Rocket(props: ComponentPropsWithRef<"svg">) {
return (
<svg width="200" height="200" viewBox="0 0 512 512" {...props}>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="6"
d="M461.81 53.81a4.4 4.4 0 0 0-3.3-3.39c-54.38-13.3-180 34.09-248.13 102.17a294.9 294.9 0 0 0-33.09 39.08c-21-1.9-42-.3-59.88 7.5c-50.49 22.2-65.18 80.18-69.28 105.07a9 9 0 0 0 9.8 10.4l81.07-8.9a180.29 180.29 0 0 0 1.1 18.3a18.15 18.15 0 0 0 5.3 11.09l31.39 31.39a18.15 18.15 0 0 0 11.1 5.3a179.91 179.91 0 0 0 18.19 1.1l-8.89 81a9 9 0 0 0 10.39 9.79c24.9-4 83-18.69 105.07-69.17c7.8-17.9 9.4-38.79 7.6-59.69a293.91 293.91 0 0 0 39.19-33.09c68.38-68 115.47-190.86 102.37-247.95ZM298.66 213.67a42.7 42.7 0 1 1 60.38 0a42.65 42.65 0 0 1-60.38 0Z"
/>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="6"
d="M109.64 352a45.06 45.06 0 0 0-26.35 12.84C65.67 382.52 64 448 64 448s65.52-1.67 83.15-19.31A44.73 44.73 0 0 0 160 402.32"
/>
</svg>
);
}
function Heart(props: ComponentPropsWithoutRef<"svg">) {
return (
<svg
width="178"
height="159"
viewBox="0 0 178 159"
fill="none"
{...props}
>
<g filter="url(#filter0_d_2_135)">
<path
d="M52 39H76M46 45.5L80 45.5M44 51.5L84 51.5M43 57.5H135M44 63.5H134M45 69.5H133M49 75.5H129M53 81.5H125M57 87.5H121M62 93.5H116M68 99.5H110M75 105.5H103M82.5 111.5H97.5M126 39H102M132 45.5L98 45.5M134 51.5L94 51.5"
strokeWidth="2"
strokeLinecap="round"
className="animate-heart stroke-foreground"
strokeDasharray={200}
/>
</g>
<defs>
<filter
id="filter0_d_2_135"
x="0"
y="0"
width="178"
height="158.5"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="21" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.7875 0 0 0 0 0.9745 0 0 0 0 1 0 0 0 1 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_2_135"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_2_135"
result="shape"
/>
</filter>
</defs>
</svg>
);
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="21" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.7875 0 0 0 0 0.9745 0 0 0 0 1 0 0 0 1 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_2_135"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_2_135"
result="shape"
/>
</filter>
</defs>
</svg>
)
}
+4 -4
View File
@@ -3,11 +3,11 @@
@tailwind utilities;
* {
@apply border-border;
@apply border-border;
}
@property --rotate-angle {
syntax: "<angle>";
inherits: false;
initial-value: -45deg;
syntax: '<angle>';
inherits: false;
initial-value: -45deg;
}
+1 -1
View File
@@ -13,4 +13,4 @@
"components": "@/components",
"utils": "@/utils/cn"
}
}
}
+50 -52
View File
@@ -1,58 +1,56 @@
"use client";
import { GithubIcon } from "lucide-react";
import { useParams } from "next/navigation";
import { Nav as OriginalNav } from "next-docs-ui/components";
import Link from "next/link";
import clsx from "clsx";
'use client'
import clsx from 'clsx'
import { GithubIcon } from 'lucide-react'
import { Nav as OriginalNav } from 'next-docs-ui/components'
import Link from 'next/link'
import { useParams } from 'next/navigation'
const item =
"px-2 py-1 rounded-md text-muted-foreground transition-colors hover:text-accent-foreground";
'px-2 py-1 rounded-md text-muted-foreground transition-colors hover:text-accent-foreground'
export function Nav() {
const { mode } = useParams();
const { mode } = useParams()
return (
<OriginalNav
enableSidebar={mode === "headless" || mode === "ui"}
links={[
{
icon: (
<GithubIcon aria-label="Github" className="w-5 h-5" />
),
href: "https://github.com/SonMooSans/next-docs",
external: true,
},
]}
>
<Link
href="/"
className="font-semibold whitespace-nowrap hover:text-muted-foreground"
>
Next Docs
</Link>
<div className="max-sm:absolute max-sm:top-[50%] max-sm:left-[50%] max-sm:-translate-x-[50%] max-sm:-translate-y-[50%]">
<div className="text-sm border border-input p-1 rounded-md bg-background">
<Link
href="/docs/headless"
className={clsx(
item,
mode === "headless" &&
"bg-accent text-accent-foreground"
)}
>
Zeta
</Link>
<Link
href="/docs/ui"
className={clsx(
item,
mode === "ui" && "bg-accent text-accent-foreground"
)}
>
UI
</Link>
</div>
</div>
</OriginalNav>
);
return (
<OriginalNav
enableSidebar={mode === 'headless' || mode === 'ui'}
links={[
{
icon: <GithubIcon aria-label="Github" className="h-5 w-5" />,
href: 'https://github.com/SonMooSans/next-docs',
external: true
}
]}
>
<Link
href="/"
className="hover:text-muted-foreground whitespace-nowrap font-semibold"
>
Next Docs
</Link>
<div className="max-sm:absolute max-sm:left-[50%] max-sm:top-[50%] max-sm:translate-x-[-50%] max-sm:translate-y-[-50%]">
<div className="border-input bg-background rounded-md border p-1 text-sm">
<Link
href="/docs/headless"
className={clsx(
item,
mode === 'headless' && 'bg-accent text-accent-foreground'
)}
>
Zeta
</Link>
<Link
href="/docs/ui"
className={clsx(
item,
mode === 'ui' && 'bg-accent text-accent-foreground'
)}
>
UI
</Link>
</div>
</div>
</OriginalNav>
)
}
+47 -47
View File
@@ -1,59 +1,59 @@
"use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
'use client'
import { cn } from "@/utils/cn";
import { cn } from '@/utils/cn'
import * as AccordionPrimitive from '@radix-ui/react-accordion'
import { ChevronDown } from 'lucide-react'
import * as React from 'react'
const Accordion = AccordionPrimitive.Root;
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b nd-not-prose", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
<AccordionPrimitive.Item
ref={ref}
className={cn('nd-not-prose border-b', className)}
{...props}
/>
))
AccordionItem.displayName = 'AccordionItem'
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 items-center justify-between py-4 font-medium [&[data-state=open]>svg]:rotate-180',
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
"overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
className
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
<AccordionPrimitive.Content
ref={ref}
className={cn(
'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all',
className
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
+43 -44
View File
@@ -1,52 +1,51 @@
import * as React from "react";
import { VariantProps, cva } from "class-variance-authority";
import { cn } from "@/utils/cn";
import { cn } from '@/utils/cn'
import type { VariantProps } from 'class-variance-authority'
import { cva } from 'class-variance-authority'
import * as React from 'react'
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'underline-offset-4 hover:underline text-primary'
},
size: {
default: 'h-10 py-2 px-4',
sm: 'h-9 px-3 rounded-md',
lg: 'h-11 px-8 rounded-md'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
);
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = 'Button'
export { Button, buttonVariants };
export { Button, buttonVariants }
+19 -20
View File
@@ -1,25 +1,24 @@
import * as React from "react";
import { cn } from "@/utils/cn";
import { cn } from '@/utils/cn'
import * as React from 'react'
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = "Input";
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'border-input ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border bg-transparent px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = 'Input'
export { Input };
export { Input }
@@ -14,12 +14,12 @@ next-docs/breadcrumb
it exports a `useBreadcrumb` hook:
```tsx
import { useBreadcrumb } from "next-docs-zeta/breadcrumb";
import { useBreadcrumb } from 'next-docs-zeta/breadcrumb'
const tree: TreeNode[] = buildPageTree(allMeta, allDocs);
const pathname = usePathname();
const tree: TreeNode[] = buildPageTree(allMeta, allDocs)
const pathname = usePathname()
const items = useBreadcrumb(pathname, tree);
const items = useBreadcrumb(pathname, tree)
```
### Breadcrumb Item
@@ -34,42 +34,42 @@ const items = useBreadcrumb(pathname, tree);
Using `lucide-react` for icons.
```tsx
import { usePathname } from "next/navigation";
import type { TreeNode } from "next-docs-zeta/server";
import { useBreadcrumb } from "next-docs-zeta/breadcrumb";
import clsx from "clsx";
import { ChevronRightIcon } from "lucide-react";
import Link from "next/link";
import { Fragment } from "react";
import clsx from 'clsx'
import { ChevronRightIcon } from 'lucide-react'
import { useBreadcrumb } from 'next-docs-zeta/breadcrumb'
import type { TreeNode } from 'next-docs-zeta/server'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Fragment } from 'react'
const itemStyles = "overflow-hidden overflow-ellipsis whitespace-nowrap";
const itemStyles = 'overflow-hidden overflow-ellipsis whitespace-nowrap'
export function Breadcrumb({ tree }: { tree: TreeNode[] }) {
const pathname = usePathname();
//get breadcrumb items
const items = useBreadcrumb(pathname, tree);
const pathname = usePathname()
//get breadcrumb items
const items = useBreadcrumb(pathname, tree)
return (
<div className="flex flex-row gap-1 text-sm text-muted-foreground items-center">
<p className={itemStyles}>Docs</p>
{items.map((item, i) => {
const active = items.length === i + 1;
const style = clsx(itemStyles, active && "text-foreground");
return (
<div className="text-muted-foreground flex flex-row items-center gap-1 text-sm">
<p className={itemStyles}>Docs</p>
{items.map((item, i) => {
const active = items.length === i + 1
const style = clsx(itemStyles, active && 'text-foreground')
return (
<Fragment key={i}>
<ChevronRightIcon className="w-4 h-4 flex-shrink-0" />
{item.url != null ? (
<Link href={item.url} className={style}>
{item.name}
</Link>
) : (
<p className={style}>{item.name}</p>
)}
</Fragment>
);
})}
</div>
);
return (
<Fragment key={i}>
<ChevronRightIcon className="h-4 w-4 flex-shrink-0" />
{item.url != null ? (
<Link href={item.url} className={style}>
{item.name}
</Link>
) : (
<p className={style}>{item.name}</p>
)}
</Fragment>
)
})}
</div>
)
}
```
@@ -1,4 +1,4 @@
{
"title": "Components",
"pages": ["breadcrumb", "sidebar", "toc", "safe-link"]
"title": "Components",
"pages": ["breadcrumb", "sidebar", "toc", "safe-link"]
}
@@ -10,18 +10,19 @@ A component that wraps next/link and handles external links in the document.
Same as using `<a>`.
```tsx
import { SafeLink } from "next-docs-zeta/link";
import { SafeLink } from 'next-docs-zeta/link'
<SafeLink href="/docs/components">Click Me</SafeLink>;
;<SafeLink href="/docs/components">Click Me</SafeLink>
```
### Dynamic hrefs
Dynamic hrefs are no longer supported in Next.js App Router. Safe Link can handle dynamic hrefs by enabling `dynamicHrefs`.
Dynamic hrefs are no longer supported in Next.js App Router. Safe Link can
handle dynamic hrefs by enabling `dynamicHrefs`.
```tsx
<SafeLink href="/[lang]/components" dynamicHrefs>
Click Me
Click Me
</SafeLink>
```
@@ -31,15 +32,15 @@ You can pass it as a component in `<MDX>`.
```tsx
//in a server component
import { SafeLink } from "next-docs-zeta/link";
import { SafeLink } from 'next-docs-zeta/link'
const MDX = getMDXComponent(code);
const MDX = getMDXComponent(code)
return (
<MDX
components={{
a: SafeLink,
}}
/>
);
<MDX
components={{
a: SafeLink
}}
/>
)
```
@@ -12,14 +12,14 @@ next-docs/sidebar
## Usage
```tsx
import * as Base from "next-docs-zeta/sidebar";
import * as Base from 'next-docs-zeta/sidebar'
return (
<Base.SidebarProvider>
<Base.SidebarTrigger />
<Base.SidebarList />
</Base.SidebarProvider>
);
<Base.SidebarProvider>
<Base.SidebarTrigger />
<Base.SidebarList />
</Base.SidebarProvider>
)
```
### Sidebar Provider
@@ -48,42 +48,42 @@ Opens the sidebar on click.
Using `lucide-react` for icons:
```tsx
"use client";
import clsx from "clsx";
import { MenuIcon } from "lucide-react";
import { ReactNode } from "react";
import * as Base from "next-docs-zeta/sidebar";
import { SidebarItem } from "./item";
'use client'
import type { TreeNode } from "next-docs-zeta/server";
import clsx from 'clsx'
import { MenuIcon } from 'lucide-react'
import type { TreeNode } from 'next-docs-zeta/server'
import * as Base from 'next-docs-zeta/sidebar'
import { ReactNode } from 'react'
import { SidebarItem } from './item'
export function SidebarProvider({ children }: { children: ReactNode }) {
return (
<Base.SidebarProvider>
<Base.SidebarTrigger className="flex flex-row gap-2 text-sm px-8 items-center">
<MenuIcon className="w-4 h-4" />
Menu
</Base.SidebarTrigger>
{children}
</Base.SidebarProvider>
);
return (
<Base.SidebarProvider>
<Base.SidebarTrigger className="flex flex-row items-center gap-2 px-8 text-sm">
<MenuIcon className="h-4 w-4" />
Menu
</Base.SidebarTrigger>
{children}
</Base.SidebarProvider>
)
}
export function Sidebar({ items }: { items: TreeNode[] }) {
return (
<Base.SidebarList
minWidth={1024} // when should it being displayed (ignore the trigger)
className={clsx(
"flex flex-col gap-3 fixed inset-0 top-24 overflow-auto",
"lg:sticky lg:top-12 lg:py-16 lg:max-h-[calc(100vh-3rem)]",
"max-lg:py-4 max-lg:px-8 max-lg:sm:px-14 max-lg:bg-background/50 max-lg:backdrop-blur-xl max-lg:z-40 max-lg:data-[open=false]:hidden"
)}
>
{items.map((item, i) => (
<SidebarItem key={i} item={item} />
))}
</Base.SidebarList>
);
return (
<Base.SidebarList
minWidth={1024} // when should it being displayed (ignore the trigger)
className={clsx(
'fixed inset-0 top-24 flex flex-col gap-3 overflow-auto',
'lg:sticky lg:top-12 lg:max-h-[calc(100vh-3rem)] lg:py-16',
'max-lg:bg-background/50 max-lg:z-40 max-lg:px-8 max-lg:py-4 max-lg:backdrop-blur-xl max-lg:data-[open=false]:hidden max-lg:sm:px-14'
)}
>
{items.map((item, i) => (
<SidebarItem key={i} item={item} />
))}
</Base.SidebarList>
)
}
```
@@ -8,13 +8,13 @@ A Table of Contents with active anchor observer.
## Usage
```tsx
import * as Base from "next-docs-zeta/toc";
import * as Base from 'next-docs-zeta/toc'
return (
<Base.TOCProvider>
<Base.TOCItem />
</Base.TOCProvider>
);
<Base.TOCProvider>
<Base.TOCItem />
</Base.TOCProvider>
)
```
### TOC Provider
@@ -36,51 +36,51 @@ return (
### Use Active Anchor
A hook for getting info of an anchor. It accepts the item url and return an `Anchor` object, must be called under the TOC Provider.
A hook for getting info of an anchor. It accepts the item url and return an
`Anchor` object, must be called under the TOC Provider.
Helpful for implementing auto scroll or advanced usages.
#### Usage
```ts
import { useActiveAnchor } from "next-docs-zeta/toc";
import { useActiveAnchor } from 'next-docs-zeta/toc'
const anchor = useActiveAnchor(item.url);
const anchor = useActiveAnchor(item.url)
```
## Example
```tsx
"use client";
import * as Primitive from "next-docs-zeta/toc";
import type { TOCItemType } from "next-docs-zeta/server";
'use client'
import type { TOCItemType } from 'next-docs-zeta/server'
import * as Primitive from 'next-docs-zeta/toc'
export function TOC({ items }: { items: TOCItemType[] }) {
return (
<Primitive.TOCProvider toc={items}>
{items.map((item, i) => (
<TOCItem key={i} item={item} />
))}
</Primitive.TOCProvider>
);
return (
<Primitive.TOCProvider toc={items}>
{items.map((item, i) => (
<TOCItem key={i} item={item} />
))}
</Primitive.TOCProvider>
)
}
function TOCItem({ item }: { item: TOCItemType }) {
return (
<div>
<Primitive.TOCItem
href={item.url}
item={item}
className="text-sm text-muted-foreground transition-colors data-[active=true]:font-semibold data-[active=true]:text-foreground"
>
{item.title}
</Primitive.TOCItem>
<div className="flex flex-col pl-4">
{item.items?.map((item, i) => (
<TOCItem key={i} item={item} />
))}
</div>
</div>
);
return (
<div>
<Primitive.TOCItem
href={item.url}
item={item}
className="text-muted-foreground data-[active=true]:text-foreground text-sm transition-colors data-[active=true]:font-semibold"
>
{item.title}
</Primitive.TOCItem>
<div className="flex flex-col pl-4">
{item.items?.map((item, i) => <TOCItem key={i} item={item} />)}
</div>
</div>
)
}
```
@@ -9,25 +9,26 @@ Next Docs has native support for Contentlayer.
1. Install `rehype-pretty-code remark-gfm rehype-slug rehype-img-size`.
2. Copy the code from [Example Configuration](https://github.com/SonMooSans/next-docs/blob/main/packages/next-docs-ui/src/contentlayer/index.ts).
2. Copy the code from
[Example Configuration](https://github.com/SonMooSans/next-docs/blob/main/packages/next-docs-ui/src/contentlayer/index.ts).
3. Edit your configuration.
```ts title="contentlayer.config.ts"
// copied code...
export default makeSource(defaultConfig);
export default makeSource(defaultConfig)
```
4. Build the [Page Tree](/docs#build-page-tree).
```ts
import { allDocs, allMeta } from "contentlayer/generated";
import { buildPageTree } from "next-docs-zeta/contentlayer";
import { allDocs, allMeta } from 'contentlayer/generated'
import { buildPageTree } from 'next-docs-zeta/contentlayer'
// load pages
const ctx = loadContext(allMeta, allDocs);
export const tree = buildPageTree(ctx);
const ctx = loadContext(allMeta, allDocs)
export const tree = buildPageTree(ctx)
```
## Pages Structure
@@ -36,7 +37,8 @@ All files are located in `/content` and `/content/docs` for documentation pages.
### File
By default, it uses [MDX](https://mdxjs.com) and supports `title` and `description` frontmatter:
By default, it uses [MDX](https://mdxjs.com) and supports `title` and
`description` frontmatter:
```mdx
---
@@ -51,19 +53,22 @@ You may edit the configuration file to add additional properties.
### Folder
You can use folders to organize multiple pages, the uppercased name of the folder will be used as the display name.
You can use folders to organize multiple pages, the uppercased name of the
folder will be used as the display name.
### Meta
You can also customize the folder name, the order of pages, or adding a separator by creating a `meta.json` in the folder.
You can also customize the folder name, the order of pages, or adding a
separator by creating a `meta.json` in the folder.
```json
{
"title": "Name of Folder",
"pages": ["guide", "---Separator---", "components"] // file name of pages
"title": "Name of Folder",
"pages": ["guide", "---Separator---", "components"] // file name of pages
}
```
### Separator
As you see, you can define a separator in meta by adding a item surrounded by `---`.
As you see, you can define a separator in meta by adding a item surrounded by
`---`.
@@ -3,129 +3,141 @@ title: Internationalization
description: Support multiple languages in your documentation
---
Next Docs has built-in support for internationalized routing in Contentlayer. You can define a list of supported languages and pass it to i18n utilities. Read the [Next.js Docs](https://nextjs.org/docs/app/building-your-application/routing/internationalization) to learn more about implementing I18n in Next.js.
Next Docs has built-in support for internationalized routing in Contentlayer.
You can define a list of supported languages and pass it to i18n utilities. Read
the
[Next.js Docs](https://nextjs.org/docs/app/building-your-application/routing/internationalization)
to learn more about implementing I18n in Next.js.
## Setup
1. Put all supported languages in a file.
```ts title="i18n.ts"
export const defaultLanguage = "en";
export const languages = ["en", "cn"];
export const defaultLanguage = 'en'
export const languages = ['en', 'cn']
```
2. Change your current configurations.
```ts title="tree.ts"
import { allMeta, allDocs } from "contentlayer/generated";
import { allDocs, allMeta } from 'contentlayer/generated'
import {
buildI18nPageTree,
createUtils,
loadContext,
} from "next-docs-zeta/contentlayer";
import { languages } from "./i18n";
buildI18nPageTree,
createUtils,
loadContext
} from 'next-docs-zeta/contentlayer'
import { languages } from './i18n'
const ctx = loadContext(allMeta, allDocs, languages);
const ctx = loadContext(allMeta, allDocs, languages)
// Use 'buildI18nPageTree' instead
export const trees = buildI18nPageTree(ctx, languages);
export const trees = buildI18nPageTree(ctx, languages)
// utilities for i18n routing
export const { getPage, getPages } = createUtils(ctx);
export const { getPage, getPages } = createUtils(ctx)
```
3. Create i18n middleware.
```ts title="middleware.ts"
import { NextRequest } from "next/server";
import { defaultLanguage, languages } from "@/i18n";
import { createI18nMiddleware } from "next-docs-zeta/middleware";
import { defaultLanguage, languages } from '@/i18n'
import { createI18nMiddleware } from 'next-docs-zeta/middleware'
import { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
return createI18nMiddleware(
request,
languages,
defaultLanguage,
(locale, slug) => `/${locale}/docs/${slug}` // the format of redirected url
);
return createI18nMiddleware(
request,
languages,
defaultLanguage,
(locale, slug) => `/${locale}/docs/${slug}` // the format of redirected url
)
}
export const config = {
// Matcher ignoring `/_next/` and `/api/`
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
// Matcher ignoring `/_next/` and `/api/`
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
```
4. Create a dynamic route, ensure all special files are nested under `/app/[lang]`.
4. Create a dynamic route, ensure all special files are nested under
`/app/[lang]`.
```tsx title="/app/[lang]/layout.tsx"
export default function Layout({ params }: { params: { lang: string } }) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
);
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}
```
### Get Pages
To get the pages with specific languages, use the utilities exported from `tree.ts`.
To get the pages with specific languages, use the utilities exported from
`tree.ts`.
```ts
import { getPages, getPage, trees } from "@/tree";
import { getPage, getPages, trees } from '@/tree'
// in the page
const page = getPage(params.lang, params.slug);
const page = getPage(params.lang, params.slug)
// in generate static params
const pages = getPages(lang);
const pages = getPages(lang)
```
### Docs Search
You will need some extra configurations in order to implement international document searching.
You will need some extra configurations in order to implement international
document searching.
The default `initSearchAPI` doesn't provide functionality for i18n. Instead, you can use `initI18nSearchAPI`.
The default `initSearchAPI` doesn't provide functionality for i18n. Instead, you
can use `initI18nSearchAPI`.
1. Update the route handler.
```ts title="/api/search/route.ts"
import { languages } from "@/app/i18n";
import { getPages } from "@/app/tree";
import { getPageUrl } from "next-docs-zeta/contentlayer";
import { initI18nSearchAPI } from "next-docs-zeta/server";
import { languages } from '@/app/i18n'
import { getPages } from '@/app/tree'
import { getPageUrl } from 'next-docs-zeta/contentlayer'
import { initI18nSearchAPI } from 'next-docs-zeta/server'
export const { GET } = initI18nSearchAPI(
languages.map((lang) => {
const pages = getPages(lang)!.map((page) => ({
title: page.title,
content: page.body.raw,
// get page url
url: getPageUrl(page.slug.split("/"), "/docs", lang),
}));
languages.map(lang => {
const pages = getPages(lang)!.map(page => ({
title: page.title,
content: page.body.raw,
// get page url
url: getPageUrl(page.slug.split('/'), '/docs', lang)
}))
return [lang, pages];
})
);
return [lang, pages]
})
)
```
2. Add `locale` to search dialog, this will only allow pages with specified locale to being searched by the user.
2. Add `locale` to search dialog, this will only allow pages with specified
locale to being searched by the user.
```tsx
function Dialog() {
const { search, setSearch, query } = useDocsSearch(locale);
const { search, setSearch, query } = useDocsSearch(locale)
//...
//...
}
```
## Pages Structure
You can create a page by adding `.{locale}` to your MDX file name. Pages can't be language-specific unless you have created the same page for default locale.
You can create a page by adding `.{locale}` to your MDX file name. Pages can't
be language-specific unless you have created the same page for default locale.
For meta files, you can add `-{locale}` to the file name instead (`meta-cn.json`).
For meta files, you can add `-{locale}` to the file name instead
(`meta-cn.json`).
If it's the default language, just leave it empty like `get-started.mdx`. **Do not use add locale code to file name**.
If it's the default language, just leave it empty like `get-started.mdx`. **Do
not use add locale code to file name**.
### Example
+123 -115
View File
@@ -5,16 +5,19 @@ description: Getting Started with Next Docs
Next Docs is a library for building documentation websites.
- Search (using flexsearch)
- Breadcrumb, Sidebar, TOC Components
- Additional utilities
- Built for Next.js App Router and works fine on Pages Router
- Search (using flexsearch)
- Breadcrumb, Sidebar, TOC Components
- Additional utilities
- Built for Next.js App Router and works fine on Pages Router
## Getting Started
Next Docs is built for Next.js App Router. Hence, this guide will use App Router.
Next Docs is built for Next.js App Router. Hence, this guide will use App
Router.
It's recommended to use Next Docs with [Tailwind CSS](https://tailwindcss.com) + [Radix UI](https://www.radix-ui.com), and [Contentlayer](https://www.contentlayer.dev) (or any CMS).
It's recommended to use Next Docs with [Tailwind CSS](https://tailwindcss.com) +
[Radix UI](https://www.radix-ui.com), and
[Contentlayer](https://www.contentlayer.dev) (or any CMS).
> **Tip:** For normal usages, you can use [Next Docs UI](/docs/ui) instead.
@@ -28,7 +31,8 @@ Open in [CodeSandbox](https://githubbox.com/SonMooSans/next-docs-template).
### ContentLayer
Follow this [guide](https://www.contentlayer.dev/docs/getting-started) to setup ContentLayer.
Follow this [guide](https://www.contentlayer.dev/docs/getting-started) to setup
ContentLayer.
```bash
npm install contentlayer next-contentlayer
@@ -36,58 +40,63 @@ npm install contentlayer next-contentlayer
### Tailwind CSS
Follow this [guide](https://tailwindcss.com/docs/guides/nextjs) to setup Tailwind CSS.
Follow this [guide](https://tailwindcss.com/docs/guides/nextjs) to setup
Tailwind CSS.
```bash
npm install clsx postcss tailwindcss @tailwindcss/typography
```
I also prefer [Shadcn UI](https://ui.shadcn.com) If you don't want to write any components.
I also prefer [Shadcn UI](https://ui.shadcn.com) If you don't want to write any
components.
### Choose a Source
Next Docs has native support for ContentLayer, but any kind of formats and sources are allowed.
Next Docs has native support for ContentLayer, but any kind of formats and
sources are allowed.
You can refer to this [guide](/docs/headless/contentlayer) to learn how to use Contentlayer with Next Docs.
You can refer to this [guide](/docs/headless/contentlayer) to learn how to use
Contentlayer with Next Docs.
### Build Page Tree
A page tree refers to structured data of all pages.
```ts
import type { TreeNode } from "next-docs-zeta/server";
import type { TreeNode } from 'next-docs-zeta/server'
const tree: TreeNode[] = [
{
type: "folder",
name: "Components",
url: "/docs/components",
index: {
type: "page",
name: "Quick Start",
url: "/docs/components",
},
children: [
{
type: "page",
name: "Button",
url: "/docs/components/button",
},
],
{
type: 'folder',
name: 'Components',
url: '/docs/components',
index: {
type: 'page',
name: 'Quick Start',
url: '/docs/components'
},
{
type: "separator",
name: "Guides",
},
{
type: "page",
name: "Example",
url: "/docs/example",
},
];
children: [
{
type: 'page',
name: 'Button',
url: '/docs/components/button'
}
]
},
{
type: 'separator',
name: 'Guides'
},
{
type: 'page',
name: 'Example',
url: '/docs/example'
}
]
```
You can convert the data fetched from any CMS to a page tree and pass into Next Docs's components.
You can convert the data fetched from any CMS to a page tree and pass into Next
Docs's components.
Moreover, It supports [Contentlayer](/docs/headless/contentlayer) natively.
@@ -98,26 +107,26 @@ Moreover, It supports [Contentlayer](/docs/headless/contentlayer) natively.
implementation of certain components won't be shown, you will learn it later.
```tsx
import { ReactNode } from "react";
import { SidebarProvider, Sidebar } from "@/components/sidebar";
import clsx from "clsx";
import { tree } from "@/utils/page-tree";
import { Sidebar, SidebarProvider } from '@/components/sidebar'
import { tree } from '@/utils/page-tree'
import clsx from 'clsx'
import { ReactNode } from 'react'
export default function DocsLayout({ children }: { children: ReactNode }) {
return (
<SidebarProvider>
<div
className={clsx(
"grid grid-cols-1 gap-12 container",
"lg:grid-cols-[250px_auto] xl:grid-cols-[250px_auto_150px] 2xl:grid-cols-[250px_auto_250px]",
"sm:px-14 xl:px-24"
)}
>
<Sidebar items={tree} />
{children}
</div>
</SidebarProvider>
);
return (
<SidebarProvider>
<div
className={clsx(
'container grid grid-cols-1 gap-12',
'lg:grid-cols-[250px_auto] xl:grid-cols-[250px_auto_150px] 2xl:grid-cols-[250px_auto_250px]',
'sm:px-14 xl:px-24'
)}
>
<Sidebar items={tree} />
{children}
</div>
</SidebarProvider>
)
}
```
@@ -126,55 +135,52 @@ export default function DocsLayout({ children }: { children: ReactNode }) {
Create `/app/docs/[[...slug]]/page.tsx`:
```tsx
import { allDocs } from "contentlayer/generated";
import { notFound } from "next/navigation";
import { getTableOfContents } from "next-docs-zeta/server";
import { getMDXComponent } from "next-contentlayer/hooks";
import { tree } from "@/utils/page-tree";
import React from "react";
import { Breadcrumb } from "@/components/breadcrumb";
import { SafeLink } from "next-docs-zeta/link";
import { TOC } from "@/components/toc";
import { Breadcrumb } from '@/components/breadcrumb'
import { TOC } from '@/components/toc'
import { tree } from '@/utils/page-tree'
import { allDocs } from 'contentlayer/generated'
import { getMDXComponent } from 'next-contentlayer/hooks'
import { SafeLink } from 'next-docs-zeta/link'
import { getTableOfContents } from 'next-docs-zeta/server'
import { notFound } from 'next/navigation'
import React from 'react'
export default async function Page({
params,
params
}: {
params: { slug?: string[] };
params: { slug?: string[] }
}) {
const path = (params.slug ?? []).join("/");
const page = allDocs.find((page) => page.slug === path);
const path = (params.slug ?? []).join('/')
const page = allDocs.find(page => page.slug === path)
if (page == null) {
notFound();
}
if (page == null) {
notFound()
}
const toc = await getTableOfContents(page.body.raw);
const MDX = getMDXComponent(page.body.code);
const toc = await getTableOfContents(page.body.raw)
const MDX = getMDXComponent(page.body.code)
return (
<>
<article className="flex flex-col gap-6 py-8 overflow-x-hidden lg:py-16">
<Breadcrumb tree={tree} />
<h1 className="text-4xl font-bold">{page.title}</h1>
<div className="prose prose-text prose-pre:grid prose-pre:border-[1px] prose-code:bg-secondary prose-code:p-1 max-w-none">
<MDX
components={{
a: SafeLink, // handles external links correctly
}}
/>
</div>
</article>
<div className="relative flex flex-col gap-3 max-xl:hidden py-16">
<div className="sticky top-28 flex flex-col gap-3 overflow-auto max-h-[calc(100vh-4rem-3rem)]">
{toc.length > 0 && (
<h3 className="font-semibold">On this page</h3>
)}
<TOC items={toc} />
</div>
</div>
</>
);
return (
<>
<article className="flex flex-col gap-6 overflow-x-hidden py-8 lg:py-16">
<Breadcrumb tree={tree} />
<h1 className="text-4xl font-bold">{page.title}</h1>
<div className="prose prose-text prose-pre:grid prose-pre:border-[1px] prose-code:bg-secondary prose-code:p-1 max-w-none">
<MDX
components={{
a: SafeLink // handles external links correctly
}}
/>
</div>
</article>
<div className="relative flex flex-col gap-3 py-16 max-xl:hidden">
<div className="sticky top-28 flex max-h-[calc(100vh-4rem-3rem)] flex-col gap-3 overflow-auto">
{toc.length > 0 && <h3 className="font-semibold">On this page</h3>}
<TOC items={toc} />
</div>
</div>
</>
)
}
```
@@ -182,9 +188,9 @@ You can generate static params as well:
```ts
export async function generateStaticParams() {
return allDocs.map((docs) => ({
slug: docs.slug.split("/"),
}));
return allDocs.map(docs => ({
slug: docs.slug.split('/')
}))
}
```
@@ -205,32 +211,34 @@ Hello World
We've just created the prototype of the website but many components are missing.
**Next Docs** offers simple document searching as well as components for building a good docs. You can click on the cards below and they will tell you how to use them.
**Next Docs** offers simple document searching as well as components for
building a good docs. You can click on the cards below and they will tell you
how to use them.
<Cards>
<Card
title="Breadcrumb"
href="/docs/components/breadcrumb"
description="The navigation component at the top of screen"
title="Breadcrumb"
href="/docs/components/breadcrumb"
description="The navigation component at the top of screen"
/>
<Card
title="TOC"
href="/docs/components/toc"
description="A Table of Contents with active anchor observer"
title="TOC"
href="/docs/components/toc"
description="A Table of Contents with active anchor observer"
/>
<Card
title="Sidebar"
href="/docs/components/sidebar"
description="The navigation bar at aside of viewport"
title="Sidebar"
href="/docs/components/sidebar"
description="The navigation bar at aside of viewport"
/>
<Card
title="Search"
href="/docs/utils/search"
description="Implement document searching"
title="Search"
href="/docs/utils/search"
description="Implement document searching"
/>
</Cards>
+11 -11
View File
@@ -1,13 +1,13 @@
{
"title": "Docs",
"pages": [
"---Guide---",
"index",
"---API References---",
"components",
"utils",
"---Contentlayer---",
"contentlayer/index",
"contentlayer/internationalization"
]
"title": "Docs",
"pages": [
"---Guide---",
"index",
"---API References---",
"components",
"utils",
"---Contentlayer---",
"contentlayer/index",
"contentlayer/internationalization"
]
}
@@ -3,8 +3,8 @@ title: Find Neighbours
description: Find the neighbours of a page from the page tree
---
Find the neighbours of a page from the page tree, it returns the next and previous page of a given page.
It is useful for implementing a footer.
Find the neighbours of a page from the page tree, it returns the next and
previous page of a given page. It is useful for implementing a footer.
For example:
@@ -19,11 +19,11 @@ For example:
It requires a page tree and the url of page.
```ts
import { getPageUrl } from "next-docs-zeta/contentlayer";
import { findNeighbour } from "next-docs-zeta/server";
import { getPageUrl } from 'next-docs-zeta/contentlayer'
import { findNeighbour } from 'next-docs-zeta/server'
const url = getPageUrl(params.slug.split("/"), "/docs");
const neighbours = findNeighbour(tree, url);
const url = getPageUrl(params.slug.split('/'), '/docs')
const neighbours = findNeighbour(tree, url)
```
| Parameter | Type | Description |
@@ -12,21 +12,21 @@ Note: If you're using a CMS, you should use the API provided by the CMS instead.
### Markdown/MDX
```ts
import { getTableOfContents } from "next-docs-zeta/server";
import { getTableOfContents } from 'next-docs-zeta/server'
const toc = await getTableOfContents(page.body.raw);
const toc = await getTableOfContents(page.body.raw)
```
### Portable Text (Sanity)
```ts
import { getTableOfContentsFromPortableText } from "next-docs-zeta/server";
import { getTableOfContentsFromPortableText } from 'next-docs-zeta/server'
const posts = await client.fetch(query);
const posts = await client.fetch(query)
const toc = await getTableOfContentsFromPortableText(posts[0].body, (s) =>
generateSlug(s)
);
const toc = await getTableOfContentsFromPortableText(posts[0].body, s =>
generateSlug(s)
)
```
### getTableOfContents
@@ -35,7 +35,8 @@ Parse markdown content and return an array of TOC items.
### getTableOfContentsFromPortableText
Takes a portable text and slug function, return an array of TOC items with their slug generated using the slug function.
Takes a portable text and slug function, return an array of TOC items with their
slug generated using the slug function.
### TOCItemType
@@ -1,4 +1,4 @@
{
"title": "Utilities",
"pages": ["search", "get-toc", "find-neighbour"]
"title": "Utilities",
"pages": ["search", "get-toc", "find-neighbour"]
}
@@ -3,7 +3,8 @@ title: Search
description: Document searching makes easy
---
Next Docs supports searching document based on [Flexsearch](https://github.com/nextapps-de/flexsearch).
Next Docs supports searching document based on
[Flexsearch](https://github.com/nextapps-de/flexsearch).
## Usage
@@ -14,26 +15,26 @@ Assumes you're using Contentlayer.
1. Create the API Route.
```tsx title="/app/api/search/route.ts"
import { allDocs } from "contentlayer/generated";
import { initSearchAPI } from "next-docs-zeta/server";
import { allDocs } from 'contentlayer/generated'
import { initSearchAPI } from 'next-docs-zeta/server'
export const { GET } = initSearchAPI(
allDocs.map((docs) => ({
title: docs.title,
content: docs.body.raw,
url: docs.url,
}))
);
allDocs.map(docs => ({
title: docs.title,
content: docs.body.raw,
url: docs.url
}))
)
```
2. Create a Search Dialog.
```tsx
import { useDocsSearch } from "next-docs-zeta/search";
import { useDocsSearch } from 'next-docs-zeta/search'
export function Dialog() {
const { search, setSearch, query } = useDocsSearch();
return <div>...</div>;
const { search, setSearch, query } = useDocsSearch()
return <div>...</div>
}
```
@@ -45,62 +46,62 @@ export function Dialog() {
Using [Shadcn UI Command](https://ui.shadcn.com/docs/components/command).
```tsx
import type { DialogProps } from "@radix-ui/react-dialog";
import { useDocsSearch } from "next-docs-zeta/search";
import type { DialogProps } from '@radix-ui/react-dialog'
import { BookOpenIcon } from 'lucide-react'
import { useDocsSearch } from 'next-docs-zeta/search'
import { useRouter } from 'next/navigation'
import { useCallback } from 'react'
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "../ui/command";
import { useRouter } from "next/navigation";
import { useCallback } from "react";
import { BookOpenIcon } from "lucide-react";
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator
} from '../ui/command'
export default function SearchDialog(props: DialogProps) {
const router = useRouter();
const { search, setSearch, query } = useDocsSearch();
const router = useRouter()
const { search, setSearch, query } = useDocsSearch()
const onOpen = useCallback(
(v: string) => {
router.push(v);
props.onOpenChange?.(false);
},
[router]
);
const onOpen = useCallback(
(v: string) => {
router.push(v)
props.onOpenChange?.(false)
},
[router]
)
return (
<CommandDialog {...props}>
<CommandInput
placeholder="Type a command or search..."
value={search}
onValueChange={setSearch}
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
return (
<CommandDialog {...props}>
<CommandInput
placeholder="Type a command or search..."
value={search}
onValueChange={setSearch}
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{query.data != "empty" &&
query.data != null &&
query.data.length !== 0 && (
<CommandGroup heading="Documents">
{query.data.map((item) => (
<CommandItem
key={item.id[0]}
value={item.doc.url}
onSelect={onOpen}
>
{item.doc.title}
</CommandItem>
))}
</CommandGroup>
)}
<CommandSeparator />
</CommandList>
</CommandDialog>
);
{query.data != 'empty' &&
query.data != null &&
query.data.length !== 0 && (
<CommandGroup heading="Documents">
{query.data.map(item => (
<CommandItem
key={item.id[0]}
value={item.doc.url}
onSelect={onOpen}
>
{item.doc.title}
</CommandItem>
))}
</CommandGroup>
)}
<CommandSeparator />
</CommandList>
</CommandDialog>
)
}
```
+37 -33
View File
@@ -10,9 +10,9 @@ The layout of documentation pages, it must be nested under the Root Provider.
Wrap the children and pass your page tree to the component.
```tsx
import { DocsLayout } from "next-docs-ui/layout";
import { DocsLayout } from 'next-docs-ui/layout'
<DocsLayout tree={tree}>{children}</DocsLayout>;
;<DocsLayout tree={tree}>{children}</DocsLayout>
```
| Prop | Type | Description |
@@ -26,9 +26,12 @@ import { DocsLayout } from "next-docs-ui/layout";
### Disabling Navbar
It is common to share a navbar across all the pages (not just the docs layout). To achieve this, you may disable the default navbar by passing `false` to the `nav` prop.
It is common to share a navbar across all the pages (not just the docs layout).
To achieve this, you may disable the default navbar by passing `false` to the
`nav` prop.
The default navbar is exported from `next-docs-ui/component`, you can add it to the root layout (under the root provider).
The default navbar is exported from `next-docs-ui/component`, you can add it to
the root layout (under the root provider).
<Accordion type="single" collapsible>
<AccordionItem value="custom-navbar">
@@ -38,35 +41,36 @@ The default navbar is exported from `next-docs-ui/component`, you can add it to
Example for custom navbar using Tailwind CSS.
```tsx
"use client";
import { GithubIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { Nav as OriginalNav } from "next-docs-ui/components";
import Link from "next/link";
'use client'
import { GithubIcon } from 'lucide-react'
import { Nav as OriginalNav } from 'next-docs-ui/components'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export function Nav() {
const pathname = usePathname();
const inDocs = pathname.startsWith("/docs/") || pathname === "/docs";
const pathname = usePathname()
const inDocs = pathname.startsWith('/docs/') || pathname === '/docs'
return (
<OriginalNav
enableSidebar={inDocs}
links={[
{
icon: <GithubIcon className="w-5 h-5" />,
href: "https://github.com/SonMooSans/next-docs",
external: true,
},
]}
>
<Link
href="/"
className="font-semibold whitespace-nowrap hover:text-muted-foreground"
>
Next Docs
</Link>
</OriginalNav>
);
return (
<OriginalNav
enableSidebar={inDocs}
links={[
{
icon: <GithubIcon className="h-5 w-5" />,
href: 'https://github.com/SonMooSans/next-docs',
external: true
}
]}
>
<Link
href="/"
className="hover:text-muted-foreground whitespace-nowrap font-semibold"
>
Next Docs
</Link>
</OriginalNav>
)
}
```
@@ -77,10 +81,10 @@ export function Nav() {
## Example
```tsx title="app/docs/[[...slug]]/layout.tsx"
import { ReactNode } from "react";
import { DocsLayout } from "next-docs-ui/layout";
import { DocsLayout } from 'next-docs-ui/layout'
import { ReactNode } from 'react'
export default function Layout({ children }: { children: ReactNode }) {
return <DocsLayout tree={tree}>{children}</DocsLayout>;
return <DocsLayout tree={tree}>{children}</DocsLayout>
}
```
+24 -19
View File
@@ -3,18 +3,19 @@ title: Docs Page
description: A single page in your documentation
---
Page is the base element of a documentation, it includes Table of contents, Footer, and Breadcrumb.
Page is the base element of a documentation, it includes Table of contents,
Footer, and Breadcrumb.
## Usage
Pass the table of contents and page tree to the component.
```tsx
import { DocsPage } from "next-docs-ui/page";
import { DocsPage } from 'next-docs-ui/page'
<DocsPage toc={toc} tree={tree}>
<MDXContent>...</MDXContent>
</DocsPage>;
;<DocsPage toc={toc} tree={tree}>
<MDXContent>...</MDXContent>
</DocsPage>
```
| Prop | Type | Description |
@@ -28,26 +29,30 @@ import { DocsPage } from "next-docs-ui/page";
### Table of Contents
It is a tree with all the headings in your document. For Markdown or MDX documents, You can parse TOC by using the [TOC Utility](/docs/headless/utils/get-toc).
It is a tree with all the headings in your document. For Markdown or MDX
documents, You can parse TOC by using the
[TOC Utility](/docs/headless/utils/get-toc).
Here is an example using Contentlayer.
```tsx
const path = (params.slug ?? []).join("/");
const page = allDocs.find((page) => page.slug === path);
const path = (params.slug ?? []).join('/')
const page = allDocs.find(page => page.slug === path)
if (page == null) {
notFound();
notFound()
}
const toc = await getTableOfContents(page.body.raw);
const toc = await getTableOfContents(page.body.raw)
```
### Footer
You need to manually enable it by passing the previous and next page to the `footer` prop.
You need to manually enable it by passing the previous and next page to the
`footer` prop.
To get those pages, it's recommended to use the [Get Neighbours](/docs/headless/utils/find-neighbour) Utility.
To get those pages, it's recommended to use the
[Get Neighbours](/docs/headless/utils/find-neighbour) Utility.
| Prop | Type | Description |
| ---------- | -------------------------------------------- | ----------- |
@@ -57,16 +62,16 @@ To get those pages, it's recommended to use the [Get Neighbours](/docs/headless/
#### Example
```tsx
import { getPageUrl } from "next-docs-zeta/contentlayer";
import { findNeighbour } from "next-docs-zeta/server";
import { getPageUrl } from 'next-docs-zeta/contentlayer'
import { findNeighbour } from 'next-docs-zeta/server'
// Get the url of page
const url = getPageUrl(page.slug.split("/"), "/docs");
const neighbours = findNeighbour(tree, url);
const url = getPageUrl(page.slug.split('/'), '/docs')
const neighbours = findNeighbour(tree, url)
<DocsPage toc={toc} tree={tree} footer={neighbours}>
...
</DocsPage>;
;<DocsPage toc={toc} tree={tree} footer={neighbours}>
...
</DocsPage>
```
### Breadcrumb
@@ -8,14 +8,14 @@ description: Use the Card component in your MDX documentation
Add it to your MDX components.
```tsx {5-6}
import { Card, Cards } from "next-docs-ui/mdx";
import { Card, Cards } from 'next-docs-ui/mdx'
<MDX
components={{
Card: (props) => <Card {...props} />,
Cards: (props) => <Cards {...props} />,
}}
/>;
;<MDX
components={{
Card: props => <Card {...props} />,
Cards: props => <Cards {...props} />
}}
/>
```
### Cards
@@ -36,11 +36,11 @@ The component to redirect users.
```mdx
<Cards>
<Card href="/" title="Home" description="Go back to home" />
<Card href="/" title="Home" description="Go back to home" />
</Cards>
```
<br />
<Cards>
<Card href="/" title="Home" description="Go back to home" />
<Card href="/" title="Home" description="Go back to home" />
</Cards>
@@ -4,22 +4,22 @@ title: Code Block
Display code blocks, required by default.
- Syntax highlighting powered by [Shiki](https://github.com/shikijs/shiki).
- Copy button
- Custom Titles
- Syntax highlighting powered by [Shiki](https://github.com/shikijs/shiki).
- Copy button
- Custom Titles
## Usage
You can add this component to `mdxComponents`.
```tsx {1,5}
import { Pre } from "next-docs-ui/mdx";
import { Pre } from 'next-docs-ui/mdx'
<MDX
components={{
pre: (props) => <Pre {...props} />,
}}
/>;
;<MDX
components={{
pre: props => <Pre {...props} />
}}
/>
```
### Highlight Lines
@@ -43,16 +43,16 @@ Add `title="x"` to the codeblock meta.
```js title="next.config.js"
/** @type {import('next').NextConfig} */
const config = {
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
reactStrictMode: true,
images: {
domains: ["i.pravatar.cc"],
},
};
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
reactStrictMode: true,
images: {
domains: ['i.pravatar.cc']
}
}
const { withContentlayer } = require("next-contentlayer");
const { withContentlayer } = require('next-contentlayer')
module.exports = withContentlayer(config);
module.exports = withContentlayer(config)
```
````
@@ -61,14 +61,14 @@ module.exports = withContentlayer(config);
```js title="next.config.js" {10}
/** @type {import('next').NextConfig} */
const config = {
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
reactStrictMode: true,
images: {
domains: ["i.pravatar.cc"],
},
};
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
reactStrictMode: true,
images: {
domains: ['i.pravatar.cc']
}
}
const { withContentlayer } = require("next-contentlayer");
const { withContentlayer } = require('next-contentlayer')
module.exports = withContentlayer(config);
module.exports = withContentlayer(config)
```
@@ -5,26 +5,26 @@ description: Heading components for your MDX documenation
The heading component.
- Add heading id
- Copy section link
- Add heading id
- Copy section link
## Usage
Add it to your MDX components, from `h1` to `h6`.
```tsx {5-10}
import { Heading } from "next-docs-ui/mdx";
import { Heading } from 'next-docs-ui/mdx'
<MDX
components={{
h1: (props) => <Heading as="h1" {...props} />,
h2: (props) => <Heading as="h2" {...props} />,
h3: (props) => <Heading as="h3" {...props} />,
h4: (props) => <Heading as="h4" {...props} />,
h5: (props) => <Heading as="h5" {...props} />,
h6: (props) => <Heading as="h6" {...props} />,
}}
/>;
;<MDX
components={{
h1: props => <Heading as="h1" {...props} />,
h2: props => <Heading as="h2" {...props} />,
h3: props => <Heading as="h3" {...props} />,
h4: props => <Heading as="h4" {...props} />,
h5: props => <Heading as="h5" {...props} />,
h6: props => <Heading as="h6" {...props} />
}}
/>
```
## Example
@@ -10,15 +10,15 @@ A button that scrolls to the top.
Add this component to your page.
```tsx title="app/docs/[[...slug]]/page.tsx"
import { RollButton } from "next-docs-ui/components";
import { RollButton } from 'next-docs-ui/components'
<DocsPage toc={toc} tree={tree}>
<RollButton />
<MDXContent>
<h1>{page.title}</h1>
<MDX />
</MDXContent>
</DocsPage>;
;<DocsPage toc={toc} tree={tree}>
<RollButton />
<MDXContent>
<h1>{page.title}</h1>
<MDX />
</MDXContent>
</DocsPage>
```
### Reference
+12 -9
View File
@@ -3,22 +3,25 @@ title: Image Optimization
description: Use Next.js image optimization in your MDX documents
---
Next Docs UI uses the built-in `next/image` component. You can enjoy the benefits of Next.js Image Optimization without extra configurations.
Next Docs UI uses the built-in `next/image` component. You can enjoy the
benefits of Next.js Image Optimization without extra configurations.
Under the hood, It gets the image size using `rehype-img-size`, then use `next/image` to display it.
Under the hood, It gets the image size using `rehype-img-size`, then use
`next/image` to display it.
## Usage
Use the `<Image />` component from `next-docs-ui/mdx`. It wrapped `next/image` and will pass the default `sizes` property.
Use the `<Image />` component from `next-docs-ui/mdx`. It wrapped `next/image`
and will pass the default `sizes` property.
```tsx {5}
import { Image } from "next-docs-ui/mdx";
import { Image } from 'next-docs-ui/mdx'
<MDX
components={{
img: (props) => <Image {...props} />,
}}
/>;
;<MDX
components={{
img: props => <Image {...props} />
}}
/>
```
### Example
+141 -139
View File
@@ -4,26 +4,27 @@ title: Quick Start
Next Docs UI is a Next.js framework for building documentation website.
It's built on the top of Next.js, Radix UI and **Next Docs**. Styled using Tailwind CSS.
It's built on the top of Next.js, Radix UI and **Next Docs**. Styled using
Tailwind CSS.
## Features
Next Docs UI provides many features out of the box.
- Light/Dark Mode
- Full-text Search
- Table of Contents
- Built-in Components
- Internationalization (I18n)
- Image Optimization
- Code Syntax highlighting powered by Shiki
- Well Designed UI & Accessibility
- Light/Dark Mode
- Full-text Search
- Table of Contents
- Built-in Components
- Internationalization (I18n)
- Image Optimization
- Code Syntax highlighting powered by Shiki
- Well Designed UI & Accessibility
**Moreover:**
- Built for App Router
- Great Flexibility
- Light & Fast
- Built for App Router
- Great Flexibility
- Light & Fast
## Getting Started
@@ -37,7 +38,8 @@ npm install next-docs-ui next-docs-zeta shiki
### Configuration
Next Docs UI doesn't provide static generation directly. Instead, it uses [Contentlayer](https://www.contentlayer.dev).
Next Docs UI doesn't provide static generation directly. Instead, it uses
[Contentlayer](https://www.contentlayer.dev).
```bash
npm install contentlayer next-contentlayer
@@ -45,15 +47,16 @@ npm install contentlayer next-contentlayer
1. **Configure Contentlayer**
You can modify the configuration by copying [Example Configuration](https://github.com/SonMooSans/next-docs/blob/main/packages/next-docs-ui/src/contentlayer/index.ts).
You can modify the configuration by copying
[Example Configuration](https://github.com/SonMooSans/next-docs/blob/main/packages/next-docs-ui/src/contentlayer/index.ts).
```ts {4} title="contentlayer.config.ts"
import { createConfig } from "next-docs-ui/contentlayer";
import { makeSource } from "contentlayer/source-files";
import { makeSource } from 'contentlayer/source-files'
import { createConfig } from 'next-docs-ui/contentlayer'
export default makeSource({
...createConfig(),
});
...createConfig()
})
```
2. **Add Contentlayer Plugin**
@@ -61,23 +64,23 @@ export default makeSource({
```js title="next.config.js"
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
};
reactStrictMode: true
}
const { withContentlayer } = require("next-contentlayer");
const { withContentlayer } = require('next-contentlayer')
module.exports = withContentlayer(config);
module.exports = withContentlayer(config)
```
3. **Build Page Tree**
```ts title="tree.ts"
import { allDocs, allMeta } from "contentlayer/generated";
import { buildPageTree, loadContext } from "next-docs-zeta/contentlayer";
import { allDocs, allMeta } from 'contentlayer/generated'
import { buildPageTree, loadContext } from 'next-docs-zeta/contentlayer'
const ctx = loadContext(allMeta, allDocs);
const ctx = loadContext(allMeta, allDocs)
export const tree = buildPageTree(ctx);
export const tree = buildPageTree(ctx)
```
Make sure you've generated documents correctly before moving to next step.
@@ -91,46 +94,45 @@ Make sure you've generated documents correctly before moving to next step.
<AccordionContent>
```tsx title="app/layout.tsx"
import { DocsLayout } from "next-docs-ui/layout";
import { tree } from "@/tree";
import { Inter } from "next/font/google";
import { RootProvider } from "next-docs-ui/provider";
import type { Metadata } from "next";
import "next-docs-ui/style.css";
import { tree } from '@/tree'
import type { Metadata } from 'next'
import { DocsLayout } from 'next-docs-ui/layout'
import { RootProvider } from 'next-docs-ui/provider'
import { Inter } from 'next/font/google'
import 'next-docs-ui/style.css'
export const metadata: Metadata = {
title: {
default: "My App",
template: "My App | %s",
},
description: "Generated by Next.js",
};
title: {
default: 'My App',
template: 'My App | %s'
},
description: 'Generated by Next.js'
}
const inter = Inter({
subsets: ["latin"],
});
subsets: ['latin']
})
export default function RootLayout({
children,
children
}: {
children: React.ReactNode;
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.className}>
<body
style={{
minHeight: "100vh",
}}
>
<RootProvider>
<DocsLayout tree={tree} navTitle="My App">
{children}
</DocsLayout>
</RootProvider>
</body>
</html>
);
return (
<html lang="en" className={inter.className}>
<body
style={{
minHeight: '100vh'
}}
>
<RootProvider>
<DocsLayout tree={tree} navTitle="My App">
{children}
</DocsLayout>
</RootProvider>
</body>
</html>
)
}
```
@@ -142,69 +144,69 @@ export default function RootLayout({
<AccordionContent>
```tsx title="app/docs/[[...slug]]/page.tsx"
import { DocsPage } from "next-docs-ui/page";
import { tree } from '@/tree'
import { allDocs } from 'contentlayer/generated'
import type { Metadata } from 'next'
import { getMDXComponent } from 'next-contentlayer/hooks'
import {
Heading,
Image,
Pre,
Link,
Table,
MDXContent,
Card,
Cards,
} from "next-docs-ui/mdx";
import { getTableOfContents } from "next-docs-zeta/server";
import { allDocs } from "contentlayer/generated";
import { notFound } from "next/navigation";
import { tree } from "@/tree";
import { getMDXComponent } from "next-contentlayer/hooks";
import type { Metadata } from "next";
Card,
Cards,
Heading,
Image,
Link,
MDXContent,
Pre,
Table
} from 'next-docs-ui/mdx'
import { DocsPage } from 'next-docs-ui/page'
import { getTableOfContents } from 'next-docs-zeta/server'
import { notFound } from 'next/navigation'
export default async function Page({
params,
params
}: {
params: { slug?: string[] };
params: { slug?: string[] }
}) {
const path = (params.slug ?? []).join("/");
const page = allDocs.find((page) => page.slug === path);
const path = (params.slug ?? []).join('/')
const page = allDocs.find(page => page.slug === path)
if (page == null) {
notFound();
}
if (page == null) {
notFound()
}
const toc = await getTableOfContents(page.body.raw);
const MDX = getMDXComponent(page.body.code);
const toc = await getTableOfContents(page.body.raw)
const MDX = getMDXComponent(page.body.code)
// We don't provide MDX components by default
return (
<DocsPage toc={toc} tree={tree}>
<MDXContent>
<h1>{page.title}</h1>
<MDX
components={{
table: (props) => <Table {...props} />,
pre: (props) => <Pre {...props} />,
a: (props) => <Link {...props} />,
h1: (props) => <Heading as="h1" {...props} />,
h2: (props) => <Heading as="h2" {...props} />,
h3: (props) => <Heading as="h3" {...props} />,
h4: (props) => <Heading as="h4" {...props} />,
h5: (props) => <Heading as="h5" {...props} />,
h6: (props) => <Heading as="h6" {...props} />,
img: (props) => <Image {...props} />,
Card: (props) => <Card {...props} />,
Cards: (props) => <Cards {...props} />,
}}
/>
</MDXContent>
</DocsPage>
);
// We don't provide MDX components by default
return (
<DocsPage toc={toc} tree={tree}>
<MDXContent>
<h1>{page.title}</h1>
<MDX
components={{
table: props => <Table {...props} />,
pre: props => <Pre {...props} />,
a: props => <Link {...props} />,
h1: props => <Heading as="h1" {...props} />,
h2: props => <Heading as="h2" {...props} />,
h3: props => <Heading as="h3" {...props} />,
h4: props => <Heading as="h4" {...props} />,
h5: props => <Heading as="h5" {...props} />,
h6: props => <Heading as="h6" {...props} />,
img: props => <Image {...props} />,
Card: props => <Card {...props} />,
Cards: props => <Cards {...props} />
}}
/>
</MDXContent>
</DocsPage>
)
}
export async function generateStaticParams(): Promise<{ slug: string[] }[]> {
return allDocs.map((docs) => ({
slug: docs.slug.split("/"),
}));
return allDocs.map(docs => ({
slug: docs.slug.split('/')
}))
}
```
@@ -216,7 +218,7 @@ export async function generateStaticParams(): Promise<{ slug: string[] }[]> {
<AccordionContent>
```tsx title="app/docs/[[...slug]]/not-found.tsx"
export { default } from "next-docs-ui/not-found";
export { default } from 'next-docs-ui/not-found'
```
</AccordionContent>
@@ -227,16 +229,16 @@ export { default } from "next-docs-ui/not-found";
<AccordionContent>
```tsx title="app/api/search/route.ts"
import { allDocs } from "contentlayer/generated";
import { initSearchAPI } from "next-docs-zeta/server";
import { allDocs } from 'contentlayer/generated'
import { initSearchAPI } from 'next-docs-zeta/server'
export const { GET } = initSearchAPI(
allDocs.map((docs) => ({
title: docs.title,
content: docs.body.raw,
url: docs.url,
}))
);
allDocs.map(docs => ({
title: docs.title,
content: docs.body.raw,
url: docs.url
}))
)
```
</AccordionContent>
@@ -265,24 +267,24 @@ npm run dev
## Learn More
<Cards>
<Card
href="/docs/headless/contentlayer#pages-structure"
title="Page Structure"
description="Learn how to structure your pages"
/>
<Card
href="/docs/ui/internationalization"
title="Learn I18n"
description="Learn how to implement I18n"
/>
<Card
href="/docs/ui/theme"
title="Theming"
description="Add themes to Next Docs UI"
/>
<Card
href="/docs/ui/components/codeblock"
title="Code Block"
description="Learn how to use code blocks in MDX"
/>
<Card
href="/docs/headless/contentlayer#pages-structure"
title="Page Structure"
description="Learn how to structure your pages"
/>
<Card
href="/docs/ui/internationalization"
title="Learn I18n"
description="Learn how to implement I18n"
/>
<Card
href="/docs/ui/theme"
title="Theming"
description="Add themes to Next Docs UI"
/>
<Card
href="/docs/ui/components/codeblock"
title="Code Block"
description="Learn how to use code blocks in MDX"
/>
</Cards>
@@ -3,43 +3,48 @@ title: Internationalization
description: Learn more implement internationalization in Next Docs UI
---
Next Docs UI supports I18n routing based on **Zeta**. Please refer to this [guide](/docs/headless/contentlayer/internationalization) to setup basic configurations and learn how to structure the documents.
Next Docs UI supports I18n routing based on **Zeta**. Please refer to this
[guide](/docs/headless/contentlayer/internationalization) to setup basic
configurations and learn how to structure the documents.
Ensure you have your page tree, layout and middleware ready before getting started.
Ensure you have your page tree, layout and middleware ready before getting
started.
## Create Provider
A `I18nProvider` is needed for internationalization.
Assume your files are nested under `/app/[lang]`, where the 'lang' parameter is at index 1.
Assume your files are nested under `/app/[lang]`, where the 'lang' parameter is
at index 1.
```tsx
"use client";
import { I18nProvider } from "next-docs-ui/i18n";
import { useParams, usePathname, useRouter } from "next/navigation";
import { ReactNode, useCallback } from "react";
import { defaultLanguage } from "@/i18n";
'use client'
import { defaultLanguage } from '@/i18n'
import { I18nProvider } from 'next-docs-ui/i18n'
import { useParams, usePathname, useRouter } from 'next/navigation'
import { ReactNode, useCallback } from 'react'
export function ClientI18nProvider({ children }: { children: ReactNode }) {
const router = useRouter();
const pathname = usePathname();
const { lang } = useParams() as { lang?: string };
const onChange = useCallback(
(v: string) => {
const segments = pathname.split("/");
const router = useRouter()
const pathname = usePathname()
const { lang } = useParams() as { lang?: string }
const onChange = useCallback(
(v: string) => {
const segments = pathname.split('/')
segments[1] = v; // update parameter
segments[1] = v // update parameter
router.push(segments.join("/"));
},
[router, pathname]
);
router.push(segments.join('/'))
},
[router, pathname]
)
return (
<I18nProvider value={{ locale: lang ?? defaultLanguage, onChange }}>
{children}
</I18nProvider>
);
return (
<I18nProvider value={{ locale: lang ?? defaultLanguage, onChange }}>
{children}
</I18nProvider>
)
}
```
@@ -47,57 +52,59 @@ Then, wrap the root provider inside of your i18n provider.
```tsx
<ClientI18nProvider>
<RootProvider>...</RootProvider>
<RootProvider>...</RootProvider>
</ClientI18nProvider>
```
## Add Language Switch
To allow users changing their language, you can put the `<LanguageSelect />` component to sidebar same as following:
To allow users changing their language, you can put the `<LanguageSelect />`
component to sidebar same as following:
```tsx title="/[lang]/docs/layout.tsx"
import { DocsLayout } from "next-docs-ui/layout";
import { trees } from "@/tree";
import { ReactNode } from "react";
import { LanguageSelect } from "next-docs-ui/i18n";
import { trees } from '@/tree'
import { LanguageSelect } from 'next-docs-ui/i18n'
import { DocsLayout } from 'next-docs-ui/layout'
import { ReactNode } from 'react'
export default function Layout({
params,
children,
params,
children
}: {
params: { lang: string };
children: ReactNode;
params: { lang: string }
children: ReactNode
}) {
const tree = trees[params.lang];
const tree = trees[params.lang]
return (
<DocsLayout
tree={tree ?? []}
navTitle="My App"
sidebarContent={
<LanguageSelect
languages={[
{
name: "English",
locale: "en",
},
{
name: "Chinese",
locale: "cn",
},
]}
/>
return (
<DocsLayout
tree={tree ?? []}
navTitle="My App"
sidebarContent={
<LanguageSelect
languages={[
{
name: 'English',
locale: 'en'
},
{
name: 'Chinese',
locale: 'cn'
}
>
{children}
</DocsLayout>
);
]}
/>
}
>
{children}
</DocsLayout>
)
}
```
## Final Step
Replace all usage to original `tree` with `trees[lang]` and check if there's any legacy usage.
Replace all usage to original `tree` with `trees[lang]` and check if there's any
legacy usage.
**Before**
@@ -117,13 +124,13 @@ Generate parameters for every language and page.
```tsx
export async function generateStaticParams(): Promise<
{ lang: string; slug: string[] }[]
{ lang: string; slug: string[] }[]
> {
return languages.flatMap((lang) =>
getPages(lang)!.map((docs) => ({
slug: docs.slug.split("/"),
lang: lang,
}))
);
return languages.flatMap(lang =>
getPages(lang)!.map(docs => ({
slug: docs.slug.split('/'),
lang: lang
}))
)
}
```
+15 -15
View File
@@ -1,17 +1,17 @@
{
"pages": [
"---Guide---",
"index",
"theme",
"images",
"internationalization",
"---Components---",
"components/codeblock",
"components/card",
"components/heading",
"components/roll-button",
"---Blocks---",
"blocks/page",
"blocks/layout"
]
"pages": [
"---Guide---",
"index",
"theme",
"images",
"internationalization",
"---Components---",
"components/codeblock",
"components/card",
"components/heading",
"components/roll-button",
"---Blocks---",
"blocks/page",
"blocks/layout"
]
}
+61 -54
View File
@@ -3,22 +3,23 @@ title: Theming
description: Add Theme to Next Docs UI
---
The styling system is based on [Shadcn UI](https://ui.shadcn.com), allowing you to overwrite css variables.
The styling system is based on [Shadcn UI](https://ui.shadcn.com), allowing you
to overwrite css variables.
## Example
```css title="global.css"
:root {
/* hsl colors */
/* use whitespace instead of comma */
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
/* hsl colors */
/* use whitespace instead of comma */
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
}
```
@@ -31,67 +32,67 @@ The styling system is based on [Shadcn UI](https://ui.shadcn.com), allowing you
```css
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
--radius: 0.5rem;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--ring: 216 34% 17%;
}
```
@@ -101,24 +102,30 @@ The styling system is based on [Shadcn UI](https://ui.shadcn.com), allowing you
## Global Styles
By importing `next-docs-ui/style.css` the default border, text and background colors will be changed.
By importing `next-docs-ui/style.css` the default border, text and background
colors will be changed.
CSS selectors such as `[data-rehype-pretty-code-fragment]` has special meanings for codeblock styling.
CSS selectors such as `[data-rehype-pretty-code-fragment]` has special meanings
for codeblock styling.
## Tailwind CSS
You can use Next Docs UI with Tailwind CSS, and copy the Tailwind [configuration](https://ui.shadcn.com/docs/installation/manual#configure-tailwindconfigjs) from Shadcn UI.
You can use Next Docs UI with Tailwind CSS, and copy the Tailwind
[configuration](https://ui.shadcn.com/docs/installation/manual#configure-tailwindconfigjs)
from Shadcn UI.
Since Next Docs UI uses Tailwind CSS for styling, the `nd-` prefix is added to all classes in order to prevent conflicts.
Since Next Docs UI uses Tailwind CSS for styling, the `nd-` prefix is added to
all classes in order to prevent conflicts.
### Fix Preflight
Some global styles might be overwritten by [Tailwind CSS Preflight](https://tailwindcss.com/docs/preflight).
Some global styles might be overwritten by
[Tailwind CSS Preflight](https://tailwindcss.com/docs/preflight).
Please add the following to your `style.css`.
```css
* {
border-color: hsl(var(--border));
border-color: hsl(var(--border));
}
```
+3 -3
View File
@@ -1,4 +1,4 @@
import { makeSource } from "contentlayer/source-files";
import { defaultConfig } from "next-docs-ui/contentlayer";
import { makeSource } from 'contentlayer/source-files'
import { defaultConfig } from 'next-docs-ui/contentlayer'
export default makeSource(defaultConfig);
export default makeSource(defaultConfig)
+11 -11
View File
@@ -1,16 +1,16 @@
const withAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
const withAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
/** @type {import('next').NextConfig} */
const config = {
pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"],
reactStrictMode: true,
images: {
domains: ["i.pravatar.cc"],
},
};
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
reactStrictMode: true,
images: {
domains: ['i.pravatar.cc']
}
}
const { withContentlayer } = require("next-contentlayer");
const { withContentlayer } = require('next-contentlayer')
module.exports = withAnalyzer(withContentlayer(config));
module.exports = withAnalyzer(withContentlayer(config))
+44 -44
View File
@@ -1,46 +1,46 @@
{
"name": "docs",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.1.1",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-select": "^1.2.1",
"autoprefixer": "10.4.14",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"contentlayer": "^0.3.4",
"lucide-react": "^0.190.0",
"next": "13.4.12",
"next-contentlayer": "^0.3.4",
"next-docs-ui": "workspace:*",
"next-docs-zeta": "workspace:*",
"next-themes": "^0.2.1",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"rehype-img-size": "^1.0.1",
"rehype-pretty-code": "^0.9.5",
"rehype-slug": "^5.1.0",
"remark-gfm": "^3.0.1",
"swr": "^2.1.5",
"tailwind-merge": "^1.12.0"
},
"devDependencies": {
"@next/bundle-analyzer": "^13.4.1",
"@types/flexsearch": "0.7.3",
"@types/node": "18.16.3",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"tailwindcss": "3.3.2",
"tailwindcss-animate": "^1.0.5"
}
"name": "docs",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.1.1",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-select": "^1.2.1",
"autoprefixer": "10.4.14",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"contentlayer": "^0.3.4",
"lucide-react": "^0.190.0",
"next": "13.4.12",
"next-contentlayer": "^0.3.4",
"next-docs-ui": "workspace:*",
"next-docs-zeta": "workspace:*",
"next-themes": "^0.2.1",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"rehype-img-size": "^1.0.1",
"rehype-pretty-code": "^0.9.5",
"rehype-slug": "^5.1.0",
"remark-gfm": "^3.0.1",
"swr": "^2.1.5",
"tailwind-merge": "^1.12.0"
},
"devDependencies": {
"@next/bundle-analyzer": "^13.4.1",
"@types/flexsearch": "0.7.3",
"@types/node": "18.16.3",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"tailwindcss": "3.3.2",
"tailwindcss-animate": "^1.0.5"
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}
+102 -103
View File
@@ -1,106 +1,105 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: [
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./content/**/*.mdx",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1200px",
},
},
extend: {
backgroundImage: {
"gradient-to-animated":
"linear-gradient(var(--rotate-angle),var(--tw-gradient-stops))",
"gradient-radial":
"radial-gradient(circle, var(--tw-gradient-stops))",
"gradient-radial-top":
"radial-gradient(40% 60% at top, var(--tw-gradient-stops))",
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
transitionProperty: {
"rotate-angle": "--rotate-angle",
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
star: {
"0%, 100%": {
transform: "translateY(0) scale(var(--tw-scale-x))",
opacity: 1,
},
"50%": {
transform: "translateY(40px) scale(var(--tw-scale-x))",
opacity: 0.2,
},
},
heart: {
"0%": {
"stroke-dashoffset": 0,
},
"50%, 100%": {
"stroke-dashoffset": 400,
},
},
},
animation: {
star: "star 4s cubic-bezier(0.4, 0, 0.6, 1) infinite",
heart: "heart 1s linear infinite",
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
darkMode: 'class',
content: [
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./content/**/*.mdx'
],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1200px'
}
},
plugins: [require("tailwindcss-animate")],
};
extend: {
backgroundImage: {
'gradient-to-animated':
'linear-gradient(var(--rotate-angle),var(--tw-gradient-stops))',
'gradient-radial': 'radial-gradient(circle, var(--tw-gradient-stops))',
'gradient-radial-top':
'radial-gradient(40% 60% at top, var(--tw-gradient-stops))'
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
}
},
transitionProperty: {
'rotate-angle': '--rotate-angle'
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: { height: 0 },
to: { height: 'var(--radix-accordion-content-height)' }
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: 0 }
},
star: {
'0%, 100%': {
transform: 'translateY(0) scale(var(--tw-scale-x))',
opacity: 1
},
'50%': {
transform: 'translateY(40px) scale(var(--tw-scale-x))',
opacity: 0.2
}
},
heart: {
'0%': {
'stroke-dashoffset': 0
},
'50%, 100%': {
'stroke-dashoffset': 400
}
}
},
animation: {
star: 'star 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
heart: 'heart 1s linear infinite',
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [require('tailwindcss-animate')]
}
+33 -33
View File
@@ -1,36 +1,36 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
},
"plugins": [
{
"name": "next"
}
]
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
],
"exclude": ["node_modules"]
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
],
"exclude": ["node_modules"]
}
+1 -1
View File
@@ -1 +1 @@
export { twMerge as cn } from "tailwind-merge";
export { twMerge as cn } from 'tailwind-merge'
+13 -13
View File
@@ -1,20 +1,20 @@
import { allDocs, allMeta } from "contentlayer/generated";
import { buildPageTree, loadContext } from "next-docs-zeta/contentlayer";
import type { TreeNode } from "next-docs-zeta/server";
import { allDocs, allMeta } from 'contentlayer/generated'
import { buildPageTree, loadContext } from 'next-docs-zeta/contentlayer'
import type { TreeNode } from 'next-docs-zeta/server'
const ctx = loadContext(allMeta, allDocs);
const ctx = loadContext(allMeta, allDocs)
export const uiTree = buildPageTree(ctx, {
root: "docs/ui",
});
root: 'docs/ui'
})
export const headlessTree = buildPageTree(ctx, {
root: "docs/headless",
});
root: 'docs/headless'
})
export function getTree(mode: "ui" | "headless" | string): TreeNode[] {
if (mode === "ui") {
return uiTree;
}
export function getTree(mode: 'ui' | 'headless' | string): TreeNode[] {
if (mode === 'ui') {
return uiTree
}
return headlessTree;
return headlessTree
}
@@ -1 +1 @@
export { default } from "next-docs-ui/not-found";
export { default } from 'next-docs-ui/not-found'
@@ -1,88 +1,87 @@
import { DocsPage } from "next-docs-ui/page";
import { languages } from '@/app/i18n'
import type { Metadata } from 'next'
import { getMDXComponent } from 'next-contentlayer/hooks'
import {
Heading,
Image,
Pre,
Link,
Table,
MDXContent,
Card,
Cards,
} from "next-docs-ui/mdx";
import { getTableOfContents } from "next-docs-zeta/server";
import { notFound } from "next/navigation";
import { getPages, getPage, trees } from "../../tree";
import { getMDXComponent } from "next-contentlayer/hooks";
import { languages } from "@/app/i18n";
import type { Metadata } from "next";
Card,
Cards,
Heading,
Image,
Link,
MDXContent,
Pre,
Table
} from 'next-docs-ui/mdx'
import { DocsPage } from 'next-docs-ui/page'
import { getTableOfContents } from 'next-docs-zeta/server'
import { notFound } from 'next/navigation'
import { getPage, getPages, trees } from '../../tree'
export default async function Page({
params,
params
}: {
params: { lang: string; slug?: string[] };
params: { lang: string; slug?: string[] }
}) {
if (!languages.includes(params.lang)) {
notFound();
}
if (!languages.includes(params.lang)) {
notFound()
}
const tree = trees[params.lang];
const page = getPage(params.lang, params.slug);
const tree = trees[params.lang]
const page = getPage(params.lang, params.slug)
if (page == null) {
notFound();
}
if (page == null) {
notFound()
}
const toc = await getTableOfContents(page.body.raw);
const MDX = getMDXComponent(page.body.code);
const toc = await getTableOfContents(page.body.raw)
const MDX = getMDXComponent(page.body.code)
return (
<DocsPage toc={toc} tree={tree}>
<MDXContent>
<h1>{page.title}</h1>
<MDX
components={{
table: (props) => <Table {...props} />,
pre: (props) => <Pre {...props} />,
a: (props) => <Link {...props} />,
h1: (props) => <Heading as="h1" {...props} />,
h2: (props) => <Heading as="h2" {...props} />,
h3: (props) => <Heading as="h3" {...props} />,
h4: (props) => <Heading as="h4" {...props} />,
h5: (props) => <Heading as="h5" {...props} />,
h6: (props) => <Heading as="h6" {...props} />,
img: (props) => <Image {...props} />,
Card: (props) => <Card {...props} />,
Cards: (props) => <Cards {...props} />,
}}
/>
</MDXContent>
</DocsPage>
);
return (
<DocsPage toc={toc} tree={tree}>
<MDXContent>
<h1>{page.title}</h1>
<MDX
components={{
table: props => <Table {...props} />,
pre: props => <Pre {...props} />,
a: props => <Link {...props} />,
h1: props => <Heading as="h1" {...props} />,
h2: props => <Heading as="h2" {...props} />,
h3: props => <Heading as="h3" {...props} />,
h4: props => <Heading as="h4" {...props} />,
h5: props => <Heading as="h5" {...props} />,
h6: props => <Heading as="h6" {...props} />,
img: props => <Image {...props} />,
Card: props => <Card {...props} />,
Cards: props => <Cards {...props} />
}}
/>
</MDXContent>
</DocsPage>
)
}
export async function generateStaticParams(): Promise<
{ lang: string; slug: string[] }[]
{ lang: string; slug: string[] }[]
> {
return languages.flatMap((lang) =>
getPages(lang)!.map((docs) => ({
slug: docs.slug.split("/"),
lang: lang,
}))
);
return languages.flatMap(lang =>
getPages(lang)!.map(docs => ({
slug: docs.slug.split('/'),
lang
}))
)
}
export function generateMetadata({
params,
params
}: {
params: { lang: string; slug?: string[] };
params: { lang: string; slug?: string[] }
}) {
const page = getPage(params.lang, params.slug);
const page = getPage(params.lang, params.slug)
if (page == null) return;
if (page == null) return
return {
title: page.title,
description: page.description,
} satisfies Metadata;
return {
title: page.title,
description: page.description
} satisfies Metadata
}
+85 -30
View File
@@ -1,38 +1,93 @@
import { DocsLayout } from "next-docs-ui/layout";
import { trees } from "../tree";
import { ReactNode } from "react";
import { LanguageSelect } from "next-docs-ui/i18n";
import { LanguageSelect } from 'next-docs-ui/i18n'
import { DocsLayout } from 'next-docs-ui/layout'
import { RootProvider } from 'next-docs-ui/provider'
import { Inter } from 'next/font/google'
import type { ReactNode } from 'react'
import { ClientI18nProvider } from '../provider'
import { trees } from '../tree'
import 'next-docs-ui/style.css'
import '../style.css'
const inter = Inter({
subsets: ['latin']
})
export default function Layout({
params,
children,
params,
children
}: {
params: { lang: string };
children: ReactNode;
params: { lang: string }
children: ReactNode
}) {
const tree = trees[params.lang];
const tree = trees[params.lang]
return (
<DocsLayout
tree={tree ?? []}
navTitle="My App"
githubUrl="https://github.com/SonMooSans/next-docs"
sidebarContent={
return (
<html lang={params.lang} className={inter.className}>
<body
style={{
position: 'relative',
display: 'flex',
flexDirection: 'column',
minHeight: '100vh'
}}
>
<ClientI18nProvider>
<RootProvider>
<div
style={{
position: 'absolute',
inset: 0,
zIndex: -1,
overflow: 'hidden'
}}
>
<div
style={{
position: 'absolute',
top: 0,
right: 0,
width: '100%',
height: 500,
background:
'linear-gradient(to bottom left, hsl(var(--gradient) / 0.5), hsl(var(--background)) 50%)'
}}
/>
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
height: 500,
background:
'linear-gradient(to top right, hsl(650 50% 50% / 0.2), transparent 30%)'
}}
/>
</div>
<DocsLayout
tree={tree ?? []}
navTitle="My App"
githubUrl="https://github.com/SonMooSans/next-docs"
sidebarContent={
<LanguageSelect
languages={[
{
name: "English",
locale: "en",
},
{
name: "Chinese",
locale: "cn",
},
]}
languages={[
{
name: 'English',
locale: 'en'
},
{
name: 'Chinese',
locale: 'cn'
}
]}
/>
}
>
{children}
</DocsLayout>
);
}
>
{children}
</DocsLayout>
</RootProvider>
</ClientI18nProvider>
</body>
</html>
)
}
+13 -13
View File
@@ -1,16 +1,16 @@
import { languages } from "@/app/i18n";
import { getPages } from "@/app/tree";
import { getPageUrl } from "next-docs-zeta/contentlayer";
import { initI18nSearchAPI } from "next-docs-zeta/server";
import { languages } from '@/app/i18n'
import { getPages } from '@/app/tree'
import { getPageUrl } from 'next-docs-zeta/contentlayer'
import { initI18nSearchAPI } from 'next-docs-zeta/server'
export const { GET } = initI18nSearchAPI(
languages.map((lang) => {
const pages = getPages(lang)!.map((page) => ({
title: page.title,
content: page.body.raw,
url: getPageUrl(page.slug.split("/"), "/", lang),
}));
languages.map(lang => {
const pages = getPages(lang)!.map(page => ({
title: page.title,
content: page.body.raw,
url: getPageUrl(page.slug.split('/'), '/', lang)
}))
return [lang, pages];
})
);
return [lang, pages]
})
)
+2 -2
View File
@@ -1,2 +1,2 @@
export const defaultLanguage = "en";
export const languages = ["en", "cn"];
export const defaultLanguage = 'en'
export const languages = ['en', 'cn']
-75
View File
@@ -1,75 +0,0 @@
import { Inter } from "next/font/google";
import { RootProvider } from "next-docs-ui/provider";
import { ClientI18nProvider } from "./provider";
import type { Metadata } from "next";
import "next-docs-ui/style.css";
import "./style.css";
export const metadata: Metadata = {
title: {
default: "My App",
template: "My App | %s",
},
description: "Generated by Next.js",
};
const inter = Inter({
subsets: ["latin"],
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<body
style={{
position: "relative",
display: "flex",
flexDirection: "column",
minHeight: "100vh",
}}
>
<ClientI18nProvider>
<RootProvider>
<div
style={{
position: "absolute",
inset: 0,
zIndex: -1,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: "100%",
height: 500,
background:
"linear-gradient(to bottom left, hsl(var(--gradient) / 0.5), hsl(var(--background)) 50%)",
}}
/>
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
height: 500,
background:
"linear-gradient(to top right, hsl(650 50% 50% / 0.2), transparent 30%)",
}}
/>
</div>
{children}
</RootProvider>
</ClientI18nProvider>
</body>
</html>
);
}
+23 -21
View File
@@ -1,27 +1,29 @@
"use client";
import { I18nProvider } from "next-docs-ui/i18n";
import { useParams, usePathname, useRouter } from "next/navigation";
import { ReactNode, useCallback } from "react";
import { defaultLanguage } from "./i18n";
'use client'
import { I18nProvider } from 'next-docs-ui/i18n'
import { useParams, usePathname, useRouter } from 'next/navigation'
import type { ReactNode } from 'react'
import { useCallback } from 'react'
import { defaultLanguage } from './i18n'
export function ClientI18nProvider({ children }: { children: ReactNode }) {
const router = useRouter();
const pathname = usePathname();
const { lang } = useParams() as { lang?: string };
const onChange = useCallback(
(v: string) => {
const segments = pathname.split("/");
const router = useRouter()
const pathname = usePathname()
const { lang } = useParams() as { lang?: string }
const onChange = useCallback(
(v: string) => {
const segments = pathname.split('/')
segments[1] = v;
segments[1] = v
router.push(segments.join("/"));
},
[router, pathname]
);
router.push(segments.join('/'))
},
[router, pathname]
)
return (
<I18nProvider value={{ locale: lang ?? defaultLanguage, onChange }}>
{children}
</I18nProvider>
);
return (
<I18nProvider value={{ locale: lang ?? defaultLanguage, onChange }}>
{children}
</I18nProvider>
)
}
+3 -3
View File
@@ -1,8 +1,8 @@
:root {
--gradient: 200 90% 85%;
--primary: 270 95% 75% !important;
--gradient: 200 90% 85%;
--primary: 270 95% 75% !important;
}
.dark {
--gradient: 630 50% 50%;
--gradient: 630 50% 50%;
}
+10 -10
View File
@@ -1,14 +1,14 @@
import { allMeta, allDocs } from "contentlayer/generated";
import { allDocs, allMeta } from 'contentlayer/generated'
import {
buildI18nPageTree,
createUtils,
loadContext,
} from "next-docs-zeta/contentlayer";
import { languages } from "./i18n";
buildI18nPageTree,
createUtils,
loadContext
} from 'next-docs-zeta/contentlayer'
import { languages } from './i18n'
const ctx = loadContext(allMeta, allDocs, languages);
const ctx = loadContext(allMeta, allDocs, languages)
export const trees = buildI18nPageTree(ctx, languages, {
baseUrl: "/",
});
baseUrl: '/'
})
export const { getPage, getPages } = createUtils(ctx);
export const { getPage, getPages } = createUtils(ctx)
@@ -5,5 +5,5 @@ title: Cards
Try components.
<Cards>
<Card href="../" title="Hello" description="Open this card" />
<Card href="../" title="Hello" description="Open this card" />
</Cards>
+1 -1
View File
@@ -8,7 +8,7 @@ title: 快速開始
```js
// 這會將“Hello World”記錄到控制台
console.log("你好世界");
console.log('你好世界')
```
| 名稱 | 組件 |
+1 -1
View File
@@ -8,7 +8,7 @@ What is even better than a battle of juice? `hello world`.
```js
// this will log "Hello World" to console
console.log("Hello World");
console.log('Hello World')
```
| Name | Comopnent |
@@ -17,9 +17,9 @@ Tardantis magno _quod_ qui militiam mutabile ferentes tenus ab tendit est
prospexit! Sibi obstipui, fortuna quaerunt placet referre mille umentes stagna.
Stipitis quamvis, ibi cristis pactus nec, in _venit quondam Apollineos_ dubita
minus, o, quem. Et similes leonis viae extis inque quae abiit opposuit notum
tuta induruit Phoebus amplectitur, cultus [Mulciber
letum](http://www.fronte.io/es)? Densus recingitur dixisse magistris lenta,
relinquent Alce ignis te.
tuta induruit Phoebus amplectitur, cultus
[Mulciber letum](http://www.fronte.io/es)? Densus recingitur dixisse magistris
lenta, relinquent Alce ignis te.
Delicuit fulgura ego et multa, moriens ante Phaestiadas zephyris, marmoreis
trepidare tamen, avidas tot pollice. Gnatis ad puer simulacraque annum saltus
@@ -144,10 +144,10 @@ Lorem markdownum _se referre precari_ pruinas et cornua, ignes in o per ducebas
**carmina**; sine. Vocat tantum huic occurrensque saltu illum; tuta temperat est
vidi foedere docendam sublimemque cognoscere videnda sub.
> Timuit Iuno vestem nemus terris frondator [bracchia captum
> quadriiugi](http://www.hoc.net/) inamabile venefica nec valuit scopulus luco
> cito, sibi. Ille _vulnera_: edentem ex inducta immaduisse auro! Hoc nec
> vicina, sonant sibi optas: nemus lintea.
> Timuit Iuno vestem nemus terris frondator
> [bracchia captum quadriiugi](http://www.hoc.net/) inamabile venefica nec
> valuit scopulus luco cito, sibi. Ille _vulnera_: edentem ex inducta immaduisse
> auro! Hoc nec vicina, sonant sibi optas: nemus lintea.
## Extendi teli humanam paret
@@ -164,10 +164,10 @@ Premitur quoque in coryli vires fuit vota nuribusque, contigerant donec, propior
vetorque forti. Senecta instructo membra: **pectora** accipiunt elusaque longa
trieterica, Inachidos mei inquit: et leti, epulanda si ignes. Emicuit tepebat
conplexa temptatos patri amoris [passa](http://dat.net/). Animo repetitum parens
exibit, his causa esse deducentia tenuit [colla
tempore](http://aethiopasquenolis.net/reliquit-abstulit) quem faciat! Cultis
_amnis quid_ leves; longoque mea _pater moenia et_ validum in nervis ut utque
unxere, ut.
exibit, his causa esse deducentia tenuit
[colla tempore](http://aethiopasquenolis.net/reliquit-abstulit) quem faciat!
Cultis _amnis quid_ leves; longoque mea _pater moenia et_ validum in nervis ut
utque unxere, ut.
> Solidas ad cursu liquidis concolor caedis me contemptoremque illam nec, amens
> luminibus agitat. Illa versato, aut Veneris, tua cornua instar omnis beatus.
@@ -179,10 +179,10 @@ dea armis robustior tum? Lugubris ardua. Ubi Achaia adicit caelo mersisque
**rumpere gravidae fatidicamque** atria, pedibusque. Cupit non fundunt obstat,
**somno qui volumine** quoque ventis agitant interea vocisque tellus Stygiae.
- Lacrimis sunt Talibus
- Non pudorem
- Contigit faciemque imitata mollibat mihi ego feruntur
- Ille illae et quod ignibus manibus vidit
- Lacrimis sunt Talibus
- Non pudorem
- Contigit faciemque imitata mollibat mihi ego feruntur
- Ille illae et quod ignibus manibus vidit
## Pariterque illa
@@ -251,17 +251,17 @@ nervoque hora.
## Exilio nostros potentia et cubilia Iris
Viriles populi voce iurant tumulo forma celsior, alis novissimus, pictis ducar.
Quod amnes exspatiata te nexuque solvit [plectrumque
iuvat](http://flere.com/illa) tamen inmissa: sed sonis fatorum. Tuorum sororibus
ramis quaedam, amori cognoscet aut rapuere audierit sine vos aevo munusque o
satis, cadet! Onus aetas, enim magis, **daret dixit** sonat;
Quod amnes exspatiata te nexuque solvit
[plectrumque iuvat](http://flere.com/illa) tamen inmissa: sed sonis fatorum.
Tuorum sororibus ramis quaedam, amori cognoscet aut rapuere audierit sine vos
aevo munusque o satis, cadet! Onus aetas, enim magis, **daret dixit** sonat;
[Sic](http://tanto.io/) at sumite amici miratur et procul Dulichius.
- Ora Aiax
- Orare Aeneae
- Luctus Phoebes sed factura mecum Antaeo
- Vultus incessere crescunt petit hoc patrio furit
- Crista natos et quam mitem accensa condi
- Ora Aiax
- Orare Aeneae
- Luctus Phoebes sed factura mecum Antaeo
- Vultus incessere crescunt petit hoc patrio furit
- Crista natos et quam mitem accensa condi
## Arbor nega profeci miraris ubique
@@ -302,8 +302,9 @@ Echo [medios](http://gargaphie.net/dicitqua) illa patriam: sive suas **mersis
nec** caput inquit, Nereidum, morbo. Credere petunt. Igne vertice incepto animos
nimbis adspergine utque sibi tum, miles nunc sanguis similis, quo vix perque.
Tigride labori: calet in bis **potenti**, resilire si formae partim? Quid
pectora veniam praedelassat iuvenes ea sensit vasta umor [sororis Laertiadaeque
postarum](http://forem.com/errorem-deficit) et portus trementem pollice.
pectora veniam praedelassat iuvenes ea sensit vasta umor
[sororis Laertiadaeque postarum](http://forem.com/errorem-deficit) et portus
trementem pollice.
> Est tu proxima recepta tingui ubi tinctas corpore vultus notam signumque longo
> sensit ultima in summa. In nec flentibus res plangi in arva patre solis
@@ -315,11 +316,11 @@ postarum](http://forem.com/errorem-deficit) et portus trementem pollice.
Amborum [turba](http://www.deceptus-nec.org/festa) liquefacta delabere dedit
vero minister, Theseus. Inrita facto solacia si praebet Styga viribus scilicet
facta in. Lavere est _ferrum resonis turpis_ ventos; deos [alti ferarum
aras](http://humi.org/retro-secum.html) uti dabitur petit, patula plangore
_lupos_! Electae paelex creberrima fuerant Aiaci tu alii committit, Apidani. Cum
vacca est nullo illi aere urbe magnis videat creatis: foramina quid patet
solane, hi petitur quod.
facta in. Lavere est _ferrum resonis turpis_ ventos; deos
[alti ferarum aras](http://humi.org/retro-secum.html) uti dabitur petit, patula
plangore _lupos_! Electae paelex creberrima fuerant Aiaci tu alii committit,
Apidani. Cum vacca est nullo illi aere urbe magnis videat creatis: foramina quid
patet solane, hi petitur quod.
> Sequar ille, tamen, admonitu quaerit, fugis idem hominis? Quoque coeperunt
> miseros murmur discrimine videre ab fertque: facta videre, est adest cura!
+7 -7
View File
@@ -1,9 +1,9 @@
{
"pages": [
"---指南---",
"index",
"install",
"---API Reference---",
"components"
]
"pages": [
"---指南---",
"index",
"install",
"---API Reference---",
"components"
]
}
+29 -29
View File
@@ -1,31 +1,31 @@
{
"pages": [
"---Guide---",
"index",
"playground",
"---Installation---",
"installation/next",
"installation/remix",
"installation/qwik",
"installation/vite",
"---Migrate---",
"migrate/1",
"migrate/2",
"---Components---",
"components/input",
"components/select",
"components/avatar",
"components/cards",
"components/button",
"components/tabs",
"components/progress",
"components/spinner",
"components/calendar",
"components/checkbox",
"components/combobox",
"components/accordion",
"components/date-picker",
"components/form",
"ssr"
]
"pages": [
"---Guide---",
"index",
"playground",
"---Installation---",
"installation/next",
"installation/remix",
"installation/qwik",
"installation/vite",
"---Migrate---",
"migrate/1",
"migrate/2",
"---Components---",
"components/input",
"components/select",
"components/avatar",
"components/cards",
"components/button",
"components/tabs",
"components/progress",
"components/spinner",
"components/calendar",
"components/checkbox",
"components/combobox",
"components/accordion",
"components/date-picker",
"components/form",
"ssr"
]
}
+32 -31
View File
@@ -17,9 +17,9 @@ Tardantis magno _quod_ qui militiam mutabile ferentes tenus ab tendit est
prospexit! Sibi obstipui, fortuna quaerunt placet referre mille umentes stagna.
Stipitis quamvis, ibi cristis pactus nec, in _venit quondam Apollineos_ dubita
minus, o, quem. Et similes leonis viae extis inque quae abiit opposuit notum
tuta induruit Phoebus amplectitur, cultus [Mulciber
letum](http://www.fronte.io/es)? Densus recingitur dixisse magistris lenta,
relinquent Alce ignis te.
tuta induruit Phoebus amplectitur, cultus
[Mulciber letum](http://www.fronte.io/es)? Densus recingitur dixisse magistris
lenta, relinquent Alce ignis te.
Delicuit fulgura ego et multa, moriens ante Phaestiadas zephyris, marmoreis
trepidare tamen, avidas tot pollice. Gnatis ad puer simulacraque annum saltus
@@ -144,10 +144,10 @@ Lorem markdownum _se referre precari_ pruinas et cornua, ignes in o per ducebas
**carmina**; sine. Vocat tantum huic occurrensque saltu illum; tuta temperat est
vidi foedere docendam sublimemque cognoscere videnda sub.
> Timuit Iuno vestem nemus terris frondator [bracchia captum
> quadriiugi](http://www.hoc.net/) inamabile venefica nec valuit scopulus luco
> cito, sibi. Ille _vulnera_: edentem ex inducta immaduisse auro! Hoc nec
> vicina, sonant sibi optas: nemus lintea.
> Timuit Iuno vestem nemus terris frondator
> [bracchia captum quadriiugi](http://www.hoc.net/) inamabile venefica nec
> valuit scopulus luco cito, sibi. Ille _vulnera_: edentem ex inducta immaduisse
> auro! Hoc nec vicina, sonant sibi optas: nemus lintea.
## Extendi teli humanam paret
@@ -164,10 +164,10 @@ Premitur quoque in coryli vires fuit vota nuribusque, contigerant donec, propior
vetorque forti. Senecta instructo membra: **pectora** accipiunt elusaque longa
trieterica, Inachidos mei inquit: et leti, epulanda si ignes. Emicuit tepebat
conplexa temptatos patri amoris [passa](http://dat.net/). Animo repetitum parens
exibit, his causa esse deducentia tenuit [colla
tempore](http://aethiopasquenolis.net/reliquit-abstulit) quem faciat! Cultis
_amnis quid_ leves; longoque mea _pater moenia et_ validum in nervis ut utque
unxere, ut.
exibit, his causa esse deducentia tenuit
[colla tempore](http://aethiopasquenolis.net/reliquit-abstulit) quem faciat!
Cultis _amnis quid_ leves; longoque mea _pater moenia et_ validum in nervis ut
utque unxere, ut.
> Solidas ad cursu liquidis concolor caedis me contemptoremque illam nec, amens
> luminibus agitat. Illa versato, aut Veneris, tua cornua instar omnis beatus.
@@ -179,10 +179,10 @@ dea armis robustior tum? Lugubris ardua. Ubi Achaia adicit caelo mersisque
**rumpere gravidae fatidicamque** atria, pedibusque. Cupit non fundunt obstat,
**somno qui volumine** quoque ventis agitant interea vocisque tellus Stygiae.
- Lacrimis sunt Talibus
- Non pudorem
- Contigit faciemque imitata mollibat mihi ego feruntur
- Ille illae et quod ignibus manibus vidit
- Lacrimis sunt Talibus
- Non pudorem
- Contigit faciemque imitata mollibat mihi ego feruntur
- Ille illae et quod ignibus manibus vidit
## Pariterque illa
@@ -251,17 +251,17 @@ nervoque hora.
## Exilio nostros potentia et cubilia Iris
Viriles populi voce iurant tumulo forma celsior, alis novissimus, pictis ducar.
Quod amnes exspatiata te nexuque solvit [plectrumque
iuvat](http://flere.com/illa) tamen inmissa: sed sonis fatorum. Tuorum sororibus
ramis quaedam, amori cognoscet aut rapuere audierit sine vos aevo munusque o
satis, cadet! Onus aetas, enim magis, **daret dixit** sonat;
Quod amnes exspatiata te nexuque solvit
[plectrumque iuvat](http://flere.com/illa) tamen inmissa: sed sonis fatorum.
Tuorum sororibus ramis quaedam, amori cognoscet aut rapuere audierit sine vos
aevo munusque o satis, cadet! Onus aetas, enim magis, **daret dixit** sonat;
[Sic](http://tanto.io/) at sumite amici miratur et procul Dulichius.
- Ora Aiax
- Orare Aeneae
- Luctus Phoebes sed factura mecum Antaeo
- Vultus incessere crescunt petit hoc patrio furit
- Crista natos et quam mitem accensa condi
- Ora Aiax
- Orare Aeneae
- Luctus Phoebes sed factura mecum Antaeo
- Vultus incessere crescunt petit hoc patrio furit
- Crista natos et quam mitem accensa condi
## Arbor nega profeci miraris ubique
@@ -302,8 +302,9 @@ Echo [medios](http://gargaphie.net/dicitqua) illa patriam: sive suas **mersis
nec** caput inquit, Nereidum, morbo. Credere petunt. Igne vertice incepto animos
nimbis adspergine utque sibi tum, miles nunc sanguis similis, quo vix perque.
Tigride labori: calet in bis **potenti**, resilire si formae partim? Quid
pectora veniam praedelassat iuvenes ea sensit vasta umor [sororis Laertiadaeque
postarum](http://forem.com/errorem-deficit) et portus trementem pollice.
pectora veniam praedelassat iuvenes ea sensit vasta umor
[sororis Laertiadaeque postarum](http://forem.com/errorem-deficit) et portus
trementem pollice.
> Est tu proxima recepta tingui ubi tinctas corpore vultus notam signumque longo
> sensit ultima in summa. In nec flentibus res plangi in arva patre solis
@@ -315,11 +316,11 @@ postarum](http://forem.com/errorem-deficit) et portus trementem pollice.
Amborum [turba](http://www.deceptus-nec.org/festa) liquefacta delabere dedit
vero minister, Theseus. Inrita facto solacia si praebet Styga viribus scilicet
facta in. Lavere est _ferrum resonis turpis_ ventos; deos [alti ferarum
aras](http://humi.org/retro-secum.html) uti dabitur petit, patula plangore
_lupos_! Electae paelex creberrima fuerant Aiaci tu alii committit, Apidani. Cum
vacca est nullo illi aere urbe magnis videat creatis: foramina quid patet
solane, hi petitur quod.
facta in. Lavere est _ferrum resonis turpis_ ventos; deos
[alti ferarum aras](http://humi.org/retro-secum.html) uti dabitur petit, patula
plangore _lupos_! Electae paelex creberrima fuerant Aiaci tu alii committit,
Apidani. Cum vacca est nullo illi aere urbe magnis videat creatis: foramina quid
patet solane, hi petitur quod.
> Sequar ille, tamen, admonitu quaerit, fugis idem hominis? Quoque coeperunt
> miseros murmur discrimine videre ab fertque: facta videre, est adest cura!
+32 -31
View File
@@ -17,9 +17,9 @@ Tardantis magno _quod_ qui militiam mutabile ferentes tenus ab tendit est
prospexit! Sibi obstipui, fortuna quaerunt placet referre mille umentes stagna.
Stipitis quamvis, ibi cristis pactus nec, in _venit quondam Apollineos_ dubita
minus, o, quem. Et similes leonis viae extis inque quae abiit opposuit notum
tuta induruit Phoebus amplectitur, cultus [Mulciber
letum](http://www.fronte.io/es)? Densus recingitur dixisse magistris lenta,
relinquent Alce ignis te.
tuta induruit Phoebus amplectitur, cultus
[Mulciber letum](http://www.fronte.io/es)? Densus recingitur dixisse magistris
lenta, relinquent Alce ignis te.
Delicuit fulgura ego et multa, moriens ante Phaestiadas zephyris, marmoreis
trepidare tamen, avidas tot pollice. Gnatis ad puer simulacraque annum saltus
@@ -144,10 +144,10 @@ Lorem markdownum _se referre precari_ pruinas et cornua, ignes in o per ducebas
**carmina**; sine. Vocat tantum huic occurrensque saltu illum; tuta temperat est
vidi foedere docendam sublimemque cognoscere videnda sub.
> Timuit Iuno vestem nemus terris frondator [bracchia captum
> quadriiugi](http://www.hoc.net/) inamabile venefica nec valuit scopulus luco
> cito, sibi. Ille _vulnera_: edentem ex inducta immaduisse auro! Hoc nec
> vicina, sonant sibi optas: nemus lintea.
> Timuit Iuno vestem nemus terris frondator
> [bracchia captum quadriiugi](http://www.hoc.net/) inamabile venefica nec
> valuit scopulus luco cito, sibi. Ille _vulnera_: edentem ex inducta immaduisse
> auro! Hoc nec vicina, sonant sibi optas: nemus lintea.
## Extendi teli humanam paret
@@ -164,10 +164,10 @@ Premitur quoque in coryli vires fuit vota nuribusque, contigerant donec, propior
vetorque forti. Senecta instructo membra: **pectora** accipiunt elusaque longa
trieterica, Inachidos mei inquit: et leti, epulanda si ignes. Emicuit tepebat
conplexa temptatos patri amoris [passa](http://dat.net/). Animo repetitum parens
exibit, his causa esse deducentia tenuit [colla
tempore](http://aethiopasquenolis.net/reliquit-abstulit) quem faciat! Cultis
_amnis quid_ leves; longoque mea _pater moenia et_ validum in nervis ut utque
unxere, ut.
exibit, his causa esse deducentia tenuit
[colla tempore](http://aethiopasquenolis.net/reliquit-abstulit) quem faciat!
Cultis _amnis quid_ leves; longoque mea _pater moenia et_ validum in nervis ut
utque unxere, ut.
> Solidas ad cursu liquidis concolor caedis me contemptoremque illam nec, amens
> luminibus agitat. Illa versato, aut Veneris, tua cornua instar omnis beatus.
@@ -179,10 +179,10 @@ dea armis robustior tum? Lugubris ardua. Ubi Achaia adicit caelo mersisque
**rumpere gravidae fatidicamque** atria, pedibusque. Cupit non fundunt obstat,
**somno qui volumine** quoque ventis agitant interea vocisque tellus Stygiae.
- Lacrimis sunt Talibus
- Non pudorem
- Contigit faciemque imitata mollibat mihi ego feruntur
- Ille illae et quod ignibus manibus vidit
- Lacrimis sunt Talibus
- Non pudorem
- Contigit faciemque imitata mollibat mihi ego feruntur
- Ille illae et quod ignibus manibus vidit
## Pariterque illa
@@ -251,17 +251,17 @@ nervoque hora.
## Exilio nostros potentia et cubilia Iris
Viriles populi voce iurant tumulo forma celsior, alis novissimus, pictis ducar.
Quod amnes exspatiata te nexuque solvit [plectrumque
iuvat](http://flere.com/illa) tamen inmissa: sed sonis fatorum. Tuorum sororibus
ramis quaedam, amori cognoscet aut rapuere audierit sine vos aevo munusque o
satis, cadet! Onus aetas, enim magis, **daret dixit** sonat;
Quod amnes exspatiata te nexuque solvit
[plectrumque iuvat](http://flere.com/illa) tamen inmissa: sed sonis fatorum.
Tuorum sororibus ramis quaedam, amori cognoscet aut rapuere audierit sine vos
aevo munusque o satis, cadet! Onus aetas, enim magis, **daret dixit** sonat;
[Sic](http://tanto.io/) at sumite amici miratur et procul Dulichius.
- Ora Aiax
- Orare Aeneae
- Luctus Phoebes sed factura mecum Antaeo
- Vultus incessere crescunt petit hoc patrio furit
- Crista natos et quam mitem accensa condi
- Ora Aiax
- Orare Aeneae
- Luctus Phoebes sed factura mecum Antaeo
- Vultus incessere crescunt petit hoc patrio furit
- Crista natos et quam mitem accensa condi
## Arbor nega profeci miraris ubique
@@ -302,8 +302,9 @@ Echo [medios](http://gargaphie.net/dicitqua) illa patriam: sive suas **mersis
nec** caput inquit, Nereidum, morbo. Credere petunt. Igne vertice incepto animos
nimbis adspergine utque sibi tum, miles nunc sanguis similis, quo vix perque.
Tigride labori: calet in bis **potenti**, resilire si formae partim? Quid
pectora veniam praedelassat iuvenes ea sensit vasta umor [sororis Laertiadaeque
postarum](http://forem.com/errorem-deficit) et portus trementem pollice.
pectora veniam praedelassat iuvenes ea sensit vasta umor
[sororis Laertiadaeque postarum](http://forem.com/errorem-deficit) et portus
trementem pollice.
> Est tu proxima recepta tingui ubi tinctas corpore vultus notam signumque longo
> sensit ultima in summa. In nec flentibus res plangi in arva patre solis
@@ -315,11 +316,11 @@ postarum](http://forem.com/errorem-deficit) et portus trementem pollice.
Amborum [turba](http://www.deceptus-nec.org/festa) liquefacta delabere dedit
vero minister, Theseus. Inrita facto solacia si praebet Styga viribus scilicet
facta in. Lavere est _ferrum resonis turpis_ ventos; deos [alti ferarum
aras](http://humi.org/retro-secum.html) uti dabitur petit, patula plangore
_lupos_! Electae paelex creberrima fuerant Aiaci tu alii committit, Apidani. Cum
vacca est nullo illi aere urbe magnis videat creatis: foramina quid patet
solane, hi petitur quod.
facta in. Lavere est _ferrum resonis turpis_ ventos; deos
[alti ferarum aras](http://humi.org/retro-secum.html) uti dabitur petit, patula
plangore _lupos_! Electae paelex creberrima fuerant Aiaci tu alii committit,
Apidani. Cum vacca est nullo illi aere urbe magnis videat creatis: foramina quid
patet solane, hi petitur quod.
> Sequar ille, tamen, admonitu quaerit, fugis idem hominis? Quoque coeperunt
> miseros murmur discrimine videre ab fertque: facta videre, est adest cura!
+3 -3
View File
@@ -1,4 +1,4 @@
import { defaultConfig } from "next-docs-ui/contentlayer";
import { makeSource } from "contentlayer/source-files";
import { makeSource } from 'contentlayer/source-files'
import { defaultConfig } from 'next-docs-ui/contentlayer'
export default makeSource(defaultConfig);
export default makeSource(defaultConfig)
+12 -12
View File
@@ -1,17 +1,17 @@
import { NextRequest } from "next/server";
import { defaultLanguage, languages } from "./app/i18n";
import { createI18nMiddleware } from "next-docs-zeta/middleware";
import { createI18nMiddleware } from 'next-docs-zeta/middleware'
import type { NextRequest } from 'next/server'
import { defaultLanguage, languages } from './app/i18n'
export function middleware(request: NextRequest) {
return createI18nMiddleware(
request,
languages,
defaultLanguage,
(locale, slug) => `/${locale}/${slug}`
);
return createI18nMiddleware(
request,
languages,
defaultLanguage,
(locale, slug) => `/${locale}/${slug}`
)
}
export const config = {
// Matcher ignoring `/_next/` and `/api/`
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
// Matcher ignoring `/_next/` and `/api/`
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
+8 -8
View File
@@ -1,12 +1,12 @@
/** @type {import('next').NextConfig} */
const config = {
pageExtensions: ["ts", "tsx", "js", "jsx"],
reactStrictMode: true,
images: {
domains: ["i.pravatar.cc"],
},
};
pageExtensions: ['ts', 'tsx', 'js', 'jsx'],
reactStrictMode: true,
images: {
domains: ['i.pravatar.cc']
}
}
const { withContentlayer } = require("next-contentlayer");
const { withContentlayer } = require('next-contentlayer')
module.exports = withContentlayer(config);
module.exports = withContentlayer(config)
+22 -22
View File
@@ -1,24 +1,24 @@
{
"name": "website",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next-docs-zeta": "workspace:*",
"next-docs-ui": "workspace:*",
"next": "13.4.12",
"react": "18.2.0",
"react-dom": "18.2.0",
"contentlayer": "^0.3.4",
"next-contentlayer": "^0.3.4"
},
"devDependencies": {
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1"
}
"name": "website",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next-docs-zeta": "workspace:*",
"next-docs-ui": "workspace:*",
"next": "13.4.12",
"react": "18.2.0",
"react-dom": "18.2.0",
"contentlayer": "^0.3.4",
"next-contentlayer": "^0.3.4"
},
"devDependencies": {
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1"
}
}
+33 -33
View File
@@ -1,36 +1,36 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
},
"plugins": [
{
"name": "next"
}
]
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"],
"contentlayer/generated": ["./.contentlayer/generated"]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
],
"exclude": ["node_modules"]
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".contentlayer/generated"
],
"exclude": ["node_modules"]
}
+33 -23
View File
@@ -1,25 +1,35 @@
{
"name": "root",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "turbo run build --filter=./packages/\\*",
"clean": "turbo run clean",
"dev": "turbo run dev",
"dev:ui": "turbo run dev --filter=website...",
"lint": "eslint --cache --ignore-path .gitignore --max-warnings 0 .",
"lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .",
"prettier": "prettier --cache --write --list-different --ignore-path .gitignore --ignore-path .prettierignore .",
"release": "changeset publish",
"test": "turbo run test",
"types:check": "turbo run types:check",
"version": "changeset version"
},
"devDependencies": {
"changeset": "^0.2.6",
"concurrently": "^8.0.1",
"tsup": "6.7.0",
"turbo": "v1.9.9",
"typescript": "5.0.4"
}
"name": "root",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "turbo run build --filter=./packages/\\*",
"clean": "turbo run clean",
"dev": "turbo run dev",
"dev:ui": "turbo run dev --filter=website...",
"lint": "eslint --cache --ignore-path .gitignore --max-warnings 0 .",
"lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .",
"prettier": "prettier --cache --write --list-different --ignore-path .gitignore --ignore-path .prettierignore .",
"release": "changeset publish",
"test": "turbo run test",
"types:check": "turbo run types:check",
"version": "changeset version"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
"@ianvs/prettier-plugin-sort-imports": "^4.1.0",
"@next/eslint-plugin-next": "^13.4.12",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"concurrently": "^8.0.1",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"eslint-plugin-react": "^7.33.1",
"eslint-plugin-tailwindcss": "^3.13.0",
"prettier": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.4.1",
"tsup": "6.7.0",
"turbo": "v1.9.9",
"typescript": "5.0.4"
}
}
+11 -10
View File
@@ -2,23 +2,24 @@
The Next.js framework for building documentation website.
It's build on the top of Next.js, Radix UI and **Next Docs**, fully styled using Tailwind CSS.
It's build on the top of Next.js, Radix UI and **Next Docs**, fully styled using
Tailwind CSS.
## Features
Next Docs UI provides many features out of the box.
- Light/Dark Mode
- Full-text Search
- Table of Contents
- Built-in Components
- Code Syntax highlighting powered by Shiki
- Well Designed UI & Accessibility
- Light/Dark Mode
- Full-text Search
- Table of Contents
- Built-in Components
- Code Syntax highlighting powered by Shiki
- Well Designed UI & Accessibility
**Moreover:**
- Built for App Router
- Great Flexibility
- Light & Fast
- Built for App Router
- Great Flexibility
- Light & Fast
[Read Documentation](https://next-docs-zeta.vercel.app/docs/ui)
+87 -87
View File
@@ -1,94 +1,94 @@
:root {
--shiki-color-text: oklch(37.53% 0 0);
--shiki-color-background: transparent;
--shiki-token-constant: oklch(56.45% 0.163 253.27);
--shiki-token-string: oklch(54.64% 0.144 147.32);
--shiki-token-comment: oklch(73.8% 0 0);
--shiki-token-keyword: oklch(56.8% 0.2 26.41);
--shiki-token-parameter: oklch(77.03% 0.174 64.05);
--shiki-token-function: oklch(50.15% 0.188 294.99);
--shiki-token-string-expression: var(--shiki-token-string);
--shiki-token-punctuation: oklch(24.78% 0 0);
--shiki-token-link: var(--shiki-token-string);
--shiki-color-text: oklch(37.53% 0 0);
--shiki-color-background: transparent;
--shiki-token-constant: oklch(56.45% 0.163 253.27);
--shiki-token-string: oklch(54.64% 0.144 147.32);
--shiki-token-comment: oklch(73.8% 0 0);
--shiki-token-keyword: oklch(56.8% 0.2 26.41);
--shiki-token-parameter: oklch(77.03% 0.174 64.05);
--shiki-token-function: oklch(50.15% 0.188 294.99);
--shiki-token-string-expression: var(--shiki-token-string);
--shiki-token-punctuation: oklch(24.78% 0 0);
--shiki-token-link: var(--shiki-token-string);
/* from github-light */
--shiki-color-ansi-black: #24292e;
--shiki-color-ansi-black-dim: #24292e80;
--shiki-color-ansi-red: #d73a49;
--shiki-color-ansi-red-dim: #d73a4980;
--shiki-color-ansi-green: #28a745;
--shiki-color-ansi-green-dim: #28a74580;
--shiki-color-ansi-yellow: #dbab09;
--shiki-color-ansi-yellow-dim: #dbab0980;
--shiki-color-ansi-blue: #0366d6;
--shiki-color-ansi-blue-dim: #0366d680;
--shiki-color-ansi-magenta: #5a32a3;
--shiki-color-ansi-magenta-dim: #5a32a380;
--shiki-color-ansi-cyan: #1b7c83;
--shiki-color-ansi-cyan-dim: #1b7c8380;
--shiki-color-ansi-white: #6a737d;
--shiki-color-ansi-white-dim: #6a737d80;
--shiki-color-ansi-bright-black: #959da5;
--shiki-color-ansi-bright-black-dim: #959da580;
--shiki-color-ansi-bright-red: #cb2431;
--shiki-color-ansi-bright-red-dim: #cb243180;
--shiki-color-ansi-bright-green: #22863a;
--shiki-color-ansi-bright-green-dim: #22863a80;
--shiki-color-ansi-bright-yellow: #b08800;
--shiki-color-ansi-bright-yellow-dim: #b0880080;
--shiki-color-ansi-bright-blue: #005cc5;
--shiki-color-ansi-bright-blue-dim: #005cc580;
--shiki-color-ansi-bright-magenta: #5a32a3;
--shiki-color-ansi-bright-magenta-dim: #5a32a380;
--shiki-color-ansi-bright-cyan: #3192aa;
--shiki-color-ansi-bright-cyan-dim: #3192aa80;
--shiki-color-ansi-bright-white: #d1d5da;
--shiki-color-ansi-bright-white-dim: #d1d5da80;
/* from github-light */
--shiki-color-ansi-black: #24292e;
--shiki-color-ansi-black-dim: #24292e80;
--shiki-color-ansi-red: #d73a49;
--shiki-color-ansi-red-dim: #d73a4980;
--shiki-color-ansi-green: #28a745;
--shiki-color-ansi-green-dim: #28a74580;
--shiki-color-ansi-yellow: #dbab09;
--shiki-color-ansi-yellow-dim: #dbab0980;
--shiki-color-ansi-blue: #0366d6;
--shiki-color-ansi-blue-dim: #0366d680;
--shiki-color-ansi-magenta: #5a32a3;
--shiki-color-ansi-magenta-dim: #5a32a380;
--shiki-color-ansi-cyan: #1b7c83;
--shiki-color-ansi-cyan-dim: #1b7c8380;
--shiki-color-ansi-white: #6a737d;
--shiki-color-ansi-white-dim: #6a737d80;
--shiki-color-ansi-bright-black: #959da5;
--shiki-color-ansi-bright-black-dim: #959da580;
--shiki-color-ansi-bright-red: #cb2431;
--shiki-color-ansi-bright-red-dim: #cb243180;
--shiki-color-ansi-bright-green: #22863a;
--shiki-color-ansi-bright-green-dim: #22863a80;
--shiki-color-ansi-bright-yellow: #b08800;
--shiki-color-ansi-bright-yellow-dim: #b0880080;
--shiki-color-ansi-bright-blue: #005cc5;
--shiki-color-ansi-bright-blue-dim: #005cc580;
--shiki-color-ansi-bright-magenta: #5a32a3;
--shiki-color-ansi-bright-magenta-dim: #5a32a380;
--shiki-color-ansi-bright-cyan: #3192aa;
--shiki-color-ansi-bright-cyan-dim: #3192aa80;
--shiki-color-ansi-bright-white: #d1d5da;
--shiki-color-ansi-bright-white-dim: #d1d5da80;
}
.dark {
--shiki-color-text: oklch(86.07% 0 0);
--shiki-token-constant: oklch(76.85% 0.121 252.34);
--shiki-token-string: oklch(81.11% 0.124 0);
--shiki-token-comment: oklch(55.18% 0.017 251.27);
--shiki-token-keyword: oklch(72.14% 0.162 15.49);
/*--shiki-token-parameter: #ff9800; is same as in light mode */
--shiki-token-function: oklch(72.67% 0.137 299.15);
--shiki-token-string-expression: oklch(69.28% 0.179 143.2);
--shiki-token-punctuation: oklch(79.21% 0 0);
--shiki-token-link: var(--shiki-token-string);
--shiki-color-text: oklch(86.07% 0 0);
--shiki-token-constant: oklch(76.85% 0.121 252.34);
--shiki-token-string: oklch(81.11% 0.124 0);
--shiki-token-comment: oklch(55.18% 0.017 251.27);
--shiki-token-keyword: oklch(72.14% 0.162 15.49);
/*--shiki-token-parameter: #ff9800; is same as in light mode */
--shiki-token-function: oklch(72.67% 0.137 299.15);
--shiki-token-string-expression: oklch(69.28% 0.179 143.2);
--shiki-token-punctuation: oklch(79.21% 0 0);
--shiki-token-link: var(--shiki-token-string);
/* from github-dark */
--shiki-color-ansi-black: #586069;
--shiki-color-ansi-black-dim: #58606980;
--shiki-color-ansi-red: #ea4a5a;
--shiki-color-ansi-red-dim: #ea4a5a80;
--shiki-color-ansi-green: #34d058;
--shiki-color-ansi-green-dim: #34d05880;
--shiki-color-ansi-yellow: #ffea7f;
--shiki-color-ansi-yellow-dim: #ffea7f80;
--shiki-color-ansi-blue: #2188ff;
--shiki-color-ansi-blue-dim: #2188ff80;
--shiki-color-ansi-magenta: #b392f0;
--shiki-color-ansi-magenta-dim: #b392f080;
--shiki-color-ansi-cyan: #39c5cf;
--shiki-color-ansi-cyan-dim: #39c5cf80;
--shiki-color-ansi-white: #d1d5da;
--shiki-color-ansi-white-dim: #d1d5da80;
--shiki-color-ansi-bright-black: #959da5;
--shiki-color-ansi-bright-black-dim: #959da580;
--shiki-color-ansi-bright-red: #f97583;
--shiki-color-ansi-bright-red-dim: #f9758380;
--shiki-color-ansi-bright-green: #85e89d;
--shiki-color-ansi-bright-green-dim: #85e89d80;
--shiki-color-ansi-bright-yellow: #ffea7f;
--shiki-color-ansi-bright-yellow-dim: #ffea7f80;
--shiki-color-ansi-bright-blue: #79b8ff;
--shiki-color-ansi-bright-blue-dim: #79b8ff80;
--shiki-color-ansi-bright-magenta: #b392f0;
--shiki-color-ansi-bright-magenta-dim: #b392f080;
--shiki-color-ansi-bright-cyan: #56d4dd;
--shiki-color-ansi-bright-cyan-dim: #56d4dd80;
--shiki-color-ansi-bright-white: #fafbfc;
--shiki-color-ansi-bright-white-dim: #fafbfc80;
/* from github-dark */
--shiki-color-ansi-black: #586069;
--shiki-color-ansi-black-dim: #58606980;
--shiki-color-ansi-red: #ea4a5a;
--shiki-color-ansi-red-dim: #ea4a5a80;
--shiki-color-ansi-green: #34d058;
--shiki-color-ansi-green-dim: #34d05880;
--shiki-color-ansi-yellow: #ffea7f;
--shiki-color-ansi-yellow-dim: #ffea7f80;
--shiki-color-ansi-blue: #2188ff;
--shiki-color-ansi-blue-dim: #2188ff80;
--shiki-color-ansi-magenta: #b392f0;
--shiki-color-ansi-magenta-dim: #b392f080;
--shiki-color-ansi-cyan: #39c5cf;
--shiki-color-ansi-cyan-dim: #39c5cf80;
--shiki-color-ansi-white: #d1d5da;
--shiki-color-ansi-white-dim: #d1d5da80;
--shiki-color-ansi-bright-black: #959da5;
--shiki-color-ansi-bright-black-dim: #959da580;
--shiki-color-ansi-bright-red: #f97583;
--shiki-color-ansi-bright-red-dim: #f9758380;
--shiki-color-ansi-bright-green: #85e89d;
--shiki-color-ansi-bright-green-dim: #85e89d80;
--shiki-color-ansi-bright-yellow: #ffea7f;
--shiki-color-ansi-bright-yellow-dim: #ffea7f80;
--shiki-color-ansi-bright-blue: #79b8ff;
--shiki-color-ansi-bright-blue-dim: #79b8ff80;
--shiki-color-ansi-bright-magenta: #b392f0;
--shiki-color-ansi-bright-magenta-dim: #b392f080;
--shiki-color-ansi-bright-cyan: #56d4dd;
--shiki-color-ansi-bright-cyan-dim: #56d4dd80;
--shiki-color-ansi-bright-white: #fafbfc;
--shiki-color-ansi-bright-white-dim: #fafbfc80;
}
+48 -46
View File
@@ -1,90 +1,92 @@
@import "./codeblock.css";
@import './codeblock.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
@apply nd-border-border;
@apply nd-border-border;
}
body {
@apply nd-bg-background nd-text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
@apply nd-bg-background nd-text-foreground;
font-feature-settings:
'rlig' 1,
'calt' 1;
}
[data-rehype-pretty-code-fragment] code {
@apply nd-grid nd-py-4;
@apply nd-grid nd-py-4;
}
[data-rehype-pretty-code-fragment] .line {
@apply nd-pl-4 nd-pr-8;
@apply nd-pl-4 nd-pr-8;
}
[data-rehype-pretty-code-fragment] .line-highlighted {
@apply nd-bg-primary/10 nd-border-l-2 nd-border-l-primary;
@apply nd-bg-primary/10 nd-border-l-2 nd-border-l-primary;
}
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
--radius: 0.5rem;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--ring: 216 34% 17%;
}
+95 -96
View File
@@ -1,102 +1,101 @@
{
"name": "next-docs-ui",
"description": "The framework for building a documentation website in Next.js",
"version": "1.1.3",
"homepage": "https://next-docs-zeta.vercel.app",
"author": "Money SonMooSans",
"repository": "github:SonMooSans/next-docs",
"license": "MIT",
"files": [
"dist/*"
],
"exports": {
"./style.css": "./dist/style.css",
"./contentlayer": {
"import": "./dist/contentlayer/index.mjs",
"types": "./dist/contentlayer/index.d.ts"
},
"./components": {
"import": "./dist/components/index.mjs",
"types": "./dist/components/index.d.ts"
},
"./mdx": {
"import": "./dist/mdx/index.mjs",
"types": "./dist/mdx/index.d.ts"
},
"./*": {
"import": "./dist/*.mjs",
"types": "./dist/*.d.ts"
}
"name": "next-docs-ui",
"description": "The framework for building a documentation website in Next.js",
"version": "1.1.3",
"homepage": "https://next-docs-zeta.vercel.app",
"author": "Money SonMooSans",
"repository": "github:SonMooSans/next-docs",
"license": "MIT",
"files": [
"dist/*"
],
"exports": {
"./style.css": "./dist/style.css",
"./contentlayer": {
"import": "./dist/contentlayer/index.mjs",
"types": "./dist/contentlayer/index.d.ts"
},
"typesVersions": {
"*": {
"contentlayer": [
"./dist/contentlayer/index.d.ts"
],
"components": [
"./dist/components/index.d.ts"
],
"mdx": [
"./dist/mdx/index.d.ts"
],
"*": [
"./dist/*.d.ts"
]
}
"./components": {
"import": "./dist/components/index.mjs",
"types": "./dist/components/index.d.ts"
},
"scripts": {
"build": "tsup --clean && postcss css/styles.css -o ./dist/style.css",
"clean": "rmdir /q/s dist",
"dev": "concurrently \"pnpm dev:layout\" \"pnpm dev:tailwind\"",
"dev:layout": "tsup --watch",
"dev:tailwind": "postcss css/styles.css -o ./dist/style.css --watch",
"prepublishOnly": "pnpm build",
"types": "tsup --dts-only",
"types:check": "tsc --noEmit"
"./mdx": {
"import": "./dist/mdx/index.mjs",
"types": "./dist/mdx/index.d.ts"
},
"dependencies": {
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-select": "^1.2.1",
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"lucide-react": "^0.190.0",
"next-docs-zeta": "workspace:*",
"next-themes": "^0.2.1",
"rehype-img-size": "^1.0.1",
"rehype-pretty-code": "^0.9.5",
"rehype-slug": "^5.1.0",
"remark-gfm": "^3.0.1",
"scroll-into-view-if-needed": "^3.0.10",
"tailwind-merge": "^1.12.0"
},
"peerDependencies": {
"contentlayer": "0.3.x",
"next": "13.x",
"react": "18.x",
"react-dom": "18.x"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"autoprefixer": "10.4.14",
"contentlayer": "^0.3.4",
"next": "13.4.12",
"postcss": "8.4.23",
"postcss-cli": "^10.1.0",
"postcss-import": "^15.1.0",
"postcss-lightningcss": "^0.9.0",
"tailwindcss": "3.3.2",
"tailwindcss-animate": "^1.0.5"
},
"keywords": [
"NextJs",
"Docs"
],
"publishConfig": {
"access": "public"
"./*": {
"import": "./dist/*.mjs",
"types": "./dist/*.d.ts"
}
},
"typesVersions": {
"*": {
"contentlayer": [
"./dist/contentlayer/index.d.ts"
],
"components": [
"./dist/components/index.d.ts"
],
"mdx": [
"./dist/mdx/index.d.ts"
],
"*": [
"./dist/*.d.ts"
]
}
},
"scripts": {
"build": "tsup --clean && postcss css/styles.css -o ./dist/style.css",
"clean": "rmdir /q/s dist",
"dev": "concurrently \"pnpm dev:layout\" \"pnpm dev:tailwind\"",
"dev:layout": "tsup --watch",
"dev:tailwind": "postcss css/styles.css -o ./dist/style.css --watch",
"prepublishOnly": "pnpm build",
"types:check": "tsc --noEmit"
},
"dependencies": {
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-select": "^1.2.1",
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"lucide-react": "^0.190.0",
"next-docs-zeta": "workspace:*",
"next-themes": "^0.2.1",
"rehype-img-size": "^1.0.1",
"rehype-pretty-code": "^0.9.5",
"rehype-slug": "^5.1.0",
"remark-gfm": "^3.0.1",
"scroll-into-view-if-needed": "^3.0.10",
"tailwind-merge": "^1.12.0"
},
"peerDependencies": {
"contentlayer": "0.3.x",
"next": "13.x",
"react": "18.x",
"react-dom": "18.x"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"autoprefixer": "10.4.14",
"contentlayer": "^0.3.4",
"next": "13.4.12",
"postcss": "8.4.23",
"postcss-cli": "^10.1.0",
"postcss-import": "^15.1.0",
"postcss-lightningcss": "^0.9.0",
"tailwindcss": "3.3.2",
"tailwindcss-animate": "^1.0.5"
},
"keywords": [
"NextJs",
"Docs"
],
"publishConfig": {
"access": "public"
}
}
+8 -8
View File
@@ -1,9 +1,9 @@
module.exports = {
plugins: {
"postcss-import": {},
tailwindcss: {},
"postcss-lightningcss": {
browsers: ">= .25%",
},
},
};
plugins: {
'postcss-import': {},
tailwindcss: {},
'postcss-lightningcss': {
browsers: '>= .25%'
}
}
}
@@ -1,38 +1,38 @@
import type { TreeNode } from "next-docs-zeta/server";
import { useBreadcrumb } from "next-docs-zeta/breadcrumb";
import clsx from "clsx";
import { ChevronRightIcon } from "lucide-react";
import Link from "next/link";
import { Fragment } from "react";
import { usePathname } from "next/navigation";
import clsx from 'clsx'
import { ChevronRightIcon } from 'lucide-react'
import { useBreadcrumb } from 'next-docs-zeta/breadcrumb'
import type { TreeNode } from 'next-docs-zeta/server'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Fragment } from 'react'
const itemStyles =
"nd-overflow-hidden nd-overflow-ellipsis nd-whitespace-nowrap";
'nd-overflow-hidden nd-overflow-ellipsis nd-whitespace-nowrap'
export function Breadcrumb({ tree }: { tree: TreeNode[] }) {
const pathname = usePathname();
const items = useBreadcrumb(pathname, tree);
const pathname = usePathname()
const items = useBreadcrumb(pathname, tree)
return (
<div className="nd-flex nd-flex-row nd-gap-1 nd-text-sm nd-text-muted-foreground nd-items-center">
<p className={itemStyles}>Docs</p>
{items.map((item, i) => {
const active = items.length === i + 1;
const style = clsx(itemStyles, active && "nd-text-foreground");
return (
<div className="nd-flex nd-flex-row nd-gap-1 nd-text-sm nd-text-muted-foreground nd-items-center">
<p className={itemStyles}>Docs</p>
{items.map((item, i) => {
const active = items.length === i + 1
const style = clsx(itemStyles, active && 'nd-text-foreground')
return (
<Fragment key={i}>
<ChevronRightIcon className="nd-w-4 nd-h-4 nd-flex-shrink-0" />
{item.url != null ? (
<Link href={item.url} className={style}>
{item.name}
</Link>
) : (
<p className={style}>{item.name}</p>
)}
</Fragment>
);
})}
</div>
);
return (
<Fragment key={i}>
<ChevronRightIcon className="nd-w-4 nd-h-4 nd-shrink-0" />
{item.url != null ? (
<Link href={item.url} className={style}>
{item.name}
</Link>
) : (
<p className={style}>{item.name}</p>
)}
</Fragment>
)
})}
</div>
)
}
@@ -1,80 +1,79 @@
import { useDocsSearch } from "next-docs-zeta/search";
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import { useRouter } from "next/navigation";
import { useCallback, useContext } from "react";
import { BookOpenIcon } from "lucide-react";
import { I18nContext } from "@/contexts/i18n";
import type { DialogProps } from "@radix-ui/react-dialog";
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator
} from '@/components/ui/command'
import { I18nContext } from '@/contexts/i18n'
import type { DialogProps } from '@radix-ui/react-dialog'
import { BookOpenIcon } from 'lucide-react'
import { useDocsSearch } from 'next-docs-zeta/search'
import { useRouter } from 'next/navigation'
import { useCallback, useContext } from 'react'
export type SearchOptions = {
/**
* links to be displayed in Search Dialog
*/
links?: [name: string, link: string][];
};
/**
* links to be displayed in Search Dialog
*/
links?: [name: string, link: string][]
}
export default function SearchDialog({
links = [],
...props
links = [],
...props
}: DialogProps & SearchOptions) {
const router = useRouter();
const locale = useContext(I18nContext)?.locale;
const { search, setSearch, query } = useDocsSearch(locale);
const router = useRouter()
const locale = useContext(I18nContext)?.locale
const { search, setSearch, query } = useDocsSearch(locale)
const onOpen = useCallback(
(v: string) => {
router.push(v);
props.onOpenChange?.(false);
},
[router]
);
const onOpen = useCallback(
(v: string) => {
router.push(v)
props.onOpenChange?.(false)
},
[router]
)
return (
<CommandDialog {...props}>
<CommandInput
placeholder="Type a command or search..."
value={search}
onValueChange={setSearch}
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
return (
<CommandDialog {...props}>
<CommandInput
placeholder="Type a command or search..."
value={search}
onValueChange={setSearch}
/>
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{query.data != "empty" &&
query.data != null &&
query.data.length !== 0 && (
<CommandGroup heading="Documents">
{query.data.map((item) => (
<CommandItem
key={item.id[0]}
value={item.doc.url}
onSelect={onOpen}
>
{item.doc.title}
</CommandItem>
))}
</CommandGroup>
)}
<CommandSeparator />
{query.data === "empty" && links.length > 0 && (
<CommandGroup heading="Links">
{links.map(([name, url], i) => (
<CommandItem key={i} value={url} onSelect={onOpen}>
<BookOpenIcon className="nd-w-5 nd-h-5 nd-mr-2" />
{name}
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</CommandDialog>
);
{query.data != 'empty' &&
query.data != null &&
query.data.length !== 0 && (
<CommandGroup heading="Documents">
{query.data.map(item => (
<CommandItem
key={item.id[0]}
value={item.doc.url}
onSelect={onOpen}
>
{item.doc.title}
</CommandItem>
))}
</CommandGroup>
)}
<CommandSeparator />
{query.data === 'empty' && links.length > 0 && (
<CommandGroup heading="Links">
{links.map(([name, url], i) => (
<CommandItem key={i} value={url} onSelect={onOpen}>
<BookOpenIcon className="nd-w-5 nd-h-5 nd-mr-2" />
{name}
</CommandItem>
))}
</CommandGroup>
)}
</CommandList>
</CommandDialog>
)
}
@@ -1,5 +1,5 @@
"use client";
export * from "./theme-toggle";
export * from "./nav";
export * from "./search-toggle";
export * from "./roll-button";
'use client'
export * from './theme-toggle'
export * from './nav'
export * from './search-toggle'
export * from './roll-button'
@@ -1,30 +1,30 @@
import { SafeLink } from "next-docs-zeta/link";
import { ReactNode } from "react";
import { SafeLink } from 'next-docs-zeta/link'
import type { ReactNode } from 'react'
export function Cards({ children }: { children: ReactNode }) {
return (
<div className="nd-grid nd-grid-cols-1 md:nd-grid-cols-2 nd-gap-5 nd-not-prose">
{children}
</div>
);
return (
<div className="nd-grid nd-grid-cols-1 md:nd-grid-cols-2 nd-gap-5 nd-not-prose">
{children}
</div>
)
}
export function Card({
href,
title,
description,
href,
title,
description
}: {
href: string;
title: string;
description: string;
href: string
title: string
description: string
}) {
return (
<SafeLink
href={href}
className="nd-flex nd-flex-col nd-gap-2 nd-shadow-lg nd-rounded-xl nd-p-4 nd-border nd-bg-card nd-text-card-foreground nd-transition-colors hover:nd-border-primary hover:nd-shadow-primary/20"
>
<h3 className="nd-font-semibold">{title}</h3>
<p className="nd-text-muted-foreground nd-text-sm">{description}</p>
</SafeLink>
);
return (
<SafeLink
href={href}
className="nd-flex nd-flex-col nd-gap-2 nd-shadow-lg nd-rounded-xl nd-p-4 nd-border nd-bg-card nd-text-card-foreground nd-transition-colors hover:nd-border-primary hover:nd-shadow-primary/20"
>
<h3 className="nd-font-semibold">{title}</h3>
<p className="nd-text-muted-foreground nd-text-sm">{description}</p>
</SafeLink>
)
}
@@ -1,39 +1,35 @@
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import Link from "next/link";
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import Link from 'next/link'
export type FooterProps = {
previous?: { name: string; url: string };
next?: { name: string; url: string };
};
previous?: { name: string; url: string }
next?: { name: string; url: string }
}
const item =
"nd-flex nd-flex-row nd-gap-2 nd-items-end nd-text-sm nd-text-muted-foreground";
'nd-flex nd-flex-row nd-gap-2 nd-items-end nd-text-sm nd-text-muted-foreground'
export function Footer({ next, previous }: FooterProps) {
return (
<div className="nd-flex nd-flex-row nd-mt-8 nd-gap-4 nd-flex-wrap">
{previous && (
<Link href={previous.url} className={item}>
<ChevronLeftIcon className="nd-w-5 nd-h-5" />
<div>
<p className="nd-text-xs">Previous</p>
<p className="nd-font-medium nd-text-foreground">
{previous.name}
</p>
</div>
</Link>
)}
{next && (
<Link href={next.url} className={`${item} nd-ml-auto`}>
<div>
<p className="nd-text-xs">Next</p>
<p className="nd-font-medium nd-text-foreground">
{next.name}
</p>
</div>
<ChevronRightIcon className="nd-w-5 nd-h-5" />
</Link>
)}
</div>
);
return (
<div className="nd-flex nd-flex-row nd-mt-8 nd-gap-4 nd-flex-wrap">
{previous && (
<Link href={previous.url} className={item}>
<ChevronLeftIcon className="nd-w-5 nd-h-5" />
<div>
<p className="nd-text-xs">Previous</p>
<p className="nd-font-medium nd-text-foreground">{previous.name}</p>
</div>
</Link>
)}
{next && (
<Link href={next.url} className={`${item} nd-ml-auto`}>
<div>
<p className="nd-text-xs">Next</p>
<p className="nd-font-medium nd-text-foreground">{next.name}</p>
</div>
<ChevronRightIcon className="nd-w-5 nd-h-5" />
</Link>
)}
</div>
)
}
@@ -1,30 +1,30 @@
import clsx from "clsx";
import { LinkIcon } from "lucide-react";
import { ComponentPropsWithoutRef } from "react";
import clsx from 'clsx'
import { LinkIcon } from 'lucide-react'
import type { ComponentPropsWithoutRef } from 'react'
type Types = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
type HeadingProps<T extends Types> = Omit<ComponentPropsWithoutRef<T>, "as"> & {
as?: T;
};
type Types = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
type HeadingProps<T extends Types> = Omit<ComponentPropsWithoutRef<T>, 'as'> & {
as?: T
}
export function Heading<T extends Types = "h1">({
id,
as,
...props
export function Heading<T extends Types = 'h1'>({
id,
as,
...props
}: HeadingProps<T>) {
const As = as ?? "h1";
const As = as ?? 'h1'
return (
<As {...props} className={clsx("nd-group", props.className)}>
<span id={id} className="nd-absolute -nd-mt-20" />
{props.children}
<a
href={`#${id}`}
className="nd-opacity-0 group-hover:nd-opacity-100 nd-inline-block nd-ml-2 nd-text-muted-foreground"
aria-label="Link to section"
>
<LinkIcon className="nd-w-4 nd-h-4" />
</a>
</As>
);
return (
<As {...props} className={clsx('nd-group', props.className)}>
<span id={id} className="nd-absolute -nd-mt-20" />
{props.children}
<a
href={`#${id}`}
className="nd-opacity-0 group-hover:nd-opacity-100 nd-inline-block nd-ml-2 nd-text-muted-foreground"
aria-label="Link to section"
>
<LinkIcon className="nd-w-4 nd-h-4" />
</a>
</As>
)
}
@@ -1,67 +1,68 @@
import { CheckIcon, CopyIcon } from "lucide-react";
import { ComponentProps, useRef, useState, useEffect } from "react";
import { CheckIcon, CopyIcon } from 'lucide-react'
import type { ComponentProps } from 'react'
import { useEffect, useRef, useState } from 'react'
export function Pre({ title, ...props }: ComponentProps<"pre">) {
const ref = useRef<HTMLPreElement>(null);
const onCopy = () => {
if (ref.current == null || ref.current.textContent == null) return;
export function Pre({ title, ...props }: ComponentProps<'pre'>) {
const ref = useRef<HTMLPreElement>(null)
const onCopy = () => {
if (ref.current == null || ref.current.textContent == null) return
navigator.clipboard.writeText(ref.current.textContent);
};
navigator.clipboard.writeText(ref.current.textContent)
}
return (
<div
className="nd-relative nd-border nd-rounded-lg nd-not-prose"
data-rehype-pretty-code-fragment
>
{title && (
<div className="nd-text-sm nd-text-muted-foreground nd-bg-muted nd-pl-4 nd-pr-12 nd-py-2 nd-border-b">
{title}
</div>
)}
<CopyButton onCopy={onCopy} />
<pre
{...props}
className="nd-overflow-auto nd-bg-secondary/50 nd-text-sm"
ref={ref}
>
{props.children}
</pre>
return (
<div
className="nd-relative nd-border nd-rounded-lg nd-not-prose"
data-rehype-pretty-code-fragment
>
{title && (
<div className="nd-text-sm nd-text-muted-foreground nd-bg-muted nd-pl-4 nd-pr-12 nd-py-2 nd-border-b">
{title}
</div>
);
)}
<CopyButton onCopy={onCopy} />
<pre
{...props}
className="nd-overflow-auto nd-bg-secondary/50 nd-text-sm"
ref={ref}
>
{props.children}
</pre>
</div>
)
}
function CopyButton({ onCopy }: { onCopy: () => void }) {
const [checked, setChecked] = useState(false);
const [checked, setChecked] = useState(false)
const onClick = () => {
onCopy();
setChecked(true);
};
const onClick = () => {
onCopy()
setChecked(true)
}
useEffect(() => {
if (!checked) return;
useEffect(() => {
if (!checked) return
const timer = setTimeout(() => {
setChecked(false);
}, 1500);
const timer = setTimeout(() => {
setChecked(false)
}, 1500)
return () => {
clearTimeout(timer);
};
}, [checked]);
return () => {
clearTimeout(timer)
}
}, [checked])
return (
<button
className="nd-absolute nd-top-1 nd-right-1 nd-p-2 nd-border nd-bg-secondary nd-text-secondary-foreground nd-transition-colors nd-rounded-md hover:nd-bg-accent"
aria-label="Copy Text"
onClick={onClick}
>
{checked ? (
<CheckIcon className="nd-w-3 nd-h-3" />
) : (
<CopyIcon className="nd-w-3 nd-h-3" />
)}
</button>
);
return (
<button
className="nd-absolute nd-top-1 nd-right-1 nd-p-2 nd-border nd-bg-secondary nd-text-secondary-foreground nd-transition-colors nd-rounded-md hover:nd-bg-accent"
aria-label="Copy Text"
onClick={onClick}
>
{checked ? (
<CheckIcon className="nd-w-3 nd-h-3" />
) : (
<CopyIcon className="nd-w-3 nd-h-3" />
)}
</button>
)
}
+46 -48
View File
@@ -1,58 +1,56 @@
import { ReactNode } from "react";
import { SearchBar } from "./search-toggle";
import { SidebarTrigger } from "./sidebar";
import { MenuIcon } from "lucide-react";
import { ThemeToggle } from "./theme-toggle";
import Link from "next/link";
import { MenuIcon } from 'lucide-react'
import Link from 'next/link'
import type { ReactNode } from 'react'
import { SearchBar } from './search-toggle'
import { SidebarTrigger } from './sidebar'
import { ThemeToggle } from './theme-toggle'
type NavLinkProps = {
icon: ReactNode;
href: string;
external?: boolean;
};
icon: ReactNode
href: string
external?: boolean
}
export function Nav({
links,
enableSidebar = true,
children,
links,
enableSidebar = true,
children
}: {
links?: NavLinkProps[];
enableSidebar?: boolean;
children: ReactNode;
links?: NavLinkProps[]
enableSidebar?: boolean
children: ReactNode
}) {
return (
<nav className="nd-sticky nd-top-0 nd-inset-x-0 nd-bg-background/10 nd-z-50 nd-backdrop-blur-xl">
<div className="nd-container nd-flex nd-flex-row nd-items-center nd-h-14 nd-gap-4 nd-max-w-[1400px]">
{children}
<div className="nd-flex nd-flex-row nd-items-center nd-ml-auto">
<SearchBar className="nd-w-[280px] nd-max-w-xs max-sm:nd-hidden nd-mr-3" />
{links?.map((item, key) => (
<NavLink key={key} {...item} />
))}
<ThemeToggle />
{enableSidebar && (
<SidebarTrigger
aria-label="Toggle Sidebar"
className="nd-p-2 nd-rounded-md hover:nd-bg-accent md:nd-hidden"
>
<MenuIcon className="nd-w-5 nd-h-5" />
</SidebarTrigger>
)}
</div>
</div>
</nav>
);
return (
<nav className="nd-sticky nd-top-0 nd-inset-x-0 nd-bg-background/10 nd-z-50 nd-backdrop-blur-xl">
<div className="nd-container nd-flex nd-flex-row nd-items-center nd-h-14 nd-gap-4 nd-max-w-[1400px]">
{children}
<div className="nd-flex nd-flex-row nd-items-center nd-ml-auto">
<SearchBar className="nd-w-[280px] nd-max-w-xs max-sm:nd-hidden nd-mr-3" />
{links?.map((item, key) => <NavLink key={key} {...item} />)}
<ThemeToggle />
{enableSidebar && (
<SidebarTrigger
aria-label="Toggle Sidebar"
className="nd-p-2 nd-rounded-md hover:nd-bg-accent md:nd-hidden"
>
<MenuIcon className="nd-w-5 nd-h-5" />
</SidebarTrigger>
)}
</div>
</div>
</nav>
)
}
export function NavLink(props: NavLinkProps) {
return (
<Link
href={props.href}
target={props.external ? "_blank" : "_self"}
rel={props.external ? "noreferrer noopener" : undefined}
className="nd-p-2 nd-rounded-md hover:nd-bg-accent max-sm:nd-hidden"
>
{props.icon}
</Link>
);
return (
<Link
href={props.href}
target={props.external ? '_blank' : '_self'}
rel={props.external ? 'noreferrer noopener' : undefined}
className="nd-p-2 nd-rounded-md hover:nd-bg-accent max-sm:nd-hidden"
>
{props.icon}
</Link>
)
}
@@ -1,50 +1,49 @@
import clsx from "clsx";
import { ChevronUpIcon } from "lucide-react";
import { useEffect, useState } from "react";
import clsx from 'clsx'
import { ChevronUpIcon } from 'lucide-react'
import { useEffect, useState } from 'react'
type RollButtonProps = {
/**
* Percentage of scroll position to display the roll button
*
* @default 0.2
*/
percentage?: number;
};
/**
* Percentage of scroll position to display the roll button
*
* @default 0.2
*/
percentage?: number
}
/**
* A button that scrolls to the top
*/
export function RollButton({ percentage = 0.2 }: RollButtonProps) {
const [show, setShow] = useState(false);
const [show, setShow] = useState(false)
useEffect(() => {
window.addEventListener("scroll", () => {
const element = document.scrollingElement;
if (!element) return;
const nearTop =
element.scrollTop /
(element.scrollHeight - element.clientHeight) <
percentage;
useEffect(() => {
window.addEventListener('scroll', () => {
const element = document.scrollingElement
if (!element) return
const nearTop =
element.scrollTop / (element.scrollHeight - element.clientHeight) <
percentage
setShow(!nearTop);
});
}, []);
setShow(!nearTop)
})
}, [])
return (
<button
aria-label="Scroll to Top"
className={clsx(
!show && "nd-translate-y-20 nd-opacity-0",
"nd-rounded-full nd-p-4 nd-transition-all nd-text-foreground nd-bg-background nd-border nd-fixed nd-bottom-12 nd-right-12 nd-z-50 2xl:nd-right-[20vw]"
)}
onClick={() => {
document.scrollingElement!.scrollTo({
top: 0,
behavior: "smooth",
});
}}
>
<ChevronUpIcon className="nd-w-5 nd-h-5" />
</button>
);
return (
<button
aria-label="Scroll to Top"
className={clsx(
!show && 'nd-translate-y-20 nd-opacity-0',
'nd-rounded-full nd-p-4 nd-transition-all nd-text-foreground nd-bg-background nd-border nd-fixed nd-bottom-12 nd-right-12 nd-z-50 2xl:nd-right-[20vw]'
)}
onClick={() => {
document.scrollingElement!.scrollTo({
top: 0,
behavior: 'smooth'
})
}}
>
<ChevronUpIcon className="nd-w-5 nd-h-5" />
</button>
)
}
@@ -1,25 +1,26 @@
import { ComponentPropsWithoutRef, useContext } from "react";
import { cn } from "@/utils/cn";
import { SearchIcon } from "lucide-react";
import { SearchContext } from "@/contexts/search";
import { SearchContext } from '@/contexts/search'
import { cn } from '@/utils/cn'
import { SearchIcon } from 'lucide-react'
import type { ComponentPropsWithoutRef } from 'react'
import { useContext } from 'react'
export function SearchBar(props: ComponentPropsWithoutRef<"button">) {
const { setOpenSearch } = useContext(SearchContext);
export function SearchBar(props: ComponentPropsWithoutRef<'button'>) {
const { setOpenSearch } = useContext(SearchContext)
return (
<button
{...props}
className={cn(
"nd-flex nd-flex-row nd-items-center nd-border nd-border-input nd-rounded-md nd-text-muted-foreground nd-bg-background/50 nd-px-3 nd-py-2 nd-text-sm",
props.className
)}
onClick={() => setOpenSearch(true)}
>
<SearchIcon className="nd-w-4 nd-h-4 nd-mr-2" />
Search...
<span className="nd-ml-auto nd-text-xs nd-px-2 nd-py-0.5 nd-border nd-rounded-md nd-bg-secondary nd-text-secondary-foreground">
Ctrl K
</span>
</button>
);
return (
<button
{...props}
className={cn(
'nd-flex nd-flex-row nd-items-center nd-border nd-border-input nd-rounded-md nd-text-muted-foreground nd-bg-background/50 nd-px-3 nd-py-2 nd-text-sm',
props.className
)}
onClick={() => setOpenSearch(true)}
>
<SearchIcon className="nd-w-4 nd-h-4 nd-mr-2" />
Search...
<span className="nd-ml-auto nd-text-xs nd-px-2 nd-py-0.5 nd-border nd-rounded-md nd-bg-secondary nd-text-secondary-foreground">
Ctrl K
</span>
</button>
)
}
+129 -132
View File
@@ -1,154 +1,151 @@
import clsx from "clsx";
import { ChevronDown } from "lucide-react";
import { ReactNode, useEffect, useState } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import type { TreeNode, FileNode, FolderNode } from "next-docs-zeta/server";
import * as Base from "next-docs-zeta/sidebar";
import * as Collapsible from "@radix-ui/react-collapsible";
import { SearchBar } from "./search-toggle";
import { ScrollArea } from "@/components/ui/scroll-area";
import { ScrollArea } from '@/components/ui/scroll-area'
import * as Collapsible from '@radix-ui/react-collapsible'
import clsx from 'clsx'
import { ChevronDown } from 'lucide-react'
import type { FileNode, FolderNode, TreeNode } from 'next-docs-zeta/server'
import * as Base from 'next-docs-zeta/sidebar'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import type { ReactNode } from 'react'
import { useEffect, useState } from 'react'
import { SearchBar } from './search-toggle'
export const SidebarProvider = Base.SidebarProvider;
export const SidebarTrigger = Base.SidebarTrigger;
export const { SidebarProvider } = Base
export const { SidebarTrigger } = Base
export type SidebarProps = { items: TreeNode[]; children?: ReactNode };
export type SidebarProps = { items: TreeNode[]; children?: ReactNode }
export function Sidebar({ items, children }: SidebarProps) {
return (
<Base.SidebarList
as="div"
minWidth={768} // md
className="nd-relative max-md:data-[open=false]:nd-hidden"
>
<aside
className={clsx(
"nd-flex nd-flex-col",
"md:nd-sticky md:nd-top-14 md:nd-h-[calc(100vh-3.5rem)]",
"max-md:nd-fixed max-md:nd-inset-0 max-md:nd-px-8 max-md:nd-bg-background/50 max-md:nd-backdrop-blur-xl max-md:nd-z-40"
)}
>
<ScrollArea className="nd-flex-1 [mask-image:linear-gradient(to_top,transparent,white_80px)]">
<div className="nd-flex nd-flex-col nd-gap-2 nd-pr-2 md:nd-py-16 max-md:nd-py-20">
<SearchBar className="nd-mb-4 sm:nd-hidden" />
{items.map((item, i) => (
<Node key={i} item={item} />
))}
</div>
</ScrollArea>
{children && (
<div className="nd-border-t nd-pt-4 nd-pb-6">
{children}
</div>
)}
</aside>
</Base.SidebarList>
);
return (
<Base.SidebarList
as="div"
minWidth={768} // md
className="nd-relative max-md:data-[open=false]:nd-hidden"
>
<aside
className={clsx(
'nd-flex nd-flex-col',
'md:nd-sticky md:nd-top-14 md:nd-h-[calc(100vh-3.5rem)]',
'max-md:nd-fixed max-md:nd-inset-0 max-md:nd-px-8 max-md:nd-bg-background/50 max-md:nd-backdrop-blur-xl max-md:nd-z-40'
)}
>
<ScrollArea className="nd-flex-1 [mask-image:linear-gradient(to_top,transparent,white_80px)]">
<div className="nd-flex nd-flex-col nd-gap-2 nd-pr-2 md:nd-py-16 max-md:nd-py-20">
<SearchBar className="nd-mb-4 sm:nd-hidden" />
{items.map((item, i) => (
<Node key={i} item={item} />
))}
</div>
</ScrollArea>
{children && (
<div className="nd-border-t nd-pt-4 nd-pb-6">{children}</div>
)}
</aside>
</Base.SidebarList>
)
}
function Node({ item }: { item: TreeNode }) {
if (item.type === "separator")
return (
<p className="nd-font-semibold nd-text-sm nd-mt-3 nd-mb-2 first-of-type:nd-mt-0">
{item.name}
</p>
);
if (item.type === "folder") return <Folder item={item} />;
if (item.type === 'separator')
return (
<p className="nd-font-semibold nd-text-sm nd-mt-3 nd-mb-2 first-of-type:nd-mt-0">
{item.name}
</p>
)
if (item.type === 'folder') return <Folder item={item} />
return <Item item={item} />;
return <Item item={item} />
}
function Item({ item }: { item: FileNode }) {
const { url, name } = item;
const pathname = usePathname();
const active = pathname === url;
const { url, name } = item
const pathname = usePathname()
const active = pathname === url
return (
<Link
href={url}
className={clsx(
"nd-text-sm nd-w-full",
active
? "nd-text-primary nd-font-semibold"
: "nd-text-muted-foreground hover:nd-text-foreground"
)}
>
{name}
</Link>
);
return (
<Link
href={url}
className={clsx(
'nd-text-sm nd-w-full',
active
? 'nd-text-primary nd-font-semibold'
: 'nd-text-muted-foreground hover:nd-text-foreground'
)}
>
{name}
</Link>
)
}
function Folder({ item }: { item: FolderNode }) {
const { name, children, index } = item;
const { name, children, index } = item
const pathname = usePathname();
const active = index && pathname === index.url;
const childActive = pathname.startsWith(item.url + "/");
const [extend, setExtend] = useState(active || childActive);
const pathname = usePathname()
const active = index && pathname === index.url
const childActive = pathname.startsWith(item.url + '/')
const [extend, setExtend] = useState(active || childActive)
useEffect(() => {
if (active || childActive) {
setExtend(true);
}
}, [active, childActive]);
useEffect(() => {
if (active || childActive) {
setExtend(true)
}
}, [active, childActive])
const onClick = () => {
if (item.index == null || active) {
setExtend((prev) => !prev);
}
};
const onClick = () => {
if (item.index == null || active) {
setExtend(prev => !prev)
}
}
const As = index == null ? "p" : Link;
return (
<Collapsible.Root
className="nd-w-full"
open={extend}
onOpenChange={setExtend}
>
<Collapsible.Trigger
return (
<Collapsible.Root
className="nd-w-full"
open={extend}
onOpenChange={setExtend}
>
<Collapsible.Trigger
className={clsx(
'nd-flex nd-flex-row nd-text-sm nd-w-full nd-rounded-xl nd-text-start',
active
? 'nd-font-semibold nd-text-primary'
: 'nd-text-muted-foreground hover:nd-text-foreground'
)}
>
{index ? (
<Link href={index.url} className="nd-flex-1" onClick={onClick}>
{name}
</Link>
) : (
<p className="nd-flex-1" onClick={onClick}>
{name}
</p>
)}
<ChevronDown
className={clsx(
'nd-w-4 nd-h-4 nd-transition-transform',
extend ? 'nd-rotate-0' : '-nd-rotate-90'
)}
/>
</Collapsible.Trigger>
<Collapsible.Content asChild>
<ul className="nd-overflow-hidden data-[state=closed]:nd-animate-collapsible-up data-[state=open]:nd-animate-collapsible-down">
{children.map((item, i) => {
const active = item.type !== 'separator' && pathname === item.url
return (
<li
key={i}
className={clsx(
"nd-flex nd-flex-row nd-text-sm nd-w-full nd-rounded-xl nd-text-start",
active
? "nd-font-semibold nd-text-primary"
: "nd-text-muted-foreground hover:nd-text-foreground"
'nd-flex nd-ml-2 nd-pl-4 nd-py-1.5 nd-border-l first:nd-mt-2',
active ? 'nd-border-primary' : 'nd-border-border'
)}
>
<As
href={index?.url as any}
className="nd-flex-1"
onClick={onClick}
>
{name}
</As>
<ChevronDown
className={clsx(
"nd-w-4 nd-h-4 nd-transition-transform",
extend ? "nd-rotate-0" : "-nd-rotate-90"
)}
/>
</Collapsible.Trigger>
<Collapsible.Content asChild>
<ul className="nd-overflow-hidden data-[state=closed]:nd-animate-collapsible-up data-[state=open]:nd-animate-collapsible-down">
{children.map((item, i) => {
const active =
item.type !== "separator" && pathname === item.url;
return (
<li
key={i}
className={clsx(
"nd-flex nd-ml-2 nd-pl-4 nd-py-1.5 nd-border-l first:nd-mt-2",
active
? "nd-border-primary"
: "nd-border-border"
)}
>
<Node item={item} />
</li>
);
})}
</ul>
</Collapsible.Content>
</Collapsible.Root>
);
>
<Node item={item} />
</li>
)
})}
</ul>
</Collapsible.Content>
</Collapsible.Root>
)
}
@@ -1,36 +1,35 @@
import { MoonIcon, SunIcon } from "lucide-react";
import { useTheme } from "next-themes";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { MoonIcon, SunIcon } from 'lucide-react'
import { useTheme } from 'next-themes'
export function ThemeToggle() {
const { setTheme } = useTheme();
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="nd-w-9 nd-h-9 nd-inline-flex nd-justify-center nd-items-center nd-rounded-md hover:nd-bg-accent hover:nd-text-accent-foreground focus-visible:nd-outline-none">
<SunIcon className="nd-h-5 nd-w-5 nd-rotate-0 nd-scale-100 nd-transition-all dark:-nd-rotate-90 dark:nd-scale-0" />
<MoonIcon className="nd-absolute nd-h-5 nd-w-5 nd-rotate-90 nd-scale-0 nd-transition-all dark:nd-rotate-0 dark:nd-scale-100" />
<span className="nd-sr-only">Toggle theme</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="nd-w-9 nd-h-9 nd-inline-flex nd-justify-center nd-items-center nd-rounded-md hover:nd-bg-accent hover:nd-text-accent-foreground focus-visible:nd-outline-none">
<SunIcon className="nd-h-5 nd-w-5 nd-rotate-0 nd-scale-100 nd-transition-all dark:-nd-rotate-90 dark:nd-scale-0" />
<MoonIcon className="nd-absolute nd-h-5 nd-w-5 nd-rotate-90 nd-scale-0 nd-transition-all dark:nd-rotate-0 dark:nd-scale-100" />
<span className="nd-sr-only">Toggle theme</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
+49 -51
View File
@@ -1,60 +1,58 @@
import * as Primitive from "next-docs-zeta/toc";
import type { TOCItemType } from "next-docs-zeta/server";
import { useEffect, useRef } from "react";
import scrollIntoView from "scroll-into-view-if-needed";
import type { TOCItemType } from 'next-docs-zeta/server'
import * as Primitive from 'next-docs-zeta/toc'
import { useEffect, useRef } from 'react'
import scrollIntoView from 'scroll-into-view-if-needed'
export function TOC({ items }: { items: TOCItemType[] }) {
return (
<div
id="nd-toc"
className="nd-flex nd-flex-col nd-gap-1 nd-overflow-hidden nd-flex-1"
>
<Primitive.TOCProvider toc={items}>
{items.length > 0 && (
<h3 className="nd-font-semibold nd-mb-2">On this page</h3>
)}
{items.map((item, i) => (
<TOCItem key={i} item={item} />
))}
</Primitive.TOCProvider>
</div>
);
return (
<div
id="nd-toc"
className="nd-flex nd-flex-col nd-gap-1 nd-overflow-hidden nd-flex-1"
>
<Primitive.TOCProvider toc={items}>
{items.length > 0 && (
<h3 className="nd-font-semibold nd-mb-2">On this page</h3>
)}
{items.map((item, i) => (
<TOCItem key={i} item={item} />
))}
</Primitive.TOCProvider>
</div>
)
}
function TOCItem({ item }: { item: TOCItemType }) {
const ref = useRef<HTMLAnchorElement>(null);
const anchor = Primitive.useActiveAnchor(item.url);
const active = anchor?.isActive ?? false;
const ref = useRef<HTMLAnchorElement>(null)
const anchor = Primitive.useActiveAnchor(item.url)
const active = anchor?.isActive ?? false
useEffect(() => {
if (active && ref.current) {
const toc = document.getElementById("nd-toc");
useEffect(() => {
if (active && ref.current) {
const toc = document.getElementById('nd-toc')
scrollIntoView(ref.current, {
behavior: "smooth",
block: "center",
inline: "center",
scrollMode: "always",
boundary: toc,
});
}
}, [active]);
scrollIntoView(ref.current, {
behavior: 'smooth',
block: 'center',
inline: 'center',
scrollMode: 'always',
boundary: toc
})
}
}, [active])
return (
<div className="nd-flex nd-flex-col nd-gap-1">
<Primitive.TOCItem
ref={ref}
href={item.url}
item={item}
className="nd-text-sm nd-text-muted-foreground nd-transition-colors nd-text-ellipsis nd-overflow-hidden data-[active=true]:nd-font-medium data-[active=true]:nd-text-primary"
>
{item.title}
</Primitive.TOCItem>
<div className="nd-flex nd-flex-col nd-pl-4">
{item.items?.map((item, i) => (
<TOCItem key={i} item={item} />
))}
</div>
</div>
);
return (
<div className="nd-flex nd-flex-col nd-gap-1">
<Primitive.TOCItem
ref={ref}
href={item.url}
item={item}
className="nd-text-sm nd-text-muted-foreground nd-transition-colors nd-text-ellipsis nd-overflow-hidden data-[active=true]:nd-font-medium data-[active=true]:nd-text-primary"
>
{item.title}
</Primitive.TOCItem>
<div className="nd-flex nd-flex-col nd-pl-4">
{item.items?.map((item, i) => <TOCItem key={i} item={item} />)}
</div>
</div>
)
}
@@ -1,147 +1,146 @@
"use client";
import * as React from "react";
'use client'
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import type { DialogProps } from "@radix-ui/react-dialog";
import { cn } from "@/utils/cn";
import { Dialog, DialogContent } from "./dialog";
import { cn } from '@/utils/cn'
import type { DialogProps } from '@radix-ui/react-dialog'
import { Command as CommandPrimitive } from 'cmdk'
import { Search } from 'lucide-react'
import * as React from 'react'
import { Dialog, DialogContent } from './dialog'
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"nd-flex nd-h-full nd-w-full nd-flex-col nd-overflow-hidden nd-rounded-md nd-bg-popover nd-text-popover-foreground",
className
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
<CommandPrimitive
ref={ref}
className={cn(
'nd-flex nd-h-full nd-w-full nd-flex-col nd-overflow-hidden nd-rounded-md nd-bg-popover nd-text-popover-foreground',
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="nd-overflow-hidden nd-p-0 nd-shadow-2xl">
<Command
className="[&_[cmdk-group-heading]]:nd-px-2 [&_[cmdk-group-heading]]:nd-font-medium [&_[cmdk-group-heading]]:nd-text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:nd-pt-0 [&_[cmdk-group]]:nd-px-2 [&_[cmdk-input-wrapper]_svg]:nd-h-5 [&_[cmdk-input-wrapper]_svg]:nd-w-5 [&_[cmdk-input]]:nd-h-12 [&_[cmdk-item]]:nd-px-2 [&_[cmdk-item]]:nd-py-3 [&_[cmdk-item]_svg]:nd-h-5 [&_[cmdk-item]_svg]:nd-w-5"
shouldFilter={false}
>
{children}
</Command>
</DialogContent>
</Dialog>
);
};
return (
<Dialog {...props}>
<DialogContent className="nd-overflow-hidden nd-p-0 nd-shadow-2xl">
<Command
className="[&_[cmdk-group-heading]]:nd-px-2 [&_[cmdk-group-heading]]:nd-font-medium [&_[cmdk-group-heading]]:nd-text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:nd-pt-0 [&_[cmdk-group]]:nd-px-2 [&_[cmdk-input-wrapper]_svg]:nd-h-5 [&_[cmdk-input-wrapper]_svg]:nd-w-5 [&_[cmdk-input]]:nd-h-12 [&_[cmdk-item]]:nd-px-2 [&_[cmdk-item]]:nd-py-3 [&_[cmdk-item]_svg]:nd-h-5 [&_[cmdk-item]_svg]:nd-w-5"
shouldFilter={false}
>
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div
className="nd-flex nd-items-center nd-border-b nd-px-3"
cmdk-input-wrapper=""
>
<Search className="nd-mr-2 nd-h-4 nd-w-4 nd-shrink-0 nd-opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"placeholder:nd-text-foreground-muted nd-flex nd-h-11 nd-w-full nd-rounded-md nd-bg-transparent nd-py-3 nd-text-sm nd-outline-none disabled:nd-cursor-not-allowed disabled:nd-opacity-50",
className
)}
{...props}
/>
</div>
));
<div
className="nd-flex nd-items-center nd-border-b nd-px-3"
cmdk-input-wrapper=""
>
<Search className="nd-mr-2 nd-h-4 nd-w-4 nd-shrink-0 nd-opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
'placeholder:nd-text-muted-foreground nd-flex nd-h-11 nd-w-full nd-rounded-md nd-bg-transparent nd-py-3 nd-text-sm nd-outline-none disabled:nd-cursor-not-allowed disabled:nd-opacity-50',
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName;
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn(
"nd-max-h-[300px] nd-overflow-y-auto nd-overflow-x-hidden",
className
)}
{...props}
/>
));
<CommandPrimitive.List
ref={ref}
className={cn(
'nd-max-h-[300px] nd-overflow-y-auto nd-overflow-x-hidden',
className
)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName;
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="nd-py-6 nd-text-center nd-text-sm"
{...props}
/>
));
<CommandPrimitive.Empty
ref={ref}
className="nd-py-6 nd-text-center nd-text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"nd-overflow-hidden nd-p-1 nd-text-foreground [&_[cmdk-group-heading]]:nd-px-2 [&_[cmdk-group-heading]]:nd-py-1.5 [&_[cmdk-group-heading]]:nd-text-xs [&_[cmdk-group-heading]]:nd-font-medium [&_[cmdk-group-heading]]:nd-text-muted-foreground",
className
)}
{...props}
/>
));
<CommandPrimitive.Group
ref={ref}
className={cn(
'nd-overflow-hidden nd-p-1 nd-text-foreground [&_[cmdk-group-heading]]:nd-px-2 [&_[cmdk-group-heading]]:nd-py-1.5 [&_[cmdk-group-heading]]:nd-text-xs [&_[cmdk-group-heading]]:nd-font-medium [&_[cmdk-group-heading]]:nd-text-muted-foreground',
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName;
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-nd-mx-1 nd-h-px nd-bg-border", className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
<CommandPrimitive.Separator
ref={ref}
className={cn('-nd-mx-1 nd-h-px nd-bg-border', className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"nd-relative nd-flex nd-cursor-pointer nd-select-none nd-items-center nd-rounded-sm nd-px-2 nd-py-1.5 nd-text-sm nd-outline-none aria-selected:nd-bg-accent aria-selected:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50",
className
)}
{...props}
/>
));
<CommandPrimitive.Item
ref={ref}
className={cn(
'nd-relative nd-flex nd-cursor-pointer nd-select-none nd-items-center nd-rounded-sm nd-px-2 nd-py-1.5 nd-text-sm nd-outline-none aria-selected:nd-bg-accent aria-selected:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50',
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName;
CommandItem.displayName = CommandPrimitive.Item.displayName
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandSeparator,
};
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandSeparator
}
@@ -1,123 +1,122 @@
"use client";
'use client'
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from '@/utils/cn'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { X } from 'lucide-react'
import * as React from 'react'
import { cn } from "@/utils/cn";
const Dialog = DialogPrimitive.Root
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = ({
className,
...props
className,
...props
}: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props} />
);
DialogPortal.displayName = DialogPrimitive.Portal.displayName;
<DialogPrimitive.Portal className={cn(className)} {...props} />
)
DialogPortal.displayName = DialogPrimitive.Portal.displayName
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"nd-fixed nd-inset-0 nd-z-50 nd-bg-background/80 nd-backdrop-blur-sm data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'nd-fixed nd-inset-0 nd-z-50 nd-bg-background/80 nd-backdrop-blur-sm data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0',
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"nd-fixed nd-left-[50%] nd-top-[50%] nd-z-50 nd-grid nd-w-full nd-max-w-lg nd-translate-x-[-50%] nd-translate-y-[-50%] nd-gap-4 nd-border nd-bg-background nd-p-6 nd-shadow-lg nd-duration-200 data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[state=closed]:nd-slide-out-to-left-1/2 data-[state=closed]:nd-slide-out-to-top-[48%] data-[state=open]:nd-slide-in-from-left-1/2 data-[state=open]:nd-slide-in-from-top-[48%] sm:nd-rounded-lg md:nd-w-full",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="nd-absolute nd-right-4 nd-top-4 nd-rounded-sm nd-opacity-70 nd-ring-offset-background nd-transition-opacity hover:nd-opacity-100 focus:nd-outline-none focus:nd-ring-2 focus:nd-ring-ring focus:nd-ring-offset-2 disabled:nd-pointer-events-none data-[state=open]:nd-bg-accent data-[state=open]:nd-text-muted-foreground">
<X className="nd-h-4 nd-w-4" />
<span className="nd-sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'nd-fixed nd-left-[50%] nd-top-[50%] nd-z-50 nd-grid nd-w-full nd-max-w-lg nd-translate-x-[-50%] nd-translate-y-[-50%] nd-gap-4 nd-border nd-bg-background nd-p-6 nd-shadow-lg nd-duration-200 data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[state=closed]:nd-slide-out-to-left-1/2 data-[state=closed]:nd-slide-out-to-top-[48%] data-[state=open]:nd-slide-in-from-left-1/2 data-[state=open]:nd-slide-in-from-top-[48%] sm:nd-rounded-lg md:nd-w-full',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="nd-absolute nd-right-4 nd-top-4 nd-rounded-sm nd-opacity-70 nd-ring-offset-background nd-transition-opacity hover:nd-opacity-100 focus:nd-outline-none focus:nd-ring-2 focus:nd-ring-ring focus:nd-ring-offset-2 disabled:nd-pointer-events-none data-[state=open]:nd-bg-accent data-[state=open]:nd-text-muted-foreground">
<X className="nd-h-4 nd-w-4" />
<span className="nd-sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"nd-flex nd-flex-col nd-space-y-1.5 nd-text-center sm:nd-text-left",
className
)}
{...props}
/>
);
DialogHeader.displayName = "DialogHeader";
<div
className={cn(
'nd-flex nd-flex-col nd-space-y-1.5 nd-text-center sm:nd-text-left',
className
)}
{...props}
/>
)
DialogHeader.displayName = 'DialogHeader'
const DialogFooter = ({
className,
...props
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"nd-flex nd-flex-col-reverse sm:nd-flex-row sm:nd-justify-end sm:nd-space-x-2",
className
)}
{...props}
/>
);
DialogFooter.displayName = "DialogFooter";
<div
className={cn(
'nd-flex nd-flex-col-reverse sm:nd-flex-row sm:nd-justify-end sm:nd-space-x-2',
className
)}
{...props}
/>
)
DialogFooter.displayName = 'DialogFooter'
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"nd-text-lg nd-font-semibold nd-leading-none nd-tracking-tight",
className
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
<DialogPrimitive.Title
ref={ref}
className={cn(
'nd-text-lg nd-font-semibold nd-leading-none nd-tracking-tight',
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("nd-text-sm nd-text-muted-foreground", className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
<DialogPrimitive.Description
ref={ref}
className={cn('nd-text-sm nd-text-muted-foreground', className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription
}
@@ -1,202 +1,201 @@
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, SquareDotIcon } from "lucide-react";
import { cn } from '@/utils/cn'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { CheckIcon, ChevronRightIcon, SquareDotIcon } from 'lucide-react'
import * as React from 'react'
import { cn } from "@/utils/cn";
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"nd-flex nd-select-none nd-items-center nd-rounded-sm nd-px-2 nd-py-1.5 nd-text-sm nd-outline-none focus:nd-bg-accent data-[state=open]:nd-bg-accent",
inset && "nd-pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="nd-ml-auto nd-h-4 nd-w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'nd-flex nd-select-none nd-items-center nd-rounded-sm nd-px-2 nd-py-1.5 nd-text-sm nd-outline-none focus:nd-bg-accent data-[state=open]:nd-bg-accent',
inset && 'nd-pl-8',
className
)}
{...props}
>
{children}
<ChevronRightIcon className="nd-ml-auto nd-h-4 nd-w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName;
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"nd-z-50 nd-min-w-[8rem] nd-overflow-hidden nd-rounded-md nd-border nd-bg-popover nd-p-1 nd-text-popover-foreground nd-shadow-lg data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[side=bottom]:nd-slide-in-from-top-2 data-[side=left]:nd-slide-in-from-right-2 data-[side=right]:nd-slide-in-from-left-2 data-[side=top]:nd-slide-in-from-bottom-2",
className
)}
{...props}
/>
));
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'nd-z-50 nd-min-w-[8rem] nd-overflow-hidden nd-rounded-md nd-border nd-bg-popover nd-p-1 nd-text-popover-foreground nd-shadow-lg data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[side=bottom]:nd-slide-in-from-top-2 data-[side=left]:nd-slide-in-from-right-2 data-[side=right]:nd-slide-in-from-left-2 data-[side=top]:nd-slide-in-from-bottom-2',
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName;
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"nd-z-50 nd-min-w-[8rem] nd-overflow-hidden nd-rounded-md nd-border nd-bg-popover nd-p-1 nd-text-popover-foreground nd-shadow-md",
"data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[side=bottom]:nd-slide-in-from-top-2 data-[side=left]:nd-slide-in-from-right-2 data-[side=right]:nd-slide-in-from-left-2 data-[side=top]:nd-slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'nd-z-50 nd-min-w-[8rem] nd-overflow-hidden nd-rounded-md nd-border nd-bg-popover nd-p-1 nd-text-popover-foreground nd-shadow-md',
'data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[side=bottom]:nd-slide-in-from-top-2 data-[side=left]:nd-slide-in-from-right-2 data-[side=right]:nd-slide-in-from-left-2 data-[side=top]:nd-slide-in-from-bottom-2',
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"nd-relative nd-flex nd-cursor-default nd-select-none nd-items-center nd-rounded-sm nd-px-2 nd-py-1.5 nd-text-sm nd-outline-none nd-transition-colors focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50",
inset && "nd-pl-8",
className
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'nd-relative nd-flex nd-cursor-default nd-select-none nd-items-center nd-rounded-sm nd-px-2 nd-py-1.5 nd-text-sm nd-outline-none nd-transition-colors focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50',
inset && 'nd-pl-8',
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"nd-relative nd-flex nd-select-none nd-items-center nd-rounded-sm nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-outline-none nd-transition-colors focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="nd-absolute nd-left-2 nd-flex nd-h-3.5 nd-w-3.5 nd-items-center nd-justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'nd-relative nd-flex nd-select-none nd-items-center nd-rounded-sm nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-outline-none nd-transition-colors focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="nd-absolute nd-left-2 nd-flex nd-h-3.5 nd-w-3.5 nd-items-center nd-justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="nd-h-4 nd-w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName;
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"nd-relative nd-flex nd-select-none nd-items-center nd-rounded-sm nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-outline-none nd-transition-colors focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50",
className
)}
{...props}
>
<span className="nd-absolute nd-left-2 nd-flex nd-h-3.5 nd-w-3.5 nd-items-center nd-justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<SquareDotIcon className="nd-h-4 nd-w-4 nd-fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'nd-relative nd-flex nd-select-none nd-items-center nd-rounded-sm nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-outline-none nd-transition-colors focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50',
className
)}
{...props}
>
<span className="nd-absolute nd-left-2 nd-flex nd-h-3.5 nd-w-3.5 nd-items-center nd-justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<SquareDotIcon className="nd-h-4 nd-w-4 nd-fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"nd-px-2 nd-py-1.5 nd-text-sm nd-font-semibold",
inset && "nd-pl-8",
className
)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
'nd-px-2 nd-py-1.5 nd-text-sm nd-font-semibold',
inset && 'nd-pl-8',
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-nd-mx-1 nd-my-1 nd-h-px nd-bg-muted", className)}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('-nd-mx-1 nd-my-1 nd-h-px nd-bg-muted', className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"nd-ml-auto nd-text-xs nd-tracking-widest nd-opacity-60",
className
)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
return (
<span
className={cn(
'nd-ml-auto nd-text-xs nd-tracking-widest nd-opacity-60',
className
)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup
}
@@ -1,47 +1,44 @@
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/utils/cn";
import { cn } from '@/utils/cn'
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
import * as React from 'react'
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("nd-relative nd-overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="nd-h-full nd-w-full nd-rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('nd-relative nd-overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="nd-h-full nd-w-full nd-rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<
typeof ScrollAreaPrimitive.ScrollAreaScrollbar
>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"nd-flex nd-touch-none nd-select-none",
orientation === "vertical" && "nd-h-full nd-w-1",
orientation === "horizontal" && "nd-h-1",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="nd-relative nd-flex-1 nd-rounded-full nd-bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
'nd-flex nd-touch-none nd-select-none',
orientation === 'vertical' && 'nd-h-full nd-w-1',
orientation === 'horizontal' && 'nd-h-1',
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="nd-relative nd-flex-1 nd-rounded-full nd-bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar };
export { ScrollArea, ScrollBar }
+101 -102
View File
@@ -1,122 +1,121 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown } from "lucide-react";
import { cn } from '@/utils/cn'
import * as SelectPrimitive from '@radix-ui/react-select'
import { Check, ChevronDown } from 'lucide-react'
import * as React from 'react'
import { cn } from "@/utils/cn";
const Select = SelectPrimitive.Root
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"nd-flex nd-h-10 nd-w-full nd-items-center nd-justify-between nd-rounded-md nd-border nd-border-input nd-bg-transparent nd-px-3 nd-py-2 nd-text-sm nd-ring-offset-background focus:nd-outline-none focus:nd-ring-2 focus:nd-ring-ring focus:nd-ring-offset-2 disabled:nd-cursor-not-allowed disabled:nd-opacity-50",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="nd-h-4 nd-w-4 nd-opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'nd-flex nd-h-10 nd-w-full nd-items-center nd-justify-between nd-rounded-md nd-border nd-border-input nd-bg-transparent nd-px-3 nd-py-2 nd-text-sm nd-ring-offset-background focus:nd-outline-none focus:nd-ring-2 focus:nd-ring-ring focus:nd-ring-offset-2 disabled:nd-cursor-not-allowed disabled:nd-opacity-50',
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="nd-h-4 nd-w-4 nd-opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"nd-relative nd-z-50 nd-min-w-[8rem] nd-overflow-hidden nd-rounded-md nd-border nd-bg-popover nd-text-popover-foreground nd-shadow-md data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[side=bottom]:nd-slide-in-from-top-2 data-[side=left]:nd-slide-in-from-right-2 data-[side=right]:nd-slide-in-from-left-2 data-[side=top]:nd-slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:nd-translate-y-1 data-[side=left]:-nd-translate-x-1 data-[side=right]:nd-translate-x-1 data-[side=top]:-nd-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"nd-p-1",
position === "popper" &&
"nd-h-[var(--radix-select-trigger-height)] nd-w-full nd-min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'nd-relative nd-z-50 nd-min-w-[8rem] nd-overflow-hidden nd-rounded-md nd-border nd-bg-popover nd-text-popover-foreground nd-shadow-md data-[state=open]:nd-animate-in data-[state=closed]:nd-animate-out data-[state=closed]:nd-fade-out-0 data-[state=open]:nd-fade-in-0 data-[state=closed]:nd-zoom-out-95 data-[state=open]:nd-zoom-in-95 data-[side=bottom]:nd-slide-in-from-top-2 data-[side=left]:nd-slide-in-from-right-2 data-[side=right]:nd-slide-in-from-left-2 data-[side=top]:nd-slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:nd-translate-y-1 data-[side=left]:-nd-translate-x-1 data-[side=right]:nd-translate-x-1 data-[side=top]:-nd-translate-y-1',
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
'nd-p-1',
position === 'popper' &&
'nd-h-[var(--radix-select-trigger-height)] nd-w-full nd-min-w-[var(--radix-select-trigger-width)]'
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn(
"nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-font-semibold",
className
)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
<SelectPrimitive.Label
ref={ref}
className={cn(
'nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-font-semibold',
className
)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"nd-relative nd-flex nd-w-full nd-select-none nd-items-center nd-rounded-sm nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-outline-none focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50",
className
)}
{...props}
>
<span className="nd-absolute nd-left-2 nd-flex nd-h-3.5 nd-w-3.5 nd-items-center nd-justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="nd-h-4 nd-w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.Item
ref={ref}
className={cn(
'nd-relative nd-flex nd-w-full nd-select-none nd-items-center nd-rounded-sm nd-py-1.5 nd-pl-8 nd-pr-2 nd-text-sm nd-outline-none focus:nd-bg-accent focus:nd-text-accent-foreground data-[disabled]:nd-pointer-events-none data-[disabled]:nd-opacity-50',
className
)}
{...props}
>
<span className="nd-absolute nd-left-2 nd-flex nd-h-3.5 nd-w-3.5 nd-items-center nd-justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="nd-h-4 nd-w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-nd-mx-1 nd-my-1 nd-h-px nd-bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
<SelectPrimitive.Separator
ref={ref}
className={cn('-nd-mx-1 nd-my-1 nd-h-px nd-bg-muted', className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
};
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator
}
+145 -184
View File
@@ -1,212 +1,173 @@
import { Args, defineDocumentType } from "contentlayer/source-files";
import rehypePrettycode, { Options as CodeOptions } from "rehype-pretty-code";
import remarkGfm from "remark-gfm";
import rehypeSlug from "rehype-slug";
import rehypeImgSize, { Options as ImgSizeOptions } from "rehype-img-size";
import type { Args } from 'contentlayer/source-files'
import { defineDocumentType } from 'contentlayer/source-files'
import type { Options as ImgSizeOptions } from 'rehype-img-size'
import rehypeImgSize from 'rehype-img-size'
import type { Options as CodeOptions } from 'rehype-pretty-code'
import rehypePrettycode from 'rehype-pretty-code'
import rehypeSlug from 'rehype-slug'
import remarkGfm from 'remark-gfm'
import {
rehypeCodeBlocksPostProcess,
rehypeCodeBlocksPreProcess
} from './mdx-plugins'
function removeSlash(path: string) {
let start = 0,
end = path.length;
while (path.charAt(start) == "/") start++;
while (path.charAt(end - 1) == "/" && end > start) end--;
let start = 0,
end = path.length
while (path.charAt(start) == '/') start++
while (path.charAt(end - 1) == '/' && end > start) end--
return path.slice(start, end);
return path.slice(start, end)
}
function removePattern(path: string, pattern: string) {
if (path.endsWith("/index") || path === "index") {
path = path.slice(0, path.length - "index".length);
}
if (path.endsWith('/index') || path === 'index') {
path = path.slice(0, path.length - 'index'.length)
}
if (!path.startsWith(pattern)) {
return path;
}
if (!path.startsWith(pattern)) {
return path
}
return removeSlash(path.slice(pattern.length));
return removeSlash(path.slice(pattern.length))
}
function visit(node: any, tagNames: any, handler: any) {
if (tagNames.includes(node.tagName)) {
handler(node);
return;
}
node.children?.forEach((n: any) => visit(n, tagNames, handler));
}
/**
* Should be added before rehype-pretty-code
*/
const rehypeCodeBlocksPreProcess = () => (tree: any) => {
visit(tree, ["pre"], (preEl: any) => {
const [codeEl] = preEl.children;
// Add default language `text` for code-blocks
codeEl.properties.className ||= ["language-text"];
});
};
/**
* Should be added after rehype-pretty-code
*/
const rehypeCodeBlocksPostProcess = () => (tree: any) => {
visit(tree, ["div"], (node: any) => {
// Remove default fragment div
// Add title to pre element
if ("data-rehype-pretty-code-fragment" in node.properties) {
if (node.children.length === 0) return;
let preEl = node.children[0];
if ("data-rehype-pretty-code-title" in preEl.properties) {
const title = preEl.children[0].value;
preEl = node.children[1];
if (!preEl) return;
preEl.properties.title = title;
}
Object.assign(node, preEl);
}
});
};
type Options = {
/**
* Where the docs files located
* @default "docs"
*/
docsPattern: string;
/**
* Where the docs files located
* @default "docs"
*/
docsPattern: string
/**
* @default "content"
*/
contentDirPath: string;
/**
* @default "content"
*/
contentDirPath: string
/**
* The directory path for images
* @default "./public"
*/
imgDirPath: string;
};
/**
* The directory path for images
* @default "./public"
*/
imgDirPath: string
}
export function createConfig(options: Partial<Options> = {}): Args {
const {
docsPattern = "docs",
contentDirPath = "content",
imgDirPath = "./public",
} = options;
const {
docsPattern = 'docs',
contentDirPath = 'content',
imgDirPath = './public'
} = options
const Docs = defineDocumentType(() => ({
name: "Docs",
filePathPattern: `${docsPattern}/**/*.mdx`,
contentType: "mdx",
fields: {
title: {
type: "string",
description: "The title of the document",
required: true,
},
description: {
type: "string",
description: "The description of the document",
required: false,
},
},
computedFields: {
locale: {
type: "string",
resolve: (post) => {
return post._raw.flattenedPath.split(".")[1];
},
},
slug: {
type: "string",
resolve: (post) => {
return removePattern(
post._raw.flattenedPath.split(".")[0],
docsPattern
);
},
},
},
}));
const Docs = defineDocumentType(() => ({
name: 'Docs',
filePathPattern: `${docsPattern}/**/*.mdx`,
contentType: 'mdx',
fields: {
title: {
type: 'string',
description: 'The title of the document',
required: true
},
description: {
type: 'string',
description: 'The description of the document',
required: false
}
},
computedFields: {
locale: {
type: 'string',
resolve: post => {
return post._raw.flattenedPath.split('.')[1]
}
},
slug: {
type: 'string',
resolve: post => {
return removePattern(
post._raw.flattenedPath.split('.')[0],
docsPattern
)
}
}
}
}))
const Meta = defineDocumentType(() => ({
name: "Meta",
filePathPattern: `${docsPattern}/**/*.json`,
contentType: "data",
fields: {
title: {
type: "string",
description: "The title of the folder",
required: false,
},
pages: {
type: "list",
of: {
type: "string",
},
description: "Pages of the folder",
default: [],
},
const Meta = defineDocumentType(() => ({
name: 'Meta',
filePathPattern: `${docsPattern}/**/*.json`,
contentType: 'data',
fields: {
title: {
type: 'string',
description: 'The title of the folder',
required: false
},
pages: {
type: 'list',
of: {
type: 'string'
},
computedFields: {
slug: {
type: "string",
resolve: (post) =>
removePattern(post._raw.sourceFileDir, docsPattern),
},
},
}));
description: 'Pages of the folder',
default: []
}
},
computedFields: {
slug: {
type: 'string',
resolve: post => removePattern(post._raw.sourceFileDir, docsPattern)
}
}
}))
return {
contentDirPath: contentDirPath,
documentTypes: [Docs, Meta],
mdx: {
rehypePlugins: [
rehypeCodeBlocksPreProcess,
[rehypePrettycode, codeOptions],
rehypeCodeBlocksPostProcess,
rehypeSlug,
[
rehypeImgSize as any,
{
dir: imgDirPath,
} as ImgSizeOptions,
],
],
remarkPlugins: [remarkGfm],
},
};
return {
contentDirPath,
documentTypes: [Docs, Meta],
mdx: {
rehypePlugins: [
rehypeCodeBlocksPreProcess,
[rehypePrettycode, codeOptions],
rehypeCodeBlocksPostProcess,
rehypeSlug,
[
/* eslint-disable */
rehypeImgSize as any,
{
dir: imgDirPath
} as ImgSizeOptions
]
],
remarkPlugins: [remarkGfm]
}
}
}
/**
* MDX Plugins
*/
export const mdxPlugins = {
rehypeCodeBlocksPreProcess,
rehypePrettycode,
rehypeCodeBlocksPostProcess,
rehypeSlug,
rehypeImgSize: rehypeImgSize as any, // fix tsc errors
remarkGfm,
};
rehypeCodeBlocksPreProcess,
rehypePrettycode,
rehypeCodeBlocksPostProcess,
rehypeSlug,
rehypeImgSize: rehypeImgSize as any, // fix tsc errors
remarkGfm
}
export const codeOptions: Partial<CodeOptions> = {
theme: "css-variables",
keepBackground: false,
onVisitLine(node) {
if (node.children.length === 0) {
node.children = [{ type: "text", value: " " }];
}
},
onVisitHighlightedLine(node) {
node.properties.className.push("line-highlighted");
},
onVisitHighlightedWord(node) {
node.properties.className = ["word-highlighted"];
},
};
theme: 'css-variables',
keepBackground: false,
onVisitLine(node) {
if (node.children.length === 0) {
node.children = [{ type: 'text', value: ' ' }]
}
},
onVisitHighlightedLine(node) {
node.properties.className.push('line-highlighted')
},
onVisitHighlightedWord(node) {
node.properties.className = ['word-highlighted']
}
}
export const defaultConfig: Args = createConfig();
export const defaultConfig: Args = createConfig()
@@ -0,0 +1,45 @@
function visit(node, tagNames, handler) {
if (tagNames.includes(node.tagName)) {
handler(node)
return
}
node.children?.forEach(n => visit(n, tagNames, handler))
}
/**
* Should be added before rehype-pretty-code
*/
export const rehypeCodeBlocksPreProcess = () => tree => {
visit(tree, ['pre'], preEl => {
const [codeEl] = preEl.children
// Add default language `text` for code-blocks
codeEl.properties.className ||= ['language-text']
})
}
/**
* Should be added after rehype-pretty-code
*/
export const rehypeCodeBlocksPostProcess = () => tree => {
visit(tree, ['div'], node => {
// Remove default fragment div
// Add title to pre element
if ('data-rehype-pretty-code-fragment' in node.properties) {
if (node.children.length === 0) return
let preEl = node.children[0]
if ('data-rehype-pretty-code-title' in preEl.properties) {
const title = preEl.children[0].value
preEl = node.children[1]
if (!preEl) return
preEl.properties.title = title
}
Object.assign(node, preEl)
}
})
}
+7 -7
View File
@@ -1,9 +1,9 @@
import { createContext } from "react";
import { createContext } from 'react'
export const I18nContext = createContext<
| {
locale: string;
onChange: (v: string) => void;
}
| undefined
>(undefined);
| {
locale: string
onChange: (v: string) => void
}
| undefined
>(undefined)

Some files were not shown because too many files have changed in this diff Show More