{"id":43394231,"url":"https://github.com/Jeshwin/react-layman","last_synced_at":"2026-02-02T23:00:51.300Z","repository":{"id":234836533,"uuid":"739221075","full_name":"Jeshwin/react-layman","owner":"Jeshwin","description":"A tiling window manager for websites in React and Typescript","archived":false,"fork":false,"pushed_at":"2025-09-28T22:21:48.000Z","size":501,"stargazers_count":6,"open_issues_count":3,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-18T08:21:46.748Z","etag":null,"topics":["react","tiling-window","typescript"],"latest_commit_sha":null,"homepage":"https://jeshwin.github.io/react-layman/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Jeshwin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-05T03:47:25.000Z","updated_at":"2025-12-12T01:34:30.000Z","dependencies_parsed_at":"2024-09-14T13:10:02.933Z","dependency_job_id":"dce6694b-2260-4af5-a84f-f6caa0933240","html_url":"https://github.com/Jeshwin/react-layman","commit_stats":null,"previous_names":["jeshwin/react-window-manager"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/Jeshwin/react-layman","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jeshwin%2Freact-layman","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jeshwin%2Freact-layman/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jeshwin%2Freact-layman/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jeshwin%2Freact-layman/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Jeshwin","download_url":"https://codeload.github.com/Jeshwin/react-layman/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jeshwin%2Freact-layman/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29022774,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T22:20:39.141Z","status":"ssl_error","status_checked_at":"2026-02-02T22:20:37.621Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react","tiling-window","typescript"],"created_at":"2026-02-02T14:00:29.506Z","updated_at":"2026-02-02T23:00:51.293Z","avatar_url":"https://github.com/Jeshwin.png","language":"TypeScript","readme":"# react-layman\r\n\r\n\u003cdiv align=\"center\"\u003e\r\n    \r\n[![Status](https://img.shields.io/badge/status-active-success.svg)]()\r\n[![GitHub commit activity](https://img.shields.io/github/commit-activity/t/Jeshwin/react-layman)](https://github.com/Jeshwin/react-layman)\r\n[![GitHub Repo stars](https://img.shields.io/github/stars/Jeshwin/react-layman)](https://github.com/Jeshwin/react-layman)\r\n[![GitHub issues](https://img.shields.io/github/issues/Jeshwin/react-layman)](https://github.com/Jeshwin/react-layman/issues)\r\n![NPM Version](https://img.shields.io/npm/v/react-layman)\r\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE)\r\n\r\n\u003c/div\u003e\r\n\r\n## About \u003ca name = \"about\"\u003e\u003c/a\u003e\r\n\r\nReact 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.\r\n\r\nYou can play around with Layman through [this demo](https://jeshwin.github.io/react-layman).\r\n\r\n## Progress\r\n\r\n-   [x] Dynamic Layout\r\n    -   [x] Rows and columns\r\n    -   [x] Adjustable windows\r\n    -   [x] Drag and drop windows\r\n    -   [x] Delete windows\r\n    -   [x] Tabbed windows\r\n        -   [x] Draggable tabs\r\n-   [x] Extra features\r\n    -   [x] Auto Arrange\r\n    -   [x] \"Add to corner\" hueristic\r\n    -   [x] Add tabs from sources external to layout\r\n\r\n## Usage\r\n\r\nTo 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. `\u003cLayman /\u003e` can be deeply nested within `LaymanProvider`. Also note, `\u003cLayman /\u003e`'s parent needs to have a defined width and height, since it's dimensions are relative to it.\r\n\r\n```tsx\r\n\u003cLaymanProvider initialLayout={initialLayout} renderPane={renderPane} renderTab={renderTab} renderNull={\u003cNullLayout /\u003e}\u003e\r\n    \u003cdiv\u003e\r\n        \u003cdiv\u003e\r\n            \u003cdiv style={{width: 1200, height: 900}}\u003e\r\n                \u003cLayman /\u003e\r\n            \u003c/div\u003e\r\n        \u003c/div\u003e\r\n    \u003c/div\u003e\r\n\u003c/LaymanProvider\u003e\r\n```\r\n\r\n### Installation\r\n\r\nTo 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`\r\n\r\n```bash\r\ngit clone https://github.com/Jeshwin/react-layman.git\r\ncd react-layman\r\nnpm install\r\nnpm run dev\r\n```\r\n\r\n### Themes\r\n\r\nYou can use the default theme in `src/styles/theme.css`, or define your own themes with CSS variables like this:\r\n\r\n```css\r\n:root {\r\n    /*** Separators ***/\r\n    /* Color of the handle on the separator */\r\n    --separator-handle-color: #c6d0f5;\r\n\r\n    /* Thickness of the separator between windows */\r\n    --separator-thickness: 4px;\r\n\r\n    /* Length of the separator handle */\r\n    --separator-handle-length: 16px;\r\n\r\n    /*** Windows ***/\r\n    /* Background color of the window */\r\n    --window-bg-color: #303446;\r\n\r\n    /* Border radius for window corners */\r\n    --border-radius: 8px;\r\n\r\n    /*** Window Toolbars ***/\r\n    /* Background color of the toolbar */\r\n    --toolbar-bg-color: #292c3c;\r\n\r\n    /* Background color of the toolbar on hover */\r\n    --toolbar-hover-bg-color: #414559;\r\n\r\n    /* Background color for toolbar buttons on hover */\r\n    --toolbar-button-hover-bg-color: #414559;\r\n\r\n    /* Height of the toolbar at the top of each window */\r\n    --toolbar-height: 32px;\r\n\r\n    /*** Tabs ***/\r\n    /* Text color of tab titles */\r\n    --tab-text-color: #c6d0f5;\r\n\r\n    /* Color of the 'close' icon in tabs, with opacity */\r\n    --close-tab-color: #e7828488;\r\n\r\n    /* Color of indicators (e.g., focus indicator) */\r\n    --indicator-color: #a6d189;\r\n\r\n    /* Thickness of indicators (e.g., focus indicator) */\r\n    --indicator-thickness: 1px;\r\n\r\n    /* Font size for text in tabs */\r\n    --tab-font-size: 14px;\r\n}\r\n```\r\n\r\nThen, 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.\r\n\r\n```tsx\r\nimport ReactDOM from \"react-dom/client\";\r\nimport App from \"./App.tsx\";\r\n\r\n// Import a custom theme\r\nimport \"./custom_theme.css\";\r\n// Or import the default theme\r\nimport \"../src/styles/theme.css\";\r\n// You must still import the global CSS settings\r\nimport \"../src/styles/global.css\";\r\n\r\nReactDOM.createRoot(document.getElementById(\"root\")!).render(\u003cApp /\u003e);\r\n```\r\n\r\n## API\r\n\r\n### Type Definitions\r\n\r\nPlease see [index.d.ts](src/index.d.ts) for the full details.\r\n\r\n#### Common Types\r\n\r\n```ts\r\nexport interface Position {\r\n    top: number;\r\n    left: number;\r\n    width: number;\r\n    height: number;\r\n}\r\n\r\nimport {v4 as uuidv4} from \"uuid\";\r\n\r\ninterface TabOptions {\r\n    [key: string]: unknown; // Allows any custom data\r\n}\r\n\r\nexport class TabData {\r\n    // private UUID representing the tab\r\n    id: string;\r\n\r\n    // Is the tab currently selected in a window?\r\n    isSelected: boolean;\r\n\r\n    // Display name of tab\r\n    public name: string;\r\n\r\n    // Optional data attached to each tab\r\n    public options: TabOptions;\r\n\r\n    /** Creates an instance of the TabData class. */\r\n    constructor(name: string, options: TabOptions = {}) {\r\n        this.id = uuidv4();\r\n        this.isSelected = false;\r\n        this.name = name;\r\n        this.options = options;\r\n    }\r\n}\r\n```\r\n\r\n#### LaymanLayout\r\n\r\n```ts\r\n// Credit: https://blog.replit.com/leaky-uis\r\n// This is a utility type, a dynamically sized tuple\r\n// that requires at least 2 elements be present. This\r\n// guarantees flatness, i.e. no awkward [[[[A]]]] case\r\nexport type Children\u003cT\u003e = [T, T, ...T[]];\r\n\r\nexport type LaymanDirection = \"column\" | \"row\";\r\nexport type LaymanPath = Array\u003cnumber\u003e;\r\n\r\nexport interface LaymanWindow {\r\n    viewPercent?: number;\r\n    tabs: TabData[];\r\n    selectedIndex?: number;\r\n}\r\n\r\nexport interface LaymanNode {\r\n    direction: LaymanDirection;\r\n    viewPercent?: number;\r\n    children: Children\u003cLaymanLayout\u003e;\r\n}\r\n\r\nexport type LaymanLayout = LaymanWindow | LaymanNode | undefined;\r\n```\r\n\r\n#### Dragged Tabs/Windows\r\n\r\n```ts\r\nexport const TabType = \"TAB\";\r\nexport const WindowType = \"WINDOW\";\r\n\r\ninterface DragTab {\r\n    tab: TabData;\r\n    path?: LaymanPath;\r\n}\r\n\r\ninterface DragWindow {\r\n    tabs: TabData[];\r\n    path: LaymanPath;\r\n    selectedIndex: number;\r\n}\r\n\r\nexport type DragData = DragTab | DragWindow;\r\n```\r\n\r\n#### LaymanProvider Props\r\n\r\n```ts\r\ntype LaymanProviderProps = {\r\n    initialLayout: LaymanLayout;\r\n    renderPane: (tab: TabData) =\u003e JSX.Element;\r\n    renderTab: (tab: TabData) =\u003e JSX.Element;\r\n    renderNull: JSX.Element;\r\n};\r\n```\r\n\r\n#### LaymanProvider Context Data\r\n\r\n```ts\r\nexport type PaneRenderer = (arg0: TabData) =\u003e JSX.Element;\r\nexport type TabRenderer = (arg0: TabData) =\u003e string | JSX.Element;\r\n\r\nexport interface LaymanContextType {\r\n    globalContainerSize: Position;\r\n    setGlobalContainerSize: Dispatch\u003cSetStateAction\u003cPosition\u003e\u003e;\r\n    layout: LaymanLayout;\r\n    layoutDispatch: React.Dispatch\u003cLaymanLayoutAction\u003e;\r\n    setDropHighlightPosition: React.Dispatch\u003cPosition\u003e;\r\n    globalDragging: boolean;\r\n    setGlobalDragging: React.Dispatch\u003cboolean\u003e;\r\n    draggedWindowTabs: TabData[];\r\n    setDraggedWindowTabs: React.Dispatch\u003cTabData[]\u003e;\r\n    windowDragStartPosition: {x: number; y: number};\r\n    setWindowDragStartPosition: React.Dispatch\u003c{x: number; y: number}\u003e;\r\n    renderPane: PaneRenderer;\r\n    renderTab: TabRenderer;\r\n    renderNull: JSX.Element;\r\n}\r\n```\r\n\r\n### Dispatch Options\r\n\r\nUpdates 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\r\n\r\n#### Add Tab\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"addTab\",\r\n    tab: TabData,\r\n    path: LaymanPath, // Path of the window to add the tab to\r\n});\r\n```\r\n\r\n#### Remove Tab\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"removeTab\",\r\n    tab: TabData,\r\n    path: LaymanPath, // Path of the window to remove the tab from\r\n});\r\n```\r\n\r\nIf the tab does not exist in the path, no changes will be made to the layout.\r\n\r\n#### Select Tab\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"selectTab\",\r\n    tab: TabData,\r\n    path: LaymanPath, // Path of the window to select the tab from\r\n});\r\n```\r\n\r\nIf the tab does not exist in the path, no changes will be made to the layout.\r\n\r\n#### Move Tab\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"moveTab\",\r\n    tab: TabData,\r\n    path: LaymanPath, // Original path of the tab\r\n    newPath: LaymanPath, // New path for the tab\r\n    placement: \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\",\r\n});\r\n```\r\n\r\nIf the tab does not exist in the original path, no changes will be made to the layout.\r\n\r\n#### Move Separator\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"moveSeparator\",\r\n    path: LaymanPath, // Path of the node that the separator is located in\r\n    index: number, // Index of the separator within the node\r\n    newSplitPercentage: number, // Updated split percentage for the layout left of the separator\r\n});\r\n```\r\n\r\n#### Add Window\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"addWindow\",\r\n    window: LaymanWindow,\r\n    path: LaymanPath, // Path of the window to add next to\r\n    placement: \"top\" | \"bottom\" | \"left\" | \"right\",\r\n});\r\n```\r\n\r\n#### Remove Window\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"removeWindow\",\r\n    path: LaymanPath, // Path of the window to remove\r\n});\r\n```\r\n\r\n#### Move Window\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"moveWindow\",\r\n    window: LaymanWindow,\r\n    path: LaymanPath, // Original path of the window\r\n    newPath: LaymanPath, // New path of the window\r\n    placement: \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\",\r\n});\r\n```\r\n\r\n#### Add Tab With Heuristic\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"addTabWithHeuristic\";\r\n    heuristic: \"topleft\" | \"topright\";\r\n    tab: TabData;\r\n})\r\n```\r\n\r\n#### Auto Arrange Layout\r\n\r\n```ts\r\nlayoutDispatch({\r\n    type: \"autoArrange\",\r\n});\r\n```\r\n\r\n## Examples\r\n\r\nSee the [demo](https://jeshwin.github.io/react-layman) for a full example of Laymans' current features!\r\n\r\n### External Tab Source\r\n\r\nThis 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\r\n\r\n```tsx\r\nexport default function TabSource({tabName, heuristic}: {tabName: string; heuristic: LaymanHeuristic}) {\r\n    const {setGlobalDragging, layoutDispatch} = useContext(LaymanContext);\r\n\r\n    const [{isDragging}, drag] = useDrag({\r\n        type: TabType,\r\n        item: {\r\n            path: undefined,\r\n            tab: new TabData(tabName),\r\n        },\r\n        collect: (monitor) =\u003e ({\r\n            isDragging: monitor.isDragging(),\r\n        }),\r\n    });\r\n\r\n    useEffect(() =\u003e {\r\n        setGlobalDragging(isDragging);\r\n    }, [isDragging, setGlobalDragging]);\r\n\r\n    const handleDoubleClick = () =\u003e {\r\n        // Add tab to the top left window\r\n        layoutDispatch({\r\n            type: \"addTabWithHeuristic\",\r\n            tab: new TabData(tabName),\r\n            heuristic: heuristic,\r\n        });\r\n    };\r\n\r\n    return (\r\n        \u003cdiv\r\n            ref={drag}\r\n            className=\"tab-source\"\r\n            style={{\r\n                width: 48,\r\n                height: 48,\r\n                display: \"grid\",\r\n                placeContent: \"center\",\r\n                textAlign: \"center\",\r\n                borderRadius: 8,\r\n                backgroundColor: \"#babbf1\",\r\n                margin: 4,\r\n                opacity: isDragging ? 0.5 : 1,\r\n                cursor: \"pointer\",\r\n            }}\r\n            onDoubleClick={handleDoubleClick}\r\n        \u003e\r\n            {tabName}\r\n        \u003c/div\u003e\r\n    );\r\n}\r\n```\r\n\r\n## License\r\n\r\nThis repository is published under the [MIT license](/LICENSE)\r\n","funding_links":[],"categories":["UI Layout"],"sub_categories":["Form Components"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJeshwin%2Freact-layman","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FJeshwin%2Freact-layman","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJeshwin%2Freact-layman/lists"}