import * as React from "react"; import { Tabs as ReactTabs, TabList, TabPanel } from "react-tabs"; import { injectGlobal, cx } from "@emotion/css"; import { TabItemProps } from "./TabItem"; import { TabTitle } from ".."; import { fontWeightMedium, themeTextColorPrimary, themeBrandPrimary, themeBgHover } from "../../design-tokens/build/js/designTokens"; import { listReset } from "../../shared/styles/styleUtils"; import { BreakpointConfig } from "../../shared/styles/breakpoints"; import { fullHeightTabs, getTabLayout } from "../style"; export const defaultTabDirection = "horiz"; // Copy & paste from node_modules/react-tabs/style/react-tabs.css // Also changed to better fit the ui kit styles. // This is needed to give the tabs a style /* eslint-disable */ injectGlobal` .react-tabs { -webkit-tap-highlight-color: transparent; } .react-tabs__tab-list { ${listReset}; } .react-tabs__tab { position: relative; cursor: pointer; font-weight: ${fontWeightMedium}; color: ${themeTextColorPrimary}; &:after{ content: ""; position: absolute; background: ${themeBrandPrimary}; display: none; } } /* Focusing on tabs with a mouse will not display an outline */ .react-tabs__tab:focus:not(:focus-visible) { outline: 0; } /* Focusing tabs with a keyboard will add a light gray background color */ .react-tabs__tab:focus-visible { outline: 0; background-color: ${themeBgHover} } .react-tabs__tab--selected { &:after{ display: block; } color: ${themeBrandPrimary}; } .react-tabs__tab--disabled { color: GrayText; cursor: default; } .react-tabs__tab-panel { display: none; flex-grow: 1; } .react-tabs__tab-panel--selected { display: block; } `; /* eslint-enable */ export type TabDirections = "horiz" | "vert"; export type TabDirection = BreakpointConfig; export type TabSelected = string; export interface TabsProps { /** * Children should use the TabItem component */ children: | Array> | React.ReactElement; /** * The index number of the selected tab */ selectedIndex?: number; /** * The function called when tab items are selected */ onSelect?: (tabIndex: number) => void; /** * Controls the orientation */ direction?: TabDirection; /** * Allows custom styling */ className?: string; /** * Human-readable selector used for writing tests */ "data-cy"?: string; } const Tabs = ({ children, selectedIndex, onSelect, className, direction = defaultTabDirection, "data-cy": dataCy = "tabs" }: TabsProps) => { const { tabs, tabsContent } = ( React.Children.toArray(children) as Array> ) .filter(item => React.isValidElement(item)) .reduce<{ tabs: React.ReactNode[]; tabsContent: React.ReactNode[]; }>( (acc, item) => { const { tabs = [], tabsContent = [] } = acc; const { children } = item.props; const key = item.key ? item.key : undefined; const childrenWithKeys = React.Children.toArray(children).map( (child, index) => React.isValidElement(child) ? React.cloneElement(child, { key: `${key}-${index}` }) : child ); const title = childrenWithKeys.find( child => React.isValidElement(child) && child.type === TabTitle ); const tabChildren = childrenWithKeys.filter( child => !(React.isValidElement(child) && child.type === TabTitle) ); return { tabs: [...tabs, title], tabsContent: [ ...tabsContent, ...(tabChildren.length ? [{tabChildren}] : []) ] }; }, { tabs: [], tabsContent: [] } ); return ( {}} data-cy={dataCy} focusTabOnClick={false} > {tabs} {tabsContent} ); }; export default React.memo(Tabs);