Source

components/copyButton.js

import React from 'react'
import PropTypes from 'prop-types'
import { makeStyles } from '@material-ui/core/styles'
import { Button } from '@material-ui/core'

import { isBrowser } from '../utils/checks'
import { error } from '../utils/log'
import sleep from '../utils/sleep'

const useStyles = makeStyles(
  {
    textarea: {
      opacity: 0,
      width: 0,
      height: 0,
      padding: 0,
    },
  },
  { name: 'copyButton' }
)

/**
 * Copy some text to the clipboard.
 *
 * @param {object} ref React reference object
 * @param {string} text Value to be copied
 */
const copyToClipboard = async (ref, text = null) => {
  const element = ref.current

  if (!isBrowser || !element) return false

  if (text) {
    element.innerHTML = text
    // Wait for some time until the DOM has been updated.
    await sleep(1000)
  }

  const range = document.createRange()
  range.selectNode(element)
  window.getSelection().addRange(range)

  let successful = false

  try {
    document.execCommand('copy')
    successful = document.execCommand('copy')
  } catch (err) {
    error(`Unable to copy: ${err}`)
    successful = false
  }

  window.getSelection().removeAllRanges()

  return successful
}

/**
 * Button to copy some text to the clipboard.
 *
 * @component
 */
const CopyButton = ({ value, children, ...props }) => {
  const classes = useStyles()
  const spanRef = React.createRef(null)

  const handleClick = () => {
    copyToClipboard(spanRef)
  }

  return (
    <React.Fragment>
      <span ref={spanRef} className={classes.textarea}>
        {value}
      </span>
      <Button onClick={handleClick} {...props}>
        {children}
      </Button>
    </React.Fragment>
  )
}

CopyButton.propTypes = {
  /** Value to be copied. */
  value: PropTypes.string,
  /** Content of the button component */
  children: PropTypes.node.isRequired,
}

export default CopyButton

export { copyToClipboard }