/* Copyright 2026 Marimo. All rights reserved. */ import { useAtom, useAtomValue } from "jotai"; import { atomWithStorage } from "jotai/utils"; import { FileIcon, HardDrive } from "lucide-react"; import React, { useCallback, useMemo } from "react"; import useResizeObserver from "use-resize-observer"; import { StorageInspector } from "@/components/storage/storage-inspector"; import { Accordion } from "@/components/ui/accordion"; import { storageNamespacesAtom } from "@/core/storage/state"; import { cn } from "@/utils/cn"; import { jotaiJsonStorage } from "@/utils/storage/jotai"; import { TreeDndProvider } from "../../file-tree/dnd-wrapper"; import { FileExplorer } from "../../file-tree/file-explorer"; import { useFileExplorerUpload } from "../../file-tree/upload"; import { PanelAccordionContent, PanelAccordionItem, PanelAccordionTrigger, PanelBadge, } from "./components"; type OpenSections = "files" | "remote-storage"; interface FileExplorerPanelState { openSections: OpenSections[]; hasUserInteracted: boolean; } const fileExplorerPanelAtom = atomWithStorage( "marimo:file-explorer-panel:state", { openSections: ["files"], hasUserInteracted: false }, jotaiJsonStorage, ); const FileExplorerComponent: React.FC<{ height: number }> = ({ height }) => { const { getRootProps, getInputProps, isDragActive } = useFileExplorerUpload({ noClick: true, noKeyboard: true, }); return (
{isDragActive && (
Drop files here
)}
); }; // Height of each accordion trigger (px-3 py-2 text-xs = ~33px) const TRIGGER_HEIGHT = 33; const FileExplorerPanel: React.FC = () => { const { ref: panelRef, height: panelHeight = 500 } = useResizeObserver(); const [state, setState] = useAtom(fileExplorerPanelAtom); const storageNamespaces = useAtomValue(storageNamespacesAtom); const remoteStorageConnections = storageNamespaces.length; const openSections = useMemo(() => { if (!state.hasUserInteracted && remoteStorageConnections > 0) { if (state.openSections.includes("remote-storage")) { return state.openSections; } return [...state.openSections, "remote-storage"]; } return state.openSections; }, [state.hasUserInteracted, state.openSections, remoteStorageConnections]); const handleValueChange = useCallback( (value: OpenSections[]) => { setState({ openSections: value, hasUserInteracted: true, }); }, [setState], ); const availableContent = panelHeight - TRIGGER_HEIGHT * 2; const storageIsOpen = openSections.includes("remote-storage"); const bothOpen = storageIsOpen && openSections.includes("files"); const storageMaxHeight = bothOpen ? Math.round(availableContent * 0.4) : availableContent; const fileTreeHeight = Math.max( 200, bothOpen ? availableContent - storageMaxHeight : availableContent, ); return (
Remote storage {remoteStorageConnections > 0 && ( {remoteStorageConnections} )} Files
); }; export default FileExplorerPanel;