/* Copyright 2026 Marimo. All rights reserved. */ import { TriangleIcon } from "lucide-react"; import type { JSX } from "react"; import { useLocale } from "react-aria"; import { z } from "zod"; import { getMimeValues } from "@/components/data-table/mime-cell"; import { cn } from "@/utils/cn"; import { Logger } from "@/utils/Logger"; import { prettyNumber } from "@/utils/numbers"; import { renderHTML } from "../core/RenderHTML"; import type { IStatelessPlugin, IStatelessPluginProps, } from "../stateless-plugin"; interface Data { value?: string | number | boolean | null; label?: string; caption?: string; bordered?: boolean; direction?: "increase" | "decrease"; target_direction?: "increase" | "decrease"; slot?: object; } export class StatPlugin implements IStatelessPlugin { tagName = "marimo-stat"; validator = z.object({ value: z.union([z.string(), z.number(), z.boolean()]).optional(), label: z.string().optional(), caption: z.string().optional(), bordered: z.boolean().default(false), direction: z.enum(["increase", "decrease"]).optional(), target_direction: z.enum(["increase", "decrease"]).default("increase"), slot: z.any().optional(), }); render({ data }: IStatelessPluginProps): JSX.Element { return ; } } export const StatComponent: React.FC = ({ value, label, caption, bordered, direction, target_direction, slot, }) => { const { locale } = useLocale(); const renderPrettyValue = () => { if (value == null) { return No value; } if (typeof value === "string") { return value; } if (typeof value === "number") { return prettyNumber(value, locale); } if (typeof value === "boolean") { return value ? "True" : "False"; } return String(value); }; const onTarget = direction === target_direction; const fillColor = onTarget ? "var(--grass-8)" : "var(--red-8)"; const strokeColor = onTarget ? "var(--grass-9)" : "var(--red-9)"; const renderSlot = () => { const mimeValues = getMimeValues(slot); if (mimeValues?.[0]) { const { mimetype, data } = mimeValues[0]; if (mimetype !== "text/html") { Logger.warn(`Expected text/html, got ${mimetype}`); } return renderHTML({ html: data, alwaysSanitizeHtml: true }); } }; return (
{label && (

{label}

)}
{renderPrettyValue()}
{caption && (

{direction === "increase" && ( )} {direction === "decrease" && ( )} {caption}

)}
{slot &&
{renderSlot()}
}
); };