All files / src/components live-code.js

0% Statements 0/14
100% Branches 0/0
0% Functions 0/7
0% Lines 0/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                                                                                                                                               
import {Absolute, BorderBox, Flex, Relative, Text} from '@primer/components'
import htmlReactParser from 'html-react-parser'
import githubTheme from 'prism-react-renderer/themes/github'
import React, { useState } from 'react'
import reactElementToJsxString from 'react-element-to-jsx-string'
import {LiveEditor, LiveError, LivePreview, LiveProvider} from 'react-live'
import {ThemeContext} from 'styled-components'
import scope from '../live-code-scope'
import ClipboardCopy from './clipboard-copy'
import LivePreviewWrapper from './live-preview-wrapper'
 
const languageTransformers = {
  html: html => htmlToJsx(html),
  jsx: jsx => wrapWithFragment(jsx),
}
 
function htmlToJsx(html) {
  try {
    const reactElement = htmlReactParser(removeNewlines(html))
    // The output of htmlReactParser could be a single React element
    // or an array of React elements. reactElementToJsxString does not accept arrays
    // so we have to wrap the output in React fragment.
    return reactElementToJsxString(<>{reactElement}</>)
  } catch (error) {
    return wrapWithFragment(html)
  }
}
 
function removeNewlines(string) {
  return string.replace(/(\r\n|\n|\r)/gm, '')
}
 
function wrapWithFragment(jsx) {
  return `<React.Fragment>${jsx}</React.Fragment>`
}
 
function LiveCode({code, language, noinline}) {
  const theme = React.useContext(ThemeContext)
  const [liveCode, setLiveCode] = useState(code)
  const handleChange = (updatedLiveCode) => setLiveCode(updatedLiveCode)
 
  return (
    <BorderBox
      flexDirection="column"
      mb={3}
    >
      <LiveProvider
        scope={scope}
        code={liveCode}
        transformCode={languageTransformers[language]}
        noInline={noinline}
      >
        <LivePreviewWrapper>
          <LivePreview />
        </LivePreviewWrapper>
        <Relative>
          <LiveEditor
            onChange={handleChange}
            theme={githubTheme}
            ignoreTabKey={true}
            padding={theme.space[3]}
            style={{
              fontFamily: theme.fonts.mono,
              fontSize: '85%',
              borderBottomLeftRadius: theme.radii[2],
              borderBottomRightRadius: theme.radii[2],
            }}
          />
          <Absolute top={0} right={0} p={2}>
            <ClipboardCopy value={liveCode} />
          </Absolute>
        </Relative>
        <Text
          as={LiveError}
          m={0}
          p={3}
          fontFamily="mono"
          fontSize={1}
          color="white"
          bg="red.5"
        />
      </LiveProvider>
    </BorderBox>
  )
}
 
export default LiveCode