Skip to content

Commit

Permalink
Merge pull request #222 from storybookjs/clipboard
Browse files Browse the repository at this point in the history
Clipboard components
  • Loading branch information
winkerVSbecks authored Nov 18, 2020
2 parents 34201db + af53994 commit ad9b349
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/components/CodeSnippets.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

import { Clipboard } from './Clipboard';
import { Clipboard } from './clipboard/Clipboard';
import { Highlight } from './Highlight';
import { color, spacing, typography } from './shared/styles';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import React from 'react';

import { action } from '@storybook/addon-actions';
import { Clipboard } from './Clipboard';
import { TooltipNote } from './tooltip/TooltipNote';
// @ts-ignore
import { TooltipNote } from '../tooltip/TooltipNote';

export default {
title: 'Design System/Clipboard',
decorators: [(storyFn) => <div style={{ padding: '3em' }}>{storyFn()}</div>],
title: 'Design System/Clipboard/Clipboard',
decorators: [(storyFn: any) => <div style={{ padding: '3em' }}>{storyFn()}</div>],
};

export const Default = () => <Clipboard toCopy="linky">Click to copy</Clipboard>;

export const Callback = () => (
<Clipboard getCopyContent={() => 'linky from callback'}>Click to copy</Clipboard>
<Clipboard getCopyContent={action('linky from callback') as any}>Click to copy</Clipboard>
);

export const Tooltips = () => (
<Clipboard
toCopy="linky"
renderCopiedTooltip={() => <TooltipNote note="Copied" />}
renderUncopiedTooltip={() => <TooltipNote note="Copy to clipboard" />}
// @ts-ignore
startOpen
>
Click to copy
</Clipboard>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ import copyToClipboard from 'copy-to-clipboard';
import PropTypes from 'prop-types';
import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';

import { TooltipNote } from './tooltip/TooltipNote';
import WithTooltip from './tooltip/WithTooltip';
// @ts-ignore
import { TooltipNote } from '../tooltip/TooltipNote';
// @ts-ignore
import WithTooltip from '../tooltip/WithTooltip';

const Tooltip = styled(WithTooltip)`
cursor: pointer;
`;

interface ClipboardProps {
children: React.ReactNode | ((copied: boolean) => React.ReactNode);
toCopy?: string;
getCopyContent?: () => string;
copyOptions?: any;
renderCopiedTooltip?: () => React.ReactNode;
renderUncopiedTooltip?: () => React.ReactNode;
resetTimeout?: number;
}

export const Clipboard = ({
children,
toCopy,
Expand All @@ -19,19 +30,20 @@ export const Clipboard = ({
renderCopiedTooltip,
renderUncopiedTooltip,
...props
}) => {
const timeout = useRef();
}: ClipboardProps) => {
const [copied, setCopied] = useState(false);

useEffect(() => {
if (copied && timeout.current) {
clearTimeout(timeout.current);
let timeoutId: NodeJS.Timeout;

if (copied && timeoutId) {
clearTimeout(timeoutId);
}
if (copied && resetTimeout) {
timeout.current = setTimeout(() => setCopied(false), resetTimeout);
if (copied && resetTimeout && timeoutId) {
timeoutId = setTimeout(() => setCopied(false), resetTimeout);
}
return () => {
clearTimeout(timeout.current);
clearTimeout(timeoutId);
};
}, [copied]);

Expand All @@ -53,21 +65,11 @@ export const Clipboard = ({
);
};

Clipboard.propTypes = {
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
toCopy: PropTypes.string,
getCopyContent: PropTypes.func,
copyOptions: PropTypes.object, // eslint-disable-line
renderCopiedTooltip: PropTypes.func,
renderUncopiedTooltip: PropTypes.func,
resetTimeout: PropTypes.number,
};

Clipboard.defaultProps = {
copyOptions: undefined,
renderCopiedTooltip: () => null,
renderUncopiedTooltip: () => null,
renderCopiedTooltip: () => {},
renderUncopiedTooltip: () => {},
resetTimeout: 3000,
toCopy: undefined,
getCopyContent: () => undefined,
getCopyContent: () => '',
};
20 changes: 20 additions & 0 deletions src/components/clipboard/ClipboardCode.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';

import { ClipboardCode } from './ClipboardCode';

export default {
title: 'Design System/Clipboard/ClipboardCode',
};

export const Default = () => (
<div style={{ padding: '3em', width: 300 }}>
<ClipboardCode code="git checkout master" />
</div>
);

export const Wrapped = () => (
<div style={{ padding: '3em', width: 250 }}>
<ClipboardCode code="git checkout master" />
</div>
);
35 changes: 35 additions & 0 deletions src/components/clipboard/ClipboardCode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import styled from 'styled-components';

import { ClipboardIcon } from './ClipboardIcon';

const Container = styled.div`
position: relative;
`;

const Code = styled.pre`
width: 100%;
display: block;
margin: 0;
padding-right: 32px;
overflow: hidden;
`;

const StyledClipboardIcon = styled(ClipboardIcon)`
position: absolute;
top: 8px;
right: 8px;
`;

interface ClipboardCodeProps {
code: string;
}

export const ClipboardCode = ({ code, ...props }: ClipboardCodeProps) => (
<Container>
<Code id="clipboard-code" {...props}>
{code}
</Code>
<StyledClipboardIcon toCopy={code} />
</Container>
);
13 changes: 13 additions & 0 deletions src/components/clipboard/ClipboardIcon.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

import { ClipboardIcon } from './ClipboardIcon';

export default {
title: 'Design System/Clipboard/ClipboardIcon',
};

export const Default = () => (
<div style={{ padding: '3em' }}>
<ClipboardIcon toCopy="value" />
</div>
);
33 changes: 33 additions & 0 deletions src/components/clipboard/ClipboardIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import styled from 'styled-components';
import { color } from '../shared/styles';
import { Icon } from '../Icon';
import { Clipboard } from './Clipboard';

const StyledClipboard = styled(Clipboard)`
line-height: 14px;
padding: 5px;
color: ${color.mediumdark};
&:hover {
color: ${color.darker};
}
`;

interface StyledIconProps {
copied: boolean;
}

const StyledIcon = styled(Icon)<StyledIconProps>`
width: 14px;
height: 14px;
vertical-align: top;
color: ${(props) => (props.copied ? color.positive : 'inherit')};
`;

type ClipboardIconProps = Omit<React.ComponentPropsWithoutRef<typeof Clipboard>, 'children'>;

export const ClipboardIcon = (props: ClipboardIconProps) => (
<StyledClipboard {...props}>
{(copied) => <StyledIcon icon={copied ? 'check' : 'copy'} copied={copied} />}
</StyledClipboard>
);
19 changes: 19 additions & 0 deletions src/components/clipboard/ClipboardInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

import { ClipboardInput } from './ClipboardInput';

export default {
title: 'Design System/Clipboard/ClipboardInput',
};

export const Default = () => (
<div style={{ padding: '3em', width: 300 }}>
<ClipboardInput label="Label" value="https://www.chromatic.com" />
</div>
);

export const Clipped = () => (
<div style={{ padding: '3em', width: 250 }}>
<ClipboardInput label="Label" value="https://www.chromatic.com" />
</div>
);
55 changes: 55 additions & 0 deletions src/components/clipboard/ClipboardInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from 'styled-components';
// @ts-ignore
import { Input } from '../Input';
import { color, spacing } from '../shared/styles';
import { ClipboardIcon } from './ClipboardIcon';

const Container = styled.div`
position: relative;
margin-top: 15px;
`;

const StyledInput = styled(Input)`
width: 100%;
display: block;
&& input {
padding-top: 7px !important;
padding-bottom: 7px !important;
padding-right: 26px !important;
background: ${color.lightest};
border: 1px solid ${color.border};
border-radius: ${spacing.borderRadius.small}px;
font-size: 11px;
&:focus {
box-shadow: none;
border: 1px solid ${color.secondary};
}
}
`;

const StyledClipboardIcon = styled(ClipboardIcon)`
position: absolute;
top: 4px;
right: 4px;
`;

interface ClipboardInputProps extends React.ComponentPropsWithoutRef<typeof Input> {
value: string;
}

export const ClipboardInput = ({ value, ...props }: ClipboardInputProps) => (
<Container>
<StyledInput
{...props}
id="clipboard-input"
icon={undefined}
hideLabel
value={value}
appearance="code"
readOnly
/>
<StyledClipboardIcon toCopy={value} />
</Container>
);
6 changes: 5 additions & 1 deletion src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export { default as WithModal } from './modal/WithModal';
export * from './table-of-contents/TableOfContents';
export * from './ShadowBoxCTA';

export * from './Clipboard';
export * from './clipboard/Clipboard';
export * from './clipboard/ClipboardIcon';
export * from './clipboard/ClipboardCode';
export * from './clipboard/ClipboardInput';

export * from './LinkTabs';
export * from './CodeSnippets';

Expand Down

0 comments on commit ad9b349

Please sign in to comment.