diff --git a/examples/copy-button.jsx b/examples/copy-button.jsx
new file mode 100644
index 0000000..dbc3f16
--- /dev/null
+++ b/examples/copy-button.jsx
@@ -0,0 +1,17 @@
+const CopyForm = React.createClass({
+ _getTarget: function() {
+ return this.targetElement;
+ },
+ render: function() {
+ return (
+
+ this.targetElement = ref}
+ />
+
+
+ );
+ },
+});
+
+return ;
diff --git a/js/copy-button.jsx b/js/copy-button.jsx
new file mode 100644
index 0000000..fc8f3b4
--- /dev/null
+++ b/js/copy-button.jsx
@@ -0,0 +1,79 @@
+const React = require("react");
+
+/* Copy to clipboard button
+ * attach to input, textarea or any other "text container" (div, span, label..)
+ */
+const CopyButton = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ defaultLabel: React.PropTypes.string,
+ didCopyLabel: React.PropTypes.string,
+ notSupportedLabel: React.PropTypes.string,
+ notSupportedLabelMac: React.PropTypes.string,
+ targetElement: React.PropTypes.func.isRequired,
+ },
+ getDefaultProps: function() {
+ return {
+ defaultLabel: "Copy",
+ didCopyLabel: "Copied",
+ notSupportedLabel: "Ctrl+C to copy",
+ notSupportedLabelMac: "⌘-C to copy",
+ };
+ },
+ getInitialState: function() {
+ return {text: this.props.defaultLabel};
+ },
+ _copy: function() {
+ const element = this.props.targetElement();
+ const selection = window.getSelection();
+
+ if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
+ element.focus();
+ element.setSelectionRange(0, element.value.length);
+ } else {
+ const range = document.createRange();
+ range.selectNodeContents(element);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+
+ const result = document.execCommand('copy');
+ this._handleResult(result, selection);
+ },
+ _handleResult: function(result, selection) {
+ let label;
+ if (!result) {
+ if (navigator.platform.indexOf("Mac") !== -1) {
+ label = this.props.notSupportedLabelMac;
+ } else {
+ label = this.props.notSupportedLabel;
+ }
+ } else {
+ label = this.props.didCopyLabel;
+ this.props.targetElement().blur();
+ selection.removeAllRanges();
+ }
+
+ this.setState({
+ text: label,
+ });
+ const _this = this;
+ setTimeout(function() {
+ _this.setState({
+ text: _this.props.defaultLabel,
+ });
+ }, 1000);
+ },
+ render: function() {
+ return (
+
+ );
+ },
+});
+
+module.exports = CopyButton;
diff --git a/reactify-components.jsx b/reactify-components.jsx
index 9c92a0c..badaec8 100644
--- a/reactify-components.jsx
+++ b/reactify-components.jsx
@@ -25,6 +25,7 @@ window.TimeAgo = require("./js/timeago.jsx");
window.TimeoutTransitionGroup = require("./js/timeout-transition-group.jsx");
window.Tooltip = require("./js/tooltip.jsx");
window.WindowDrag = require("./js/window-drag.jsx");
+window.CopyButton = require("./js/copy-button.jsx");
// Create a for each example.
var examples = document.querySelectorAll('div.example_div');
diff --git a/template.html b/template.html
index a95e301..6cf3cb7 100644
--- a/template.html
+++ b/template.html
@@ -161,6 +161,7 @@ Components
Timeout Transition Group
Backbone Mixin
Window Drag
+ Copy to clipboard button
@@ -257,6 +258,12 @@ Window Drag
Detect drags into and out of the page.
{% code_example "window-drag.jsx" %}
+
+ {{ stability("experimental") }}
+ {{ depends([]) }}
+ Copies input, textarea or other text content to clipboard.
+ {% code_example "copy-button.jsx" %}
+
Timeout Transition Group
{{ stability("unstable") }}
{{ depends([("http://jquery.com/", "jQuery")]) }}