Bug 1266414 - device modal fades in/out, r=jryans
☠☠ backed out by 829e9870eb1d ☠ ☠
author"Helen V. Holmes" <helen.v.holmes@gmail.com>
Thu, 07 Jul 2016 06:32:00 +0200
changeset 304588 d192030869ef289c9fc5e9bdfcea37f2a24f7967
parent 304587 3cfd5be71eed88f862b030c7bb50066d4a05b060
child 304589 a53b203b8cbafe17b3335a859866e900a7e1ed2c
push id19956
push usercbook@mozilla.com
push dateTue, 12 Jul 2016 09:55:19 +0000
treeherderfx-team@b798e05334be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjryans
bugs1266414
milestone50.0a1
Bug 1266414 - device modal fades in/out, r=jryans Device modal can be closed with escape and outside click
devtools/client/responsive.html/components/device-modal.js
devtools/client/responsive.html/index.css
--- a/devtools/client/responsive.html/components/device-modal.js
+++ b/devtools/client/responsive.html/components/device-modal.js
@@ -20,30 +20,38 @@ module.exports = createClass({
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {};
   },
 
+  componentDidMount() {
+    window.addEventListener("keydown", this.onKeyDown, true);
+  },
+
   componentWillReceiveProps(nextProps) {
     let {
       devices,
     } = nextProps;
 
     for (let type of devices.types) {
       for (let device of devices[type]) {
         this.setState({
           [device.name]: device.displayed,
         });
       }
     }
   },
 
+  componentWillUnmount() {
+    window.removeEventListener("keydown", this.onKeyDown, true);
+  },
+
   onDeviceCheckboxClick({ target }) {
     this.setState({
       [target.value]: !this.state[target.value]
     });
   },
 
   onDeviceModalSubmit() {
     let {
@@ -73,80 +81,99 @@ module.exports = createClass({
         }
       }
     }
 
     onDeviceListUpdate(preferredDevices);
     onUpdateDeviceModalOpen(false);
   },
 
+  onKeyDown(event) {
+    if (!this.props.devices.isModalOpen) {
+      return;
+    }
+    // Escape keycode
+    if (event.keyCode === 27) {
+      let {
+        onUpdateDeviceModalOpen
+      } = this.props;
+      onUpdateDeviceModalOpen(false);
+    }
+  },
+
   render() {
     let {
       devices,
       onUpdateDeviceModalOpen,
     } = this.props;
 
-    let modalClass = "device-modal container";
-
-    if (!devices.isModalOpen) {
-      modalClass += " hidden";
-    }
-
     const sortedDevices = {};
     for (let type of devices.types) {
       sortedDevices[type] = Object.assign([], devices[type])
         .sort((a, b) => a.name.localeCompare(b.name));
     }
 
     return dom.div(
       {
-        className: modalClass,
+        id: "device-modal-wrapper",
+        className: this.props.devices.isModalOpen ? "opened" : "closed",
       },
-      dom.button({
-        id: "device-close-button",
-        className: "toolbar-button devtools-button",
-        onClick: () => onUpdateDeviceModalOpen(false),
-      }),
       dom.div(
         {
-          className: "device-modal-content",
+          className: "device-modal container",
         },
-        devices.types.map(type => {
-          return dom.div(
-            {
-              className: "device-type",
-              key: type,
-            },
-            dom.header(
+        dom.button({
+          id: "device-close-button",
+          className: "toolbar-button devtools-button",
+          onClick: () => onUpdateDeviceModalOpen(false),
+        }),
+        dom.div(
+          {
+            className: "device-modal-content",
+          },
+          devices.types.map(type => {
+            return dom.div(
               {
-                className: "device-header",
+                className: "device-type",
+                key: type,
               },
-              type
-            ),
-            sortedDevices[type].map(device => {
-              return dom.label(
+              dom.header(
                 {
-                  className: "device-label",
-                  key: device.name,
+                  className: "device-header",
                 },
-                dom.input({
-                  className: "device-input-checkbox",
-                  type: "checkbox",
-                  value: device.name,
-                  checked: this.state[device.name],
-                  onChange: this.onDeviceCheckboxClick,
-                }),
-                device.name
-              );
-            })
-          );
-        })
+                type
+              ),
+              sortedDevices[type].map(device => {
+                return dom.label(
+                  {
+                    className: "device-label",
+                    key: device.name,
+                  },
+                  dom.input({
+                    className: "device-input-checkbox",
+                    type: "checkbox",
+                    value: device.name,
+                    checked: this.state[device.name],
+                    onChange: this.onDeviceCheckboxClick,
+                  }),
+                  device.name
+                );
+              })
+            );
+          })
+        ),
+        dom.button(
+          {
+            id: "device-submit-button",
+            onClick: this.onDeviceModalSubmit,
+          },
+          getStr("responsive.done")
+        )
       ),
-      dom.button(
+      dom.div(
         {
-          id: "device-submit-button",
-          onClick: this.onDeviceModalSubmit,
-        },
-        getStr("responsive.done")
+          className: "modal-overlay",
+          onClick: () => onUpdateDeviceModalOpen(false),
+        }
       )
     );
   },
 });
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -34,30 +34,29 @@
 }
 
 * {
   box-sizing: border-box;
 }
 
 #root,
 html, body {
+  height: 100%;
   margin: 0;
 }
 
 #app {
   /* Center the viewports container */
   display: flex;
   align-items: center;
   flex-direction: column;
-  height: 100vh;
-
-  /* Snap to the top of the app when there isn't enough vertical space anymore
-     to center the viewports (so we don't lose the global toolbar) */
-  position: sticky;
-  top: 0;
+  padding-top: 15px;
+  padding-bottom: 1%;
+  position: relative;
+  height: 100%;
 }
 
 /**
  * Common style for containers and toolbar buttons
  */
 
 .container {
   background-color: var(--theme-toolbar-background);
@@ -81,17 +80,17 @@ html, body {
 /**
  * Global Toolbar
  */
 
 #global-toolbar {
   color: var(--theme-body-color-alt);
   border-radius: 2px;
   box-shadow: var(--rdm-box-shadow);
-  margin: 10% 0 30px 0;
+  margin: 0 0 15px 0;
   padding: 4px 5px;
   display: inline-flex;
   -moz-user-select: none;
 }
 
 #global-toolbar > .title {
   border-right: 1px solid var(--theme-splitter-color);
   padding: 1px 6px 0 2px;
@@ -331,31 +330,76 @@ html, body {
 .viewport-dimension-separator {
   -moz-user-select: none;
 }
 
 /**
  * Device Modal
  */
 
+@keyframes fade-in-and-up {
+  0% {
+    opacity: 0;
+    transform: translateY(5px);
+  }
+  100% {
+    opacity: 1;
+    transform: translateY(0px);
+  }
+}
+
+@keyframes fade-down-and-out {
+  0% {
+    opacity: 1;
+    transform: translateY(0px);
+  }
+  100% {
+    opacity: 0;
+    transform: translateY(5px);
+    visibility: hidden;
+  }
+}
+
 .device-modal {
   border-radius: 2px;
   box-shadow: var(--rdm-box-shadow);
+  display: none;
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   width: 642px;
   height: 612px;
+  z-index: 1;
+}
+
+/* Handles the opening/closing of the modal */
+#device-modal-wrapper.opened .device-modal {
+  animation: fade-in-and-up 0.3s ease;
+  animation-fill-mode: forwards;
+  display: block;
 }
 
-.device-modal.hidden {
-  display: none;
+#device-modal-wrapper.closed .device-modal {
+  animation: fade-down-and-out 0.3s ease;
+  animation-fill-mode: forwards;
+  display: block;
+}
+
+#device-modal-wrapper.opened .modal-overlay {
+  background-color: var(--theme-splitter-color);
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+  z-index: 0;
+  opacity: 0.5;
 }
 
 .device-modal-content {
   display: flex;
   flex-direction: column;
   flex-wrap: wrap;
   overflow: auto;
   height: 550px;