From 46411d2eec86237c8281a3e2a7f3b3a3221ca2cd Mon Sep 17 00:00:00 2001 From: Milan Bhandari Date: Tue, 27 Oct 2026 15:35:02 -0400 Subject: [PATCH 1/6] Pad animations and let them expand fullscreen Give the animation stage 16px of padding so a drawing no longer runs into the card's edges. The stage and its slot overlays sit in an inner box, so host code peeks still line up. Add an Expand button to the animation header. It portals the figure into a fullscreen overlay over the canvas (themed, Escape closes, canvas scroll locked), centres the drawing, and keeps a placeholder inline so the document holds its place. Focus moves to Close on open and back to Expand on close. The stage iframe now inherits the page's color-scheme instead of "normal". In dark themes the mismatch made Chromium paint an opaque backdrop behind the frame, which the new padding exposed. Agent-Session: 9433c530-a019-433a-95a7-428a25de521d --- .../app/src/animation/animation-block.tsx | 192 ++++++++++++++---- 1 file changed, 155 insertions(+), 37 deletions(-) diff --git a/packages/review/app/src/animation/animation-block.tsx b/packages/review/app/src/animation/animation-block.tsx index fd22ad0e0..27251ad5d 100644 --- a/packages/review/app/src/animation/animation-block.tsx +++ b/packages/review/app/src/animation/animation-block.tsx @@ -2,6 +2,8 @@ import type { BlockProps } from "@canvas/blocks"; import { CodePeekCard } from "@canvas/CodePeek"; import { useReviewDebugSettings } from "@canvas/debug-settings"; import { DiagramHeader } from "@canvas/diagram-header"; +import { diagramStyles } from "@canvas/diagram-styles"; +import { useDiagramTourShell } from "@canvas/diagram-tour"; import { drawStyles } from "@canvas/draw-styles"; import { useOptionalReviewPanel } from "@canvas/review-panel"; import { @@ -13,9 +15,11 @@ import { space, tracking, } from "@canvas/scale.stylex"; +import { shellStyles } from "@canvas/shell-styles"; import { withClass } from "@canvas/stylex-props"; +import { themeClass, themeStyles } from "@canvas/theme-styles"; import { tokens } from "@canvas/tokens.stylex"; -import { IconButton } from "@canvas/ui/button"; +import { Button, IconButton } from "@canvas/ui/button"; import { useTooltip } from "@canvas/ui/tooltip"; import { anchorSelection } from "@review/lens-selection"; import { type Block, animationCodeId } from "@review/review-api/document"; @@ -30,6 +34,7 @@ import { useRef, useState, } from "react"; +import { createPortal } from "react-dom"; import { type HostMessage, @@ -195,6 +200,11 @@ export function AnimationBlock({ const [contentHeight, setContentHeight] = useState(120); const [slots, setSlots] = useState([]); const [error, setError] = useState(null); + // The inline figure's height while it is out fullscreen, held by a + // placeholder so the document keeps its place. + const [expanded, setExpanded] = useState(null); + const expandRef = useRef(null); + const closeRef = useRef(null); const indexRef = useRef(index); indexRef.current = index; @@ -471,6 +481,22 @@ export function AnimationBlock({ if (!activeFrames.some((item) => item.key === frame.key)) go(0, true); }, [activeFrames, frame.key, go]); + const collapse = useCallback(() => setExpanded(null), []); + + const { portalTarget } = useDiagramTourShell(expanded !== null, collapse); + + // Focus follows the figure in and back out to the control that opened it. + const wasExpanded = useRef(false); + + useEffect(() => { + if (expanded !== null) closeRef.current?.focus(); + else if (wasExpanded.current) expandRef.current?.focus(); + + wasExpanded.current = expanded !== null; + }, [expanded]); + + const fullscreen = expanded !== null && portalTarget !== null; + const onKeyDown = (event: KeyboardEvent) => { if (event.key === "ArrowRight") go(activeIndex + 1, false); else if (event.key === "ArrowLeft") go(activeIndex - 1, false); @@ -479,10 +505,15 @@ export function AnimationBlock({ event.preventDefault(); }; - return ( + const figure = (
+ fullscreen + ? collapse() + : setExpanded(figureRef.current?.offsetHeight ?? 0) + } + > + {fullscreen ? "Close" : "Expand"} + + } />
-