1 import React from 'react'
2 import {Box, Text, Button, Octicon, themeGet} from '@primer/react'
3 import {Highlight, themes, Prism} from 'prism-react-renderer'
4 import styled from 'styled-components'
5 import {CheckIcon, CopyIcon} from '@primer/octicons-react'
6 import copyToClipboard from 'copy-to-clipboard'
7 import {announce} from '../util/aria-live'
8 ;(typeof global !== 'undefined' ? global : window).Prism = Prism
9 require('prismjs/components/prism-bash')
10
11 const ClipboardCopy = ({value, ...props}) => {
12 const [copied, setCopied] = React.useState(false)
13
14 React.useEffect(() => {
15 const timeout = setTimeout(() => {
16 if (copied) {
17 setCopied(false)
18 }
19 }, 1000)
20
21 return () => clearTimeout(timeout)
22 }, [copied])
23
24 return (
25 <Button
26 {...props}
27 aria-label="Copy to clipboard"
28 onClick={() => {
29 copyToClipboard(value)
30 setCopied(true)
31 announce(`Copied to clipboard`)
32 }}
33 >
34 <Octicon icon={copied ? CheckIcon : CopyIcon} sx={{color: copied ? 'success.fg' : 'fg.muted'}} />
35 </Button>
36 )
37 }
38
39 export const InlineCode = styled.code`
40 padding: 0.2em 0.4em;
41 font-family: ${themeGet('fonts.mono')};
42 font-size: 85%;
43 background-color: ${themeGet('colors.neutral.muted')};
44 border-radius: ${themeGet('radii.2')};
45 `
46 const colorMap = {
47 'token comment': '#747458',
48 'token function': '#cf3846',
49 'token parameter variable': '#277d7b',
50 'token assign-left variable': '#277d7b',
51 }
52
53 const MonoText = props => <Text sx={{fontFamily: 'mono', fontSize: 1}} {...props} />
54
55 const CodeBlock = ({children, code, className, style}) => (
56 <Box
57 sx={{
58 // Make <pre> adjust to the width of the container
59 // https://stackoverflow.com/a/14406386
60 display: 'table',
61 tableLayout: 'fixed',
62 width: '100%',
63 mb: 3,
64 }}
65 >
66 <Box
67 style={style}
68 sx={{
69 ...(code ? {display: 'flex', justifyContent: 'space-between', flexDirection: 'row-reverse'} : {}),
70 borderRadius: 2,
71 borderStyle: 'solid',
72 borderWidth: 1,
73 borderColor: 'border.muted',
74 }}
75 >
76 {code ? (
77 <ClipboardCopy
78 value={code}
79 sx={{
80 borderRadius: 0,
81 borderStyle: 'solid',
82 borderWidth: 1,
83 borderColor: 'border.muted',
84 marginTop: '-1px',
85 marginRight: '-1px',
86 borderTopRightRadius: 2,
87 borderBottomLeftRadius: 2,
88 }}
89 />
90 ) : null}
91 <Box sx={{m: 0, p: 3, overflowX: 'auto'}}>
92 <Box as="pre" className={className} tabIndex={0} sx={{m: 0}}>
93 {children}
94 </Box>
95 </Box>
96 </Box>
97 </Box>
98 )
99
100 function Code({className = '', prompt, children}) {
101 if (prompt) {
102 return (
103 <CodeBlock style={themes.github.plain}>
104 <MonoText>{children}</MonoText>
105 </CodeBlock>
106 )
107 }
108
109 const code = children.trim()
110 const isBlock = className.startsWith('language-') || code.includes('\n')
111
112 if (!isBlock) {
113 return <InlineCode className={className}>{code}</InlineCode>
114 }
115
116 return (
117 <Highlight code={code} language={className.replace(/language-/, '') || 'bash'} theme={themes.github}>
118 {({className: highlightClassName, style, tokens, getLineProps, getTokenProps}) => (
119 <CodeBlock className={highlightClassName} style={style} code={code}>
120 {tokens.map((line, i) => (
121 <Box key={i} {...getLineProps({line, key: i})}>
122 {line.map((token, key) => {
123 const tokenProps = getTokenProps({token, key})
124 const tokenStyle = colorMap[tokenProps.className]
125 ? {...tokenProps.style, color: colorMap[tokenProps.className]}
126 : tokenProps.style
127
128 return <MonoText key={key} {...tokenProps} style={tokenStyle} />
129 })}
130 </Box>
131 ))}
132 </CodeBlock>
133 )}
134 </Highlight>
135 )
136 }
137
138 export default Code