Skip to content

Commit

Permalink
feat(v2): CodeBlock copy button (#1643)
Browse files Browse the repository at this point in the history
* feat(v2): CodeBlock copy button

* fix: live theme editor breaking bug
  • Loading branch information
bvego authored and endiliey committed Jul 12, 2019
1 parent 4faa608 commit 7b7d1e6
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/docusaurus-theme-classic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@mdx-js/mdx": "^1.0.20",
"@mdx-js/react": "^1.0.20",
"classnames": "^2.2.6",
"clipboard": "^2.0.4",
"infima": "0.2.0-alpha.2",
"prism-react-renderer": "^0.1.6",
"react-toggle": "^4.0.2"
Expand Down
62 changes: 52 additions & 10 deletions packages/docusaurus-theme-classic/src/theme/CodeBlock/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,73 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, {useEffect, useState, useRef} from 'react';
import classnames from 'classnames';
import Highlight, {defaultProps} from 'prism-react-renderer';
import nightOwlTheme from 'prism-react-renderer/themes/nightOwl';
import Clipboard from 'clipboard';
import styles from './styles.module.css';

export default ({children, className: languageClassName}) => {
const [showCopied, setShowCopied] = useState(false);
const target = useRef(null);
const button = useRef(null);

useEffect(() => {
let clipboard;

if (button.current) {
clipboard = new Clipboard(button.current, {
target: () => target.current,
});
}

return () => {
if (clipboard) {
clipboard.destroy();
}
};
}, [button.current, target.current]);

const language =
languageClassName && languageClassName.replace(/language-/, '');

const handleCopyCode = () => {
window.getSelection().empty();
setShowCopied(true);

setTimeout(() => setShowCopied(false), 2000);
};

return (
<Highlight
{...defaultProps}
theme={nightOwlTheme}
code={children.trim()}
language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<pre className={classnames(className, styles.codeBlock)} style={style}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({line, key: i})}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
))}
</div>
))}
</pre>
<div className={styles.codeBlockWrapper}>
<pre
ref={target}
className={classnames(className, styles.codeBlock)}
style={style}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({line, key: i})}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
))}
</div>
))}
</pre>
<button
ref={button}
type="button"
aria-label="Copy code to clipboard"
className={styles.copyButton}
onClick={handleCopyCode}>
{showCopied ? 'Copied' : 'Copy'}
</button>
</div>
)}
</Highlight>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,31 @@
overflow-wrap: break-word;
white-space: pre-wrap;
}

.codeBlockWrapper {
position: relative;
}

.codeBlockWrapper:hover > .copyButton {
bottom: calc(var(--ifm-pre-padding) - 2px);
visibility: visible;
opacity: 1;
}

.copyButton {
position: absolute;
right: var(--ifm-pre-padding);
bottom: calc(var(--ifm-pre-padding) - 4px);
padding: 4px 8px;
visibility: hidden;
opacity: 0;
transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out,
bottom 200ms ease-in-out;
border: 1px solid rgb(214, 222, 235);
border-radius: var(--ifm-global-radius);
outline: none;
cursor: pointer;
line-height: 12px;
background: rgb(1, 22, 39);
color: rgb(214, 222, 235);
}
1 change: 1 addition & 0 deletions packages/docusaurus-theme-live-codeblock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"license": "MIT",
"dependencies": {
"classnames": "^2.2.6",
"clipboard": "^2.0.4",
"prism-react-renderer": "^0.1.6",
"react-live": "^2.1.2",
"react-loadable": "^5.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, {useEffect, useState, useRef} from 'react';
import classnames from 'classnames';
import LoadableVisibility from 'react-loadable-visibility/react-loadable';
import Highlight, {defaultProps} from 'prism-react-renderer';
import nightOwlTheme from 'prism-react-renderer/themes/nightOwl';
import Clipboard from 'clipboard';
import Loading from '@theme/Loading';
import styles from './styles.module.css';

Expand All @@ -20,6 +21,26 @@ const Playground = LoadableVisibility({
});

export default ({children, className: languageClassName, live, ...props}) => {
const [showCopied, setShowCopied] = useState(false);
const target = useRef(null);
const button = useRef(null);

useEffect(() => {
let clipboard;

if (button.current) {
clipboard = new Clipboard(button.current, {
target: () => target.current,
});
}

return () => {
if (clipboard) {
clipboard.destroy();
}
};
}, [button.current, target.current]);

if (live) {
return (
<Playground
Expand All @@ -30,24 +51,46 @@ export default ({children, className: languageClassName, live, ...props}) => {
/>
);
}

const language =
languageClassName && languageClassName.replace(/language-/, '');

const handleCopyCode = () => {
window.getSelection().empty();
setShowCopied(true);

setTimeout(() => setShowCopied(false), 2000);
};

return (
<Highlight
{...defaultProps}
theme={nightOwlTheme}
code={children.trim()}
language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<pre className={classnames(className, styles.codeBlock)} style={style}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({line, key: i})}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
))}
</div>
))}
</pre>
<div className={styles.codeBlockWrapper}>
<pre
ref={target}
className={classnames(className, styles.codeBlock)}
style={style}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({line, key: i})}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
))}
</div>
))}
</pre>
<button
ref={button}
type="button"
aria-label="Copy code to clipboard"
className={styles.copyButton}
onClick={handleCopyCode}>
{showCopied ? 'Copied' : 'Copy'}
</button>
</div>
)}
</Highlight>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,31 @@
overflow-wrap: break-word;
white-space: pre-wrap;
}

.codeBlockWrapper {
position: relative;
}

.codeBlockWrapper:hover > .copyButton {
bottom: calc(var(--ifm-pre-padding) - 2px);
visibility: visible;
opacity: 1;
}

.copyButton {
position: absolute;
right: var(--ifm-pre-padding);
bottom: calc(var(--ifm-pre-padding) - 4px);
padding: 4px 8px;
visibility: hidden;
opacity: 0;
transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out,
bottom 200ms ease-in-out;
border: 1px solid rgb(214, 222, 235);
border-radius: var(--ifm-global-radius);
outline: none;
cursor: pointer;
line-height: 12px;
background: rgb(1, 22, 39);
color: rgb(214, 222, 235);
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3799,7 +3799,7 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=

clipboard@^2.0.0:
clipboard@^2.0.0, clipboard@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==
Expand Down

0 comments on commit 7b7d1e6

Please sign in to comment.