add copy to clipboard to web editor (#9009)

pull/9022/head
Matt Hook 2023-05-31 12:28:11 +12:00 committed by GitHub
parent 1cda08ca11
commit 3a49dbf803
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 18 deletions

View File

@ -7,6 +7,8 @@ import { useMemo } from 'react';
import { createTheme } from '@uiw/codemirror-themes';
import { tags as highlightTags } from '@lezer/highlight';
import { CopyButton } from '@@/buttons/CopyButton';
import styles from './CodeEditor.module.css';
import { TextTip } from './Tip/TextTip';
@ -82,7 +84,24 @@ export function CodeEditor({
return (
<>
{!!placeholder && <TextTip color="blue">{placeholder}</TextTip>}
<div className="mb-2 flex flex-col">
<div className="flex">
<div className="flex-1">
{!!placeholder && <TextTip color="blue">{placeholder}</TextTip>}
</div>
<div className="ml-auto">
<CopyButton
copyText={value}
color="none"
className="!text-sm !font-medium text-blue-9 hover:!text-blue-11 th-highcontrast:text-blue-7 hover:th-highcontrast:!text-blue-6 th-dark:text-blue-7 hover:th-dark:!text-blue-6"
indicatorPosition="left"
>
Copy to clipboard
</CopyButton>
</div>
</div>
</div>
<CodeMirror
className={styles.root}
theme={theme}

View File

@ -14,3 +14,10 @@
@apply th-dark:bg-warning-5 th-dark:bg-opacity-10 th-dark:text-white;
@apply th-highcontrast:bg-warning-5 th-highcontrast:bg-opacity-10 th-highcontrast:text-white;
}
.btn-none:active {
outline: none;
background-color: transparent;
box-shadow: none;
-webkit-box-shadow: none;
}

View File

@ -15,6 +15,7 @@ export interface Props {
displayText?: string;
className?: string;
color?: ComponentProps<typeof Button>['color'];
indicatorPosition?: 'left' | 'right';
}
export function CopyButton({
@ -23,12 +24,30 @@ export function CopyButton({
displayText = 'copied',
className,
color,
indicatorPosition = 'right',
children,
}: PropsWithChildren<Props>) {
const { handleCopy, copiedSuccessfully } = useCopy(copyText, fadeDelay);
function copiedIndicator() {
return (
<span
className={clsx(
copiedSuccessfully && styles.fadeout,
styles.copyButton,
'mx-1',
'vertical-center'
)}
>
<Icon icon={Check} />
{displayText && <span className="space-left">{displayText}</span>}
</span>
);
}
return (
<div className={styles.container}>
{indicatorPosition === 'left' && copiedIndicator()}
<Button
className={className}
color={color}
@ -37,21 +56,11 @@ export function CopyButton({
title="Copy Value"
type="button"
icon={Copy}
disabled={!copyText}
>
{children}
</Button>
<span
className={clsx(
copiedSuccessfully && styles.fadeout,
styles.copyButton,
'space-left',
'vertical-center'
)}
>
<Icon icon={Check} />
{displayText && <span className="space-left">{displayText}</span>}
</span>
{indicatorPosition === 'right' && copiedIndicator()}
</div>
);
}

View File

@ -1,6 +1,12 @@
import { useEffect, useState } from 'react';
export function useCopy(copyText: string, fadeDelay = 1000) {
export type CopyTextType = string | (() => string);
export function useCopy(
copyText: CopyTextType,
fadeDelay = 1000,
context: HTMLElement = document.body
) {
const [copiedSuccessfully, setCopiedSuccessfully] = useState(false);
useEffect(() => {
@ -15,19 +21,25 @@ export function useCopy(copyText: string, fadeDelay = 1000) {
}, [copiedSuccessfully, fadeDelay]);
function handleCopy() {
const text = typeof copyText === 'function' ? copyText() : copyText;
if (!text) {
return;
}
// https://developer.mozilla.org/en-US/docs/Web/API/Clipboard
// https://caniuse.com/?search=clipboard
if (navigator.clipboard) {
navigator.clipboard.writeText(copyText);
navigator.clipboard.writeText(text);
} else {
// https://stackoverflow.com/a/57192718
const inputEl = document.createElement('textarea');
inputEl.value = copyText;
document.body.appendChild(inputEl);
inputEl.value = text;
context.appendChild(inputEl);
inputEl.select();
document.execCommand('copy');
inputEl.hidden = true;
document.body.removeChild(inputEl);
context.removeChild(inputEl);
}
setCopiedSuccessfully(true);
}