1import { css } from '@emotion/react';
2import { spacing } from '@expo/styleguide-base';
3import { TerminalSquareIcon } from '@expo/styleguide-icons';
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  includeMargin?: boolean;
17  title?: string;
18};
19
20export const Terminal = ({
21  cmd,
22  cmdCopy,
23  hideOverflow,
24  includeMargin = true,
25  title = 'Terminal',
26}: TerminalProps) => (
27  <Snippet css={wrapperStyle} includeMargin={includeMargin}>
28    <SnippetHeader alwaysDark title={title} Icon={TerminalSquareIcon}>
29      {renderCopyButton({ cmd, cmdCopy })}
30    </SnippetHeader>
31    <SnippetContent alwaysDark hideOverflow={hideOverflow} className="grid grid-cols-auto-min-1">
32      {cmd.map(cmdMapper)}
33    </SnippetContent>
34  </Snippet>
35);
36
37/**
38 * This method attempts to naively generate the basic cmdCopy from the given cmd list.
39 * Currently, the implementation is simple, but we can add multiline support in the future.
40 */
41function getDefaultCmdCopy(cmd: TerminalProps['cmd']) {
42  const validLines = cmd.filter(line => !line.startsWith('#') && line !== '');
43  if (validLines.length === 1) {
44    return validLines[0].startsWith('$') ? validLines[0].slice(2) : validLines[0];
45  }
46  return undefined;
47}
48
49function renderCopyButton({ cmd, cmdCopy }: TerminalProps) {
50  const copyText = cmdCopy || getDefaultCmdCopy(cmd);
51  return copyText && <CopyAction alwaysDark text={copyText} />;
52}
53
54/**
55 * Map all provided lines and render the correct component.
56 * This method supports:
57 *   - Render newlines for empty strings
58 *   - Render a line with `#` prefix as comment with secondary text
59 *   - Render a line without `$` prefix as primary text
60 *   - Render a line with `$` prefix, as secondary and primary text
61 */
62function cmdMapper(line: string, index: number) {
63  const key = `line-${index}`;
64
65  if (line.trim() === '') {
66    return <br key={key} className="select-none" />;
67  }
68
69  if (line.startsWith('#')) {
70    return (
71      <CODE
72        key={key}
73        className="whitespace-pre !bg-[transparent] !border-none select-none !text-palette-gray10">
74        {line}
75      </CODE>
76    );
77  }
78
79  if (line.startsWith('$')) {
80    return (
81      <div key={key}>
82        <CODE className="whitespace-pre !bg-[transparent] !border-none select-none !text-secondary">
83          -&nbsp;
84        </CODE>
85        <CODE className="whitespace-pre !bg-[transparent] !border-none text-default">
86          {line.substring(1).trim()}
87        </CODE>
88      </div>
89    );
90  }
91
92  return (
93    <CODE key={key} className="whitespace-pre !bg-[transparent] !border-none text-default">
94      {line}
95    </CODE>
96  );
97}
98
99const wrapperStyle = css`
100  li & {
101    margin-top: ${spacing[4]}px;
102    display: flex;
103  }
104`;
105