import { useRef, type MouseEvent } from "react"; import { RotateCcw, RotateCw, Camera } from "../icons/SystemIcons"; import { STUDIO_INSPECTOR_PANELS_ENABLED, STUDIO_MANUAL_EDITING_DISABLED_TITLE, } from "./editor/manualEditingAvailability"; import { getHistoryShortcutLabel } from "../utils/studioHelpers"; import { useStudioShellContext } from "../contexts/StudioContext"; import { usePanelLayoutContext } from "../contexts/PanelLayoutContext"; import { useViewMode, type StudioViewMode } from "../contexts/ViewModeContext"; import { trackStudioEvent } from "../utils/studioTelemetry"; import { Tooltip } from "./ui"; export interface StudioHeaderProps { captureFrameHref: string; captureFrameFilename: string; handleCaptureFrameClick: (event: MouseEvent) => void; refreshCaptureFrameTime: () => void; capturing?: boolean; inspectorButtonActive: boolean; inspectorPanelActive: boolean; onExport?: () => void; } function HyperframesLogo() { // Full logo from logo-dark.svg (263×79): heygen label + gradient mark + hyperframes wordmark. // All fill="black" paths inverted to white for the dark header. const height = 28; const width = Math.round(height * (263 / 79)); return ( {/* heygen label */} {/* gradient icon mark */} {/* hyperframes wordmark */} ); } const VIEW_MODE_OPTIONS: Array<{ mode: StudioViewMode; label: string }> = [ { mode: "storyboard", label: "Storyboard" }, { mode: "timeline", label: "Preview" }, ]; /** Segmented control switching the main stage between storyboard and preview. */ function ViewModeToggle() { const { viewMode, setViewMode } = useViewMode(); const tabRefs = useRef>([]); const selectMode = (mode: StudioViewMode) => { if (mode === viewMode) return; trackStudioEvent("view_mode_toggle", { mode }); setViewMode(mode); }; // Complete APG tabs pattern: roving tabIndex + arrow-key navigation. const handleKeyDown = (e: React.KeyboardEvent, index: number) => { if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return; e.preventDefault(); const dir = e.key === "ArrowLeft" ? -1 : 1; const next = (index + dir + VIEW_MODE_OPTIONS.length) % VIEW_MODE_OPTIONS.length; tabRefs.current[next]?.focus(); selectMode(VIEW_MODE_OPTIONS[next].mode); }; return (
{VIEW_MODE_OPTIONS.map(({ mode, label }, index) => { const active = viewMode === mode; return ( ); })}
); } // fallow-ignore-next-line complexity export function StudioHeader({ captureFrameHref, captureFrameFilename, handleCaptureFrameClick, refreshCaptureFrameTime, capturing, inspectorButtonActive, inspectorPanelActive, onExport, }: StudioHeaderProps) { const { projectId, editHistory, handleUndo, handleRedo, renderQueue } = useStudioShellContext(); const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext(); const isRendering = renderQueue.isRendering; return (
{/* Left: logo + project name */}
{projectId}
{/* Center: storyboard / preview toggle */} {/* Right: toolbar buttons */}
{ if (capturing) { e.preventDefault(); return; } trackStudioEvent("toolbar_action", { action: "capture_frame" }); handleCaptureFrameClick(e); }} onFocus={refreshCaptureFrameTime} onPointerDown={refreshCaptureFrameTime} aria-disabled={capturing || undefined} className={`h-7 flex items-center gap-1.5 px-2.5 rounded-md text-[11px] font-medium transition-colors ${ capturing ? "text-neutral-600 cursor-default" : "text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800 active:scale-[0.98]" }`} aria-label={capturing ? "Capturing frame" : "Capture current frame"} > {capturing ? ( ) : ( )} {capturing ? "Capturing…" : "Capture"}
); }