From 186c803c31e111aeb98ba36a5edf9c66ea2b13a7 Mon Sep 17 00:00:00 2001 From: Paul Sealock Date: Wed, 18 Jul 2018 15:00:20 +1200 Subject: [PATCH] FilterPicker: Add Animation --- .../components/animation-slider/README.md | 69 ++++++++++++++++++ .../components/animation-slider/index.js | 71 +++++++++++++++++++ .../components/animation-slider/style.scss | 71 +++++++++++++++++++ .../client/components/filter-picker/index.js | 53 +++++++------- .../components/filter-picker/style.scss | 46 ++++++------ plugins/woocommerce-admin/package-lock.json | 32 +++++++++ plugins/woocommerce-admin/package.json | 3 +- 7 files changed, 293 insertions(+), 52 deletions(-) create mode 100644 plugins/woocommerce-admin/client/components/animation-slider/README.md create mode 100644 plugins/woocommerce-admin/client/components/animation-slider/index.js create mode 100644 plugins/woocommerce-admin/client/components/animation-slider/style.scss diff --git a/plugins/woocommerce-admin/client/components/animation-slider/README.md b/plugins/woocommerce-admin/client/components/animation-slider/README.md new file mode 100644 index 00000000000..321c4fd7525 --- /dev/null +++ b/plugins/woocommerce-admin/client/components/animation-slider/README.md @@ -0,0 +1,69 @@ +AnimationSlider +============ + +This component creates slideable content controlled by an animate prop to direct the contents to slide left or right + +## How to use: + +```jsx +import AnimationSlider from 'components/animation-slider'; + +class MySlider extends Component { + constructor() { + super(); + this.state = { + pages: [ 44, 55, 66, 77, 88 ], + page: 0, + animate: null, + }; + this.forward = this.forward.bind( this ); + this.back = this.forward.back( this ); + } + + forward() { + this.setState( state => ( { + page: state.page + 1, + animate: 'left', + } ) ); + } + + back() { + this.setState( state => ( { + page: state.page - 1, + animate: 'right', + } ) ); + } + + render() { + const { page, pages, animate } = this.state; + return ( +
+ + + + { status => ( + { + ) } + +
+ ); + } +} +``` + +## `AnimationSlider` Props + +* `children` (required): A function returning rendered content with argument status, reflecting `CSSTransition` status +* `animationKey` (required): A unique identifier for each slideable page +* `animate`: null, 'left', 'right', to designate which direction to slide on a change +* `focusOnChange`: When set to true, the first focusable element will be focused after an animation has finished + +All other props are passed to `CSSTransition`. More info at http://reactcommunity.org/react-transition-group/css-transition diff --git a/plugins/woocommerce-admin/client/components/animation-slider/index.js b/plugins/woocommerce-admin/client/components/animation-slider/index.js new file mode 100644 index 00000000000..8d116602067 --- /dev/null +++ b/plugins/woocommerce-admin/client/components/animation-slider/index.js @@ -0,0 +1,71 @@ +/** @format */ +/** + * External dependencies + */ +import { Component, createRef } from '@wordpress/element'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +/** + * Internal dependencies + */ +import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import './style.scss'; + +class AnimationSlider extends Component { + constructor() { + super(); + this.state = { + animate: null, + }; + this.container = createRef(); + this.onExited = this.onExited.bind( this ); + } + + onExited() { + const { onExited, focusOnChange } = this.props; + if ( onExited ) { + onExited(); + } + if ( focusOnChange ) { + const focusable = this.container.current.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + if ( focusable ) { + focusable.focus(); + } + } + } + + render() { + const { children, animationKey, animate } = this.props; + const containerClasses = classnames( + 'woocommerce-slide-animation', + animate && `animate-${ animate }` + ); + return ( +
+ + + { status => children( { status } ) } + + +
+ ); + } +} + +AnimationSlider.propTypes = { + children: PropTypes.func.isRequired, + animationKey: PropTypes.any.isRequired, + animate: PropTypes.oneOf( [ null, 'left', 'right' ] ), + focusOnChange: PropTypes.bool, +}; + +export default AnimationSlider; diff --git a/plugins/woocommerce-admin/client/components/animation-slider/style.scss b/plugins/woocommerce-admin/client/components/animation-slider/style.scss new file mode 100644 index 00000000000..cd5f449b3c1 --- /dev/null +++ b/plugins/woocommerce-admin/client/components/animation-slider/style.scss @@ -0,0 +1,71 @@ +/** @format */ + +@keyframes slide-in-left { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(0); + } +} + +@keyframes slide-out-left { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(-200%); + } +} + +@keyframes slide-in-right { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(0); + } +} + +@keyframes slide-out-right { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(0%); + } +} + +$duration: 200ms; + +/** +The CSSTransition element creates a containing div without a class + */ +.woocommerce-slide-animation { + & > div { + width: 100%; + white-space: nowrap; + overflow: hidden; + display: flex; + } + + &.animate-left .slide-enter-active { + animation: slide-in-left; + animation-duration: $duration; + } + + &.animate-left .slide-exit-active { + animation: slide-out-left; + animation-duration: $duration; + } + + &.animate-right .slide-enter-active { + animation: slide-in-right; + animation-duration: $duration; + } + + &.animate-right .slide-exit-active { + animation: slide-out-right; + animation-duration: $duration; + } +} diff --git a/plugins/woocommerce-admin/client/components/filter-picker/index.js b/plugins/woocommerce-admin/client/components/filter-picker/index.js index edc70a73dbf..e2f5067fa00 100644 --- a/plugins/woocommerce-admin/client/components/filter-picker/index.js +++ b/plugins/woocommerce-admin/client/components/filter-picker/index.js @@ -3,7 +3,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, Fragment } from '@wordpress/element'; import { Dropdown, Button, Dashicon } from '@wordpress/components'; import { stringify as stringifyQueryObject } from 'qs'; import { omit, find, partial } from 'lodash'; @@ -14,6 +14,7 @@ import PropTypes from 'prop-types'; * Internal dependencies */ import DropdownButton from 'components/dropdown-button'; +import AnimationSlider from 'components/animation-slider'; import Link from 'components/link'; import './style.scss'; @@ -24,8 +25,8 @@ class FilterPicker extends Component { const { filterPaths, getQueryParamValue } = props; this.state = { nav: filterPaths[ getQueryParamValue() ], + animate: null, }; - this.listRef = createRef(); this.getSelectionPath = this.getSelectionPath.bind( this ); this.getOtherQueries = this.getOtherQueries.bind( this ); @@ -66,8 +67,7 @@ class FilterPicker extends Component { selectSubFilters( value ) { const nav = [ ...this.state.nav ]; nav.push( value ); - this.setState( { nav } ); - this.focusFirstFilter(); + this.setState( { nav, animate: 'left' } ); } getVisibleFilters( filters, nav ) { @@ -82,17 +82,7 @@ class FilterPicker extends Component { goBack() { const nav = [ ...this.state.nav ]; nav.pop(); - this.setState( { nav } ); - this.focusFirstFilter(); - } - - focusFirstFilter() { - setTimeout( () => { - const list = this.listRef.current; - if ( list.children.length && list.children[ 0 ].children.length ) { - list.children[ 0 ].children[ 0 ].focus(); - } - }, 0 ); + this.setState( { nav, animate: 'right' } ); } renderButton( filter, onClose ) { @@ -129,7 +119,7 @@ class FilterPicker extends Component { return ( { filter.label } @@ -139,7 +129,8 @@ class FilterPicker extends Component { render() { const { filters } = this.props; - const visibleFilters = this.getVisibleFilters( filters, [ ...this.state.nav ] ); + const { nav, animate } = this.state; + const visibleFilters = this.getVisibleFilters( filters, [ ...nav ] ); const selectedFilter = this.getSelectedFilter(); return (
@@ -157,18 +148,22 @@ class FilterPicker extends Component { /> ) } renderContent={ ( { onClose } ) => ( - + + { () => ( +
    + { visibleFilters.map( filter => ( +
  • + { this.renderButton( filter, onClose ) } +
  • + ) ) } +
+ ) } +
) } />
diff --git a/plugins/woocommerce-admin/client/components/filter-picker/style.scss b/plugins/woocommerce-admin/client/components/filter-picker/style.scss index 8c093b95e8a..71995d2ecf2 100644 --- a/plugins/woocommerce-admin/client/components/filter-picker/style.scss +++ b/plugins/woocommerce-admin/client/components/filter-picker/style.scss @@ -42,33 +42,35 @@ .woocommerce-filter-picker__content-list { margin: 0; + width: 100%; + min-width: 100%; +} - .woocommerce-filter-picker__content-list-item { - border-bottom: 1px solid $core-grey-light-700; - margin: 0; +.woocommerce-filter-picker__content-list-item { + border-bottom: 1px solid $core-grey-light-700; + margin: 0; - &:last-child { - border-bottom: none; - } + &:last-child { + border-bottom: none; + } - &.is-selected { - .woocommerce-filter-picker__content-list-item-btn { + &.is-selected { + .woocommerce-filter-picker__content-list-item-btn { + background-color: $white; + + &.components-button:not(:disabled):not([aria-disabled='true']):focus { background-color: $white; + } - &.components-button:not(:disabled):not([aria-disabled='true']):focus { - background-color: $white; - } - - &::before { - content: ''; - width: 8px; - height: 8px; - background-color: $woocommerce; - position: absolute; - top: 50%; - left: 1em; - transform: translate(50%, -50%); - } + &::before { + content: ''; + width: 8px; + height: 8px; + background-color: $woocommerce; + position: absolute; + top: 50%; + left: 1em; + transform: translate(50%, -50%); } } } diff --git a/plugins/woocommerce-admin/package-lock.json b/plugins/woocommerce-admin/package-lock.json index ec9a7c41f8d..c50fdbb29c8 100644 --- a/plugins/woocommerce-admin/package-lock.json +++ b/plugins/woocommerce-admin/package-lock.json @@ -4772,6 +4772,11 @@ "esutils": "^2.0.2" } }, + "dom-helpers": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", + "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" + }, "dom-react": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-react/-/dom-react-2.2.1.tgz", @@ -13817,6 +13822,11 @@ "integrity": "sha512-xpb0PpALlFWNw/q13A+1aHeyJyLYCg0/cCHPUA43zYluZuIPHaHL3k8OBsTgQtxqW0FhyDEMvi8fZ/+7+r4OSQ==", "dev": true }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-moment-proptypes": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/react-moment-proptypes/-/react-moment-proptypes-1.6.0.tgz", @@ -13915,6 +13925,28 @@ "react-is": "^16.4.1" } }, + "react-transition-group": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz", + "integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==", + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "prop-types": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "requires": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + } + } + }, "react-with-direction": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.3.0.tgz", diff --git a/plugins/woocommerce-admin/package.json b/plugins/woocommerce-admin/package.json index 61a2dd410aa..544dbc03d55 100755 --- a/plugins/woocommerce-admin/package.json +++ b/plugins/woocommerce-admin/package.json @@ -78,6 +78,7 @@ "lodash": "^4.17.10", "prop-types": "^15.6.1", "react-dates": "^16.7.0", - "react-slot-fill": "^2.0.1" + "react-slot-fill": "^2.0.1", + "react-transition-group": "^2.4.0" } }