ui: figures join the document — no card chrome, caption row below
Owner round: the figure card loses its container entirely (no border, no padding box) - the figure is document material. Image first, then the CAPTION ROW below it: 'Figure' label aligned to the image's left edge, the controls (invert toggle, download, fullscreen) on the same row aligned to its right edge. The wrapper carries the natural-sizing law, so for a declared-width svg the row hugs the centered figure's own edges, not the column's. The invert affordance: the house Toggle gains an optional icon disc - the figure's toggle renders lucide's Contrast (the half-filled circle) AS the sliding disc, plus a native 'Invert colors' tooltip; every plain toggle elsewhere is unchanged. Interactive demo cards keep their boxed stage (owner ruling: an interactive surface reads as its own stage).
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useContext, useState } from "react";
|
||||
import { ChatActionsContext } from "@/components/chat";
|
||||
import { DownloadIcon, MaximizeIcon } from "lucide-react";
|
||||
import { ContrastIcon, DownloadIcon, MaximizeIcon } from "lucide-react";
|
||||
import { Toggle } from "@/components/settings/controls";
|
||||
import { useSessionMermaid } from "@/components/session-overlay";
|
||||
import { downloadFile } from "@/components/artifacts/download";
|
||||
@@ -99,11 +99,47 @@ export function VisualOutputCard({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="rounded-xl border bg-card p-4 text-card-foreground" data-testid="visual-card">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
/* NO CARD CHROME (owner round, 2026-10-07): the figure is document
|
||||
material — no border, no padding box. Image first, then the CAPTION
|
||||
ROW below: "Figure" left, the controls right, both edges following
|
||||
the IMAGE's own width (the wrapper carries the natural-sizing law,
|
||||
so the row hugs a centered narrow figure, not the column). */
|
||||
<div
|
||||
className={
|
||||
natural !== null
|
||||
? "my-4 mx-auto w-fit max-w-full"
|
||||
: "my-4 w-full"
|
||||
}
|
||||
data-testid="visual-card"
|
||||
>
|
||||
{/* Natural sizing (owner, 2026-10-07): a declared-width svg sits at
|
||||
ITS size; max-w-full still shrinks oversized ones to fit. No
|
||||
declared size (viewBox-only, %-width) keeps the full-width law.
|
||||
The invert filter rides the bg-white holder, so the backing flips
|
||||
white→black together with the strokes; transparent areas ARE the
|
||||
backing. */}
|
||||
<div
|
||||
className={
|
||||
natural !== null
|
||||
? "w-fit max-w-full overflow-hidden rounded-lg bg-white [&_svg]:h-auto [&_svg]:max-w-full"
|
||||
: "w-full overflow-hidden rounded-lg bg-white [&_svg]:h-auto [&_svg]:w-full"
|
||||
}
|
||||
style={{ filter: inverted ? "invert(1) hue-rotate(180deg)" : undefined }}
|
||||
dangerouslySetInnerHTML={{ __html: sanitizeSvg(result.svg) }}
|
||||
data-testid="visual-svg"
|
||||
/>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">Figure</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Toggle checked={inverted} onChange={toggleInverted} label="Invert figure brightness" />
|
||||
{/* The invert affordance (owner round, 2026-10-07): the disc IS
|
||||
the half-filled contrast circle; the tooltip names it. */}
|
||||
<Toggle
|
||||
checked={inverted}
|
||||
onChange={toggleInverted}
|
||||
label="Invert figure brightness"
|
||||
title="Invert colors"
|
||||
discIcon={<ContrastIcon className="size-3" />}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Download figure as SVG"
|
||||
@@ -128,23 +164,6 @@ export function VisualOutputCard({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Natural sizing (owner, 2026-10-07): a declared-width svg sits at
|
||||
ITS size — the white figure area hugs it and centers (mx-auto),
|
||||
max-w-full still shrinks oversized ones to fit the card. No
|
||||
declared size (viewBox-only, %-width) keeps the old full-width
|
||||
law. The invert filter rides the bg-white holder, so the backing
|
||||
flips white→black together with the strokes; transparent areas
|
||||
ARE the backing. */}
|
||||
<div
|
||||
className={
|
||||
natural !== null
|
||||
? "mx-auto mt-2 w-fit max-w-full overflow-hidden rounded-lg bg-white [&_svg]:h-auto [&_svg]:max-w-full"
|
||||
: "mt-2 w-full overflow-hidden rounded-lg bg-white [&_svg]:h-auto [&_svg]:w-full"
|
||||
}
|
||||
style={{ filter: inverted ? "invert(1) hue-rotate(180deg)" : undefined }}
|
||||
dangerouslySetInnerHTML={{ __html: sanitizeSvg(result.svg) }}
|
||||
data-testid="visual-svg"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user