refactor: replace text-based copy code button with icons (#6964)

This commit is contained in:
Alexey Pyltsyn 2022-03-24 12:47:56 +03:00 committed by GitHub
parent 19d2a18817
commit b8d2a4e84d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 55 deletions

View file

@ -126,6 +126,14 @@ declare module '@theme/CodeBlock' {
export default function CodeBlock(props: Props): JSX.Element;
}
declare module '@theme/CodeBlock/CopyButton' {
export interface Props {
readonly code: string;
}
export default function CopyButton(props: Props): JSX.Element;
}
declare module '@theme/DocCard' {
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';

View file

@ -0,0 +1,65 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, {useState} from 'react';
import clsx from 'clsx';
import copy from 'copy-text-to-clipboard';
import {translate} from '@docusaurus/Translate';
import type {Props} from '@theme/CodeBlock/CopyButton';
import styles from './styles.module.css';
export default function CopyButton({code}: Props): JSX.Element {
const [isCopied, setIsCopied] = useState(false);
const handleCopyCode = () => {
copy(code);
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
};
return (
<button
type="button"
aria-label={
isCopied
? translate({
id: 'theme.CodeBlock.copied',
message: 'Copied',
description: 'The copied button label on code blocks',
})
: translate({
id: 'theme.CodeBlock.copyButtonAriaLabel',
message: 'Copy code to clipboard',
description: 'The ARIA label for copy code blocks button',
})
}
// @todo: check it again later
title={translate({
id: 'theme.CodeBlock.copy',
message: 'Copy',
description: 'The copy button label on code blocks',
})}
className={clsx(
styles.copyButton,
'clean-btn',
isCopied && styles.copyButtonCopied,
)}
onClick={handleCopyCode}>
<span className={styles.copyButtonIcons} aria-hidden="true">
<svg className={styles.copyButtonIcon} viewBox="0 0 24 24">
<path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />
</svg>
<svg className={styles.copyButtonSuccessIcon} viewBox="0 0 24 24">
<path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
</svg>
</span>
</button>
);
}

View file

@ -0,0 +1,66 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.copyButton {
display: flex;
background: inherit;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: var(--ifm-global-radius);
padding: 0.4rem;
position: absolute;
right: calc(var(--ifm-pre-padding) / 2);
top: calc(var(--ifm-pre-padding) / 2);
transition: opacity 200ms ease-in-out;
opacity: 0;
}
.copyButton:focus-visible,
.copyButton:hover,
:global(.theme-code-block:hover) .copyButtonCopied {
opacity: 1 !important;
}
:global(.theme-code-block:hover) .copyButton:not(.copyButtonCopied) {
opacity: 0.4;
}
.copyButtonIcons {
position: relative;
width: 1.125rem;
height: 1.125rem;
}
.copyButtonIcon,
.copyButtonSuccessIcon {
position: absolute;
top: 0;
left: 0;
fill: currentColor;
opacity: inherit;
width: inherit;
height: inherit;
transition: all 0.15s ease;
}
.copyButtonSuccessIcon {
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.33);
opacity: 0;
color: #00d600;
}
.copyButtonCopied .copyButtonIcon {
transform: scale(0.33);
opacity: 0;
}
.copyButtonCopied .copyButtonSuccessIcon {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
transition-delay: 0.075s;
}

View file

@ -8,8 +8,6 @@
import React, {isValidElement, useEffect, useState} from 'react';
import clsx from 'clsx';
import Highlight, {defaultProps, type Language} from 'prism-react-renderer';
import copy from 'copy-text-to-clipboard';
import Translate, {translate} from '@docusaurus/Translate';
import {
useThemeConfig,
parseCodeBlockTitle,
@ -18,6 +16,7 @@ import {
ThemeClassNames,
usePrismTheme,
} from '@docusaurus/theme-common';
import CopyButton from '@theme/CodeBlock/CopyButton';
import type {Props} from '@theme/CodeBlock';
import styles from './styles.module.css';
@ -31,7 +30,6 @@ export default function CodeBlock({
}: Props): JSX.Element {
const {prism} = useThemeConfig();
const [showCopied, setShowCopied] = useState(false);
const [mounted, setMounted] = useState(false);
// The Prism theme on SSR is always the default theme but the site theme
// can be in a different mode. React hydration doesn't update DOM styles
@ -90,13 +88,6 @@ export default function CodeBlock({
languageProp ?? parseLanguage(blockClassName) ?? prism.defaultLanguage;
const {highlightLines, code} = parseLines(content, metastring, language);
const handleCopyCode = () => {
copy(code);
setShowCopied(true);
setTimeout(() => setShowCopied(false), 2000);
};
return (
<Highlight
{...defaultProps}
@ -120,12 +111,13 @@ export default function CodeBlock({
{codeBlockTitle}
</div>
)}
<div className={clsx(styles.codeBlockContent, language)}>
<div
className={clsx(styles.codeBlockContent, language)}
style={style}>
<pre
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
tabIndex={0}
className={clsx(className, styles.codeBlock, 'thin-scrollbar')}
style={style}>
className={clsx(className, styles.codeBlock, 'thin-scrollbar')}>
<code className={styles.codeBlockLines}>
{tokens.map((line, i) => {
if (line.length === 1 && line[0]!.content === '\n') {
@ -150,29 +142,7 @@ export default function CodeBlock({
</code>
</pre>
<button
type="button"
aria-label={translate({
id: 'theme.CodeBlock.copyButtonAriaLabel',
message: 'Copy code to clipboard',
description: 'The ARIA label for copy code blocks button',
})}
className={clsx(styles.copyButton, 'clean-btn')}
onClick={handleCopyCode}>
{showCopied ? (
<Translate
id="theme.CodeBlock.copied"
description="The copied button label on code blocks">
Copied
</Translate>
) : (
<Translate
id="theme.CodeBlock.copy"
description="The copy button label on code blocks">
Copy
</Translate>
)}
</button>
<CopyButton code={code} />
</div>
</div>
)}

View file

@ -30,6 +30,7 @@
margin: 0;
padding: 0;
border-radius: var(--ifm-global-radius);
background-color: inherit;
}
.codeBlockTitle + .codeBlockContent .codeBlock {
@ -42,25 +43,6 @@
border-radius: var(--ifm-global-radius);
}
.copyButton {
background: rgb(0 0 0 / 30%);
border-radius: var(--ifm-global-radius);
color: var(--ifm-color-white);
opacity: 0;
user-select: none;
padding: 0.4rem 0.5rem;
position: absolute;
right: calc(var(--ifm-pre-padding) / 2);
top: calc(var(--ifm-pre-padding) / 2);
transition: opacity 200ms ease-in-out;
}
.copyButton:focus,
.codeBlockContent:hover > .copyButton,
.codeBlockTitle:hover + .codeBlockContent .copyButton {
opacity: 1;
}
.codeBlockLines {
font: inherit;
/* rtl:ignore */