Skip to content

Commit

Permalink
feat(tabs): add support for multi row tabs
Browse files Browse the repository at this point in the history
adds support for multi row tabs

#206
  • Loading branch information
arturbien committed Nov 8, 2020
1 parent a396762 commit cebda73
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 17 deletions.
9 changes: 5 additions & 4 deletions src/Tab/Tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,21 @@ const StyledTab = styled.button`
`
z-index: 1;
height: calc(${blockSizes.md} + 4px);
top: -3px;
top: -4px;
margin-bottom: -6px;
padding: 0 16px;
margin-left: -8px;
margin-right: -8px;
&:not(:last-child) {
margin-right: -8px;
}
`}
&:before {
content: '';
position: absolute;
width: calc(100% - 4px);
height: 6px;
background: ${({ theme }) => theme.material};
bottom: -3px;
bottom: -4px;
left: 2px;
}
`;
Expand Down
67 changes: 61 additions & 6 deletions src/Tabs/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,50 @@ import styled from 'styled-components';

const StyledTabs = styled.div`
position: relative;
left: 8px;
${({ isMultiRow, theme }) =>
isMultiRow &&
`
button {
flex-grow: 1;
}
button:last-child:before {
border-right: 2px solid ${theme.borderDark};
}
`}
`;

const Row = styled.div.attrs(() => ({
'data-testid': 'tab-row'
}))`
position: relative;
display: flex;
flex-wrap: no-wrap;
text-align: left;
left: 8px;
width: calc(100% - 8px);
&:not(:first-child):before {
content: '';
position: absolute;
right: 0;
left: 0;
height: 100%;
border-right: 2px solid ${({ theme }) => theme.borderDarkest};
border-left: 2px solid ${({ theme }) => theme.borderLightest};
}
`;

function splitToChunks(array, parts) {
const result = [];
for (let i = parts; i > 0; i -= 1) {
result.push(array.splice(0, Math.ceil(array.length / i)));
}
return result;
}

const Tabs = React.forwardRef(function Tabs(props, ref) {
const { value, onChange, children, ...otherProps } = props;
const { value, onChange, children, rows, ...otherProps } = props;

const childrenWithProps = React.Children.map(children, child => {
if (!React.isValidElement(child)) {
return null;
Expand All @@ -21,22 +59,39 @@ const Tabs = React.forwardRef(function Tabs(props, ref) {
};
return React.cloneElement(child, tabProps);
});

// split tabs into equal rows and assign key to each row
const tabRows = splitToChunks(childrenWithProps, rows).map((tabs, i) => ({
key: i,
tabs
}));

// move row containing currently selected tab to the bottom
const currentlySelectedRowIndex = tabRows.findIndex(tabRow =>
tabRow.tabs.some(tab => tab.props.selected)
);
tabRows.push(tabRows.splice(currentlySelectedRowIndex, 1)[0]);

return (
<StyledTabs {...otherProps} role='tablist' ref={ref}>
{childrenWithProps}
<StyledTabs {...otherProps} isMultiRow={rows > 1} role='tablist' ref={ref}>
{tabRows.map(row => (
<Row key={row.key}>{row.tabs}</Row>
))}
</StyledTabs>
);
});

Tabs.defaultProps = {
onChange: () => {},
children: null
children: null,
rows: 1
};

Tabs.propTypes = {
// eslint-disable-next-line react/require-default-props, react/forbid-prop-types
value: propTypes.any,
onChange: propTypes.func,
children: propTypes.node
children: propTypes.node,
rows: propTypes.number
};
export default Tabs;
51 changes: 51 additions & 0 deletions src/Tabs/Tabs.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,55 @@ describe('<Tabs />', () => {
expect(handleChange.mock.calls[0][1]).toBe(1);
});
});

describe('prop: rows', () => {
it('should render specified number of rows', () => {
const tabs = (
<Tabs value={1} rows={4}>
{/* row 1 */}
<Tab value={0} />
<Tab value={1} />
<Tab value={3} />

{/* row 2 */}
<Tab value={4} />
<Tab value={5} />

{/* row 3 */}
<Tab value={6} />
<Tab value={7} />

{/* row 4 */}
<Tab value={8} />
<Tab value={9} />
</Tabs>
);
const { getAllByTestId } = renderWithTheme(tabs);
const rowElements = getAllByTestId('tab-row');
expect(rowElements.length).toBe(4);
});

it('row containing currently selected tab should be at the bottom (last row)', () => {
const tabs = (
<Tabs value={4} rows={3}>
<Tab value={0} />
<Tab value={1} />
<Tab value={3} />

<Tab value={4} />
<Tab value={5} />
<Tab value={6} />

<Tab value={7} />
<Tab value={8} />
<Tab value={9} />
</Tabs>
);
const { container, getAllByTestId } = renderWithTheme(tabs);
const rowElements = getAllByTestId('tab-row');
const selectedTab = container.querySelector('[aria-selected=true]');

expect(rowElements.pop().contains(selectedTab)).toBe(true);
});
});
});
56 changes: 49 additions & 7 deletions src/Tabs/Tabs.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
WindowContent,
Fieldset,
NumberField,
Checkbox
Checkbox,
Anchor
} from 'react95';

export default {
Expand Down Expand Up @@ -39,12 +40,7 @@ export const Default = () => {
const { activeTab } = state;
return (
<Window style={{ width: 350 }}>
<WindowHeader>
<span role='img' aria-label='dress'>
👗
</span>
store.exe
</WindowHeader>
<WindowHeader>store.exe</WindowHeader>
<WindowContent>
<Tabs value={activeTab} onChange={handleChange}>
<Tab value={0}>Shoes</Tab>
Expand Down Expand Up @@ -87,3 +83,49 @@ export const Default = () => {
Default.story = {
name: 'default'
};

export const MultiRow = () => {
const [state, setState] = useState({
activeTab: 'Shoes'
});

const handleChange = (e, value) => setState({ activeTab: value });

const { activeTab } = state;
return (
<Window style={{ width: 450 }}>
<WindowHeader>store.exe</WindowHeader>
<WindowContent>
<Tabs rows={2} value={activeTab} onChange={handleChange}>
<Tab value='Shoes'>Shoes</Tab>
<Tab value='Accesories'>Accesories</Tab>
<Tab value='Clothing'>Clothing</Tab>
<Tab value='Cars'>Cars</Tab>
<Tab value='Electronics'>Electronics</Tab>
<Tab value='Art'>Art</Tab>
<Tab value='Perfumes'>Perfumes</Tab>
<Tab value='Games'>Games</Tab>
<Tab value='Food'>Food</Tab>
</Tabs>
<TabBody style={{ height: 300 }}>
<p>
Currently active tab: <mark>{activeTab}</mark>
</p>
<br />
<p>
Keep in mind that multi row tabs are{' '}
<Anchor href='http://hallofshame.gp.co.at/tabs.htm' target='_blank'>
REALLY bad UX
</Anchor>
. We&apos;ve added them just because it was a thing back in the day,
but there are better ways to handle navigation with many options.
</p>
</TabBody>
</WindowContent>
</Window>
);
};

MultiRow.story = {
name: 'multi row'
};

1 comment on commit cebda73

@vercel
Copy link

@vercel vercel bot commented on cebda73 Nov 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.