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