replace old docs

This commit is contained in:
MapleLeaf
2022-01-03 04:20:29 -06:00
committed by Darius
parent 557fb4f8dc
commit e46b62f888
63 changed files with 67 additions and 1113 deletions

View File

@@ -1,19 +1,28 @@
{
"name": "reacord-docs",
"name": "reacord-docs-new",
"type": "module",
"private": true,
"main": "./src/main.tsx",
"scripts": {
"dev": "esmo server.ts",
"build": "vite build && vite build --ssr",
"start": "NODE_ENV=production esmo server.ts",
"dev": "esmo --no-warnings scripts/dev.ts | pino-colada",
"serve": "esmo --experimental-import-meta-resolve --no-warnings --enable-source-maps src/main.tsx",
"start": "NODE_ENV=production pnpm serve",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@heroicons/react": "^1.0.5",
"@reach/rect": "^0.16.0",
"@tinyhttp/app": "^2.0.15",
"@tinyhttp/logger": "^1.3.0",
"clsx": "^1.1.1",
"esbuild": "^0.14.10",
"express": "^4.17.2",
"gray-matter": "^4.0.3",
"http-terminator": "^3.0.4",
"pino": "^7.6.2",
"pino-colada": "^2.2.2",
"pino-http": "^6.5.0",
"pino-pretty": "^7.3.0",
"reacord": "workspace:*",
"react": "^18.0.0-rc.0",
"react-dom": "^18.0.0-rc.0",
@@ -21,29 +30,54 @@
"react-head": "^3.4.0",
"react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"rehype-stringify": "^9.0.2",
"reload": "^3.2.0",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"shrink-ray-current": "^4.1.3",
"sirv": "^2.0.0",
"unified": "^10.1.1",
"unified-stream": "^2.0.0",
"vite-plugin-ssr": "^0.3.42"
},
"devDependencies": {
"@mapbox/rehype-prism": "^0.8.0",
"@tailwindcss/typography": "^0.5.0",
"@types/browser-sync": "^2.26.3",
"@types/compression": "^1.7.2",
"@types/express": "^4.17.13",
"@types/markdown-it": "^12.2.3",
"@types/node": "*",
"@types/nodemon": "^1.19.1",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.9",
"@types/update-notifier": "^5.1.0",
"@types/wait-on": "^5.3.1",
"@vitejs/plugin-react": "^1.1.3",
"autoprefixer": "^10.4.1",
"browser-sync": "^2.27.7",
"chokidar": "^3.5.2",
"compression": "^1.7.4",
"debounce-fn": "^5.1.0",
"esno": "^0.13.0",
"execa": "^6.0.0",
"fast-glob": "^3.2.7",
"markdown-it": "^12.3.0",
"markdown-it-prism": "^2.2.1",
"nodemon": "^2.0.15",
"npm-run-all": "^4.1.5",
"polka": "^0.5.2",
"postcss": "^8.4.5",
"rehype-highlight": "^5.0.2",
"rehype-prism-plus": "^1.1.3",
"remark-frontmatter": "^4.0.1",
"rxjs": "^7.5.1",
"tailwindcss": "^3.0.8",
"type-fest": "^2.8.0",
"typescript": "^4.5.4",
"update-notifier": "^5.1.0",
"vite": "^2.7.10",
"vite-plugin-markdown": "^2.0.2"
"vite-plugin-markdown": "^2.0.2",
"wait-on": "^6.0.0"
}
}

View File

@@ -1,40 +0,0 @@
import { unlink, writeFile } from "node:fs/promises"
import type { Plugin } from "vite"
import { transformWithEsbuild } from "vite"
const prevalPattern = /\.preval\.(js|ts|jsx|tsx|mts|cts)$/
export function preval(): Plugin {
return {
name: "preval",
enforce: "pre",
async transform(code, filePath) {
if (!prevalPattern.test(filePath)) return
const tempFilePath = `${filePath}.preval.mjs`
try {
const transformResult = await transformWithEsbuild(code, filePath, {
target: "node16",
format: "esm",
})
await writeFile(tempFilePath, transformResult.code)
const runResult = await import(tempFilePath)
const serialized = Object.entries(runResult)
.map(([key, value]) => {
return key === "default"
? `export default ${JSON.stringify(value)}`
: `export const ${key} = ${JSON.stringify(value)}`
})
.join("\n")
return serialized + "\n"
} finally {
await unlink(tempFilePath).catch(console.warn)
}
},
}
}

View File

@@ -0,0 +1,85 @@
import browserSync from "browser-sync"
import chokidar from "chokidar"
import type { ExecaChildProcess } from "execa"
import { execa } from "execa"
import pino from "pino"
import { concatMap, debounceTime, map, Observable, tap } from "rxjs"
import waitOn from "wait-on"
import packageJson from "../package.json"
const console = pino()
let app: ExecaChildProcess | undefined
async function stopApp() {
if (app) {
if (app.pid != undefined) {
process.kill(-app.pid, "SIGINT")
} else {
app.kill("SIGINT")
}
await new Promise((resolve) => app?.once("close", resolve))
}
}
async function startApp() {
console.info(app ? "Restarting app..." : "Starting app...")
await stopApp()
const [command, ...flags] = packageJson.scripts.serve.split(/\s+/)
app = execa(command!, flags, {
stdio: "inherit",
detached: true,
})
app.catch((error) => {
if (error.signal !== "SIGINT") {
console.error(error)
}
})
await waitOn({ resources: ["http-get://localhost:3000"] })
console.info("App running")
}
const browser = browserSync.create()
process.on("SIGINT", async () => {
console.info("Shutting down...")
await stopApp()
browser.exit()
process.exit()
})
await startApp()
browser.emitter.on("init", () => {
console.info("Browsersync started")
})
browser.emitter.on("browser:reload", () => {
console.info("Browser reloaded")
})
browser.init({
proxy: "http://localhost:3000",
port: 3001,
ui: false,
logLevel: "silent",
})
new Observable<string>((subscriber) => {
chokidar
.watch(".", { ignored: /node_modules/, ignoreInitial: true })
.on("all", (_, path) => subscriber.next(path))
})
.pipe(
tap((path) => console.info(`Changed:`, path)),
debounceTime(100),
concatMap(startApp),
map(() => browser.reload()),
)
.subscribe()
// chokidar.watch(".", { ignored: /node_modules/, ignoreInitial: true }).on('all', )

View File

@@ -1,42 +0,0 @@
import compression from "compression"
import express from "express"
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
import { createPageRenderer } from "vite-plugin-ssr"
const isProduction = process.env.NODE_ENV === "production"
const root = dirname(fileURLToPath(import.meta.url))
const app = express()
app.use(compression())
let viteDevServer
if (isProduction) {
app.use(express.static(join(root, "dist/client")))
} else {
const vite = await import("vite")
viteDevServer = await vite.createServer({
root,
server: { middlewareMode: "ssr" },
})
app.use(viteDevServer.middlewares)
}
const renderPage = createPageRenderer({ viteDevServer, isProduction, root })
app.get("*", async (req, res, next) => {
const url = req.originalUrl
const pageContextInit = {
url,
}
const pageContext = await renderPage(pageContextInit)
const { httpResponse } = pageContext
if (!httpResponse) return next()
const { body, statusCode, contentType } = httpResponse
res.status(statusCode).type(contentType).send(body)
})
const port = process.env.PORT || 3000
app.listen(port, () => {
console.info(`Server running at http://localhost:${port}`)
})

View File

@@ -1,25 +0,0 @@
import React from "react"
import { createRoot } from "react-dom"
import { HeadProvider } from "react-head"
import type { PageContextBuiltInClient } from "vite-plugin-ssr/client"
import { getPage } from "vite-plugin-ssr/client"
import { App } from "./app"
import { RouteContextProvider } from "./route-context"
const context = await getPage<PageContextBuiltInClient>()
createRoot(document.querySelector("#app")!).render(
<HeadProvider>
<RouteContextProvider value={{ routeParams: {}, ...(context as any) }}>
<App>
<context.Page />
</App>
</RouteContextProvider>
</HeadProvider>,
)
declare module "react-dom" {
export function createRoot(element: Element): {
render(element: React.ReactNode): void
}
}

View File

@@ -1,49 +0,0 @@
import React from "react"
import { renderToStaticMarkup, renderToString } from "react-dom/server.js"
import { HeadProvider } from "react-head"
import type { PageContextBuiltIn } from "vite-plugin-ssr"
import { dangerouslySkipEscape, escapeInject } from "vite-plugin-ssr"
import { App } from "./app"
import { RouteContextProvider } from "./route-context"
export const passToClient = ["routeParams", "pageData"]
export function render(context: PageContextBuiltIn) {
const headTags: React.ReactElement[] = []
const pageHtml = renderToString(
<HeadProvider headTags={headTags}>
<RouteContextProvider value={context}>
<App>
<context.Page />
</App>
</RouteContextProvider>
</HeadProvider>,
)
const documentHtml = escapeInject/* HTML */ `
<!DOCTYPE html>
<html lang="en" class="bg-slate-900 text-slate-100">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet"
/>
${dangerouslySkipEscape(renderToStaticMarkup(<>{headTags}</>))}
</head>
<body>
<div id="app" class="contents">${dangerouslySkipEscape(pageHtml)}</div>
</body>
</html>
`
return { documentHtml }
}

View File

@@ -1,14 +0,0 @@
import { description } from "reacord/package.json"
import { Meta, Title } from "react-head"
import "tailwindcss/tailwind.css"
import "./styles/prism-theme.css"
export function App({ children }: { children: React.ReactNode }) {
return (
<>
<Title>Reacord</Title>
<Meta name="description" content={description} />
{children}
</>
)
}

View File

@@ -1,3 +1,4 @@
import React from "react"
import { ExternalLink } from "./external-link"
export type AppLinkProps = {

View File

@@ -1,4 +1,5 @@
import type { ComponentPropsWithoutRef } from "react"
import React from "react"
export function ExternalLink(props: ComponentPropsWithoutRef<"a">) {
return (

View File

@@ -1,5 +1,6 @@
import clsx from "clsx"
import { guideLinks } from "../data/guide-links.preval"
import React from "react"
import { guideLinks } from "../data/guide-links"
import { useScrolled } from "../hooks/dom/use-scrolled"
import {
docsProseClass,

View File

@@ -0,0 +1,23 @@
import * as React from "react"
import { mainLinks } from "../data/main-links"
import type { AppLinkProps } from "./app-link"
import { AppLink } from "./app-link"
import { PopoverMenu } from "./popover-menu"
export type MainNavigationMobileMenuData = {
guideLinks: AppLinkProps[]
}
export function render(data: MainNavigationMobileMenuData) {
return (
<PopoverMenu>
{mainLinks.map((link) => (
<AppLink {...link} key={link.to} className={PopoverMenu.itemClass} />
))}
<hr className="border-0 h-[2px] bg-black/50" />
{data.guideLinks.map((link) => (
<AppLink {...link} key={link.to} className={PopoverMenu.itemClass} />
))}
</PopoverMenu>
)
}

View File

@@ -1,4 +1,5 @@
import { guideLinks } from "../data/guide-links.preval"
import React from "react"
import { guideLinks } from "../data/guide-links"
import { mainLinks } from "../data/main-links"
import { linkClass } from "../styles/components"
import { AppLink } from "./app-link"
@@ -15,7 +16,7 @@ export function MainNavigation() {
<AppLink {...link} key={link.to} className={linkClass} />
))}
</div>
<div className="md:hidden">
<div className="md:hidden" id="main-navigation-popover">
<PopoverMenu>
{mainLinks.map((link) => (
<AppLink

View File

@@ -0,0 +1,38 @@
import clsx from "clsx"
const menus = document.querySelectorAll("[data-popover]")
for (const menu of menus) {
const button = menu.querySelector<HTMLButtonElement>("[data-popover-button]")!
const panel = menu.querySelector<HTMLDivElement>("[data-popover-panel]")!
const panelClasses = clsx`${panel.className} transition-all`
const visibleClass = clsx`${panelClasses} visible opacity-100 translate-y-0`
const hiddenClass = clsx`${panelClasses} invisible opacity-0 translate-y-2`
let visible = false
const setVisible = (newVisible: boolean) => {
visible = newVisible
panel.className = visible ? visibleClass : hiddenClass
if (!visible) return
requestAnimationFrame(() => {
const handleClose = (event: MouseEvent) => {
if (panel.contains(event.target as Node)) return
setVisible(false)
window.removeEventListener("click", handleClose)
}
window.addEventListener("click", handleClose)
})
}
const toggleVisible = () => setVisible(!visible)
button.addEventListener("click", toggleVisible)
setVisible(false)
panel.hidden = false
}

View File

@@ -1,63 +1,22 @@
import { MenuAlt4Icon } from "@heroicons/react/outline"
import { useRect } from "@reach/rect"
import clsx from "clsx"
import { useRef, useState } from "react"
import { FocusOn } from "react-focus-on"
import React from "react"
import { linkClass } from "../styles/components"
// todo: remove useRect usage and rely on css absolute positioning instead
export function PopoverMenu({ children }: { children: React.ReactNode }) {
const [visible, setVisible] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const buttonRect = useRect(buttonRef)
const panelRef = useRef<HTMLDivElement>(null)
const panelRect = useRect(panelRef)
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
return (
<>
<button
title="Menu"
className={linkClass}
onClick={() => setVisible(!visible)}
ref={buttonRef}
>
<div data-popover className="relative">
<button data-popover-button title="Menu" className={linkClass}>
<MenuAlt4Icon className="w-6" />
</button>
<FocusOn
enabled={visible}
onClickOutside={() => setVisible(false)}
onEscapeKey={() => setVisible(false)}
<div
data-popover-panel
hidden
className="absolute w-48 bg-slate-800 rounded-lg shadow overflow-hidden max-h-[calc(100vh-4rem)] overflow-y-auto right-0 top-[calc(100%+8px)]"
>
<div
className="fixed"
style={{
left: (buttonRect?.right ?? 0) - (panelRect?.width ?? 0),
top: (buttonRect?.bottom ?? 0) + 8,
}}
onClick={() => setVisible(false)}
>
<div
className={clsx(
"transition-all",
visible
? "opacity-100 visible"
: "translate-y-2 opacity-0 invisible",
)}
>
<div ref={panelRef}>
<div className="w-48 bg-slate-800 rounded-lg shadow overflow-hidden max-h-[calc(100vh-4rem)] overflow-y-auto">
{children}
</div>
</div>
</div>
</div>
</FocusOn>
</>
{children}
</div>
</div>
)
}

View File

@@ -0,0 +1,16 @@
import type { ComponentPropsWithoutRef } from "react"
import React from "react"
import type { Merge } from "type-fest"
export function Script({
children,
...props
}: Merge<ComponentPropsWithoutRef<"script">, { children: string }>) {
return (
<script
type="module"
dangerouslySetInnerHTML={{ __html: children }}
{...props}
/>
)
}

View File

@@ -3,6 +3,7 @@ import {
DocumentTextIcon,
ExternalLinkIcon,
} from "@heroicons/react/solid"
import React from "react"
import type { AppLinkProps } from "../components/app-link"
import { inlineIconClass } from "../styles/components"

View File

@@ -0,0 +1,51 @@
import { build } from "esbuild"
import { readFile } from "node:fs/promises"
import { dirname } from "node:path"
import React from "react"
import { Script } from "../components/script"
let nextId = 0
export async function createHydrater<Data>(scriptFilePath: string) {
const id = `hydrate-root-${nextId}`
nextId += 1
const scriptSource = await readFile(scriptFilePath, "utf-8")
const scriptBuild = await build({
bundle: true,
stdin: {
contents: [scriptSource, clientBootstrap(id)].join(";\n"),
sourcefile: scriptFilePath,
loader: "tsx",
resolveDir: dirname(scriptFilePath),
},
target: ["chrome89", "firefox89"],
format: "esm",
write: false,
})
const serverModule = await import(scriptFilePath)
return function Hydrater({ data }: { data: Data }) {
return (
<>
<div id={id} data-server-data={JSON.stringify(data)}>
{serverModule.render(data)}
</div>
<Script>{scriptBuild.outputFiles[0]?.text!}</Script>
</>
)
}
}
function clientBootstrap(id: string) {
return /* ts */ `
import { createRoot } from "react-dom"
const rootElement = document.querySelector("#${id}")
const data = JSON.parse(rootElement.dataset.serverData)
createRoot(rootElement).render(render(data))
`
}

View File

@@ -1,11 +0,0 @@
import { lazy } from "react"
export function lazyNamed<
Key extends string,
Component extends React.ComponentType,
>(key: Key, loadModule: () => Promise<Record<Key, Component>>) {
return lazy<Component>(async () => {
const mod = await loadModule()
return { default: mod[key] }
})
}

View File

@@ -0,0 +1,15 @@
import grayMatter from "gray-matter"
import MarkdownIt from "markdown-it"
import prism from "markdown-it-prism"
import { readFile } from "node:fs/promises"
const renderer = new MarkdownIt({
html: true,
linkify: true,
}).use(prism)
export async function renderMarkdownFile(filePath: string) {
const { data, content } = grayMatter(await readFile(filePath, "utf8"))
const html = renderer.render(content)
return { html, data }
}

View File

@@ -0,0 +1,7 @@
import type { Response } from "express"
import { renderToStaticMarkup } from "react-dom/server.js"
export function sendJsx(res: Response, jsx: React.ReactElement) {
res.set("Content-Type", "text/html")
res.send(`<!DOCTYPE html>\n${renderToStaticMarkup(jsx)}`)
}

View File

@@ -0,0 +1,26 @@
import { build } from "esbuild"
import type { RequestHandler } from "express"
import { readFile } from "node:fs/promises"
import { dirname } from "node:path"
export async function serveCompiledScript(
scriptFilePath: string,
): Promise<RequestHandler> {
const scriptBuild = await build({
bundle: true,
stdin: {
contents: await readFile(scriptFilePath, "utf-8"),
sourcefile: scriptFilePath,
loader: "tsx",
resolveDir: dirname(scriptFilePath),
},
target: ["chrome89", "firefox89"],
format: "esm",
write: false,
})
return (req, res) => {
res.setHeader("Content-Type", "application/javascript")
res.end(scriptBuild.outputFiles[0]!.contents)
}
}

View File

@@ -0,0 +1,5 @@
import type { RequestHandler } from "express"
export function serveFile(path: string): RequestHandler {
return (req, res) => res.sendFile(path)
}

View File

@@ -0,0 +1,25 @@
import type { RequestHandler } from "express"
import { readFile } from "node:fs/promises"
import { fileURLToPath } from "node:url"
import type { Result } from "postcss"
import postcss from "postcss"
import tailwindcss from "tailwindcss"
const tailwindTemplatePath = fileURLToPath(
await import.meta.resolve!("tailwindcss/tailwind.css"),
)
const tailwindTemplate = await readFile(tailwindTemplatePath, "utf-8")
let result: Result | undefined
export function serveTailwindCss(): RequestHandler {
return async (req, res) => {
if (!result || process.env.NODE_ENV !== "production") {
result = await postcss(tailwindcss).process(tailwindTemplate, {
from: tailwindTemplatePath,
})
}
res.set("Content-Type", "text/css").send(result.css)
}
}

View File

@@ -2,7 +2,9 @@ import { useState } from "react"
import { useWindowEvent } from "./use-window-event"
export function useScrolled() {
const [scrolled, setScrolled] = useState(false)
const [scrolled, setScrolled] = useState(
typeof window !== "undefined" ? window.scrollY > 0 : false,
)
useWindowEvent("scroll", () => setScrolled(window.scrollY > 0))
return scrolled
}

View File

@@ -0,0 +1,41 @@
import packageJson from "reacord/package.json"
import type { ReactNode } from "react"
import React from "react"
export function Html({
title = "Reacord",
description = packageJson.description,
children,
}: {
title?: string
description?: string
children: ReactNode
}) {
return (
<html lang="en" className="bg-slate-900 text-slate-100">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content={description} />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin=""
/>
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet"
/>
<link href="/tailwind.css" rel="stylesheet" />
<link href="/prism-theme.css" rel="stylesheet" />
<script type="module" src="/popover-menu.client.js" />
<title>{title}</title>
</head>
<body>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,72 @@
import compression from "compression"
import type { Request } from "express"
import express from "express"
import httpTerminator from "http-terminator"
import pino from "pino"
import pinoHttp from "pino-http"
import * as React from "react"
import { renderMarkdownFile } from "./helpers/markdown"
import { sendJsx } from "./helpers/send-jsx"
import { serveCompiledScript } from "./helpers/serve-compiled-script"
import { serveFile } from "./helpers/serve-file"
import { serveTailwindCss } from "./helpers/tailwind"
import DocsPage from "./pages/docs"
import { Landing } from "./pages/landing"
const logger = pino()
const port = process.env.PORT || 3000
const app = express()
.use(pinoHttp({ logger }))
.use(compression())
.get("/tailwind.css", serveTailwindCss())
.get(
"/prism-theme.css",
serveFile(new URL("./styles/prism-theme.css", import.meta.url).pathname),
)
.get(
"/popover-menu.client.js",
await serveCompiledScript(
new URL("./components/popover-menu.client.tsx", import.meta.url).pathname,
),
)
.get("/docs/*", async (req: Request<{ 0: string }>, res) => {
const { html, data } = await renderMarkdownFile(
`src/docs/${req.params[0]}.md`,
)
sendJsx(
res,
<DocsPage
title={data.title}
description={data.description}
html={html}
/>,
)
})
.get("/", (req, res) => {
sendJsx(res, <Landing />)
})
const server = app.listen(port, () => {
logger.info(`Server is running on https://localhost:${port}`)
})
const terminator = httpTerminator.createHttpTerminator({ server })
process.on("SIGINT", () => {
terminator
.terminate()
.then(() => {
logger.info("Server terminated")
})
.catch((error) => {
logger.error(error)
})
.finally(() => {
process.exit()
})
})

View File

@@ -1 +0,0 @@
export default "/docs/*"

View File

@@ -1,23 +0,0 @@
import type { OnBeforeRenderFn } from "../router-types"
export type DocsPageProps = {
title?: string
description?: string
content: string
}
export const onBeforeRender: OnBeforeRenderFn<DocsPageProps> = async (
context,
) => {
const documentPath = context.routeParams["*"]
const document = await import(`../docs/${documentPath}.md`)
return {
pageContext: {
pageData: {
title: document.attributes.title,
description: document.attributes.description,
content: document.html,
},
},
}
}

View File

@@ -1,28 +1,31 @@
import clsx from "clsx"
import { Meta, Title } from "react-head"
import React from "react"
import { AppLink } from "../components/app-link"
import { MainNavigation } from "../components/main-navigation"
import { guideLinks } from "../data/guide-links.preval"
import { useScrolled } from "../hooks/dom/use-scrolled"
import { usePageData } from "../route-context"
import { guideLinks } from "../data/guide-links"
import { Html } from "../html"
import {
docsProseClass,
linkClass,
maxWidthContainer,
} from "../styles/components"
import type { DocsPageProps } from "./docs.page.server"
export default function DocsPage() {
const data = usePageData<DocsPageProps>()
export default function DocsPage({
title,
description,
html,
}: {
title: string
description: string
html: string
}) {
return (
<>
<Title>{data.title} | Reacord</Title>
<Meta name="description" content={data.description} />
<HeaderPanel>
<Html title={`${title} | Reacord`} description={description}>
<header className="bg-slate-700/30 shadow sticky top-0 backdrop-blur-sm transition z-10 flex">
<div className={maxWidthContainer}>
<MainNavigation />
</div>
</HeaderPanel>
</header>
<main className={clsx(maxWidthContainer, "mt-8 flex items-start gap-4")}>
<nav className="w-48 sticky top-24 hidden md:block">
<h2 className="text-2xl">Guides</h2>
@@ -36,20 +39,9 @@ export default function DocsPage() {
</nav>
<section
className={clsx(docsProseClass, "pb-8 flex-1 min-w-0")}
dangerouslySetInnerHTML={{ __html: data.content }}
dangerouslySetInnerHTML={{ __html: html }}
/>
</main>
</>
</Html>
)
}
function HeaderPanel({ children }: { children: React.ReactNode }) {
const isScrolled = useScrolled()
const className = clsx(
isScrolled ? "bg-slate-700/30" : "bg-slate-800",
"shadow sticky top-0 backdrop-blur-sm transition z-10 flex",
)
return <header className={className}>{children}</header>
}

View File

@@ -1,30 +0,0 @@
import packageJson from "reacord/package.json"
import { html as landingExampleHtml } from "../components/landing-example.md"
import { MainNavigation } from "../components/main-navigation"
import { maxWidthContainer } from "../styles/components"
export default function LandingPage() {
return (
<div className="flex flex-col min-w-0 min-h-screen text-center">
<header className={maxWidthContainer}>
<MainNavigation />
</header>
<div className="px-4 pb-8 flex flex-1">
<main className="px-4 py-6 rounded-lg shadow bg-slate-800 space-y-5 m-auto w-full max-w-xl">
<h1 className="text-6xl font-light">reacord</h1>
<section
className="mx-auto text-sm sm:text-base"
dangerouslySetInnerHTML={{ __html: landingExampleHtml }}
/>
<p className="text-2xl font-light">{packageJson.description}</p>
<a
href="/docs/getting-started"
className="inline-block px-4 py-3 text-xl transition rounded-lg bg-emerald-700 hover:translate-y-[-2px] hover:bg-emerald-800 hover:shadow"
>
Get Started
</a>
</main>
</div>
</div>
)
}

View File

@@ -0,0 +1,38 @@
import packageJson from "reacord/package.json"
import React from "react"
import { MainNavigation } from "../components/main-navigation"
import { renderMarkdownFile } from "../helpers/markdown"
import { Html } from "../html"
import { maxWidthContainer } from "../styles/components"
const landingExample = await renderMarkdownFile(
new URL("../components/landing-example.md", import.meta.url).pathname,
)
export function Landing() {
return (
<Html>
<div className="flex flex-col min-w-0 min-h-screen text-center">
<header className={maxWidthContainer}>
<MainNavigation />
</header>
<div className="px-4 pb-8 flex flex-1">
<main className="px-4 py-6 rounded-lg shadow bg-slate-800 space-y-5 m-auto w-full max-w-xl">
<h1 className="text-6xl font-light">reacord</h1>
<section
className="mx-auto text-sm sm:text-base"
dangerouslySetInnerHTML={{ __html: landingExample.html }}
/>
<p className="text-2xl font-light">{packageJson.description}</p>
<a
href="/docs/getting-started"
className="inline-block px-4 py-3 text-xl transition rounded-lg bg-emerald-700 hover:translate-y-[-2px] hover:bg-emerald-800 hover:shadow"
>
Get Started
</a>
</main>
</div>
</div>
</Html>
)
}

View File

@@ -2,3 +2,9 @@ import "react"
declare module "react" {
export function createContext<Value>(): Context<Value | undefined>
}
declare module "react-dom" {
export function createRoot(element: Element): {
render(element: React.ReactNode): void
}
}

5
packages/docs/src/reload.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module "reload" {
import type { Express } from "express"
function reload(server: Express): Promise<void>
export = reload
}

View File

@@ -1,18 +0,0 @@
import { createContext, useContext } from "react"
export type RouteContextValue = {
routeParams: Record<string, string>
pageData?: Record<string, unknown>
}
const Context = createContext<RouteContextValue>()
export const RouteContextProvider = Context.Provider
export function useRouteParams() {
return useContext(Context)?.routeParams ?? {}
}
export function usePageData<T>() {
return useContext(Context)?.pageData as T
}

View File

@@ -1,10 +0,0 @@
import type { Promisable } from "type-fest"
import type { PageContextBuiltIn } from "vite-plugin-ssr"
export type OnBeforeRenderFn<PageProps> = (
context: PageContextBuiltIn,
) => Promisable<{
pageContext: {
pageData: PageProps
}
}>

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,8 +0,0 @@
/// <reference types="vite/client" />
declare module "*.md" {
import type { ComponentType } from "react"
export const attributes: Record<string, any>
export const html: string
export const ReactComponent: ComponentType
}

View File

@@ -1,6 +1,3 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx"
}
"extends": "../../tsconfig.base.json"
}

View File

@@ -1,35 +0,0 @@
import react from "@vitejs/plugin-react"
import MarkdownIt from "markdown-it"
import prism from "markdown-it-prism"
import { createRequire } from "node:module"
import { defineConfig } from "vite"
import type * as markdownType from "vite-plugin-markdown"
import ssr from "vite-plugin-ssr/plugin"
import { preval } from "./plugins/preval"
const require = createRequire(import.meta.url)
const markdown: typeof markdownType = require("vite-plugin-markdown")
export default defineConfig({
build: {
target: ["node16", "chrome89", "firefox89"],
},
plugins: [
ssr(),
react(),
markdown.default({
mode: [markdown.Mode.HTML],
markdownIt: new MarkdownIt({
html: true,
linkify: true,
}).use(prism),
}),
preval(),
],
resolve: {
alias: {
// https://github.com/brillout/vite-plugin-mdx/issues/44#issuecomment-974540152
"react/jsx-runtime": "react/jsx-runtime.js",
},
},
})