diff --git a/docs/bun.lock b/docs/bun.lock index bb8426a..6ae8b47 100644 --- a/docs/bun.lock +++ b/docs/bun.lock @@ -9,10 +9,15 @@ "@fontsource-variable/dm-sans": "latest", "@fontsource-variable/instrument-sans": "latest", "@fontsource-variable/jetbrains-mono": "latest", + "@fuma-translate/react": "^1.0.2", + "@fumadocs/base-ui": "^16.11.3", + "class-variance-authority": "^0.7.1", + "cnfast": "^0.0.8", "fumadocs-core": "latest", "fumadocs-mdx": "latest", "fumadocs-ui": "npm:@fumadocs/base-ui@latest", "fumapress": "latest", + "lucide-react": "^1.23.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-server-dom-webpack": "^19.2.0", @@ -123,6 +128,8 @@ "@fuma-translate/react": ["@fuma-translate/react@1.0.2", "", { "peerDependencies": { "@types/react": "*", "react": "^19.2.0", "react-dom": "^19.2.0" }, "optionalPeers": ["@types/react"] }, "sha512-uOiOtBx3nRXR8Nu1GzBf1tApgF1FErDBTHxRIAQeyQdyOoZbrNRN6H4kDCWObY4qyGeGbHydG0DHzgeUgFDMIw=="], + "@fumadocs/base-ui": ["@fumadocs/base-ui@16.11.3", "", { "dependencies": { "@base-ui/react": "^1.6.0", "@fuma-translate/react": "^1.0.2", "@fumadocs/tailwind": "0.1.1", "class-variance-authority": "^0.7.1", "cnfast": "^0.0.8", "lucide-react": "^1.23.0", "motion": "^12.42.2", "next-themes": "^0.4.6", "react-remove-scroll": "^2.7.2", "rehype-raw": "^7.0.0", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^4.3.1", "unist-util-visit": "^5.1.0" }, "peerDependencies": { "@types/mdx": "*", "@types/react": "*", "fumadocs-core": "16.11.3", "next": "16.x.x", "react": "^19.2.0", "react-dom": "^19.2.0", "takumi-js": "*" }, "optionalPeers": ["@types/mdx", "@types/react", "next", "takumi-js"] }, "sha512-O3xELlkEXAEEbkfR1EzD2XIvrxxe5sJSNpCrSF2+uuJwX7qObDcvtGPsDNJzwOsynOqJYPsOvrG1kLkAILeasw=="], + "@fumadocs/tailwind": ["@fumadocs/tailwind@0.1.1", "", { "peerDependencies": { "tailwindcss": "^4.0.0" }, "optionalPeers": ["tailwindcss"] }, "sha512-BnPe52UxSaG8yKlHMKBxXw8h6GpK5qO55ci6+Qd5JnquTvIw6SpfbC1P+qAi82PuPWv1KZAWY8bxRk4+x9ctXw=="], "@hono/node-server": ["@hono/node-server@2.0.8", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GuCWzLxwg218fy1JaHculFsdcuY12hxit83V+algozTPnwhNjLrRL/Alg9OYjLZLoUZ1rw/S4CdTMsnkSKCmFA=="], diff --git a/docs/cli.json b/docs/cli.json new file mode 100644 index 0000000..5d3a3d3 --- /dev/null +++ b/docs/cli.json @@ -0,0 +1,14 @@ +{ + "$schema": "node_modules/@fumadocs/cli/dist/schema.json", + "aliases": { + "uiDir": "./components/ui", + "componentsDir": "./components", + "layoutDir": "./layouts", + "cssDir": "./styles", + "libDir": "./lib" + }, + "baseDir": "src", + "uiLibrary": "base-ui", + "framework": "waku", + "commands": {} +} \ No newline at end of file diff --git a/docs/package.json b/docs/package.json index db24e6e..1f88e46 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,10 +13,15 @@ "@fontsource-variable/dm-sans": "latest", "@fontsource-variable/instrument-sans": "latest", "@fontsource-variable/jetbrains-mono": "latest", + "@fuma-translate/react": "^1.0.2", + "@fumadocs/base-ui": "^16.11.3", + "class-variance-authority": "^0.7.1", + "cnfast": "^0.0.8", "fumadocs-core": "latest", "fumadocs-mdx": "latest", "fumadocs-ui": "npm:@fumadocs/base-ui@latest", "fumapress": "latest", + "lucide-react": "^1.23.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-server-dom-webpack": "^19.2.0", diff --git a/docs/press.config.tsx b/docs/press.config.tsx index 3a95531..ab960e6 100644 --- a/docs/press.config.tsx +++ b/docs/press.config.tsx @@ -1,5 +1,10 @@ import { defineConfig } from "fumapress"; import { fumadocsMdx } from "fumapress/adapters/mdx"; +import defaultMdxComponents, { createRelativeLink } from "fumadocs-ui/mdx"; +import { TypeTable } from "fumadocs-ui/components/type-table"; +import { Step, Steps } from "fumadocs-ui/components/steps"; +import { Tabs, Tab } from "fumadocs-ui/components/tabs"; +import { Accordion, Accordions } from "fumadocs-ui/components/accordion"; import { flexsearchPlugin } from "fumapress/plugins/flexsearch"; import { llmsPlugin } from "fumapress/plugins/llms.txt"; import { docs } from "./.source/server"; @@ -9,11 +14,11 @@ export default defineConfig({ name: "Docs", baseUrl: import.meta.env.DEV ? "http://localhost:3000" - : "https://docs.example.com", + : "https://cs.prad.nu", git: { - user: "your-github-user", + user: "prdlk", branch: "main", - repo: "docs", + repo: "leetcode", }, }, content: { @@ -26,4 +31,21 @@ export default defineConfig({ }, }) .plugins(flexsearchPlugin(), llmsPlugin()) - .adapters(fumadocsMdx()); + .adapters( + fumadocsMdx({ + async getMdxComponents(page) { + const source = await this.getLoader(); + return { + ...defaultMdxComponents, + TypeTable, + Steps, + Step, + Tabs, + Tab, + Accordion, + Accordions, + a: createRelativeLink(source, page), + }; + }, + }), + ); diff --git a/docs/src/components/accordion.tsx b/docs/src/components/accordion.tsx new file mode 100644 index 0000000..e9d172b --- /dev/null +++ b/docs/src/components/accordion.tsx @@ -0,0 +1,105 @@ +'use client'; + +import { Check, LinkIcon } from 'lucide-react'; +import { type ComponentProps, type ReactNode, useEffect, useRef, useState } from 'react'; +import { cn } from '../lib/cn'; +import { useCopyButton } from '@fumadocs/base-ui/utils/use-copy-button'; +import { buttonVariants } from './ui/button'; +import { mergeRefs } from '../lib/merge-refs'; +import { useTranslations } from '@fuma-translate/react'; +import { + Accordion as Root, + AccordionContent, + AccordionHeader, + AccordionItem, + AccordionTrigger, +} from './ui/accordion'; + +export function Accordions({ + ref, + className, + defaultValue, + ...props +}: ComponentProps) { + const rootRef = useRef(null); + const composedRef = mergeRefs(ref, rootRef); + const [value, setValue] = useState(defaultValue ?? []); + + useEffect(() => { + const id = window.location.hash.substring(1); + const element = rootRef.current; + if (!element || id.length === 0) return; + + const selected = document.getElementById(id); + if (!selected || !element.contains(selected)) return; + const value = selected.getAttribute('data-accordion-value'); + + if (value) setValue((prev) => [value, ...prev]); + }, []); + + return ( + + cn( + 'divide-y divide-fd-border overflow-hidden rounded-lg border bg-fd-card', + typeof className === 'function' ? className(s) : className, + ) + } + {...props} + /> + ); +} + +export function Accordion({ + title, + id, + value = String(title), + children, + ...props +}: Omit, 'value' | 'title'> & { + title: string | ReactNode; + value?: string; +}) { + return ( + + + {title} + {id ? : null} + + +
+ {children} +
+
+
+ ); +} + +function CopyButton({ id }: { id: string }) { + const t = useTranslations({ note: 'accordion' }); + const [checked, onClick] = useCopyButton(() => { + const url = new URL(window.location.href); + url.hash = id; + + return navigator.clipboard.writeText(url.toString()); + }); + + return ( + + ); +} diff --git a/docs/src/components/steps.tsx b/docs/src/components/steps.tsx new file mode 100644 index 0000000..ce348a2 --- /dev/null +++ b/docs/src/components/steps.tsx @@ -0,0 +1,9 @@ +import type { ReactNode } from 'react'; + +export function Steps({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function Step({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/docs/src/components/ui/accordion.tsx b/docs/src/components/ui/accordion.tsx new file mode 100644 index 0000000..4a8d076 --- /dev/null +++ b/docs/src/components/ui/accordion.tsx @@ -0,0 +1,85 @@ +'use client'; + +import { Accordion as Primitive } from '@base-ui/react/accordion'; +import { ChevronRight } from 'lucide-react'; +import { type ComponentProps } from 'react'; +import { cn } from '../../lib/cn'; + +export function Accordion({ className, ...props }: ComponentProps) { + return ( + + cn( + 'divide-y divide-fd-border overflow-hidden rounded-lg border bg-fd-card', + typeof className === 'function' ? className(s) : className, + ) + } + {...props} + /> + ); +} + +export function AccordionItem({ children, ...props }: ComponentProps) { + return {children}; +} + +export function AccordionHeader({ + className, + children, + ...props +}: ComponentProps) { + return ( + + cn( + 'scroll-m-24 not-prose flex flex-row items-center text-fd-card-foreground font-medium has-focus-visible:bg-fd-accent', + typeof className === 'function' ? className(s) : className, + ) + } + {...props} + > + {children} + + ); +} + +export function AccordionTrigger({ + className, + children, + ...props +}: ComponentProps) { + return ( + + cn( + 'group flex flex-1 items-center gap-2 px-3 py-2.5 text-start focus-visible:outline-none', + typeof className === 'function' ? className(s) : className, + ) + } + {...props} + > + + {children} + + ); +} + +export function AccordionContent({ + className, + children, + ...props +}: ComponentProps) { + return ( + + cn( + 'h-(--accordion-panel-height) overflow-hidden transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0', + typeof className === 'function' ? className(s) : className, + ) + } + {...props} + > + {children} + + ); +} diff --git a/docs/src/components/ui/button.tsx b/docs/src/components/ui/button.tsx new file mode 100644 index 0000000..b427d4e --- /dev/null +++ b/docs/src/components/ui/button.tsx @@ -0,0 +1,28 @@ +import { cva, type VariantProps } from 'class-variance-authority'; + +const variants = { + primary: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80', + outline: 'border hover:bg-fd-accent hover:text-fd-accent-foreground', + ghost: 'hover:bg-fd-accent hover:text-fd-accent-foreground', + secondary: + 'border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground', +} as const; + +export const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring', + { + variants: { + variant: variants, + // fumadocs use `color` instead of `variant` + color: variants, + size: { + sm: 'gap-1 px-2 py-1.5 text-xs', + icon: 'p-1.5 [&_svg]:size-5', + 'icon-sm': 'p-1.5 [&_svg]:size-4.5', + 'icon-xs': 'p-1 [&_svg]:size-4', + }, + }, + }, +); + +export type ButtonProps = VariantProps; diff --git a/docs/src/lib/cn.ts b/docs/src/lib/cn.ts new file mode 100644 index 0000000..241ffb3 --- /dev/null +++ b/docs/src/lib/cn.ts @@ -0,0 +1 @@ +export { cn } from 'cnfast'; diff --git a/docs/src/lib/merge-refs.ts b/docs/src/lib/merge-refs.ts new file mode 100644 index 0000000..cf019f1 --- /dev/null +++ b/docs/src/lib/merge-refs.ts @@ -0,0 +1,13 @@ +import type * as React from 'react'; + +export function mergeRefs(...refs: (React.Ref | undefined)[]): React.RefCallback { + return (value) => { + refs.forEach((ref) => { + if (typeof ref === 'function') { + ref(value); + } else if (ref) { + ref.current = value; + } + }); + }; +}