From d352673d2842c30ae4a496f05dc6ae2b7b0d0a93 Mon Sep 17 00:00:00 2001 From: David Chin Date: Tue, 20 Aug 2019 15:35:48 +1000 Subject: [PATCH] refactor(checkout): CHECKOUT-4338 Use media query component to conditionally render checkout step --- src/app/checkout/CheckoutStep.spec.tsx | 11 +++- src/app/checkout/CheckoutStep.tsx | 90 +++++++++++++++----------- 2 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/app/checkout/CheckoutStep.spec.tsx b/src/app/checkout/CheckoutStep.spec.tsx index 188f65fe51..75e465ab27 100644 --- a/src/app/checkout/CheckoutStep.spec.tsx +++ b/src/app/checkout/CheckoutStep.spec.tsx @@ -1,7 +1,10 @@ import { mount } from 'enzyme'; +import { noop } from 'lodash'; import React from 'react'; import { CSSTransition } from 'react-transition-group'; +import { MOBILE_MAX_WIDTH } from '../ui/responsive'; + import CheckoutStep, { CheckoutStepProps } from './CheckoutStep'; import CheckoutStepHeader from './CheckoutStepHeader'; import CheckoutStepType from './CheckoutStepType'; @@ -25,7 +28,13 @@ describe('CheckoutStep', () => { window.scrollTo = jest.fn(); // Mock `matchMedia` to detect mobile viewport - window.matchMedia = jest.fn(query => ({ matches: query === '(max-width: 968px)' ? isMobile : false }) as MediaQueryList); + window.matchMedia = jest.fn(query => ({ + matches: query === `(max-width: ${MOBILE_MAX_WIDTH}px)` ? isMobile : false, + addListener: noop, + addEventListener: noop, + removeListener: noop, + removeEventListener: noop, + }) as MediaQueryList); }); afterEach(() => { diff --git a/src/app/checkout/CheckoutStep.tsx b/src/app/checkout/CheckoutStep.tsx index ea7dc37a7c..9539dc1e6f 100644 --- a/src/app/checkout/CheckoutStep.tsx +++ b/src/app/checkout/CheckoutStep.tsx @@ -1,8 +1,10 @@ import classNames from 'classnames'; import { noop } from 'lodash'; -import React, { createRef, Component, ReactNode, RefObject } from 'react'; +import React, { createRef, Component, ReactNode } from 'react'; import { CSSTransition } from 'react-transition-group'; +import { isMobileView, MobileView } from '../ui/responsive'; + import CheckoutStepHeader from './CheckoutStepHeader'; import CheckoutStepType from './CheckoutStepType'; @@ -17,13 +19,11 @@ export interface CheckoutStepProps { onEdit?(step: CheckoutStepType): void; } -const LARGE_SCREEN_BREAKPOINT = 968; -const LARGE_SCREEN_ANIMATION_DELAY = 610; - export default class CheckoutStep extends Component { - private containerRef = createRef(); - private mobileQuery = window.matchMedia(`(max-width: ${LARGE_SCREEN_BREAKPOINT}px)`); + private containerRef = createRef(); + private contentRef = createRef(); private timeoutRef?: number; + private timeoutDelay?: number; componentDidMount(): void { const { isActive } = this.props; @@ -67,7 +67,7 @@ export default class CheckoutStep extends Component { 'optimizedCheckout-checkoutStep', { [`checkout-step--${type}`]: !!type } ) } - ref={ this.containerRef as RefObject } + ref={ this.containerRef } >
{ private renderContent(): ReactNode { const { children, isActive } = this.props; - if (this.mobileQuery.matches) { - if (!isActive) { - return null; - } - - return ( -
- { children } -
- ); - } - - return ( - { - node.addEventListener('transitionend', ({ target }) => { - if (target === node) { - done(); - } - }); + return <> + + { matched => { + if (matched) { + return !isActive ? null :
+ { children } +
; + } + + return { + node.addEventListener('transitionend', ({ target }) => { + if (target === node) { + done(); + } + }); + } } + classNames="checkout-view-content" + timeout={ {} } + in={ isActive } + unmountOnExit + mountOnEnter + > +
+ { children } +
+
; } } - classNames="checkout-view-content" - timeout={ {} } - in={ isActive } - unmountOnExit - mountOnEnter - > -
- { children } -
-
- ); + + ; } private focusStep(): void { - const delay = this.mobileQuery.matches ? 0 : LARGE_SCREEN_ANIMATION_DELAY; + const delay = isMobileView() ? 0 : this.getTransitionDelay(); this.timeoutRef = window.setTimeout(() => { const input = this.getChildInput(); @@ -187,4 +188,15 @@ export default class CheckoutStep extends Component { return this.containerRef.current ? this.containerRef.current : undefined; } + + private getTransitionDelay(): number { + if (this.timeoutDelay !== undefined) { + return this.timeoutDelay; + } + + // Cache the result to avoid unnecessary reflow + this.timeoutDelay = parseFloat(this.contentRef.current ? getComputedStyle(this.contentRef.current).transitionDuration : '0s') * 1000; + + return this.timeoutDelay; + } }