From 8d72067d16f9038dd26b4a93411deef5232e4f9c Mon Sep 17 00:00:00 2001 From: Antoine BERNIER Date: Thu, 8 Aug 2024 17:35:18 +0200 Subject: [PATCH] wip --- next.config.mjs | 1 + package.json | 15 +- src/app/[...slug]/DocsContext.tsx | 38 + src/app/[...slug]/layout.tsx | 178 ++ src/app/[...slug]/page.tsx | 62 + src/app/favicon.ico | Bin 0 -> 25931 bytes src/{pages/main.css => app/globals.css} | 139 ++ src/app/layout.tsx | 22 + src/{pages/index.tsx => app/page.tsx} | 10 +- src/components/Codesandbox.tsx | 74 +- src/components/Layout.tsx | 141 -- src/components/LibSwitcher.tsx | 22 +- src/components/Nav.tsx | 20 +- src/components/Post.tsx | 46 +- src/components/Search/SearchItem.tsx | 4 +- src/components/Search/SearchModal.tsx | 2 +- .../Search/SearchModalContainer.tsx | 41 +- src/components/Search/index.tsx | 14 +- src/components/Seo.tsx | 9 +- src/components/Toc.tsx | 4 +- src/components/ToggleTheme.tsx | 2 + src/data/libraries.ts | 160 +- src/hooks/useCSB.ts | 39 - src/hooks/useDocs.ts | 31 - src/lib/createRequiredContext.tsx | 23 + src/pages/[...slug].tsx | 106 - src/pages/_app.tsx | 7 - src/pages/_document.tsx | 34 - src/pages/pmndrs.css | 138 -- src/utils/docs.ts | 86 +- src/utils/rehype.ts | 4 +- tailwind.config.js | 5 - tailwind.config.ts | 12 + tsconfig.json | 12 +- yarn.lock | 2005 ++++++++--------- 35 files changed, 1721 insertions(+), 1785 deletions(-) create mode 100644 src/app/[...slug]/DocsContext.tsx create mode 100644 src/app/[...slug]/layout.tsx create mode 100644 src/app/[...slug]/page.tsx create mode 100644 src/app/favicon.ico rename src/{pages/main.css => app/globals.css} (61%) create mode 100644 src/app/layout.tsx rename src/{pages/index.tsx => app/page.tsx} (96%) delete mode 100644 src/components/Layout.tsx delete mode 100644 src/hooks/useCSB.ts delete mode 100644 src/hooks/useDocs.ts create mode 100644 src/lib/createRequiredContext.tsx delete mode 100644 src/pages/[...slug].tsx delete mode 100644 src/pages/_app.tsx delete mode 100644 src/pages/_document.tsx delete mode 100644 src/pages/pmndrs.css delete mode 100644 tailwind.config.js create mode 100644 tailwind.config.ts diff --git a/next.config.mjs b/next.config.mjs index b635071c..43bda94a 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,6 +5,7 @@ const nextConfig = { // domains: ['codesandbox.io'], unoptimized: true, }, + output: 'export', async redirects() { return [ { diff --git a/package.json b/package.json index 3e28c863..d66c80f0 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,8 @@ { + "name": "pmndrs-docs", + "version": "0.1.0", + "private": true, + "type": "module", "devDependencies": { "@types/node": "^18.6.4", "@types/react": "^18.3.0", @@ -6,10 +10,10 @@ "autoprefixer": "^10.4.8", "eslint": "^8.21.0", "eslint-config-next": "^14.2.5", - "postcss": "^8.4.16", + "postcss": "^8", "prettier": "^2.7.1", "sharp": "^0.30.7", - "tailwindcss": "^3.1.8", + "tailwindcss": "^3.4.1", "typescript": "^5.5.4" }, "dependencies": { @@ -22,7 +26,8 @@ "match-sorter": "^6.3.1", "memfs": "^3.4.7", "next": "^14.2.5", - "next-mdx-remote": "^4.1.0", + "next-mdx-remote": "^4.4.1", + "p-memoize": "^7.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^4.12.0", @@ -30,9 +35,9 @@ "remark-gfm": "^3.0.1" }, "scripts": { - "dev": "next", + "dev": "NODE_OPTIONS='--inspect' next", "start": "next start", - "build": "next build", + "build": "rm -rf out && next build", "format": "prettier -w src/", "lint": "eslint src/**/*.{ts,tsx} && prettier . --check" }, diff --git a/src/app/[...slug]/DocsContext.tsx b/src/app/[...slug]/DocsContext.tsx new file mode 100644 index 00000000..2ec1f25c --- /dev/null +++ b/src/app/[...slug]/DocsContext.tsx @@ -0,0 +1,38 @@ +'use client' + +import { ReactNode } from 'react' + +import { createRequiredContext } from '@/lib/createRequiredContext' + +export type DocToC = { + id: string + level: number + title: string + description: string + content: string + url: string + parent: DocToC | null + label: string +} + +export type Doc = { + slug: string[] + url: string + editURL: string + nav: number + title: string + description: string + content: string + boxes: string[] + tableOfContents: DocToC[] +} + +export type Ctx = Doc[] + +const [hook, Provider] = createRequiredContext() + +export { hook as useDocs } + +export function DocsContext({ children, value = [] }: { children?: ReactNode; value: Ctx }) { + return {children} +} diff --git a/src/app/[...slug]/layout.tsx b/src/app/[...slug]/layout.tsx new file mode 100644 index 00000000..4eb26cb4 --- /dev/null +++ b/src/app/[...slug]/layout.tsx @@ -0,0 +1,178 @@ +import clsx from 'clsx' +import * as React from 'react' +import LibSwitcher from '@/components/LibSwitcher' +import Nav from '@/components/Nav' +import Icon from '@/components/Icon' +import Toc from '@/components/Toc' +import Search from '@/components/Search' +import Link from 'next/link' +// import { useRouter } from 'next/router' +import { useLockBodyScroll } from '@/hooks/useLockBodyScroll' +import libs from '@/data/libraries' + +import ToggleTheme from '@/components/ToggleTheme' + +import { Doc, DocsContext } from './DocsContext' +import libraries from '@/data/libraries' +import { notFound } from 'next/navigation' +import { getData, getDocs } from '@/utils/docs' + +export type Props = { + params: { slug: string[] } + children: React.ReactNode +} + +export default async function Layout({ params, children }: Props) { + const slug = params.slug + + // 404 + if (!(slug[0] in libraries)) { + return notFound() + } + + const asPath = `/${slug.join('/')}` + + const lib = slug[0] + + const { docs, doc } = await getData(...slug) + // console.log('docs', docs) + + // const [menuOpen, setMenuOpen] = React.useState(false) + // useLockBodyScroll(menuOpen) + + // const { asPath } = useRouter() + const currentPageIndex = docs.findIndex((item) => item.url === asPath) + const currentPage = docs[currentPageIndex] + const previousPage = currentPageIndex > 0 && docs[currentPageIndex - 1] + const nextPage = currentPageIndex < docs.length - 1 && docs[currentPageIndex + 1] + + // React.useEffect(() => setMenuOpen(false), [asPath]) + + return ( + <> + +
+
+ +
+ Pmndrs + .docs +
+ + + + +
+
+ +
+
+ +
+
+
+
{children}
+ +
+ {!!currentPage && ( + + )} + + {(!!previousPage || !!nextPage) && ( + + )} +
+
+ + +
+
+
+
+
+ + ) +} diff --git a/src/app/[...slug]/page.tsx b/src/app/[...slug]/page.tsx new file mode 100644 index 00000000..dbef2715 --- /dev/null +++ b/src/app/[...slug]/page.tsx @@ -0,0 +1,62 @@ +import * as React from 'react' + +import SEO from '@/components/Seo' +import Post from '@/components/Post' + +import { getData, getDocs } from '@/utils/docs' + +export type Props = { + params: { slug: string[] } +} + +export default async function Page({ params }: Props) { + const slug = params.slug + + const { doc } = await getData(...slug) // [ 'react-three-fiber', 'getting-started', 'introduction' ] + + return ( + <> + {/* */} +
+
+

{doc?.title}

+ {!!doc?.description?.length && ( +

{doc.description}

+ )} +
+
{doc ? : 'empty doc'}
+
+ + ) +} + +export async function generateStaticParams() { + console.log('generateStaticParams') + + const docs = await getDocs('react-three-fiber') + const paths = docs.map(({ slug }) => ({ slug })) + + return [ + { slug: ['react-three-fiber', 'getting-started', 'introduction'] }, + { slug: ['react-three-fiber', 'getting-started', 'installation'] }, + { slug: ['react-three-fiber', 'getting-started', 'your-first-scene'] }, + // { slug: [ 'react-three-fiber', 'getting-started', 'examples' ] }, + // { slug: [ 'react-three-fiber', 'api', 'canvas' ] }, + // { slug: [ 'react-three-fiber', 'api', 'objects' ] }, + // { slug: [ 'react-three-fiber', 'api', 'hooks' ] }, + // { slug: [ 'react-three-fiber', 'api', 'events' ] }, + // { slug: [ 'react-three-fiber', 'api', 'additional-exports' ] }, + // { slug: [ 'react-three-fiber', 'advanced', 'scaling-performance' ] }, + // { slug: [ 'react-three-fiber', 'advanced', 'pitfalls' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'v8-migration-guide' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'events-and-interaction' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'loading-models' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'loading-textures' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'basic-animations' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'using-with-react-spring' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'typescript' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'testing' ] }, + // { slug: [ 'react-three-fiber', 'tutorials', 'how-it-works' ] } + ] + // return paths +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/src/pages/main.css b/src/app/globals.css similarity index 61% rename from src/pages/main.css rename to src/app/globals.css index 776fe048..8b240907 100644 --- a/src/pages/main.css +++ b/src/app/globals.css @@ -218,3 +218,142 @@ ul.grid-list > li:before { ::-webkit-scrollbar-track { @apply dark:bg-gray-800; } + +/** + * Pmndrs theme for JavaScript, CSS and HTML + * Loosely based on https://marketplace.visualstudio.com/items?itemName=pmndrs.pmndrs + * @author Paul Henschel + */ + +code[class*='language-'], +pre[class*='language-'] { + color: #e4f0fb; + background: none; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: Menlo, Monaco, 'Courier New', monospace; + font-size: 0.95em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*='language-'] { + --comment: #a6accd; + --punctuation: #e4f0fb; + --property: #e4f0fb; + --boolean: #5de4c7; + --string: #5de4c7; + --operator: #add7ff; + --function: #5de4c7; + --keyword: #add7ff; + --literal: #fffac2; + --falsy: #f087bd; + + padding: 1.75em; + margin: 1.5em 0; + overflow: auto; + border-radius: 0.75em; +} + +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background: #252b37; +} + +/* Inline code */ +:not(pre) > code[class*='language-'] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.namespace { + opacity: 0.7; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: var(--comment); +} + +.token.punctuation { + color: var(--punctuation); +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: var(--property); +} + +.token.boolean, +.token.number { + color: var(--boolean); +} + +.token.selector, +.token.attr-value, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: var(--string); +} + +.token.attr-name, +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: var(--operator); +} + +.token.atrule, +.token.function, +.token.class-name { + color: var(--function); +} + +.token.keyword { + color: var(--keyword); +} + +.token.regex, +.token.important { + color: var(--literal); +} + +.token.deleted { + color: var(--falsy); +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 00000000..3314e478 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/src/pages/index.tsx b/src/app/page.tsx similarity index 96% rename from src/pages/index.tsx rename to src/app/page.tsx index b99a9113..21376fc3 100644 --- a/src/pages/index.tsx +++ b/src/app/page.tsx @@ -1,14 +1,14 @@ import Head from 'next/head' import Link from 'next/link' import Image from 'next/image' -import Icon from 'components/Icon' -import libs from 'data/libraries' -import ToggleTheme from 'components/ToggleTheme' +import Icon from '@/components/Icon' +import libs from '@/data/libraries' +import ToggleTheme from '@/components/ToggleTheme' export default function HomePage() { return ( <> - + {/* pmnd.rs docs @@ -32,7 +32,7 @@ export default function HomePage() { - + */}
diff --git a/src/components/Codesandbox.tsx b/src/components/Codesandbox.tsx index c42f7b49..f8b35b3d 100644 --- a/src/components/Codesandbox.tsx +++ b/src/components/Codesandbox.tsx @@ -1,26 +1,30 @@ import Image from 'next/image' -import { useCSB } from 'hooks/useCSB' + import clsx from 'clsx' -export interface CodesandboxProps { - id: string - tags?: string[] - description?: string - title?: string - hideTitle?: boolean +export type CSB = { + title: string + description: string + content: string + screenshot_url: string + tags: string[] } export default function Codesandbox({ id, + data, tags: defaultTags, description: defaultDescription, title: defaultTitle, hideTitle = false, -}: CodesandboxProps) { - const boxes = useCSB() - const data = boxes[id] - if (!data) return - +}: { + id: string + data: CSB + tags?: string[] + description?: string + title?: string + hideTitle?: boolean +}) { const tags = defaultTags || data?.tags || [] const description = defaultDescription || data?.description || '' const title = defaultTitle || data?.title || '' @@ -28,15 +32,17 @@ export default function Codesandbox({ return ( <> - {title} + {data?.screenshot_url && ( + {title} + )} {!hideTitle && ( <> @@ -60,3 +66,29 @@ export default function Codesandbox({ ) } + +export async function fetchCSB(ids: string[]) { + // console.log('fetchCSB', ids) + + const boxes: Record = {} + + const slimData = await fetch('https://codesandbox.io/api/v1/sandboxes/mslim', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ ids }), + }).then((res) => res.json()) + + for (const { id, title } of slimData) { + boxes[id] = { + title, + description: '', + content: '', + screenshot_url: `https://codesandbox.io/api/v1/sandboxes/${id}/screenshot.png`, + tags: [], + } + } + + return boxes +} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx deleted file mode 100644 index 1df3ffe1..00000000 --- a/src/components/Layout.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import clsx from 'clsx' -import * as React from 'react' -import LibSwitcher from 'components/LibSwitcher' -import Nav from 'components/Nav' -import Icon from 'components/Icon' -import Toc from 'components/Toc' -import Search from 'components/Search' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { useLockBodyScroll } from 'hooks/useLockBodyScroll' -import { Doc, useDocs } from 'hooks/useDocs' -import ToggleTheme from './ToggleTheme' - -export interface LayoutProps { - doc: Doc - children: React.ReactNode -} - -export default function Layout({ doc, children }: LayoutProps) { - const [menuOpen, setMenuOpen] = React.useState(false) - useLockBodyScroll(menuOpen) - - const docs = useDocs() - const { asPath } = useRouter() - const currentPageIndex = docs.findIndex((item) => item.url === asPath) - const previousPage = currentPageIndex > 0 && docs[currentPageIndex - 1] - const nextPage = currentPageIndex < docs.length - 1 && docs[currentPageIndex + 1] - - React.useEffect(() => setMenuOpen(false), [asPath]) - - return ( - <> -
-
- -
- Pmndrs - .docs -
- - - - -
-
-
-
- -
-
-
-
{children}
- {!!docs[currentPageIndex] && ( - - )} - {(!!previousPage || !!nextPage) && ( -
- {!!previousPage && ( -
- -
- - {previousPage.title} - -
-
- )} - {!!nextPage && ( -
- -
- - {nextPage.title} - -
-
- )} -
- )} -
-
- {doc.tableOfContents.length ? : null} -
-
-
-
-
- - ) -} diff --git a/src/components/LibSwitcher.tsx b/src/components/LibSwitcher.tsx index 58262e05..af3cc692 100644 --- a/src/components/LibSwitcher.tsx +++ b/src/components/LibSwitcher.tsx @@ -1,14 +1,22 @@ +'use client' + import * as React from 'react' import clsx from 'clsx' import Link from 'next/link' -import { useRouter } from 'next/router' +// import { useRouter } from 'next/router' import { Popover } from '@headlessui/react' -import libs from 'data/libraries' +import libs from '@/data/libraries' -export default function LibSwitcher() { - const router = useRouter() - const { query } = router - const currentPage = React.useMemo(() => libs[query.slug![0]].title, [query]) +export default function LibSwitcher({ + currentPage, + lib, +}: { + currentPage: string + lib: keyof typeof libs +}) { + // const router = useRouter() + // const { query } = router + // const currentPage = React.useMemo(() => libs[query.slug![0]].title, [query]) return ( @@ -23,7 +31,7 @@ export default function LibSwitcher() { href={data.url} className={clsx( 'px-3 py-2 hover:bg-gray-50 rounded-md font-normal text-base dark:hover:bg-gray-600/30', - id === query.slug![0] && 'sr-only' + id === lib && 'sr-only' )} > {data.title} diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx index fc9be97c..0997e0f6 100644 --- a/src/components/Nav.tsx +++ b/src/components/Nav.tsx @@ -1,18 +1,15 @@ import * as React from 'react' import clsx from 'clsx' import Link from 'next/link' -import { useRouter } from 'next/router' -import { Doc, useDocs } from 'hooks/useDocs' +import { Doc } from '../app/[...slug]/DocsContext' interface NavItemProps { doc: Doc + asPath: string } -function NavItem({ doc }: NavItemProps) { - const { asPath } = useRouter() - const [active, setActive] = React.useState(false) - - React.useEffect(() => setActive(doc.url === asPath), [doc.url, asPath]) +function NavItem({ doc, asPath }: NavItemProps) { + const active = doc.url === `/${asPath}` return ( > -function Nav() { - const docs = useDocs() +function Nav({ docs, asPath }: { docs: Doc[]; asPath: string }) { const nav = React.useMemo( () => docs.reduce((acc, doc) => { @@ -56,9 +52,11 @@ function Nav() { {key.replace(/\-/g, ' ')} {doc.url ? ( - + ) : ( - Object.entries(doc).map(([key, doc]: [string, Doc]) => ) + Object.entries(doc).map(([key, doc]: [string, Doc]) => ( + + )) )} ))} diff --git a/src/components/Post.tsx b/src/components/Post.tsx index e4afcfcf..02fddd7c 100644 --- a/src/components/Post.tsx +++ b/src/components/Post.tsx @@ -1,10 +1,15 @@ import * as React from 'react' -import Codesandbox from 'components/Codesandbox' -import { MDXRemoteProps, MDXRemoteSerializeResult, MDXRemote } from 'next-mdx-remote' -import { MARKDOWN_REGEX } from 'utils/docs' +import Codesandbox from '@/components/Codesandbox' +import { MDXRemote } from 'next-mdx-remote/rsc' +import { MARKDOWN_REGEX } from '@/utils/docs' +import { fetchCSB } from '@/components/Codesandbox' + +import remarkGFM from 'remark-gfm' +import rehypePrismPlus from 'rehype-prism-plus' +import { codesandbox, toc } from '@/utils/rehype' +import { Doc } from '@/app/[...slug]/DocsContext' const components = { - Codesandbox, Hint: ({ children }: { children: React.ReactNode }) => (
{children} @@ -80,7 +85,6 @@ const components = { target = isAnchor ? '_blank' : target rel = isAnchor ? 'noopener noreferrer' : rel href = isAnchor ? href : href.replace(MARKDOWN_REGEX, '') - return ( {children} @@ -112,6 +116,34 @@ const components = { ), } -export default function Post(props: MDXRemoteSerializeResult) { - return +export default async function Post({ doc }: { doc: Doc }) { + const { content, url, title } = doc + + return ( + ) => { + const boxes = await fetchCSB(doc.boxes) + // console.log('boxes', boxes) + const data = boxes[props.id] // retrieve the data from its `id` + + return + }, + } as React.ComponentProps['components'] + } + options={{ + mdxOptions: { + remarkPlugins: [remarkGFM], + rehypePlugins: [ + rehypePrismPlus, + codesandbox(doc.boxes), // will populate `doc.boxes` + toc(doc.tableOfContents, url, title, content), // will populate `doc.tableOfContents` + ], + }, + }} + /> + ) } diff --git a/src/components/Search/SearchItem.tsx b/src/components/Search/SearchItem.tsx index f2e33b87..38e34543 100644 --- a/src/components/Search/SearchItem.tsx +++ b/src/components/Search/SearchItem.tsx @@ -1,6 +1,6 @@ import Link from 'next/link' -import Icon from 'components/Icon' -import { highlight } from 'utils/text' +import Icon from '@/components/Icon' +import { highlight } from '@/utils/text' export interface SearchResult { title: string diff --git a/src/components/Search/SearchModal.tsx b/src/components/Search/SearchModal.tsx index 2dfd1cb1..30e5f459 100644 --- a/src/components/Search/SearchModal.tsx +++ b/src/components/Search/SearchModal.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx' import * as React from 'react' -import Icon from 'components/Icon' +import Icon from '@/components/Icon' import SearchItem, { SearchResult } from './SearchItem' export interface SearchModelProps { diff --git a/src/components/Search/SearchModalContainer.tsx b/src/components/Search/SearchModalContainer.tsx index 74ec50dc..b7d2a06d 100644 --- a/src/components/Search/SearchModalContainer.tsx +++ b/src/components/Search/SearchModalContainer.tsx @@ -1,23 +1,23 @@ import * as React from 'react' -import { useRouter } from 'next/router' +import { useRouter } from 'next/navigation' import { matchSorter } from 'match-sorter' -import { useCSB } from 'hooks/useCSB' -import { useDocs } from 'hooks/useDocs' +import { useDocs } from '../../app/[...slug]/DocsContext' import SearchModal from './SearchModal' import type { SearchResult } from './SearchItem' -import { escape } from 'utils/text' +import { escape } from '@/utils/text' export interface SearchModalContainerProps { onClose: React.MouseEventHandler } export const SearchModalContainer = ({ onClose }: SearchModalContainerProps) => { - const router = useRouter() - const boxes = useCSB() + // const router = useRouter() + // const boxes = useCSB() const docs = useDocs() - const [lib] = router.query.slug as string[] + console.log('docs', docs) + // const [lib] = router.query.slug as string[] const [query, setQuery] = React.useState('') const deferredQuery = React.useDeferredValue(query) const [results, setResults] = React.useState([]) @@ -32,18 +32,19 @@ export const SearchModalContainer = ({ onClose }: SearchModalContainerProps) => result.title.length // Search - const entries = (docs.flatMap(({ tableOfContents }) => tableOfContents) as SearchResult[]) - .filter((entry) => entry.description.length > 0) - .concat( - Object.entries(boxes).flatMap(([id, data]) => ({ - ...data, - label: 'codesandbox.io', - description: data.description ?? '', - content: data.content ?? '', - url: `https://codesandbox.io/s/${id}`, - image: data.screenshot_url, - })) - ) + const entries = ( + docs.flatMap(({ tableOfContents }) => tableOfContents) as SearchResult[] + ).filter((entry) => entry.description.length > 0) + // .concat( + // Object.entries(boxes).flatMap(([id, data]) => ({ + // ...data, + // label: 'codesandbox.io', + // description: data.description ?? '', + // content: data.content ?? '', + // url: `https://codesandbox.io/s/${id}`, + // image: data?.screenshot_url, + // })) + // ) const results = matchSorter(entries, deferredQuery, { keys: ['title', 'description', 'content'], @@ -56,7 +57,7 @@ export const SearchModalContainer = ({ onClose }: SearchModalContainerProps) => setResults(results) }) - }, [boxes, docs, lib, deferredQuery]) + }, [docs, deferredQuery]) return ( setShowSearchModal(false), [router.asPath]) + React.useEffect(() => setShowSearchModal(false), []) return ( <> diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx index 28fc13ac..12de255a 100644 --- a/src/components/Seo.tsx +++ b/src/components/Seo.tsx @@ -1,13 +1,8 @@ import Head from 'next/head' -import libs from 'data/libraries' -import { useRouter } from 'next/router' - -export default function SEO() { - const { query } = useRouter() - const [lib] = query.slug as string[] +import libs from '@/data/libraries' +export default function SEO({ lib }: { lib: keyof typeof libs }) { const currentSeo = libs[lib] - if (!currentSeo) return null const title = `${currentSeo.title} Documentation` diff --git a/src/components/Toc.tsx b/src/components/Toc.tsx index 6a1c32e9..2e49d55f 100644 --- a/src/components/Toc.tsx +++ b/src/components/Toc.tsx @@ -1,6 +1,8 @@ +'use client' + import * as React from 'react' import clsx from 'clsx' -import type { DocToC } from 'hooks/useDocs' +import type { DocToC } from '../app/[...slug]/DocsContext' export interface ToCProps { toc: DocToC[] diff --git a/src/components/ToggleTheme.tsx b/src/components/ToggleTheme.tsx index f5c11419..5e0a715d 100644 --- a/src/components/ToggleTheme.tsx +++ b/src/components/ToggleTheme.tsx @@ -1,3 +1,5 @@ +'use client' + import * as React from 'react' import { CiDark, CiLight } from 'react-icons/ci' diff --git a/src/data/libraries.ts b/src/data/libraries.ts index 25030244..434b49d2 100644 --- a/src/data/libraries.ts +++ b/src/data/libraries.ts @@ -1,9 +1,9 @@ -import reactThreeFiberShare from 'assets/react-three-fiber.jpg' -import zustandShare from 'assets/zustand.jpg' -import zustandIcon from 'assets/zustand-icon.png' -import jotaiIcon from 'assets/jotai-icon.png' -import reactThreeA11yShare from 'assets/react-three-a11y.jpg' -import reactPostprocessingShare from 'assets/react-postprocessing.jpg' +import reactThreeFiberShare from '@/assets/react-three-fiber.jpg' +import zustandShare from '@/assets/zustand.jpg' +import zustandIcon from '@/assets/zustand-icon.png' +import jotaiIcon from '@/assets/jotai-icon.png' +import reactThreeA11yShare from '@/assets/react-three-a11y.jpg' +import reactPostprocessingShare from '@/assets/react-postprocessing.jpg' export interface Library { title: string @@ -21,7 +21,7 @@ export interface Library { docs?: string } -const libraries: Record = { +const libraries = { 'react-three-fiber': { title: 'React Three Fiber', url: '/react-three-fiber', @@ -30,77 +30,79 @@ const libraries: Record = { image: reactThreeFiberShare.src, docs: 'pmndrs/react-three-fiber/master/docs', }, - 'react-spring': { - title: 'React Spring', - url: 'https://react-spring.io', - github: 'https://github.com/pmndrs/react-spring', - description: 'Bring your components to life with simple spring animation primitives for React', - }, - drei: { - title: 'Drei', - url: 'https://github.com/pmndrs/drei#readme', - github: 'https://github.com/pmndrs/drei', - description: - 'Drei is a growing collection of useful helpers and abstractions for react-three-fiber', - }, - zustand: { - title: 'Zustand', - url: '/zustand', - github: 'https://github.com/pmndrs/zustand', - description: - 'Zustand is a small, fast and scalable bearbones state-management solution, it has a comfy api based on hooks', - icon: zustandIcon.src, - iconWidth: zustandIcon.width, - iconHeight: zustandIcon.height, - image: zustandShare.src, - docs: 'pmndrs/zustand/main/docs', - }, - jotai: { - title: 'Jotai', - url: 'https://jotai.org/docs/introduction', - github: 'https://github.com/pmndrs/jotai', - description: 'Jotai is a primitive and flexible state management library for React', - icon: jotaiIcon.src, - iconWidth: jotaiIcon.width, - iconHeight: jotaiIcon.height, - }, - valtio: { - title: 'Valtio', - url: 'https://valtio.pmnd.rs', - github: 'https://github.com/pmndrs/valtio', - description: 'Valtio makes proxy-state simple for React and Vanilla', - }, - a11y: { - title: 'A11y', - url: '/a11y', - github: 'https://github.com/pmndrs/react-three-a11y', - description: - '@react-three/a11y brings accessibility to webGL with easy-to-use react-three-fiber components', - image: reactThreeA11yShare.src, - docs: 'pmndrs/react-three-a11y/main/docs', - }, - 'react-postprocessing': { - title: 'React Postprocessing', - url: '/react-postprocessing', - github: 'https://github.com/pmndrs/react-postprocessing', - description: 'React Postprocessing is a postprocessing wrapper for @react-three/fiber', - image: reactPostprocessingShare.src, - docs: 'pmndrs/react-postprocessing/master/docs', - }, - uikit: { - title: 'uikit', - url: '/uikit', - github: 'https://github.com/pmndrs/uikit', - description: 'uikit brings user interfaces to @react-three/fiber', - docs: 'pmndrs/uikit/main/docs', - }, - xr: { - title: 'xr', - url: '/xr', - github: 'https://github.com/pmndrs/xr', - description: 'VR/AR for @react-three/fiber', - docs: 'pmndrs/xr/main/docs', - }, -} + // 'react-spring': { + // title: 'React Spring', + // url: 'https://react-spring.io', + // github: 'https://github.com/pmndrs/react-spring', + // description: 'Bring your components to life with simple spring animation primitives for React', + // }, + // drei: { + // title: 'Drei', + // url: 'https://github.com/pmndrs/drei#readme', + // github: 'https://github.com/pmndrs/drei', + // description: + // 'Drei is a growing collection of useful helpers and abstractions for react-three-fiber', + // }, + // zustand: { + // title: 'Zustand', + // url: '/zustand', + // github: 'https://github.com/pmndrs/zustand', + // description: + // 'Zustand is a small, fast and scalable bearbones state-management solution, it has a comfy api based on hooks', + // icon: zustandIcon.src, + // iconWidth: zustandIcon.width, + // iconHeight: zustandIcon.height, + // image: zustandShare.src, + // docs: 'pmndrs/zustand/main/docs', + // }, + // jotai: { + // title: 'Jotai', + // url: 'https://jotai.org/docs/introduction', + // github: 'https://github.com/pmndrs/jotai', + // description: 'Jotai is a primitive and flexible state management library for React', + // icon: jotaiIcon.src, + // iconWidth: jotaiIcon.width, + // iconHeight: jotaiIcon.height, + // }, + // valtio: { + // title: 'Valtio', + // url: 'https://valtio.pmnd.rs', + // github: 'https://github.com/pmndrs/valtio', + // description: 'Valtio makes proxy-state simple for React and Vanilla', + // }, + // a11y: { + // title: 'A11y', + // url: '/a11y', + // github: 'https://github.com/pmndrs/react-three-a11y', + // description: + // '@react-three/a11y brings accessibility to webGL with easy-to-use react-three-fiber components', + // image: reactThreeA11yShare.src, + // docs: 'pmndrs/react-three-a11y/main/docs', + // }, + // 'react-postprocessing': { + // title: 'React Postprocessing', + // url: '/react-postprocessing', + // github: 'https://github.com/pmndrs/react-postprocessing', + // description: 'React Postprocessing is a postprocessing wrapper for @react-three/fiber', + // image: reactPostprocessingShare.src, + // docs: 'pmndrs/react-postprocessing/master/docs', + // }, + // uikit: { + // title: 'uikit', + // url: '/uikit', + // github: 'https://github.com/pmndrs/uikit', + // description: 'uikit brings user interfaces to @react-three/fiber', + // docs: 'pmndrs/uikit/main/docs', + // }, + // xr: { + // title: 'xr', + // url: '/xr', + // github: 'https://github.com/pmndrs/xr', + // description: 'VR/AR for @react-three/fiber', + // docs: 'pmndrs/xr/main/docs', + // }, +} satisfies Record + +export type Lib = keyof typeof libraries export default libraries diff --git a/src/hooks/useCSB.ts b/src/hooks/useCSB.ts deleted file mode 100644 index f958c202..00000000 --- a/src/hooks/useCSB.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react' - -export interface CSB { - title: string - description: string - content: string - screenshot_url: string - tags: string[] -} - -export const CSBContext = React.createContext>({}) - -export async function fetchCSB(ids: string[]) { - const boxes: Record = {} - - const slimData = await fetch('https://codesandbox.io/api/v1/sandboxes/mslim', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ ids }), - }).then((res) => res.json()) - - for (const { id, title } of slimData) { - boxes[id] = { - title, - description: '', - content: '', - screenshot_url: `https://codesandbox.io/api/v1/sandboxes/${id}/screenshot.png`, - tags: [], - } - } - - return boxes -} - -export function useCSB() { - return React.useContext(CSBContext) -} diff --git a/src/hooks/useDocs.ts b/src/hooks/useDocs.ts deleted file mode 100644 index e504a12a..00000000 --- a/src/hooks/useDocs.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react' -import type { MDXRemoteSerializeResult } from 'next-mdx-remote' - -export interface DocToC { - id: string - level: number - title: string - description: string - content: string - url: string - parent: DocToC | null - label: string -} - -export interface Doc { - slug: string[] - url: string - editURL: string - nav: number - title: string - description: string - source: MDXRemoteSerializeResult - boxes: string[] - tableOfContents: DocToC[] -} - -export const DocsContext = React.createContext(null!) - -export function useDocs() { - return React.useContext(DocsContext) -} diff --git a/src/lib/createRequiredContext.tsx b/src/lib/createRequiredContext.tsx new file mode 100644 index 00000000..327c86d5 --- /dev/null +++ b/src/lib/createRequiredContext.tsx @@ -0,0 +1,23 @@ +import { createContext, useContext } from 'react' + +// +// Create a strongly typed context and a hook +// +// see: https://www.totaltypescript.com/workshops/advanced-react-with-typescript/advanced-hooks/strongly-typing-react-context/solution +// + +export const createRequiredContext = () => { + const Ctx = createContext(null) + + const useCtx = () => { + const contextValue = useContext(Ctx) + + if (contextValue === null) { + throw new Error('Context value is null') + } + + return contextValue + } + + return [useCtx, Ctx.Provider] as const +} diff --git a/src/pages/[...slug].tsx b/src/pages/[...slug].tsx deleted file mode 100644 index 53cdee96..00000000 --- a/src/pages/[...slug].tsx +++ /dev/null @@ -1,106 +0,0 @@ -import * as React from 'react' -import type { GetStaticProps } from 'next' -import type libs from 'data/libraries' -import Layout from 'components/Layout' -import SEO from 'components/Seo' -import Post from 'components/Post' -import { Doc, DocsContext } from 'hooks/useDocs' -import { type CSB, CSBContext, fetchCSB } from 'hooks/useCSB' -import { getDocs } from 'utils/docs' - -export interface PostPageProps { - docs: Doc[] - doc: Doc - boxes: Record -} - -export default function PostPage({ docs, doc, boxes }: PostPageProps) { - return ( - - - - -
-
-

{doc.title}

- {!!doc.description?.length && ( -

{doc.description}

- )} -
-
- -
-
-
-
-
- ) -} - -export const getStaticProps: GetStaticProps = async ({ params }) => { - const slug = params!.slug as string[] - const lib = slug[0] as keyof typeof libs - - const allDocs = await getDocs(lib, false) - // console.log('allDocs', allDocs) - if (!allDocs?.length) return { notFound: true } - - const url = `/${slug.join('/')}`.toLowerCase() - const theDoc = allDocs.find((doc) => doc.url === url) - - if (!theDoc) { - const alternate = allDocs.find((doc) => doc.url.startsWith(url)) - return alternate - ? { redirect: { permanent: false, destination: alternate.url } } - : { notFound: true } - } - - const boxes = await fetchCSB(allDocs.flatMap((doc) => doc.boxes)) - - // - // Clean useless datas - // - - // - // step 1. Clean `docs`: no source / no tableOfContents (will be provided by `doc` itself) - // - const docs = allDocs.map((doc) => ({ - ...doc, - source: null!, - tableOfContents: [], - })) - // console.log('docs', JSON.stringify(docs, null, 2)) - - // - // Step 2. Clean `doc`: tableOfContents[].content|description => useless - // - const noContentNoDesc = (tocItem: Doc['tableOfContents'][number]) => { - // Recursively clear parent - if (tocItem.parent) tocItem.parent = noContentNoDesc({ ...tocItem.parent }) - - return { - ...tocItem, - content: 'NO_CONTENT', - description: 'NO_DESC', - } - } - const doc = { - ...theDoc, - tableOfContents: theDoc.tableOfContents.map(noContentNoDesc), // Clear all content - } - // console.log('doc', JSON.stringify(doc, null, 2)) - - return { - props: { - docs, - doc, - boxes, - }, - revalidate: 3600, - } -} - -export const getStaticPaths = async () => { - const paths = (await getDocs(undefined, true)).map(({ slug }) => ({ params: { slug } })) - return { paths, fallback: 'blocking' } -} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx deleted file mode 100644 index 57178e39..00000000 --- a/src/pages/_app.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import type { AppProps } from 'next/app' -import './main.css' -import './pmndrs.css' - -export default function App({ Component, pageProps }: AppProps) { - return -} diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx deleted file mode 100644 index 64b843a7..00000000 --- a/src/pages/_document.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Head, Html, Main, NextScript } from 'next/document' - -export default function Document() { - return ( - - - - - - -