Skip to content

Commit

Permalink
feat #299 - Add code copy feat
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-dassana committed Apr 22, 2021
1 parent 05a7ece commit a871c7e
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
72 changes: 72 additions & 0 deletions src/components/Code/CodeControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import cn from 'classnames'
import { createUseStyles } from 'react-jss'
import { faCopy } from '@fortawesome/pro-regular-svg-icons'
import { generateThemedControlsStyles } from './utils'
import { IconButton } from 'components/IconButton'
import { Tooltip } from 'components/Tooltip'
import React, { FC } from 'react'
import { styleguide, ThemeType } from 'components/assets/styles'

const { borderRadius, spacing } = styleguide

const { light, dark } = ThemeType

const useStyles = createUseStyles({
codeControls: {
...generateThemedControlsStyles(light),
borderRadius,
padding: `${spacing.xs}px ${spacing.s}px`,
position: 'absolute',
right: spacing['s+']
},
iconButton: {
padding: {
left: spacing.s,
right: spacing.s
}
},
// eslint-disable-next-line sort-keys
'@global': {
[`.${dark}`]: {
'& $codeControls': generateThemedControlsStyles(dark)
}
}
})

// This is left as an object so more controls (e.g. download) can be added later
export interface DisplayCodeControls {
copy?: boolean
}

interface Props {
classes?: string[]
displayControls?: DisplayCodeControls
isCopied?: boolean
onClickCopyCode: () => void
}

export const CodeControls: FC<Props> = ({
classes = [],
displayControls = {},
isCopied = false,
onClickCopyCode
}: Props) => {
const compClasses = useStyles()

const { copy = true } = displayControls

return (
<div className={cn(compClasses.codeControls, classes)}>
{copy && (
<Tooltip placement='top' title={isCopied ? 'Copied!' : 'Copy'}>
<IconButton
classes={[compClasses.iconButton]}
icon={faCopy}
onClick={onClickCopyCode}
size={14}
/>
</Tooltip>
)}
</div>
)
}
34 changes: 31 additions & 3 deletions src/components/Code/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import './prism.css'
import cn from 'classnames'
import { isObject } from 'lodash'
import Mark from 'mark.js'
import Prism from 'prismjs'
import { useStyles } from './utils'
import { CodeControls, DisplayCodeControls } from './CodeControls'
import { copyToClipboard, useStyles } from './utils'
import { Input, InputProps } from 'components/Input'
import React, { ChangeEvent, FC, useEffect, useRef } from 'react'
import React, { ChangeEvent, FC, useEffect, useRef, useState } from 'react'

require('prismjs/plugins/line-numbers/prism-line-numbers')
require('prismjs/components/prism-json')

export interface CodeProps {
classes?: string[]
code: string | Record<string, any>
displayControls?: DisplayCodeControls | false
language?: 'css' | 'html' | 'javascript' | 'json'
lineNumbers?: boolean
readOnly?: boolean
Expand All @@ -22,6 +26,7 @@ export interface CodeProps {
export const Code: FC<CodeProps> = ({
classes = [],
code,
displayControls = {},
language = 'json',
lineNumbers = true,
readOnly = true,
Expand All @@ -30,6 +35,20 @@ export const Code: FC<CodeProps> = ({
const compClasses = useStyles()
const codeRef = useRef<HTMLElement>(null)

const [isCopied, setIsCopied] = useState(false)

const copyCode = () => {
const stringifiedCode = isObject(code)
? JSON.stringify(code, null, '\t')
: code

copyToClipboard(stringifiedCode, () => setIsCopied(true))
}

useEffect(() => {
if (isCopied) setTimeout(() => setIsCopied(false), 1250)
}, [isCopied])

useEffect(() => {
/**
* We want to highlight the code after it is rendered onto the page,
Expand Down Expand Up @@ -72,9 +91,18 @@ export const Code: FC<CodeProps> = ({
<div className={cn(classes)}>
{search && <Input {...searchProps} onChange={onSearch} />}
<pre
className={cn({ 'line-numbers': lineNumbers })}
className={cn(compClasses.preCode, {
'line-numbers': lineNumbers
})}
contentEditable={!readOnly}
>
{displayControls && (
<CodeControls
classes={[compClasses.controls]}
isCopied={isCopied}
onClickCopyCode={copyCode}
/>
)}
<code className={`language-${language}`} ref={codeRef}>
{typeof code === 'string'
? code
Expand Down
32 changes: 31 additions & 1 deletion src/components/Code/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { createUseStyles } from 'react-jss'
import { styleguide, themedStyles, ThemeType } from 'components/assets/styles'
import {
styleguide,
themedStyles,
themes,
ThemeType
} from 'components/assets/styles'

const { dark, light } = ThemeType

Expand All @@ -10,6 +15,12 @@ const {
spacing
} = styleguide

interface CopyToClipboard {
(str: string, callback?: () => void): void
}
export const copyToClipboard: CopyToClipboard = (str, callback) =>
navigator.clipboard.writeText(str).then(callback)

const codePalette = {
[dark]: {
background: blacks['darken-20']
Expand All @@ -32,6 +43,17 @@ const generateThemedCodeStyles = (themeType: ThemeType) => {
}
}

export const generateThemedControlsStyles = (themeType: ThemeType) => {
const {
base: { borderColor }
} = themedStyles[themeType]

return {
background: themes[themeType].background.secondary,
border: `1px solid ${borderColor}`
}
}

const generateThemedPreCodeStyles = (themeType: ThemeType) => {
const { background } = codePalette[themeType]

Expand Down Expand Up @@ -96,6 +118,14 @@ const preCodeSelector = "pre[class*='language-']"
/* eslint-enable quotes */

export const useStyles = createUseStyles({
controls: { opacity: 0 },
preCode: {
'&:hover': {
'& $controls': { opacity: 1 }
},
position: 'relative'
},
// eslint-disable-next-line sort-keys
'@global': {
[preCodeSelector]: {
'& .line-numbers-rows': {
Expand Down

0 comments on commit a871c7e

Please sign in to comment.