xref: /expo/docs/ui/components/Tabs/Tabs.tsx (revision dfd15ebd)
1import { css } from '@emotion/react';
2import { borderRadius, shadows, spacing, theme } from '@expo/styleguide';
3import { TabList, TabPanels, Tabs as ReachTabs, TabsProps } from '@reach/tabs';
4import * as React from 'react';
5
6import { TabButton } from './TabButton';
7
8type Props = React.PropsWithChildren<TabsProps> & {
9  tabs: string[];
10};
11
12const generateTabLabels = (children: React.ReactNode) => {
13  return React.Children.map(children, child =>
14    React.isValidElement(child) ? child?.props?.label : child || '[untitled]'
15  );
16};
17
18export const Tabs = ({ children, tabs }: Props) => {
19  const tabTitles = tabs || generateTabLabels(children);
20  const [tabIndex, setTabIndex] = React.useState(0);
21
22  return (
23    <ReachTabs index={tabIndex} onChange={setTabIndex} css={tabsWrapperStyle}>
24      <TabList css={tabsListStyle}>
25        {tabTitles.map((title, index) => (
26          <TabButton key={index} selected={index === tabIndex}>
27            {title}
28          </TabButton>
29        ))}
30      </TabList>
31      <TabPanels css={tabsPanelStyle}>{children}</TabPanels>
32    </ReachTabs>
33  );
34};
35
36const tabsWrapperStyle = css({
37  border: `1px solid ${theme.border.default}`,
38  borderRadius: borderRadius.small,
39  boxShadow: shadows.micro,
40  margin: `${spacing[4]}px 0`,
41});
42
43const tabsPanelStyle = css({
44  padding: `${spacing[4]}px ${spacing[5]}px ${spacing[1]}px`,
45
46  'pre:first-child': {
47    marginTop: spacing[1],
48  },
49
50  ul: {
51    marginBottom: spacing[3],
52  },
53});
54
55const tabsListStyle = css({
56  borderBottom: `1px solid ${theme.border.default}`,
57});
58