Skip to content

Commit 46121b9

Browse files
committed
feat(tabs): implement <Tabs> and <StatefulTabs> components
1 parent df4f8ba commit 46121b9

File tree

6 files changed

+152
-0
lines changed

6 files changed

+152
-0
lines changed

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './forms';
22
export * from './table';
3+
export * from './tabs';

src/tabs/StatefulTabs.jsx

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React, { useState } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Tabs } from './Tabs';
4+
5+
export function StatefulTabs({ activeTab: _, tabs, ...props }) {
6+
const { getSelected, select } = useTabs(tabs);
7+
8+
return <Tabs tabs={tabs} {...props} activeTab={getSelected()} onSelect={select} />;
9+
}
10+
11+
StatefulTabs.propTypes = {
12+
activeTab: PropTypes.any,
13+
tabs: PropTypes.arrayOf(PropTypes.object),
14+
};
15+
16+
function useTabs(tabs) {
17+
const [activeTab, setActiveTab] = useState(0);
18+
19+
return {
20+
getSelected() {
21+
return activeTab;
22+
},
23+
select(index) {
24+
if (index >= tabs.length) {
25+
throw new Error('Invalid tab');
26+
}
27+
28+
setActiveTab(index);
29+
},
30+
};
31+
}

src/tabs/TabContent.jsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
export function TabContent({ isActive, content }) {
5+
return (
6+
<div
7+
className={`tab-pane fade ${isActive ? 'show active' : ''}`}
8+
// id="home"
9+
role="tabpanel"
10+
// aria-labelledby="home-tab"
11+
>
12+
{content}
13+
</div>
14+
);
15+
}
16+
17+
TabContent.propTypes = {
18+
isActive: PropTypes.bool,
19+
content: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
20+
};

src/tabs/TabHeader.jsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
export function TabHeader({ index, isActive, title, onSelect }) {
5+
return (
6+
<li className="nav-item">
7+
<a
8+
className={`nav-link ${isActive ? 'active' : ''}`}
9+
// id="home-tab"
10+
data-toggle="tab"
11+
href=""
12+
// href="#home"
13+
onClick={safeClick(onSelect, index)}
14+
role="tab"
15+
// aria-controls="home"
16+
aria-selected={isActive}
17+
>
18+
{title}
19+
</a>
20+
</li>
21+
);
22+
}
23+
24+
TabHeader.propTypes = {
25+
index: PropTypes.number,
26+
isActive: PropTypes.bool,
27+
title: PropTypes.string,
28+
onSelect: PropTypes.func,
29+
};
30+
31+
function safeClick(callback, index) {
32+
return (e) => {
33+
e.stopPropagation();
34+
e.preventDefault();
35+
callback(index);
36+
};
37+
}

src/tabs/Tabs.jsx

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { TabHeader } from './TabHeader';
4+
import { TabContent } from './TabContent';
5+
6+
export function Tabs({ vertical, tabs, activeTab, onlyRenderActiveTab, bordered, onSelect }) {
7+
if (activeTab >= tabs.length) {
8+
console.warn('Invalid tab selected:', activeTab);
9+
}
10+
11+
return (
12+
<div className={`custom-tabs-container ${vertical ? 'd-flex' : ''}`}>
13+
<div className="tabs-navigation">
14+
<ul className={`nav ${vertical ? 'nav-pills flex-column' : 'nav-tabs'}`} id="myTab" role="tablist">
15+
{tabs.map((tab, tabIndex) => (
16+
<TabHeader
17+
key={tabIndex}
18+
index={tabIndex}
19+
isActive={tabIndex === activeTab}
20+
title={tab.title}
21+
onSelect={onSelect}
22+
/>
23+
))}
24+
</ul>
25+
</div>
26+
27+
<div
28+
className={`tab-content ${
29+
vertical ? 'flex-fill ml-3' : `${bordered ? 'border-left border-right border-bottom p-2' : 'py-2'}`
30+
}`}
31+
// id="myTabContent"
32+
>
33+
{onlyRenderActiveTab ? (
34+
<TabContent isActive={true} content={tabs[activeTab] && tabs[activeTab].content} />
35+
) : (
36+
tabs.map((tab, tabIndex) => (
37+
<TabContent key={tabIndex} isActive={tabIndex === activeTab} content={tab.content} />
38+
))
39+
)}
40+
</div>
41+
</div>
42+
);
43+
}
44+
45+
Tabs.defaultProps = {
46+
vertical: false,
47+
bordered: false,
48+
activeTab: 0,
49+
onlyRenderActiveTab: false,
50+
onSelect: () => {},
51+
};
52+
53+
Tabs.propTypes = {
54+
vertical: PropTypes.bool,
55+
tabs: PropTypes.arrayOf(PropTypes.object),
56+
activeTab: PropTypes.number,
57+
onlyRenderActiveTab: PropTypes.bool,
58+
bordered: PropTypes.bool,
59+
};

src/tabs/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './StatefulTabs';
2+
export * from './TabContent';
3+
export * from './TabHeader';
4+
export * from './Tabs';

0 commit comments

Comments
 (0)