0.7.15 • Published 6 months ago

flexlayout-react v0.7.15

Weekly downloads
4,362
License
ISC
Repository
github
Last release
6 months ago

FlexLayout

GitHub npm npm

FlexLayout is a layout manager that arranges React components in multiple tab sets, tabs can be resized and moved.

FlexLayout Demo Screenshot

Run the Demo

Try it now using JSFiddle

API Doc

Screenshot of Caplin Liberator Explorer using FlexLayout

FlexLayout's only dependency is React.

Features:

  • splitters
  • tabs
  • tab dragging and ordering
  • tabset dragging (move all the tabs in a tabset in one operation)
  • dock to tabset or edge of frame
  • maximize tabset (double click tabset header or use icon)
  • tab overflow (show menu when tabs overflow, scroll tabs using mouse wheel)
  • border tabsets
  • popout tabs into new browser windows
  • submodels, allow layouts inside layouts
  • tab renaming (double click tab text to rename)
  • theming - light, underline, gray and dark
  • touch events - works on mobile devices (iPad, Android)
  • add tabs using drag, indirect drag, add to active tabset, add to tabset by id
  • preferred pixel size tabsets (try to keep their size when window resizes)
  • headed tabsets
  • tab and tabset attributes: enableHeader, enableTabStrip, enableDock, enableDrop...
  • customizable tabs and tabset header rendering
  • component state is preserved when tabs are moved
  • typescript type declarations included

Installation

FlexLayout is in the npm repository. install using:

npm install flexlayout-react

Import FlexLayout in your modules:

import {Layout, Model} from 'flexlayout-react';

Include the light, underline, gray or dark theme by either:

Adding an additional import:

import 'flexlayout-react/style/light.css';  

or by adding the css to your html:

<link rel="stylesheet" href="node_modules/flexlayout-react/style/light.css" />

Usage

The <Layout> component renders the tabsets and splitters, it takes the following props:

Required props:

PropDescription
modelthe layout model
factorya factory function for creating React components

Additional optional props

The model is tree of Node objects that define the structure of the layout.

The factory is a function that takes a Node object and returns a React component that should be hosted by a tab in the layout.

The model can be created using the Model.fromJson(jsonObject) static method, and can be saved using the model.toJson() method.

Example Configuration:

var json = {
    global: {},
    borders: [],
    layout: {
        type: "row",
        weight: 100,
        children: [
            {
                type: "tabset",
                weight: 50,
                children: [
                    {
                        type: "tab",
                        name: "One",
                        component: "button",
                    }
                ]
            },
            {
                type: "tabset",
                weight: 50,
                children: [
                    {
                        type: "tab",
                        name: "Two",
                        component: "button",
                    }
                ]
            }
        ]
    }
};

Example Code

const model = Model.fromJson(json);

function App() {

  const factory = (node) => {
    var component = node.getComponent();

    if (component === "button") {
      return <button>{node.getName()}</button>;
    }
  }

  return (
    <Layout
      model={model}
      factory={factory} />
  );
}

The above code would render two tabsets horizontally each containing a single tab that hosts a button component. The tabs could be moved and resized by dragging and dropping. Additional grids could be added to the layout by sending actions to the model.

Try it now using JSFiddle

A simple Typescript example can be found here:

https://github.com/nealus/FlexLayout_cra_example

The model is built up using 4 types of 'node':

  • row - rows contains a list of tabsets and child rows, the top level 'row' will render horizontally (unless the global attribute rootOrientationVertical is set) , child 'rows' will render in the opposite orientation to their parent.

  • tabset - tabsets contain a list of tabs and the index of the selected tab

  • tab - tabs specify the name of the component that they should host (that will be loaded via the factory) and the text of the actual tab.

  • border - borders contain a list of tabs and the index of the selected tab, they can only be used in the borders top level element.

The main layout is defined with rows within rows that contain tabsets that themselves contain tabs.

The model json contains 3 top level elements:

  • global - where global options are defined
  • layout - where the main row/tabset/tabs layout hierarchy is defined
  • borders - (optional) where up to 4 borders are defined ("top", "bottom", "left", "right").

Weights on rows and tabsets specify the relative weight of these nodes within the parent row, the actual values do not matter just their relative values (ie two tabsets of weights 30,70 would render the same if they had weights of 3,7).

NOTE: the easiest way to create your initial layout JSON is to use the demo app, modify one of the existing layouts by dragging/dropping and adding nodes then press the 'Show Layout JSON in console' button to print the JSON to the browser developer console.

To control where nodes can be dropped you can add a callback function to the model:

model.setOnAllowDrop(this.allowDrop);

example:

    allowDrop(dragNode, dropInfo) {
        let dropNode = dropInfo.node;

        // prevent non-border tabs dropping into borders
        if (dropNode.getType() == "border" && (dragNode.getParent() == null || dragNode.getParent().getType() != "border"))
            return false;

        // prevent border tabs dropping into main layout
        if (dropNode.getType() != "border" && (dragNode.getParent() != null && dragNode.getParent().getType() == "border"))
            return false;

        return true;
    }

By changing global or node attributes you can change the layout appearance and functionality, for example:

Setting tabSetEnableTabStrip:false in the global options would change the layout into a multi-splitter (without tabs or drag and drop).

 global: {tabSetEnableTabStrip:false},

Floating Tabs (Popouts)

Tabs can be rendered into external browser windows (for use in multi-monitor setups) by configuring them with the enableFloat attribute. When this attribute is present an additional icon is shown in the tab header bar allowing the tab to be popped out into an external window.

For popouts to work there needs to be an additional html page 'popout.html' hosted at the same location as the main page (copy the one from examples/demo). The popout.html is the host page for the popped out tab, the styles from the main page will be copied into it at runtime.

Because popouts are rendering into a different document to the main layout any code in the popped out tab that uses the global document or window objects will not work correctly (for example custom popup menus), they need to instead use the document/window of the popout. To get the document/window of the popout use the following method on one of the elements rendered in the popout (for example a ref or target in an event handler):

    const currentDocument = this.selfRef.current.ownerDocument;
    const currentWindow = currentDocument.defaultView!;

In the above code selfRef is a React ref to the toplevel element in the tab being rendered.

Note: some libraries support popout windows by allowing you to specify the document to use, for example see the getDocument() callback in agGrid at https://www.ag-grid.com/javascript-grid-callbacks/

Optional Props

PropDescription
fontthe tab font (overrides value in css). Example: font={{size:"12px", style:"italic"}}
iconsobject mapping keys among close, maximize, restore, more, popout to React nodes to use in place of the default icons, can alternatively return functions for creating the React nodes
onActionfunction called whenever the layout generates an action to update the model (allows for intercepting actions before they are dispatched to the model, for example, asking the user to confirm a tab close.) Returning undefined from the function will halt the action, otherwise return the action to continue
onRenderTabfunction called when rendering a tab, allows leading (icon), content section, buttons and name used in overflow menu to be customized
onRenderTabSetfunction called when rendering a tabset, allows header and buttons to be customized
onModelChangefunction called when model has changed
onExternalDragfunction called when an external object (not a tab) gets dragged onto the layout, with a single dragenter argument. Should return either undefined to reject the drag/drop or an object with keys dragText, jsonDrop, to create a tab via drag (similar to a call toaddTabToTabSet). FunctiononDropis passed the added tabNodeand thedropDragEvent`, unless the drag was canceled.
classNameMapperfunction called with default css class name, return value is class name that will be used. Mainly for use with css modules.
i18nMapperfunction called for each I18nLabel to allow user translation, currently used for tab and tabset move messages, return undefined to use default values
supportsPopoutif left undefined will do simple check based on userAgent
popoutURLURL of popout window relative to origin, defaults to popout.html
realtimeResizeboolean value, defaults to false, resize tabs as splitters are dragged. Warning: this can cause resizing to become choppy when tabs are slow to draw
onTabDragfunction called while dragging a tab, whether from the layout or using addTabWithDragAndDrop. Called with the TabNode being dragged / the tab json from addTabWithDragAndDrop, the TabNode being dragged over, the x and y coordinates relative to the dragged-over tab, and the DockLocation that would be used. Should return undefined for default behavior, or an object containing x, y, width, height, callback, cursor fields. Coordinates are in pixels relative to the dragged-over tab, and callback will be called with the same arguments if the tab is dropped. cursor is an optional string field that should contain a CSS cursor value, such as copy or row-resize. If callback is called, the layout does not perform its default behavior on drop.
onRenderDragRectcallback for rendering the drag rectangles
onRenderFloatingTabPlaceholdercallback for rendering the floating tab placeholder
onContextMenucallback for handling context actions on tabs and tabsets
onAuxMouseClickcallback for handling mouse clicks on tabs and tabsets with alt, meta, shift keys, also handles center mouse clicks
onShowOverflowMenucallback for handling the display of the tab overflow menu
onTabSetPlaceHoldercallback for rendering a placeholder when a tabset is empty
iconFactorya factory function for creating icon components for tab bar buttons. NOTE: for greater customization of the tab use onRenderTab instead of this callback
titleFactorya factory function for creating title components for tab bar buttons. NOTE: for greater customization of the tab use onRenderTab instead of this callback

Global Config attributes

Attributes allowed in the 'global' element

AttributeDefaultDescription
splitterSize8width in pixels of all splitters between tabsets/borders
splitterExtra0additional width in pixels of the splitter hit test area
legacyOverflowMenufalseuse the legacy text only overflow menu
enableEdgeDocktrue
enableRotateBorderIconstrueboolean indicating if tab icons should rotate with the text in the left and right borders
tabEnableClosetrueallow user to close all tabs via close button
tabCloseType1see values in ICloseType
tabEnableDragtrueallow user to drag all tabs to new location
tabEnableRenametrueallow user to rename all tabs by double clicking
tabEnableFloatfalseenable popouts in all tabs (in popout capable browser)
tabClassNamenull
tabContentClassNamenull
tabIconnull
tabEnableRenderOnDemandtruewhether to avoid rendering component until tab is visible
tabDragSpeed0.3CSS transition speed of drag outlines (in seconds)
tabBorderWidth-1width when added to border, -1 will use border size
tabBorderHeight-1height when added to border, -1 will use border size
tabSetEnableDeleteWhenEmptytrue
tabSetEnableDroptrueallow user to drag tabs into all tabsets
tabSetEnableDragtrueallow user to drag tabs out of all tabsets
tabSetEnableDividetrueallow user to drag tabs to region of all tabsets, splitting into new tabset
tabSetEnableMaximizetrueallow user to maximize all tabsets to fill view via maximize button
tabSetEnableClosefalseallow user to close all tabsets via close button
tabSetAutoSelectTabtruewhether to select new/moved tabs in all tabsets
tabSetClassNameTabStripnullheight in pixels of tab strips in all tabsets
tabSetEnableSingleTabStretchfalseif a tabset has only a single tab then stretch the single tab to fill area and display in a header style
tabSetClassNameHeadernull
tabSetEnableTabStriptrueenable tab strip and allow multiple tabs in all tabsets
tabSetHeaderHeight0height of tabset header in pixels; if left as 0 then the value will be calculated from the current fontSize
tabSetTabStripHeight0height of tabset tab bar in pixels; if left as 0 then the value will be calculated from the current fontSize
borderBarSize0size of the border bars in pixels; if left as 0 then the value will be calculated from the current fontSize
borderEnableAutoHidefalsehide border if it has zero tabs
borderEnableDroptrueallow user to drag tabs into this border
borderAutoSelectTabWhenOpentruewhether to select new/moved tabs in border when the border is already open
borderAutoSelectTabWhenClosedfalsewhether to select new/moved tabs in border when the border is curently closed
borderClassNamenull
borderSize200initial width in pixels for left/right borders, height for top/bottom borders
borderMinSize0minimum width in pixels for left/right borders, height for top/bottom borders
tabSetMinHeight0minimum width (in px) for all tabsets
tabSetMinWidth0minimum height (in px) for all tabsets
tabSetTabLocationtopshow tabs in location top or bottom
rootOrientationVerticalfalsethe top level 'row' will layout horizontally by default, set this option true to make it layout vertically

Row Attributes

Attributes allowed in nodes of type 'row'.

AttributeDefaultDescription
typerow
weight100
widthnullpreferred pixel width
heightnullpreferred pixel height
childrenrequireda list of row and tabset nodes

Tab Attributes

Attributes allowed in nodes of type 'tab'.

Inherited defaults will take their value from the associated global attributes (see above).

AttributeDefaultDescription
typetab
namerequiredname of tab to be displayed in the tab button
altNameoptionalif there is no name specifed then this value will be used in the overflow menu
componentrequiredstring identifying which component to run (for factory)
confignulla place to hold json config for the hosted component
idauto generated
helpTextoptionalAn optional help text for the tab to be displayed upon tab hover.
enableCloseinheritedallow user to close tab via close button
closeTypeinheritedsee values in ICloseType
enableDraginheritedallow user to drag tab to new location
enableRenameinheritedallow user to rename tabs by double clicking
enableFloatinheritedenable popout (in popout capable browser)
floatingfalse
classNameinheritedclass applied to tab button
contentClassNameinheritedclass applied to tab content
tabsetClassNameundefinedclass applied to parent tabset when this is the only tab and it is stretched to fill the tabset
iconinherited
enableRenderOnDemandinheritedwhether to avoid rendering component until tab is visible
borderWidthinheritedwidth when added to border, -1 will use border size
borderHeightinheritedheight when added to border, -1 will use border size

Tab nodes have a getExtraData() method that initially returns an empty object, this is the place to add extra data to a tab node that will not be saved.

TabSet Attributes

Attributes allowed in nodes of type 'tabset'.

Inherited defaults will take their value from the associated global attributes (see above).

Note: tabsets can be dynamically created as tabs are moved and deleted when all their tabs are removed (unless enableDeleteWhenEmpty is false).

AttributeDefaultDescription
typetabset
weight100relative weight for sizing of this tabset in parent row
widthnullpreferred pixel width
heightnullpreferred pixel height
namenullnamed tabsets will show a header bar above the tabs
confignulla place to hold json config used in your own code
selected0index of selected/visible tab in tabset
activefalsewhether tabset is currently active; this attribute can only be used in the initial configuration, to change the active tabset you should use the setActiveTabset action on the model
maximizedfalsewhether tabset is currently maximized to fill view
enableClosefalseallow user to close tabset via a close button
idauto generated
childrenrequireda list of tab nodes
enableDeleteWhenEmptyinherited
enableDropinheritedallow user to drag tabs into this tabset
enableDraginheritedallow user to drag tabs out this tabset
enableDivideinheritedallow user to drag tabs to region of this tabset, splitting into new tabset
enableMaximizeinheritedallow user to maximize tabset to fill view via maximize button
enableSingleTabStretchinheritedif the tabset has only a single tab then stretch the single tab to fill area and display in a header style
autoSelectTabinheritedwhether to select new/moved tabs in tabset
classNameTabStripinherited
classNameHeaderinherited
enableTabStripinheritedenable tab strip and allow multiple tabs in this tabset
headerHeightinherited
tabStripHeightinheritedheight in pixels of tab strip
tabLocationinheritedshow tabs in location top or bottom
minHeightinheritedminimum height (in px) for this tabset
minWidthinheritedminimum width (in px) for this tabset

Border Attributes

Attributes allowed in nodes of type 'border'.

Inherited defaults will take their value from the associated global attributes (see above).

AttributeDefaultDescription
typeborder
sizeinheritedsize of the tab body when selected
minSizeinherited
selected-1index of selected/visible tab in border; -1 means no tab unselected / border closed
idauto generatedborder_ + border name e.g. border_left
confignulla place to hold json config used in your own code
showtrueshow/hide this border
enableAutoHidefalsehide border if it has zero tabs
childrenrequireda list of tab nodes
barSizeinheritedsize of this border's bar in pixels; if left as 0 then the value will be calculated from the current fontSize
enableDropinherited
autoSelectTabWhenOpeninheritedwhether to select new/moved tabs in border when the border is already open
autoSelectTabWhenClosedinheritedwhether to select new/moved tabs in border when the border is currently closed
classNameinheritedclass applied to tab button

Model Actions

All changes to the model are applied through actions. You can intercept actions resulting from GUI changes before they are applied by implementing the onAction callback property of the Layout. You can also apply actions directly using the Model.doAction() method. This method takes a single argument, created by one of the following action generators (typically accessed as FlexLayout.Actions.<actionName>):

Action CreatorDescription
Actions.addNode(newNodeJson, toNodeId, location, index, select?)add a new tab node to the given tabset node; select specifies whether to select new tab, defaulting to autoSelectTab attribute; returns the created Node
Actions.moveNode(fromNodeId, toNodeId, location, index, select?)move a tab node from its current location to the new node and location; select specifies whether to select tab, defaulting to new tabset's autoSelectTab attribute
Actions.deleteTab(tabNodeId)delete the given tab
Actions.renameTab(tabNodeId, newName)rename the given tab
Actions.selectTab(tabNodeId)select the given tab
Actions.setActiveTabset(tabsetNodeId)set the tabset as the active tabset
Actions.adjustSplit(splitterNodeId, value)adjust the size of the given splitter
Actions.adjustBorderSplit(borderNodeId, pos)updates the size of the given border node
Actions.maximizeToggle(tabsetNodeId)toggles whether the given tabset node is maximized
Actions.updateModelAttributes(attributes)updates the global attributes
Actions.updateNodeAttributes(nodeId, attributes)updates the attributes of the given node
Actions.floatTab(nodeId)popout the tab into a floating browser window
Actions.unFloatTab(nodeId)restore a popped out tab to the main layout

Examples

model.doAction(FlexLayout.Actions.updateModelAttributes({
    splitterSize:40,
    tabSetHeaderHeight:40,
    tabSetTabStripHeight:40
}));

The above example would increase the size of the splitters, tabset headers and tabs, this could be used to make adjusting the layout easier on a small device.

model.doAction(FlexLayout.Actions.addNode(
    {type:"tab", component:"grid", name:"a grid", id:"5"},
    "1", FlexLayout.DockLocation.CENTER, 0));

This example adds a new grid component to the center of tabset with id "1" and at the 0'th tab position (use value -1 to add to the end of the tabs). Note: you can get the id of a node (e.g., the node returned by the addNode action) using the method node.getId(). If an id wasn't assigned when the node was created, then one will be created for you of the form #<uuid> (e.g. #0c459064-8dee-444e-8636-eb9ab910fb27).

Layout Component Methods to Create New Tabs

Methods on the Layout Component for adding tabs, the tabs are specified by their layout json.

Example:

this.layoutRef.current.addTabToTabSet("NAVIGATION", {type:"tab", component:"grid", name:"a grid"});

This would add a new grid component to the tabset with id "NAVIGATION" (where this.layoutRef is a ref to the Layout element, see https://reactjs.org/docs/refs-and-the-dom.html ).

Layout MethodDescription
addTabToTabSet(tabsetId, json)adds a new tab to the tabset with the given Id
addTabToActiveTabSet(json)adds a new tab to the active tabset
addTabWithDragAndDrop(dragText, json, onDrop)adds a new tab by dragging a marker to the required location, with the drag starting immediately; on success, onDrop is passed the created tab Node; on cancel, no arguments are passed
addTabWithDragAndDropIndirect(dragText, json, onDrop)adds a new tab by dragging a marker to the required location, the marker is shown and must be clicked on to start dragging
moveTabWithDragAndDrop( node, dragText)Move a tab/tabset using drag and drop triggered from a custom event

Tab Node Events

You can handle events on nodes by adding a listener, this would typically be done in the components constructor() method.

Example:

    constructor(props) {
        super(props);
        let config = this.props.node.getConfig();

        // save state in flexlayout node tree
        this.props.node.setEventListener("save", (p) => {
             config.subject = this.subject;
        };
    }
EventparametersDescription
resizecalled when tab is resized during layout, called before it is rendered with the new size
closecalled when a tab is closed
visibilitycalled when the visibility of a tab changes
savecalled before a tabnode is serialized to json, use to save node config by adding data to the object returned by node.getConfig()

Running the Examples and Building the Project

First install dependencies:

yarn install

Compile the project and run the examples:

yarn start

Open your browser at http://localhost:8080/examples/ to show the examples directory, click on the examples to run them.

The 'yarn start' command will watch for changes to flexlayout and example source, so you can make changes to the code and then refresh the browser to see the result.

To run the tests in the Cypress interactive runner use:

yarn cypress

FlexLayout Cypress tests

To build the npm distribution run 'yarn build', this will create the artifacts in the dist dir.

Alternative Layout Managers

NameRepository
rc-dockhttps://github.com/ticlo/rc-dock
Dockviewhttps://dockview.dev/
luminohttps://github.com/jupyterlab/lumino
golden-layouthttps://github.com/golden-layout/golden-layout
react-mosaichttps://github.com/nomcopter/react-mosaic
0.7.13

6 months ago

0.7.15

6 months ago

0.7.14

6 months ago

0.7.11

8 months ago

0.7.10

8 months ago

0.7.12

7 months ago

0.7.9

9 months ago

0.7.8

9 months ago

0.7.6

1 year ago

0.7.7

1 year ago

0.7.5

2 years ago

0.7.4

2 years ago

0.7.2

2 years ago

0.7.1

2 years ago

0.7.3

2 years ago

0.6.9

2 years ago

0.6.10

2 years ago

0.7.0

2 years ago

0.6.8

2 years ago

0.6.7

2 years ago

0.6.6

2 years ago

0.6.3

2 years ago

0.6.2

2 years ago

0.6.5

2 years ago

0.6.4

2 years ago

0.5.21

2 years ago

0.5.20

2 years ago

0.6.1

2 years ago

0.6.0

2 years ago

0.5.19

3 years ago

0.5.18

3 years ago

0.5.16

3 years ago

0.5.17

3 years ago

0.5.15

3 years ago

0.5.14

3 years ago

0.5.13

3 years ago

0.5.12

3 years ago

0.5.11

3 years ago

0.5.10

3 years ago

0.5.6

3 years ago

0.5.8

3 years ago

0.5.7

3 years ago

0.5.9

3 years ago

0.5.5

3 years ago

0.5.4

4 years ago

0.5.3

4 years ago

0.5.2

4 years ago

0.5.1

4 years ago

0.5.0

4 years ago

0.4.9

4 years ago

0.4.8

4 years ago

0.4.7

4 years ago

0.4.6

4 years ago

0.4.5

4 years ago

0.4.4

4 years ago

0.4.3

4 years ago

0.4.2

4 years ago

0.4.1

4 years ago

0.4.0

4 years ago

0.3.11

4 years ago

0.3.10

4 years ago

0.3.9

4 years ago

0.3.8

4 years ago

0.3.7

4 years ago

0.3.6

4 years ago

0.3.5

4 years ago

0.3.4

5 years ago

0.3.3

5 years ago

0.3.2

5 years ago

0.3.1

6 years ago

0.3.0

6 years ago

0.2.5

7 years ago

0.2.4

7 years ago

0.2.3

7 years ago

0.2.2

7 years ago

0.2.1

7 years ago

0.2.0

7 years ago

0.1.3

7 years ago

0.1.2

7 years ago

0.1.1

7 years ago

0.1.0

7 years ago

0.0.8

7 years ago

0.0.7

8 years ago

0.0.6

8 years ago

0.0.5

8 years ago

0.0.4

8 years ago

0.0.3

8 years ago

0.0.2

8 years ago

0.0.1

8 years ago