react-dismissable-layers

Context and hook to add support for nested, auto-dismissable layers. State can be globally controlled through context. Best used with react-popper.

npm i react-dismissable-layers
yarn add react-dismissable-layers

Quick Start

Add <DismissableLayersGlobalProvider> on a parent component. Use the useDismissable() hook to associate different toggleable components.

import { useDismissable } from 'react-dismissable-layers';

// open and close
const Component = () => {
  const [open, toggleOpen] = useDismissable(false);

  return <div>
    <button onClick={toggleOpen}>Open Tooltip</button>
    {open && (
      <Popper>
        Tooltip Content
      </Popper>
    )}
  </div>
}

import { DismissableLayerContext } from 'react-dismissable-layers';

// close all dismissibles in context
const OtherComponent = () => {
  const dismissOverlay = React.useContext(DismissableLayerContext);

  const close = React.useCallback(() => {
    dismissOverlay.dismissAllGlobally();
  }, [])

  return <button onClick={close}>Close All</button>
}

API

  • DismissableLayersGlobalProvider - global provider for Dismissable Layers, wrap the whole app to make sure the useDismissable hook works with layers.

      interface DismissableLayersGlobalProviderProps {
        /**
         * optional prop, the HTML-node to listen close events, default is `document`
        */
        rootNode?: HTMLElement | Document;
      }
    
      const DismissableLayersGlobalProvider = React.FC<DismissableLayersGlobalProviderProps>
    
  • useDismissable - a hook to toggle and dismiss poppers.

    interface Options {
      /**
       * ref for the popper content, to not close on the content's [dismissEvent] action
       */
      ref?: RefObject<Element>;
    
      /**
       * callback which will be invoked when the popper is closed
       */
      onClose?: null | VoidFunction;
    
      /**
       * event on which popper will be closed, default is `'click'`
       */
      dismissEvent?: DismissEventType;
    
      /**
       * the popper will be closed just by the [dismissEvent] action, without any layers logic, default is `false`
       */
      disableLayers?: boolean;
    
      /**
       * do not close on default prevented events, default is `true`
       */
      skipDefaultPrevented?: boolean;
    }
    
    type Api = readonly [
      isOpened: boolean,
    
      /**
       * function to toggle popper
       */
      toggle: VoidFunction,
    
      /**
       * function to force close popper
       */
      close: VoidFunction
    ];
    
    const useDismissable = (defaultValue = false, options: Options = {}) => Api;
    
  • DismissableLayerContext - a context to read a dissmissable layer, in most cases shouldn't be used in app layer.

      interface DismissableLayerValue<T extends HTMLElement | Document = Document> {
        /**
         * for internal usage only
        */
        readonly _subscriber: Subscriber;
    
        /**
         * root node of the dismiss layer
        */
        readonly rootNode: T;
    
        /**
         * dismiss currently opened in the current layer
        */
        readonly dismiss: VoidFunction;
    
        /**
         * has handler on the current layer
        */
        readonly hasHandler: () => boolean;
    
        /**
         * add close handler to the current layer
        */
        readonly addHandler: (eventType: DismissEventType, handler: DismissEventHandler) => void;
    
        /**
         * remove close handler from the current layer
        */
        readonly removeHandler: (eventType: DismissEventType) => void;
    
        /**
         * dismiss all on all layers
        */
        readonly dismissAllGlobally: VoidFunction;
    
        /**
         * has subscriber on any layer
        */
        readonly hasHandlersGlobally: () => boolean;
      }
    
      const DismissableLayersGlobalProvider = React.FC<DismissableLayersGlobalProviderProps>
    
  • DismissableLayerProvider - provider for Dismissable Layer, wrap the popper content to make the nested poppers works as a nested ones.

      interface DismissableLayerProviderProps {
        /**
         * optional prop, the HTML-node to listen close events, default is `document`
        */
        rootNode?: HTMLElement | Document;
      }
    
      const DismissableLayerProvider = React.FC<DismissableLayerProviderProps>