import { ArrowLeft, ArrowRight, Camera, ExternalLink, MousePointerClick, RotateCw, } from "lucide-react"; import { type FormEvent, type KeyboardEvent, type ReactNode, useEffect, useRef, useState, } from "react"; import { Button } from "~/components/ui/button"; import { InputGroup, InputGroupAddon, InputGroupInput } from "~/components/ui/input-group"; import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; import { cn } from "~/lib/utils"; interface Props { url: string; displayUrl?: string | undefined; loading: boolean; loadProgress: number; canGoBack: boolean; canGoForward: boolean; refreshDisabled: boolean; inputDisabled?: boolean | undefined; /** Bumping this value re-focuses and selects the URL input. */ focusUrlNonce?: number | undefined; onBack: () => void; onForward: () => void; onRefresh: () => void; onSubmit: (url: string) => void; /** When provided, renders an "Open in browser" affordance to the right. */ onOpenInBrowser?: (() => void) | undefined; onCapture?: ((record: boolean) => void) | undefined; captureDisabled?: boolean | undefined; recording?: boolean | undefined; /** * When provided, renders an annotation-mode toggle button to the right of * the URL input. Pressed while annotation mode is active (button shows in `pressed` * state). Disabled in `pickDisabled` mode. */ onPickElement?: (() => void) | undefined; pickActive?: boolean | undefined; pickDisabled?: boolean | undefined; /** Optional reason string surfaced in the disabled tooltip. */ pickDisabledReason?: string | undefined; /** * Trailing slot rendered after the URL input. Used by the preview view * to mount the three-dot menu (hard reload, devtools, zoom, clear data). */ trailingActions?: ReactNode; } const NOOP = () => {}; export function PreviewChromeRow({ url, displayUrl, loading, loadProgress, canGoBack, canGoForward, refreshDisabled, inputDisabled, focusUrlNonce, onBack, onForward, onRefresh, onSubmit, onOpenInBrowser, onCapture, captureDisabled, recording, onPickElement, pickActive, pickDisabled, pickDisabledReason, trailingActions, }: Props) { const inputRef = useRef(null); const [draft, setDraft] = useState(url); const [inputFocused, setInputFocused] = useState(false); useEffect(() => { if (focusUrlNonce == null) return; const node = inputRef.current; if (!node) return; node.focus(); }, [focusUrlNonce]); const submit = (event?: FormEvent | KeyboardEvent) => { event?.preventDefault(); const next = draft.trim(); if (next.length === 0) return; onSubmit(next); inputRef.current?.blur(); }; return (
} > Back } > Forward } > {loading ? "Loading…" : "Refresh"}
setDraft(event.target.value)} onFocus={() => { setDraft(url); setInputFocused(true); queueMicrotask(() => inputRef.current?.select()); }} onBlur={() => { setInputFocused(false); }} onKeyDown={(event) => { if (event.key === "Enter") submit(event); if (event.key === "Escape") { event.preventDefault(); setDraft(url); inputRef.current?.blur(); } }} placeholder="Search or enter URL" spellCheck={false} disabled={inputDisabled} data-preview-url-input size="sm" /> } /> {!inputFocused && displayUrl ? {url} : null} {onOpenInBrowser && !inputFocused ? ( } > Open in system browser ) : null} {onPickElement ? ( } > {pickDisabled && pickDisabledReason ? pickDisabledReason : pickActive ? "Cancel annotation (Esc)" : "Annotate elements, regions, and drawings"} ) : null} {onCapture ? ( onCapture(event.shiftKey)} aria-label={recording ? "Stop recording" : "Capture screenshot"} type="button" className="relative" disabled={captureDisabled} /> } > {recording ? ( ) : null} {recording ? "Stop recording" : "Screenshot · Shift-click to record"} ) : null} {trailingActions}
{loadProgress > 0 ? (
) : null}
); }