box-ui-elements-mlh
Version:
226 lines (187 loc) • 6.31 kB
Flow
// @flow
import * as React from 'react';
import classNames from 'classnames';
import omit from 'lodash/omit';
import { FormattedMessage } from 'react-intl';
import messages from '../../common/messages';
import TextInput from '../text-input';
import Button from '../button';
import './TextInputWithCopyButton.scss';
const DEFAULT_SUCCESS_STATE_DURATION = 3000;
const defaultCopyText = <FormattedMessage {...messages.copy} />;
const defaultCopiedText = <FormattedMessage {...messages.copied} />;
type Props = {
/** Set the focus to input when component loads */
autofocus?: boolean,
/** Default copy button text */
buttonDefaultText: string | React.Node,
/** Copy button text when copy is successful */
buttonProps?: Object,
buttonSuccessText?: string | React.Node,
className: string,
disabled?: boolean,
/** Label displayed for the text input */
// TODO: Make label required
label?: React.Node,
/** onFocus handler for the input el */
onCopySuccess?: Function,
/** Function called when link is copied by keyboard or button */
onFocus?: Function,
/** Duration (milliseconds) in which to show the copy success state */
successStateDuration: number,
/** trigger the copy animation when the component loads (used to simulate a click on copy button) */
triggerCopyOnLoad?: boolean,
/** html input types (email, url, text, number), defaults to 'text' */
type: string,
/** Value of the text input */
value: React.Node,
};
type State = {
buttonText: string | React.Node,
copySuccess: boolean,
hasFocused: boolean,
};
class TextInputWithCopyButton extends React.PureComponent<Props, State> {
static defaultProps = {
buttonDefaultText: defaultCopyText,
buttonProps: {},
buttonSuccessText: defaultCopiedText,
className: '',
hideOptionalLabel: true,
readOnly: true,
successStateDuration: DEFAULT_SUCCESS_STATE_DURATION,
type: 'text',
};
constructor(props: Props) {
super(props);
this.isCopyCommandSupported = document.queryCommandSupported('copy');
this.state = {
copySuccess: false,
buttonText: props.buttonDefaultText,
hasFocused: false,
};
}
componentDidMount() {
const { autofocus, value } = this.props;
if (autofocus && value) {
this.performAutofocus();
}
}
componentDidUpdate() {
const { autofocus, value, triggerCopyOnLoad } = this.props;
const { copySuccess, hasFocused } = this.state;
// if we've set focus before, and should auto focus on update, make sure to
// focus after component update
if (autofocus && value) {
this.performAutofocus();
}
if (triggerCopyOnLoad && !copySuccess && !hasFocused) {
this.animateCopyButton();
}
}
componentWillUnmount() {
this.clearCopySuccessTimeout();
}
copyInputRef: ?HTMLInputElement;
copySuccessTimeout: ?TimeoutID;
isCopyCommandSupported: boolean;
animateCopyButton() {
const { successStateDuration, buttonSuccessText } = this.props;
this.clearCopySuccessTimeout();
this.setState(
{
copySuccess: true,
buttonText: buttonSuccessText,
hasFocused: true,
},
() => {
this.copySuccessTimeout = setTimeout(() => {
this.restoreCopyButton();
}, successStateDuration);
},
);
}
clearCopySuccessTimeout() {
if (!this.copySuccessTimeout) {
return;
}
clearTimeout(this.copySuccessTimeout);
this.copySuccessTimeout = null;
}
copySelectedText = () => document.execCommand('copy');
restoreCopyButton = () => {
this.setState({
copySuccess: false,
buttonText: this.props.buttonDefaultText,
});
};
handleCopyButtonClick = () => {
this.performAutofocus();
this.copySelectedText();
this.animateCopyButton();
};
handleFocus = (event: SyntheticEvent<>) => {
if (this.copyInputRef) {
this.performAutofocus();
}
if (this.props.onFocus) {
this.props.onFocus(event);
}
};
handleCopyEvent = (event: SyntheticEvent<>) => {
this.animateCopyButton();
const { onCopySuccess } = this.props;
if (onCopySuccess) {
onCopySuccess(event);
}
};
performAutofocus = () => {
const { copyInputRef } = this;
if (copyInputRef) {
copyInputRef.select();
copyInputRef.scrollLeft = 0;
}
};
renderCopyButton = () =>
this.isCopyCommandSupported ? (
<Button
isDisabled={this.props.disabled}
onClick={this.handleCopyButtonClick}
type="button"
{...this.props.buttonProps}
>
{this.state.buttonText}
</Button>
) : null;
render() {
const { className, ...rest } = this.props;
const { copySuccess } = this.state;
const { isCopyCommandSupported } = this;
const inputProps = omit(rest, [
'autofocus',
'buttonDefaultText',
'buttonSuccessText',
'buttonProps',
'onCopySuccess',
'successStateDuration',
'triggerCopyOnLoad',
]);
if (isCopyCommandSupported) {
inputProps.inputRef = ref => {
this.copyInputRef = ref;
};
}
const wrapperClasses = classNames(className, {
'copy-success': copySuccess,
'text-input-with-copy-button-container': isCopyCommandSupported,
});
const copyEvent = isCopyCommandSupported ? { onCopy: this.handleCopyEvent } : {};
return (
<div className={wrapperClasses} {...copyEvent}>
<TextInput {...inputProps} onFocus={this.handleFocus} />
{this.renderCopyButton()}
</div>
);
}
}
export default TextInputWithCopyButton;