Add CopyTextButton

This commit is contained in:
Stefan Ålund
2020-04-13 17:05:31 +02:00
parent 5938544c46
commit 8d82d2e530
5 changed files with 206 additions and 0 deletions
@@ -0,0 +1,109 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { IconButton, Tooltip, withStyles } from '@material-ui/core';
import CopyIcon from '@material-ui/icons/FileCopy';
const buttonStyles = theme => ({
button: {
'&:hover': {
backgroundColor: theme.palette.highlight,
cursor: 'pointer',
},
},
});
/**
* Copy text button with visual feedback in the form of
* - a hover color
* - click ripple
* - Tooltip shown when user has clicked
*
* Properties:
* - text: the text to be copied
* - tooltipDelay: Number os ms to show the tooltip, default: 1000ms
* - tooltipText: Text to show in the tooltip when user has clicked the button, default: "Text
* copied to clipboard"
*
* Example:
* <CopyTextButton text="My text that I want to be copied to the clipboard" />
*/
class CopyTextButton extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
tooltipDelay: PropTypes.number,
tooltipText: PropTypes.string,
};
static defaultProps = {
tooltipDelay: 1000,
tooltipText: 'Text copied to clipboard',
};
state = {
open: false,
};
handleTooltipClose = () => {
this.setState({ open: false });
};
handleTooltipOpen = () => {
this.setState({ open: true });
};
handleCopyClick = e => {
e.stopPropagation();
this.handleTooltipOpen();
try {
this.clipboardInput.select();
document.execCommand('copy');
} catch (error) {
// console.error(error);
}
};
render() {
const { classes, text, tooltipDelay, tooltipText } = this.props;
return (
<Fragment>
<input
ref={el => (this.clipboardInput = el)}
type="text"
style={{ position: 'absolute', top: '-9999px', left: '-9999px' }}
defaultValue={text}
/>
<Tooltip
id="copy-test-tooltip"
title={tooltipText}
placement="top"
leaveDelay={tooltipDelay}
onClose={this.handleTooltipClose}
open={this.state.open}
>
<IconButton onClick={this.handleCopyClick} className={classes.button}>
<CopyIcon />
</IconButton>
</Tooltip>
</Fragment>
);
}
}
export default withStyles(buttonStyles)(CopyTextButton);
@@ -0,0 +1,34 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import CopyTextButton from '.';
export default {
title: 'CopyTextButton',
component: CopyTextButton,
};
export const Default = () => (
<CopyTextButton text="The text to copy to clipboard" />
);
export const WithTooltip = () => (
<CopyTextButton
text="The text to copy to clipboard"
tooltipText="Custom tooltip shown on button click"
/>
);
@@ -0,0 +1,45 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from '@backstage/test-utils';
import CopyTextButton from 'shared/components/CopyTextButton';
const props = {
text: 'mockText',
tooltipDelay: 2,
tooltipText: 'mockTooltip',
};
describe('<CopyTextButton />', () => {
it('renders without exploding', () => {
const { getByDisplayValue } = render(
wrapInThemedTestApp(<CopyTextButton {...props} />),
);
getByDisplayValue('mockText');
});
it('displays tooltip on click', () => {
const spy = jest.fn();
Object.defineProperty(document, 'execCommand', { value: spy });
const rendered = render(wrapInThemedTestApp(<CopyTextButton {...props} />));
const button = rendered.getByTitle('mockTooltip');
button.click();
expect(spy).toHaveBeenCalled();
rendered.getByText('mockTooltip');
});
});
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { default } from './CopyTextButton';
+1
View File
@@ -27,6 +27,7 @@ export * from './layout/Sidebar';
export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid';
export { default as ProgressCard } from './components/ProgressCard';
export { default as CircleProgress } from './components/CircleProgress';
export { default as CopyTextButton } from './components/CopyTextButton';
export { default as Progress } from './components/Progress';
export { AlphaLabel, BetaLabel } from './components/Lifecycle';
export { default as SupportButton } from './components/SupportButton';