import { existsSync } from "node:fs"; import { join } from "node:path"; import { getImageDimensions } from "@/lib/image-dimensions"; import { cn } from "@/lib/utils"; import { Button } from "@openstatus/ui"; import { MDXRemote, type MDXRemoteProps } from "next-mdx-remote/rsc"; import Image from "next/image"; import Link from "next/link"; import React from "react"; import { Tweet, type TweetProps } from "react-tweet"; import { highlight } from "sugar-high"; import { CopyButton } from "./copy-button"; import { HighlightText } from "./highlight-text"; import { ImageZoom } from "./image-zoom"; import { LatencyChartTable } from "./latency-chart-table"; function Table({ data, }: { data: { headers: React.ReactNode[]; rows: React.ReactNode[][] }; }) { const headers = data.headers.map((header: React.ReactNode, index: number) => ( {header} )); const rows = data.rows.map((row: React.ReactNode[], index: number) => ( {row.map((cell: React.ReactNode, cellIndex: number) => ( {cell} ))} )); return (
{headers}{rows}
); } function Grid({ cols = 2, children, className, }: { cols?: 1 | 2 | 3 | 4 | 5; children: React.ReactNode; className?: string; }) { const colsClass = { 1: "md:grid-cols-1", 2: "md:grid-cols-2", 3: "md:grid-cols-3", 4: "md:grid-cols-4", 5: "md:grid-cols-5", }; // Remove top border from all except first row const topBorderClass = { 1: "[&>*]:border-t-0 [&>*:first-child]:border-t", 2: "[&>*]:border-t-0 [&>*:first-child]:border-t md:[&>*:nth-child(-n+2)]:border-t", 3: "[&>*]:border-t-0 [&>*:first-child]:border-t md:[&>*:nth-child(-n+3)]:border-t", 4: "[&>*]:border-t-0 [&>*:first-child]:border-t md:[&>*:nth-child(-n+4)]:border-t", 5: "[&>*]:border-t-0 [&>*:first-child]:border-t md:[&>*:nth-child(-n+5)]:border-t", }; // Remove left border from all except first column (only on md+ screens) const leftBorderClass = { 1: "", 2: "md:[&>*]:border-l-0 md:[&>*:nth-child(2n+1)]:border-l", 3: "md:[&>*]:border-l-0 md:[&>*:nth-child(3n+1)]:border-l", 4: "md:[&>*]:border-l-0 md:[&>*:nth-child(4n+1)]:border-l", 5: "md:[&>*]:border-l-0 md:[&>*:nth-child(5n+1)]:border-l", }; return (
*]:border [&>*]:border-border [&>*]:p-4", // NOTE: remove extra margin from prose grid cells of first and last element "[&>*>*:first-child]:!mt-0 [&>*>*:last-child]:!mb-0", colsClass[cols], topBorderClass[cols], leftBorderClass[cols], className, )} > {children}
); } function CustomLink(props: React.ComponentProps<"a">) { const href = props.href ?? ""; if (href.startsWith("/")) { return ( {props.children} ); } if (href.startsWith("#")) { return ; } return ; } function ButtonLink( props: React.ComponentProps & { href: string }, ) { return ( ); } function Code({ children, className, ...props }: React.ComponentProps<"code">) { // Only apply syntax highlighting if a language is specified (className contains "language-") const hasLanguage = className?.includes("language-"); if (hasLanguage) { const codeHTML = highlight(children?.toString() ?? ""); return ( dangerouslySetInnerHTML={{ __html: codeHTML }} className={className} {...props} /> ); } // Plain code block without language - render as-is return ( {children} ); } function extractTextFromReactNode(node: React.ReactNode): string { if (typeof node === "string") { return node; } if (typeof node === "number") { return String(node); } if (Array.isArray(node)) { return node.map(extractTextFromReactNode).join(""); } if (React.isValidElement(node)) { const props = node.props as { children?: React.ReactNode }; if (props.children) { return extractTextFromReactNode(props.children); } } return ""; } function Pre({ children, ...props }: React.ComponentProps<"pre">) { const textContent = extractTextFromReactNode(children); return (
{children}
); } export function slugify(str: string) { return str .toString() .toLowerCase() .trim() // Remove whitespace from both ends of a string .replace(/\s+/g, "-") // Replace spaces with - .replace(/&/g, "-and-") // Replace & with 'and' .replace(/[^\w-]+/g, "") // Remove all non-word characters except for - .replace(/--+/g, "-"); // Replace multiple - with single - } function createHeading(level: number) { const Heading = ({ children }: { children: React.ReactNode }) => { const slug = slugify(children?.toString() ?? ""); return React.createElement( `h${level}`, { id: slug }, [ React.createElement("a", { href: `#${slug}`, key: `link-${slug}`, className: "anchor", }), ], children, ); }; Heading.displayName = `Heading${level}`; return Heading; } function Details({ children, summary, open = false, }: { children: React.ReactNode; summary: string; open?: boolean; }) { return (
{summary} {React.isValidElement(children) ? // biome-ignore lint/suspicious/noExplicitAny: React.cloneElement(children, { hidden: "until-found" } as any) : children}
); } function CustomImage({ className, ...props }: React.ComponentProps) { const { src, alt, width, height, ...rest } = props; if (!src || typeof src !== "string") { return (
{alt
{alt}
); } // Get actual image dimensions from filesystem const dimensions = getImageDimensions(src); const imageWidth = width || dimensions?.width || 1200; const imageHeight = height || dimensions?.height || 630; // Generate dark mode image path by adding .dark before extension const getDarkImagePath = (path: string) => { const match = path.match(/^(.+)(\.[^.]+)$/); if (match) { return `${match[1]}.dark${match[2]}`; } return path; }; // Check if dark image exists, fallback to light version if not const checkDarkImageExists = (darkPath: string) => { // If path starts with /, it's in the public directory if (darkPath.startsWith("/")) { const publicPath = join(process.cwd(), "public", darkPath); return existsSync(publicPath); } // For relative paths, check relative to public const publicPath = join(process.cwd(), "public", darkPath); return existsSync(publicPath); }; const darkSrc = getDarkImagePath(src); const useDarkImage = checkDarkImageExists(darkSrc); return (
{alt {alt {alt &&
{alt}
}
); } export const components = { h1: createHeading(1), h2: createHeading(2), h3: createHeading(3), h4: createHeading(4), h5: createHeading(5), h6: createHeading(6), Image: CustomImage, a: CustomLink, ButtonLink: ButtonLink, code: Code, pre: Pre, Table, Grid, Details, // Capital D for JSX usage with props details: Details, // lowercase for HTML tag replacement SimpleChart: LatencyChartTable, Tweet: (props: TweetProps) => { return (
); }, }; function MDXContent(props: MDXRemoteProps) { return ( ); } export function CustomMDX(props: MDXRemoteProps) { return ( }> ); }