From 6254179d0c82df53a62dad0a5f3faa4f2223e497 Mon Sep 17 00:00:00 2001
From: Ariya Hidayat <ariya@metabase.com>
Date: Wed, 20 Oct 2021 07:07:31 -0700
Subject: [PATCH] For reduced motion, snap the motion translation (#18564)

Also, in case reduced motion is preferred, increase the spring stiffness
of the motion to finish the transition faster (timing-wide, the actual
animation won't appear due to the above snapping).
---
 frontend/src/metabase/lib/dom.js              |  5 +++
 .../notebook/NotebookStepPreview.jsx          | 37 +++++++++++++------
 .../query_builder/components/view/View.jsx    | 32 +++++++++++-----
 3 files changed, 53 insertions(+), 21 deletions(-)

diff --git a/frontend/src/metabase/lib/dom.js b/frontend/src/metabase/lib/dom.js
index cd140a42362..820a3294ad8 100644
--- a/frontend/src/metabase/lib/dom.js
+++ b/frontend/src/metabase/lib/dom.js
@@ -426,3 +426,8 @@ export function isEventOverElement(event, element) {
 
   return y >= top && y <= bottom && x >= left && x <= right;
 }
+
+export function isReducedMotionPreferred() {
+  const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
+  return mediaQuery && mediaQuery.matches;
+}
diff --git a/frontend/src/metabase/query_builder/components/notebook/NotebookStepPreview.jsx b/frontend/src/metabase/query_builder/components/notebook/NotebookStepPreview.jsx
index a418235c108..e340b2cd24d 100644
--- a/frontend/src/metabase/query_builder/components/notebook/NotebookStepPreview.jsx
+++ b/frontend/src/metabase/query_builder/components/notebook/NotebookStepPreview.jsx
@@ -4,6 +4,8 @@ import React from "react";
 import cx from "classnames";
 import _ from "underscore";
 
+import { isReducedMotionPreferred } from "metabase/lib/dom";
+
 import Icon from "metabase/components/Icon";
 import Button from "metabase/components/Button";
 import { Box, Flex } from "grid-styled";
@@ -47,6 +49,11 @@ class NotebookStepPreview extends React.Component {
 
     const isDirty = this.getIsDirty();
 
+    const preferReducedMotion = isReducedMotionPreferred();
+    const springOpts = preferReducedMotion
+      ? { stiffness: 500 }
+      : { stiffness: 170 };
+
     return (
       <Box pt={2}>
         <Flex align="center" justify="space-between" mb={1}>
@@ -72,18 +79,26 @@ class NotebookStepPreview extends React.Component {
             {({ rawSeries, result }) => (
               <Motion
                 defaultStyle={{ height: 36 }}
-                style={{ height: spring(getPreviewHeightForResult(result)) }}
+                style={{
+                  height: spring(getPreviewHeightForResult(result), springOpts),
+                }}
               >
-                {({ height }) => (
-                  <Visualization
-                    rawSeries={rawSeries}
-                    error={result && result.error}
-                    className={cx("bordered shadowed rounded bg-white", {
-                      p2: result && result.error,
-                    })}
-                    style={{ minHeight: height }}
-                  />
-                )}
+                {({ height }) => {
+                  const targetHeight = getPreviewHeightForResult(result);
+                  const snapHeight =
+                    height > targetHeight / 2 ? targetHeight : 0;
+                  const minHeight = preferReducedMotion ? snapHeight : height;
+                  return (
+                    <Visualization
+                      rawSeries={rawSeries}
+                      error={result && result.error}
+                      className={cx("bordered shadowed rounded bg-white", {
+                        p2: result && result.error,
+                      })}
+                      style={{ minHeight }}
+                    />
+                  );
+                }}
               </Motion>
             )}
           </QuestionResultLoader>
diff --git a/frontend/src/metabase/query_builder/components/view/View.jsx b/frontend/src/metabase/query_builder/components/view/View.jsx
index a1eda306792..29f9ba04d72 100644
--- a/frontend/src/metabase/query_builder/components/view/View.jsx
+++ b/frontend/src/metabase/query_builder/components/view/View.jsx
@@ -4,6 +4,8 @@ import { t } from "ttag";
 
 import cx from "classnames";
 
+import { isReducedMotionPreferred } from "metabase/lib/dom";
+
 import ExplicitSize from "metabase/components/ExplicitSize";
 import Popover from "metabase/components/Popover";
 import DebouncedFrame from "metabase/components/DebouncedFrame";
@@ -193,6 +195,14 @@ export default class View extends React.Component {
 
     const isSidebarOpen = leftSideBar || rightSideBar;
 
+    const MOTION_Y = -100;
+
+    const preferReducedMotion = isReducedMotionPreferred();
+
+    const springOpts = preferReducedMotion
+      ? { stiffness: 500 }
+      : { stiffness: 170 };
+
     return (
       <div className={fitClassNames}>
         <div className={cx("QueryBuilder flex flex-column bg-white spread")}>
@@ -226,34 +236,36 @@ export default class View extends React.Component {
                 defaultStyle={
                   isNewQuestion
                     ? { opacity: 1, translateY: 0 }
-                    : { opacity: 0, translateY: -100 }
+                    : { opacity: 0, translateY: MOTION_Y }
                 }
                 style={
                   queryBuilderMode === "notebook"
                     ? {
-                        opacity: spring(1),
-                        translateY: spring(0),
+                        opacity: spring(1, springOpts),
+                        translateY: spring(0, springOpts),
                       }
                     : {
-                        opacity: spring(0),
-                        translateY: spring(-100),
+                        opacity: spring(0, springOpts),
+                        translateY: spring(MOTION_Y, springOpts),
                       }
                 }
               >
-                {({ opacity, translateY }) =>
-                  opacity > 0 ? (
+                {({ opacity, translateY }) => {
+                  const snapY = translateY < MOTION_Y / 2 ? MOTION_Y : 0;
+                  const shiftY = preferReducedMotion ? snapY : translateY;
+                  return opacity > 0 ? (
                     // note the `bg-white class here is necessary to obscure the other layer
                     <div
                       className="spread bg-white scroll-y z2 border-top border-bottom"
                       style={{
                         // opacity: opacity,
-                        transform: `translateY(${translateY}%)`,
+                        transform: `translateY(${shiftY}%)`,
                       }}
                     >
                       <Notebook {...this.props} />
                     </div>
-                  ) : null
-                }
+                  ) : null;
+                }}
               </Motion>
             )}
 
-- 
GitLab