npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-layman

v0.2.10

Published

A dynamic tiling layout manager made for React

Downloads

1,237

Readme

react-layman

Status GitHub commit activity GitHub Repo stars GitHub issues NPM Version 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's IDE, LeetCode's new UI, and the pre-existing React Mosaic project.

You can play around with Layman through this demo.

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. <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>

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

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:

: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 />);

API

Type Definitions

Please see index.d.ts for the full details.

Common Types

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

// 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;

Dragged Tabs/Windows

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

type LaymanProviderProps = {
    initialLayout: LaymanLayout;
    renderPane: (tab: TabData) => JSX.Element;
    renderTab: (tab: TabData) => JSX.Element;
    renderNull: JSX.Element;
};

LaymanProvider Context Data

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;
}

Dispatch Options

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

Add Tab

layoutDispatch({
    type: "addTab",
    tab: TabData,
    path: LaymanPath, // Path of the window to add the tab to
});

Remove Tab

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

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

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

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

layoutDispatch({
    type: "addWindow",
    window: LaymanWindow,
    path: LaymanPath, // Path of the window to add next to
    placement: "top" | "bottom" | "left" | "right",
});

Remove Window

layoutDispatch({
    type: "removeWindow",
    path: LaymanPath, // Path of the window to remove
});

Move Window

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

layoutDispatch({
    type: "addTabWithHeuristic";
    heuristic: "topleft" | "topright";
    tab: TabData;
})

Auto Arrange Layout

layoutDispatch({
    type: "autoArrange",
});

Examples

See the demo 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

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>
    );
}

License

This repository is published under the MIT license