Skip to content

Modus-Logo-Long-BlackCreated with Sketch.

  • Services
  • Work
  • Blog
  • Resources

    OUR RESOURCES

    Innovation Podcast

    Explore transformative innovation with industry leaders.

    Guides & Playbooks

    Implement leading digital innovation with our strategic guides.

    Practical guide to building an effective AI strategy
  • Who we are

    Our story

    Learn about our values, vision, and commitment to client success.

    Open Source

    Discover how we contribute to and benefit from the global open source ecosystem.

    Careers

    Join our dynamic team and shape the future of digital transformation.

    How we built our unique culture
  • Let's talk
  • EN
  • FR

Ext JS to React: Panel

Published on March 20, 2018
Last Updated on April 23, 2021
Application Development

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.

The Ext JS Panel component makes it easy to include a collapsible container with a header element containing a title, collapse control, and any number of other icon tools you choose. In this article we’ll look at how we can set up the collapse wrapper and header element needed to approximate a panel component. The collapsed state of the panel may be managed by either a passed prop or a user control for the same flexibility you might expect from an Ext JS Panel. Additionally, we’ll use the all new Font Awesome 5 font package for our collapse control.

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.

First, let’s install the font icon packages that we’ll use in our panel header and panel instance code below:

npm i --save @fortawesome/fontawesome
npm i --save @fortawesome/react-fontawesome
npm i --save @fortawesome/fontawesome-free-solid
npm i --save @fortawesome/fontawesome-free-brands

React Header Class

Let’s create the Header component that our Panel component will use:

import React, { Component } from 'react';
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import faChevronDown from '@fortawesome/fontawesome-free-solid/faChevronDown';
import './Header.css';

class Header extends Component {
  render() {
    const { collapsible, preTools, postTools, title, toggleCollapse } = this.props;

    return (
      <div className="header">
        {title}
        <div style={{ flex: 1 }}></div>
        {preTools}
        {collapsible &&
          <FontAwesomeIcon
            className="collapse-tool header-tool"
            icon={faChevronDown}
            onClick={toggleCollapse}
          />
        }
        {postTools}
      </div>
    );
  }
}

export default Header;

Our header component accepts a title config to display text similar to the title config in an Ext JS Panel / Header. If collapsible is passed, the header will display a collapse / expand control that calls the function passed in as the toggleCollapse prop. Here is where we use the faChevronDown font icon from our import above. Arbitrary tools, any element or React component you’d like, may be passed in using the preTools and postTools props to live in the space next to the collapse control. We’ll demonstrate this option in our code example further down in the article. Finally, you’ll see there is a full-width spacer div set between the title and the tools to push each to the opposite ends of the header.

React Header CSS

The Header class imports its own Header.css file:

.header {
  display: flex;
  border: 1px solid #3d83cc;
  border-bottom-width: 0;
  background-color: #3d83cc;
  padding: 4px 8px;
  color: white;
}
.header-tool {
  cursor: pointer;
  transition: all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
  margin-left: 6px;
}
.expanded .collapse-tool {
  transform: rotate(-180deg);
}

React Expander Class

Next, we’ll set up the expander logic and JSX used to collapse and expand the panel body. We’ve abstracted the expander out to be its own component. This makes it easy to use in our Panel component or any other component we want to have a collapsible element. The panel’s body element and any “docked” items are rendered within the expander element using a render prop returning the panel body’s JSX.

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

class Expander extends Component {
  static defaultProps = {
    expanded: true
  }

  componentDidMount = () => {
    this.setHeight(this.props.expanded);
    this.forceUpdate();
  }

  componentWillReceiveProps = ({ expanded }) => {
    this.setHeight(expanded);
  }

  setHeight = (expanded) => {
    const { scrollHeight } = this.expandWrap;
    this.wrapHeight = expanded ? scrollHeight + 'px' : 0;
  }

  render () {
    const { className = 'expander-wrap', style = {} } = this.props;
    Object.assign(style, {
      height: this.wrapHeight
    })

    return (
      <div
        ref={el => this.expandWrap = el}
        className={className}
        style={style}
      >
        {this.props.render(this.props)}
      </div>
    );
  }
}

export default Expander;

To animate the expand / collapse action we need to set the height of the wrapping Expander div explicitly. The setHeight method fetches the scrollHeight property of the wrapping div for use by the render method. When the Expander is mounted or receives props the height is re-evaluated. The expanded prop is set to true by default. Passing in expanded as false will collapse the expander div to a height of 0. Our Panel’s toggleCollapse click handler will do just that, which we’ll see in the Panel component code below.

React Expander CSS

The CSS file for the Expander class:

.expander-wrap {
  overflow-y: auto;
  transition: all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
  border: 1px solid #3d83cc;
  border-top-width: 0;
}

React Panel Class

Our Panel class will incorporate the Header and Expander class defined above. We’ll accept any of the props needed by the Header or Expander and pass them along so that the props can be used directly on a Panel instance similar to how the title config option is used in an Ext JS panel and passed on to the Header.

import React, { Component } from 'react';
import Header from './Header';
import Expander from './Expander';
import './Panel.css';

class Panel extends Component {
  static defaultProps = {
    expanded: true
  }

  state = {
    expanded: this.props.expanded
  }

  componentWillReceiveProps = ({ expanded }) => {
    this.setState({ expanded });
  }

  toggleCollapse = () => {
    this.setState({
      expanded: !this.state.expanded
    });
  }

  render() {
    const { collapsible, expandDir, preTools,
      postTools, style = {}, title } = this.props;
    const { expanded } = this.state;
    const showHeader = title.length || collapsible;
    const className = `panel${expanded ? ' expanded' : ''}`;

    return (
      <div className={className} style={style}>
        {showHeader &&
          <Header
            title={title}
            collapsible={collapsible}
            toggleCollapse={this.toggleCollapse}
            preTools={preTools}
            postTools={postTools}
          />
        }
        <Expander expanded={expanded} expandDir={expandDir} render={() => (
          <div className="body-el">
            {this.props.children}
          </div>
        )} />
      </div>
    );
  }
}

export default Panel;

The bulk of the Panel class is the JSX used to render the panel along with its header and expander. If neither the title nor collapsible prop is passed the header will not be rendered. Whether the panel is collapsed or expanded is dictated by the panel’s expanded property in the component state. The expanded property is set by the expanded prop initially as well as when the component is already mounted and receives new props. The expanded prop is also set when the toggleCollapse method is called. The toggleCollapse method is passed as a prop to the header component for use with the expand / collapse control. Within the expander we have a panel body div that will hold any elements / components passed to a panel instance.

React Panel CSS

Below is the CSS used by the Panel class:

.panel {
  display: flex;
  flex-direction: column;
}
.body-el {
  padding: 8px;
}

React Panel Example

Now that we’ve looked at how we might abstract a panel, header, and the expand / collapse mechanism out, let’s take a look at how we might use a Panel component within an application.

import React, { Component } from 'react';
import Panel from './Panel';
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import faReact from '@fortawesome/fontawesome-free-brands/faReact';

class App extends Component {
  render() {
    return (
      <Panel
        title="My Panel"
        collapsible
        style={{width: '300px'}}
        preTools={
          <FontAwesomeIcon
            className="header-tool"
            icon={faReact}
            onClick={() => console.log('React button handler')}
          />
        }
      >
        1
        <br />
        2
        <br />
        3
      </Panel>
    );
  }
}
export default App;

The App component makes use of our Panel class and passes the props needed for our custom panel instance. The panel receives a title and is set as collapsible (boolean props passed with no associated value evaluate to true). We constrain its width to 300px by passing in a style object. The preTools prop is passed on to the header within the panel. By passing a component to preTools we’re able to render an additional user control with a bespoke click handler defined within the component. At render, it will reside just before the collapse / expand control. Finally, we pass in a few arbitrary elements as children of the panel, which end up being the contents hidden from view when the panel is collapsed.

By default the panel is expanded:

Ext JS to React: Panel, Expanded

Clicking on the expand / collapse tool collapses the Panel body (Expander element):

Ext JS to React: Panel, Collapsed

Conclusion

With a little bit of setup we were able to define a flexible set of components that can easily be reused within one or more projects. All of the functionality of our panel could have easily resided within a single component. The Header is effectively just a simple div with a flexbox layout. The expander logic could be wrapped by the Panel class itself versus being defined separately. In this exercise, we explored how that logic could be broken off and easily used across various components, but of course, your own level of encapsulation used will likely vary according to your needs or that of your employer. Stay tuned to this series for our next article on recreating the Ext JS tab panel in React!

Posted in Application Development
Share this

Mitchell Simoens

Mitchell Simoens is a Senior Front End Engineer at Modus Create. Mitchell has spent the last 10 years working with Ext JS including developing core functionality, Sencha Fiddle and (I hope your insurance covers blown minds) supporting the online community with over 40,000 posts on the Sencha Forums. Before working with Ext JS, Mitchell used Perl and PHP but loves spending time with Node JS for today's needs. When not working, you can find Mitchell relaxing with his wife and daughter, or developing his talents as an amateur furniture maker.
Follow

Related Posts

  • React Landing
    Ext JS to React: Migration to Open Source

    Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…

  • Ext JS to React: FAQ
    Ext JS to React: FAQ

    This is part of the Ext JS to React blog series. React is Facebook's breakout…

Want more insights to fuel your innovation efforts?

Sign up to receive our monthly newsletter and exclusive content about digital transformation and product development.

What we do

Our services
AI and data
Product development
Design and UX
IT modernization
Platform and MLOps
Developer experience
Security

Our partners
Atlassian
AWS
GitHub
Other partners

Who we are

Our story
Careers
Open source

Our work

Our case studies

Our resources

Blog
Innovation podcast
Guides & playbooks

Connect with us

Get monthly insights on AI adoption

© 2025 Modus Create, LLC

Privacy PolicySitemap
Scroll To Top
  • Services
  • Work
  • Blog
  • Resources
    • Innovation Podcast
    • Guides & Playbooks
  • Who we are
    • Our story
    • Careers
  • Let’s talk
  • EN
  • FR