diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22e739a09c6..95caab3b62a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)
+- Added `EuiCopy` ([#1112](https://github.com/elastic/eui/pull/1112))
- Added `disabled` to `EuiRadioGroup.options` ([#1111](https://github.com/elastic/eui/pull/1111))
**Bug fixes**
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js
index f2de5c15871..2f76fc32321 100644
--- a/src-docs/src/routes.js
+++ b/src-docs/src/routes.js
@@ -87,6 +87,9 @@ import { ComboBoxExample }
import { ContextMenuExample }
from './views/context_menu/context_menu_example';
+import { CopyExample }
+ from './views/copy/copy_example';
+
import { DatePickerExample }
from './views/date_picker/date_picker_example';
@@ -377,6 +380,7 @@ const navigation = [{
name: 'Utilities',
items: [
AccessibilityExample,
+ CopyExample,
ResponsiveExample,
DelayHideExample,
ErrorBoundaryExample,
diff --git a/src-docs/src/views/copy/copy.js b/src-docs/src/views/copy/copy.js
new file mode 100644
index 00000000000..ee61773db9f
--- /dev/null
+++ b/src-docs/src/views/copy/copy.js
@@ -0,0 +1,43 @@
+import React, { Component } from 'react';
+
+import {
+ EuiCopy,
+ EuiButton,
+ EuiFieldText,
+ EuiSpacer,
+} from '../../../../src/components/';
+
+export default class extends Component {
+
+ state = {
+ copyText: 'I am the text that will be copied'
+ }
+
+ onChange = e => {
+ this.setState({
+ copyText: e.target.value,
+ });
+ };
+
+ render() {
+ return (
+
+
+
+
+
+
+ {(copy) => (
+
+ Click to copy input text
+
+ )}
+
+
+ );
+ }
+}
diff --git a/src-docs/src/views/copy/copy_example.js b/src-docs/src/views/copy/copy_example.js
new file mode 100644
index 00000000000..e6954463f82
--- /dev/null
+++ b/src-docs/src/views/copy/copy_example.js
@@ -0,0 +1,38 @@
+import React from 'react';
+
+import { renderToHtml } from '../../services';
+
+import {
+ GuideSectionTypes,
+} from '../../components';
+
+import {
+ EuiCode,
+ EuiCopy,
+} from '../../../../src/components';
+
+import Copy from './copy';
+const copySource = require('!!raw-loader!./copy');
+const copyHtml = renderToHtml(Copy);
+
+export const CopyExample = {
+ title: 'Copy',
+ sections: [{
+ source: [{
+ type: GuideSectionTypes.JS,
+ code: copySource,
+ }, {
+ type: GuideSectionTypes.HTML,
+ code: copyHtml,
+ }],
+ text: (
+
+ The EuiCopy component is a utility for copying text to clipboard.
+ Wrap a function that returns a Component. The first argument will be a `copy` function.
+
+ ),
+ components: { EuiCopy },
+ demo: ,
+ props: { EuiCopy },
+ }],
+};
diff --git a/src/components/copy/copy.js b/src/components/copy/copy.js
new file mode 100644
index 00000000000..a505632f742
--- /dev/null
+++ b/src/components/copy/copy.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { copyToClipboard } from '../../services';
+import { EuiToolTip } from '../tool_tip';
+
+export class EuiCopy extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ tooltipText: this.props.beforeMessage
+ };
+ }
+
+ copy = () => {
+ const isCopied = copyToClipboard(this.props.textToCopy);
+ if (isCopied) {
+ this.setState({
+ tooltipText: this.props.afterMessage,
+ });
+ }
+ }
+
+ resetTooltipText = () => {
+ this.setState({
+ tooltipText: this.props.beforeMessage,
+ });
+ }
+
+ render() {
+ const {
+ children,
+ textToCopy, // eslint-disable-line no-unused-vars
+ beforeMessage, // eslint-disable-line no-unused-vars
+ afterMessage, // eslint-disable-line no-unused-vars
+ ...rest
+ } = this.props;
+
+ return (
+
+ {children(this.copy)}
+
+ );
+ }
+}
+
+EuiCopy.propTypes = {
+
+ /**
+ * Text that will be copied to clipboard when copy function is executed.
+ */
+ textToCopy: PropTypes.string.isRequired,
+
+ /**
+ * Tooltip message displayed before copy function is called.
+ */
+ beforeMessage: PropTypes.string,
+
+ /**
+ * Tooltip message displayed after copy function is called that lets the user know that
+ * 'textToCopy' has been copied to the clipboard.
+ */
+ afterMessage: PropTypes.string.isRequired,
+
+ /**
+ * Function that must return a Component. First argument is 'copy' function.
+ * Use your own logic to create the component that user's interactact with when triggering copy.
+ */
+ children: PropTypes.func.isRequired,
+};
+
+EuiCopy.defaultProps = {
+ afterMessage: 'Copied',
+};
+
diff --git a/src/components/copy/index.js b/src/components/copy/index.js
new file mode 100644
index 00000000000..fdcffd58765
--- /dev/null
+++ b/src/components/copy/index.js
@@ -0,0 +1,3 @@
+export {
+ EuiCopy,
+} from './copy';
diff --git a/src/components/index.js b/src/components/index.js
index 4148d1ab284..0213f6fe84f 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -65,6 +65,10 @@ export {
EuiContextMenuItem,
} from './context_menu';
+export {
+ EuiCopy,
+} from './copy';
+
export {
EuiDatePicker,
EuiDatePickerRange,
diff --git a/src/components/tool_tip/tool_tip.js b/src/components/tool_tip/tool_tip.js
index aa6b1ac0155..21f58ef27cc 100644
--- a/src/components/tool_tip/tool_tip.js
+++ b/src/components/tool_tip/tool_tip.js
@@ -116,6 +116,10 @@ export class EuiToolTip extends Component {
this.hideToolTip();
}
}
+
+ if (this.props.onMouseOut) {
+ this.props.onMouseOut();
+ }
};
render() {
@@ -142,7 +146,7 @@ export class EuiToolTip extends Component {
);
let tooltip;
- if (visible) {
+ if (visible && (content || title)) {
tooltip = (