Git Product home page Git Product logo

codastic / react-positioning-portal Goto Github PK

View Code? Open in Web Editor NEW
24.0 5.0 4.0 4.58 MB

The positioning portal is a low level React component to build all kinds of absolutely positioned flyouts which are anchored to another element in the viewport.

Home Page: https://codastic.github.io/react-positioning-portal/

License: MIT License

TypeScript 45.45% JavaScript 54.55%
react-portal flyout positioning typescript javascript ui ui-component

react-positioning-portal's Introduction

React Positioning Portal

Build Status NPM

The positioning portal is a low level React component to build all kinds of absolutely positioned flyouts which are anchored to another element in the viewport. This can be used to create dropdowns, tooltips, context menus, etc.

The positioning portal is build as unopinionated as possible which means, it creates only mininmal DOM and does not have any dependencies to specific styling libraries. It only uses some inline styles on the portal for positioning.

How the portal is positioned in relation to its anchor in the DOM is defined by a positioning strategy. The default positioning strategy should be enough for a lot of use cases. It either positions the portal above (left or right aligned) to the anchor or below depending on the available space in the current viewport. This default strategy is perfectly suited to build e.g. custom dropdowns.

It is also possible to overwrite the positioning strategy. An example for a tooltip is shown in storybook.

See storybook for more examples.

Installation

$ npm install @codastic/react-positioning-portal react react-dom --save

Usage

Basic usage of <PositioningPortal /> where state is handled outside.

import { useState } from 'react';
import { PositioningPortal } from '@codastic/react-positioning-portal';

// ...

const [isPortalOpen, setIsPortalOpen] = useState(false);

<PositioningPortal
  isOpen={isPortalOpen}
  onOpen={() => setIsPortalOpen(true)}
  onShouldClose={() => setIsPortalOpen(false)}
  portalContent={
    <div>
      Portal content goes here.
      <button type="button" onClick={() => setIsPortalOpen(false)}>
        Close
      </button>
    </div>
  }
>
  <button type="button" onClick={() => setIsPortalOpen(true)}>
    Open portal
  </button>
</PositioningPortal>

Basic usage of <PositioningPortalWithState /> which handles state inside.

import { PositioningPortalWithState } from '@codastic/react-positioning-portal';

// ...

<PositioningPortalWithState
  portalContent={
    <div>
      Portal content goes here.
    </div>
  }
>
  {({ open}) => (
    <button type="button" onClick={open}>
      Open portal
    </button>
  )}
</PositioningPortalWithState>

Features

<PositioningPortal />

  • Supports custom positioning strategies.
  • State handled outside the PositioningPortal.
  • Handles outside click to close the portal.
  • Handles key down to close the portal.
  • Portal transitions when opening and closing.
  • Define a custom root node for the portal.

<PositioningPortalWithState />

This component is almost feature equivalent to <PositioningPortal /> with the exception that the isOpen state is already handled by the component itself. So if you don't have to control this state outside it can be easier to use <PositioningPortalWithState /> and let it handle the state itself.

API Reference

<PositioningPortal />

  • children: React.ReactNode:

    The PositioningPortal component takes the portal's anchor element as children. Its width is passed to the portalContent render prop argument relatedWidth.

  • portalElement?: React.ReactElement: (default: <div />)

    The portalElement wraps the portalContent. When rendering the portalContent the PositioningPortal appends the styles position, width, left, top and visibility to the portalElement. By defining your own styles on the portalElement you can overwrite those styles. E.g. if you prefer a fixed position for the portal you can do the following:

      <PositioningPortal
        portalElement={<div style={{ position: 'fixed' }} />}
      >
      // ...
    
  • portalContent: React.ReactNode | ((params: PortalContentRenderProps<Strategy>) => React.ReactNode);:

    Actual content rendered when the portal is open. portalContent can be any react node or a function returning a react node. The function receives the following props:

    interface PortalContentRenderProps<Strategy> {
      close: () => void; // Basically calls the handler passed to onShouldClose
      isOpen: boolean;
      isPositioned: boolean; // Becomes true after the portalContent is positioned and visible
      strategy: Strategy; // Whatever the positionStrategy returns
      relatedWidth: number; // The width of the PositioningPortal children
      transitionStarted: () => void; // Signals that there is a transition
      transitionEnded: () => void; // Should be called when the portal can safely be removed from the DOM
    }
    
  • onOpen: () => void:

    Will be called after the portal content is positioned and visible.

  • onClose?: () => void

    Callback when portal closes.

  • onShouldClose?: () => void:

    This gets called if PortalContentRenderProps.close gets called, by clicking outside the portal content (in case closeOnOutsideClick is true) or if closeOnKeyDown returns true.

  • closeOnOutsideClick?: boolean: (default: true)

    If set to true, onShouldClose gets called by clicking outside the portal content.

  • closeOnKeyDown?: (event: KeyboardEvent) => boolean: (default: event => event.keyCode === 27, close on ESC)

    A function, which will be called on keydown. If it returns true, the handler passed to onShouldClose will be called.

  • isOpen?: boolean:

    Sets the PositioningPortal to be open or closed.

  • positionStrategy?: PositioningStrategy<Strategy>:

    Sets a custom PositioningStrategy. For details see the PositioningStrategy section.

  • rootNode?: HTMLElement:

    Pass a custom root node for the portal to be added to.

<PositioningPortalWithState />

Extends properties of <PositioningPortal /> and adds/changes the folling properties:

  • children: React.ReactNode | ((params: RenderProps) => React.ReactNode):

    Since state is handled inside of the component, it is possible to render the children via a render function and to receive the following render props:

    interface RenderProps {
      close: () => void;
      open: () => void;
      isOpen: boolean;
    }
    

PositioningStrategy

The positioning strategy is a function which receives the rectangle of the parent anchor element parentRect relative to the viewport (measured with getBoundingClientRect()) and the rectangle for the portal. During the first render phase the portal is rendered invisible until the positioning strategy has been called.

The strategy function should return the position for the portal (top and left). I can optionally return other arbitrary data (strategy) which can be used to customize the rendering of the portal. E.g. to define which orientation the arrow of a tooltip should have.

const positionStrategy (
  parentRect,
  portalRect
  props
) => {
  // Compute where the portal should be positioned...

  return {
    top,
    left,
    strategy
  };
};

This is the type definition of a positioning strategy:

export type PositioningStrategy<Strategy> = (
  parentRect: ClientRect,
  portalRect: ClientRect,
  props: Props<Strategy>
) => {
  top: number;
  left: number;
  strategy: Strategy;
};

Examples

react-positioning-portal's People

Contributors

braunreuthera avatar dependabot[bot] avatar korbinianfritsch avatar ranzwertig avatar tommybacco avatar webholics 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

Watchers

 avatar  avatar  avatar  avatar  avatar

react-positioning-portal's Issues

Doesn't seem to do anything

Hey Codastic team!

I came across this package and gave it a try, but it doesn't seem to have any affect on the DOM. It doesn't seem to be creating a portal, or using one I pass in if I use the rootNode prop.

I tried making my portalContent a function and logging relatedWidth each time it executes, but it apparently never executes. Any ideas? Thanks for sharing!

Portal not visible if that part of the parent div is not visible.

Thank you for this component. I have a problem/feature request:

Problem:

If the calculated position for the portal is in an area of the parent div that is not visible, the portal is not visible.

Duplication:

  1. Run the scrollable test: https://codastic.github.io/react-positioning-portal/?path=/story/example-tooltip--scrollable-test
  2. Move your window to the right so that the right edge, up to the button is not visible:
image
  1. Hover over the button
  2. Observe that the portal can not be seen

Desired Solution:

Consider what's visible in the positioning strategy and, in this case, put the portal on the left of the button instead, and in general find a way to make it visible.

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.