Skip to content

Commit

Permalink
feat(tabs): Allow for higher order components (#196)
Browse files Browse the repository at this point in the history
* Allow for higher order components

* Added tests with higher order components.

* Test if solution works with hoist-non-react-statics
  • Loading branch information
karevn authored and danez committed Sep 5, 2017
1 parent 722d52f commit 1969e65
Show file tree
Hide file tree
Showing 24 changed files with 246 additions and 27 deletions.
11 changes: 7 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
"react/forbid-prop-types": "off",
"react/sort-comp": "off",
"jsx-a11y/no-static-element-interactions": "off",
"import/no-extraneous-dependencies": ["error", {
"devDependencies": ["**/__tests__/*-test.js"],
"optionalDependencies": false
}]
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/__tests__/**/*"],
"optionalDependencies": false
}
]
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"eslint-plugin-jsx-a11y": "^5.0.3",
"eslint-plugin-prettier": "^2.2.0",
"eslint-plugin-react": "^7.0.1",
"hoist-non-react-statics": "^2.3.1",
"husky": "^0.14.3",
"jest-cli": "^20.0.0",
"lint-staged": "^4.0.4",
Expand All @@ -84,7 +85,8 @@
"jest": {
"roots": [
"src"
]
],
"testRegex": "/__tests__/.+-test\\.js$"
},
"lint-staged": {
"src/**/*.js": [
Expand Down
2 changes: 2 additions & 0 deletions src/components/Tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,5 @@ export default class Tab extends Component {
);
}
}

Tab.tabsRole = 'Tab';
2 changes: 2 additions & 0 deletions src/components/TabList.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ export default class TabList extends Component {
);
}
}

TabList.tabsRole = 'TabList';
2 changes: 2 additions & 0 deletions src/components/TabPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ export default class TabPanel extends Component {
);
}
}

TabPanel.tabsRole = 'TabPanel';
2 changes: 2 additions & 0 deletions src/components/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,5 @@ For more information about controlled and uncontrolled mode of react-tabs see th
return <UncontrolledTabs {...props}>{children}</UncontrolledTabs>;
}
}

Tabs.tabsRole = 'Tabs';
10 changes: 4 additions & 6 deletions src/components/UncontrolledTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import React, { cloneElement, Component } from 'react';
import cx from 'classnames';
import uuid from '../helpers/uuid';
import { childrenPropType } from '../helpers/propTypes';
import Tab from './Tab';
import TabList from './TabList';
import TabPanel from './TabPanel';
import { getPanelsCount, getTabsCount } from '../helpers/count';
import { deepMap } from '../helpers/childrenDeepMap';
import { isTabList, isTabPanel, isTab } from '../helpers/elementTypes';

// Determine if a node from event.target is a Tab element
function isTabNode(node) {
Expand Down Expand Up @@ -145,7 +143,7 @@ export default class UncontrolledTabs extends Component {
let result = child;

// Clone TabList and Tab components to have refs
if (child.type === TabList) {
if (isTabList(child)) {
let listIndex = 0;

// Figure out if the current focus in the DOM is set on a Tab
Expand All @@ -155,7 +153,7 @@ export default class UncontrolledTabs extends Component {
if (canUseActiveElement) {
wasTabFocused = React.Children
.toArray(child.props.children)
.filter(tab => tab.type === Tab)
.filter(isTab)
.some((tab, i) => document.activeElement === this.getTab(i));
}

Expand All @@ -182,7 +180,7 @@ export default class UncontrolledTabs extends Component {
return cloneElement(tab, props);
}),
});
} else if (child.type === TabPanel) {
} else if (isTabPanel(child)) {
const props = {
id: this.panelIds[index],
tabId: this.tabIds[index],
Expand Down
5 changes: 5 additions & 0 deletions src/components/__tests__/Tab-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import Tab from '../Tab';
import { TabWrapper } from './helpers/higherOrder';

function expectToMatchSnapshot(component) {
expect(renderer.create(component).toJSON()).toMatchSnapshot();
Expand Down Expand Up @@ -51,4 +52,8 @@ describe('<Tab />', () => {
// eslint-disable-next-line jsx-a11y/aria-role
expectToMatchSnapshot(<Tab role="micro-tab" />);
});

it('should allow to be wrapped in higher-order-component', () => {
expectToMatchSnapshot(<TabWrapper />);
});
});
14 changes: 14 additions & 0 deletions src/components/__tests__/TabList-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Tab from '../Tab';
import TabList from '../TabList';
import TabPanel from '../TabPanel';
import Tabs from '../Tabs';
import { TabListWrapper, TabWrapper } from './helpers/higherOrder';

function expectToMatchSnapshot(component) {
expect(renderer.create(component).toJSON()).toMatchSnapshot();
Expand Down Expand Up @@ -77,4 +78,17 @@ describe('<TabList />', () => {
</Tabs>,
);
});

it('should allow for higher order components', () => {
expectToMatchSnapshot(
<Tabs>
<TabListWrapper>
<TabWrapper>Foo</TabWrapper>
<TabWrapper>Bar</TabWrapper>
</TabListWrapper>
<TabPanel>Foo</TabPanel>
<TabPanel>Bar</TabPanel>
</Tabs>,
);
});
});
5 changes: 5 additions & 0 deletions src/components/__tests__/TabPanel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import TabPanel from '../TabPanel';
import { TabPanelWrapper } from './helpers/higherOrder';

function expectToMatchSnapshot(component) {
expect(renderer.create(component).toJSON()).toMatchSnapshot();
Expand Down Expand Up @@ -55,4 +56,8 @@ describe('<TabPanel />', () => {
// eslint-disable-next-line jsx-a11y/aria-role
expectToMatchSnapshot(<TabPanel role="micro-tab" />);
});

it('should allow for higher-order components', () => {
expectToMatchSnapshot(<TabPanelWrapper />);
});
});
14 changes: 14 additions & 0 deletions src/components/__tests__/Tabs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import TabList from '../TabList';
import TabPanel from '../TabPanel';
import Tabs from '../Tabs';
import { reset as resetIdCounter } from '../../helpers/uuid';
import { TabListWrapper, TabWrapper, TabPanelWrapper } from './helpers/higherOrder';

function expectToMatchSnapshot(component) {
expect(renderer.create(component).toJSON()).toMatchSnapshot();
Expand Down Expand Up @@ -483,4 +484,17 @@ describe('<Tabs />', () => {
.simulate('click');
assertTabSelected(wrapper, 2);
});

it('should allow for higher order components', () => {
expectToMatchSnapshot(
<Tabs>
<TabListWrapper>
<TabWrapper>Foo</TabWrapper>
<TabWrapper>Bar</TabWrapper>
</TabListWrapper>
<TabPanelWrapper>Foo</TabPanelWrapper>
<TabPanelWrapper>Bar</TabPanelWrapper>
</Tabs>,
);
});
});
12 changes: 12 additions & 0 deletions src/components/__tests__/__snapshots__/Tab-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ exports[`<Tab /> should accept className 1`] = `
/>
`;

exports[`<Tab /> should allow to be wrapped in higher-order-component 1`] = `
<li
aria-controls={null}
aria-disabled="false"
aria-selected="false"
className="react-tabs__tab"
id={null}
role="tab"
tabIndex={null}
/>
`;

exports[`<Tab /> should have sane defaults 1`] = `
<li
aria-controls={null}
Expand Down
53 changes: 53 additions & 0 deletions src/components/__tests__/__snapshots__/TabList-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,59 @@ exports[`<TabList /> should accept className 1`] = `
/>
`;

exports[`<TabList /> should allow for higher order components 1`] = `
<div
className="react-tabs"
data-tabs={true}
onClick={[Function]}
onKeyDown={[Function]}
>
<ul
className="react-tabs__tab-list"
role="tablist"
>
<li
aria-controls="react-tabs-13"
aria-disabled="false"
aria-selected="true"
className="react-tabs__tab react-tabs__tab--selected"
id="react-tabs-12"
role="tab"
tabIndex="0"
>
Foo
</li>
<li
aria-controls="react-tabs-15"
aria-disabled="false"
aria-selected="false"
className="react-tabs__tab"
id="react-tabs-14"
role="tab"
tabIndex={null}
>
Bar
</li>
</ul>
<div
aria-labelledby="react-tabs-12"
className="react-tabs__tab-panel react-tabs__tab-panel--selected"
id="react-tabs-13"
role="tabpanel"
style={Object {}}
>
Foo
</div>
<div
aria-labelledby="react-tabs-14"
className="react-tabs__tab-panel"
id="react-tabs-15"
role="tabpanel"
style={Object {}}
/>
</div>
`;

exports[`<TabList /> should display the custom classnames for selected and disabled tab 1`] = `
<div
className="react-tabs"
Expand Down
10 changes: 10 additions & 0 deletions src/components/__tests__/__snapshots__/TabPanel-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ exports[`<TabPanel /> should accept className 1`] = `
/>
`;

exports[`<TabPanel /> should allow for higher-order components 1`] = `
<div
aria-labelledby={undefined}
className="react-tabs__tab-panel"
id={undefined}
role="tabpanel"
style={Object {}}
/>
`;

exports[`<TabPanel /> should have sane defaults 1`] = `
<div
aria-labelledby={undefined}
Expand Down
53 changes: 53 additions & 0 deletions src/components/__tests__/__snapshots__/Tabs-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,59 @@ exports[`<Tabs /> props should honor positive defaultIndex prop 1`] = `
</div>
`;

exports[`<Tabs /> should allow for higher order components 1`] = `
<div
className="react-tabs"
data-tabs={true}
onClick={[Function]}
onKeyDown={[Function]}
>
<ul
className="react-tabs__tab-list"
role="tablist"
>
<li
aria-controls="react-tabs-1"
aria-disabled="false"
aria-selected="true"
className="react-tabs__tab react-tabs__tab--selected"
id="react-tabs-0"
role="tab"
tabIndex="0"
>
Foo
</li>
<li
aria-controls="react-tabs-3"
aria-disabled="false"
aria-selected="false"
className="react-tabs__tab"
id="react-tabs-2"
role="tab"
tabIndex={null}
>
Bar
</li>
</ul>
<div
aria-labelledby="react-tabs-0"
className="react-tabs__tab-panel react-tabs__tab-panel--selected"
id="react-tabs-1"
role="tabpanel"
style={Object {}}
>
Foo
</div>
<div
aria-labelledby="react-tabs-2"
className="react-tabs__tab-panel"
id="react-tabs-3"
role="tabpanel"
style={Object {}}
/>
</div>
`;

exports[`<Tabs /> should not add known props to dom 1`] = `
<div
className="react-tabs"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import hoist from 'hoist-non-react-statics';
import TabList from '../../../../components/TabList';

function TabListWrapper(props) {
return <TabList {...props} />;
}

export default hoist(TabListWrapper, TabList);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import hoist from 'hoist-non-react-statics';
import TabPanel from '../../../../components/TabPanel';

function TabPanelWrapper(props) {
return <TabPanel {...props} />;
}

export default hoist(TabPanelWrapper, TabPanel);
9 changes: 9 additions & 0 deletions src/components/__tests__/helpers/higherOrder/TabWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import hoist from 'hoist-non-react-statics';
import Tab from '../../../../components/Tab';

function TabWrapper(props) {
return <Tab {...props} />;
}

export default hoist(TabWrapper, Tab);
5 changes: 5 additions & 0 deletions src/components/__tests__/helpers/higherOrder/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import TabWrapper from './TabWrapper';
import TabListWrapper from './TabListWrapper';
import TabPanelWrapper from './TabPanelWrapper';

export { TabWrapper, TabListWrapper, TabPanelWrapper };
Loading

0 comments on commit 1969e65

Please sign in to comment.