Improved NavPills component and added test

This commit is contained in:
Alejandro Celaya 2022-02-14 19:58:20 +01:00
parent b0fa14fcfe
commit f24c8052a9
4 changed files with 70 additions and 27 deletions

View file

@ -1,7 +1,7 @@
import { FC, ReactNode } from 'react'; import { FC, ReactNode } from 'react';
import { Navigate, Routes, Route } from 'react-router-dom'; import { Navigate, Routes, Route } from 'react-router-dom';
import { NoMenuLayout } from '../common/NoMenuLayout'; import { NoMenuLayout } from '../common/NoMenuLayout';
import { NavPills } from '../utils/NavPills'; import { NavPillItem, NavPills } from '../utils/NavPills';
const SettingsSections: FC<{ items: ReactNode[] }> = ({ items }) => ( const SettingsSections: FC<{ items: ReactNode[] }> = ({ items }) => (
<> <>
@ -18,13 +18,11 @@ const Settings = (
Tags: FC, Tags: FC,
) => () => ( ) => () => (
<NoMenuLayout> <NoMenuLayout>
<NavPills <NavPills>
items={[ <NavPillItem to="app">App</NavPillItem>
{ to: 'app', children: 'App' }, <NavPillItem to="short-urls">Short URLs</NavPillItem>
{ to: 'short-urls', children: 'Short URLs' }, <NavPillItem to="others">Others</NavPillItem>
{ to: 'others', children: 'Others' }, </NavPills>
]}
/>
<Routes> <Routes>
<Route path="app" element={<SettingsSections items={[ <UserInterface key="one" />, <RealTimeUpdates key="two" /> ]} />} /> <Route path="app" element={<SettingsSections items={[ <UserInterface key="one" />, <RealTimeUpdates key="two" /> ]} />} />

View file

@ -1,17 +1,29 @@
import { FC, ReactNode } from 'react'; import { FC, Children, isValidElement } from 'react';
import { Card, Nav, NavLink } from 'reactstrap'; import { Card, Nav, NavLink } from 'reactstrap';
import { NavLink as RouterNavLink } from 'react-router-dom'; import { NavLink as RouterNavLink } from 'react-router-dom';
import './NavPills.scss'; import './NavPills.scss';
interface NavPillsProps { interface NavPillProps {
items: { children: ReactNode; to: string; replace?: boolean }[]; to: string;
replace?: boolean;
} }
export const NavPills: FC<NavPillsProps> = ({ items }) => ( export const NavPillItem: FC<NavPillProps> = ({ children, ...rest }) => (
<NavLink className="nav-pills__nav-link" tag={RouterNavLink} {...rest}>
{children}
</NavLink>
);
export const NavPills: FC = ({ children }) => (
<Card className="nav-pills__nav p-0 overflow-hidden mb-3" body> <Card className="nav-pills__nav p-0 overflow-hidden mb-3" body>
<Nav pills fill> <Nav pills fill>
{items.map(({ children, ...rest }, index) => {Children.map(children, (child) => {
<NavLink key={index} className="nav-pills__nav-link" tag={RouterNavLink} {...rest}>{children}</NavLink>)} if (!isValidElement(child) || child.type !== NavPillItem) {
throw new Error('Only NavPillItem children are allowed inside NavPills.');
}
return child;
})}
</Nav> </Nav>
</Card> </Card>
); );

View file

@ -15,7 +15,7 @@ import { Settings } from '../settings/reducers/settings';
import { SelectedServer } from '../servers/data'; import { SelectedServer } from '../servers/data';
import { supportsBotVisits } from '../utils/helpers/features'; import { supportsBotVisits } from '../utils/helpers/features';
import { prettify } from '../utils/helpers/numbers'; import { prettify } from '../utils/helpers/numbers';
import { NavPills } from '../utils/NavPills'; import { NavPillItem, NavPills } from '../utils/NavPills';
import LineChartCard from './charts/LineChartCard'; import LineChartCard from './charts/LineChartCard';
import VisitsTable from './VisitsTable'; import VisitsTable from './VisitsTable';
import { NormalizedOrphanVisit, NormalizedVisit, VisitsFilter, VisitsInfo, VisitsParams } from './types'; import { NormalizedOrphanVisit, NormalizedVisit, VisitsFilter, VisitsInfo, VisitsParams } from './types';
@ -143,18 +143,14 @@ const VisitsStats: FC<VisitsStatsProps> = ({
return ( return (
<> <>
<NavPills <NavPills>
items={Object.values(sections).map(({ title, icon, subPath }) => ({ {Object.values(sections).map(({ title, icon, subPath }, index) => (
replace: true, <NavPillItem key={index} to={buildSectionUrl(subPath)} replace>
to: buildSectionUrl(subPath), <FontAwesomeIcon icon={icon} />
children: ( <span className="ml-2 d-none d-sm-inline">{title}</span>
<> </NavPillItem>
<FontAwesomeIcon icon={icon} /> ))}
<span className="ml-2 d-none d-sm-inline">{title}</span> </NavPills>
</>
),
}))}
/>
<Row> <Row>
<Routes> <Routes>
<Route <Route

View file

@ -0,0 +1,37 @@
import { shallow } from 'enzyme';
import { Card, Nav } from 'reactstrap';
import { NavPillItem, NavPills } from '../../src/utils/NavPills';
describe('<NavPills />', () => {
it.each([
[ 'Foo' ],
[ <span key="1">Hi!</span> ],
[[ <NavPillItem key="1" to="" />, <span key="2">Hi!</span> ]],
])('throws error when any of the children is not a NavPillItem', (children) => {
expect.assertions(1);
try {
shallow(<NavPills>{children}</NavPills>);
} catch (e: any) {
expect(e.message).toEqual('Only NavPillItem children are allowed inside NavPills.');
}
});
it('renders provided items', () => {
const wrapper = shallow(
<NavPills>
<NavPillItem to="1">1</NavPillItem>
<NavPillItem to="2">2</NavPillItem>
<NavPillItem to="3">3</NavPillItem>
</NavPills>,
);
const card = wrapper.find(Card);
const nav = wrapper.find(Nav);
expect(card).toHaveLength(1);
expect(card.prop('body')).toEqual(true);
expect(nav).toHaveLength(1);
expect(nav.prop('pills')).toEqual(true);
expect(nav.prop('fill')).toEqual(true);
});
});