React + Material-UIで簡単なタブ作成


できること

<CenteredTabs labels={['label1', 'label2', 'label3']}>
    <div>aa</div>
    <div>bb</div>
    <div>cc</div>
</CenteredTabs>

こんな感じに書くだけで

こんな風にタブページを作れるコンポーネントを作成する

プログラム

公式のReact Tabs component - Material-UIから極力書き換えずにプログラムを作成します。Centered Tab(中央揃え)が気に入ったのでそれにします。

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

const useStyles = makeStyles({
  root: {
    flexGrow: 1,
  },
});

// 引数propsを追加
export default function CenteredTabs(props) {
  const classes = useStyles();
  const [value, setValue] = React.useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <Paper className={classes.root}>
      <Tabs
        value={value}
        onChange={handleChange}
        indicatorColor="primary"
        textColor="primary"
        centered
      >
       {/* 書き換え
        <Tab label="Item One" />
        <Tab label="Item Two" />
        <Tab label="Item Three" />
        */}
      {props.labels.map(label => <Tab label={label}></Tab>)} {/*追加*/}
      </Tabs>
    </Paper>
  );
}

propsで配列を受け取ってMaterial-uiのTabコンポーネントを複数作成するようにします。これで使いまわせるタブが作成できました。使う際はこんな感じ↓

<CenteredTabs labels={['label1', 'label2', 'label3']} />

ただ、これだとまだ各タブのコンテンツが表示できないので、さらに記述していきます。
公式ドキュメントの一番上のSimple Tab(シンプルなタブ)を参考にコンテンツを表示する部分のコードを書きます。各タブのコンテンツのコンポーネントはTabPanelです。

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Box from '@material-ui/core/Box';


const useStyles = makeStyles({
  root: {
    flexGrow: 1,
  },
});

// 追加
const TabPanel = (props) => {
    const { children, value, index, ...other } = props;

    return (
      <div
        role="tabpanel"
        hidden={value !== index}
        id={`simple-tabpanel-${index}`}
        aria-labelledby={`simple-tab-${index}`}
        {...other}
      >
        {value === index && (
          <Box p={3}>
            {children}
          </Box>
        )}
      </div>
    );
}


const CenteredTabs = (props) => {
  const classes = useStyles();
  const [value, setValue] = React.useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <div>
        <Paper className={classes.root}>
            <Tabs
                value={value}
                onChange={handleChange}
                indicatorColor="primary"
                textColor="primary"
                centered
            >
                {props.labels.map(label => <Tab label={label}></Tab>)} {/* さっきの */}
            </Tabs>
        </Paper>

        {/* 公式ドキュメントの各タブのコンテンツ
        <TabPanel value={value} index={0}>
            Item One
        </TabPanel>
        <TabPanel value={value} index={1}>
            Item Two
        </TabPanel>
        <TabPanel value={value} index={2}>
            Item Three
        </TabPanel>
        */}

        {/* 追加 */}
        {props.children.map((child, index) => 
            <TabPanel value={value} index={index}>{child}</TabPanel>)
        }
    </div>
  );
}

export default CenteredTabs

コンポーネント を使う際はこんな感じ↓

<CenteredTabs labels={['label1', 'label2', 'label3']}>
    <div>aa</div>
    <div>bb</div>
    <div>cc</div>
</CenteredTabs>

propsで受け取ったchildrenをTabPanelに埋め込むことで、子要素を各タブのコンテンツとして表示させるコンポーネントにすることができました。これで完成です!