replace old docs
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import { ExternalLink } from "./external-link"
|
||||
|
||||
export type AppLinkProps = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ComponentPropsWithoutRef } from "react"
|
||||
import React from "react"
|
||||
|
||||
export function ExternalLink(props: ComponentPropsWithoutRef<"a">) {
|
||||
return (
|
||||
|
||||
@@ -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,
|
||||
|
||||
23
packages/docs/src/components/main-navigation-mobile-menu.tsx
Normal file
23
packages/docs/src/components/main-navigation-mobile-menu.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
38
packages/docs/src/components/popover-menu.client.tsx
Normal file
38
packages/docs/src/components/popover-menu.client.tsx
Normal 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
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
16
packages/docs/src/components/script.tsx
Normal file
16
packages/docs/src/components/script.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
51
packages/docs/src/helpers/hydration.tsx
Normal file
51
packages/docs/src/helpers/hydration.tsx
Normal 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))
|
||||
`
|
||||
}
|
||||
@@ -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] }
|
||||
})
|
||||
}
|
||||
15
packages/docs/src/helpers/markdown.ts
Normal file
15
packages/docs/src/helpers/markdown.ts
Normal 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 }
|
||||
}
|
||||
7
packages/docs/src/helpers/send-jsx.ts
Normal file
7
packages/docs/src/helpers/send-jsx.ts
Normal 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)}`)
|
||||
}
|
||||
26
packages/docs/src/helpers/serve-compiled-script.ts
Normal file
26
packages/docs/src/helpers/serve-compiled-script.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
5
packages/docs/src/helpers/serve-file.ts
Normal file
5
packages/docs/src/helpers/serve-file.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { RequestHandler } from "express"
|
||||
|
||||
export function serveFile(path: string): RequestHandler {
|
||||
return (req, res) => res.sendFile(path)
|
||||
}
|
||||
25
packages/docs/src/helpers/tailwind.ts
Normal file
25
packages/docs/src/helpers/tailwind.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
41
packages/docs/src/html.tsx
Normal file
41
packages/docs/src/html.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
72
packages/docs/src/main.tsx
Normal file
72
packages/docs/src/main.tsx
Normal 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()
|
||||
})
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
export default "/docs/*"
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
38
packages/docs/src/pages/landing.tsx
Normal file
38
packages/docs/src/pages/landing.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
6
packages/docs/src/react.d.ts
vendored
6
packages/docs/src/react.d.ts
vendored
@@ -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
5
packages/docs/src/reload.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module "reload" {
|
||||
import type { Express } from "express"
|
||||
function reload(server: Express): Promise<void>
|
||||
export = reload
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}>
|
||||
@@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
8
packages/docs/src/vite-env.d.ts
vendored
8
packages/docs/src/vite-env.d.ts
vendored
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user