
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
react-layman
Advanced tools
React Layman is a fully-featured, dynamic layout manager made for React. It is written in Typescript and provides typing, but may also be used with regular Javascript. Layman is inspired by Replit's IDE, LeetCode's new UI, and the pre-existing React Mosaic project.
You can play around with Layman through this demo.
To use Layman, add the LaymanProvider component, which sets up your initial layout, rendering functions, and other configurations. Add the Layman component within the provider to render out the layout. <Layman /> can be deeply nested within LaymanProvider. Also note, <Layman />'s parent needs to have a defined width and height, since it's dimensions are relative to it.
<LaymanProvider initialLayout={initialLayout} renderPane={renderPane} renderTab={renderTab} renderNull={<NullLayout />}>
<div>
<div>
<div style={{width: 1200, height: 900}}>
<Layman />
</div>
</div>
</div>
</LaymanProvider>
To install and run Layman locally, run the following commands in your terminal. This will clone this repository, install all necessary packages, and run the demo page at localhost:5173/react-layman
git clone https://github.com/Jeshwin/react-layman.git
cd react-layman
npm install
npm run dev
You can use the default theme in src/styles/theme.css, or define your own themes with CSS variables like this:
:root {
/*** Separators ***/
/* Color of the handle on the separator */
--separator-handle-color: #c6d0f5;
/* Thickness of the separator between windows */
--separator-thickness: 4px;
/* Length of the separator handle */
--separator-handle-length: 16px;
/*** Windows ***/
/* Background color of the window */
--window-bg-color: #303446;
/* Border radius for window corners */
--border-radius: 8px;
/*** Window Toolbars ***/
/* Background color of the toolbar */
--toolbar-bg-color: #292c3c;
/* Background color of the toolbar on hover */
--toolbar-hover-bg-color: #414559;
/* Background color for toolbar buttons on hover */
--toolbar-button-hover-bg-color: #414559;
/* Height of the toolbar at the top of each window */
--toolbar-height: 32px;
/*** Tabs ***/
/* Text color of tab titles */
--tab-text-color: #c6d0f5;
/* Color of the 'close' icon in tabs, with opacity */
--close-tab-color: #e7828488;
/* Color of indicators (e.g., focus indicator) */
--indicator-color: #a6d189;
/* Thickness of indicators (e.g., focus indicator) */
--indicator-thickness: 1px;
/* Font size for text in tabs */
--tab-font-size: 14px;
}
Then, you can import this theme at the root of your project. Note, you must still import the global CSS file, since this is required for Layman to work properly.
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
// Import a custom theme
import "./custom_theme.css";
// Or import the default theme
import "../src/styles/theme.css";
// You must still import the global CSS settings
import "../src/styles/global.css";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
Please see index.d.ts for the full details.
export interface Position {
top: number;
left: number;
width: number;
height: number;
}
import {v4 as uuidv4} from "uuid";
interface TabOptions {
[key: string]: unknown; // Allows any custom data
}
export class TabData {
// private UUID representing the tab
id: string;
// Is the tab currently selected in a window?
isSelected: boolean;
// Display name of tab
public name: string;
// Optional data attached to each tab
public options: TabOptions;
/** Creates an instance of the TabData class. */
constructor(name: string, options: TabOptions = {}) {
this.id = uuidv4();
this.isSelected = false;
this.name = name;
this.options = options;
}
}
// Credit: https://blog.replit.com/leaky-uis
// This is a utility type, a dynamically sized tuple
// that requires at least 2 elements be present. This
// guarantees flatness, i.e. no awkward [[[[A]]]] case
export type Children<T> = [T, T, ...T[]];
export type LaymanDirection = "column" | "row";
export type LaymanPath = Array<number>;
export interface LaymanWindow {
viewPercent?: number;
tabs: TabData[];
selectedIndex?: number;
}
export interface LaymanNode {
direction: LaymanDirection;
viewPercent?: number;
children: Children<LaymanLayout>;
}
export type LaymanLayout = LaymanWindow | LaymanNode | undefined;
export const TabType = "TAB";
export const WindowType = "WINDOW";
interface DragTab {
tab: TabData;
path?: LaymanPath;
}
interface DragWindow {
tabs: TabData[];
path: LaymanPath;
selectedIndex: number;
}
export type DragData = DragTab | DragWindow;
type LaymanProviderProps = {
initialLayout: LaymanLayout;
renderPane: (tab: TabData) => JSX.Element;
renderTab: (tab: TabData) => JSX.Element;
renderNull: JSX.Element;
};
export type PaneRenderer = (arg0: TabData) => JSX.Element;
export type TabRenderer = (arg0: TabData) => string | JSX.Element;
export interface LaymanContextType {
globalContainerSize: Position;
setGlobalContainerSize: Dispatch<SetStateAction<Position>>;
layout: LaymanLayout;
layoutDispatch: React.Dispatch<LaymanLayoutAction>;
setDropHighlightPosition: React.Dispatch<Position>;
globalDragging: boolean;
setGlobalDragging: React.Dispatch<boolean>;
draggedWindowTabs: TabData[];
setDraggedWindowTabs: React.Dispatch<TabData[]>;
windowDragStartPosition: {x: number; y: number};
setWindowDragStartPosition: React.Dispatch<{x: number; y: number}>;
renderPane: PaneRenderer;
renderTab: TabRenderer;
renderNull: JSX.Element;
}
Updates to the layout are handled through a React Reducer with the function layoutDispatch. Here are the following actions that you can use for controlling changes to the layout
layoutDispatch({
type: "addTab",
tab: TabData,
path: LaymanPath, // Path of the window to add the tab to
});
layoutDispatch({
type: "removeTab",
tab: TabData,
path: LaymanPath, // Path of the window to remove the tab from
});
If the tab does not exist in the path, no changes will be made to the layout.
layoutDispatch({
type: "selectTab",
tab: TabData,
path: LaymanPath, // Path of the window to select the tab from
});
If the tab does not exist in the path, no changes will be made to the layout.
layoutDispatch({
type: "moveTab",
tab: TabData,
path: LaymanPath, // Original path of the tab
newPath: LaymanPath, // New path for the tab
placement: "top" | "bottom" | "left" | "right" | "center",
});
If the tab does not exist in the original path, no changes will be made to the layout.
layoutDispatch({
type: "moveSeparator",
path: LaymanPath, // Path of the node that the separator is located in
index: number, // Index of the separator within the node
newSplitPercentage: number, // Updated split percentage for the layout left of the separator
});
layoutDispatch({
type: "addWindow",
window: LaymanWindow,
path: LaymanPath, // Path of the window to add next to
placement: "top" | "bottom" | "left" | "right",
});
layoutDispatch({
type: "removeWindow",
path: LaymanPath, // Path of the window to remove
});
layoutDispatch({
type: "moveWindow",
window: LaymanWindow,
path: LaymanPath, // Original path of the window
newPath: LaymanPath, // New path of the window
placement: "top" | "bottom" | "left" | "right" | "center",
});
layoutDispatch({
type: "addTabWithHeuristic";
heuristic: "topleft" | "topright";
tab: TabData;
})
layoutDispatch({
type: "autoArrange",
});
See the demo for a full example of Laymans' current features!
This is the code for the tab sources in the demo, which support dragging into the layout to create a new tab, or adding using a specified heuristic
export default function TabSource({tabName, heuristic}: {tabName: string; heuristic: LaymanHeuristic}) {
const {setGlobalDragging, layoutDispatch} = useContext(LaymanContext);
const [{isDragging}, drag] = useDrag({
type: TabType,
item: {
path: undefined,
tab: new TabData(tabName),
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
useEffect(() => {
setGlobalDragging(isDragging);
}, [isDragging, setGlobalDragging]);
const handleDoubleClick = () => {
// Add tab to the top left window
layoutDispatch({
type: "addTabWithHeuristic",
tab: new TabData(tabName),
heuristic: heuristic,
});
};
return (
<div
ref={drag}
className="tab-source"
style={{
width: 48,
height: 48,
display: "grid",
placeContent: "center",
textAlign: "center",
borderRadius: 8,
backgroundColor: "#babbf1",
margin: 4,
opacity: isDragging ? 0.5 : 1,
cursor: "pointer",
}}
onDoubleClick={handleDoubleClick}
>
{tabName}
</div>
);
}
This repository is published under the MIT license
FAQs
A dynamic tiling layout manager made for React
We found that react-layman demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.