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 sx={{
35 ...props.sx,
36 '&:focus-visible': {
37 outline: '2px solid',
38 outlineColor: '-webkit-focus-ring-color',
39 outlineOffset: '1px',
40 },
41 }}
42 >
43 <Octicon icon={copied ? CheckIcon : CopyIcon} sx={{color: copied ? 'success.fg' : 'fg.muted'}} />
44 </Button>
45 )
46 }
47
48 export const InlineCode = styled.code`
49 padding: 0.2em 0.4em;
50 font-family: ${themeGet('fonts.mono')};
51 font-size: 80%;
52 background-color: ${themeGet('colors.neutral.muted')};
53 border-radius: ${themeGet('radii.2')};
54 `
55 const colorMap = {
56 'token comment': '#747458',
57 'token function': '#cf3846',
58 'token parameter variable': '#277d7b',
59 'token assign-left variable': '#277d7b',
60 'token string': '#db1068',
61 }
62
63 const MonoText = props => <Text sx={{fontFamily: 'mono', fontSize: 1}} {...props} />
64
65 const CodeBlock = ({children, code, className, style}) => (
66 <Box
67 sx={{
68 // Make <pre> adjust to the width of the container
69 // https://stackoverflow.com/a/14406386
70 display: 'table',
71 tableLayout: 'fixed',
72 width: '100%',
73 mb: 3,
74 }}
75 >
76 <Box
77 style={style}
78 sx={{
79 ...(code ? {display: 'flex', justifyContent: 'space-between', flexDirection: 'row-reverse'} : {}),
80 borderRadius: 2,
81 borderStyle: 'solid',
82 borderWidth: 1,
83 borderColor: 'border.muted',
84 }}
85 >
86 {code ? (
87 <ClipboardCopy
88 value={code}
89 sx={{
90 borderRadius: 0,
91 borderStyle: 'solid',
92 borderWidth: 1,
93 borderColor: 'border.muted',
94 marginTop: '-1px',
95 marginRight: '-1px',
96 borderTopRightRadius: 2,
97 borderBottomLeftRadius: 2,
98 }}
99 />
100 ) : null}
101 <Box sx={{m: 0, p: 3, overflowX: 'auto'}}>
102 <Box as="pre" className={className} tabIndex={0} sx={{m: 0}}>
103 {children}
104 </Box>
105 </Box>
106 </Box>
107 </Box>
108 )
109
110 function Code({className = '', prompt, children}) {
111 if (prompt) {
112 return (
113 <CodeBlock style={themes.github.plain}>
114 <MonoText>{children}</MonoText>
115 </CodeBlock>
116 )
117 }
118
119 const code = children.trim()
120 const isBlock = className.startsWith('language-') || code.includes('\n')
121
122 if (!isBlock) {
123 return <InlineCode className={className}>{code}</InlineCode>
124 }
125
126 return (
127 <Highlight code={code} language={className.replace(/language-/, '') || 'bash'} theme={themes.github}>
128 {({className: highlightClassName, style, tokens, getLineProps, getTokenProps}) => (
129 <CodeBlock className={highlightClassName} style={style} code={code}>
130 {tokens.map((line, i) => (
131 <Box key={i} {...getLineProps({line, key: i})}>
132 {line.map((token, key) => {
133 const tokenProps = getTokenProps({token, key})
134 const tokenStyle = colorMap[tokenProps.className]
135 ? {...tokenProps.style, color: colorMap[tokenProps.className]}
136 : tokenProps.style
137
138 return <MonoText key={key} {...tokenProps} style={tokenStyle} />
139 })}
140 </Box>
141 ))}
142 </CodeBlock>
143 )}
144 </Highlight>
145 )
146 }
147
148 export default Code