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