Reusable React file tree sidebar extracted from Sonar Code Editor. It ships as a ready-made explorer panel out of the box, but also exposes the hooks you need to restyle or replace the shell pieces in Electron apps, web sandboxes, IDEs, or any host that can implement the file operations.
- Ready-to-use sidebar container with header, empty state, and tree content
- Left/right sidebar placement styling with sensible defaults
- Configurable panel width, min width, and max width
- Top full-width open-folder CTA out of the box
- Bring-your-own open-folder button and empty-state UI hooks
- Bring-your-own header and footer UI hooks
- Theme tokens for colors, fonts, borders, and top/titlebar offsets
- Built-in context menus with per-action enable/disable controls
- Bring-your-own context menu renderer
- Nested file and folder explorer
- Inline create and rename
- Drag and drop move
- Cut, copy, and paste
- Soft delete with undo (
Cmd/Ctrl+Z) - Monaco-safe input shield for rename/create inputs
- TypeScript types and library build config ready for npm publishing
npm install @knurdz/jack-file-treePeer dependencies:
reactreact-dom
import "@knurdz/jack-file-tree/keyboard-shield";
import { FileTree, type FileTreeFsAdapter } from "@knurdz/jack-file-tree";
const fsAdapter: FileTreeFsAdapter = window.electronAPI.fs;
export function Explorer() {
return (
<FileTree
fs={fsAdapter}
workspaceRoot={workspaceRoot}
sidebarPosition="left"
width={300}
activeFilePath={activeFilePath}
onOpenFolder={openFolder}
onFileClick={(path, name) => openFile(path, name)}
onFileOpened={(path, name) => openFile(path, name)}
onFileDeleted={handleFileDeleted}
onFileRenamed={handleFileRenamed}
onFileCreated={handleFileCreated}
onFolderCreated={handleFolderCreated}
onFileCopied={handleFileCopied}
onFileMoved={handleFileMoved}
refreshTrigger={refreshKey}
newFileTrigger={newFileTrigger}
/>
);
}@knurdz/jack-file-tree now auto-injects its built-in styles when you import the package entry, so the extra stylesheet import is optional. If you want explicit stylesheet control for ordering or overrides, you can still import @knurdz/jack-file-tree/styles.css manually.
The default component is a full sidebar panel, but you can still swap in your own UI for key pieces while keeping the built-in tree behavior.
<FileTree
fs={fsAdapter}
workspaceRoot={workspaceRoot}
onOpenFolder={openFolder}
onFileClick={openFile}
sidebarPosition="right"
width={320}
theme={{
backgroundSecondary: "#0f172a",
backgroundHover: "rgba(56, 189, 248, 0.12)",
accent: "#38bdf8",
panelTopPadding: 12,
}}
renderHeader={({ title, defaultActions, className, titleClassName, actionsClassName }) => (
<div className={className}>
<span className={titleClassName}>{title}</span>
<div className={actionsClassName}>{defaultActions}</div>
</div>
)}
footer={<div>Project Explorer</div>}
renderOpenFolderButton={({ className, label, onClick }) => (
<button className={className} onClick={onClick}>
{label}
</button>
)}
contextMenu={{
actions: {
cut: false,
delete: false,
},
}}
/>Useful props:
sidebarPosition:"left"or"right"styling for the built-in sidebar shellwidth,minWidth,maxWidth: size the built-in sidebar without extra wrapper CSSshowHeader: toggle the built-in header rowshowHeaderActions: toggle the built-in create-file/create-folder action buttonsrenderHeader: replace the built-in header while still receiving default actions and labelscontentClassName,contentStyle: style the scrollable tree content areafooter/renderFooter: append a built-in footer area at the bottom of the sidebarshowOpenFolderButton: toggle the built-in open-folder CTAopenFolderButtonPosition:"top"or"center"in the empty staterenderOpenFolderButton: render your own open-folder button while keeping library behaviorrenderEmptyState: replace the full empty-state UItheme: set colors, fonts, borders, and inset offsets without overriding the stylesheetcontextMenu.enabled: enable or disable library context menuscontextMenu.actions: hide individual built-in menu actionscontextMenu.renderMenu: render your own context menu UI
Useful CSS variables:
--sft-bg-primary,--sft-bg-secondary,--sft-bg-hover--sft-text-primary,--sft-text-secondary,--sft-text-muted--sft-accent,--sft-accent-transparent,--sft-danger--sft-sidebar-border--sft-panel-top-padding: adds top inset for custom titlebar layouts--sft-header-title-offset-y: nudges the header title vertically--sft-header-actions-offset-y: nudges the header action buttons vertically--sft-open-folder-btn-bg,--sft-open-folder-btn-text,--sft-open-folder-btn-border: style the built-in open-folder CTA--sft-menu-bg,--sft-menu-border,--sft-menu-hover,--sft-menu-text: style the built-in context menu
The theme prop writes those same CSS variables for you, so hosts can choose between inline theme tokens or plain stylesheet overrides.
If your host app uses Monaco, import jack-file-tree/keyboard-shield before Monaco is loaded. This prevents Monaco capture listeners from swallowing keystrokes in the tree's inline rename/create inputs.
import "@knurdz/jack-file-tree/keyboard-shield";
import "monaco-editor";If you do not use Monaco, you can skip that import.
The component is intentionally decoupled from Electron. Provide an adapter that matches this shape:
interface FileTreeFsAdapter {
readDirectory(path: string): Promise<FileTreeNode[]>;
readFile?(path: string): Promise<string>;
createFile(path: string): Promise<string | void>;
createFolder(path: string): Promise<string | void>;
renameItem(oldPath: string, newPath: string): Promise<string | void>;
copyItem(oldPath: string, newPath: string): Promise<string | void>;
}- Move the
file-tree/folder into its own repository. - Update the package
name,repository, andauthorfields if needed. - Run
npm install. - Run
npm run build. - Publish with
npm publish.
An Electron wiring example is included in examples/electron-usage.tsx.