Giter Club home page Giter Club logo

splitter's Introduction

Splitter

Splitter is a React component that allows you to split views into resizable panels. Similar to tabs in Visual Studio Code, for example. Here's a gif of what you can build with Splitter:

Splitter is inspired by Split.js and written as 100% functional component:

  • All size calculation is done through CSS using calc with minimal JS. This makes it much faster
  • Fully responsive
  • No dependencies beside React
  • Minimal assumptions about your styling and views

CodeSandbox project

Installing

npm install @devbookhq/splitter
# or
yarn add @devbookhq/splitter

Usage

Horizontal split

import Splitter, { SplitDirection } from '@devbookhq/splitter'

function MyComponent() {
  return (
    <Splitter>
      <div>Tile 1</div>
      <div>Tile 2</div>
    </Splitter>
  );
}

Vertical split

import Splitter, { SplitDirection } from '@devbookhq/splitter'

function MyComponent() {
  return (
    <Splitter direction={SplitDirection.Vertical}>
      <div>Tile 1</div>
      <div>Tile 2</div>
    </Splitter>
  );
}

Nested split

import Splitter, { SplitDirection } from '@devbookhq/splitter'

function MyComponent() {
  return (
    <Splitter direction={SplitDirection.Vertical}>
      <div>Tile 1</div>
      <Splitter direction={SplitDirection.Horizontal}>
        <div>Tile 2</div>
        <Splitter direction={SplitDirection.Vertical}>
          <div>Tile 3</div>
          <div>Tile 4</div>
        </Splitter>
      </Splitter>
    </Splitter>
  );
}

Get sizes of tiles

import Splitter, { SplitDirection } from '@devbookhq/splitter'

function MyComponent() {
  function handleResizeStarted(gutterIdx: number) {
    console.log('Resize started!', gutterIdx);
  }
  function handleResizeFinished(gutterIdx: number, newSizes: number[]) {
    console.log('Resize finished!', gutterIdx, newSizes);
  }

  return (
    <Splitter
      direction={SplitDirection.Vertical}
      onResizeStarted={handleResizeStarted}
      onResizeFinished={handleResizeFinished}
    >
      <div>Tile 1</div>
      <div>Tile 2</div>
    </Splitter>
  );
}

To see more examples check out the examples section.

Examples

Check the example folder or the CodeSandbox project.

splitter's People

Contributors

arminfro avatar mlejva avatar valentatomas avatar xch1029 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

splitter's Issues

Keyboard support

As far as I can tell, the panels can only be resized by using a pointer device, like a mouse. It would be awesome if the panels could also be resized using a keyboard!

I imagine that the dividers could be buttons instead of divs, and when focused, the arrow keys could resize the panels (maybe it could jump in increments of 10px at a time, to make it a bit less tedious).

I'm excited about this project and I'd be happy to promote it, if it was a bit more accessible!

Thanks for open-sourcing your work 😄

Preventing selection and pointer events while dragging

Hi, is there a way to prevent selection and pointer events while resizing the splitter ?
I tried creating overlay on mousedown which gets removed on mouseup, but that results in resizing snapping back to original size.
I also tried to set splitter children style to userSelect: 'none', pointerEvents: 'none' which works partially, but ideally I would wanna disable the whole viewport from these events because if I am resizing and and I get to minSize and then I go over those element I get bunch of menus/toolbars opens, so its not the best experience.

onReady event

Is there any event that indicates the dom is ready? I need to use scrollIntoView to go bottom of the page when the page refresh.

Equivalent to react-split-pane's primary functionality?

Is it possible to achieve an effect similar to react-split-pane's primary functionality? "The first pane keeps then its size while the second pane is resized by browser window. By default it is the left pane for 'vertical' SplitPane and the top pane for 'horizontal' SplitPane."

I'm looking for a way to "anchor" the size of my leftmost pane.

Feature Request: Add Arrow Icon for Collapsing and Uncollapsing Split Pane

Description:
I would like to propose a feature enhancement for the devbookhq/splitter library to add an arrow icon that can indicate and trigger the collapsible state of the split panes.

Use Case:
Currently, when using the devbookhq/splitter library, there is no built-in visual indicator for showing whether a split pane is collapsed or expanded. Adding an arrow icon that points left when a pane is collapsed and right when it's expanded would greatly improve the user experience and make it more intuitive for users to interact with collapsible split panes.

like this:
image

Expected Behavior:
When a split pane is collapsed, the arrow icon should point left (◄), indicating that clicking on the icon will expand the pane. When the pane is expanded, the arrow icon should point right (►), indicating that clicking on the icon will collapse the pane.

Additional Information:

  • I believe this feature would enhance the usability and user experience of the devbookhq/splitter library by providing a clear visual indication of the collapsible state of split panes.

  • This feature aligns well with the common UX patterns seen in other UI libraries and applications.

  • The proposed solution is backwards compatible, as it introduces a new prop that can be optionally used.

Thank you for considering this feature request. I believe that adding the arrow icon functionality will make the devbookhq/splitter library even more user-friendly and valuable for developers. If there are any questions or concerns about the implementation, I'm happy to discuss further.

Percentage total of a pair is not respected while dragging if more than 2 children are passed to Splitter

Hi

Use Case

I'm using the Splitter to show multiple devices stacked next to each other in a resizable fashion. I'm passing initialSizes to the Splitter to persist the size change in case of re-renders by saving the size array obtained from onResizeFinished event handler.

Problem

If you notice in the elements tab of dev tools, whenever I start dragging a gutter the pair that is involved in the operation loses the percentage sum of that pair which results in the bouncing nature of the other panes in the splitter.

My understanding

I'm attaching a video for reference here. In this video, you'll notice that when I start dragging the first gutter before drag starts the sum of percentage for the pair is (20.98 + 28.35) 49.33%. Now, as soon as I start the drag operation the sum of the percentage of the pair goes above this value (for instance at 0:02 in the video, the values are 36.92 & 29.746 which adds up to 66.66%). Similarly, sometimes it goes below the actual sum in which case you'll notice a whitesmoke colored background after the 3rd device (which shows how the sum of the three panes is not actually 100% but much lower than that.

SplitterIssueRecording2.mov

Although I have handled this issue when the drag ends by calculating the percentage change in one item of the pair. Although, I want to fix this flakiness while dragging.

Unfortunately, I cannot share any code snippets here but I've added a code sandbox to reproduce this. If you can suggest any possible solutions that I could implement at the earliest that would be great.

Sandbox Link:
https://codesandbox.io/embed/devbookhq-spliiter-example-forked-8is0vz?fontsize=14&hidenavigation=1&theme=dark

useLocation somehow breaks this component

Hello, and thank you for this component.
I am using this component inside a Tabpane (provided by Antd) and so far it works fine, but somehow, adding React-router-dom's useLocation hook throws an error when switching tabs.

  • On enabling the tab containing the Splitter and useLocation the component displays fine
  • Switching to any other tab however throws Cannot create pairs - parent has undefined or zero size: undefined. error

Simply remove useLocation from the component and it starts working perfectly again.
I honestly haven't the faintest idea why it behaves that way, I've been at it all morning and I'm all out of ideas atm.
Any pointers would be most helpful.

Thank you.

Add `onDidResize` callback

Would be nice to have this in order to store the user's preferences.

Api could look something like this:

interface SplitProps {
  direction: SplitDirection;
  minWidth?: number; // In pixels.
  minHeight?: number; // In pixels.
  initialSizes?: number[]; // In percentage.
+ onSizesChanged?: (sizes: number[]) => void; // Called when sizes change
  gutterClassName?: string;
  draggerClassName?: string;
  children?: React.ReactNode;
}

[FEATURE] Get current panel sizes

First off I just want to say thanks for a great library. I tried like 3 other splitter libraries before stumbling onto this one, and this one was the one that made the most sense from an implementation standpoint.

My feature request is that I would like to be able to save the position of the splitter to user settings, but I wasn't able to find an easy way to extract the current position of the splitter.

Perhaps a single function that can extract the current percentage of each panel, so that I can just feed those numbers to initialSizes upon form load.

 Updating to the latest version resulted in this error

./node_modules/@devbookhq/splitter/lib/mjs/index.js 209:6
Module parse failed: Unexpected token (209:6)
File was processed with these loaders:
 * ./node_modules/react-scripts/node_modules/babel-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
|     });
|     const n = v.pairs[t];
>     x?.(n.idx), n.a.style.userSelect = "none", n.b.style.userSelect = "none", n.gutter.style.cursor = p(e), n.parent.style.cursor = p(e), document.body.style.cursor = p(e);
|   }, [v.pairs]),
|         N = n.useCallback(() => {

Cannot get window size while resizing

I am using this library in react with canvas. I need to update canvas width when resizing windows using this package.
But it provides on resize start and resize finish methods. Is there any way to get data while resizing window?

Support maxWidths and maxHeights

Some panel contents might not look good if allowed to become very large. It would be great to accept maximum dimensions as well as minimum

Gutter height not properly accounted for

The gutter height, which is subtracted from each section's height, is always initially calculated as being 0 due to a duplicate declaration of the gutterSize variable here which causes the calculation result to stay in the conditional block scope. This causes an issue where the total heights of sections exceeds 100% of the container height. You can see this happening in the screenshots below where the overall splitter container exceeds the height of it's parent, causing content in the last section to be obscured from view.

Screen Shot 2023-06-28 at 10 22 29 AM Screen Shot 2023-06-28 at 10 22 16 AM

Testing with testing-library

Is it possible to run testing-library tests against components which use the splitter?

For example, consider the following test:

import { render, screen } from "@testing-library/react";
import Splitter, { SplitDirection } from '@devbookhq/splitter'

// ResizeObserver is required by DevbookHQ's Splitter component.
// Mock it by using the resize-observer-polyfill installed here as a dev dependency.
global.ResizeObserver = require("resize-observer-polyfill");

describe("Splitter", () => {
  it("works as expected", () => {
    render(
      <Splitter direction={SplitDirection.Vertical}>
        <div>Tile 1</div>
        <div>Tile 2</div>
      </Splitter>
    );

    expect(screen.getByText(/Tile 1/i)).toBeInTheDocument();
  });
});

When the above test is executed, it fails as the render function generates the following HTML, which is missing the two tile divs:

<body>
  <div>
    <div
      class="__dbk__container Vertical"
    />
  </div>
</body>

Allow hidden elements to stay mounted

If I use a splitter from this library with e.g. a tab component, it will sometimes live in a div which is hidden. This seems to result in the children of the splitter not being mounted, which is difficult to work around

Regression in 1.3.2: splitter snaps back to initial size on child change

Consider the following example:

import Splitter from "@devbookhq/splitter";
import { useState, useCallback } from "react";

function MyComponent() {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => setCount(count + 1), [count]);

  return (
    <Splitter>
      <div onClick={handleClick}>Click me</div>
      <div>{count}</div>
    </Splitter>
  );
}

Background

A component which renders a splitter with two children.
The component has a state - a counter.
When the user clicks the first child of the splitter, the counter is incremented.
The second child of the splitter renders the counter.

Reproduction steps

  • After the component is rendered, grab the splitter and move it to a new location
  • Click on the "Click me" child of the splitter

Observed behavior with splitter 1.3.2

The splitter snaps back to its initial location and the counter rendered in the second child goes up.

Observed behavior with 1.2.4

The splitter maintains its current position and the counter rendered in the second child goes up.


I believe the issue is caused by this:
https://github.com/DevbookHQ/splitter/blob/master/src/index.tsx#L358

The useEffect hook is called whenever the children of the splitter change, which leads to the splitter snapping back to its initial location.

Doesn't work on mobile

Tried both using chrome dev tools to emulate a mobile environment as well as using the sandbox demo url from my android phone also using chrome. Dragging w/touch doesn't work.

No working Inside react bootstrap tab

If I implemented this splitter inside react bootstrap tab, on tab change, I am getting the following issue.

Cannot set initial sizes - parent has undefined size

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.