1import { css } from '@emotion/react';
2import { breakpoints, spacing } from '@expo/styleguide-base';
3import { FileCode01Icon } from '@expo/styleguide-icons';
4import { PropsWithChildren } from 'react';
5
6import { cleanCopyValue } from '~/components/base/code';
7import { Snippet } from '~/ui/components/Snippet/Snippet';
8import { SnippetContent } from '~/ui/components/Snippet/SnippetContent';
9import { SnippetHeader } from '~/ui/components/Snippet/SnippetHeader';
10import { CopyAction } from '~/ui/components/Snippet/actions/CopyAction';
11
12const MDX_CLASS_NAME_TO_TAB_NAME: Record<string, string> = {
13  'language-swift': 'Swift',
14  'language-kotlin': 'Kotlin',
15  'language-javascript': 'JavaScript',
16  'language-typescript': 'TypeScript',
17  'language-json': 'JSON',
18  'language-ruby': 'Ruby',
19  'language-groovy': 'Gradle',
20};
21
22type Props = PropsWithChildren<{
23  tabs?: string[];
24  connected?: boolean;
25}>;
26
27export function CodeBlocksTable({ children, tabs, connected = true, ...rest }: Props) {
28  const childrenArray = Array.isArray(children) ? children : [children];
29  const codeBlocks = childrenArray.filter(
30    ({ props }) =>
31      props.children.props.className && props.children.props.className.startsWith('language-')
32  );
33  const tabNames =
34    tabs ||
35    codeBlocks.map(child => {
36      const className = child.props.children.props.className;
37      return MDX_CLASS_NAME_TO_TAB_NAME[className] || className.replace('language-', '');
38    });
39
40  return (
41    <div css={[codeBlocksWrapperStyle, connected && codeBlockConnectedWrapperStyle]} {...rest}>
42      {codeBlocks.map((codeBlock, index) => (
43        <Snippet key={index} css={snippetWrapperStyle}>
44          <SnippetHeader title={tabNames[index]} Icon={FileCode01Icon}>
45            <CopyAction text={cleanCopyValue(codeBlock.props.children.props.children)} />
46          </SnippetHeader>
47          <SnippetContent className="p-0 h-full">{codeBlock}</SnippetContent>
48        </Snippet>
49      ))}
50    </div>
51  );
52}
53
54const codeBlocksWrapperStyle = css({
55  display: 'grid',
56  gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)',
57  gap: spacing[4],
58  gridAutoRows: '1fr',
59
60  pre: {
61    border: 0,
62    margin: 0,
63    gridTemplateRows: 'minmax(100px, 1fr)',
64    height: '100%',
65  },
66
67  [`@media screen and (max-width: ${breakpoints.large}px)`]: {
68    gridTemplateColumns: 'minmax(0, 1fr)',
69    gridAutoRows: 'auto',
70  },
71});
72
73const codeBlockConnectedWrapperStyle = css({
74  [`@media screen and (min-width: ${breakpoints.large}px)`]: {
75    gridGap: 0,
76
77    '> div:nth-of-type(odd)': {
78      '> div': {
79        borderRight: 0,
80        borderTopRightRadius: 0,
81        borderBottomRightRadius: 0,
82      },
83    },
84
85    '> div:nth-of-type(even)': {
86      '> div': {
87        borderTopLeftRadius: 0,
88        borderBottomLeftRadius: 0,
89      },
90    },
91  },
92});
93
94const snippetWrapperStyle = css({
95  [`@media screen and (max-width: ${breakpoints.large}px)`]: {
96    marginBottom: 0,
97
98    '&:last-of-type': {
99      marginBottom: spacing[4],
100    },
101  },
102});
103