VS Code-style split panes, editor tabs, sidebars, and bottom panels for React.
It is a good fit for dashboards and inspector-style interfaces where the main content is already React components: code viewers, AI trajectories, logs, diffs, evaluation details, or other drill-down panels.
The package is framework-agnostic on the React side and ships plain CSS for layout and theming. It works fine in Tailwind apps, but it is not implemented with Tailwind classes.
- Nested horizontal and vertical split panes with draggable sashes
- Editor tab groups with close, reorder, split, dirty, preview, and MRU behavior
- Left and right sidebars with collapsible, resizable, reorderable sections
- Bottom panel with draggable tabs and persisted active-tab state
- Cross-container drag between sidebars, panel, and editor tabs
- Built-in code review primitives:
CodeFileTree,ChangedFilesList,MonacoCodeViewer,MonacoDiffViewer,UnifiedDiffPreview - Built-in agent trace support via
AgentTraceViewerandparseAgentTrace(...) - Self-contained file and folder icons plus git-status colors
- Dark and light themes via CSS custom properties
- TypeScript types and Storybook examples with Playwright coverage, including screenshot checks
npm install @datacurve/react-code-panesImport the packaged stylesheet once in your app:
import "@datacurve/react-code-panes/styles.css";The main thing to know: Workbench fills the size of its parent. In most apps you will embed it inside an existing layout region with a bounded height, not mount it fullscreen.
The core mental model is simple:
- your app renders whatever custom sidebar or panel UI it wants
- that UI creates tabs with a stable
id activateOrOpenTab(...)reuses the existing tab when thatidis already open
import "@datacurve/react-code-panes/styles.css";
import {
Workbench,
createLeaf,
useActiveWorkbenchGroupId,
useWorkbenchActions,
} from "@datacurve/react-code-panes";
import type { SidebarSection, Tab, TabFactory } from "@datacurve/react-code-panes";
type ReviewItem = {
id: string;
title: string;
subtitle: string;
summary: string;
accent: string;
};
const reviewItems: ReviewItem[] = [
{
id: "run:review-candidate-a",
title: "Review candidate A",
subtitle: "Primary review candidate",
summary: "Strongest local result from the April 10 runs.",
accent: "#4ec9b0",
},
{
id: "finding:exit-sentinel",
title: "Completion sentinel fix",
subtitle: "Prompt contract mismatch",
summary: "The harness prompt clobbered the agent's native completion sentinel.",
accent: "#f2cc60",
},
];
const makeReviewTab: TabFactory<ReviewItem> = (item) => ({
id: item.id,
title: item.title,
labelColor: item.accent,
content: (
<div style={{ padding: 20, display: "grid", gap: 12 }}>
<div style={{ fontSize: 24, fontWeight: 600 }}>{item.title}</div>
<div style={{ color: "#9da5b4" }}>{item.subtitle}</div>
<div style={{ lineHeight: 1.7 }}>{item.summary}</div>
</div>
),
});
function ReviewQueue() {
const actions = useWorkbenchActions();
const activeGroupId = useActiveWorkbenchGroupId();
return (
<div style={{ padding: 8, display: "grid", gap: 6 }}>
{reviewItems.map((item) => (
<button
key={item.id}
type="button"
onClick={() => {
if (!activeGroupId) return;
actions.activateOrOpenTab(activeGroupId, makeReviewTab(item));
}}
style={{
textAlign: "left",
border: "1px solid rgba(255,255,255,0.08)",
background: "rgba(255,255,255,0.025)",
color: "inherit",
padding: 10,
cursor: "pointer",
}}
>
<div style={{ fontWeight: 600 }}>{item.title}</div>
<div style={{ fontSize: 12, color: "#8b949e", marginTop: 4 }}>{item.subtitle}</div>
</button>
))}
</div>
);
}
const leftSidebarSections: SidebarSection[] = [
{
id: "queue",
title: "Queue",
content: <ReviewQueue />,
},
];
export function ReviewWorkbench() {
return (
<section style={{ minHeight: 560, height: "70vh", border: "1px solid #2d2d2d" }}>
<Workbench
theme="dark"
initialState={{
splitTree: createLeaf("main"),
groups: {
main: {
tabs: [makeReviewTab(reviewItems[0])],
activeTabId: reviewItems[0].id,
mruOrder: [reviewItems[0].id],
},
},
activeGroupId: "main",
}}
leftSidebar={{
title: "Explorer",
sections: leftSidebarSections,
defaultWidth: 280,
minWidth: 200,
}}
/>
</section>
);
}@datacurve/react-code-panes is intentionally React-first.
- A tab is just metadata plus mounted React content.
idis the logical identity for that tab.- Calling
activateOrOpenTab(groupId, tab)with an already-openidfocuses the existing tab instead of opening a duplicate. - The existing tab is refreshed with the latest title, icon, color, and content you pass in.
- Inactive editor tabs stay mounted while they remain open, so local component state can stay alive.
- Explicit split actions are the exception: splitting a tab intentionally creates a second view of the current content in another editor group.
The public Tab shape is:
interface Tab {
id: string;
title: string;
icon?: React.ReactNode;
content: React.ReactElement;
isDirty?: boolean;
isPinned?: boolean;
isPreview?: boolean;
closable?: boolean;
labelColor?: string;
}If you like the “tab factory” pattern, the package also exports:
type TabFactory<T> = (item: T) => Tab;Workbench uses width: 100% and height: 100%, so the parent element must define the available size.
Typical patterns:
- a dashboard card or page region with a fixed or viewport-relative height
- a CSS grid area with
min-height: 0 - a flex child that gets remaining space from an app shell
Example:
<div style={{ display: "grid", gridTemplateRows: "auto minmax(0, 1fr)", height: "100vh" }}>
<Header />
<main style={{ minHeight: 0 }}>
<ReviewWorkbench />
</main>
</div>The library does not require a file tree model.
Your custom UI can be:
- a queue of runs
- a trace list
- a search results pane
- a timeline
- a notebook outline
- a totally fake tree or flat list
As long as that UI can turn an item into a Tab, it can drive the workbench.
Storybook includes a dedicated Workbench / Custom Views Open Tabs example showing plain React sidebar rows opening and dragging custom tabs without using CodeFileTree or ChangedFilesList.
If you want a stronger default out of the box, the package exports a small review-oriented component set:
CodeFileTreefor nested explorer trees with file icons, status badges, and optional drag-to-tab behaviorChangedFilesListfor compact changed-file sidebarsMonacoCodeViewerfor read-only file viewsMonacoDiffViewerfor before/after file diffsUnifiedDiffPreviewfor full unified patch inspectionAgentTraceViewerfor rendered agent tracesparseAgentTrace(raw)for Claude Code, Codex CLI, OpenCode, Gemini CLI, and mini-swe-agent trace formats
The flagship Storybook workbench uses these pieces together to show a realistic review surface rather than a toy fullscreen editor.
The library uses scoped class names under .mosaic-workbench and .mosaic-*, so it generally coexists cleanly with app CSS and Tailwind.
- It does not reset global
html,body, or element styles outside the workbench container. - It does ship plain CSS with fixed layout rules for the workbench itself.
- Colors, fonts, and many visual tokens are exposed as CSS custom properties.
Override tokens on .mosaic-workbench or a parent element:
.mosaic-workbench {
--mosaic-bg: #111827;
--mosaic-tab-border-active: #22c55e;
--mosaic-sash-hover: #22c55e;
--mosaic-font-family: "Inter", sans-serif;
}Yes. The package is compatible with Tailwind apps.
- Use Tailwind for the surrounding page and for content you render inside tabs or sidebars.
- Import
@datacurve/react-code-panes/styles.cssonce so the workbench layout styles are present. - Override the CSS variables or add targeted selectors if you want the workbench to match your design system.
Top-level layout component that combines the activity bar, sidebars, editor area, and bottom panel.
Key props:
initialState: initial split tree, groups, and active groupleftSidebar: left sidebar configurationrightSidebar: right sidebar configurationpanel: bottom panel configurationactivityBar: activity bar itemsshowToolbar: show or hide the toolbartheme:"dark"or"light"
useWorkbench()for direct access to state and dispatchuseWorkbenchActions()for tab and layout actionsuseActiveWorkbenchGroupId()for the current target group when your custom UI wants to open tabs
The package also exports TabBar, EditorGroup, Sidebar, Panel, and split-tree utilities (createLeaf, createBranch) for advanced or custom setups.
