import classNames from "classnames"; import * as React from "react"; import * as Classes from "../../common/classes"; import { Elevation } from "../../common/elevation"; import { DISPLAYNAME_PREFIX, HTMLDivProps, IProps } from "../../common/props"; import { Tab, TabId } from "./Tab"; export interface ITabsProps extends IProps { /** * Controls the intensity of the drop shadow beneath the tab: the higher * the elevation, the higher the drop shadow. At elevation `0`, no drop * shadow is applied. * * @default 0 */ elevation?: Elevation; /** * Whether the tab should respond to user interactions. If set to `true`, * hovering over the tab will increase the tab's elevation * and change the mouse cursor to a pointer. * * @default false */ interactive?: boolean; /** * Header props */ additionalHeaderProps?: IProps & HTMLDivProps; /** * Additional Header element */ additionalHeader?: React.ReactNode; /** * Header element */ children?: Tab[] | React.ReactNode; /** * Header element */ selectedTabId?: TabId; /** * A callback function that is invoked when a tab in the tab list is clicked. */ onChange?(newTabId: TabId, prevTabId: TabId, event: React.MouseEvent): void; } export class Tabs extends React.PureComponent { public static displayName = `${DISPLAYNAME_PREFIX}.Tabs`; public static defaultProps: ITabsProps = { elevation: Elevation.ZERO, interactive: false, }; state: { activeItemId: TabId } = { activeItemId: undefined } public componentWillReceiveProps({ selectedTabId }: ITabsProps) { if (selectedTabId !== undefined) { // keep state in sync with controlled prop, so state is canonical source of truth this.setState({ activeItemId: selectedTabId }); } } private isTabActive(item: Tab, index: number): boolean { return this.state.activeItemId === undefined && index === 0 ? true : item.props.id === this.state.activeItemId } private renderHeaders() { return (this.props.children as Tab[]).map((item, index) => { const { className: headerClassName, ...htmHeaderProps } = item.props.headerProps || { className: null }; const activeClass: string = this.isTabActive(item, index) && Classes.TAB_HEADER_ACTIVE; const disabledClass: string = item.props.disabled && Classes.TAB_HEADER_DISABLED; const classesHeader = classNames( Classes.TAB_HEADER, activeClass, disabledClass, "col-auto", headerClassName ); return
{ if (!item.props.disabled && item.props.id !== this.state.activeItemId) { if (this.props.onChange) this.props.onChange(item.props.id, this.state.activeItemId, e); this.setState({activeItemId: item.props.id}); } }}> {item.props.title}
}) } public render() { const { className, elevation, interactive, children, additionalHeader, additionalHeaderProps, onChange, ...htmlProps } = this.props; const { className: additionalHeaderClassName, ...htmHeaderProps } = additionalHeaderProps || { className: null }; const classes = classNames( Classes.TABS, { [Classes.INTERACTIVE]: interactive }, Classes.elevationClass(elevation), className, ); const classesHeader = classNames( Classes.TABS_HEADER, "align-items-stretch", "row" ); const classesAdditionalHeader = classNames( "col", additionalHeaderClassName, ); return (
{this.renderHeaders()} {additionalHeader &&
{additionalHeader}
}
{(children as Tab[]).filter((item, index) => this.isTabActive(item, index)).map(i => React.cloneElement(i as any, {key: i.props.id}))}
); } }