Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes onSelect being called despite isDisabled #591

Merged
merged 3 commits into from
Oct 21, 2016
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 143 additions & 55 deletions src/components/Tabs/Tabs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,122 @@ import * as reducers from './Tabs.reducers';
const cx = lucidClassNames.bind('&-Tabs');

const {
string,
number,
any,
bool,
func,
node,
number,
string,
} = React.PropTypes;

/**
*
* Content that will be rendered in a tab. Be sure to nest a Title inside
* each Tab or provide it as a prop. Props other than `isDisabled`, `isSelected`,
* and `Title` can be inferred from the parent `Tabs` component, but directly
* provided `props` will take precedence.
*/
const Tab = createClass({
displayName: 'Tabs.Tab',

propName: 'Tab',

propTypes: {
/**
* The index of this `Tab` within the list of `Tabs`.
*/
index: number,

/**
* Styles a `Tab` as disabled. This is typically used with
* `isProgressive` to disable steps that have not been completed
* and should not be selected until the current step has been
* completed.
*/
isDisabled: bool,

/**
* If `true`, this `Tab` is the last `Tab` in the list of `Tabs`.
*/
isLastTab: bool,

/**
* If `true` then the active `Tab` will appear open on the bottom.
*/
isOpen: bool,

/**
* If `true`, the `Tab` will appear as a `Progressive` tab.
*/
isProgressive: bool,

/**
* If `true`, the `Tab` will appear selected.
*/
isSelected: bool,

/**
* Callback for when the user clicks a `Tab`. Called with the index of the
* `Tab` that was clicked.
*/
onSelect: func,

/**
* The content to be rendered as the `Title` of the `Tab`.
*/
Title: node,
},

handleClick(event) {
const {
props,
props: {
index,
onSelect,
...passThroughs,
},
} = this;

if (!props.isDisabled) {
onSelect(index, passThroughs, event);
}
},

render() {
const {
isDisabled,
isLastTab,
isOpen,
isProgressive,
isSelected,
Title,
} = this.props;

return (
<li
className={cx('&-Tab', {
'&-Tab-is-active': isSelected,
'&-Tab-is-disabled': isDisabled,
'&-Tab-is-active-and-open': isOpen && isSelected,
'&-Tab-is-progressive': isProgressive && !isLastTab,
})}
onClick={this.handleClick}
>
<span className={cx('&-Tab-content')}>{Title}</span>
{isProgressive && !isLastTab &&
<span className={cx('&-Tab-arrow')} >
<svg version='1.1' viewBox='0 0 8 28' preserveAspectRatio='none' >
<polygon className={cx('&-Tab-arrow-background')} fill='#fff' points='0,0 8,14 0,28'/>
<polyline className={cx('&-Tab-arrow-tab-line')} fill='#fff' points='0,0 1,1 0,1'/>
<polyline className={cx('&-Tab-arrow-line')} fill='none' stroke='#fff' strokeWidth='1' points='0,28 7.9,14 0,0'/>
</svg>
</span>
}
</li>
);
},
});

/**
*
* {"categories": ["navigation"]}
Expand All @@ -24,27 +134,7 @@ const Tabs = createClass({
displayName: 'Tabs',

components: {
/**
* Content that will be rendered in a tab. Be sure to nest a Title inside
* each Tab or provide it as a prop.
*/
Tab: createClass({
displayName: 'Tabs.Tab',
propName: 'Tab',
propTypes: {
/**
* Determines if the Tab is selected.
*/
isSelected: bool,
/**
* Styles a Tab as disabled. This is typically used with
* `isProgressive` to to disabled steps that have not been compleated
* and should not be selected until the current step has been
* compleated.
*/
isDisabled: bool,
},
}),
Tab,
/**
* Titles can be provided as a child or prop to a Tab.
*/
Expand Down Expand Up @@ -92,6 +182,14 @@ const Tabs = createClass({
* for improved readability when there are multiple React elements in the tab headers.
*/
hasMultilineTitle: bool,

/**
* *Child Element*
*
* Can be used to define one or more individual `Tab`s in the sequence of `Tabs`.
*
*/
Tab: any,
},

getDefaultProps() {
Expand All @@ -104,18 +202,26 @@ const Tabs = createClass({
};
},

handleClicked(index, tabProps, event) {
const {
onSelect,
} = this.props;

onSelect(index, { event, props: tabProps });
},

render() {
const {
className,
selectedIndex,
hasMultilineTitle,
isOpen,
isProgressive,
hasMultilineTitle,
selectedIndex,
...passThroughs,
} = this.props;

// Grab props array from each Tab
const tabChildProps = _.map(findTypes(this.props, Tabs.Tab), 'props');
const tabChildProps = _.map(findTypes(this.props, Tab), 'props');

const selectedIndexFromChildren = _.findLastIndex(tabChildProps, {
isSelected: true,
Expand All @@ -133,28 +239,18 @@ const Tabs = createClass({
<ul className={cx('&-bar', {
'&-bar-is-multiline': hasMultilineTitle,
})}>
{_.map(tabChildProps, (tabChildProp, index) => (
<li
className={cx('&-Tab', {
'&-Tab-is-active': index === actualSelectedIndex,
'&-Tab-is-disabled': tabChildProp.isDisabled,
'&-Tab-is-active-and-open': isOpen && index === actualSelectedIndex,
'&-Tab-is-progressive': isProgressive && index !== tabChildProps.length - 1,
})}
{_.map(tabChildProps, (tabProps, index) => (
<Tab
key={index}
onClick={_.partial(this.handleClicked, index, tabChildProp)}
>
<span className={cx('&-Tab-content')}>{_.get(getFirst(tabChildProp, Tabs.Title), 'props.children', '')}</span>
{isProgressive && index !== tabChildProps.length - 1 ?
<span className={cx('&-Tab-arrow')} >
<svg version='1.1' viewBox='0 0 8 28' preserveAspectRatio='none' >
<polygon className={cx('&-Tab-arrow-background')} fill='#fff' points='0,0 8,14 0,28'/>
<polyline className={cx('&-Tab-arrow-tab-line')} fill='#fff' points='0,0 1,1 0,1'/>
<polyline className={cx('&-Tab-arrow-line')} fill='none' stroke='#fff' strokeWidth='1' points='0,28 7.9,14 0,0'/>
</svg>
</span>
: null}
</li>
index={index}
isLastTab={index === tabChildProps.length - 1}
isOpen={isOpen}
isProgressive={isProgressive}
isSelected={index === actualSelectedIndex}
onSelect={this.handleClicked}
Title={_.get(getFirst(tabProps, Tabs.Title), 'props.children', '')}
{...tabProps}
/>
))}
</ul>
<div className={cx('&-content')}>
Expand All @@ -163,14 +259,6 @@ const Tabs = createClass({
</div>
);
},

handleClicked(index, tabProps, event) {
const {
onSelect,
} = this.props;

onSelect(index, { event, props: tabProps });
},
});

export default Tabs;
50 changes: 32 additions & 18 deletions src/components/Tabs/Tabs.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('Tabs', () => {
<Tabs Tab={[{children: 'Bert'}, {children: 'Ernie'}]} />
);

assert.equal(wrapper.find('.lucid-Tabs-Tab').length, 2);
assert.equal(wrapper.find('.lucid-Tabs-bar').children().length, 2);
assert.equal(wrapper.find('.lucid-Tabs-content').text(), 'Bert');
});

Expand All @@ -40,8 +40,11 @@ describe('Tabs', () => {
]} />
);

assert.equal(wrapper.find('.lucid-Tabs-Tab').length, 2);
assert.equal(wrapper.find('.lucid-Tabs-Tab-is-active').text(), 'Coolest');
const tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

assert.equal(tabBar.children().length, 2);
assert.equal(tabBar.childAt(0).prop('Title'), 'Coolest');
assert(tabBar.childAt(0).prop('isSelected'));
assert.equal(wrapper.find('.lucid-Tabs-content').text(), 'Bert');
});

Expand All @@ -53,7 +56,9 @@ describe('Tabs', () => {
</Tabs>
);

assert.equal(wrapper.find('.lucid-Tabs-Tab').first().text(), 'Froyo');
const tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

assert.equal(tabBar.childAt(0).prop('Title'), 'Froyo');
});

it('Title as children', () => {
Expand All @@ -67,7 +72,9 @@ describe('Tabs', () => {
</Tabs>
);

assert.equal(wrapper.find('.lucid-Tabs-Tab').first().text(), 'Froyo');
const tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

assert.equal(tabBar.childAt(0).prop('Title'), 'Froyo');
});

it('selectedIndex', () => {
Expand All @@ -78,8 +85,10 @@ describe('Tabs', () => {
</Tabs>
);

assert.equal(wrapper.find('.lucid-Tabs-Tab-is-active').text(), 'Slurpee');
assert.equal(wrapper.find('.lucid-Tabs-content').text(), 'Yum');
const tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

assert(!tabBar.childAt(0).prop('isSelected'));
assert(tabBar.childAt(1).prop('isSelected'));
});

it('Tab.isSelected', () => {
Expand All @@ -90,8 +99,10 @@ describe('Tabs', () => {
</Tabs>
);

assert.equal(wrapper.find('.lucid-Tabs-Tab-is-active').text(), 'Slurpee');
assert.equal(wrapper.find('.lucid-Tabs-content').text(), 'Yum');
const tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

assert(!tabBar.childAt(0).prop('isSelected'));
assert(tabBar.childAt(1).prop('isSelected'));
});

it('last Tab.isSelected beats selectedIndex', () => {
Expand All @@ -103,15 +114,19 @@ describe('Tabs', () => {
</Tabs>
);

assert.equal(wrapper.find('.lucid-Tabs-Tab-is-active').text(), 'Three');
assert.equal(wrapper.find('.lucid-Tabs-content').text(), 'Three content');
const tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

assert(!tabBar.childAt(0).prop('isSelected'));
assert(tabBar.childAt(1).prop('isSelected'));
assert(tabBar.childAt(2).prop('isSelected'));
});

describe('onSelect', () => {

let onSelect = sinon.spy();
let clickEvent;
let wrapper;
let tabBar;

beforeEach(() => {

Expand All @@ -123,24 +138,23 @@ describe('Tabs', () => {
<Tabs.Tab>Two</Tabs.Tab>
</Tabs>
);
tabBar = wrapper.find('.lucid-Tabs-bar').shallow();

});

it('should call onSelect with the correct arguments', () => {
wrapper.find('.lucid-Tabs-Tab').at(1).simulate('click', clickEvent);
tabBar.childAt(1).shallow().simulate('click', clickEvent);
const selectedIndex = onSelect.args[0][0];
const meta = onSelect.args[0][1];
assert(onSelect.called);
assert.equal(selectedIndex, 1);
assert.equal(meta.event, clickEvent);
assert.deepEqual(meta.props, {children: 'Two'});
assert.deepEqual(meta.props, {isLastTab: true, isOpen: true, isProgressive: false, isSelected: false, Title: '', children: 'Two'});
});

it('should call onSelect with isDisabled prop', () => {
wrapper.find('.lucid-Tabs-Tab').at(0).simulate('click', clickEvent);
const meta = onSelect.args[0][1];
assert(onSelect.called);
assert(meta.props.isDisabled, 'isDisabled should be true');
it('should not call onSelect if the `Tab` isDisabled', () => {
tabBar.childAt(0).shallow().simulate('click', clickEvent);
assert(!onSelect.called);
});

});
Expand Down
12 changes: 12 additions & 0 deletions src/components/Tabs/examples/5-tab-as-prop.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import { Tabs } from '../../../index';

export default React.createClass({
render() {
return (
<div>
<Tabs Tab={[{Title: 'Bert', children: 'Bert'}, {Title: 'Ernie', children: 'Ernie'}]} />
</div>
);
},
});