Skip to content

Commit

Permalink
Merge pull request #15559 from melindali255/copyPaste
Browse files Browse the repository at this point in the history
UI: Allow keyboard shortcut to copy code in preview blocks
  • Loading branch information
shilman committed Jul 16, 2021
2 parents 9cc34e0 + 2cb3910 commit c09ce85
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 16 deletions.
49 changes: 42 additions & 7 deletions lib/components/src/blocks/Preview.tsx
@@ -1,13 +1,22 @@
import React, { Children, FunctionComponent, ReactElement, ReactNode, useState } from 'react';
import React, {
Children,
ClipboardEvent,
FunctionComponent,
ReactElement,
ReactNode,
useState,
} from 'react';
import { darken } from 'polished';
import { styled } from '@storybook/theming';

import global from 'global';
import { getBlockBackgroundStyle } from './BlockBackgroundStyles';
import { Source, SourceProps } from './Source';
import { ActionBar, ActionItem } from '../ActionBar/ActionBar';
import { Toolbar } from './Toolbar';
import { ZoomContext } from './ZoomContext';
import { Zoom } from '../Zoom/Zoom';
import { createCopyToClipboardFunction } from '../syntaxhighlighter/syntaxhighlighter';

export interface PreviewProps {
isColumn?: boolean;
Expand Down Expand Up @@ -130,7 +139,7 @@ const getSource = (
}
default: {
return {
source: null,
source: <StyledSource {...withSource} dark />,
actionItem: {
title: 'Show code',
className: 'docblock-code-toggle',
Expand Down Expand Up @@ -197,13 +206,39 @@ const Preview: FunctionComponent<PreviewProps> = ({
const previewClasses = [className].concat(['sbdocs', 'sbdocs-preview']);

const defaultActionItems = withSource ? [actionItem] : [];
const actionItems = additionalActions
? [...defaultActionItems, ...additionalActions]
: defaultActionItems;
const [additionalActionItems, setAdditionalActionItems] = useState(
additionalActions ? [...additionalActions] : []
);
const actionItems = [...defaultActionItems, ...additionalActionItems];

// @ts-ignore
const layout = getLayout(Children.count(children) === 1 ? [children] : children);

const { window: globalWindow } = global;
const copyToClipboard: (text: string) => Promise<void> = createCopyToClipboardFunction();

const onCopyCapture = (e: ClipboardEvent<HTMLInputElement>) => {
e.preventDefault();
if (additionalActionItems.filter((item) => item.title === 'Copied').length === 0) {
copyToClipboard(source.props.code).then(() => {
setAdditionalActionItems([
...additionalActionItems,
{
title: 'Copied',
onClick: () => {},
},
]);
globalWindow.setTimeout(
() =>
setAdditionalActionItems(
additionalActionItems.filter((item) => item.title !== 'Copied')
),
1500
);
});
}
};

return (
<PreviewContainer
{...{ withSource, withToolbar }}
Expand All @@ -220,7 +255,7 @@ const Preview: FunctionComponent<PreviewProps> = ({
/>
)}
<ZoomContext.Provider value={{ scale }}>
<Relative className="docs-story">
<Relative className="docs-story" onCopyCapture={withSource && onCopyCapture}>
<ChildrenContainer
isColumn={isColumn || !Array.isArray(children)}
columns={columns}
Expand All @@ -238,7 +273,7 @@ const Preview: FunctionComponent<PreviewProps> = ({
<ActionBar actionItems={actionItems} />
</Relative>
</ZoomContext.Provider>
{withSource && source}
{withSource && expanded && source}
</PreviewContainer>
);
};
Expand Down
29 changes: 20 additions & 9 deletions lib/components/src/syntaxhighlighter/syntaxhighlighter.tsx
@@ -1,4 +1,10 @@
import React, { ComponentProps, FunctionComponent, MouseEvent, useState } from 'react';
import React, {
ClipboardEvent,
ComponentProps,
FunctionComponent,
MouseEvent,
useState,
} from 'react';
import { logger } from '@storybook/client-logger';
import { styled } from '@storybook/theming';
import global from 'global';
Expand Down Expand Up @@ -54,12 +60,13 @@ const themedSyntax = memoize(2)((theme) =>
Object.entries(theme.code || {}).reduce((acc, [key, val]) => ({ ...acc, [`* .${key}`]: val }), {})
);

let copyToClipboard: (text: string) => Promise<void>;
const copyToClipboard: (text: string) => Promise<void> = createCopyToClipboardFunction();

if (navigator?.clipboard) {
copyToClipboard = (text: string) => navigator.clipboard.writeText(text);
} else {
copyToClipboard = async (text: string) => {
export function createCopyToClipboardFunction() {
if (navigator?.clipboard) {
return (text: string) => navigator.clipboard.writeText(text);
}
return async (text: string) => {
const tmp = document.createElement('TEXTAREA');
const focus = document.activeElement;

Expand All @@ -72,6 +79,7 @@ if (navigator?.clipboard) {
focus.focus();
};
}

export interface WrapperProps {
bordered?: boolean;
padded?: boolean;
Expand Down Expand Up @@ -152,10 +160,13 @@ export const SyntaxHighlighter: FunctionComponent<Props> = ({
const highlightableCode = format ? formatter(children) : children.trim();
const [copied, setCopied] = useState(false);

const onClick = (e: MouseEvent<HTMLButtonElement>) => {
const onClick = (e: MouseEvent<HTMLButtonElement> | ClipboardEvent<HTMLDivElement>) => {
e.preventDefault();

copyToClipboard(highlightableCode)
const selectedText = globalWindow.getSelection().toString();
const textToCopy = e.type !== 'click' && selectedText ? selectedText : highlightableCode;

copyToClipboard(textToCopy)
.then(() => {
setCopied(true);
globalWindow.setTimeout(() => setCopied(false), 1500);
Expand All @@ -164,7 +175,7 @@ export const SyntaxHighlighter: FunctionComponent<Props> = ({
};

return (
<Wrapper bordered={bordered} padded={padded} className={className}>
<Wrapper bordered={bordered} padded={padded} className={className} onCopyCapture={onClick}>
<Scroller>
<ReactSyntaxHighlighter
padded={padded || bordered}
Expand Down

0 comments on commit c09ce85

Please sign in to comment.