79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
|
import React, { FunctionComponent, ReactNode, useEffect, useState } from 'react';
|
||
|
import { useHistory, useLocation, useRouteMatch } from 'react-router';
|
||
|
import { Link } from 'react-router-dom';
|
||
|
|
||
|
export interface Tab {
|
||
|
route: string
|
||
|
name: string
|
||
|
icon ?: string
|
||
|
render: (tab: Tab) => ReactNode
|
||
|
}
|
||
|
|
||
|
export interface RoutedTabsProps {
|
||
|
tabs: Tab[]
|
||
|
baseRoute?: string
|
||
|
defaultTabIndex?: number
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
export const RoutedTabs: FunctionComponent<RoutedTabsProps> = ({ tabs, baseRoute, defaultTabIndex }) => {
|
||
|
const history = useHistory();
|
||
|
const location = useLocation();
|
||
|
|
||
|
const tabRoute = (route: string): string => {
|
||
|
return `${baseRoute}${route}`;
|
||
|
};
|
||
|
|
||
|
const [ selectedTabIndex, setSelectedTabIndex ] = useState(defaultTabIndex || 0);
|
||
|
const expectedTab = tabs[selectedTabIndex];
|
||
|
const expectedTabRoute = tabRoute(expectedTab.route);
|
||
|
|
||
|
let matchExpectedTabRoute = useRouteMatch(expectedTabRoute);
|
||
|
|
||
|
useEffect(() => {
|
||
|
if (matchExpectedTabRoute) return;
|
||
|
|
||
|
const newTabIndex = tabs.findIndex(t => location.pathname === tabRoute(t.route));
|
||
|
|
||
|
if (newTabIndex !== -1) {
|
||
|
selectTab(newTabIndex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
history.push(expectedTabRoute);
|
||
|
}, [matchExpectedTabRoute]);
|
||
|
|
||
|
const selectTab = (tabIndex: number) => {
|
||
|
setSelectedTabIndex(tabIndex);
|
||
|
const newTab = tabs[tabIndex];
|
||
|
history.push(tabRoute(newTab.route));
|
||
|
};
|
||
|
|
||
|
return (
|
||
|
<React.Fragment>
|
||
|
<div className="tabs is-medium is-boxed">
|
||
|
<ul>
|
||
|
{
|
||
|
tabs.map((t: Tab, i: number) => {
|
||
|
return (
|
||
|
<li key={`tab-${i}`} className={`has-background-white ${selectedTabIndex === i ? 'is-active': ''}`}
|
||
|
onClick={selectTab.bind(null, i)}>
|
||
|
<a>
|
||
|
{
|
||
|
t.icon ?
|
||
|
<span className="icon is-small"><i className={t.icon} aria-hidden="true"></i></span> :
|
||
|
null
|
||
|
}
|
||
|
<span>{t.name}</span>
|
||
|
</a>
|
||
|
</li>
|
||
|
);
|
||
|
})
|
||
|
}
|
||
|
</ul>
|
||
|
</div>
|
||
|
{ expectedTab.render(expectedTab) }
|
||
|
</React.Fragment>
|
||
|
);
|
||
|
}
|