https://github.com/Jeshwin/react-layman
A tiling window manager for websites in React and Typescript
https://github.com/Jeshwin/react-layman
react tiling-window typescript
Last synced: about 2 months ago
JSON representation
A tiling window manager for websites in React and Typescript
- Host: GitHub
- URL: https://github.com/Jeshwin/react-layman
- Owner: Jeshwin
- License: mit
- Created: 2024-01-05T03:47:25.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2025-09-28T22:21:48.000Z (6 months ago)
- Last Synced: 2026-01-18T08:21:46.748Z (2 months ago)
- Topics: react, tiling-window, typescript
- Language: TypeScript
- Homepage: https://jeshwin.github.io/react-layman/
- Size: 489 KB
- Stars: 6
- Watchers: 1
- Forks: 1
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-react-components - react-layman - [demo](https://jeshwin.github.io/react-layman/) - Dynamic tiling layout manager with tabs (UI Layout / Form Components)
- fucking-awesome-react-components - react-layman - 🌎 [demo](jeshwin.github.io/react-layman/) - Dynamic tiling layout manager with tabs (UI Layout / Form Components)
README
# react-layman
[]()
[](https://github.com/Jeshwin/react-layman)
[](https://github.com/Jeshwin/react-layman)
[](https://github.com/Jeshwin/react-layman/issues)

[](/LICENSE)
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](https://replit.com)'s IDE, [LeetCode](https://leetcode.com)'s new UI, and the pre-existing [React Mosaic](https://github.com/nomcopter/react-mosaic) project.
You can play around with Layman through [this demo](https://jeshwin.github.io/react-layman).
## Progress
- [x] Dynamic Layout
- [x] Rows and columns
- [x] Adjustable windows
- [x] Drag and drop windows
- [x] Delete windows
- [x] Tabbed windows
- [x] Draggable tabs
- [x] Extra features
- [x] Auto Arrange
- [x] "Add to corner" hueristic
- [x] Add tabs from sources external to layout
## Usage
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. `` can be deeply nested within `LaymanProvider`. Also note, ``'s parent needs to have a defined width and height, since it's dimensions are relative to it.
```tsx
}>
```
### Installation
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`
```bash
git clone https://github.com/Jeshwin/react-layman.git
cd react-layman
npm install
npm run dev
```
### Themes
You can use the default theme in `src/styles/theme.css`, or define your own themes with CSS variables like this:
```css
: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.
```tsx
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();
```
## API
### Type Definitions
Please see [index.d.ts](src/index.d.ts) for the full details.
#### Common Types
```ts
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;
}
}
```
#### LaymanLayout
```ts
// 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[]];
export type LaymanDirection = "column" | "row";
export type LaymanPath = Array;
export interface LaymanWindow {
viewPercent?: number;
tabs: TabData[];
selectedIndex?: number;
}
export interface LaymanNode {
direction: LaymanDirection;
viewPercent?: number;
children: Children;
}
export type LaymanLayout = LaymanWindow | LaymanNode | undefined;
```
#### Dragged Tabs/Windows
```ts
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;
```
#### LaymanProvider Props
```ts
type LaymanProviderProps = {
initialLayout: LaymanLayout;
renderPane: (tab: TabData) => JSX.Element;
renderTab: (tab: TabData) => JSX.Element;
renderNull: JSX.Element;
};
```
#### LaymanProvider Context Data
```ts
export type PaneRenderer = (arg0: TabData) => JSX.Element;
export type TabRenderer = (arg0: TabData) => string | JSX.Element;
export interface LaymanContextType {
globalContainerSize: Position;
setGlobalContainerSize: Dispatch>;
layout: LaymanLayout;
layoutDispatch: React.Dispatch;
setDropHighlightPosition: React.Dispatch;
globalDragging: boolean;
setGlobalDragging: React.Dispatch;
draggedWindowTabs: TabData[];
setDraggedWindowTabs: React.Dispatch;
windowDragStartPosition: {x: number; y: number};
setWindowDragStartPosition: React.Dispatch<{x: number; y: number}>;
renderPane: PaneRenderer;
renderTab: TabRenderer;
renderNull: JSX.Element;
}
```
### Dispatch Options
Updates to the layout are handled through a [React Reducer](https://react.dev/learn/scaling-up-with-reducer-and-context) with the function `layoutDispatch`. Here are the following actions that you can use for controlling changes to the layout
#### Add Tab
```ts
layoutDispatch({
type: "addTab",
tab: TabData,
path: LaymanPath, // Path of the window to add the tab to
});
```
#### Remove Tab
```ts
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.
#### Select Tab
```ts
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.
#### Move Tab
```ts
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.
#### Move Separator
```ts
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
});
```
#### Add Window
```ts
layoutDispatch({
type: "addWindow",
window: LaymanWindow,
path: LaymanPath, // Path of the window to add next to
placement: "top" | "bottom" | "left" | "right",
});
```
#### Remove Window
```ts
layoutDispatch({
type: "removeWindow",
path: LaymanPath, // Path of the window to remove
});
```
#### Move Window
```ts
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",
});
```
#### Add Tab With Heuristic
```ts
layoutDispatch({
type: "addTabWithHeuristic";
heuristic: "topleft" | "topright";
tab: TabData;
})
```
#### Auto Arrange Layout
```ts
layoutDispatch({
type: "autoArrange",
});
```
## Examples
See the [demo](https://jeshwin.github.io/react-layman) for a full example of Laymans' current features!
### External Tab Source
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
```tsx
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 (
{tabName}
);
}
```
## License
This repository is published under the [MIT license](/LICENSE)