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