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 Tab Panel component is a commonly used view in both desktop and mobile UIs. Using tabbed views maximizes the real estate available for otherwise large, even full-screen layouts. The switchable view effectively offers different “pages” of UI within a given application view. In this article we’ll take a look at a sample implementation of tabs using React to render our views and handle user interactions.
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 Tab Class
In Ext JS, Tab Panels are passed items with text
attributes and / or icons to display in auto-generated Tabs. To accomplish the same thing in React we need to first build our Tab
and TabPanel
classes. We can then look at an example implementation using both classes along with the CSS needed for styling and layout. Our Tab
class will be used internally by our TabPanel
class.
import React from 'react'; import FontAwesome from '@fortawesome/react-fontawesome'; import * as Icons from '@fortawesome/fontawesome-free-solid'; import './Tab.css'; const Tab = props => { const { tabtext, icon, activetab, cardindex, onClick } = props, isActive = activetab === cardindex ? ' active' : ''; return ( <div className={`tab${isActive}`} onClick={onClick}> {icon ? <FontAwesome icon={Icons[`fa${icon}`]} /> : ''} {tabtext} </div> ); }; export default Tab;
This example requires installing the FontAwesome icon modules:
npm install --save @fortawesome/fontawesome npm install --save @fortawesome/react-fontawesome npm install --save @fortawesome/fontawesome-free-solid
Initially, we import React, FontAwesome, and the FontAwesome icons. Next, we’ll extract some variables from the props passed in from the parent TabPanel
container (which we’ll define in the next section). This will give us:
tabtext
astext
: the tab texticon
(if provided)activetab
: the currently active index / tabcardindex
: the index where this tab resides- The derived
isActive
state indicating whether the active index matches our tab index. We’ll use this to set an “active” CSS class on our tab to differentiate from inactive tabs onClick
: ourTabPanel
‘s tab-click handler method
Finally, we’ll return the JSX that defines the shape of our tab. This will include the class decoration when the tab is active, the click handler, and the tab text / icon.
React Tab CSS
Below is the CSS imported by the above Tab.js
:
.tab { text-align: center; cursor: pointer; border: 2px solid transparent; } .tab:hover { background-color: #d8d8d8; } .tab.active:hover { background-color: transparent; } .tab .svg-inline--fa { margin-right: 8px; }
React TabPanel Class
Next, let’s look at the TabPanel
class:
import React, { Component } from 'react'; import Tab from './Tab'; import './TabPanel.css'; class TabPanel extends Component { static defaultProps = { activetab: 0, className: '', position: 'top' }; constructor(props) { super(props); this.state = { activetab: props.activetab }; } render() { const { children, className, position } = this.props; const { activetab } = this.state; return ( <div {...this.props} className={`${className} tab-panel ${position}`}> <div className={`tab-strip`}> {React.Children.map(children, (child, i) => ( <Tab onClick={this.onTabClick.bind(this, i)} {...child.props} cardindex={i} activetab={activetab} /> ))} </div> <div className="card-ct"> {React.Children.map(children, (child, i) => { let { className } = child.props; className = className ? ` ${className}` : ''; const isActive = i === activetab ? ' active' : ''; const cardProps = { ...child.props, className: `card${className}${isActive}`, cardindex: i, activetab: activetab }; return React.cloneElement(child, cardProps); })} </div> </div> ); } onTabClick(activetab) { this.setState({ activetab }); } } export default TabPanel;
React TabPanel Class Explained
The static defaultProps
property sets the defaults for various props on the TabPanel
. The constructor
method sets the initial state and activetab
property. The onTabClick
method handles the Tab
click that sets the activetab
property that decorates the active tab and shows the active child card. The render
method:
- Combines any
className
string passed in with those added by the class - Collects the
activetab
from the component state object to inform the tabs / cards which one is currently active / visible - In the return:
- Create the wrapping
TabPanel
element that will house the tab container and card container in a flex layout - The tab container is added and we iterate over the child nodes (cards) passed to our
TabPanel
to createTab
instances passing in the child nodes’ text and icon along with the current node index and the active tab index from theTabPanel
‘s state. The active tab is decorated as active when thecardindex
matches theactivetab
. - The child node (card) container is added and we loop over the child nodes, this time calling
React.cloneElement
in order to add a few props likeclassName
,cardindex
, andactivetab
to the original nodes that were passed in. The cloned elements are returned in an array as the child nodes of the parent element. The card whosecardindex
matches theactivetab
is shown while the other cards are hidden using CSS rules.
- Create the wrapping
React TabPanel CSS
Before we look at implementing our TabPanel
, let’s first show the CSS used to make our TabPanel
lay out correctly and toggle active tabs / cards:
.tab-panel { display: flex; } .tab-panel.top { flex-direction: column; } .tab-panel.bottom { flex-direction: column-reverse; } .tab-panel.left { flex-direction: row; } .tab-panel.right { flex-direction: row-reverse; } .tab-strip { background-color: #e8e8e8; display: flex; justify-content: flex-start; } .tab-strip.indicator { justify-content: center; } .tab-panel.top .tab-strip, .tab-panel.bottom .tab-strip { flex-direction: row; } .tab-panel.left .tab-strip, .tab-panel.right .tab-strip { flex-direction: column; } .tab-panel.top .tab, .tab-panel.bottom .tab { padding: 12px 6px; } .tab-panel.left .tab, .tab-panel.right .tab { padding: 6px 12px; } .tab-panel.top .tab.active { border-bottom-color: #1e8bfb; } .tab-panel.left .tab.active { border-right-color: #1e8bfb; } .tab-panel.bottom .tab.active { border-top-color: #1e8bfb; } .tab-panel.right .tab.active { border-left-color: #1e8bfb; } .card-ct { flex: 1; display: flex; } .card { padding: 12px; flex: 1 0 auto; display: none; } .card.active { display: block !important; }
React TabPanel Example
Below is the code used to create a TabPanel
with two tabs / cards each with tab text + icon:
import React, { Component } from 'react'; import TabPanel from './TabPanel'; class App extends Component { render() { return ( <TabPanel style={{ height: '400px', width: '600px' }}> <div tabtext="Home" icon="Home"> Content for the first panel </div> <div tabtext="User" icon="User"> ... and the second panel </div> </TabPanel> ); } } export default App;
In our instance example, we’ve passed in a style
prop to give the TabPanel
specific dimensions. The activetab
index may be passed in as a prop (defaults to 0). Another available prop is `position` that orients the tabs on one of the four TabPanel
‘s edges (default is “top”). Our TabPanel
class appends the position
passed (top, left, right, and bottom) to the className
of the TabPanel
‘s outer element. The CSS rules then position the tabs accordingly using flexbox.
Conclusion
There are a number of pre-built UI solutions that exist in the React community. A few worth mentioning are Material UI, Semantic, and Kendo UI. If you’d like to animate and / or swipe cards within your TabPanel
be sure to check out the code in the Swipeable Example on Material UI’s tab component page. In our next blog article we’ll take a look at the carousel whose utility closely resembles that of the tab panel, but with a bit leaner navigation UI.
Seth Lemmons
Related Posts
-
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
This is part of the Ext JS to React blog series. React is Facebook's breakout…