From 2dc8ea881b563f279877540702074d1b62abab52 Mon Sep 17 00:00:00 2001 From: Chris Manciero Date: Fri, 15 Feb 2019 15:54:56 -0500 Subject: [PATCH 1/9] fix: Refactor Tabs component, separated controls --- src/Tabs/Tab.js | 50 ++++ src/Tabs/TabContent.js | 36 +++ src/Tabs/TabGroup.js | 79 ++++++ src/Tabs/Tabs.Component.js | 201 ++----------- src/Tabs/Tabs.js | 143 ---------- src/Tabs/Tabs.test.js | 132 +++------ src/Tabs/__snapshots__/Tabs.test.js.snap | 344 +++++++++++------------ src/index.js | 4 +- 8 files changed, 409 insertions(+), 580 deletions(-) create mode 100644 src/Tabs/Tab.js create mode 100644 src/Tabs/TabContent.js create mode 100644 src/Tabs/TabGroup.js delete mode 100644 src/Tabs/Tabs.js diff --git a/src/Tabs/Tab.js b/src/Tabs/Tab.js new file mode 100644 index 000000000..ef3eb0028 --- /dev/null +++ b/src/Tabs/Tab.js @@ -0,0 +1,50 @@ +import classnames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; + +export const Tab = (props) => { + const { title, className, disabled, glyph, id, selected, tabLinkProps, ...rest } = props; + + // css classes used for tabs + const linkClasses = classnames( + className, + 'fd-tabs__link', + { + [`sap-icon--${glyph}`]: !!glyph + } + ); + + return ( + { + props.onClick(event, id); + } : null} + role='tab'> + {title} + + ); +}; +Tab.displayName = 'Tab'; + +Tab.propTypes = { + className: PropTypes.string, + disabled: PropTypes.bool, + glyph: PropTypes.string, + id: PropTypes.string, + selected: PropTypes.bool, + title: PropTypes.string +}; + +Tab.propDescriptions = { + glyph: 'Icon to display on tab', + selected: 'Set to **true** to mark tab as selected.', + tabLinkProps: 'Additional props to be spread to the tab\'s link element.', + title: 'String to display on tab' +}; diff --git a/src/Tabs/TabContent.js b/src/Tabs/TabContent.js new file mode 100644 index 000000000..2e618495c --- /dev/null +++ b/src/Tabs/TabContent.js @@ -0,0 +1,36 @@ +import classnames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; + +export const TabContent = (props) => { + const { children, id, selected, className, ...tabContentProps } = props; + + // css classes for tab panels + const tabPanelClasses = classnames( + 'fd-tabs__panel', + className + ); + + return ( +
+ {children} +
+ ); +}; +TabContent.displayName = 'TabContent'; + +TabContent.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + id: PropTypes.string, + selected: PropTypes.bool +}; + +TabContent.propDescriptions = { + children: 'Content to render when tab is selected.' +}; diff --git a/src/Tabs/TabGroup.js b/src/Tabs/TabGroup.js new file mode 100644 index 000000000..cfe53c780 --- /dev/null +++ b/src/Tabs/TabGroup.js @@ -0,0 +1,79 @@ +import classnames from 'classnames'; +import PropTypes from 'prop-types'; +import { Tab } from './Tab'; +import { TabContent } from './TabContent'; +import React, { useState } from 'react'; + +export const TabGroup = (props) => { + const { children, className, selectedId, tabLinkProps, tabContentProps, ...tabGroupProps } = props; + const [selectedTab, setSelectedTab] = useState(selectedId); + + // css classes to use for tab group + const tabGroupClasses = classnames( + 'fd-tabs', + className + ); + + // set selected tab + const handleTabSelection = (event, id) => { + event.preventDefault(); + setSelectedTab(id); + }; + + // create tab list + const renderTabs = () => { + return React.Children.map(children, (child) => { + return ( +
  • + +
  • ); + }); + }; + + // create content to show below tab list + const renderContent = () => { + return React.Children.map(children, (child) => { + return ( + + {child.props.children} + ); + }); + }; + + return ( + + + {renderContent()} + + ); +}; +TabGroup.displayName = 'TabGroup'; + +TabGroup.defaultProps = { + selectedId: '1' +}; + +TabGroup.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + selectedId: PropTypes.string, + tabContentProps: PropTypes.object, + tabGroupProps: PropTypes.object, + tabLinkProps: PropTypes.object +}; + +TabGroup.propDescriptions = { + children: 'One or more `Tab` components to render within the component.', + selectedId: 'The `id` of the selected `Tab`.', + tabContentProps: 'Additional props to be spread to the tab content\'s
    element.', + tabGroupProps: 'Additional props to be spread to the tab group.', + tabLinkProps: 'Additional props to be spread to the tab\'s
  • element.' +}; diff --git a/src/Tabs/Tabs.Component.js b/src/Tabs/Tabs.Component.js index 17f9400ab..d504bb806 100644 --- a/src/Tabs/Tabs.Component.js +++ b/src/Tabs/Tabs.Component.js @@ -1,95 +1,26 @@ import path from 'path'; import React from 'react'; +import { Tab } from './Tab'; +import { TabGroup } from './TabGroup'; import { Description, DocsText, DocsTile, Header, Import, Properties, Separator } from '../_playground'; -import { Link, MemoryRouter } from 'react-router-dom'; -import { Tab, TabGroup } from '../'; export const TabsComponent = () => { const tabGroupCode = ` - - - Tab 1 + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. - - Tab 2 + + Numquam libero id corporis odit animi voluptat, Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus quia tempore eligendi tempora repellat officia rerum laudantium, veritatis officiis asperiores ipsum nam, distinctio, dolor provident culpa voluptatibus esse deserunt animi? - - Tab 3 + + Lorem ipsum dolor sit amet consectetur adipisicing elit. - `; - - const tabsGroupWithAnchorCode = ` - - - - Tab 1 - - - - - Tab 2 - - - - - Tab 2 - + + Please review your shopping chart. `; - const tabsGroupWithLinkCode = ` - - - - - Tab 1 - - - - - Tab 2 - - - - - Tab 2 - - - - `; - return (
    Tab Group
    @@ -98,115 +29,37 @@ export const TabsComponent = () => { metaphor and is used to separate content into different sections. They should be ordered to create a visual hierarchy based on priority. - + + - + + -

    Tab Group with URL

    +

    Tab Group

    - - - Tab 1 + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. - - Tab 2 + + Numquam libero id corporis odit animi voluptat, Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus quia tempore eligendi tempora repellat officia rerum laudantium, veritatis officiis asperiores ipsum nam, distinctio, dolor provident culpa voluptatibus esse deserunt animi? - - Tab 3 + + Lorem ipsum dolor sit amet consectetur adipisicing elit. - - - {tabGroupCode} - - - -

    Tab Group with Anchor

    - - - - - - Tab 1 - - - - - Tab 2 - - - - - Tab 2 - + + Please review your shopping chart. - {tabsGroupWithAnchorCode} - - - -

    Tab Group with Link

    + {tabGroupCode} - - - - - - Tab 1 - - - - - Tab 2 - - - - - Tab 2 - - - - - - {tabsGroupWithLinkCode}
    ); }; diff --git a/src/Tabs/Tabs.js b/src/Tabs/Tabs.js deleted file mode 100644 index 382fd3a72..000000000 --- a/src/Tabs/Tabs.js +++ /dev/null @@ -1,143 +0,0 @@ -import classnames from 'classnames'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; - -export const Tab = props => { - const { children, className, content, disabled, id, onClick, selected, tabContentProps, tabLinkProps, url, ...rest } = props; - - const linkClasses = classnames( - className, - 'fd-tabs__link', - { - 'is-selected': selected - } - ); - - const renderLink = () => { - if (url) { - return ( - { - if (!disabled) { - onClick(e); - } - }}> - {children} - - ); - } else if (children && React.isValidElement(children)) { - return React.cloneElement(children, { - ...tabLinkProps, - 'aria-disabled': disabled, - className: linkClasses, - disabled: disabled, - onClick: (e) => { - if (!disabled) { - onClick(e); - } - } - }); - } else if (children) { - return children; - } - }; - - const tabClasses = classnames('fd-tabs__item', className); - - return ( -
  • - {renderLink()} - {selected ? ( -

    {content}

    - ) : null} -
  • - ); -}; -Tab.displayName = 'Tab'; - -Tab.propTypes = { - children: PropTypes.node, - className: PropTypes.string, - content: PropTypes.node, - disabled: PropTypes.bool, - id: PropTypes.string, - selected: PropTypes.bool, - tabContentProps: PropTypes.object, - tabLinkProps: PropTypes.object, - url: PropTypes.string, - onClick: PropTypes.func -}; - -Tab.defaultProps = { - onClick: () => { } -}; - -Tab.propDescriptions = { - children: 'Can be link text, an achor tag, or a react component like React Routers\'s `Link`.', - content: 'Content to render when tab is selected.', - selected: 'Set to **true** to mark tab as selected.', - url: 'Creates an internal anchor when a child anchor is not provided.', - tabContentProps: 'Additional props to be spread to the tab content\'s

    ` element.', - tabLinkProps: 'Additional props to be spread to the tab\'s link element.' -}; - -export class TabGroup extends Component { - constructor(props) { - super(props); - - this.state = { - selectedId: this.props.selectedId - }; - } - - handleTabSelection = (e, id) => { - this.setState({ selectedId: id }); - }; - - renderTabs = (children) => { - return React.Children.map(children, (child) => { - - return React.cloneElement(child, { - selected: this.state.selectedId === child.props.id, - onClick: (e) => { - child.props.onClick && child.props.onClick(e); - this.handleTabSelection(e, child.props.id); - } - }); - }); - } - - render() { - const { children, className, selectedId, ...rest } = this.props; - - const tabClasses = classnames( - 'fd-tabs', - className - ); - - return ( -

    - ); - } -} -TabGroup.displayName = 'TabGroup'; - -TabGroup.propTypes = { - children: PropTypes.node, - className: PropTypes.string, - selectedId: PropTypes.string -}; - -TabGroup.propDescriptions = { - children: 'One or more `Tab` components to render within the component.', - selectedId: 'The `id` of the selected `Tab`.' -}; diff --git a/src/Tabs/Tabs.test.js b/src/Tabs/Tabs.test.js index ddb0d808a..8ab90fc85 100644 --- a/src/Tabs/Tabs.test.js +++ b/src/Tabs/Tabs.test.js @@ -1,101 +1,56 @@ import { mount } from 'enzyme'; import React from 'react'; import renderer from 'react-test-renderer'; -import { Link, MemoryRouter } from 'react-router-dom'; -import { Tab, TabGroup } from './Tabs'; - +import { Tab } from './Tab'; +import { TabGroup } from './TabGroup'; describe('', () => { - const tabsExample = ( - [ + const defaultTabs = ( + - Tab 1 - , - - Tab 2 - , + title='Tab 1'> + Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore et ducimus veritatis officiis amet? Vitae officia optio dolor exercitationem incidunt magnam non, suscipit, illo quisquam numquam fugiat? Debitis, delectus sequi? + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam libero id corporis odit animi voluptat, Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus quia tempore eligendi tempora repellat officia rerum laudantium, veritatis officiis asperiores ipsum nam, distinctio, dolor provident culpa voluptatibus esse deserunt animi? + - - Tab 3 - + title='Tab 3'> + Lorem ipsum dolor sit amet consectetur adipisicing elit. - ] - ); - - const tabsExampleWithLink = ( - [ - - - Tab 1 - - , - - - Tab 2 - - , - - - Tab 3 - + + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam ipsa cumque soluta debitis accusantium iste alias quas vel perferendis voluptatibus quod asperiores praesentium quaerat, iusto repellendus nulla, maiores eius. - ] - ); - - const defaultTabs = ( - - {tabsExample} ); const defaultTabsWithClass = ( - {tabsExample} + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore et ducimus veritatis officiis amet? Vitae officia optio dolor exercitationem incidunt magnam non, suscipit, illo quisquam numquam fugiat? Debitis, delectus sequi? + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam libero id corporis odit animi voluptat, Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus quia tempore eligendi tempora repellat officia rerum laudantium, veritatis officiis asperiores ipsum nam, distinctio, dolor provident culpa voluptatibus esse deserunt animi? + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. + + + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam ipsa cumque soluta debitis accusantium iste alias quas vel perferendis voluptatibus quod asperiores praesentium quaerat, iusto repellendus nulla, maiores eius. + ); - const routerTabs = ( - - - {tabsExampleWithLink} - - - ); - const routerTabsWithClass = ( - - - {tabsExampleWithLink} - - - ); - test('create tabs component', () => { let component = renderer.create(defaultTabs); let tree = component.toJSON(); @@ -105,28 +60,27 @@ describe('', () => { tree = component.toJSON(); expect(tree).toMatchSnapshot(); - component = renderer.create(routerTabs); - tree = component.toJSON(); - expect(tree).toMatchSnapshot(); - - component = renderer.create(routerTabsWithClass); - tree = component.toJSON(); - expect(tree).toMatchSnapshot(); }); test('tab selection', () => { const wrapper = mount(defaultTabsWithClass); // check selected tab - expect(wrapper.state(['selectedId'])).toEqual('1'); + // expect(wrapper.state(['selectedId'])).toEqual('1'); + // expect(wrapper.selectedTab).toEqual('1'); wrapper .find('ul.fd-tabs li.fd-tabs__item a.fd-tabs__link') .at(1) .simulate('click'); + wrapper + .find('ul.fd-tabs li.fd-tabs__item a.fd-tabs__link') + .at(3) + .simulate('click'); + // check selected tab changed - expect(wrapper.state(['selectedId'])).toEqual('2'); + // expect(wrapper.state(['selectedId'])).toEqual('2'); }); describe('Prop spreading', () => { @@ -146,7 +100,7 @@ describe('', () => { ).toBe('Sample'); }); - test('should allow props to be spread to the Tab component\'s li elements', () => { + xtest('should allow props to be spread to the Tab component\'s li elements', () => { const element = mount(); expect( @@ -154,7 +108,7 @@ describe('', () => { ).toBe('Sample'); }); - test('should allow props to be spread to the Tab component\'s content component', () => { + xtest('should allow props to be spread to the Tab component\'s content component', () => { const element = mount( @@ -165,7 +119,7 @@ describe('', () => { ).toBe('Sample'); }); - test('should allow props to be spread to the TabGroup component\'s Link component', () => { + xtest('should allow props to be spread to the TabGroup component\'s Link component', () => { const element = mount(); diff --git a/src/Tabs/__snapshots__/Tabs.test.js.snap b/src/Tabs/__snapshots__/Tabs.test.js.snap index 0cc71a6f1..5f967dfe1 100644 --- a/src/Tabs/__snapshots__/Tabs.test.js.snap +++ b/src/Tabs/__snapshots__/Tabs.test.js.snap @@ -1,189 +1,187 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` create tabs component 1`] = ` -, +
    - - Tab 3 - - - -`; - -exports[` create tabs component 2`] = ` - + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam ipsa cumque soluta debitis accusantium iste alias quas vel perferendis voluptatibus quod asperiores praesentium quaerat, iusto repellendus nulla, maiores eius. +
    , +] `; -exports[` create tabs component 3`] = ` - -`; - -exports[` create tabs component 4`] = ` - + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam ipsa cumque soluta debitis accusantium iste alias quas vel perferendis voluptatibus quod asperiores praesentium quaerat, iusto repellendus nulla, maiores eius. +
    , +] `; diff --git a/src/index.js b/src/index.js index 9b48f3150..a2cbf27da 100644 --- a/src/index.js +++ b/src/index.js @@ -52,8 +52,10 @@ export { ProductTileContent } from './Tile/Tile'; export { ProductTileMedia } from './Tile/Tile'; export { SearchInput } from './SearchInput/SearchInput'; export { SideNav, SideNavList, SideNavListItem } from './SideNavigation/SideNavigation'; +export { Tab } from './Tabs/Tab'; +export { TabContent } from './Tabs/TabContent'; +export { TabGroup } from './Tabs/TabGroup'; export { Table } from './Table/Table'; -export { Tab, TabGroup } from './Tabs/Tabs'; export { Token } from './Token/Token'; export { Tile } from './Tile/Tile'; export { TileContent } from './Tile/Tile'; From 57645c357d2e2894dc4bd4c9eefe2bdac0fcb7ad Mon Sep 17 00:00:00 2001 From: Chris Manciero Date: Fri, 15 Feb 2019 16:06:49 -0500 Subject: [PATCH 2/9] updated spreading props --- src/Tabs/TabGroup.js | 6 ++++-- src/Tabs/Tabs.Component.js | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Tabs/TabGroup.js b/src/Tabs/TabGroup.js index cfe53c780..d30b700d1 100644 --- a/src/Tabs/TabGroup.js +++ b/src/Tabs/TabGroup.js @@ -5,7 +5,7 @@ import { TabContent } from './TabContent'; import React, { useState } from 'react'; export const TabGroup = (props) => { - const { children, className, selectedId, tabLinkProps, tabContentProps, ...tabGroupProps } = props; + const { children, className, selectedId, tabLinkProps, tabContentProps, tabGroupProps, ...rest } = props; const [selectedTab, setSelectedTab] = useState(selectedId); // css classes to use for tab group @@ -47,7 +47,9 @@ export const TabGroup = (props) => { return ( -
      {renderTabs()}
    diff --git a/src/Tabs/Tabs.Component.js b/src/Tabs/Tabs.Component.js index d504bb806..e7b7063a1 100644 --- a/src/Tabs/Tabs.Component.js +++ b/src/Tabs/Tabs.Component.js @@ -59,7 +59,6 @@ export const TabsComponent = () => { {tabGroupCode} - ); }; From 7b8d7294a65faf57c22d54ebc5bf866aa5d0cd80 Mon Sep 17 00:00:00 2001 From: Chris Manciero Date: Fri, 15 Feb 2019 18:14:59 -0500 Subject: [PATCH 3/9] converted TabGroup to React class component --- src/Tabs/TabGroup.js | 82 +++++++++++++++++++++++--------------- src/Tabs/Tabs.Component.js | 5 ++- src/Tabs/Tabs.test.js | 33 ++++++++------- 3 files changed, 68 insertions(+), 52 deletions(-) diff --git a/src/Tabs/TabGroup.js b/src/Tabs/TabGroup.js index d30b700d1..660883de8 100644 --- a/src/Tabs/TabGroup.js +++ b/src/Tabs/TabGroup.js @@ -2,61 +2,77 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; import { Tab } from './Tab'; import { TabContent } from './TabContent'; -import React, { useState } from 'react'; +import React, { Component } from 'react'; -export const TabGroup = (props) => { - const { children, className, selectedId, tabLinkProps, tabContentProps, tabGroupProps, ...rest } = props; - const [selectedTab, setSelectedTab] = useState(selectedId); - - // css classes to use for tab group - const tabGroupClasses = classnames( - 'fd-tabs', - className - ); +export class TabGroup extends Component { + constructor(props) { + super(props); + this.state = { + selectedId: props.selectedId + }; + } // set selected tab - const handleTabSelection = (event, id) => { + handleTabSelection = (event, id) => { event.preventDefault(); - setSelectedTab(id); + this.setState({ + selectedId: id + }); }; // create tab list - const renderTabs = () => { - return React.Children.map(children, (child) => { + renderTabs = () => { + return React.Children.map(this.props.children, (child) => { return ( -
  • - +
  • ); }); }; // create content to show below tab list - const renderContent = () => { - return React.Children.map(children, (child) => { + renderContent = () => { + return React.Children.map(this.props.children, (child) => { return ( + {...this.props.tabContentProps} + selected={this.state.selectedId === child.props.id}> {child.props.children} ); }); }; - return ( - -
      - {renderTabs()} -
    - {renderContent()} -
    - ); -}; + render() { + const { + children, + className, + selectedId, + tabLinkProps, + tabContentProps, + tabGroupProps, + ...rest } = this.props; + + // css classes to use for tab group + const tabGroupClasses = classnames( + 'fd-tabs', + className + ); + return ( + +
      + {this.renderTabs(children)} +
    + {this.renderContent(children)} +
    + ); + } +} TabGroup.displayName = 'TabGroup'; TabGroup.defaultProps = { diff --git a/src/Tabs/Tabs.Component.js b/src/Tabs/Tabs.Component.js index e7b7063a1..43c5700c9 100644 --- a/src/Tabs/Tabs.Component.js +++ b/src/Tabs/Tabs.Component.js @@ -42,11 +42,12 @@ export const TabsComponent = () => {

    Tab Group

    - + Lorem ipsum dolor sit amet consectetur adipisicing elit. - + Numquam libero id corporis odit animi voluptat, Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus quia tempore eligendi tempora repellat officia rerum laudantium, veritatis officiis asperiores ipsum nam, distinctio, dolor provident culpa voluptatibus esse deserunt animi? ', () => { component = renderer.create(defaultTabsWithClass); tree = component.toJSON(); expect(tree).toMatchSnapshot(); - }); test('tab selection', () => { const wrapper = mount(defaultTabsWithClass); // check selected tab - // expect(wrapper.state(['selectedId'])).toEqual('1'); - // expect(wrapper.selectedTab).toEqual('1'); + expect(wrapper.state(['selectedId'])).toEqual('1'); wrapper .find('ul.fd-tabs li.fd-tabs__item a.fd-tabs__link') @@ -80,7 +78,7 @@ describe('', () => { .simulate('click'); // check selected tab changed - // expect(wrapper.state(['selectedId'])).toEqual('2'); + expect(wrapper.state(['selectedId'])).toEqual('4'); }); describe('Prop spreading', () => { @@ -100,31 +98,32 @@ describe('', () => { ).toBe('Sample'); }); - xtest('should allow props to be spread to the Tab component\'s li elements', () => { - const element = mount(); + test('should allow props to be spread to the TabGroup component\'s li elements', () => { + const element = mount( + + ); expect( - element.find('li').at(0).getDOMNode().attributes['data-sample'].value + element.find('ul li').at(0).getDOMNode().attributes['data-sample'].value ).toBe('Sample'); }); - xtest('should allow props to be spread to the Tab component\'s content component', () => { - const element = mount( - - - ); + test('should allow props to be spread to the Tab component\'s a elements', () => { + const element = mount(); expect( - element.find('li p').at(0).getDOMNode().attributes['data-sample'].value + element.find('a').at(0).getDOMNode().attributes['data-sample'].value ).toBe('Sample'); }); - xtest('should allow props to be spread to the TabGroup component\'s Link component', () => { - const element = mount(); + test('should allow props to be spread to the Tab component\'s content component', () => { + const element = mount( + + + ); expect( - element.find('li a').at(0).getDOMNode().attributes['data-sample'].value + element.find('div.fd-tabs__panel').at(0).getDOMNode().attributes['data-sample'].value ).toBe('Sample'); }); }); From 84215a9e9f6a2b80ab2d4e90ec5e2e6fb2a9ba00 Mon Sep 17 00:00:00 2001 From: Chris Manciero Date: Mon, 18 Feb 2019 18:10:01 -0500 Subject: [PATCH 4/9] added changes based of code review --- src/Tabs/Tab.js | 11 +-- src/Tabs/Tab.test.js | 68 +++++++++++++++++++ src/Tabs/TabGroup.js | 35 +++++++--- src/Tabs/{Tabs.test.js => TabGroup.test.js} | 18 +---- src/Tabs/Tabs.Component.js | 6 +- src/Tabs/{TabContent.js => _TabContent.js} | 4 +- src/Tabs/_TabContent.test.js | 42 ++++++++++++ src/Tabs/__snapshots__/Tab.test.js.snap | 36 ++++++++++ ...abs.test.js.snap => TabGroup.test.js.snap} | 0 .../__snapshots__/_TabContent.test.js.snap | 21 ++++++ src/index.js | 1 - 11 files changed, 205 insertions(+), 37 deletions(-) create mode 100644 src/Tabs/Tab.test.js rename src/Tabs/{Tabs.test.js => TabGroup.test.js} (87%) rename src/Tabs/{TabContent.js => _TabContent.js} (87%) create mode 100644 src/Tabs/_TabContent.test.js create mode 100644 src/Tabs/__snapshots__/Tab.test.js.snap rename src/Tabs/__snapshots__/{Tabs.test.js.snap => TabGroup.test.js.snap} (100%) create mode 100644 src/Tabs/__snapshots__/_TabContent.test.js.snap diff --git a/src/Tabs/Tab.js b/src/Tabs/Tab.js index ef3eb0028..94cd5ce49 100644 --- a/src/Tabs/Tab.js +++ b/src/Tabs/Tab.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React from 'react'; export const Tab = (props) => { - const { title, className, disabled, glyph, id, selected, tabLinkProps, ...rest } = props; + const { title, className, disabled, glyph, id, selected, ...rest } = props; // css classes used for tabs const linkClasses = classnames( @@ -17,7 +17,6 @@ export const Tab = (props) => { return ( { }; Tab.displayName = 'Tab'; +Tab.defaultProps = { + onClick: () => { } +}; + Tab.propTypes = { className: PropTypes.string, disabled: PropTypes.bool, glyph: PropTypes.string, id: PropTypes.string, selected: PropTypes.bool, - title: PropTypes.string + title: PropTypes.string, + onClick: PropTypes.func }; Tab.propDescriptions = { glyph: 'Icon to display on tab', selected: 'Set to **true** to mark tab as selected.', - tabLinkProps: 'Additional props to be spread to the tab\'s link element.', title: 'String to display on tab' }; diff --git a/src/Tabs/Tab.test.js b/src/Tabs/Tab.test.js new file mode 100644 index 000000000..dbc312033 --- /dev/null +++ b/src/Tabs/Tab.test.js @@ -0,0 +1,68 @@ +import { mount } from 'enzyme'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import { Tab } from './Tab'; + +describe('', () => { + const mockOnClick = jest.fn(); + + const defaultTab = ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit.Dolore et ducimus veritatis officiis amet ? Vitae officia optio dolor exercitationem incidunt magnam non, suscipit, illo quisquam numquam fugiat ? Debitis, delectus sequi ? + ); + + const disabledTab = ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. + ); + + const glyphTab = ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. A quibusdam ipsa cumque soluta debitis accusantium iste alias quas vel perferendis voluptatibus quod asperiores praesentium quaerat, iusto repellendus nulla, maiores eius. + ); + + + test('create tabs component', () => { + let component = renderer.create(defaultTab); + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + + component = renderer.create(disabledTab); + tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + + component = renderer.create(glyphTab); + tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); + + test('onClick of tab', () => { + const wrapper = mount(defaultTab); + wrapper.find('a').simulate('click'); + expect(wrapper.prop('onClick')).toBeCalledTimes(1); + }); + + describe('Prop spreading', () => { + test('should allow props to be spread to the Tab component', () => { + const element = mount(); + + expect( + element.getDOMNode().attributes['data-sample'].value + ).toBe('Sample'); + }); + + test('should allow props to be spread to the Tab component\'s a elements', () => { + const element = mount(); + + expect( + element.find('a').at(0).getDOMNode().attributes['data-sample'].value + ).toBe('Sample'); + }); + }); +}); diff --git a/src/Tabs/TabGroup.js b/src/Tabs/TabGroup.js index 660883de8..7d01a3c69 100644 --- a/src/Tabs/TabGroup.js +++ b/src/Tabs/TabGroup.js @@ -1,7 +1,6 @@ import classnames from 'classnames'; import PropTypes from 'prop-types'; -import { Tab } from './Tab'; -import { TabContent } from './TabContent'; +import { TabContent } from './_TabContent'; import React, { Component } from 'react'; export class TabGroup extends Component { @@ -12,24 +11,40 @@ export class TabGroup extends Component { }; } + static getDerivedStateFromProps(nextProps, prevState) { + return nextProps.selectedId !== prevState.selectedId ? { selectedId: nextProps.selectedId } : null; + } + // set selected tab handleTabSelection = (event, id) => { event.preventDefault(); this.setState({ selectedId: id }); + + if (this.props.onTabChange) { + this.props.onTabChange(event, id); + } }; + // clone Tab element + cloneElement = (child) => { + return (React.cloneElement(child, { + onClick: this.handleTabSelection, + selected: this.state.selectedId === child.props.id + })); + } + // create tab list renderTabs = () => { return React.Children.map(this.props.children, (child) => { return ( -
  • - -
  • ); + {this.cloneElement(child)} + ); }); }; @@ -50,8 +65,8 @@ export class TabGroup extends Component { children, className, selectedId, - tabLinkProps, tabContentProps, + tabLinkProps, tabGroupProps, ...rest } = this.props; @@ -63,7 +78,6 @@ export class TabGroup extends Component { return (
    ,
    Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore et ducimus veritatis officiis amet? Vitae officia optio dolor exercitationem incidunt magnam non, suscipit, illo quisquam numquam fugiat? Debitis, delectus sequi?
    ,
    diff --git a/src/Tabs/__snapshots__/_TabContent.test.js.snap b/src/Tabs/__snapshots__/_TabContent.test.js.snap index aa0eac45f..160191239 100644 --- a/src/Tabs/__snapshots__/_TabContent.test.js.snap +++ b/src/Tabs/__snapshots__/_TabContent.test.js.snap @@ -3,6 +3,7 @@ exports[` create TabContent component 1`] = `
    Lorem ipsum dolor sit amet consectetur adipisicing elit.Dolore et ducimus veritatis officiis amet ? Vitae officia optio dolor exercitationem incidunt magnam non, suscipit, illo quisquam numquam fugiat ? Debitis, delectus sequi ? @@ -12,6 +13,7 @@ exports[` create TabContent component 1`] = ` exports[` create TabContent component 2`] = `
    Lorem ipsum dolor sit amet consectetur adipisicing elit.Dolore et ducimus veritatis officiis amet ? Vitae officia optio dolor exercitationem incidunt magnam non, suscipit, illo quisquam numquam fugiat ? Debitis, delectus sequi ? From 0d54724d57ec3150204ec057683bde4c7c2c235a Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Wed, 20 Feb 2019 09:17:42 -0600 Subject: [PATCH 9/9] Minor prop description edits --- src/Tabs/Tab.js | 11 +++++++---- src/Tabs/TabGroup.js | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Tabs/Tab.js b/src/Tabs/Tab.js index 22bef78b5..df7cc5170 100644 --- a/src/Tabs/Tab.js +++ b/src/Tabs/Tab.js @@ -61,6 +61,7 @@ Tab.propTypes = { glyph: PropTypes.string, id: PropTypes.string, index: PropTypes.number, + linkProps: PropTypes.object, selected: PropTypes.bool, tabContentProps: PropTypes.object, title: PropTypes.string, @@ -68,9 +69,11 @@ Tab.propTypes = { }; Tab.propDescriptions = { - glyph: 'Icon to display on tab', - selected: 'Set to **true** to mark tab as selected.', - title: 'String to display on tab', + glyph: 'Icon to display on the tab.', + index: '_INTERNAL USE ONLY._', + selected: '_INTERNAL USE ONLY._', + title: 'Localized text to display on the tab.', tabContentProps: 'Additional props to be spread to the tab content\'s