1import { css } from '@emotion/react'; 2import { darkTheme, spacing } from '@expo/styleguide'; 3import React from 'react'; 4 5import { Snippet } from '../Snippet'; 6import { SnippetContent } from '../SnippetContent'; 7import { SnippetHeader } from '../SnippetHeader'; 8import { CopyAction } from '../actions/CopyAction'; 9 10import { CODE } from '~/ui/components/Text'; 11 12type TerminalProps = { 13 cmd: string[]; 14 cmdCopy?: string; 15 hideOverflow?: boolean; 16 title?: string; 17}; 18 19export const Terminal = ({ cmd, cmdCopy, hideOverflow, title = 'Terminal' }: TerminalProps) => ( 20 <Snippet style={wrapperStyle}> 21 <SnippetHeader alwaysDark title={title}> 22 {renderCopyButton({ cmd, cmdCopy })} 23 </SnippetHeader> 24 <SnippetContent alwaysDark hideOverflow={hideOverflow}> 25 {cmd.map(cmdMapper)} 26 </SnippetContent> 27 </Snippet> 28); 29 30/** 31 * This method attempts to naively generate the basic cmdCopy from the given cmd list. 32 * Currently, the implementation is simple, but we can add multiline support in the future. 33 */ 34function getDefaultCmdCopy(cmd: TerminalProps['cmd']) { 35 const validLines = cmd.filter(line => !line.startsWith('#') && line !== ''); 36 if (validLines.length === 1) { 37 return validLines[0].startsWith('$') ? validLines[0].slice(2) : validLines[0]; 38 } 39 return undefined; 40} 41 42function renderCopyButton({ cmd, cmdCopy }: TerminalProps) { 43 const copyText = cmdCopy || getDefaultCmdCopy(cmd); 44 return copyText && <CopyAction alwaysDark text={copyText} />; 45} 46 47/** 48 * Map all provided lines and render the correct component. 49 * This method supports: 50 * - Render newlines for empty strings 51 * - Render a line with `#` prefix as comment with secondary text 52 * - Render a line without `$` prefix as primary text 53 * - Render a line with `$` prefix, as secondary and primary text 54 */ 55function cmdMapper(line: string, index: number) { 56 const key = `line-${index}`; 57 58 if (line.trim() === '') { 59 return <br key={key} css={unselectableStyle} />; 60 } 61 62 if (line.startsWith('#')) { 63 return ( 64 <CODE key={key} css={[codeStyle, unselectableStyle, { color: darkTheme.code.comment }]}> 65 {line} 66 </CODE> 67 ); 68 } 69 70 if (line.startsWith('$')) { 71 return ( 72 <div key={key}> 73 <CODE 74 css={[ 75 codeStyle, 76 unselectableStyle, 77 { display: 'inline', color: darkTheme.text.secondary }, 78 ]}> 79 → 80 </CODE> 81 <CODE css={codeStyle}>{line.substring(1).trim()}</CODE> 82 </div> 83 ); 84 } 85 86 return ( 87 <CODE key={key} css={[codeStyle, { display: 'inherit' }]}> 88 {line} 89 </CODE> 90 ); 91} 92 93const wrapperStyle = css` 94 li & { 95 margin-top: ${spacing[4]}px; 96 display: flex; 97 } 98`; 99 100const unselectableStyle = css` 101 user-select: none; 102`; 103 104const codeStyle = css` 105 display: inline-block; 106 line-height: 130%; 107 background-color: transparent; 108 border: none; 109 color: ${darkTheme.text.default}; 110`; 111