mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-07 13:22:26 +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;
|
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' {
|
declare module '@theme/DocCard' {
|
||||||
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
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 React, {isValidElement, useEffect, useState} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Highlight, {defaultProps, type Language} from 'prism-react-renderer';
|
import Highlight, {defaultProps, type Language} from 'prism-react-renderer';
|
||||||
import copy from 'copy-text-to-clipboard';
|
|
||||||
import Translate, {translate} from '@docusaurus/Translate';
|
|
||||||
import {
|
import {
|
||||||
useThemeConfig,
|
useThemeConfig,
|
||||||
parseCodeBlockTitle,
|
parseCodeBlockTitle,
|
||||||
|
@ -18,6 +16,7 @@ import {
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
usePrismTheme,
|
usePrismTheme,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
|
import CopyButton from '@theme/CodeBlock/CopyButton';
|
||||||
import type {Props} from '@theme/CodeBlock';
|
import type {Props} from '@theme/CodeBlock';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
@ -31,7 +30,6 @@ export default function CodeBlock({
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const {prism} = useThemeConfig();
|
const {prism} = useThemeConfig();
|
||||||
|
|
||||||
const [showCopied, setShowCopied] = useState(false);
|
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
// The Prism theme on SSR is always the default theme but the site theme
|
// 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
|
// 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;
|
languageProp ?? parseLanguage(blockClassName) ?? prism.defaultLanguage;
|
||||||
const {highlightLines, code} = parseLines(content, metastring, language);
|
const {highlightLines, code} = parseLines(content, metastring, language);
|
||||||
|
|
||||||
const handleCopyCode = () => {
|
|
||||||
copy(code);
|
|
||||||
setShowCopied(true);
|
|
||||||
|
|
||||||
setTimeout(() => setShowCopied(false), 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Highlight
|
<Highlight
|
||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
|
@ -120,12 +111,13 @@ export default function CodeBlock({
|
||||||
{codeBlockTitle}
|
{codeBlockTitle}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={clsx(styles.codeBlockContent, language)}>
|
<div
|
||||||
|
className={clsx(styles.codeBlockContent, language)}
|
||||||
|
style={style}>
|
||||||
<pre
|
<pre
|
||||||
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
|
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={clsx(className, styles.codeBlock, 'thin-scrollbar')}
|
className={clsx(className, styles.codeBlock, 'thin-scrollbar')}>
|
||||||
style={style}>
|
|
||||||
<code className={styles.codeBlockLines}>
|
<code className={styles.codeBlockLines}>
|
||||||
{tokens.map((line, i) => {
|
{tokens.map((line, i) => {
|
||||||
if (line.length === 1 && line[0]!.content === '\n') {
|
if (line.length === 1 && line[0]!.content === '\n') {
|
||||||
|
@ -150,29 +142,7 @@ export default function CodeBlock({
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<button
|
<CopyButton code={code} />
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: var(--ifm-global-radius);
|
border-radius: var(--ifm-global-radius);
|
||||||
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeBlockTitle + .codeBlockContent .codeBlock {
|
.codeBlockTitle + .codeBlockContent .codeBlock {
|
||||||
|
@ -42,25 +43,6 @@
|
||||||
border-radius: var(--ifm-global-radius);
|
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 {
|
.codeBlockLines {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
/* rtl:ignore */
|
/* rtl:ignore */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue