react-side-pane

React component - Side pane

Usage no npm install needed!

<script type="module">
  import reactSidePane from 'https://cdn.skypack.dev/react-side-pane';
</script>

README

react-side-pane npm Dependabot badge

First developed and used by Aitenders

License: MIT Stars React React transition group Size Downloads PR styled with: prettier

Live Demo

Getting started

Install react-side-pane

npm i react-side-pane
yarn add react-side-pane

And import it inside your components via

:warning: Breaking change:
As of v2.0.0, you can import SidePane only via its named export (as below).
Prior to v2.0.0, please use its default export import SidePane from "react-side-pane";

import { SidePane } from "react-side-pane";

Usage

react-side-pane is using React Portals under the hood and React transition group to handle the transitions. The SidePane should take all the screen. It can accept only one child (either a function or an element/component/dom node).

The pane only appears from the right.. it is really a react-right-side-pane at the moment. Also, react-side-pane offers only a few props to customize the pane so if you need any additionnal tweaks, feel free to open an issue or to contribute.

One pane usage

<SidePane open={open} width={50} onClose={handleClose}>
    <SomeComponent /> // or some function {() => <>Hello world!</>}
</SidePane>

Multi-pane usage

<SidePane open={open} width={50} onClose={handleClose}>
    {/* Assuming SomeComponent calls a SidePane */}
    {({ onActive }) => <SomeComponentWithASidePane onActive={onActive} />}
</SidePane>

or

// SomeComponent.js
export default function SomeComponent({ someComponentProps, onActive }) {
  // onActive --> callback received from SidePane
  ... // Handle its SidePane open/close state
  return (
    <>
      {...}
      <SidePane open={open} width={50} onActive={onActive} onClose={handleClose}>
        <span>Hello world!</span>
      </SidePane>
    </>
  );
}

// Elsewhere
<SidePane open={open} width={50} onClose={handleClose}>
  <SomeComponent someComponentProps={...} />
</SidePane>

Autowidth

The autoWidth prop will override width by taking the width of the SidePane's content via ref. This ref is passed down from SidePane to its content via forwarding:

const SidePaneContent = React.forwardRef(({ onActive }, ref) => {
  // onActive callback to pass to an inner SidePane (see example above)
  return (
    <div ref={ref} style={{ width: "250px" }}>
    {...}
    </div>
  );
});

Props

Prop Description Default
appNodeId DOM node id that contains the application (for aria-hidden) "root"
aria-describedby aria-describedby ""
aria-label aria-label "side pane"
aria-labelledby aria-labelledby ""
autoWidth Will take the width bounding box's width of the SidePane's child instead of width false
backdropClassName Classname to pass to the backdrop ""
backdropStyle Style object to pass to the backdrop {}
children One React element or a function that can hold the onActive callback (required)
className Classname to pass to the pane ""
containerId You can specify an element ID to receive the side panes (portal). containerId will be passed to document.getElementById. If not filled, the side panes will portal to document.body. See usage below ""
disableBackdropClick Prevents click on backdrop to trigger onClose false
disableEscapeKeyDown Prevents Escape key down to trigger onClose. Recommended: Should not be true as it is part of a11y specs. false
disableRestoreFocus Prevents restoring focus on previous active element after closing. Recommended: Should not be true as it is part of a11y specs. false
duration Animation dur. (ms). Aniamtions are diabled when reduce-motion is on 250 (ms)
hideBackdrop Makes the backdrop transparent false
offset Space (width in %) between parent and child when both are open 10 (%)
onActive Callback from child to parent to pass on the child width on open null
onClose Callback triggered on Escape or click on backdrop (required)
open Whether to display the pane false (required)
style Style object to pass to the pane {}
width Width of the pane in percentage. Max: 100. 0 (%)

Known issues

Focus fighting

Notes: Noticed while using MUI dialog with SidePane

MUI Dialog and SidePane are rendering by default in document.body using Portals. But using both at the same time triggered Maximum call stack size exceeded. at tryFocus from one of our old dependency. We removed said dependency to use react-focus-lock instead which resolved the issue but popped a new one:

FocusLock: focus-fighting detected. Only one focus management system could be active.

To fix this, we are using shards and whiteList props from react-focus-lock. If you stumble upon this one in your console, consider the fix below and if it does not work, open an issue.

<body>
  <div id="root"></div> <!-- your react app container -->
  <div id="container"></div> <!-- the SidePane container (instead of document.body) -->
  <!-- other stuff ... -->
  <!-- other Modals/Dialogs etc... -->
</body>
function App() {
  const [open, dispatchOpen] = useReducer((prev) => !prev, false);
  return (
    <div>
      <button onClick={dispatchOpen}>Open</button>
      <SidePane open={open} onClose={dispatchOpen} width={50} containerId="container">
        <Component />
      </SidePane>
    </div>
    );
}

Credits

This project was started at Aitenders as a more modern and nicer way to display data and additionnal user actions.