import { Check, Copy, DownloadSimple, FileText, Info, Warning, X } from "@phosphor-icons/react";
import { useState, type ReactNode } from "react";
import { cn } from "../../lib/cn";
import { IconButton } from "../actions/icon-button";
export type AdvancedBlockTone = "info" | "success" | "warning" | "error";
function useCopy(value: string, onCopy?: (value: string) => void) {
const [copied, setCopied] = useState(false);
async function copy() {
onCopy?.(value);
if (typeof navigator !== "undefined" && navigator.clipboard) {
await navigator.clipboard.writeText(value);
}
setCopied(true);
window.setTimeout(() => setCopied(false), 1400);
}
return { copied, copy };
}
function CopyControl({ value, onCopy }: { value: string; onCopy?: (value: string) => void }) {
const { copied, copy } = useCopy(value, onCopy);
return (
: }
label={copied ? "Copied" : "Copy content"}
onClick={copy}
title={copied ? "Copied" : "Copy content"}
/>
);
}
function BlockFrame({
actions,
children,
className,
eyebrow,
title,
}: {
actions?: ReactNode;
children: ReactNode;
className?: string;
eyebrow?: string;
title?: ReactNode;
}) {
return (
{eyebrow || title || actions ? (
{eyebrow ?
{eyebrow}
: null}
{title ?
{title}
: null}
{actions ? {actions}
: null}
) : null}
{children}
);
}
export function CodeBlock({
code,
language,
onCopy,
title,
}: {
code: string;
language?: string;
onCopy?: (value: string) => void;
title?: ReactNode;
}) {
return (
} eyebrow={language || "Code"} title={title}>
{code}
);
}
export function MathBlock({
display = true,
formula,
onCopy,
title = "Formula",
}: {
display?: boolean;
formula: string;
onCopy?: (value: string) => void;
title?: ReactNode;
}) {
return (
} eyebrow="Math" title={title}>
{formula}
);
}
export function DiagramBlock({
language = "diagram",
onCopy,
source,
title = "Diagram",
}: {
language?: string;
onCopy?: (value: string) => void;
source: string;
title?: ReactNode;
}) {
return (
} eyebrow={language} title={title}>
{source}
);
}
export function CalloutBlock({
children,
title,
tone = "info",
}: {
children: ReactNode;
title?: ReactNode;
tone?: AdvancedBlockTone;
}) {
const icons = { error: , info: , success: , warning: };
return (
{icons[tone]}
{title ?
{title}
: null}
{children}
);
}
export function QuoteBlock({ children, cite }: { children: ReactNode; cite?: ReactNode }) {
return {children}
{cite ? : null}
;
}
export function TableBlock({
headers,
rows,
}: {
headers: ReactNode[];
rows: ReactNode[][];
}) {
return (
{headers.map((header, index) => | {header} | )}
{rows.map((row, rowIndex) => {row.map((cell, cellIndex) => | {cell} | )}
)}
);
}
export function StepsBlock({ steps }: { steps: Array<{ description?: ReactNode; title: ReactNode }> }) {
return {steps.map((step, index) => - {index + 1}
{step.title}
{step.description ?
{step.description}
: null}
)}
;
}
export function ChecklistBlock({ items }: { items: Array<{ checked?: boolean; label: ReactNode }> }) {
return {items.map((item, index) => - {item.checked ? : null}{item.label}
)}
;
}
export function KeyValueBlock({ items }: { items: Array<{ label: ReactNode; value: ReactNode }> }) {
return {items.map((item, index) => - {item.label}
- {item.value}
)}
;
}
export function TimelineBlock({ items }: { items: Array<{ description?: ReactNode; title: ReactNode; time?: ReactNode }> }) {
return {items.map((item, index) => - {index < items.length - 1 ? : null}
{item.title}
{item.time ?
: null}
{item.description ?
{item.description}
: null}
)}
;
}
export function FileBlock({
download,
meta,
name,
onDownload,
}: {
download?: string;
meta?: ReactNode;
name: ReactNode;
onDownload?: () => void;
}) {
return {name}
{meta ?
{meta}
: null}
{download || onDownload ?
: null}
;
}
export function MediaBlock({ alt, src, type = "image" }: { alt?: string; src: string; type?: "audio" | "image" | "video" }) {
if (type === "video") return ;
if (type === "audio") return ;
return
;
}
export function EmbedBlock({ href, title }: { href: string; title?: ReactNode }) {
return {title ?? href};
}