Code Monkey home page Code Monkey logo

react-resizable-panels's Introduction

React Resizable Panels logo

react-resizable-panels

React components for resizable panel groups/layouts.

Supported input methods include mouse, touch, and keyboard (via Window Splitter).


If you like this project, πŸŽ‰ become a sponsor or β˜• buy me a coffee


FAQ

Can panel sizes be specified in pixels?

No. Pixel-based constraints added significant complexity to the initialization and validation logic and so I've decided not to support them. You may be able to implement a version of this yourself following a pattern like this but it is not officially supported by this library.

How can I fix layout/sizing problems with conditionally rendered panels?

The Panel API doesn't require id and order props because they aren't necessary for static layouts. When panels are conditionally rendered though, it's best to supply these values.

<PanelGroup direction="horizontal">
  {renderSideBar && (
    <>
      <Panel id="sidebar" minSize={25} order={1}>
        <Sidebar />
      </Panel>
      <PanelResizeHandle />
    </>
  )}
  <Panel minSize={25} order={2}>
    <Main />
  </Panel>
</PanelGroup>

Can a attach a ref to the DOM elements?

No. I think exposing two refs (one for the component's imperative API and one for a DOM element) would be awkward. This library does export several utility methods for accessing the underlying DOM elements though. For example:

import {
  getPanelElement,
  getPanelGroupElement,
  getResizeHandleElement,
  Panel,
  PanelGroup,
  PanelResizeHandle,
} from "react-resizable-panels";

export function Example() {
  const refs = useRef();

  useEffect(() => {
    const groupElement = getPanelGroupElement("group");
    const leftPanelElement = getPanelElement("left-panel");
    const rightPanelElement = getPanelElement("right-panel");
    const resizeHandleElement = getResizeHandleElement("resize-handle");

    // If you want to, you can store them in a ref to pass around
    refs.current = {
      groupElement,
      leftPanelElement,
      rightPanelElement,
      resizeHandleElement,
    };
  }, []);

  return (
    <PanelGroup direction="horizontal" id="group">
      <Panel id="left-panel">{/* ... */}</Panel>
      <PanelResizeHandle id="resize-handle" />
      <Panel id="right-panel">{/* ... */}</Panel>
    </PanelGroup>
  );
}

Why don't I see any resize UI?

This likely means that you haven't applied any CSS to style the resize handles. By default, a resize handle is just an empty DOM element. To add styling, use the className or style props:

// Tailwind example
<PanelResizeHandle className="w-2 bg-blue-800" />

How can I use persistent layouts with SSR?

By default, this library uses localStorage to persist layouts. With server rendering, this can cause a flicker when the default layout (rendered on the server) is replaced with the persisted layout (in localStorage). The way to avoid this flicker is to also persist the layout with a cookie like so:

Server component

import ResizablePanels from "@/app/ResizablePanels";
import { cookies } from "next/headers";

export function ServerComponent() {
  const layout = cookies().get("react-resizable-panels:layout");

  let defaultLayout;
  if (layout) {
    defaultLayout = JSON.parse(layout.value);
  }

  return <ClientComponent defaultLayout={defaultLayout} />;
}

Client component

"use client";

import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";

export function ClientComponent({
  defaultLayout = [33, 67],
}: {
  defaultLayout: number[] | undefined;
}) {
  const onLayout = (sizes: number[]) => {
    document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}`;
  };

  return (
    <PanelGroup direction="horizontal" onLayout={onLayout}>
      <Panel defaultSize={defaultLayout[0]}>{/* ... */}</Panel>
      <PanelResizeHandle className="w-2 bg-blue-800" />
      <Panel defaultSize={defaultLayout[1]}>{/* ... */}</Panel>
    </PanelGroup>
  );
}

A demo of this is available here.

react-resizable-panels's People

Contributors

4nnikaf avatar andarist avatar andrskr avatar bvaughn avatar caweidmann avatar d3oxy avatar danilowoz avatar danmindru avatar dylanklohr avatar fundon avatar geobde avatar go7hic avatar jyash97 avatar koralle avatar kyleamathews avatar mohsinulhaq avatar mokendkomer avatar mokshit06 avatar papahigh avatar psvensso avatar psychedelicious avatar reinhard avatar rijk avatar sloebel avatar stefanprobst avatar thibautmarechal avatar wbsdickson 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  avatar  avatar

react-resizable-panels's Issues

Conditional panel positions

PanelGroup infers panel positions based on the order panels are registered in. Conditional rendering can "break" this, as panels registered after mount will be added to the end of the container.

There needs to be a way for panels to specify their ordering via a prop. This prop could be optional for cases when there is no conditional rendering.

Active resize handle styling

If you use the website in Mobile and try resizing panels you will notice that the active styles are not triggered, on debugging I found that on the web it focuses on the handle but in Mobile, the focus is only added if we click the handle but if we start moving it doesn't focus on the handle.

Maybe we can add a data attribute called data-panel-active=true whenever the handle is active and is being dragged. As of now we need to add focus styles for the active handle.

I am open to helping on this issue :D

Reproducible steps:
https://user-images.githubusercontent.com/22376783/209684416-e780f949-4a5d-4008-8c66-09fd61a2c109.mov

Warning during SSR due to useLayoutEffect

Hi @bvaughn, thanks for this amazing library!

I get a warning due to the use of useLayoutEffect in this library during SSR using Next.js:

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.
    at $4b51bd7b90b9da8b$export$2e2bcd8739ae039 (/Users/erikmuller/cnapp/node_modules/react-resizable-panels/dist/react-resizable-panels.js:29:63)

Is it possible to use something like useIsomorphicLayoutEffect instead of useLayoutEffect?

Compile Error with Create React App with React 17

Hello πŸ‘‹πŸ½,

I'm getting a compile error when I try to use the react-resizable-panels components in my project:

Screenshot 2023-01-05 at 5 26 57 PM

I have the following setup in my package.json:

"react": "17.0.0", "react-dom": "17.0.0", "react-scripts": "4.0.3",

I'll appreciate any pointers to resolve this, thank you!

Collapse on click

Thanks for the really nice package!

One feature I'd love to see is the ability to close and open (ie; collapse) a panel on click of the ResizeHandle.

Add min/maxSize support for px values

If your UI is designed to be reasonably sized within specific dimensions, having access to limit size with pixel values is essential.

Currently implementing with CSS values only works in 2-column/2-row setup,

Thanks for working on this component for all of us! <3

Iframe inside panels

I noted that the drag event doesn't work correctly when there's an iframe inside a Panel. It could be because the dragging listener is on the main window, and it doesn't consider the other window, which is the iframe.

Here's a screenrecording (sandbox):

Screen.Recording.2023-01-05.at.14.06.37.mov

Possible solution

It just works if you set point-events: none on the iframe.

Screen.Recording.2023-01-05.at.14.13.17.mov

Mobile (touch) support

If it's possible to do so without introducing too much complexity, it would be nice to support mobile/touch platforms.

The primary purpose for this component is Replay.io, which does not support touch devices, but it would still be nice to support this for other users.

Hope to support more property settings

This is a great job, but when setting defaultSize, maxSize, minSize and other attributes, only numbers are supported. In use, pixels need to be used to operate elements more precisely, and more setting methods are expected to be supported.

external-persistence URL hash hinders history

  1. External persistence
  2. resize a panel or 2
  3. try to go back in history
  4. feel like you're starting an idle game ;)

Seems a new URL hash is written on every mouse move, which makes for a long history history.

Suggested fixes:

  1. debounce would reduce the number of updates
  2. wait for a mouseup (or timer for keyboard control) to change the URL hash.

I guess you might have excessive localStorage writes also, but didn't test for it.


Thanks for this project; I bookmarked for usage later, & I'm sure many other folks will use in their projects!

Ability to keep dragging with mouse outside the window

If you compare https://codesandbox.io/s/react-resizable-panels-zf7hwd and https://react-grid-layout.github.io/react-grid-layout/ you'll notice that react-grid-layout lets you keep dragging even if your mouse leaves the window, but with react-resizable-panels the drag event seems to stop as soon as the mouse leaves the window.

This is probably not a dealbreaker, but it would arguably be nicer UX if the drag event didn't just stop if you accidentally dragged too much.

Sorry, I'm currently on my phone so I can't record a video to make it crystal clear what I'm talking about but I hope the description suffices!

Resizing panels should not require a full re-render

Resizing a panel should just be a matter of changing the style value, which could be done imperatively (without requiring a re-render). This has some consequences, since rendering that's conditional on size would not "work" with a partially imperative UI, but the way panel sizes are being managed (as CSSProperties objects) is somewhat opaque already so this change would probably be okay.

Related: #27

Add isCollapsed boolean or size value to the Imperative API

It would be handy to include either a status boolean or the current size of a panel in the Imperative API to use alongside the methods it provides.

Scenario:
You have a panel you want to toggle open or closed via another button component:

 import {
   ImperativePanelHandle,
   Panel,
   PanelGroup,
   PanelResizeHandle,
 } from "react-resizable-panels";
 
 const ref = useRef<ImperativePanelHandle>(null);
 
 const collapsePanel = () => {
   const panel = ref.current;
   if (panel.isCollapased) {
     panel.expand();
   } else {
     panel.collapse();
   }
 };
 
 <PanelGroup direction="horizontal">
   <Panel collapsible ref={ref}>
     left
   </Panel>
   <PanelResizeHandle />
   <Panel>
     right
   </Panel>
 </PanelGroup>

External persistence

Would it be possible to expose a way to provide custom persistence (for example, if you wanna persist the sizes/layouts to a database rather than local storage)?

Flickering slider saveId bug

The flickering: https://codesandbox.io/s/react-resizable-panels-forked-r68rvr?file=/src/App.js

I've just spent over an hour trying to pinpoint why my sliders were flickering.
Long story short, I had the slider state saved in the local storage with the following content:

{"1:10":[20],"1:10,1:10":[10,30],"1:10,2:10":[16.461173681090695,23.538826318909305],"1:10,2:10,3:10":[18.993775933609953,31.006224066390047,10],"1:10,3:10":[10,30],"1:100,2:10,3:10":[10,40,10],"1:5,2:10,3:10":[10,40,10],"10,10":[16.611570247933884,23.388429752066116],"10,10,10":[0,66.66666666666667,33.333333333333336],"0,10,10":[0,61.74632352941176,38.25367647058824]}"

Deleting the local storage entry fixed my problem.

I don't know how it became broken - I worked off of the codesandbox example so occasionally I had three panels, maybe 4 at most. I'm not sure exactly what the JSON means, but I'm dropping the content of it in here hoping someone will understand what happened, and will help avoid the bug from occurring again. (Not sure whether it's exclusive to development or can also occur in production)

Add minSize/maxSize, but in pixels

Hi! Can you add min/max panel size restrictions, but in pixels? Sometimes, if the window size becomes too small, panel content may shrink too much, which causes UI issues (overflows, etc.).

How to use this in NextJS

Hi,
This looks mighty impressive. However, when I try to use in NextJs, I am seeing the error message "document is not defined".

When I try dynamic importing it, the linter throws me this error

image

Thanks

Panel collapse mode

Panels in VS Code can be fully collapsed when shrunk past a certain point.

Kapture.2022-12-23.at.07.24.54.mp4

This is an interesting UX that might be worth copying (if it can be done without adding a lot of complexity).

Collapse/open via external component

A use-case I would have for this would be clicking on a list item to open up a detailed side panel view, it would great if the library could include the functionality of triggering the opening of the side panel on click of another element.

Playwright e2e tests

Should test various mouse/keyboard interactions as well as layout persistence (including with conditional panels)

useId usage breaks persistence

useId ids are not deterministic, meaning that they break the current persistence mechanism. We should re-work that somehow so that it's not based on panel ids but more on the panel data's attributes (which are deterministic?)

Copy button for code snippits

Ideally, the code snippets should have copy buttons. You can copy the code now by hand, but you end up getting the line numbers as well, which is annoying to try to take out.

Proposed fix in PR #61

Better DEV warnings for invalid props

Invalid default sizes

The following panel group has an invalid configuration, which will cause flickering during resize:

<PanelGroup direction="horizontal">
  <Panel defaultSize={20}>
    {/* ... */}
   </Panel>
  <ResizeHandle />
  <Panel defaultSize={20}>
    {/* ... */}
   </Panel>
</PanelGroup>

Panel with onResize prop and no order prop

The docs warn about this but we could also programmatically detect and warn as well since it is non-obvious.

New size should only be committed after dragging ends

Currently, when dragging panel 'right' and causing another panel 'left' to resize, it immediately commits the new size of the panel 'left', even if I drag panel 'right' back to its original position. See:

Screen.Recording.2023-01-09.at.12.23.14.mov

It feels counterintuitive, since it's quite common that I'm not 100% precise on my drag and will overshoot. Ideally all new sizes are only committed after I release the mouse button and panels spring back to their original position. See e.g. in vscode:

Screen.Recording.2023-01-09.at.12.24.10.mov

Support toggling panel visibility

This is a use case that Replay needs to show/hide the video/canvas panel in the top right so that the secondary toolbox (console, network, etc) can be full height.

In this case the PanelGroup should re-calculate size with the subset of components. If the autoSaveId prop is provided, it should also save/restore sizes separately for each Panel combination.

Server side rendering causes the layout to shift when loading

I am using NextJS and at the start (after a refresh), the panels start in their default configuration (so not whatever the user had before). Then it quickly shifts to the user's intended position (within a couple ms).

It looks like this:

2023-01-17 16 52 40

I feel like the panel should be client side rendered, but I do not want the content of the panels to be client side rendered. Is there anything that can be done against this? I do not have a lot of experience with NextJS, so there is probably an easy solution I am not seeing.

Change cursor based on min/max state

External feature request:

Will there be a change to the mouse cursor changing its icon based on the given state? For example, if the mouse resizes to the min size and it can't keep resizing, the cursor will change to the icon that reflects the allowed directions.

This video is from the VS Code editor.

Support (or recommended approach) for handling "position: absolute" panels

We have a sidebar navigation panel on the left, and main content on the right, and a resizer between them.

I'm sure this is a fairly niche requirement but the navigation panel can be "unpinned" meaning it's given position: absolute and is "disconnected" from the main content so that the main content expands/"falls behind" the sidebar, yet the sidebar can continue to be expanded and collapsed as if the main content was still there.

Because this is using flex, it's not automatically compatible with an absolute panel. I was wondering if you can recommend a solution?

My initial thoughts are to use onResize to set fixed dimensions if it's supposed to be unpinned.

P.S. Sorry for filing an issue, I didn't see a better place to ask

Nested PanelGroup flickering when resizing

I've noticed this border flickering issue when resizing the nested Panel as seen in the below video.
It may be more noticeable on slow machines

2022-12-27.01-12-11.mp4

Module parse error

I am trying to using the library in an existing project bootstrapped with craco and create-react-app. Following is the error I get at the compile time.

./node_modules/react-resizable-panels/dist/react-resizable-panels.module.js 141:23
Module parse failed: Unexpected token (141:23)
File was processed with these loaders:
 * ./node_modules/babel-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
|   if (state) {
|     const key = $6ff1a22b27cc039d$var$getSerializationKey(panels);
>     return state[key] ?? null;
|   }
|

Package.json details:
"@craco/craco": "^6.0.0", "react": "^17.0.2", "react-dom": "^17.0.2",

Please let me know if you need more details.

Direct control of sizes

Are there any ways to directly set the sizes for panels? On init, it seems like the defaultSize attribute for a Panel can do that but then there aren't any ways to override that later.

Add maxSize to Panel

Currently there is a property minSize that restricts flex-grow to become smaller than this value. Can you add a new property maxSize that restricts flex-grow to become larger than this value?

Render custom PanelResizeHandle based on whether it's resizing or not

What you did:

I'm using a styled resize handle, like the following:

<panels.PanelResizeHandle className="w-0.5 relative">
    <div className="absolute inset-y-0 -left-[0.1875rem] w-1.5 bg-blue-500 opacity-0 hover:opacity-100 transition-opacity"></div>
</panels.PanelResizeHandle>

It basically becomes blue on hover.

image

What happened:

The problem is that, while resizing, the handle will stop being highlighted in blue if the mouse moves too fast. It happens because the mouse won't be on top of the handle for a few milliseconds.

Suggested solution:

The PanelResizeHandle component could pass a isResizing boolean to its children so we can keep the highlighting in place. It'd look like this:

<panels.PanelResizeHandle className="w-0.5 relative">
    {({ isResizing }) => (
        <div className={classNames("absolute inset-y-0 -left-[0.1875rem] w-1.5 bg-blue-500 hover:opacity-100 transition-opacity", { "opacity-100": isResizing, "opacity-0": !isResizing })}></div>
    )}
</panels.PanelResizeHandle>

Support pixel values for sizes

We need to set specific pixel size values for the default, min, and max sizes for a panel. Please add support for pixel values, like 200px

export "useId" was not found in "react"

Please note: This is related to Webpack issue: webpack/webpack#14814

Some bundles (Webpack) may throw an error during compilation if code references a named import that a module does not export– even if the import is only used conditionally.

The error thrown by Webpack will look like:

export 'useId' (imported as '$fpI56$useId') was not found in 'react'

To work around this, the following change was made in 0.0.38:

- const useReactId = (React as any).useId as () => string;
+ // `toString()` prevents bundlers from trying to `import { useId } from 'react'`
+ const useReactId = (React as any)['useId'.toString()] as () => string;

Unfortunately it caused a regression and was reverted in 0.0.39. (Seemingly the new syntax confused Parcel.)


(Description modified by @bvaughn)

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.