1 import React from 'react'
2 import {Text, Button} 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 import * as styles from './code.module.css'
9 import {clsx} from 'clsx'
10 ;(typeof global !== 'undefined' ? global : window).Prism = Prism
11 require('prismjs/components/prism-bash')
12
13 const ClipboardCopy = ({value, ...props}) => {
14 const [copied, setCopied] = React.useState(false)
15
16 React.useEffect(() => {
17 const timeout = setTimeout(() => {
18 if (copied) {
19 setCopied(false)
20 }
21 }, 1000)
22
23 return () => clearTimeout(timeout)
24 }, [copied])
25
26 return (
27 <Button
28 aria-label="Copy to clipboard"
29 size="small"
30 onClick={() => {
31 copyToClipboard(value)
32 setCopied(true)
33 announce(`Copied to clipboard`)
34 }}
35 className={clsx(styles.Button, props.className)}
36 >
37 {copied ? <CheckIcon fill="#1a7f37" /> : <CopyIcon fill="#656d76" />}
38 </Button>
39 )
40 }
41
42 export const InlineCode = styled.code`
43 padding: 0.2em 0.4em;
44 font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);
45 font-size: 80%;
46 background-color: var(--bgColor-neutral-muted, #afb8c133);
47 border-radius: 6px;
48 `
49 const colorMap = {
50 'token comment': '#747458',
51 'token function': '#cf3846',
52 'token parameter variable': '#277d7b',
53 'token assign-left variable': '#277d7b',
54 'token string': '#db1068',
55 }
56
57 const MonoText = props => <Text className={styles.Text} {...props} />
58
59 const CodeBlock = ({children, code, className, style}) => (
60 <div className={styles.Box}>
61 <div style={style} className={styles.Box_1}>
62 {code ? <ClipboardCopy value={code} className={styles.ClipboardCopy} /> : null}
63 <div className={styles.Box_2}>
64 <pre className={clsx(className, styles.Box_3)} tabIndex={0}>
65 {children}
66 </pre>
67 </div>
68 </div>
69 </div>
70 )
71
72 function Code({className = '', prompt, children}) {
73 if (prompt) {
74 return (
75 <CodeBlock style={themes.github.plain}>
76 <MonoText>{children}</MonoText>
77 </CodeBlock>
78 )
79 }
80
81 const code = children.trim()
82 const isBlock = className.startsWith('language-') || code.includes('\n')
83
84 if (!isBlock) {
85 return <InlineCode className={className}>{code}</InlineCode>
86 }
87
88 return (
89 <Highlight code={code} language={className.replace(/language-/, '') || 'bash'} theme={themes.github}>
90 {({className: highlightClassName, style, tokens, getLineProps, getTokenProps}) => (
91 <CodeBlock className={highlightClassName} style={style} code={code}>
92 {tokens.map((line, i) => (
93 <div key={i} {...getLineProps({line, key: i})}>
94 {line.map((token, key) => {
95 const tokenProps = getTokenProps({token, key})
96 const tokenStyle = colorMap[tokenProps.className]
97 ? {...tokenProps.style, color: colorMap[tokenProps.className]}
98 : tokenProps.style
99
100 return <MonoText key={key} {...tokenProps} style={tokenStyle} />
101 })}
102 </div>
103 ))}
104 </CodeBlock>
105 )}
106 </Highlight>
107 )
108 }
109
110 export default Code