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 console.warn(codeBlocks); 41 42 return ( 43 <div css={[codeBlocksWrapperStyle, connected && codeBlockConnectedWrapperStyle]} {...rest}> 44 {codeBlocks.map((codeBlock, index) => ( 45 <Snippet key={index} css={snippetWrapperStyle}> 46 <SnippetHeader title={tabNames[index]} Icon={FileCode01Icon}> 47 <CopyAction text={cleanCopyValue(codeBlock.props.children.props.children)} /> 48 </SnippetHeader> 49 <SnippetContent className="p-0 h-full">{codeBlock}</SnippetContent> 50 </Snippet> 51 ))} 52 </div> 53 ); 54} 55 56const codeBlocksWrapperStyle = css({ 57 display: 'grid', 58 gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr)', 59 gap: spacing[4], 60 gridAutoRows: '1fr', 61 62 pre: { 63 border: 0, 64 margin: 0, 65 gridTemplateRows: 'minmax(100px, 1fr)', 66 height: '100%', 67 }, 68 69 [`@media screen and (max-width: ${breakpoints.large}px)`]: { 70 gridTemplateColumns: 'minmax(0, 1fr)', 71 gridAutoRows: 'auto', 72 }, 73}); 74 75const codeBlockConnectedWrapperStyle = css({ 76 [`@media screen and (min-width: ${breakpoints.large}px)`]: { 77 gridGap: 0, 78 79 '> div:nth-of-type(odd)': { 80 '> div': { 81 borderRight: 0, 82 borderTopRightRadius: 0, 83 borderBottomRightRadius: 0, 84 }, 85 }, 86 87 '> div:nth-of-type(even)': { 88 '> div': { 89 borderTopLeftRadius: 0, 90 borderBottomLeftRadius: 0, 91 }, 92 }, 93 }, 94}); 95 96const snippetWrapperStyle = css({ 97 [`@media screen and (max-width: ${breakpoints.large}px)`]: { 98 marginBottom: 0, 99 100 '&:last-of-type': { 101 marginBottom: spacing[4], 102 }, 103 }, 104}); 105