import { type ReactNode, useEffect, useState } from "react"; import { isElectron } from "~/env"; import { useResizableWidth } from "~/hooks/useResizableWidth"; import { cn } from "~/lib/utils"; import { RightPanelResizeHandle } from "./RightPanelResizeHandle"; export type PreviewPanelMode = "inline" | "sheet" | "sidebar" | "embedded"; const PREVIEW_PANEL_WIDTH_STORAGE_KEY = "t3code:preview-panel-width"; const PREVIEW_PANEL_MIN_WIDTH = 360; /** Fraction of the viewport allowed, preserving the remaining space for chat. */ const PREVIEW_PANEL_MAX_WIDTH_FRACTION = 0.7; const PREVIEW_PANEL_DEFAULT_WIDTH = 540; export function getPreviewPanelMaxWidth(viewportWidth: number): number { return Math.floor(viewportWidth * PREVIEW_PANEL_MAX_WIDTH_FRACTION); } /** * Shell for the preview panel. In inline mode the panel is user-resizable * via a drag handle on the left edge; width persists per browser. In * sheet/sidebar modes the parent owns the size. */ export function PreviewPanelShell(props: { mode: PreviewPanelMode; maximized?: boolean; children: ReactNode; }) { const useDragRegion = isElectron && props.mode !== "sheet" && props.mode !== "embedded"; const isInline = props.mode === "inline"; const maxWidth = useViewportClampedMaxWidth(); const { width, handlers } = useResizableWidth({ storageKey: PREVIEW_PANEL_WIDTH_STORAGE_KEY, defaultWidth: PREVIEW_PANEL_DEFAULT_WIDTH, minWidth: PREVIEW_PANEL_MIN_WIDTH, maxWidth, edge: "left", }); return (
{isInline && !props.maximized ? : null} {useDragRegion ?
: null} {props.children}
); } /** * Track viewport width to derive a sensible upper bound for the panel. * Resize-aware so dragging the OS window narrower re-clamps the stored * width on the next render (the hook's clamp picks this up automatically). */ function useViewportClampedMaxWidth(): number { const [vw, setVw] = useState(() => (typeof window === "undefined" ? 1280 : window.innerWidth)); useEffect(() => { if (typeof window === "undefined") return; let frame = 0; const onResize = () => { // Coalesce rapid resize events into one rAF tick. if (frame !== 0) return; frame = window.requestAnimationFrame(() => { frame = 0; setVw(window.innerWidth); }); }; window.addEventListener("resize", onResize); return () => { window.removeEventListener("resize", onResize); if (frame !== 0) window.cancelAnimationFrame(frame); }; }, []); return getPreviewPanelMaxWidth(vw); }