From 498374195d0d90e8316f450cd9c4c16344392f1d Mon Sep 17 00:00:00 2001 From: BennyKok Date: Thu, 28 Dec 2023 00:06:32 +0800 Subject: [PATCH] feat(docs): add docs and restructure --- web/bun.lockb | Bin 342489 -> 447943 bytes web/drizzle.config.ts | 14 +- web/mdx-components.tsx | 9 + web/next.config.js | 8 - web/next.config.mjs | 23 + web/package.json | 35 +- web/src/app/{ => (app)}/api-keys/page.tsx | 0 .../app/{ => (app)}/api/file-upload/route.ts | 2 +- web/src/app/{ => (app)}/api/run/route.ts | 4 +- .../app/{ => (app)}/api/update-run/route.ts | 2 +- web/src/app/{ => (app)}/api/upload/route.ts | 2 +- web/src/app/{ => (app)}/api/view/route.ts | 2 +- web/src/app/{ => (app)}/favicon.ico | Bin web/src/app/{ => (app)}/globals.css | 18 + web/src/app/{ => (app)}/layout.tsx | 7 + web/src/app/{ => (app)}/machines/page.tsx | 0 web/src/app/{ => (app)}/page.tsx | 0 .../workflows/[workflow_id]/page.tsx | 4 +- web/src/app/{ => (app)}/workflows/page.tsx | 0 web/src/app/(docs)/docs/install/page.mdx | 49 ++ web/src/app/(docs)/docs/layout.tsx | 40 ++ web/src/app/(docs)/docs/not-found.tsx | 24 + web/src/app/(docs)/docs/page.mdx | 34 ++ web/src/app/(docs)/docs/providers.tsx | 37 ++ web/src/components/VersionSelect.tsx | 2 +- web/src/components/docs/Button.tsx | 82 +++ web/src/components/docs/Code.tsx | 380 +++++++++++++ web/src/components/docs/Feedback.tsx | 105 ++++ web/src/components/docs/Footer.tsx | 144 +++++ web/src/components/docs/GridPattern.tsx | 55 ++ web/src/components/docs/Guides.tsx | 54 ++ web/src/components/docs/Header.tsx | 99 ++++ web/src/components/docs/Heading.tsx | 116 ++++ web/src/components/docs/HeroPattern.tsx | 32 ++ web/src/components/docs/Layout.tsx | 52 ++ web/src/components/docs/Libraries.tsx | 81 +++ web/src/components/docs/Logo.tsx | 14 + web/src/components/docs/MobileNavigation.tsx | 173 ++++++ web/src/components/docs/Navigation.tsx | 265 +++++++++ web/src/components/docs/Prose.tsx | 24 + web/src/components/docs/Resources.tsx | 182 +++++++ web/src/components/docs/Search.tsx | 504 ++++++++++++++++++ web/src/components/docs/SectionProvider.tsx | 155 ++++++ web/src/components/docs/Tag.tsx | 63 +++ web/src/components/docs/ThemeToggle.tsx | 44 ++ web/src/components/docs/mdx.tsx | 144 +++++ web/src/components/ui/tabs.tsx | 27 +- web/src/lib/remToPx.ts | 8 + web/src/mdx/recma.mjs | 3 + web/src/mdx/rehype.mjs | 124 +++++ web/src/mdx/remark.mjs | 4 + web/src/mdx/search.mjs | 137 +++++ web/src/middleware.ts | 2 +- web/tailwind.config.ts | 52 +- web/types.d.ts | 11 + web/typography.ts | 353 ++++++++++++ 56 files changed, 3751 insertions(+), 54 deletions(-) create mode 100644 web/mdx-components.tsx delete mode 100644 web/next.config.js create mode 100644 web/next.config.mjs rename web/src/app/{ => (app)}/api-keys/page.tsx (100%) rename web/src/app/{ => (app)}/api/file-upload/route.ts (94%) rename web/src/app/{ => (app)}/api/run/route.ts (96%) rename web/src/app/{ => (app)}/api/update-run/route.ts (95%) rename web/src/app/{ => (app)}/api/upload/route.ts (98%) rename web/src/app/{ => (app)}/api/view/route.ts (79%) rename web/src/app/{ => (app)}/favicon.ico (100%) rename web/src/app/{ => (app)}/globals.css (75%) rename web/src/app/{ => (app)}/layout.tsx (92%) rename web/src/app/{ => (app)}/machines/page.tsx (100%) rename web/src/app/{ => (app)}/page.tsx (100%) rename web/src/app/{ => (app)}/workflows/[workflow_id]/page.tsx (93%) rename web/src/app/{ => (app)}/workflows/page.tsx (100%) create mode 100644 web/src/app/(docs)/docs/install/page.mdx create mode 100644 web/src/app/(docs)/docs/layout.tsx create mode 100644 web/src/app/(docs)/docs/not-found.tsx create mode 100644 web/src/app/(docs)/docs/page.mdx create mode 100644 web/src/app/(docs)/docs/providers.tsx create mode 100644 web/src/components/docs/Button.tsx create mode 100644 web/src/components/docs/Code.tsx create mode 100644 web/src/components/docs/Feedback.tsx create mode 100644 web/src/components/docs/Footer.tsx create mode 100644 web/src/components/docs/GridPattern.tsx create mode 100644 web/src/components/docs/Guides.tsx create mode 100644 web/src/components/docs/Header.tsx create mode 100644 web/src/components/docs/Heading.tsx create mode 100644 web/src/components/docs/HeroPattern.tsx create mode 100644 web/src/components/docs/Layout.tsx create mode 100644 web/src/components/docs/Libraries.tsx create mode 100644 web/src/components/docs/Logo.tsx create mode 100644 web/src/components/docs/MobileNavigation.tsx create mode 100644 web/src/components/docs/Navigation.tsx create mode 100644 web/src/components/docs/Prose.tsx create mode 100644 web/src/components/docs/Resources.tsx create mode 100644 web/src/components/docs/Search.tsx create mode 100644 web/src/components/docs/SectionProvider.tsx create mode 100644 web/src/components/docs/Tag.tsx create mode 100644 web/src/components/docs/ThemeToggle.tsx create mode 100644 web/src/components/docs/mdx.tsx create mode 100644 web/src/lib/remToPx.ts create mode 100644 web/src/mdx/recma.mjs create mode 100644 web/src/mdx/rehype.mjs create mode 100644 web/src/mdx/remark.mjs create mode 100644 web/src/mdx/search.mjs create mode 100644 web/types.d.ts create mode 100644 web/typography.ts diff --git a/web/bun.lockb b/web/bun.lockb index 3e3f3673cd327480659b74b3af483213cbf01844..bc1f764062f701b83b260017c04c36a46f50dd72 100755 GIT binary patch delta 139747 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

qdm8Uhx|Vc)c}K;hazZ4kKq>i+ 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

^)0OROlU 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 delta 71615 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

TU2u9sYbwZDB{{a+ZaqyxiDan-uk=6>#=E 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 diff --git a/web/drizzle.config.ts b/web/drizzle.config.ts index 9e8a158..ca865cc 100644 --- a/web/drizzle.config.ts +++ b/web/drizzle.config.ts @@ -1,15 +1,17 @@ -import type { Config } from 'drizzle-kit'; -import { config } from 'dotenv'; +import { config } from "dotenv"; +import type { Config } from "drizzle-kit"; config({ path: `.env.local`, }); export default { - schema: './src/db/schema.ts', - driver: 'pg', - out: './drizzle', + schema: "./src/db/schema.ts", + driver: "pg", + out: "./drizzle", dbCredentials: { - connectionString: (process.env.POSTGRES_URL as string) + ( process.env.POSTGRES_SSL !== "false" ? '?ssl=true' : ""), + connectionString: + (process.env.POSTGRES_URL as string) + + (process.env.POSTGRES_SSL !== "false" ? "?ssl=true" : ""), }, } satisfies Config; diff --git a/web/mdx-components.tsx b/web/mdx-components.tsx new file mode 100644 index 0000000..d3a29cc --- /dev/null +++ b/web/mdx-components.tsx @@ -0,0 +1,9 @@ +import * as mdxComponents from "@/components/docs/mdx"; +import { type MDXComponents } from "mdx/types"; + +export function useMDXComponents(components: MDXComponents) { + return { + ...components, + ...mdxComponents, + }; +} diff --git a/web/next.config.js b/web/next.config.js deleted file mode 100644 index 954fac0..0000000 --- a/web/next.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - }, -}; - -module.exports = nextConfig; diff --git a/web/next.config.mjs b/web/next.config.mjs new file mode 100644 index 0000000..eb8a553 --- /dev/null +++ b/web/next.config.mjs @@ -0,0 +1,23 @@ +import { recmaPlugins } from "./src/mdx/recma.mjs"; +import { rehypePlugins } from "./src/mdx/rehype.mjs"; +import { remarkPlugins } from "./src/mdx/remark.mjs"; +import withSearch from "./src/mdx/search.mjs"; +import nextMDX from "@next/mdx"; + +const withMDX = nextMDX({ + options: { + remarkPlugins, + rehypePlugins, + recmaPlugins, + }, +}); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + pageExtensions: ["js", "jsx", "ts", "tsx", "mdx"], + eslint: { + ignoreDuringBuilds: true, + }, +}; + +export default withSearch(withMDX(nextConfig)); diff --git a/web/package.json b/web/package.json index 262e558..a6dba84 100644 --- a/web/package.json +++ b/web/package.json @@ -15,11 +15,17 @@ "db-dev": "bun run db-up && bun run migrate-local" }, "dependencies": { + "@algolia/autocomplete-core": "^1.13.0", "@aws-sdk/client-s3": "^3.472.0", "@aws-sdk/s3-request-presigner": "^3.472.0", "@clerk/nextjs": "^4.27.4", + "@headlessui/react": "^1.7.17", + "@headlessui/tailwindcss": "^0.2.0", "@hookform/resolvers": "^3.3.2", + "@mdx-js/loader": "^3.0.0", + "@mdx-js/react": "^3.0.0", "@neondatabase/serverless": "^0.6.0", + "@next/mdx": "^14.0.4", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", @@ -34,41 +40,54 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", + "@sindresorhus/slugify": "^2.2.1", + "@tailwindcss/typography": "^0.5.10", "@tanstack/react-table": "^8.10.7", "@types/jsonwebtoken": "^9.0.5", + "@types/react-highlight-words": "^0.16.7", "@types/uuid": "^9.0.7", + "acorn": "^8.11.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "date-fns": "^3.0.5", "dayjs": "^1.11.10", "drizzle-orm": "^0.29.1", "drizzle-zod": "^0.5.1", + "fast-glob": "^3.3.2", + "flexsearch": "^0.7.31", + "framer-motion": "^10.16.16", "jsonwebtoken": "^9.0.2", "lucide-react": "^0.294.0", + "mdast-util-to-string": "^4.0.0", + "mdx-annotations": "^0.1.4", "nanoid": "^5.0.4", "next": "14.0.3", "next-plausible": "^3.12.0", + "next-themes": "^0.2.1", "next-usequerystate": "^1.13.2", "react": "^18", "react-day-picker": "^8.9.1", "react-dom": "^18", + "react-highlight-words": "^0.20.0", "react-hook-form": "^7.48.2", "react-use-websocket": "^4.5.0", + "remark": "^15.0.1", + "remark-gfm": "^4.0.0", + "remark-mdx": "^3.0.0", + "shiki": "^0.14.7", "shikiji": "^0.9.3", + "simple-functional-loader": "^1.2.1", "sonner": "^1.2.4", "swr": "^2.2.4", "tailwind-merge": "^2.1.0", "tailwindcss-animate": "^1.0.7", + "unist-util-filter": "^5.0.1", + "unist-util-visit": "^5.0.0", "uuid": "^9.0.1", "zod": "^3.22.4", "zustand": "^4.4.7" }, "devDependencies": { - "eslint-config-next": "^14.0.4", - "eslint-config-prettier": "^8.6.0", - "eslint-config-turbo": "latest", - "eslint-plugin-prettier": "4.2.1", - "prettier-plugin-tailwindcss": "0.2.5", "@trivago/prettier-plugin-sort-imports": "4.1.1", "@types/node": "^20", "@types/react": "^18", @@ -80,10 +99,16 @@ "dotenv": "^16.3.1", "drizzle-kit": "^0.20.6", "eslint": "8.34.0", + "eslint-config-next": "^14.0.4", + "eslint-config-prettier": "^8.6.0", + "eslint-config-turbo": "latest", + "eslint-plugin-prettier": "4.2.1", "eslint-plugin-unused-imports": "^3.0.0", "postcss": "^8", "postgres": "^3.4.3", "prettier": "2.8.6", + "prettier-plugin-tailwindcss": "0.2.5", + "sharp": "^0.33.1", "tailwindcss": "^3.3.0", "typescript": "^5" } diff --git a/web/src/app/api-keys/page.tsx b/web/src/app/(app)/api-keys/page.tsx similarity index 100% rename from web/src/app/api-keys/page.tsx rename to web/src/app/(app)/api-keys/page.tsx diff --git a/web/src/app/api/file-upload/route.ts b/web/src/app/(app)/api/file-upload/route.ts similarity index 94% rename from web/src/app/api/file-upload/route.ts rename to web/src/app/(app)/api/file-upload/route.ts index ea9e10b..8f5ba7b 100644 --- a/web/src/app/api/file-upload/route.ts +++ b/web/src/app/(app)/api/file-upload/route.ts @@ -1,4 +1,4 @@ -import { parseDataSafe } from "../../../lib/parseDataSafe"; +import { parseDataSafe } from "../../../../lib/parseDataSafe"; import { handleResourceUpload } from "@/server/resource"; import { NextResponse } from "next/server"; import { z } from "zod"; diff --git a/web/src/app/api/run/route.ts b/web/src/app/(app)/api/run/route.ts similarity index 96% rename from web/src/app/api/run/route.ts rename to web/src/app/(app)/api/run/route.ts index f15c31d..7b7372f 100644 --- a/web/src/app/api/run/route.ts +++ b/web/src/app/(app)/api/run/route.ts @@ -1,5 +1,5 @@ -import { parseDataSafe } from "../../../lib/parseDataSafe"; -import { createRun } from "../../../server/createRun"; +import { parseDataSafe } from "../../../../lib/parseDataSafe"; +import { createRun } from "../../../../server/createRun"; import { db } from "@/db/db"; import { deploymentsTable } from "@/db/schema"; import { isKeyRevoked } from "@/server/curdApiKeys"; diff --git a/web/src/app/api/update-run/route.ts b/web/src/app/(app)/api/update-run/route.ts similarity index 95% rename from web/src/app/api/update-run/route.ts rename to web/src/app/(app)/api/update-run/route.ts index b82b0ed..e712eb1 100644 --- a/web/src/app/api/update-run/route.ts +++ b/web/src/app/(app)/api/update-run/route.ts @@ -1,4 +1,4 @@ -import { parseDataSafe } from "../../../lib/parseDataSafe"; +import { parseDataSafe } from "../../../../lib/parseDataSafe"; import { db } from "@/db/db"; import { workflowRunOutputs, workflowRunsTable } from "@/db/schema"; import { eq } from "drizzle-orm"; diff --git a/web/src/app/api/upload/route.ts b/web/src/app/(app)/api/upload/route.ts similarity index 98% rename from web/src/app/api/upload/route.ts rename to web/src/app/(app)/api/upload/route.ts index 309e45f..f3325a8 100644 --- a/web/src/app/api/upload/route.ts +++ b/web/src/app/(app)/api/upload/route.ts @@ -1,4 +1,4 @@ -import { parseJWT } from "../../../server/parseJWT"; +import { parseJWT } from "../../../../server/parseJWT"; import { db } from "@/db/db"; import { workflowAPIType, diff --git a/web/src/app/api/view/route.ts b/web/src/app/(app)/api/view/route.ts similarity index 79% rename from web/src/app/api/view/route.ts rename to web/src/app/(app)/api/view/route.ts index 2411e5b..513a7ea 100644 --- a/web/src/app/api/view/route.ts +++ b/web/src/app/(app)/api/view/route.ts @@ -1,4 +1,4 @@ -import { getFileDownloadUrl } from "../../../server/getFileDownloadUrl"; +import { getFileDownloadUrl } from "../../../../server/getFileDownloadUrl"; import { NextResponse, type NextRequest } from "next/server"; export async function GET(request: NextRequest) { diff --git a/web/src/app/favicon.ico b/web/src/app/(app)/favicon.ico similarity index 100% rename from web/src/app/favicon.ico rename to web/src/app/(app)/favicon.ico diff --git a/web/src/app/globals.css b/web/src/app/(app)/globals.css similarity index 75% rename from web/src/app/globals.css rename to web/src/app/(app)/globals.css index 1d95465..88aecda 100644 --- a/web/src/app/globals.css +++ b/web/src/app/(app)/globals.css @@ -1,3 +1,21 @@ +@layer base { + :root { + --shiki-color-text: theme('colors.white'); + --shiki-token-constant: theme('colors.emerald.300'); + --shiki-token-string: theme('colors.emerald.300'); + --shiki-token-comment: theme('colors.zinc.500'); + --shiki-token-keyword: theme('colors.sky.300'); + --shiki-token-parameter: theme('colors.pink.300'); + --shiki-token-function: theme('colors.violet.300'); + --shiki-token-string-expression: theme('colors.emerald.300'); + --shiki-token-punctuation: theme('colors.zinc.200'); + } + + [inert] ::-webkit-scrollbar { + display: none; + } +} + @tailwind base; @tailwind components; @tailwind utilities; diff --git a/web/src/app/layout.tsx b/web/src/app/(app)/layout.tsx similarity index 92% rename from web/src/app/layout.tsx rename to web/src/app/(app)/layout.tsx index a9a4fc8..1ce4740 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/(app)/layout.tsx @@ -57,6 +57,13 @@ export default function RootLayout({

+ +
+ + ); +} diff --git a/web/src/app/(docs)/docs/page.mdx b/web/src/app/(docs)/docs/page.mdx new file mode 100644 index 0000000..310eee7 --- /dev/null +++ b/web/src/app/(docs)/docs/page.mdx @@ -0,0 +1,34 @@ +import { Guides } from '@/components/docs/Guides' +import { Resources } from '@/components/docs/Resources' +import { HeroPattern } from '@/components/docs/HeroPattern' + +export const metadata = { + title: 'Comfy Deploy Documentation', + description: + 'Get your comfy deploy setup running in minutes. Learn how to deploy your generative workflow to comfy deploy.', +} + + + +# Introduction + +Open source comfyui deployment platform, a vercel for generative workflow infra. + +
+ + +
+ +{/* ## Getting started {{ anchor: false }} + +Get started by first installing Comfy Deploy plugin + +
+ +
*/} diff --git a/web/src/app/(docs)/docs/providers.tsx b/web/src/app/(docs)/docs/providers.tsx new file mode 100644 index 0000000..2f3c261 --- /dev/null +++ b/web/src/app/(docs)/docs/providers.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { ThemeProvider, useTheme } from "next-themes"; +import { useEffect } from "react"; + +function ThemeWatcher() { + const { resolvedTheme, setTheme } = useTheme(); + + useEffect(() => { + const media = window.matchMedia("(prefers-color-scheme: dark)"); + + function onMediaChange() { + const systemTheme = media.matches ? "dark" : "light"; + if (resolvedTheme === systemTheme) { + setTheme("system"); + } + } + + onMediaChange(); + media.addEventListener("change", onMediaChange); + + return () => { + media.removeEventListener("change", onMediaChange); + }; + }, [resolvedTheme, setTheme]); + + return null; +} + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + ); +} diff --git a/web/src/components/VersionSelect.tsx b/web/src/components/VersionSelect.tsx index 54aa702..9eac012 100644 --- a/web/src/components/VersionSelect.tsx +++ b/web/src/components/VersionSelect.tsx @@ -96,7 +96,7 @@ export function MachineSelect({ setMachine(v); }} > - + diff --git a/web/src/components/docs/Button.tsx b/web/src/components/docs/Button.tsx new file mode 100644 index 0000000..18932d0 --- /dev/null +++ b/web/src/components/docs/Button.tsx @@ -0,0 +1,82 @@ +import clsx from "clsx"; +import Link from "next/link"; + +function ArrowIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +const variantStyles = { + primary: + "rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-emerald-400/10 dark:text-emerald-400 dark:ring-1 dark:ring-inset dark:ring-emerald-400/20 dark:hover:bg-emerald-400/10 dark:hover:text-emerald-300 dark:hover:ring-emerald-300", + secondary: + "rounded-full bg-zinc-100 py-1 px-3 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300", + filled: + "rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-emerald-500 dark:text-white dark:hover:bg-emerald-400", + outline: + "rounded-full py-1 px-3 text-zinc-700 ring-1 ring-inset ring-zinc-900/10 hover:bg-zinc-900/2.5 hover:text-zinc-900 dark:text-zinc-400 dark:ring-white/10 dark:hover:bg-white/5 dark:hover:text-white", + text: "text-emerald-500 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-500", +}; + +type ButtonProps = { + variant?: keyof typeof variantStyles; + arrow?: "left" | "right"; +} & ( + | React.ComponentPropsWithoutRef + | (React.ComponentPropsWithoutRef<"button"> & { href?: undefined }) +); + +export function Button({ + variant = "primary", + className, + children, + arrow, + ...props +}: ButtonProps) { + className = clsx( + "inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition items-center ", + variantStyles[variant], + className + ); + + const arrowIcon = ( + + ); + + const inner = ( + <> + {arrow === "left" && arrowIcon} + {children} + {arrow === "right" && arrowIcon} + + ); + + if (typeof props.href === "undefined") { + return ( + + ); + } + + return ( + + {inner} + + ); +} diff --git a/web/src/components/docs/Code.tsx b/web/src/components/docs/Code.tsx new file mode 100644 index 0000000..8395dc3 --- /dev/null +++ b/web/src/components/docs/Code.tsx @@ -0,0 +1,380 @@ +"use client"; + +import { Tag } from "@/components/docs/Tag"; +import { Tab } from "@headlessui/react"; +import clsx from "clsx"; +import { + Children, + createContext, + isValidElement, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { create } from "zustand"; + +const languageNames: Record = { + js: "JavaScript", + ts: "TypeScript", + javascript: "JavaScript", + typescript: "TypeScript", + php: "PHP", + python: "Python", + ruby: "Ruby", + go: "Go", +}; + +function getPanelTitle({ + title, + language, +}: { + title?: string; + language?: string; +}) { + if (title) { + return title; + } + if (language && language in languageNames) { + return languageNames[language]; + } + return "Code"; +} + +function ClipboardIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function CopyButton({ code }: { code: string }) { + const [copyCount, setCopyCount] = useState(0); + const copied = copyCount > 0; + + useEffect(() => { + if (copyCount > 0) { + const timeout = setTimeout(() => setCopyCount(0), 1000); + return () => { + clearTimeout(timeout); + }; + } + }, [copyCount]); + + return ( + + ); +} + +function CodePanelHeader({ tag, label }: { tag?: string; label?: string }) { + if (!tag && !label) { + return null; + } + + return ( +
+ {tag && ( +
+ {tag} +
+ )} + {tag && label && ( + + )} + {label && ( + {label} + )} +
+ ); +} + +function CodePanel({ + children, + tag, + label, + code, +}: { + children: React.ReactNode; + tag?: string; + label?: string; + code?: string; +}) { + const child = Children.only(children); + + if (isValidElement(child)) { + tag = child.props.tag ?? tag; + label = child.props.label ?? label; + code = child.props.code ?? code; + } + + if (!code) { + throw new Error( + "`CodePanel` requires a `code` prop, or a child with a `code` prop." + ); + } + + return ( +
+ +
+
{children}
+ +
+
+ ); +} + +function CodeGroupHeader({ + title, + children, + selectedIndex, +}: { + title: string; + children: React.ReactNode; + selectedIndex: number; +}) { + const hasTabs = Children.count(children) > 1; + + if (!title && !hasTabs) { + return null; + } + + return ( +
+ {title && ( +

+ {title} +

+ )} + {hasTabs && ( + + {Children.map(children, (child, childIndex) => ( + + {getPanelTitle(isValidElement(child) ? child.props : {})} + + ))} + + )} +
+ ); +} + +function CodeGroupPanels({ + children, + ...props +}: React.ComponentPropsWithoutRef) { + const hasTabs = Children.count(children) > 1; + + if (hasTabs) { + return ( + + {Children.map(children, (child) => ( + + {child} + + ))} + + ); + } + + return {children}; +} + +function usePreventLayoutShift() { + const positionRef = useRef(null); + const rafRef = useRef(); + + useEffect(() => { + return () => { + if (typeof rafRef.current !== "undefined") { + window.cancelAnimationFrame(rafRef.current); + } + }; + }, []); + + return { + positionRef, + preventLayoutShift(callback: () => void) { + if (!positionRef.current) { + return; + } + + const initialTop = positionRef.current.getBoundingClientRect().top; + + callback(); + + rafRef.current = window.requestAnimationFrame(() => { + const newTop = + positionRef.current?.getBoundingClientRect().top ?? initialTop; + window.scrollBy(0, newTop - initialTop); + }); + }, + }; +} + +const usePreferredLanguageStore = create<{ + preferredLanguages: Array; + addPreferredLanguage: (language: string) => void; +}>()((set) => ({ + preferredLanguages: [], + addPreferredLanguage: (language) => + set((state) => ({ + preferredLanguages: [ + ...state.preferredLanguages.filter( + (preferredLanguage) => preferredLanguage !== language + ), + language, + ], + })), +})); + +function useTabGroupProps(availableLanguages: Array) { + const { preferredLanguages, addPreferredLanguage } = + usePreferredLanguageStore(); + const [selectedIndex, setSelectedIndex] = useState(0); + const activeLanguage = [...availableLanguages].sort( + (a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a) + )[0]; + const languageIndex = availableLanguages.indexOf(activeLanguage); + const newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex; + if (newSelectedIndex !== selectedIndex) { + setSelectedIndex(newSelectedIndex); + } + + const { positionRef, preventLayoutShift } = usePreventLayoutShift(); + + return { + as: "div" as const, + ref: positionRef, + selectedIndex, + onChange: (newSelectedIndex: number) => { + preventLayoutShift(() => + addPreferredLanguage(availableLanguages[newSelectedIndex]) + ); + }, + }; +} + +const CodeGroupContext = createContext(false); + +export function CodeGroup({ + children, + title, + ...props +}: React.ComponentPropsWithoutRef & { title: string }) { + const languages = + Children.map(children, (child) => + getPanelTitle(isValidElement(child) ? child.props : {}) + ) ?? []; + const tabGroupProps = useTabGroupProps(languages); + const hasTabs = Children.count(children) > 1; + + const containerClassName = + "my-6 overflow-hidden rounded-2xl bg-zinc-900 shadow-md dark:ring-1 dark:ring-white/10"; + const header = ( + + {children} + + ); + const panels = {children}; + + return ( + + {hasTabs ? ( + +
+ {header} + {panels} +
+
+ ) : ( +
+
+ {header} + {panels} +
+
+ )} +
+ ); +} + +export function Code({ + children, + ...props +}: React.ComponentPropsWithoutRef<"code">) { + const isGrouped = useContext(CodeGroupContext); + + if (isGrouped) { + if (typeof children !== "string") { + throw new Error( + "`Code` children must be a string when nested inside a `CodeGroup`." + ); + } + return ; + } + + return {children}; +} + +export function Pre({ + children, + ...props +}: React.ComponentPropsWithoutRef) { + const isGrouped = useContext(CodeGroupContext); + + if (isGrouped) { + return children; + } + + return {children}; +} diff --git a/web/src/components/docs/Feedback.tsx b/web/src/components/docs/Feedback.tsx new file mode 100644 index 0000000..4fa92ed --- /dev/null +++ b/web/src/components/docs/Feedback.tsx @@ -0,0 +1,105 @@ +'use client' + +import { forwardRef, Fragment, useState } from 'react' +import { Transition } from '@headlessui/react' + +function CheckIcon(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + ) +} + +function FeedbackButton( + props: Omit, 'type' | 'className'>, +) { + return ( + + + {page.title} + + + ); +} + +function PageNavigation() { + const pathname = usePathname(); + const allPages = navigation.flatMap((group) => group.links); + const currentPageIndex = allPages.findIndex((page) => page.href === pathname); + + if (currentPageIndex === -1) { + return null; + } + + const previousPage = allPages[currentPageIndex - 1]; + const nextPage = allPages[currentPageIndex + 1]; + + if (!previousPage && !nextPage) { + return null; + } + + return ( +
+ {previousPage && ( +
+ +
+ )} + {nextPage && ( +
+ +
+ )} +
+ ); +} + +function TwitterIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function GitHubIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function DiscordIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function SocialLink({ + href, + icon: Icon, + children, +}: { + href: string; + icon: React.ComponentType<{ className?: string }>; + children: React.ReactNode; +}) { + return ( + + {children} + + + ); +} + +function SmallPrint() { + return ( +
+

+ © Copyright {new Date().getFullYear()}. All rights reserved. +

+
+ + Follow us on Twitter + + + Follow us on GitHub + + + Join our Discord server + +
+
+ ); +} + +export function Footer() { + return ( +
+ + +
+ ); +} diff --git a/web/src/components/docs/GridPattern.tsx b/web/src/components/docs/GridPattern.tsx new file mode 100644 index 0000000..9b989b3 --- /dev/null +++ b/web/src/components/docs/GridPattern.tsx @@ -0,0 +1,55 @@ +import { useId } from "react"; + +export function GridPattern({ + width, + height, + x, + y, + squares, + ...props +}: React.ComponentPropsWithoutRef<"svg"> & { + width: number; + height: number; + x: string | number; + y: string | number; + squares: Array<[x: number, y: number]>; +}) { + const patternId = useId(); + + return ( + + ); +} diff --git a/web/src/components/docs/Guides.tsx b/web/src/components/docs/Guides.tsx new file mode 100644 index 0000000..980f58e --- /dev/null +++ b/web/src/components/docs/Guides.tsx @@ -0,0 +1,54 @@ +import { Button } from "@/components/docs/Button"; +import { Heading } from "@/components/docs/Heading"; + +const guides = [ + { + href: "/authentication", + name: "Authentication", + description: "Learn how to authenticate your API requests.", + }, + { + href: "/pagination", + name: "Pagination", + description: "Understand how to work with paginated responses.", + }, + { + href: "/errors", + name: "Errors", + description: + "Read about the different types of errors returned by the API.", + }, + { + href: "/webhooks", + name: "Webhooks", + description: + "Learn how to programmatically configure webhooks for your app.", + }, +]; + +export function Guides() { + return ( +
+ + Guides + +
+ {guides.map((guide) => ( +
+

+ {guide.name} +

+

+ {guide.description} +

+

+ +

+
+ ))} +
+
+ ); +} diff --git a/web/src/components/docs/Header.tsx b/web/src/components/docs/Header.tsx new file mode 100644 index 0000000..04d8fe8 --- /dev/null +++ b/web/src/components/docs/Header.tsx @@ -0,0 +1,99 @@ +import { + MobileNavigation, + useIsInsideMobileNavigation, +} from "@/components/docs/MobileNavigation"; +import { useMobileNavigationStore } from "@/components/docs/MobileNavigation"; +import { MobileSearch, Search } from "@/components/docs/Search"; +import { ThemeToggle } from "@/components/docs/ThemeToggle"; +import clsx from "clsx"; +import { motion, useScroll, useTransform } from "framer-motion"; +import meta from "next-gen/config"; +import Link from "next/link"; +import { forwardRef } from "react"; + +function TopLevelNavItem({ + href, + children, +}: { + href: string; + children: React.ReactNode; +}) { + return ( +
  • + + {children} + +
  • + ); +} + +export const Header = forwardRef< + React.ElementRef<"div">, + { className?: string } +>(function Header({ className }, ref) { + const { isOpen: mobileNavIsOpen } = useMobileNavigationStore(); + const isInsideMobileNavigation = useIsInsideMobileNavigation(); + + const { scrollY } = useScroll(); + const bgOpacityLight = useTransform(scrollY, [0, 72], [0.5, 0.9]); + const bgOpacityDark = useTransform(scrollY, [0, 72], [0.2, 0.8]); + + return ( + +
    + +
    + + {/* + + */} + + {meta.name} + +
    +
    + {/* */} +
    +
    + + +
    + {/*
    + +
    */} +
    + + ); +}); diff --git a/web/src/components/docs/Heading.tsx b/web/src/components/docs/Heading.tsx new file mode 100644 index 0000000..e0f4403 --- /dev/null +++ b/web/src/components/docs/Heading.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { useSectionStore } from "@/components/docs/SectionProvider"; +import { Tag } from "@/components/docs/Tag"; +import { remToPx } from "@/lib/remToPx"; +import { useInView } from "framer-motion"; +import Link from "next/link"; +import { useEffect, useRef } from "react"; + +function AnchorIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function Eyebrow({ tag, label }: { tag?: string; label?: string }) { + if (!tag && !label) { + return null; + } + + return ( +
    + {tag && {tag}} + {tag && label && ( + + )} + {label && ( + {label} + )} +
    + ); +} + +function Anchor({ + id, + inView, + children, +}: { + id: string; + inView: boolean; + children: React.ReactNode; +}) { + return ( + + {inView && ( +
    +
    + +
    +
    + )} + {children} + + ); +} + +export function Heading({ + children, + tag, + label, + level, + anchor = true, + ...props +}: React.ComponentPropsWithoutRef<`h${Level}`> & { + id: string; + tag?: string; + label?: string; + level?: Level; + anchor?: boolean; +}) { + level = level ?? (2 as Level); + const Component = `h${level}` as "h2" | "h3"; + const ref = useRef(null); + const registerHeading = useSectionStore((s) => s.registerHeading); + + const inView = useInView(ref, { + margin: `${remToPx(-3.5)}px 0px 0px 0px`, + amount: "all", + }); + + useEffect(() => { + if (level === 2) { + registerHeading({ id: props.id, ref, offsetRem: tag || label ? 8 : 6 }); + } + }); + + return ( + <> + + + {anchor ? ( + + {children} + + ) : ( + children + )} + + + ); +} diff --git a/web/src/components/docs/HeroPattern.tsx b/web/src/components/docs/HeroPattern.tsx new file mode 100644 index 0000000..b97bf8c --- /dev/null +++ b/web/src/components/docs/HeroPattern.tsx @@ -0,0 +1,32 @@ +import { GridPattern } from "@/components/docs/GridPattern"; + +export function HeroPattern() { + return ( +
    +
    +
    + +
    + +
    +
    + ); +} diff --git a/web/src/components/docs/Layout.tsx b/web/src/components/docs/Layout.tsx new file mode 100644 index 0000000..39d2ebd --- /dev/null +++ b/web/src/components/docs/Layout.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { Footer } from "@/components/docs/Footer"; +import { Header } from "@/components/docs/Header"; +import { Navigation } from "@/components/docs/Navigation"; +import { + type Section, + SectionProvider, +} from "@/components/docs/SectionProvider"; +import { motion } from "framer-motion"; +import meta from "next-gen/config"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +export function Layout({ + children, + allSections, +}: { + children: React.ReactNode; + allSections: Record>; +}) { + const pathname = usePathname(); + + return ( + +
    + +
    +
    + + {meta.name} + +
    +
    + +
    +
    +
    +
    {children}
    +
    +
    +
    +
    + ); +} diff --git a/web/src/components/docs/Libraries.tsx b/web/src/components/docs/Libraries.tsx new file mode 100644 index 0000000..14ef852 --- /dev/null +++ b/web/src/components/docs/Libraries.tsx @@ -0,0 +1,81 @@ +import { Button } from "@/components/docs/Button"; +import { Heading } from "@/components/docs/Heading"; +import logoGo from "@/images/logos/go.svg"; +import logoNode from "@/images/logos/node.svg"; +import logoPhp from "@/images/logos/php.svg"; +import logoPython from "@/images/logos/python.svg"; +import logoRuby from "@/images/logos/ruby.svg"; +import Image from "next/image"; + +const libraries = [ + { + href: "#", + name: "PHP", + description: + "A popular general-purpose scripting language that is especially suited to web development.", + logo: logoPhp, + }, + { + href: "#", + name: "Ruby", + description: + "A dynamic, open source programming language with a focus on simplicity and productivity.", + logo: logoRuby, + }, + { + href: "#", + name: "Node.js", + description: + "Node.js® is an open-source, cross-platform JavaScript runtime environment.", + logo: logoNode, + }, + { + href: "#", + name: "Python", + description: + "Python is a programming language that lets you work quickly and integrate systems more effectively.", + logo: logoPython, + }, + { + href: "#", + name: "Go", + description: + "An open-source programming language supported by Google with built-in concurrency.", + logo: logoGo, + }, +]; + +export function Libraries() { + return ( +
    + + Official libraries + +
    + {libraries.map((library) => ( +
    +
    +

    + {library.name} +

    +

    + {library.description} +

    +

    + +

    +
    + +
    + ))} +
    +
    + ); +} diff --git a/web/src/components/docs/Logo.tsx b/web/src/components/docs/Logo.tsx new file mode 100644 index 0000000..ac0eb27 --- /dev/null +++ b/web/src/components/docs/Logo.tsx @@ -0,0 +1,14 @@ +export function Logo(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + ) +} diff --git a/web/src/components/docs/MobileNavigation.tsx b/web/src/components/docs/MobileNavigation.tsx new file mode 100644 index 0000000..7b28964 --- /dev/null +++ b/web/src/components/docs/MobileNavigation.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { Header } from "@/components/docs/Header"; +import { Navigation } from "@/components/docs/Navigation"; +import { Dialog, Transition } from "@headlessui/react"; +import { motion } from "framer-motion"; +import { usePathname, useSearchParams } from "next/navigation"; +import { + createContext, + Fragment, + Suspense, + useContext, + useEffect, + useRef, +} from "react"; +import { create } from "zustand"; + +function MenuIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function XIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +const IsInsideMobileNavigationContext = createContext(false); + +function MobileNavigationDialog({ + isOpen, + close, +}: { + isOpen: boolean; + close: () => void; +}) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const initialPathname = useRef(pathname).current; + const initialSearchParams = useRef(searchParams).current; + + useEffect(() => { + if (pathname !== initialPathname || searchParams !== initialSearchParams) { + close(); + } + }, [pathname, searchParams, close, initialPathname, initialSearchParams]); + + function onClickDialog(event: React.MouseEvent) { + if (!(event.target instanceof HTMLElement)) { + return; + } + + const link = event.target.closest("a"); + if ( + link && + link.pathname + link.search + link.hash === + window.location.pathname + window.location.search + window.location.hash + ) { + close(); + } + } + + return ( + + + +
    + + + + +
    + + + + + + + + +
    +
    + ); +} + +export function useIsInsideMobileNavigation() { + return useContext(IsInsideMobileNavigationContext); +} + +export const useMobileNavigationStore = create<{ + isOpen: boolean; + open: () => void; + close: () => void; + toggle: () => void; +}>()((set) => ({ + isOpen: false, + open: () => set({ isOpen: true }), + close: () => set({ isOpen: false }), + toggle: () => set((state) => ({ isOpen: !state.isOpen })), +})); + +export function MobileNavigation() { + const isInsideMobileNavigation = useIsInsideMobileNavigation(); + const { isOpen, toggle, close } = useMobileNavigationStore(); + const ToggleIcon = isOpen ? XIcon : MenuIcon; + + return ( + + + {!isInsideMobileNavigation && ( + + + + )} + + ); +} diff --git a/web/src/components/docs/Navigation.tsx b/web/src/components/docs/Navigation.tsx new file mode 100644 index 0000000..29c62fa --- /dev/null +++ b/web/src/components/docs/Navigation.tsx @@ -0,0 +1,265 @@ +"use client"; + +import { Button } from "@/components/docs/Button"; +import { useIsInsideMobileNavigation } from "@/components/docs/MobileNavigation"; +import { useSectionStore } from "@/components/docs/SectionProvider"; +import { Tag } from "@/components/docs/Tag"; +import { remToPx } from "@/lib/remToPx"; +import clsx from "clsx"; +import { AnimatePresence, motion, useIsPresent } from "framer-motion"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useRef } from "react"; + +interface NavGroup { + title: string; + links: Array<{ + title: string; + href: string; + }>; +} + +function useInitialValue(value: T, condition = true) { + const initialValue = useRef(value).current; + return condition ? initialValue : value; +} + +function TopLevelNavItem({ + href, + children, +}: { + href: string; + children: React.ReactNode; +}) { + return ( +
  • + + {children} + +
  • + ); +} + +function NavLink({ + href, + children, + tag, + active = false, + isAnchorLink = false, +}: { + href: string; + children: React.ReactNode; + tag?: string; + active?: boolean; + isAnchorLink?: boolean; +}) { + return ( + + {children} + {tag && ( + + {tag} + + )} + + ); +} + +function VisibleSectionHighlight({ + group, + pathname, +}: { + group: NavGroup; + pathname: string; +}) { + const [sections, visibleSections] = useInitialValue( + [ + useSectionStore((s) => s.sections), + useSectionStore((s) => s.visibleSections), + ], + useIsInsideMobileNavigation() + ); + + const isPresent = useIsPresent(); + const firstVisibleSectionIndex = Math.max( + 0, + [{ id: "_top" }, ...sections].findIndex( + (section) => section.id === visibleSections[0] + ) + ); + const itemHeight = remToPx(2); + const height = isPresent + ? Math.max(1, visibleSections.length) * itemHeight + : itemHeight; + const top = + group.links.findIndex((link) => link.href === pathname) * itemHeight + + firstVisibleSectionIndex * itemHeight; + + return ( + + ); +} + +function ActivePageMarker({ + group, + pathname, +}: { + group: NavGroup; + pathname: string; +}) { + const itemHeight = remToPx(2); + const offset = remToPx(0.25); + const activePageIndex = group.links.findIndex( + (link) => link.href === pathname + ); + const top = offset + activePageIndex * itemHeight; + + return ( + + ); +} + +function NavigationGroup({ + group, + className, +}: { + group: NavGroup; + className?: string; +}) { + // If this is the mobile navigation then we always render the initial + // state, so that the state does not change during the close animation. + // The state will still update when we re-open (re-render) the navigation. + const isInsideMobileNavigation = useIsInsideMobileNavigation(); + const [pathname, sections] = useInitialValue( + [usePathname(), useSectionStore((s) => s.sections)], + isInsideMobileNavigation + ); + + const isActiveGroup = + group.links.findIndex((link) => link.href === pathname) !== -1; + + return ( +
  • + + {group.title} + +
    + + {isActiveGroup && ( + + )} + + + + {isActiveGroup && ( + + )} + +
      + {group.links.map((link) => ( + + + {link.title} + + + {link.href === pathname && sections.length > 0 && ( + + {sections.map((section) => ( +
    • + + {section.title} + +
    • + ))} +
      + )} +
      +
      + ))} +
    +
    +
  • + ); +} + +export const navigation: Array = [ + { + title: "Guides", + links: [ + { title: "Introduction", href: "/docs" }, + { title: "Installation", href: "/docs/install" }, + ], + }, +]; + +export function Navigation(props: React.ComponentPropsWithoutRef<"nav">) { + return ( + + ); +} diff --git a/web/src/components/docs/Prose.tsx b/web/src/components/docs/Prose.tsx new file mode 100644 index 0000000..940b370 --- /dev/null +++ b/web/src/components/docs/Prose.tsx @@ -0,0 +1,24 @@ +import clsx from 'clsx' + +export function Prose({ + as, + className, + ...props +}: Omit, 'as' | 'className'> & { + as?: T + className?: string +}) { + let Component = as ?? 'div' + + return ( + *)` is used to select all direct children without an increase in specificity like you'd get from just `& > *` + '[html_:where(&>*)]:mx-auto [html_:where(&>*)]:max-w-2xl [html_:where(&>*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(&>*)]:lg:max-w-3xl', + )} + {...props} + /> + ) +} diff --git a/web/src/components/docs/Resources.tsx b/web/src/components/docs/Resources.tsx new file mode 100644 index 0000000..9a0c3a9 --- /dev/null +++ b/web/src/components/docs/Resources.tsx @@ -0,0 +1,182 @@ +"use client"; + +import { GridPattern } from "@/components/docs/GridPattern"; +import { Heading } from "@/components/docs/Heading"; +import { + type MotionValue, + motion, + useMotionTemplate, + useMotionValue, +} from "framer-motion"; +import { Mail, MessageSquare, User, Users } from "lucide-react"; +import Link from "next/link"; + +interface Resource { + href: string; + name: string; + description: string; + icon: React.ComponentType<{ className?: string }>; + pattern: Omit< + React.ComponentPropsWithoutRef, + "width" | "height" | "x" + >; +} + +const resources: Array = [ + { + href: "/contacts", + name: "Contacts", + description: + "Learn about the contact model and how to create, retrieve, update, delete, and list contacts.", + icon: User, + pattern: { + y: 16, + squares: [ + [0, 1], + [1, 3], + ], + }, + }, + { + href: "/conversations", + name: "Conversations", + description: + "Learn about the conversation model and how to create, retrieve, update, delete, and list conversations.", + icon: MessageSquare, + pattern: { + y: -6, + squares: [ + [-1, 2], + [1, 3], + ], + }, + }, + { + href: "/messages", + name: "Messages", + description: + "Learn about the message model and how to create, retrieve, update, delete, and list messages.", + icon: Mail, + pattern: { + y: 32, + squares: [ + [0, 2], + [1, 4], + ], + }, + }, + { + href: "/groups", + name: "Groups", + description: + "Learn about the group model and how to create, retrieve, update, delete, and list groups.", + icon: Users, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, +]; + +function ResourceIcon({ icon: Icon }: { icon: Resource["icon"] }) { + return ( +
    + +
    + ); +} + +function ResourcePattern({ + mouseX, + mouseY, + ...gridProps +}: Resource["pattern"] & { + mouseX: MotionValue; + mouseY: MotionValue; +}) { + const maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)`; + const style = { maskImage, WebkitMaskImage: maskImage }; + + return ( +
    +
    + +
    + + + + +
    + ); +} + +function Resource({ resource }: { resource: Resource }) { + const mouseX = useMotionValue(0); + const mouseY = useMotionValue(0); + + function onMouseMove({ + currentTarget, + clientX, + clientY, + }: React.MouseEvent) { + const { left, top } = currentTarget.getBoundingClientRect(); + mouseX.set(clientX - left); + mouseY.set(clientY - top); + } + + return ( +
    + +
    +
    + +

    + + + {resource.name} + +

    +

    + {resource.description} +

    +
    +
    + ); +} + +export function Resources() { + return ( +
    + + Resources + +
    + {resources.map((resource) => ( + + ))} +
    +
    + ); +} diff --git a/web/src/components/docs/Search.tsx b/web/src/components/docs/Search.tsx new file mode 100644 index 0000000..5d2b4a9 --- /dev/null +++ b/web/src/components/docs/Search.tsx @@ -0,0 +1,504 @@ +"use client"; + +import { navigation } from "@/components/docs/Navigation"; +import { type Result } from "@/mdx/search.mjs"; +import { + type AutocompleteApi, + createAutocomplete, + type AutocompleteState, + type AutocompleteCollection, +} from "@algolia/autocomplete-core"; +import { Dialog, Transition } from "@headlessui/react"; +import clsx from "clsx"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { + forwardRef, + Fragment, + Suspense, + useCallback, + useEffect, + useId, + useRef, + useState, +} from "react"; +import Highlighter from "react-highlight-words"; + +type EmptyObject = Record; + +type Autocomplete = AutocompleteApi< + Result, + React.SyntheticEvent, + React.MouseEvent, + React.KeyboardEvent +>; + +function useAutocomplete({ close }: { close: () => void }) { + const id = useId(); + const router = useRouter(); + const [autocompleteState, setAutocompleteState] = useState< + AutocompleteState | EmptyObject + >({}); + + function navigate({ itemUrl }: { itemUrl?: string }) { + if (!itemUrl) { + return; + } + + router.push(itemUrl); + + if ( + itemUrl === + window.location.pathname + window.location.search + window.location.hash + ) { + close(); + } + } + + const [autocomplete] = useState(() => + createAutocomplete< + Result, + React.SyntheticEvent, + React.MouseEvent, + React.KeyboardEvent + >({ + id, + placeholder: "Find something...", + defaultActiveItemId: 0, + onStateChange({ state }) { + setAutocompleteState(state); + }, + shouldPanelOpen({ state }) { + return state.query !== ""; + }, + navigator: { + navigate, + }, + getSources({ query }) { + return import("@/mdx/search.mjs").then(({ search }) => { + return [ + { + sourceId: "documentation", + getItems() { + return search(query, { limit: 5 }); + }, + getItemUrl({ item }) { + return item.url; + }, + onSelect: navigate, + }, + ]; + }); + }, + }) + ); + + return { autocomplete, autocompleteState }; +} + +function SearchIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function NoResultsIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +function LoadingIcon(props: React.ComponentPropsWithoutRef<"svg">) { + const id = useId(); + + return ( + + ); +} + +function HighlightQuery({ text, query }: { text: string; query: string }) { + return ( + + ); +} + +function SearchResult({ + result, + resultIndex, + autocomplete, + collection, + query, +}: { + result: Result; + resultIndex: number; + autocomplete: Autocomplete; + collection: AutocompleteCollection; + query: string; +}) { + const id = useId(); + + const sectionTitle = navigation.find((section) => + section.links.find((link) => link.href === result.url.split("#")[0]) + )?.title; + const hierarchy = [sectionTitle, result.pageTitle].filter( + (x): x is string => typeof x === "string" + ); + + return ( +
  • 0 && "border-t border-zinc-100 dark:border-zinc-800" + )} + aria-labelledby={`${id}-hierarchy ${id}-title`} + {...autocomplete.getItemProps({ + item: result, + source: collection.source, + })} + > + + {hierarchy.length > 0 && ( + + )} +
  • + ); +} + +function SearchResults({ + autocomplete, + query, + collection, +}: { + autocomplete: Autocomplete; + query: string; + collection: AutocompleteCollection; +}) { + if (collection.items.length === 0) { + return ( +
    + +

    + Nothing found for{" "} + + ‘{query}’ + + . Please try again. +

    +
    + ); + } + + return ( +
      + {collection.items.map((result, resultIndex) => ( + + ))} +
    + ); +} + +const SearchInput = forwardRef< + React.ElementRef<"input">, + { + autocomplete: Autocomplete; + autocompleteState: AutocompleteState | EmptyObject; + onClose: () => void; + } +>(function SearchInput({ autocomplete, autocompleteState, onClose }, inputRef) { + const inputProps = autocomplete.getInputProps({ inputElement: null }); + + return ( +
    + + { + if ( + event.key === "Escape" && + !autocompleteState.isOpen && + autocompleteState.query === "" + ) { + // In Safari, closing the dialog with the escape key can sometimes cause the scroll position to jump to the + // bottom of the page. This is a workaround for that until we can figure out a proper fix in Headless UI. + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + + onClose(); + } else { + inputProps.onKeyDown(event); + } + }} + /> + {autocompleteState.status === "stalled" && ( +
    + +
    + )} +
    + ); +}); + +function SearchDialog({ + open, + setOpen, + className, +}: { + open: boolean; + setOpen: (open: boolean) => void; + className?: string; +}) { + const formRef = useRef>(null); + const panelRef = useRef>(null); + const inputRef = useRef>(null); + const { autocomplete, autocompleteState } = useAutocomplete({ + close() { + setOpen(false); + }, + }); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + useEffect(() => { + setOpen(false); + }, [pathname, searchParams, setOpen]); + + useEffect(() => { + if (open) { + return; + } + + function onKeyDown(event: KeyboardEvent) { + if (event.key === "k" && (event.metaKey || event.ctrlKey)) { + event.preventDefault(); + setOpen(true); + } + } + + window.addEventListener("keydown", onKeyDown); + + return () => { + window.removeEventListener("keydown", onKeyDown); + }; + }, [open, setOpen]); + + return ( + autocomplete.setQuery("")} + > + + +
    + + +
    + + +
    +
    + setOpen(false)} + /> +
    + {autocompleteState.isOpen && ( + + )} +
    + +
    +
    +
    +
    +
    +
    + ); +} + +function useSearchProps() { + const buttonRef = useRef>(null); + const [open, setOpen] = useState(false); + + return { + buttonProps: { + ref: buttonRef, + onClick() { + setOpen(true); + }, + }, + dialogProps: { + open, + setOpen: useCallback( + (open: boolean) => { + const { width = 0, height = 0 } = + buttonRef.current?.getBoundingClientRect() ?? {}; + if (!open || (width !== 0 && height !== 0)) { + setOpen(open); + } + }, + [setOpen] + ), + }, + }; +} + +export function Search() { + const [modifierKey, setModifierKey] = useState(); + const { buttonProps, dialogProps } = useSearchProps(); + + useEffect(() => { + setModifierKey( + /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? "⌘" : "Ctrl " + ); + }, []); + + return ( +
    + + + + +
    + ); +} + +export function MobileSearch() { + const { buttonProps, dialogProps } = useSearchProps(); + + return ( +
    + + + + +
    + ); +} diff --git a/web/src/components/docs/SectionProvider.tsx b/web/src/components/docs/SectionProvider.tsx new file mode 100644 index 0000000..53a5239 --- /dev/null +++ b/web/src/components/docs/SectionProvider.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { remToPx } from "@/lib/remToPx"; +import { + createContext, + useContext, + useEffect, + useLayoutEffect, + useState, +} from "react"; +import { type StoreApi, createStore, useStore } from "zustand"; + +export interface Section { + id: string; + title: string; + offsetRem?: number; + tag?: string; + headingRef?: React.RefObject; +} + +interface SectionState { + sections: Array
    ; + visibleSections: Array; + setVisibleSections: (visibleSections: Array) => void; + registerHeading: ({ + id, + ref, + offsetRem, + }: { + id: string; + ref: React.RefObject; + offsetRem: number; + }) => void; +} + +function createSectionStore(sections: Array
    ) { + return createStore()((set) => ({ + sections, + visibleSections: [], + setVisibleSections: (visibleSections) => + set((state) => + state.visibleSections.join() === visibleSections.join() + ? {} + : { visibleSections } + ), + registerHeading: ({ id, ref, offsetRem }) => + set((state) => { + return { + sections: state.sections.map((section) => { + if (section.id === id) { + return { + ...section, + headingRef: ref, + offsetRem, + }; + } + return section; + }), + }; + }), + })); +} + +function useVisibleSections(sectionStore: StoreApi) { + const setVisibleSections = useStore( + sectionStore, + (s) => s.setVisibleSections + ); + const sections = useStore(sectionStore, (s) => s.sections); + + useEffect(() => { + function checkVisibleSections() { + const { innerHeight, scrollY } = window; + const newVisibleSections = []; + + for ( + let sectionIndex = 0; + sectionIndex < sections.length; + sectionIndex++ + ) { + const { id, headingRef, offsetRem = 0 } = sections[sectionIndex]; + + if (!headingRef?.current) { + continue; + } + + const offset = remToPx(offsetRem); + const top = headingRef.current.getBoundingClientRect().top + scrollY; + + if (sectionIndex === 0 && top - offset > scrollY) { + newVisibleSections.push("_top"); + } + + const nextSection = sections[sectionIndex + 1]; + const bottom = + (nextSection?.headingRef?.current?.getBoundingClientRect().top ?? + Infinity) + + scrollY - + remToPx(nextSection?.offsetRem ?? 0); + + if ( + (top > scrollY && top < scrollY + innerHeight) || + (bottom > scrollY && bottom < scrollY + innerHeight) || + (top <= scrollY && bottom >= scrollY + innerHeight) + ) { + newVisibleSections.push(id); + } + } + + setVisibleSections(newVisibleSections); + } + + const raf = window.requestAnimationFrame(() => checkVisibleSections()); + window.addEventListener("scroll", checkVisibleSections, { passive: true }); + window.addEventListener("resize", checkVisibleSections); + + return () => { + window.cancelAnimationFrame(raf); + window.removeEventListener("scroll", checkVisibleSections); + window.removeEventListener("resize", checkVisibleSections); + }; + }, [setVisibleSections, sections]); +} + +const SectionStoreContext = createContext | null>(null); + +const useIsomorphicLayoutEffect = + typeof window === "undefined" ? useEffect : useLayoutEffect; + +export function SectionProvider({ + sections, + children, +}: { + sections: Array
    ; + children: React.ReactNode; +}) { + const [sectionStore] = useState(() => createSectionStore(sections)); + + useVisibleSections(sectionStore); + + useIsomorphicLayoutEffect(() => { + sectionStore.setState({ sections }); + }, [sectionStore, sections]); + + return ( + + {children} + + ); +} + +export function useSectionStore(selector: (state: SectionState) => T) { + const store = useContext(SectionStoreContext); + return useStore(store!, selector); +} diff --git a/web/src/components/docs/Tag.tsx b/web/src/components/docs/Tag.tsx new file mode 100644 index 0000000..06b21d6 --- /dev/null +++ b/web/src/components/docs/Tag.tsx @@ -0,0 +1,63 @@ +import clsx from 'clsx' + +const variantStyles = { + small: '', + medium: 'rounded-lg px-1.5 ring-1 ring-inset', +} + +const colorStyles = { + emerald: { + small: 'text-emerald-500 dark:text-emerald-400', + medium: + 'ring-emerald-300 dark:ring-emerald-400/30 bg-emerald-400/10 text-emerald-500 dark:text-emerald-400', + }, + sky: { + small: 'text-sky-500', + medium: + 'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400', + }, + amber: { + small: 'text-amber-500', + medium: + 'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400', + }, + rose: { + small: 'text-red-500 dark:text-rose-500', + medium: + 'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400', + }, + zinc: { + small: 'text-zinc-400 dark:text-zinc-500', + medium: + 'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400', + }, +} + +const valueColorMap = { + GET: 'emerald', + POST: 'sky', + PUT: 'amber', + DELETE: 'rose', +} as Record + +export function Tag({ + children, + variant = 'medium', + color = valueColorMap[children] ?? 'emerald', +}: { + children: keyof typeof valueColorMap & (string | {}) + variant?: keyof typeof variantStyles + color?: keyof typeof colorStyles +}) { + return ( + + {children} + + ) +} diff --git a/web/src/components/docs/ThemeToggle.tsx b/web/src/components/docs/ThemeToggle.tsx new file mode 100644 index 0000000..eb9969f --- /dev/null +++ b/web/src/components/docs/ThemeToggle.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from 'react' +import { useTheme } from 'next-themes' + +function SunIcon(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + ) +} + +function MoonIcon(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + ) +} + +export function ThemeToggle() { + let { resolvedTheme, setTheme } = useTheme() + let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark' + let [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + return ( + + ) +} diff --git a/web/src/components/docs/mdx.tsx b/web/src/components/docs/mdx.tsx new file mode 100644 index 0000000..7f4a2ae --- /dev/null +++ b/web/src/components/docs/mdx.tsx @@ -0,0 +1,144 @@ +import { Feedback } from "@/components/docs/Feedback"; +import { Heading } from "@/components/docs/Heading"; +import { Prose } from "@/components/docs/Prose"; +import { cn } from "@/lib/utils"; +import clsx from "clsx"; +import Link from "next/link"; + +export const a = Link; +export { Button } from "@/components/docs/Button"; +export { CodeGroup, Code as code, Pre as pre } from "@/components/docs/Code"; + +export function wrapper({ children }: { children: React.ReactNode }) { + return ( +
    + {children} +
    + +
    +
    + ); +} + +export const h2 = function H2( + props: Omit, "level"> +) { + return ; +}; + +function InfoIcon(props: React.ComponentPropsWithoutRef<"svg">) { + return ( + + ); +} + +export function Image({ + className, + ...props +}: React.ComponentPropsWithoutRef<"img">) { + return ( + + ); +} + +export function Note({ children }: { children: React.ReactNode }) { + return ( +
    + +
    + {children} +
    +
    + ); +} + +export function Row({ children }: { children: React.ReactNode }) { + return ( +
    + {children} +
    + ); +} + +export function Col({ + children, + sticky = false, +}: { + children: React.ReactNode; + sticky?: boolean; +}) { + return ( +
    :first-child]:mt-0 [&>:last-child]:mb-0", + sticky && "xl:sticky xl:top-24" + )} + > + {children} +
    + ); +} + +export function Properties({ children }: { children: React.ReactNode }) { + return ( +
    +
      + {children} +
    +
    + ); +} + +export function Property({ + name, + children, + type, +}: { + name: string; + children: React.ReactNode; + type?: string; +}) { + return ( +
  • +
    +
    Name
    +
    + {name} +
    + {type && ( + <> +
    Type
    +
    + {type} +
    + + )} +
    Description
    +
    + {children} +
    +
    +
  • + ); +} diff --git a/web/src/components/ui/tabs.tsx b/web/src/components/ui/tabs.tsx index 26eb109..1e22c2f 100644 --- a/web/src/components/ui/tabs.tsx +++ b/web/src/components/ui/tabs.tsx @@ -1,11 +1,10 @@ -"use client" +"use client"; -import * as React from "react" -import * as TabsPrimitive from "@radix-ui/react-tabs" +import { cn } from "@/lib/utils"; +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import * as React from "react"; -import { cn } from "@/lib/utils" - -const Tabs = TabsPrimitive.Root +const Tabs = TabsPrimitive.Root; const TabsList = React.forwardRef< React.ElementRef, @@ -19,8 +18,8 @@ const TabsList = React.forwardRef< )} {...props} /> -)) -TabsList.displayName = TabsPrimitive.List.displayName +)); +TabsList.displayName = TabsPrimitive.List.displayName; const TabsTrigger = React.forwardRef< React.ElementRef, @@ -29,13 +28,13 @@ const TabsTrigger = React.forwardRef< -)) -TabsTrigger.displayName = TabsPrimitive.Trigger.displayName +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; const TabsContent = React.forwardRef< React.ElementRef, @@ -49,7 +48,7 @@ const TabsContent = React.forwardRef< )} {...props} /> -)) -TabsContent.displayName = TabsPrimitive.Content.displayName +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; -export { Tabs, TabsList, TabsTrigger, TabsContent } +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/web/src/lib/remToPx.ts b/web/src/lib/remToPx.ts new file mode 100644 index 0000000..d3c3953 --- /dev/null +++ b/web/src/lib/remToPx.ts @@ -0,0 +1,8 @@ +export function remToPx(remValue: number) { + let rootFontSize = + typeof window === 'undefined' + ? 16 + : parseFloat(window.getComputedStyle(document.documentElement).fontSize) + + return remValue * rootFontSize +} diff --git a/web/src/mdx/recma.mjs b/web/src/mdx/recma.mjs new file mode 100644 index 0000000..237ac11 --- /dev/null +++ b/web/src/mdx/recma.mjs @@ -0,0 +1,3 @@ +import { mdxAnnotations } from "mdx-annotations"; + +export const recmaPlugins = [mdxAnnotations.recma]; diff --git a/web/src/mdx/rehype.mjs b/web/src/mdx/rehype.mjs new file mode 100644 index 0000000..8219655 --- /dev/null +++ b/web/src/mdx/rehype.mjs @@ -0,0 +1,124 @@ +import { slugifyWithCounter } from "@sindresorhus/slugify"; +import * as acorn from "acorn"; +import { toString } from "mdast-util-to-string"; +import { mdxAnnotations } from "mdx-annotations"; +import shiki from "shiki"; +import { visit } from "unist-util-visit"; + +function rehypeParseCodeBlocks() { + return (tree) => { + visit(tree, "element", (node, _nodeIndex, parentNode) => { + if (node.tagName === "code" && node.properties.className) { + parentNode.properties.language = node.properties.className[0]?.replace( + /^language-/, + "" + ); + } + }); + }; +} + +let highlighter; + +function rehypeShiki() { + return async (tree) => { + highlighter = + highlighter ?? (await shiki.getHighlighter({ theme: "css-variables" })); + + visit(tree, "element", (node) => { + if (node.tagName === "pre" && node.children[0]?.tagName === "code") { + let codeNode = node.children[0]; + let textNode = codeNode.children[0]; + + node.properties.code = textNode.value; + + if (node.properties.language) { + let tokens = highlighter.codeToThemedTokens( + textNode.value, + node.properties.language + ); + + textNode.value = shiki.renderToHtml(tokens, { + elements: { + pre: ({ children }) => children, + code: ({ children }) => children, + line: ({ children }) => `${children}`, + }, + }); + } + } + }); + }; +} + +function rehypeSlugify() { + return (tree) => { + let slugify = slugifyWithCounter(); + visit(tree, "element", (node) => { + if (node.tagName === "h2" && !node.properties.id) { + node.properties.id = slugify(toString(node)); + } + }); + }; +} + +function rehypeAddMDXExports(getExports) { + return (tree) => { + let exports = Object.entries(getExports(tree)); + + for (let [name, value] of exports) { + for (let node of tree.children) { + if ( + node.type === "mdxjsEsm" && + new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value) + ) { + return; + } + } + + let exportStr = `export const ${name} = ${value}`; + + tree.children.push({ + type: "mdxjsEsm", + value: exportStr, + data: { + estree: acorn.parse(exportStr, { + sourceType: "module", + ecmaVersion: "latest", + }), + }, + }); + } + }; +} + +function getSections(node) { + let sections = []; + + for (let child of node.children ?? []) { + if (child.type === "element" && child.tagName === "h2") { + sections.push(`{ + title: ${JSON.stringify(toString(child))}, + id: ${JSON.stringify(child.properties.id)}, + ...${child.properties.annotation} + }`); + } else if (child.children) { + sections.push(...getSections(child)); + } + } + + return sections; +} + +export const rehypePlugins = [ + mdxAnnotations.rehype, + rehypeParseCodeBlocks, + rehypeShiki, + rehypeSlugify, + [ + rehypeAddMDXExports, + (tree) => ({ + sections: `[${getSections(tree).join()}]`, + }), + ], +]; diff --git a/web/src/mdx/remark.mjs b/web/src/mdx/remark.mjs new file mode 100644 index 0000000..e523464 --- /dev/null +++ b/web/src/mdx/remark.mjs @@ -0,0 +1,4 @@ +import { mdxAnnotations } from 'mdx-annotations' +import remarkGfm from 'remark-gfm' + +export const remarkPlugins = [mdxAnnotations.remark, remarkGfm] diff --git a/web/src/mdx/search.mjs b/web/src/mdx/search.mjs new file mode 100644 index 0000000..c9dcf4c --- /dev/null +++ b/web/src/mdx/search.mjs @@ -0,0 +1,137 @@ +import { slugifyWithCounter } from "@sindresorhus/slugify"; +import glob from "fast-glob"; +import * as fs from "fs"; +import { toString } from "mdast-util-to-string"; +import * as path from "path"; +import { remark } from "remark"; +import remarkMdx from "remark-mdx"; +import { createLoader } from "simple-functional-loader"; +import { filter } from "unist-util-filter"; +import { SKIP, visit } from "unist-util-visit"; +import * as url from "url"; + +const __filename = url.fileURLToPath(import.meta.url); +const processor = remark().use(remarkMdx).use(extractSections); +const slugify = slugifyWithCounter(); + +function isObjectExpression(node) { + return ( + node.type === "mdxTextExpression" && + node.data?.estree?.body?.[0]?.expression?.type === "ObjectExpression" + ); +} + +function excludeObjectExpressions(tree) { + return filter(tree, (node) => !isObjectExpression(node)); +} + +function extractSections() { + return (tree, { sections }) => { + slugify.reset(); + + visit(tree, (node) => { + if (node.type === "heading" || node.type === "paragraph") { + let content = toString(excludeObjectExpressions(node)); + if (node.type === "heading" && node.depth <= 2) { + let hash = node.depth === 1 ? null : slugify(content); + sections.push([content, hash, []]); + } else { + sections.at(-1)?.[2].push(content); + } + return SKIP; + } + }); + }; +} + +export default function (nextConfig = {}) { + let cache = new Map(); + + return Object.assign({}, nextConfig, { + webpack(config, options) { + config.module.rules.push({ + test: __filename, + use: [ + createLoader(function () { + let appDir = path.resolve("./src/app/(docs)/docs"); + this.addContextDependency(appDir); + + let files = glob.sync("**/*.mdx", { cwd: appDir }); + let data = files.map((file) => { + let url = `/${file.replace(/(^|\/)page\.mdx$/, "")}`; + let mdx = fs.readFileSync(path.join(appDir, file), "utf8"); + + let sections = []; + + if (cache.get(file)?.[0] === mdx) { + sections = cache.get(file)[1]; + } else { + let vfile = { value: mdx, sections }; + processor.runSync(processor.parse(vfile), vfile); + cache.set(file, [mdx, sections]); + } + + url = `/docs/${url}`; + + return { url, sections }; + }); + + // When this file is imported within the application + // the following module is loaded: + return ` + import FlexSearch from 'flexsearch' + + let sectionIndex = new FlexSearch.Document({ + tokenize: 'full', + document: { + id: 'url', + index: 'content', + store: ['title', 'pageTitle'], + }, + context: { + resolution: 9, + depth: 2, + bidirectional: true + } + }) + + let data = ${JSON.stringify(data)} + + for (let { url, sections } of data) { + for (let [title, hash, content] of sections) { + sectionIndex.add({ + url: url + (hash ? ('#' + hash) : ''), + title, + content: [title, ...content].join('\\n'), + pageTitle: hash ? sections[0][0] : undefined, + }) + } + } + + export function search(query, options = {}) { + let result = sectionIndex.search(query, { + ...options, + enrich: true, + }) + if (result.length === 0) { + return [] + } + return result[0].result.map((item) => ({ + url: item.id, + title: item.doc.title, + pageTitle: item.doc.pageTitle, + })) + } + `; + }), + ], + }); + + if (typeof nextConfig.webpack === "function") { + return nextConfig.webpack(config, options); + } + + return config; + }, + }); +} diff --git a/web/src/middleware.ts b/web/src/middleware.ts index 612e677..49b26d2 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -5,7 +5,7 @@ import { authMiddleware, redirectToSignIn } from "@clerk/nextjs"; // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware export default authMiddleware({ // debug: true, - publicRoutes: ['/',"/api/(.*)"], + publicRoutes: ["/", "/api/(.*)", "/docs(.*)"], // publicRoutes: ["/", "/(.*)"], async afterAuth(auth, req, evt) { // redirect them to organization selection page diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts index 18b63cf..b65de4c 100644 --- a/web/tailwind.config.ts +++ b/web/tailwind.config.ts @@ -1,14 +1,29 @@ +import typographyStyles from "./typography"; +import headlessuiPlugin from "@headlessui/tailwindcss"; +import typographyPlugin from "@tailwindcss/typography"; import type { Config } from "tailwindcss"; const config: Config = { darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], + content: ["./src/**/*.{js,mjs,jsx,ts,tsx,mdx}"], theme: { + fontSize: { + "2xs": ["0.75rem", { lineHeight: "1.25rem" }], + xs: ["0.8125rem", { lineHeight: "1.5rem" }], + sm: ["0.875rem", { lineHeight: "1.5rem" }], + base: ["1rem", { lineHeight: "1.75rem" }], + lg: ["1.125rem", { lineHeight: "1.75rem" }], + xl: ["1.25rem", { lineHeight: "1.75rem" }], + "2xl": ["1.5rem", { lineHeight: "2rem" }], + "3xl": ["1.875rem", { lineHeight: "2.25rem" }], + "4xl": ["2.25rem", { lineHeight: "2.5rem" }], + "5xl": ["3rem", { lineHeight: "1" }], + "6xl": ["3.75rem", { lineHeight: "1" }], + "7xl": ["4.5rem", { lineHeight: "1" }], + "8xl": ["6rem", { lineHeight: "1" }], + "9xl": ["8rem", { lineHeight: "1" }], + }, + typography: typographyStyles, container: { center: true, padding: "2rem", @@ -66,22 +81,37 @@ const config: Config = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, - 'background-shine': { + "background-shine": { from: { - backgroundPosition: '0 0', + backgroundPosition: "0 0", }, to: { - backgroundPosition: '-200% 0', + backgroundPosition: "-200% 0", }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", - 'background-shine': 'background-shine 2s linear infinite', + "background-shine": "background-shine 2s linear infinite", + }, + boxShadow: { + glow: "0 0 4px rgb(0 0 0 / 0.1)", + }, + maxWidth: { + lg: "33rem", + "2xl": "40rem", + "3xl": "50rem", + "5xl": "66rem", + }, + opacity: { + 1: "0.01", + 2.5: "0.025", + 7.5: "0.075", + 15: "0.15", }, }, }, - plugins: [require("tailwindcss-animate")], + plugins: [require("tailwindcss-animate"), typographyPlugin, headlessuiPlugin], }; export default config; diff --git a/web/types.d.ts b/web/types.d.ts new file mode 100644 index 0000000..2a23707 --- /dev/null +++ b/web/types.d.ts @@ -0,0 +1,11 @@ +import { type SearchOptions } from 'flexsearch' + +declare module '@/mdx/search.mjs' { + export type Result = { + url: string + title: string + pageTitle?: string + } + + export function search(query: string, options?: SearchOptions): Array +} diff --git a/web/typography.ts b/web/typography.ts new file mode 100644 index 0000000..65e7700 --- /dev/null +++ b/web/typography.ts @@ -0,0 +1,353 @@ +import { type PluginUtils } from "tailwindcss/types/config"; + +export default function typographyStyles({ theme }: PluginUtils) { + return { + DEFAULT: { + css: { + "--tw-prose-body": theme("colors.zinc.700"), + "--tw-prose-headings": theme("colors.zinc.900"), + "--tw-prose-links": theme("colors.emerald.500"), + "--tw-prose-links-hover": theme("colors.emerald.600"), + "--tw-prose-links-underline": theme("colors.emerald.500 / 0.3"), + "--tw-prose-bold": theme("colors.zinc.900"), + "--tw-prose-counters": theme("colors.zinc.500"), + "--tw-prose-bullets": theme("colors.zinc.300"), + "--tw-prose-hr": theme("colors.zinc.900 / 0.05"), + "--tw-prose-quotes": theme("colors.zinc.900"), + "--tw-prose-quote-borders": theme("colors.zinc.200"), + "--tw-prose-captions": theme("colors.zinc.500"), + "--tw-prose-code": theme("colors.zinc.900"), + "--tw-prose-code-bg": theme("colors.zinc.100"), + "--tw-prose-code-ring": theme("colors.zinc.300"), + "--tw-prose-th-borders": theme("colors.zinc.300"), + "--tw-prose-td-borders": theme("colors.zinc.200"), + + "--tw-prose-invert-body": theme("colors.zinc.400"), + "--tw-prose-invert-headings": theme("colors.white"), + "--tw-prose-invert-links": theme("colors.emerald.400"), + "--tw-prose-invert-links-hover": theme("colors.emerald.500"), + "--tw-prose-invert-links-underline": theme("colors.emerald.500 / 0.3"), + "--tw-prose-invert-bold": theme("colors.white"), + "--tw-prose-invert-counters": theme("colors.zinc.400"), + "--tw-prose-invert-bullets": theme("colors.zinc.600"), + "--tw-prose-invert-hr": theme("colors.white / 0.05"), + "--tw-prose-invert-quotes": theme("colors.zinc.100"), + "--tw-prose-invert-quote-borders": theme("colors.zinc.700"), + "--tw-prose-invert-captions": theme("colors.zinc.400"), + "--tw-prose-invert-code": theme("colors.white"), + "--tw-prose-invert-code-bg": theme("colors.zinc.700 / 0.15"), + "--tw-prose-invert-code-ring": theme("colors.white / 0.1"), + "--tw-prose-invert-th-borders": theme("colors.zinc.600"), + "--tw-prose-invert-td-borders": theme("colors.zinc.700"), + + // Base + color: "var(--tw-prose-body)", + fontSize: theme("fontSize.sm")[0], + lineHeight: theme("lineHeight.7"), + + // Text + p: { + marginTop: theme("spacing.6"), + marginBottom: theme("spacing.6"), + }, + '[class~="lead"]': { + fontSize: theme("fontSize.base")[0], + ...theme("fontSize.base")[1], + }, + + // Lists + ol: { + listStyleType: "decimal", + marginTop: theme("spacing.5"), + marginBottom: theme("spacing.5"), + paddingLeft: "1.625rem", + }, + 'ol[type="A"]': { + listStyleType: "upper-alpha", + }, + 'ol[type="a"]': { + listStyleType: "lower-alpha", + }, + 'ol[type="A" s]': { + listStyleType: "upper-alpha", + }, + 'ol[type="a" s]': { + listStyleType: "lower-alpha", + }, + 'ol[type="I"]': { + listStyleType: "upper-roman", + }, + 'ol[type="i"]': { + listStyleType: "lower-roman", + }, + 'ol[type="I" s]': { + listStyleType: "upper-roman", + }, + 'ol[type="i" s]': { + listStyleType: "lower-roman", + }, + 'ol[type="1"]': { + listStyleType: "decimal", + }, + ul: { + listStyleType: "disc", + marginTop: theme("spacing.5"), + marginBottom: theme("spacing.5"), + paddingLeft: "1.625rem", + }, + li: { + marginTop: theme("spacing.2"), + marginBottom: theme("spacing.2"), + }, + ":is(ol, ul) > li": { + paddingLeft: theme("spacing[1.5]"), + }, + "ol > li::marker": { + fontWeight: "400", + color: "var(--tw-prose-counters)", + }, + "ul > li::marker": { + color: "var(--tw-prose-bullets)", + }, + "> ul > li p": { + marginTop: theme("spacing.3"), + marginBottom: theme("spacing.3"), + }, + "> ul > li > *:first-child": { + marginTop: theme("spacing.5"), + }, + "> ul > li > *:last-child": { + marginBottom: theme("spacing.5"), + }, + "> ol > li > *:first-child": { + marginTop: theme("spacing.5"), + }, + "> ol > li > *:last-child": { + marginBottom: theme("spacing.5"), + }, + "ul ul, ul ol, ol ul, ol ol": { + marginTop: theme("spacing.3"), + marginBottom: theme("spacing.3"), + }, + + // Horizontal rules + hr: { + borderColor: "var(--tw-prose-hr)", + borderTopWidth: 1, + marginTop: theme("spacing.16"), + marginBottom: theme("spacing.16"), + maxWidth: "none", + marginLeft: `calc(-1 * ${theme("spacing.4")})`, + marginRight: `calc(-1 * ${theme("spacing.4")})`, + "@screen sm": { + marginLeft: `calc(-1 * ${theme("spacing.6")})`, + marginRight: `calc(-1 * ${theme("spacing.6")})`, + }, + "@screen lg": { + marginLeft: `calc(-1 * ${theme("spacing.8")})`, + marginRight: `calc(-1 * ${theme("spacing.8")})`, + }, + }, + + // Quotes + blockquote: { + fontWeight: "500", + fontStyle: "italic", + color: "var(--tw-prose-quotes)", + borderLeftWidth: "0.25rem", + borderLeftColor: "var(--tw-prose-quote-borders)", + quotes: '"\\201C""\\201D""\\2018""\\2019"', + marginTop: theme("spacing.8"), + marginBottom: theme("spacing.8"), + paddingLeft: theme("spacing.5"), + }, + "blockquote p:first-of-type::before": { + content: "open-quote", + }, + "blockquote p:last-of-type::after": { + content: "close-quote", + }, + + // Headings + h1: { + color: "var(--tw-prose-headings)", + fontWeight: "700", + fontSize: theme("fontSize.2xl")[0], + ...theme("fontSize.2xl")[1], + marginBottom: theme("spacing.2"), + }, + h2: { + color: "var(--tw-prose-headings)", + fontWeight: "600", + fontSize: theme("fontSize.lg")[0], + ...theme("fontSize.lg")[1], + marginTop: theme("spacing.16"), + marginBottom: theme("spacing.2"), + }, + h3: { + color: "var(--tw-prose-headings)", + fontSize: theme("fontSize.base")[0], + ...theme("fontSize.base")[1], + fontWeight: "600", + marginTop: theme("spacing.10"), + marginBottom: theme("spacing.2"), + }, + + // Media + // img: { + // marginTop: theme("spacing.2"), + // marginBottom: theme("spacing.2"), + // }, + "video, figure": { + marginTop: theme("spacing.8"), + marginBottom: theme("spacing.8"), + }, + "figure > *": { + marginTop: "0", + marginBottom: "0", + }, + figcaption: { + color: "var(--tw-prose-captions)", + fontSize: theme("fontSize.xs")[0], + ...theme("fontSize.xs")[1], + marginTop: theme("spacing.2"), + }, + + // Tables + table: { + width: "100%", + tableLayout: "auto", + textAlign: "left", + marginTop: theme("spacing.8"), + marginBottom: theme("spacing.8"), + lineHeight: theme("lineHeight.6"), + }, + thead: { + borderBottomWidth: "1px", + borderBottomColor: "var(--tw-prose-th-borders)", + }, + "thead th": { + color: "var(--tw-prose-headings)", + fontWeight: "600", + verticalAlign: "bottom", + paddingRight: theme("spacing.2"), + paddingBottom: theme("spacing.2"), + paddingLeft: theme("spacing.2"), + }, + "thead th:first-child": { + paddingLeft: "0", + }, + "thead th:last-child": { + paddingRight: "0", + }, + "tbody tr": { + borderBottomWidth: "1px", + borderBottomColor: "var(--tw-prose-td-borders)", + }, + "tbody tr:last-child": { + borderBottomWidth: "0", + }, + "tbody td": { + verticalAlign: "baseline", + }, + tfoot: { + borderTopWidth: "1px", + borderTopColor: "var(--tw-prose-th-borders)", + }, + "tfoot td": { + verticalAlign: "top", + }, + ":is(tbody, tfoot) td": { + paddingTop: theme("spacing.2"), + paddingRight: theme("spacing.2"), + paddingBottom: theme("spacing.2"), + paddingLeft: theme("spacing.2"), + }, + ":is(tbody, tfoot) td:first-child": { + paddingLeft: "0", + }, + ":is(tbody, tfoot) td:last-child": { + paddingRight: "0", + }, + + // Inline elements + a: { + color: "var(--tw-prose-links)", + textDecoration: "underline transparent", + fontWeight: "500", + transitionProperty: "color, text-decoration-color", + transitionDuration: theme("transitionDuration.DEFAULT"), + transitionTimingFunction: theme("transitionTimingFunction.DEFAULT"), + "&:hover": { + color: "var(--tw-prose-links-hover)", + textDecorationColor: "var(--tw-prose-links-underline)", + }, + }, + ":is(h1, h2, h3) a": { + fontWeight: "inherit", + }, + strong: { + color: "var(--tw-prose-bold)", + fontWeight: "600", + }, + ":is(a, blockquote, thead th) strong": { + color: "inherit", + }, + code: { + color: "var(--tw-prose-code)", + borderRadius: theme("borderRadius.lg"), + paddingTop: theme("padding.1"), + paddingRight: theme("padding[1.5]"), + paddingBottom: theme("padding.1"), + paddingLeft: theme("padding[1.5]"), + boxShadow: "inset 0 0 0 1px var(--tw-prose-code-ring)", + backgroundColor: "var(--tw-prose-code-bg)", + fontSize: theme("fontSize.2xs"), + }, + ":is(a, h1, h2, h3, blockquote, thead th) code": { + color: "inherit", + }, + "h2 code": { + fontSize: theme("fontSize.base")[0], + fontWeight: "inherit", + }, + "h3 code": { + fontSize: theme("fontSize.sm")[0], + fontWeight: "inherit", + }, + + // Overrides + ":is(h1, h2, h3) + *": { + marginTop: "0", + }, + "> :first-child": { + marginTop: "0 !important", + }, + "> :last-child": { + marginBottom: "0 !important", + }, + }, + }, + invert: { + css: { + "--tw-prose-body": "var(--tw-prose-invert-body)", + "--tw-prose-headings": "var(--tw-prose-invert-headings)", + "--tw-prose-links": "var(--tw-prose-invert-links)", + "--tw-prose-links-hover": "var(--tw-prose-invert-links-hover)", + "--tw-prose-links-underline": "var(--tw-prose-invert-links-underline)", + "--tw-prose-bold": "var(--tw-prose-invert-bold)", + "--tw-prose-counters": "var(--tw-prose-invert-counters)", + "--tw-prose-bullets": "var(--tw-prose-invert-bullets)", + "--tw-prose-hr": "var(--tw-prose-invert-hr)", + "--tw-prose-quotes": "var(--tw-prose-invert-quotes)", + "--tw-prose-quote-borders": "var(--tw-prose-invert-quote-borders)", + "--tw-prose-captions": "var(--tw-prose-invert-captions)", + "--tw-prose-code": "var(--tw-prose-invert-code)", + "--tw-prose-code-bg": "var(--tw-prose-invert-code-bg)", + "--tw-prose-code-ring": "var(--tw-prose-invert-code-ring)", + "--tw-prose-th-borders": "var(--tw-prose-invert-th-borders)", + "--tw-prose-td-borders": "var(--tw-prose-invert-td-borders)", + }, + }, + }; +}