Skip to content

Commit

Permalink
Merge pull request #2123 from sijad/hookify-tab
Browse files Browse the repository at this point in the history
[Tabs] hookify tab
  • Loading branch information
chloerice authored Oct 1, 2019
2 parents 5c92aec + 5e79f5b commit 7a6ca09
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 104 deletions.
1 change: 1 addition & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f
### Code quality

- Added `MediaQueryProvider` to ease the use of media queries and reduce duplication ([#2117](https://github.com/Shopify/polaris-react/pull/2117))
- Migrated `Tab` to use hooks instead of `withAppProvider` ([#2096](https://github.com/Shopify/polaris-react/pull/2096))

### Deprecations

Expand Down
173 changes: 73 additions & 100 deletions src/components/Tabs/components/Tab/Tab.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import React from 'react';
import React, {useEffect, useRef} from 'react';
import {focusFirstFocusableNode} from '@shopify/javascript-utilities/focus';

import {classNames} from '../../../../utilities/css';
import {UnstyledLink} from '../../../UnstyledLink';
import {
withAppProvider,
WithAppProviderProps,
} from '../../../../utilities/with-app-provider';
import {handleMouseUpByBlurring} from '../../../../utilities/focus';

import styles from '../../Tabs.scss';
Expand All @@ -24,17 +20,26 @@ export interface TabProps {
onClick?(id: string): void;
}

type CombinedProps = TabProps & WithAppProviderProps;

class Tab extends React.PureComponent<CombinedProps, never> {
private node: HTMLElement | null = null;
export function Tab({
id,
focused,
siblingTabHasFocus,
children,
onClick,
selected,
url,
panelID,
measuring,
accessibilityLabel,
}: TabProps) {
const wasSelected = useRef(selected);
const panelFocused = useRef(false);
const node = useRef<HTMLLIElement | null>(null);

// A tab can start selected when it is moved from the disclosure dropdown
// into the main list, so we need to send focus from the tab to the panel
// on mount and update
componentDidMount() {
const {id, measuring, selected, panelID, focused} = this.props;

useEffect(() => {
if (measuring) {
return;
}
Expand All @@ -47,99 +52,71 @@ class Tab extends React.PureComponent<CombinedProps, never> {

// If we just check for selected, the panel for the active tab will
// be focused on page load, which we don’t want
if (itemHadFocus && selected && panelID != null) {
if (itemHadFocus && selected && panelID != null && !panelFocused.current) {
focusPanelID(panelID);
}
}

componentDidUpdate(previousProps: TabProps) {
const {selected: wasSelected} = previousProps;
const {focused, measuring, selected, panelID} = this.props;

if (measuring) {
return;
panelFocused.current = true;
}

if (selected && !wasSelected && panelID != null) {
if (selected && !wasSelected.current && panelID != null) {
focusPanelID(panelID);
} else if (focused && this.node != null) {
focusFirstFocusableNode(this.node);
} else if (focused && node.current != null) {
focusFirstFocusableNode(node.current);
}
}

render() {
const {
id,
focused,
siblingTabHasFocus,
children,
onClick,
selected,
url,
panelID,
measuring,
accessibilityLabel,
} = this.props;

const handleClick = onClick && onClick.bind(null, id);

const className = classNames(
styles.Tab,
selected && styles['Tab-selected'],
);

let tabIndex: 0 | -1;

if (selected && !siblingTabHasFocus && !measuring) {
tabIndex = 0;
} else if (focused && !measuring) {
tabIndex = 0;
} else {
tabIndex = -1;
}
wasSelected.current = selected;
}, [focused, id, measuring, panelID, selected]);

const handleClick = onClick && onClick.bind(null, id);

const markup = url ? (
<UnstyledLink
id={id}
url={url}
role="tab"
tabIndex={tabIndex}
onClick={handleClick}
className={className}
aria-selected={selected}
aria-controls={panelID}
aria-label={accessibilityLabel}
onMouseUp={handleMouseUpByBlurring}
>
<span className={styles.Title}>{children}</span>
</UnstyledLink>
) : (
<button
id={id}
role="tab"
type="button"
tabIndex={tabIndex}
className={className}
onClick={handleClick}
aria-selected={selected}
aria-controls={panelID}
aria-label={accessibilityLabel}
onMouseUp={handleMouseUpByBlurring}
>
<span className={styles.Title}>{children}</span>
</button>
);

return (
<li className={styles.TabContainer} ref={this.setNode}>
{markup}
</li>
);
const className = classNames(styles.Tab, selected && styles['Tab-selected']);

let tabIndex: 0 | -1;

if (selected && !siblingTabHasFocus && !measuring) {
tabIndex = 0;
} else if (focused && !measuring) {
tabIndex = 0;
} else {
tabIndex = -1;
}

private setNode = (node: HTMLElement | null) => {
this.node = node;
};
const markup = url ? (
<UnstyledLink
id={id}
url={url}
role="tab"
tabIndex={tabIndex}
onClick={handleClick}
className={className}
aria-selected={selected}
aria-controls={panelID}
aria-label={accessibilityLabel}
onMouseUp={handleMouseUpByBlurring}
>
<span className={styles.Title}>{children}</span>
</UnstyledLink>
) : (
<button
id={id}
role="tab"
type="button"
tabIndex={tabIndex}
className={className}
onClick={handleClick}
aria-selected={selected}
aria-controls={panelID}
aria-label={accessibilityLabel}
onMouseUp={handleMouseUpByBlurring}
>
<span className={styles.Title}>{children}</span>
</button>
);

return (
<li className={styles.TabContainer} ref={node}>
{markup}
</li>
);
}

function focusPanelID(panelID: string) {
Expand All @@ -148,7 +125,3 @@ function focusPanelID(panelID: string) {
panel.focus({preventScroll: true});
}
}

// Use named export once withAppProvider is refactored away
// eslint-disable-next-line import/no-default-export
export default withAppProvider<TabProps>()(Tab);
4 changes: 1 addition & 3 deletions src/components/Tabs/components/Tab/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import Tab, {TabProps} from './Tab';

export {Tab, TabProps};
export {Tab, TabProps} from './Tab';
2 changes: 1 addition & 1 deletion src/components/Tabs/components/Tab/tests/Tab.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import {mountWithAppProvider} from 'test-utilities/legacy';
import Tab from '../Tab';
import {Tab} from '../Tab';

describe('<Tab />', () => {
it('has the tab role', () => {
Expand Down

0 comments on commit 7a6ca09

Please sign in to comment.