Skip to content
This repository was archived by the owner on Jan 14, 2025. It is now read-only.

feat(tab): implement setFocusOnActivate #722

Merged
merged 5 commits into from Mar 12, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions packages/tab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class MyApp extends React.Component {
Prop Name | Type | Description
--- | --- | ---
active | boolean | If true will activate the tab and indicator.
focusOnActivate | boolean | If true will focus itself when activated. Defaults to `true`.
className | string | Classes to appear on className attribute of root element.
isFadingIndicator | boolean | Enables a fading indicator, instead of sliding (default).
indicatorContent | element | Element that will appear within the `<TabIndicator />` element.
Expand Down
17 changes: 13 additions & 4 deletions packages/tab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import TabRipple, {TabRippleProps} from './TabRipple';

export interface TabProps extends React.HTMLProps<HTMLButtonElement> {
active?: boolean;
focusOnActivate?: boolean;
isFadingIndicator?: boolean;
indicatorContent?: React.ReactNode;
minWidth?: boolean;
Expand Down Expand Up @@ -59,6 +60,7 @@ export default class Tab extends React.Component<TabProps, TabState> {

static defaultProps: Partial<TabProps> = {
active: false,
focusOnActivate: true,
className: '',
isFadingIndicator: false,
indicatorContent: null,
Expand All @@ -76,9 +78,11 @@ export default class Tab extends React.Component<TabProps, TabState> {
};

componentDidMount() {
const {active, focusOnActivate} = this.props;
this.foundation = new MDCTabFoundation(this.adapter);
this.foundation.init();
if (this.props.active) {
this.foundation.setFocusOnActivate(focusOnActivate);
if (active) {
this.foundation.activate();
}
}
Expand All @@ -88,10 +92,14 @@ export default class Tab extends React.Component<TabProps, TabState> {
}

componentDidUpdate(prevProps: TabProps) {
if (this.props.active !== prevProps.active) {
if (this.props.active) {
const {active, focusOnActivate, previousIndicatorClientRect} = this.props;
if (focusOnActivate !== prevProps.focusOnActivate) {
this.foundation.setFocusOnActivate(focusOnActivate);
}
if (active !== prevProps.active) {
if (active) {
// If active state is updated through props, previousIndicatorClientRect must also be passed through props
this.activate(this.props.previousIndicatorClientRect);
this.activate(previousIndicatorClientRect);
} else {
this.deactivate();
}
Expand Down Expand Up @@ -176,6 +184,7 @@ export default class Tab extends React.Component<TabProps, TabState> {
const {
/* eslint-disable */
active,
focusOnActivate,
previousIndicatorClientRect,
className,
isFadingIndicator,
Expand Down
50 changes: 50 additions & 0 deletions test/unit/tab/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,56 @@ test('if props.active updates to false, foundation.deactivate is called', () =>
td.verify(wrapper.instance().deactivate(), {times: 1});
});

test('calls foundation.setFocusOnActivate when props.focusOnActivate changes from false to true', () => {
const wrapper = shallow<Tab>(<Tab focusOnActivate={false} />);
wrapper.instance().foundation.setFocusOnActivate = td.func();
wrapper.setProps({focusOnActivate: true});
td.verify(wrapper.instance().foundation.setFocusOnActivate(true), {times: 1});
});

test('calls foundation.setFocusOnActivate when props.focusOnActivate changes from true to false', () => {
const wrapper = shallow<Tab>(<Tab focusOnActivate />);
wrapper.instance().foundation.setFocusOnActivate = td.func();
wrapper.setProps({focusOnActivate: false});
td.verify(wrapper.instance().foundation.setFocusOnActivate(false), {times: 1});
});

test('when props.focusOnActivate is true, an active tab should be focused on mount', () => {
const div = document.createElement('div');
document.body.append(div);
const wrapper = mount<Tab>(<Tab active focusOnActivate />, {attachTo: div});
assert.equal(document.activeElement, wrapper.getDOMNode());
div.remove();
});

test('when props.focusOnActivate is true and active is changed to true, the tab should be focused', () => {
const div = document.createElement('div');
document.body.append(div);
const wrapper = mount<Tab>(<Tab focusOnActivate />, {attachTo: div});
assert.notEqual(document.activeElement, wrapper.getDOMNode());
wrapper.setProps({active: true});
assert.equal(document.activeElement, wrapper.getDOMNode());
div.remove();
});

test('when props.focusOnActivate is false, an active tab should not be focused on mount', () => {
const div = document.createElement('div');
document.body.append(div);
const wrapper = mount<Tab>(<Tab active focusOnActivate={false} />, {attachTo: div});
assert.notEqual(document.activeElement, wrapper.getDOMNode());
div.remove();
});

test('when props.focusOnActivate is false and active is changed to true, the tab should not be focused', () => {
const div = document.createElement('div');
document.body.append(div);
const wrapper = mount<Tab>(<Tab focusOnActivate={false} />, {attachTo: div});
assert.notEqual(document.activeElement, wrapper.getDOMNode());
wrapper.setProps({active: true});
assert.notEqual(document.activeElement, wrapper.getDOMNode());
div.remove();
});

test('#adapter.addClass adds class to state.classList', () => {
const wrapper = shallow<Tab>(<Tab />);
wrapper.instance().adapter.addClass('test-class');
Expand Down