An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# react-layman



[![Status](https://img.shields.io/badge/status-active-success.svg)]()
[![GitHub commit activity](https://img.shields.io/github/commit-activity/t/Jeshwin/react-layman)](https://github.com/Jeshwin/react-layman)
[![GitHub Repo stars](https://img.shields.io/github/stars/Jeshwin/react-layman)](https://github.com/Jeshwin/react-layman)
[![GitHub issues](https://img.shields.io/github/issues/Jeshwin/react-layman)](https://github.com/Jeshwin/react-layman/issues)
![NPM Version](https://img.shields.io/npm/v/react-layman)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE)

## About

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)