mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-07-27 04:31:57 +00:00
Configure eslint & prettier
This commit is contained in:
@@ -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
@@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,7 @@ node_modules
|
||||
out
|
||||
dist
|
||||
.contentlayer
|
||||
.eslintcache
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pnpm-lock.yaml
|
||||
.changeset/*.md
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
[](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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 ->
|
||||
</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 ->
|
||||
</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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 ->
|
||||
</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 ->
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
"components": "@/components",
|
||||
"utils": "@/utils/cn"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}))
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
|
||||
+102
-103
@@ -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
@@ -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 @@
|
||||
export { twMerge as cn } from "tailwind-merge";
|
||||
export { twMerge as cn } from 'tailwind-merge'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const defaultLanguage = "en";
|
||||
export const languages = ["en", "cn"];
|
||||
export const defaultLanguage = 'en'
|
||||
export const languages = ['en', 'cn']
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,7 +8,7 @@ title: 快速開始
|
||||
|
||||
```js
|
||||
// 這會將“Hello World”記錄到控制台
|
||||
console.log("你好世界");
|
||||
console.log('你好世界')
|
||||
```
|
||||
|
||||
| 名稱 | 組件 |
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"pages": [
|
||||
"---指南---",
|
||||
"index",
|
||||
"install",
|
||||
"---API Reference---",
|
||||
"components"
|
||||
]
|
||||
"pages": [
|
||||
"---指南---",
|
||||
"index",
|
||||
"install",
|
||||
"---API Reference---",
|
||||
"components"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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).*)']
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user