Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ClipboardCopy): Convert clipboard copy to typescript #2131

Merged
merged 14 commits into from Jun 27, 2019

This file was deleted.

Expand Up @@ -2,6 +2,7 @@
title: 'Clipboard copy'
cssPrefix: 'pf-c-copyclipboard'
propComponents: ['ClipboardCopy']
typescript: true
---

import { ClipboardCopy, ClipboardCopyVariant } from '@patternfly/react-core';
Expand Down
@@ -1,15 +1,16 @@
import React from 'react';
import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/ClipboardCopy/clipboard-copy';
import { css } from '@patternfly/react-styles';
import PropTypes from 'prop-types';
import { OneOf, Omit } from '../../helpers/typeUtils';
import { PopoverPosition } from '../Popover';
import { TextInput } from '../TextInput';
import { TooltipPosition } from '../Tooltip';
import GenerateId from '../../helpers/GenerateId/GenerateId';
import CopyButton from './CopyButton';
import ToggleButton from './ToggleButton';
import ExpandedContent from './ExpandedContent';
import { ClipboardCopyButton } from './ClipboardCopyButton';
import { ClipboardCopyToggle } from './ClipboardCopyToggle';
import { ClipboardCopyExpanded } from './ClipboardCopyExpanded';

const clipboardCopyFunc = (event, text) => {
export const clipboardCopyFunc = (event: any, text: string) => {
const clipboard = event.currentTarget.parentElement;
const el = document.createElement('input');
el.value = text;
Expand All @@ -19,34 +20,89 @@ const clipboardCopyFunc = (event, text) => {
clipboard.removeChild(el);
};

export const ClipboardCopyVariant = {
inline: 'inline',
expansion: 'expansion'
};
export enum ClipboardCopyVariant {
inline = 'inline',
expansion = 'expansion'
}

export interface ClipboardCopyState {
text: string | number;
expanded: boolean;
copied: boolean;
}

export interface ClipboardCopyProps extends Omit<React.HTMLProps<HTMLDivElement>, 'onChange'> {
/** Additional classes added to the clipboard copy container. */
className?: string;
/** Tooltip message to display when hover the copy button */
hoverTip?: string;
/** Tooltip message to display when clicking the copy button */
clickTip?: string;
/** Custom flag to show that the input requires an associated id or aria-label. */
textAriaLabel?: string;
/** Custom flag to show that the toggle button requires an associated id or aria-label. */
toggleAriaLabel?: string;
/** Flag to show if the input is read only. */
isReadOnly?: boolean;
/** Adds Clipboard Copy variant styles. */
variant?: typeof ClipboardCopyVariant | 'inline' | 'expansion';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we decided to just have the union of the values here and not the "Typeof"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that we need the typeof for the older consumers using the enum.

/** Copy button popover position. */
position?: OneOf<typeof PopoverPosition, keyof typeof PopoverPosition>;
/** Maximum width of the tooltip (default 150px). */
maxWidth?: string;
/** Delay in ms before the tooltip disappears. */
exitDelay?: number;
/** Delay in ms before the tooltip appears. */
entryDelay?: number;
/** Delay in ms before the tooltip message switch to hover tip. */
switchDelay?: number;
/** A function that is triggered on clicking the copy button. */
onCopy?: (event: React.ClipboardEvent<HTMLDivElement>, text?: React.ReactNode) => void;
/** A function that is triggered on changing the text. */
onChange?: (text?: string | number) => void;
/** The text which is copied. */
children?: React.ReactNode;
}

class ClipboardCopy extends React.Component {
constructor(props) {
export class ClipboardCopy extends React.Component<ClipboardCopyProps, ClipboardCopyState> {
timer = null as NodeJS.Timer;
constructor(props: ClipboardCopyProps) {
super(props);
this.timer = null;
this.state = {
text: this.props.children,
text: this.props.children as string | number,
expanded: false,
copied: false
};
}

expandContent = () => {
static defaultProps = {
hoverTip: 'Copy to clipboard',
clickTip: 'Successfully copied to clipboard!',
isReadOnly: false,
variant: 'inline',
position: TooltipPosition.top,
maxWidth: '150px',
exitDelay: 1600,
entryDelay: 100,
switchDelay: 2000,
onCopy: clipboardCopyFunc,
onChange: (): any => undefined,
textAriaLabel: 'Copyable input',
toggleAriaLabel: 'Show content'
}

expandContent = (_event: React.MouseEvent<Element, MouseEvent>) => {
this.setState(prevState => ({
expanded: !prevState.expanded
}));
};

updateText = text => {
updateText = (text: string | number) => {
this.setState({ text });
this.props.onChange(text);
};

render() {
render = () => {
const {
isReadOnly,
exitDelay,
Expand All @@ -61,22 +117,23 @@ class ClipboardCopy extends React.Component {
variant,
position,
className,
...props
onChange, // Don't pass to <div>
...divProps
} = this.props;
const textIdPrefix = 'text-input-';
const toggleIdPrefix = 'toggle-';
const contentIdPrefix = 'content-';
return (
<div
className={css(styles.clipboardCopy, this.state.expanded && styles.modifiers.expanded, className)}
{...props}
{...divProps}
>
<GenerateId prefix="">
{id => (
<React.Fragment>
<div className={css(styles.clipboardCopyGroup)}>
{variant === 'expansion' && (
<ToggleButton
<ClipboardCopyToggle
isExpanded={this.state.expanded}
onClick={this.expandContent}
id={`${toggleIdPrefix}-${id}`}
Expand All @@ -88,19 +145,19 @@ class ClipboardCopy extends React.Component {
<TextInput
isReadOnly={isReadOnly || this.state.expanded}
onChange={this.updateText}
value={this.state.text}
value={this.state.text as string | number}
id={`text-input-${id}`}
aria-label={textAriaLabel}
/>
<CopyButton
<ClipboardCopyButton
exitDelay={exitDelay}
entryDelay={entryDelay}
maxWidth={maxWidth}
position={position}
id={`copy-button-${id}`}
textId={`text-input-${id}`}
aria-label={hoverTip}
onClick={event => {
onClick={(event: any) => {
if (this.timer) {
clearTimeout(this.timer);
this.setState({ copied: false });
Expand All @@ -115,12 +172,12 @@ class ClipboardCopy extends React.Component {
}}
>
{this.state.copied ? clickTip : hoverTip}
</CopyButton>
</ClipboardCopyButton>
</div>
{this.state.expanded && (
<ExpandedContent id={`content-${id}`} onChange={this.updateText}>
<ClipboardCopyExpanded id={`content-${id}`} onChange={this.updateText}>
{this.state.text}
</ExpandedContent>
</ClipboardCopyExpanded>
)}
</React.Fragment>
)}
Expand All @@ -129,57 +186,3 @@ class ClipboardCopy extends React.Component {
);
}
}

ClipboardCopy.propTypes = {
/** Additional classes added to the clipboard copy container. */
className: PropTypes.string,
/** Tooltip message to display when hover the copy button */
hoverTip: PropTypes.string,
/** Tooltip message to display when clicking the copy button */
clickTip: PropTypes.string,
/** Custom flag to show that the input requires an associated id or aria-label. */
textAriaLabel: PropTypes.string,
/** Custom flag to show that the toggle button requires an associated id or aria-label. */
toggleAriaLabel: PropTypes.string,
/** Flag to show if the input is read only. */
isReadOnly: PropTypes.bool,
/** Adds Clipboard Copy variant styles. */
variant: PropTypes.oneOf(Object.keys(ClipboardCopyVariant)),
/** Copy button popover position. */
position: PropTypes.oneOf(Object.keys(TooltipPosition)),
/** Maximum width of the tooltip (default 150px). */
maxWidth: PropTypes.string,
/** Delay in ms before the tooltip disappears. */
exitDelay: PropTypes.number,
/** Delay in ms before the tooltip appears. */
entryDelay: PropTypes.number,
/** Delay in ms before the tooltip message switch to hover tip. */
switchDelay: PropTypes.number,
/** A function that is triggered on clicking the copy button. */
onCopy: PropTypes.func,
/** A function that is triggered on changing the text. */
onChange: PropTypes.func,
/** The text which is copied. */
children: PropTypes.node.isRequired,
/** Additional props are spread to the container <div>. */
'': PropTypes.any // eslint-disable-line react/require-default-props
};

ClipboardCopy.defaultProps = {
className: '',
hoverTip: 'Copy to clipboard',
clickTip: 'Successfully copied to clipboard!',
isReadOnly: false,
variant: ClipboardCopyVariant.inline,
position: TooltipPosition.top,
maxWidth: '150px',
exitDelay: 1600,
entryDelay: 100,
switchDelay: 2000,
onCopy: clipboardCopyFunc,
onChange: () => {},
textAriaLabel: 'Copyable input',
toggleAriaLabel: 'Show content'
};

export default ClipboardCopy;
@@ -1,30 +1,30 @@
import React from 'react';
import { shallow } from 'enzyme';
import CopyButton from './CopyButton';
import { ClipboardCopyButton } from './ClipboardCopyButton';

const props = {
id: 'my-id',
textId: 'my-text-id',
className: 'fancy-copy-button',
onClick: jest.fn(),
exitDelay: 1000,
entryDelay: 2000,
entryDelay: 2000,
maxWidth: '500px',
position: 'right',
position: 'right' as 'right',
'aria-label': 'click this button to copy text'
};

test('copy button render', () => {
const view = shallow(<CopyButton {...props}>Copy Me</CopyButton>);
const view = shallow(<ClipboardCopyButton {...props}>Copy Me</ClipboardCopyButton>);
expect(view).toMatchSnapshot();
});

test('copy button onClick', () => {
const onclick = jest.fn();
const view = shallow(
<CopyButton {...props} onClick={onclick}>
<ClipboardCopyButton {...props} onClick={onclick}>
Copy to Clipboard
</CopyButton>
</ClipboardCopyButton>
);
view.find('button').simulate('click');
expect(onclick).toBeCalled();
Expand Down
@@ -0,0 +1,52 @@
import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/ClipboardCopy/clipboard-copy';
import { css } from '@patternfly/react-styles';
import { CopyIcon } from '@patternfly/react-icons';
import { Tooltip } from '../Tooltip';

export interface ClipboardCopyButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
onClick: (event: React.MouseEvent) => void;
children: React.ReactNode;
id: string;
textId: string;
className?: string;
exitDelay?: number;
entryDelay?: number;
maxWidth?: string;
position?: 'top' | 'bottom' | 'left' | 'right';
'aria-label'?: string;
}

export const ClipboardCopyButton: React.FunctionComponent<ClipboardCopyButtonProps> = ({
onClick,
className = '',
exitDelay = 100,
entryDelay = 100,
maxWidth = '100px',
position = 'top',
'aria-label': ariaLabel = 'Copyable input',
id,
textId,
children,
...props
}: ClipboardCopyButtonProps) => (
<Tooltip
trigger="mouseenter focus click"
exitDelay={exitDelay}
entryDelay={entryDelay}
maxWidth={maxWidth}
position={position}
content={<div>{children}</div>}
>
<button
onClick={onClick}
className={css(styles.clipboardCopyGroupCopy, className)}
aria-label={ariaLabel}
id={id}
aria-labelledby={`${id} ${textId}`}
{...props}
>
<CopyIcon />
</button>
</Tooltip>
);
@@ -1,6 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import ExpandedContent from './ExpandedContent';
import { ClipboardCopyExpanded } from './ClipboardCopyExpanded';

const props = {
className: 'class-1',
Expand All @@ -9,9 +9,9 @@ const props = {

test('expanded content render', () => {
const view = shallow(
<ExpandedContent {...props} onChange={() => {}}>
<ClipboardCopyExpanded {...props}>
This is my text
</ExpandedContent>
</ClipboardCopyExpanded>
);
expect(view).toMatchSnapshot();
});