mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-04 11:52:39 +02:00
refactor: replace text-based copy code button with icons (#6964)
This commit is contained in:
parent
19d2a18817
commit
b8d2a4e84d
5 changed files with 146 additions and 55 deletions
|
@ -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';
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue