A simple tab scroller built with React.js that utilizes jQuery for the animation.
Questions
- I've only just started learning React and I've recently come across
<MyComponent></MyComponent>, though the information was limited. Where can I go to learn more about this? - Would it be better to use the previous question's way to design this program? It seems, if I understand it correctly, that I'm essentially doing the same thing, but the way I've done it doesn't allow for true scalability and isn't the proper way to accomplish this task.
I've created a JSFiddle demo
/**
* Name: React + jQuery Tab Scroller
* Description: A simple Tab Scroller
* @package Chimera Apps
* @version 1.0.7
* @author Chimera.Zen
* @copyright Copyright (c) 2018, Chimera.Zen
* @link https://github.com/ChimeraZen
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
/*
To be added:
- a means of getting the width of the tab list
- enclose the list in a container and get displayed width
- disable TabNav arrow if at start/end of tab list scrollable area
*/
const tabs = [
{
id: 0,
label: "Archery",
content: "Lorem Ipsum 1"
},
{
id: 1,
label: "Baseball",
content: "Lorem Ipsum 2"
},
{
id: 2,
label: "Basketball",
content: "Lorem Ipsum 3"
},
{
id: 3,
label: "Boxing",
content: "Lorem Ipsum 4"
},
{
id: 4,
label: "Football",
content: "Lorem Ipsum 5"
},
{
id: 5,
label: "Golf",
content: "Lorem Ipsum 6"
},
{
id: 6,
label: "Soccer",
content: "Lorem Ipsum 7"
},
{
id: 7,
label: "Surfing",
content: "Lorem Ipsum 8"
}
];
function TabContent(props) {
return (
<div className="tabContent">
{props.content}
</div>
);
}
class Tab extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(el) {
this.props.handleClick(el.target)
}
render() {
let active = (this.props.id === this.props.activeTab) ? "active" : ""
return (
<li id={this.props.id} onClick={this.onClick} className={active}>
{this.props.label}
</li>
);
}
}
class TabList extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
$(this.refs.tabList).animate({scrollLeft: this.props.scrollPosition}, 400)
}
render() {
let tabList = this.props.tabs.map((tab) => {
return (
<Tab
key={tab.id}
id={tab.id}
activeTab={this.props.activeTab}
label={tab.label}
handleClick={this.props.handleClick}
/>
);
});
return (
<ul className="tabList" ref="tabList">
{tabList}
</ul>
);
}
}
class TabNav extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(el) {
this.props.handleClick(el.target)
}
render() {
return (
<div className="tabNav">
<i className="fa fa-chevron-left prev" onClick={this.onClick}></i>
<i className="fa fa-chevron-right next" onClick={this.onClick}></i>
</div>
);
}
}
class TabScroller extends React.Component {
constructor(props) {
super(props);
this.state = {
tabListWidth: 556, // Get TabList width, TabList's total width
scrollDistance: 100, // Distance TabList should be scrolled
scrollPosition: 0,
scrollSpeed: 400, // Transition speed (Time in ms)
activeTab: 0, // ID of active Tab
tabs: tabs // Array of objects {id, label, content}
}
this.handleNavClick = this.handleNavClick.bind(this)
this.handleTabClick = this.handleTabClick.bind(this)
}
handleNavClick(el) {
let scrollPosition = this.state.scrollPosition
let scrollRemaining = this.state.tabListWidth - this.state.scrollPosition
if ($(el).hasClass("next")) {
if (scrollRemaining < this.state.scrollDistance) {
scrollPosition = scrollPosition + scrollRemaining
} else {
scrollPosition = scrollPosition + this.state.scrollDistance
}
} else {
if (scrollPosition - this.state.scrollDistance < this.state.scrollDistance) {
scrollPosition = 0
} else {
scrollPosition = scrollPosition - this.state.scrollDistance
}
}
this.setState({scrollPosition: scrollPosition})
}
handleTabClick(el) {
let tabId = parseInt(el.id)
this.setState({activeTab: tabId})
}
render() {
return (
<div className="tabScroller">
<div className="NavList">
<TabNav handleClick={this.handleNavClick} />
<TabList
tabs={this.state.tabs}
activeTab={this.state.activeTab}
scrollPosition={this.state.scrollPosition}
handleClick={this.handleTabClick}
/>
</div>
<TabContent content={this.state.tabs[this.state.activeTab].content} />
</div>
);
}
}
// ========================================
ReactDOM.render(
<TabScroller />,
document.getElementById('root')
);
* {
margin: 0;
padding: 0;
}
#root {
background: #20262e;
color: white;
}
.tabScroller {
display: flex;
flex-wrap: wrap;
}
.NavList {
display: flex;
overflow: hidden;
}
.tabNav {
display: flex;
justify-content: center;
align-items: center;
}
.tabNav i {
display: flex;
justify-content: center;
align-items: center;
width: 35px;
height: 35px;
cursor: pointer;
}
.tabNav i:hover {
background: #353f4c;
}
ul {
display: flex;
align-items: stretch;
width: 100%;
list-style-type: none;
border-left: 1.5px solid #2D333B;
border-right: 1.5px solid #2D333B;
overflow: hidden;
}
li {
display: flex;
align-items: center;
padding: 0 10px;
cursor: pointer;
transition: all .3s ease-in-out;
}
ul li:last-child {
margin: 0;
border-right: none;
}
ul li:hover {
background: #1566b4;
transition: all .3s ease-in-out;
}
.active {
background: #104C86;
}
.tabContent {
display: flex;
width: 100%;
height: 300px;
padding: 10px;
border-top: 1.5px solid #2D333B;
}
<html>
<body>
<div id="root"></div>
</body>
</html>