Show cutesy animation when copy to clipboard succeeds
authorGreg Hurrell <greg@hurrell.net>
Mon, 3 Aug 2015 23:11:38 +0000 (16:11 -0700)
committerGreg Hurrell <greg@hurrell.net>
Mon, 3 Aug 2015 23:11:38 +0000 (16:11 -0700)
package.json
src/App.css
src/Field.react.js

index b960db7a80c7e9c1fdae47f8a01b64bd618a7a16..e2bd79dbbe61b238fd05028c5b3f821a46376701 100644 (file)
@@ -41,6 +41,7 @@
     "webpack-dev-server": "^1.8.2"
   },
   "dependencies": {
+    "classnames": "^2.1.3",
     "react": "^0.13.0"
   }
 }
index ca6cea4fbb3304c24a9c4d5a1ec7ea7d1c500f9f..1656dad73a6dae2ea17106af8c1b0a7bd2d3d2d8 100644 (file)
@@ -21,6 +21,7 @@ body {
   flex-grow: 5;
   font-family: monospace;
   font-size: 12px;
+  position: relative;
 }
 
 .hextrapolate-field input {
@@ -52,6 +53,7 @@ body {
   background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj48cGF0aCBmaWxsPSIjNDQ0IiBkPSJNMjAgOFYwSDZMMCA2djE4aDEydjhoMjBWOEgyMHpNNiAyLjgyOFY2SDIuODI4TDYgMi44Mjh6TTIgMjJWOGg2VjJoMTB2NmwtNiA2djhIMnptMTYtMTEuMTcyVjE0aC0zLjE3MkwxOCAxMC44Mjh6TTMwIDMwSDE0VjE2aDZ2LTZoMTB2MjB6Ii8+PC9zdmc+);
   background-repeat: no-repeat;
   background-size: 16px 16px;
+  cursor: pointer;
   display: inline-block;
   height: 16px;
   margin-left: 8px;
@@ -59,5 +61,23 @@ body {
   position: relative;
   text-indent: -10000px;
   top: 4px;
+  user-select: none;
   width: 16px;
 }
+
+.hextrapolate-copy-status {
+  color: #0f0;
+  opacity: 0;
+  position: absolute;
+  top: 4px;
+  right: 4px;
+  transform: scale(1);
+  transition: transform .25s, opacity .25s;
+  user-select: none;
+}
+
+.hextrapolate-copy-success {
+  transform: scale(5);
+  opacity: .5;
+  transition: transform .5s, opacity .5s;
+}
index 912a2aee80e88ec52e9ee9acef1ca7ab7e8ff9f4..a629d30362cb1ea48e10d20f91e5a44da6196e07 100644 (file)
@@ -9,6 +9,7 @@
 
 import React from 'react';
 import convert from './convert';
+import cx from 'classnames';
 
 export type Value = {
   base: number;
@@ -50,6 +51,7 @@ export default class Field extends React.Component {
         `base prop must be between 2..${DIGITS.length}`
       );
     }
+    this.state = {copySucceeded: false};
   }
 
   _isValid(value: string): boolean {
@@ -69,14 +71,20 @@ export default class Field extends React.Component {
     }
   }
 
-  _onCopy = () => {
+  _onCopy = event => {
+    event.preventDefault();
+    event.stopPropagation();
     React.findDOMNode(this._input).select();
 
     // May throw a SecurityError.
     try {
-      document.execCommand('copy');
+      this.setState({
+        copySucceeded: document.execCommand('copy'),
+      });
+      setTimeout(() => this.setState({copySucceeded: false}), 750);
     } catch(error) { // eslint-disable-line no-empty
       // Swallow.
+      this.setState({copySucceeded: false});
     }
   }
 
@@ -95,6 +103,14 @@ export default class Field extends React.Component {
     );
   }
 
+  _copyStatus() {
+    const classNames = cx({
+      'hextrapolate-copy-status': true,
+      'hextrapolate-copy-success': this.state.copySucceeded,
+    });
+    return <span className={classNames}>&#x2713;</span>;
+  }
+
   focus() {
     React.findDOMNode(this._input).focus();
   }
@@ -108,6 +124,7 @@ export default class Field extends React.Component {
           type="text"
           value={fromValue(this.props.value, this.props.base)}
         />
+        {this._copyStatus()}
         {this._copyLink()}
       </span>
     );