Ext JS to React: Floating Components

   JavaScript
Ext JS to React: Floating Components

This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.

Components are commonly rendered to the page in a rather stationary layout. But, sometimes you want to position a component above the others. Modal alerts, loading indicators, context menus, and tooltips all want to be positioned absolutely on the page. With React we’ll use CSS to position our floating components right where we want them.

Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.

React Menu Class

A common use case is to allow users to right-click to view a menu of options relating to wherever they just clicked. To demonstrate how we can do this in React we’ll modify the starter app generated by create-react-app. Let’s say that we want a popup menu to appear when we right-click in the target div and that we want the menu to appear right where we clicked.

First, let’s define our menu component. Our menu will accept three props: show, x, and y. The show prop determines whether the menu will be visible or not. The x and y props will set the position of the menu on the page.

import React, { Component } from 'react';
import './Menu.css';

class Menu extends Component {
  state = {}

  showMenu = (show) => {
    this.setState({ show });
  }

  componentWillReceiveProps ({ show }) {
    this.showMenu(show);
  }

  componentDidMount () {
    document.body.addEventListener('click', this.showMenu, false);
  }

  componentWillUnmount () {
    document.body.removeEventListener('click', this.showMenu, false);
  }
  
  render() {
    const { x, y, show : menuShow } = this.props;
    const { show = menuShow } = this.state;
    const menuStyle = {
      top: y,
      left: x
    };

    return show ?
      (
        <div style={menuStyle} className="menu">
          {this.props.children}
        </div>
      ) :
      null;
  }
}

export default Menu;

React Menu Class Explained

The Menu class employs the following methods:

  • showMenu: sets the value of show on the component state. This value is what determines visibility within the render method.
  • componentWillReceiveProps: The show value of the component state is updated when new props are passed in
  • componentDidMount: We want the menu to hide when a click event is registered. Here we add a click listener to the document body to hide the menu.
  • componentWillUnmount: When the menu component is unmounted, we remove the document body click handler
  • render: The menu element itself is relatively simple as it’s just a div with a CSS class name of “menu” and is absolutely positioned using the component’s props and state. The menu div wraps any children passed in.

React Menu CSS

Below is the contents of the CSS file imported by the Menu class:

.menu {
  background: white;
  position: absolute;
}
.menu > hr {
  margin: 5px 0;
  border: 1px solid #ececec;
}
.menu-item {
  color: #555;
  padding: 12px;
  cursor: pointer;
  font-size: 16px;
}
.menu-item:hover {
  background: #c7c7c7;
}

To use the menu in our target div we’ll add an onContextMenu handler that sets the menuShow, x, and y values on the state of our App class. That state value will then be passed down to the Menu as a show prop:

import React, { Component } from 'react';
import Menu from './Menu';

const style = {
  height: '300px',
  width: '300px',
  background: '#e02d42',
  padding: '12px',
  color: 'white',
  textAlign: 'center'
}

class App extends Component {
  state = {}

  handleContextMenu = (e) => {
    e.preventDefault();

    const { clientX : x, clientY : y } = e;
    const menuShow = true;
    
    this.setState({ menuShow, x, y });
  }

  render() {
    const { menuShow, x, y } = this.state;

    return (
      <div style={style} onContextMenu={this.handleContextMenu}>
        right-click in this div to show a menu
        <Menu show={menuShow} x={x} y={y}>
          <div className="menu-item">First Menu Item</div>
          <hr />
          <div className="menu-item">Second Menu Item</div>
        </Menu>
      </div>
    );
  }
}

export default App;


Ext JS to React: Floating Components, Menu



You can review the sample code on the Git repo.

React Tooltips Class

Let’s consider how we might go about displaying tooltips in our apps. We’ll want to display some text when hovering the cursor over a target element / component on the page. To do that, we will wrap any target element with a div used to display a tooltip. The Tooltip will accept two props: tiptext which we use as the text of the tooltip and position which will be a class name used to position the tooltip next to our target element:

import React from 'react';
import './Tooltip.css';

const Tooltip = ({ children, tiptext, position = 't-t'}) => (
  <div data-tiptext={tiptext} className={`tooltip ${position}`}>
    {children}
  </div>
)

export default Tooltip;

React Tooltip CSS

In this case, the CSS needed is much weightier than the Tooltip class itself.

.tooltip {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.tooltip:after {
  content: attr(data-tiptext);
  opacity: 0;
  position: absolute;
  padding: 6px 12px;
  background: #555;
  color: white;
  font-size: 12px;
  border-radius: 4px;
  pointer-events: none;

  transition-property: opacity, transform;
	transition-duration: .2s;
}
.tooltip:hover:after {
  opacity : 1;
  transition-delay: .3s;
}
.t-t:after {
  left: 50%;
  transform: translate(-50%, calc(-100% - 6px));
  top: 0;
}
.t-t:hover:after {
  transform: translate(-50%, calc(-100% - 12px));
}
.b-b:after {
  left: 50%;
  bottom: 0;
  transform: translate(-50%, calc(100% + 6px));
}
.b-b:hover:after {
  transform: translate(-50%, calc(100% + 12px));
}

To demonstrate, we’ll render some text and wrap the word “moon” with our Tooltip.

import React, { Component } from 'react';
import Tooltip from './Tooltip';

class App extends Component {
  render() {
    return (
      <div style={{fontSize: '20px', color: '#555', margin: '60px'}}>
        <span>THAT'S NO </span>
        <Tooltip tiptext="DEATH STAR">
          <span style={{color: '#a0a0a0'}}>MOON</span>
        </Tooltip>
      </div>
    );
  }
}

export default App;

Hovering the cursor over the word “MOON” will show the tooltip above it. The position of 't-t' used to position the tooltip over our target is the default value so we can leave it out of the Tooltip instance.

Ext JS to React: Floating Components, Tooltip



If you’d prefer an out-of-the-box solution you might look at Tippy.js. Tippy.js is a javascript solution built on Popper.js that lets you create tooltips that show on hover, show on click, show on delay, contain HTML, are closable, have animations built in, and more!

You can review the sample code on the Git repo.

To Modal or not to Modal

The final popular use case we’ll look at is floating modal components – what you might think of in Ext JS as a MsgBox or a Window. For this example we’ll keep the contents of the floating component generic. That allows you to pass in any element or component you choose and have it float above the application. We’ll also make the modal mask an optional property so that you can choose whether you want to allow users to interact with the underlying application components.

Before we get to our floating component code, let’s take just a moment to discuss a new feature from React 16 that we’ll be taking advantage of: portals. Portals allow a component to be rendered anywhere in the DOM no matter where the component is placed in the overall component composition. If we want a floating loading message to appear in the containing element in which our floating component lives, then there’s no need to use ReactDOM.createPortal() in the render method. However, if a given component’s load action should mask the entire viewport, then that’s where the portal feature will come in handy. To see it in action, let’s first take a look at the Floating component class.

import React, { Component } from 'react';
// Required to use React portals
import ReactDOM from 'react-dom';
import './Floating.css';

class Floating extends Component {
  state = {
    show: !!this.props.show
  };

  componentWillReceiveProps ({ show }) {
    this.setState({ show });
  }

  handleModalClick = () => {
    this.setState({
      show: false
    });
  };

  render () {
    const { modal, viewport, closeOnModalClick } = this.props;
    const { show } = this.state;
    const isModal = modal ? ' modal' : '';
    const doShow = show ? ' show' : '';
    const isViewport = viewport ? ' viewport' : '';
    const clsName = `float-wrap${isModal}${doShow}${isViewport}`;
    const props = {
      onClick: closeOnModalClick ? this.handleModalClick : null
    };
    const float = (
      <div {...props} className={clsName}>
        {this.props.children}
      </div>
    );

    return viewport ? ReactDOM.createPortal(float, document.body) : float;
  }
}

export default Floating;

React Floating Class Explained

Let’s look at the methods from our Floating component:

  • constructor: In the constructor method, we set the show property of the component state using the show prop. The show value will be used to display our floating component via a CSS class name.
  • onModalClick: Handles setting the show value on the component state to false to hide the floating component. Will only be called if the closeOnModalClick property is passed.
  • render: Defines the basic structure of our floating component. The return value is what deserves special mention. If the viewport property is passed, we’ll render our component as a portal using ReactDOM.createPortal. The portal will render to the document body and mask our entire application. If viewport is not passed, then our floating component will render into the DOM naturally as it’s composed.

React Floating Class CSS

Our floating component only has a few methods since most of the modal and positioning business is accomplished via CSS.

.float-wrap {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  display: none;
}
.modal {
  background-color: rgba(0, 0, 0, .1);
  pointer-events: auto;
}
.modal-msg {
  padding: 12px 24px 12px 0;
  background: #ffffff;
  border: 3px solid #61dafb;
  color: #555;
}
.viewport {
  position: fixed;
}
.modal-msg > * {
  vertical-align: middle;
}
.show {
  display: flex;
}

React Floating Component Example

Let’s see our Floating component in action with a simple loading message as the body of the floating component. The Floating component will initially be hidden and shown when clicking on the “Show Floating” button. Clicking on the modal mask will hide the Floating component. Then, to render the Floating component to the wrapping div all we would need to do is omit the viewport prop. Below shows how to render the Floating component across the entire viewport / application:

import React, { Component } from 'react';
import logo from './logo.svg';
import Floating from './Floating';
import './App.css';

class App extends Component {
  state = {};

  showFloating = () => {
    this.setState({
      floating: true
    });
  };

  render() {
    const { floating } = this.state;

    return (
      <div style={{width: '400px', height: '400px', position: 'relative'}}>
        <button onClick={this.showFloating}>Show Floating</button>
        <Floating modal show={floating} viewport closeOnModalClick>
          <div className="modal-msg">
            <img src={logo} className="App-logo" alt="logo" />
            Loading...
          </div>
        </Floating>
      </div>
    );
  }
}

export default App;

We’ve composed the Floating component in a div just below our “show” button in this example, but it could have been composed anywhere since we’re rendering it directly to the document body. React did gift us with a bit of magic with portals, though. If the events fired on the portal should bubble up to the components above it in the JSX hierarchy, then composition of our Floating component would matter. See the React guide for more information.

Ext JS to React: Floating Components, Modal



You can review the sample code on the Git repo.

Conclusion

These small examples demonstrate what you can do with just a little React and CSS for the most elemental use cases. If your application grows in complexity you may need to implement a floating component manager that sets z-index automatically. Other libraries such as react-boostrap, material-ui, and semantic-ui-react also provide many of the floating components you may need if you’re looking for additional pre-built solutions. Your menu component may need to accept a sub-menus component: <MenuItem><Menu /></MenuItem>. With React applications consisting of small, reusable components, each component can be imported as needed so that your application imports complexity only when it needs it!


Like What You See?

Got any questions?