import { memo, useState, useCallback, useImperativeHandle, useRef, forwardRef, type ReactNode, } from "react"; import { CompositionsTab } from "./CompositionsTab"; import { AssetsTab } from "./AssetsTab"; import { trackStudioEvent } from "../../utils/studioTelemetry"; import { BlocksTab, type BlockPreviewInfo } from "./BlocksTab"; import { FileTree } from "../editor/FileTree"; import { STUDIO_BLOCKS_PANEL_ENABLED } from "../editor/manualEditingAvailability"; import { Tooltip } from "../ui"; export type SidebarTab = "compositions" | "assets" | "code" | "blocks"; export interface LeftSidebarHandle { selectTab: (tab: SidebarTab) => void; getTab: () => SidebarTab; } const STORAGE_KEY = "hf-studio-sidebar-tab"; function getPersistedTab(): SidebarTab { const stored = localStorage.getItem(STORAGE_KEY); if (stored === "assets") return "assets"; if (stored === "code") return "code"; if (stored === "blocks") return "blocks"; return "compositions"; } interface LeftSidebarProps { width?: number; projectId: string; compositions: string[]; assets: string[]; activeComposition: string | null; onSelectComposition: (comp: string) => void; onImportFiles?: (files: FileList, dir?: string) => void; fileTree?: string[]; editingFile?: { path: string; content: string | null } | null; onSelectFile?: (path: string) => void; onCreateFile?: (path: string) => void; onCreateFolder?: (path: string) => void; onDeleteFile?: (path: string) => void; onRenameFile?: (oldPath: string, newPath: string) => void; onDuplicateFile?: (path: string) => void; onMoveFile?: (oldPath: string, newPath: string) => void; codeChildren?: ReactNode; onRenderComposition?: (comp: string) => void; isRendering?: boolean; onLint?: () => void; linting?: boolean; lintFindingCount?: number; lintFindingsByFile?: Map; onToggleCollapse?: () => void; onAddBlock?: (blockName: string) => void; onPreviewBlock?: (preview: BlockPreviewInfo | null) => void; takeoverContent?: ReactNode; } export const LeftSidebar = memo( forwardRef(function LeftSidebar( { width = 240, projectId, compositions, assets, activeComposition, onSelectComposition, onImportFiles, fileTree: fileProp, editingFile, onSelectFile, onCreateFile, onCreateFolder, onDeleteFile, onRenameFile, onDuplicateFile, onMoveFile, codeChildren, onRenderComposition, isRendering, onLint, linting, lintFindingCount, lintFindingsByFile, onToggleCollapse, onAddBlock, onPreviewBlock, takeoverContent, }, ref, ) { const [tab, setTab] = useState(getPersistedTab); const tabRef = useRef(tab); tabRef.current = tab; const selectTab = useCallback((t: SidebarTab) => { setTab(t); localStorage.setItem(STORAGE_KEY, t); trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t }); }, []); const getTab = useCallback(() => tabRef.current, []); useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]); return (
{takeoverContent ? (
{takeoverContent}
) : ( <> {/* Tabs — Code first */}
{STUDIO_BLOCKS_PANEL_ENABLED && ( )}
{onToggleCollapse && ( )}
{/* Tab content */} {tab === "compositions" && ( )} {tab === "assets" && ( )} {tab === "code" && (
{(fileProp?.length ?? 0) > 0 && (
{})} onCreateFile={onCreateFile} onCreateFolder={onCreateFolder} onDeleteFile={onDeleteFile} onRenameFile={onRenameFile} onDuplicateFile={onDuplicateFile} onMoveFile={onMoveFile} onImportFiles={onImportFiles} lintFindingsByFile={lintFindingsByFile} />
)}
{codeChildren ?? (
Select a file to edit
)}
)} {STUDIO_BLOCKS_PANEL_ENABLED && tab === "blocks" && ( )} {/* Lint button pinned at the bottom */} {onLint && (
)} )}
); }), );