Skip to content
Open
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
File renamed without changes.
20 changes: 20 additions & 0 deletions src/components/tab-view/Tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';

export interface TabProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Content displayed in the tab panel */
children?: React.ReactNode;
/** Custom class name applied to the tab button */
className?: string;
/** Custom component used to render a linked tab */
component?: React.ElementType;
/** URL used to render the tab as a link */
href?: string;
/** Ref property used by a custom link component */
refProp?: string;
/** Label displayed in the tab */
title: string;
}

const Tab: (props: TabProps) => React.ReactElement = () => <i />;

export default Tab;
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow
import * as React from 'react';

import Tab from './Tab';
Expand All @@ -22,7 +21,7 @@ export const basic = () => (

export const withCallback = () => {
// eslint-disable-next-line no-alert
const cb = selectedIndex => alert(selectedIndex);
const cb = (selectedIndex: number) => alert(selectedIndex);
return (
<TabView defaultSelectedIndex={1} onTabSelect={cb}>
<Tab title="Item 1">
Expand Down
113 changes: 113 additions & 0 deletions src/components/tab-view/TabView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as React from 'react';
import classNames from 'classnames';

import TabViewPrimitive, { TAB_KEY, TAB_PANEL_ROLE } from './TabViewPrimitive';

export interface TabViewProps {
/** Tabs and their associated panel content */
children: React.ReactNode;
/** Custom class name applied to the tab view */
className?: string;
/** Index of the tab selected initially and when the tab view resets */
defaultSelectedIndex: number;
/** Whether tabs use the scrollable dynamic layout */
isDynamic?: boolean;
/** Callback invoked when the selected tab changes */
onTabSelect?: (selectedIndex: number) => void;
}

interface TabViewState {
focusedIndex: number;
selectedIndex: number;
showOutline: boolean;
}

class TabView extends React.Component<TabViewProps, TabViewState> {
static defaultProps = {
defaultSelectedIndex: 0,
isDynamic: false,
};

constructor(props: TabViewProps) {
super(props);

this.state = {
focusedIndex: props.defaultSelectedIndex,
showOutline: false,
selectedIndex: props.defaultSelectedIndex,
};
}

componentDidUpdate(prevProps: TabViewProps) {
const { defaultSelectedIndex } = this.props;
if (prevProps.defaultSelectedIndex !== defaultSelectedIndex) {
this.resetActiveTab();
}
}

getActiveDocElement = () => document.activeElement;

resetActiveTab = () => {
this.setState({
focusedIndex: this.props.defaultSelectedIndex,
selectedIndex: this.props.defaultSelectedIndex,
});
};

resetFocusedTab = () => {
this.setState({ focusedIndex: this.state.selectedIndex });
};

handleOnTabSelect = (selectedIndex: number): void =>
this.setState({ selectedIndex }, () => {
const { onTabSelect } = this.props;

if (onTabSelect) {
onTabSelect(this.state.selectedIndex);
}
});

handleOnTabFocus = (index: number) => this.setState({ focusedIndex: index });

// By default the outline is set to none when tabpanel is focused. This is so that
// when clicking into it, it doesn't outline it.
// However, for accessibility, when tabbing into and out of the tabpanel, the focus
// is pretty important to show the user what is being focused. By adding this class,
// we can specify an outline for the focus pseudo state.
handleKeyUp = (event: React.KeyboardEvent<HTMLDivElement>) => {
const activeElement = this.getActiveDocElement();
const isTabPanelFocused = activeElement && activeElement.getAttribute('role') === TAB_PANEL_ROLE;
const isTabPanelFocusedWithTabKey = isTabPanelFocused && event.key === TAB_KEY;

if (isTabPanelFocusedWithTabKey) {
this.setState({ showOutline: true });
} else if (!isTabPanelFocused && this.state.showOutline) {
this.setState({ showOutline: false });
}
};

render() {
const { children, className, isDynamic } = this.props;
const { focusedIndex, selectedIndex, showOutline } = this.state;

return (
<TabViewPrimitive
className={classNames(className, {
'show-outline': showOutline,
})}
focusedIndex={focusedIndex}
isDynamic={isDynamic}
onKeyUp={this.handleKeyUp}
onTabFocus={this.handleOnTabFocus}
onTabSelect={this.handleOnTabSelect}
resetActiveTab={this.resetActiveTab}
resetFocusedTab={this.resetFocusedTab}
selectedIndex={selectedIndex}
>
{children}
</TabViewPrimitive>
);
}
}

export default TabView;
Loading
Loading