Выше приведено довольно много повторяющегося и ненужного кода.Я потратил некоторое время на рефакторинг и сделал его немного более модульным / более простым в использовании.
Рабочий пример: https://codesandbox.io/s/rlwq35oz4o
Изменения:
Tabs
действует как контейнер для нескольких Tab
компонентов Tab
- это простой многократно используемый компонент, требующий title
и children
- Все вкладки теперь управляются
activeTab
и сравнивается с сопоставленным key
(это можно легко изменить / реализовать для уникального id
) - Использование
prevProps.children.length
для определения длины табуляции в обратном вызове setState
a
(элемент ссылки) TabPane
изменен на b
(неформатированный текстовый элемент), так как это вызвало проблему стилевого оформления со ссылками, встроенными в TabBody
с использованием того же role="tablist"
- Добавлено
word-wrap: break-word;
до a
элементов в TabBody
, чтобы они не сломали section
для маленьких экранов.
index.js
import React from "react";
import { render } from "react-dom";
import Tabs, { Tab } from "./components/Tabs";
import "./styles.css";
const App = () => (
<Tabs>
<Tab title="Section 1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod,
tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim
lacus a velit.<a href="#">Nam luctus</a>, enim in interdum condimentum,
nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non
odio bibendum magna bibendum accumsan.
</Tab>
<Tab title="Section 2">
Nullam at diam nec arcu suscipit auctor non a erat. Sed et magna semper,
eleifend magna non, facilisis nisl. Proin et est et lorem dictum finibus
ut nec turpis. Aenean nisi tortor, euismod a mauris a, mattis scelerisque
tortor. Sed dolor risus, varius a nibh id, condimentum lacinia est. In
lacinia cursus odio a aliquam. Curabitur tortor magna, laoreet ut rhoncus
at, sodales consequat
</Tab>
<Tab title="Section 3">
Phasellus ac tristique orci. Nulla maximus
<a href="">justo nec dignissim consequat</a>. Sed vehicula diam sit amet
mi efficitur vehicula in in nisl. Aliquam erat volutpat. Suspendisse lorem
turpis, accumsan consequat consectetur gravida,
<a href="#">pellentesque ac ante</a>. Aliquam in commodo ligula, sit amet
mollis neque. Vestibulum at facilisis massa.
</Tab>
<Tab title="Section 4">
Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel
volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum
accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam
euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec
laoreet enim lacus a velit.
</Tab>
</Tabs>
);
render(<App />, document.getElementById("root"));
компонентов / Tabs.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import TabPane from "./TabPane";
import TabBody from "./TabBody";
export const Tab = ({ title, children }) => <div tab={title}>{children}</div>;
Tab.propTypes = {
children: PropTypes.node.isRequired,
title: PropTypes.string.isRequired
};
class Tabs extends Component {
state = { activeTab: 0 };
componentDidMount = () => {
document.addEventListener("keydown", this.handleKeyPress, false);
window.focus();
}
componentWillUnmount = () =>
document.removeEventListener("keydown", this.handleKeyPress, false);
handleClickTabItem = ({ target: { id } }) =>
this.setState({ activeTab: ~~id });
handleKeyPress = ({ keyCode }) => {
if (keyCode === 37 || keyCode === 39) {
this.setState((prevState, prevProps) => {
const nextTab = keyCode === 37
? prevState.activeTab - 1
: prevState.activeTab + 1;
return nextTab >= 0 && nextTab < prevProps.children.length
? { activeTab: nextTab }
: null;
});
}
};
render = () => {
const { activeTab } = this.state;
const { children } = this.props;
return (
<div className="tabbed">
<ul role="tablist">
{children.map(({ props }, key) => (
<TabPane
key={key}
activeTab={activeTab}
onClickTabItem={this.handleClickTabItem}
id={key}
{...props}
/>
))}
<div className="tab-content">
{children.map(({ props }, key) =>
key === activeTab ? (
<TabBody key={key} id={key} {...props} />
) : null
)}
</div>
</ul>
</div>
);
};
}
Tabs.propTypes = {
children: PropTypes.node.isRequired
};
export default Tabs;
компонентов / TabPane.js
import React from "react";
import PropTypes from "prop-types";
const TabPane = ({ activeTab, id, onClickTabItem, title, ...rest }) => (
<li role="presentation">
<b
id={id}
aria-selected={activeTab === id ? "true" : null}
onClick={onClickTabItem}
role="tab"
{...rest}
>
{title}
</b>
</li>
);
TabPane.propTypes = {
activeTab: PropTypes.number.isRequired,
id: PropTypes.number.isRequired,
onClickTabItem: PropTypes.func.isRequired,
title: PropTypes.string.isRequired
};
export default TabPane;
компоненты / TabBody.js
import React from "react";
import PropTypes from "prop-types";
const TabBody = ({ title, id, children }) => (
<section id={id} role="tabpanel" tabIndex="-1" aria-labelledby={id}>
<h2>{title}</h2>
<div>{children}</div>
</section>
);
TabBody.propTypes = {
children: PropTypes.node.isRequired,
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired
};
export default TabBody;
styles.css
body {
max-width: 40rem;
padding: 0 1rem;
font-size: 125%;
line-height: 1.5;
margin: 1.5rem auto;
font-family: "Lato", Arial, sans-serif;
font-size: 16px;
}
* {
color: inherit;
margin: 0;
}
[role="tablist"] {
padding: 0;
}
[role="tablist"] li,
[role="tablist"] b {
display: inline-block;
}
[role="tablist"] b {
text-decoration: none;
padding: 0.5rem 1em;
cursor: pointer;
}
[role="tablist"] a {
text-decoration: none;
padding-left: 0.2rem;
word-wrap: break-word;
}
[role="tablist"] [aria-selected] {
border: 2px solid;
background: #fff;
border-bottom: 0;
position: relative;
top: 2px;
}
[role="tabpanel"] {
border: 2px solid;
padding: 1.5rem;
}
[role="tabpanel"] * + * {
margin-top: 0.75rem;
}
*:focus {
outline: none;
box-shadow: inset 0 0 0 4px lightBlue;
}
@media (max-width: 550px) {
[role="tablist"] li,
[role="tablist"] b {
display: block;
position: static;
}
[role="tablist"] b {
border: 2px solid #222 !important;
}
[role="tablist"] li + li b {
border-top: 0 !important;
}
[role="tablist"] [aria-selected] {
position: static;
}
[role="tablist"] [aria-selected]::after {
content: "\0020⬅";
}
[role="tabpanel"] {
border-top: 0;
}
}
section a {
color: rgb(66, 133, 244);
}