diff --git a/client/analytics/settings/historical-data/index.js b/client/analytics/settings/historical-data/index.js index f8f50fb62d5..920cd180efd 100644 --- a/client/analytics/settings/historical-data/index.js +++ b/client/analytics/settings/historical-data/index.js @@ -51,13 +51,13 @@ class HistoricalData extends Component { } makeQuery( path, errorMessage ) { - const { createNotice } = this.props; + const { addNotice } = this.props; apiFetch( { path, method: 'POST' } ) .then( response => { if ( 'success' === response.status ) { - createNotice( 'success', response.message ); + addNotice( { status: 'success', message: response.message } ); } else { - createNotice( 'error', errorMessage ); + addNotice( { status: 'error', message: errorMessage } ); this.setState( { activeImport: false, lastImportStopTimestamp: Date.now(), @@ -66,7 +66,7 @@ class HistoricalData extends Component { } ) .catch( error => { if ( error && error.message ) { - createNotice( 'error', error.message ); + addNotice( { status: 'error', message: error.message } ); this.setState( { activeImport: false, lastImportStopTimestamp: Date.now(), diff --git a/client/analytics/settings/index.js b/client/analytics/settings/index.js index 6e396e2cd73..44fe86a56a8 100644 --- a/client/analytics/settings/index.js +++ b/client/analytics/settings/index.js @@ -84,22 +84,25 @@ class Settings extends Component { }; componentDidUpdate() { - const { createNotice, isError, isRequesting } = this.props; + const { addNotice, isError, isRequesting } = this.props; const { saving, isDirty } = this.state; let newIsDirtyState = isDirty; if ( saving && ! isRequesting ) { if ( ! isError ) { - createNotice( - 'success', - __( 'Your settings have been successfully saved.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'success', + message: __( 'Your settings have been successfully saved.', 'woocommerce-admin' ), + } ); newIsDirtyState = false; } else { - createNotice( - 'error', - __( 'There was an error saving your settings. Please try again.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( + 'There was an error saving your settings. Please try again.', + 'woocommerce-admin' + ), + } ); } /* eslint-disable react/no-did-update-set-state */ this.setState( { saving: false, isDirty: newIsDirtyState } ); @@ -148,7 +151,7 @@ class Settings extends Component { } render() { - const { createNotice } = this.props; + const { addNotice } = this.props; const { hasError } = this.state; if ( hasError ) { return null; @@ -175,7 +178,7 @@ class Settings extends Component { - + ); } @@ -192,11 +195,11 @@ export default compose( return { getSettings, isError, isRequesting, settings }; } ), withDispatch( dispatch => { - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice } = dispatch( 'wc-admin' ); const { updateSettings } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateSettings, }; } ) diff --git a/client/analytics/settings/setting.js b/client/analytics/settings/setting.js index bb782503cef..e22b4bdebcc 100644 --- a/client/analytics/settings/setting.js +++ b/client/analytics/settings/setting.js @@ -74,7 +74,7 @@ class Setting extends Component { }; handleInputCallback = () => { - const { createNotice, callback } = this.props; + const { addNotice, callback } = this.props; if ( 'function' !== typeof callback ) { return; @@ -82,7 +82,7 @@ class Setting extends Component { return new Promise( ( resolve, reject ) => { this.setState( { disabled: true } ); - callback( resolve, reject, createNotice ); + callback( resolve, reject, addNotice ); } ) .then( () => { this.setState( { disabled: false } ); @@ -197,7 +197,7 @@ Setting.propTypes = { export default compose( withDispatch( dispatch => { - const { createNotice } = dispatch( 'core/notices' ); - return { createNotice }; + const { addNotice } = dispatch( 'wc-admin' ); + return { addNotice }; } ) )( Setting ); diff --git a/client/dashboard/profile-wizard/index.js b/client/dashboard/profile-wizard/index.js index a1ecc423bae..b102d040841 100644 --- a/client/dashboard/profile-wizard/index.js +++ b/client/dashboard/profile-wizard/index.js @@ -93,7 +93,7 @@ class ProfileWizard extends Component { } async goToNextStep() { - const { createNotice, isError, updateProfileItems } = this.props; + const { addNotice, isError, updateProfileItems } = this.props; const currentStep = this.getCurrentStep(); const currentStepIndex = getSteps().findIndex( s => s.key === currentStep.key ); const nextStep = getSteps()[ currentStepIndex + 1 ]; @@ -102,10 +102,10 @@ class ProfileWizard extends Component { await updateProfileItems( { completed: true } ); if ( isError ) { - createNotice( - 'error', - __( 'There was a problem completing the profiler.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem completing the profiler.', 'woocommerce-admin' ), + } ); } return; } @@ -142,11 +142,10 @@ export default compose( return { isError }; } ), withDispatch( dispatch => { - const { updateProfileItems } = dispatch( 'wc-api' ); - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice, updateProfileItems } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateProfileItems, }; } ) diff --git a/client/dashboard/profile-wizard/steps/business-details.js b/client/dashboard/profile-wizard/steps/business-details.js index 8f17db6adaf..83556652ea4 100644 --- a/client/dashboard/profile-wizard/steps/business-details.js +++ b/client/dashboard/profile-wizard/steps/business-details.js @@ -40,7 +40,7 @@ class BusinessDetails extends Component { } async onContinue() { - const { createNotice, goToNextStep, isError, updateProfileItems } = this.props; + const { addNotice, goToNextStep, isError, updateProfileItems } = this.props; const { other_platform, product_count, selling_venues } = this.state; const extensions = keys( pickBy( this.state.extensions ) ); @@ -62,10 +62,10 @@ class BusinessDetails extends Component { if ( ! isError ) { goToNextStep(); } else { - createNotice( - 'error', - __( 'There was a problem updating your business details.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem updating your business details.', 'woocommerce-admin' ), + } ); } } @@ -316,11 +316,10 @@ export default compose( return { isError }; } ), withDispatch( dispatch => { - const { updateProfileItems } = dispatch( 'wc-api' ); - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice, updateProfileItems } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateProfileItems, }; } ) diff --git a/client/dashboard/profile-wizard/steps/industry.js b/client/dashboard/profile-wizard/steps/industry.js index f93a73ed1ee..011f4db2678 100644 --- a/client/dashboard/profile-wizard/steps/industry.js +++ b/client/dashboard/profile-wizard/steps/industry.js @@ -27,7 +27,7 @@ class Industry extends Component { } async onContinue() { - const { createNotice, goToNextStep, isError, updateProfileItems } = this.props; + const { addNotice, goToNextStep, isError, updateProfileItems } = this.props; recordEvent( 'storeprofiler_store_industry_continue', { store_industry: this.state.selected } ); await updateProfileItems( { industry: this.state.selected } ); @@ -35,10 +35,10 @@ class Industry extends Component { if ( ! isError ) { goToNextStep(); } else { - createNotice( - 'error', - __( 'There was a problem updating your industries.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem updating your industries.', 'woocommerce-admin' ), + } ); } } @@ -104,11 +104,10 @@ export default compose( return { isError }; } ), withDispatch( dispatch => { - const { updateProfileItems } = dispatch( 'wc-api' ); - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice, updateProfileItems } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateProfileItems, }; } ) diff --git a/client/dashboard/profile-wizard/steps/plugins.js b/client/dashboard/profile-wizard/steps/plugins.js index 60fae53fda6..af0affb7c99 100644 --- a/client/dashboard/profile-wizard/steps/plugins.js +++ b/client/dashboard/profile-wizard/steps/plugins.js @@ -119,7 +119,10 @@ class Plugins extends Component { } ); return pluginResponse; } catch ( err ) { - this.props.createNotice( 'error', this.getErrorMessage( action, plugin ) ); + this.props.addNotice( { + status: 'error', + message: this.getErrorMessage( action, plugin ), + } ); this.setState( { isPending: false, isError: true, @@ -138,7 +141,10 @@ class Plugins extends Component { } throw new Error(); } catch ( err ) { - this.props.createNotice( 'error', this.getErrorMessage( 'activate', 'jetpack' ) ); + this.props.addNotice( { + status: 'error', + message: this.getErrorMessage( 'activate', 'jetpack' ), + } ); this.setState( { isPending: false, isError: true, @@ -202,9 +208,9 @@ class Plugins extends Component { export default compose( withDispatch( dispatch => { - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice } = dispatch( 'wc-admin' ); return { - createNotice, + addNotice, }; } ) )( Plugins ); diff --git a/client/dashboard/profile-wizard/steps/product-types.js b/client/dashboard/profile-wizard/steps/product-types.js index 862d1a1e595..122ed7aa398 100644 --- a/client/dashboard/profile-wizard/steps/product-types.js +++ b/client/dashboard/profile-wizard/steps/product-types.js @@ -30,7 +30,7 @@ class ProductTypes extends Component { } async onContinue() { - const { createNotice, goToNextStep, isError, updateProfileItems } = this.props; + const { addNotice, goToNextStep, isError, updateProfileItems } = this.props; recordEvent( 'storeprofiler_store_product_type_continue', { product_type: this.state.selected, @@ -40,10 +40,10 @@ class ProductTypes extends Component { if ( ! isError ) { goToNextStep(); } else { - createNotice( - 'error', - __( 'There was a problem updating your product types.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem updating your product types.', 'woocommerce-admin' ), + } ); } } @@ -137,11 +137,10 @@ export default compose( return { isError }; } ), withDispatch( dispatch => { - const { updateProfileItems } = dispatch( 'wc-api' ); - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice, updateProfileItems } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateProfileItems, }; } ) diff --git a/client/dashboard/profile-wizard/steps/start/index.js b/client/dashboard/profile-wizard/steps/start/index.js index 7a2957791d9..eb478964b71 100644 --- a/client/dashboard/profile-wizard/steps/start/index.js +++ b/client/dashboard/profile-wizard/steps/start/index.js @@ -76,7 +76,7 @@ class Start extends Component { } async skipWizard() { - const { createNotice, isProfileItemsError, updateProfileItems, isSettingsError } = this.props; + const { addNotice, isProfileItemsError, updateProfileItems, isSettingsError } = this.props; recordEvent( 'storeprofiler_welcome_clicked', { proceed_without_install: true } ); @@ -84,15 +84,15 @@ class Start extends Component { await this.updateTracking(); if ( isProfileItemsError || isSettingsError ) { - createNotice( - 'error', - __( 'There was a problem updating your preferences.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem updating your preferences.', 'woocommerce-admin' ), + } ); } } async startWizard() { - const { createNotice, isSettingsError } = this.props; + const { addNotice, isSettingsError } = this.props; recordEvent( 'storeprofiler_welcome_clicked', { get_started: true } ); @@ -101,10 +101,10 @@ class Start extends Component { if ( ! isSettingsError ) { this.props.goToNextStep(); } else { - createNotice( - 'error', - __( 'There was a problem updating your preferences.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem updating your preferences.', 'woocommerce-admin' ), + } ); } } @@ -217,11 +217,10 @@ export default compose( return { getSettings, isSettingsError, isProfileItemsError, isSettingsRequesting }; } ), withDispatch( dispatch => { - const { updateProfileItems, updateSettings } = dispatch( 'wc-api' ); - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice, updateProfileItems, updateSettings } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateProfileItems, updateSettings, }; diff --git a/client/dashboard/profile-wizard/steps/store-details.js b/client/dashboard/profile-wizard/steps/store-details.js index 2dde6034827..51236a3d6d7 100644 --- a/client/dashboard/profile-wizard/steps/store-details.js +++ b/client/dashboard/profile-wizard/steps/store-details.js @@ -52,7 +52,7 @@ class StoreDetails extends Component { return; } - const { createNotice, goToNextStep, isError, updateSettings } = this.props; + const { addNotice, goToNextStep, isError, updateSettings } = this.props; const { addressLine1, addressLine2, city, countryState, postCode } = this.state; recordEvent( 'storeprofiler_store_details_continue', { @@ -72,10 +72,10 @@ class StoreDetails extends Component { if ( ! isError ) { goToNextStep(); } else { - createNotice( - 'error', - __( 'There was a problem saving your store details.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem saving your store details.', 'woocommerce-admin' ), + } ); } } @@ -185,11 +185,11 @@ export default compose( return { getSettings, isError, isRequesting, settings }; } ), withDispatch( dispatch => { - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice } = dispatch( 'wc-api' ); const { updateSettings } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateSettings, }; } ) diff --git a/client/dashboard/profile-wizard/steps/theme/index.js b/client/dashboard/profile-wizard/steps/theme/index.js index 79c8c9b6a53..043b71f6968 100644 --- a/client/dashboard/profile-wizard/steps/theme/index.js +++ b/client/dashboard/profile-wizard/steps/theme/index.js @@ -40,7 +40,7 @@ class Theme extends Component { } async onChoose( theme ) { - const { createNotice, goToNextStep, isError, updateProfileItems } = this.props; + const { addNotice, goToNextStep, isError, updateProfileItems } = this.props; recordEvent( 'storeprofiler_store_theme_choose', { theme } ); await updateProfileItems( { theme } ); @@ -49,10 +49,10 @@ class Theme extends Component { // @todo This should send profile information to woocommerce.com. goToNextStep(); } else { - createNotice( - 'error', - __( 'There was a problem selecting your store theme.', 'woocommerce-admin' ) - ); + addNotice( { + status: 'error', + message: __( 'There was a problem selecting your store theme.', 'woocommerce-admin' ), + } ); } } @@ -206,11 +206,10 @@ export default compose( return { isError }; } ), withDispatch( dispatch => { - const { updateProfileItems } = dispatch( 'wc-api' ); - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice, updateProfileItems } = dispatch( 'wc-api' ); return { - createNotice, + addNotice, updateProfileItems, }; } ) diff --git a/client/embedded.js b/client/embedded.js index af3a7fa9285..a5a75525bf5 100644 --- a/client/embedded.js +++ b/client/embedded.js @@ -2,7 +2,6 @@ /** * External dependencies */ -import '@wordpress/notices'; import { render } from '@wordpress/element'; /** @@ -10,6 +9,7 @@ import { render } from '@wordpress/element'; */ import './stylesheets/_embedded.scss'; import { EmbedLayout, PrimaryLayout as NoticeArea } from './layout'; +import 'store'; import 'wc-api/wp-data-store'; const embeddedRoot = document.getElementById( 'woocommerce-embedded-root' ); diff --git a/client/index.js b/client/index.js index 2437114734a..b5d548d7925 100644 --- a/client/index.js +++ b/client/index.js @@ -2,7 +2,6 @@ /** * External dependencies */ -import '@wordpress/notices'; import { render } from '@wordpress/element'; /** @@ -10,6 +9,7 @@ import { render } from '@wordpress/element'; */ import './stylesheets/_index.scss'; import { PageLayout } from './layout'; +import 'store'; import 'wc-api/wp-data-store'; render( , document.getElementById( 'root' ) ); diff --git a/client/layout/transient-notices/index.js b/client/layout/transient-notices/index.js index 25c86aeb103..454096cc4ad 100644 --- a/client/layout/transient-notices/index.js +++ b/client/layout/transient-notices/index.js @@ -6,25 +6,24 @@ import classnames from 'classnames'; import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import PropTypes from 'prop-types'; -import { SnackbarList } from '@wordpress/components'; -import { withDispatch } from '@wordpress/data'; /** * Internal dependencies */ import './style.scss'; +import TransientNotice from './transient-notice'; import withSelect from 'wc-api/with-select'; class TransientNotices extends Component { render() { - const { className, notices, onRemove } = this.props; - const classes = classnames( - 'woocommerce-transient-notices', - 'components-notices__snackbar', - className - ); + const { className, notices } = this.props; + const classes = classnames( 'woocommerce-transient-notices', className ); - return ; + return ( +
+ { notices && notices.map( ( notice, i ) => ) } +
+ ); } } @@ -41,11 +40,9 @@ TransientNotices.propTypes = { export default compose( withSelect( select => { - const notices = select( 'core/notices' ).getNotices(); + const { getNotices } = select( 'wc-admin' ); + const notices = getNotices(); return { notices }; - } ), - withDispatch( dispatch => ( { - onRemove: dispatch( 'core/notices' ).removeNotice, - } ) ) + } ) )( TransientNotices ); diff --git a/client/layout/transient-notices/style.scss b/client/layout/transient-notices/style.scss index ca006c09116..57a2826db30 100644 --- a/client/layout/transient-notices/style.scss +++ b/client/layout/transient-notices/style.scss @@ -5,13 +5,34 @@ bottom: $gap-small; left: 0; z-index: 99999; +} + +.woocommerce-transient-notice { + transform: translateX(calc(-100% - #{$gap})); + transition: all 300ms cubic-bezier(0.42, 0, 0.58, 1); + max-height: 300px; // Used to animate sliding down when multiple notices exist on exit. + + @media screen and (prefers-reduced-motion: reduce) { + transition: none; + } + + &.slide-enter-active, + &.slide-enter-done { + transform: translateX(0%); + } + + &.slide-exit-active { + transform: translateX(calc(-100% - #{$gap})); + } - .components-snackbar-list__notice-container { - margin-left: $gap-small; - margin-right: $gap-small; + &.slide-exit-done { + max-height: 0; + margin: 0; + padding: 0; + visibility: hidden; } - .components-snackbar { - margin: 0 auto; + .components-notice { + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); } } diff --git a/client/layout/transient-notices/transient-notice.js b/client/layout/transient-notices/transient-notice.js new file mode 100644 index 00000000000..d74ceb6a598 --- /dev/null +++ b/client/layout/transient-notices/transient-notice.js @@ -0,0 +1,104 @@ +/** @format */ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { Component } from '@wordpress/element'; +import { CSSTransition } from 'react-transition-group'; +import { noop } from 'lodash'; +import { Notice } from '@wordpress/components'; +import PropTypes from 'prop-types'; +import { speak } from '@wordpress/a11y'; + +class TransientNotice extends Component { + constructor( props ) { + super( props ); + + this.state = { + visible: false, + timeout: null, + }; + } + + componentDidMount() { + const exitTime = this.props.exitTime; + const timeout = setTimeout( + () => { + this.setState( { visible: false } ); + }, + exitTime, + name, + exitTime + ); + /* eslint-disable react/no-did-mount-set-state */ + this.setState( { visible: true, timeout } ); + /* eslint-enable react/no-did-mount-set-state */ + speak( this.props.message ); + } + + componentWillUnmount() { + clearTimeout( this.state.timeout ); + } + + render() { + const { actions, className, isDismissible, message, onRemove, status } = this.props; + const classes = classnames( 'woocommerce-transient-notice', className ); + + return ( + +
+ + { message } + +
+
+ ); + } +} + +TransientNotice.propTypes = { + /** + * Array of action objects. + * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/ + */ + actions: PropTypes.array, + /** + * Additional class name to style the component. + */ + className: PropTypes.string, + /** + * Determines if the notice dimiss button should be shown. + * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/ + */ + isDismissible: PropTypes.bool, + /** + * Function called when dismissing the notice. + * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/ + */ + onRemove: PropTypes.func, + /** + * Type of notice to display. + * See https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/notice/ + */ + status: PropTypes.oneOf( [ 'success', 'error', 'warning' ] ), + /** + * Time in milliseconds until exit. + */ + exitTime: PropTypes.number, +}; + +TransientNotice.defaultProps = { + actions: [], + className: '', + exitTime: 7000, + isDismissible: false, + onRemove: noop, + status: 'warning', +}; + +export default TransientNotice; diff --git a/client/store/index.js b/client/store/index.js new file mode 100644 index 00000000000..1e9e141e1f8 --- /dev/null +++ b/client/store/index.js @@ -0,0 +1,22 @@ +/** @format */ +/** + * External dependencies + */ +import { combineReducers, registerStore } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import notices from './notices'; + +registerStore( 'wc-admin', { + reducer: combineReducers( { + ...notices.reducers, + } ), + actions: { + ...notices.actions, + }, + selectors: { + ...notices.selectors, + }, +} ); diff --git a/client/store/notices/actions.js b/client/store/notices/actions.js new file mode 100644 index 00000000000..24065c7ecc2 --- /dev/null +++ b/client/store/notices/actions.js @@ -0,0 +1,9 @@ +/** @format */ + +const addNotice = notice => { + return { type: 'ADD_NOTICE', notice }; +}; + +export default { + addNotice, +}; diff --git a/client/store/notices/index.js b/client/store/notices/index.js new file mode 100644 index 00000000000..10e658b90ed --- /dev/null +++ b/client/store/notices/index.js @@ -0,0 +1,13 @@ +/** @format */ +/** + * Internal dependencies + */ +import reducers from './reducers'; +import actions from './actions'; +import selectors from './selectors'; + +export default { + reducers, + actions, + selectors, +}; diff --git a/client/store/notices/reducers.js b/client/store/notices/reducers.js new file mode 100644 index 00000000000..f0a2634160d --- /dev/null +++ b/client/store/notices/reducers.js @@ -0,0 +1,15 @@ +/** @format */ + +const DEFAULT_STATE = []; + +const notices = ( state = DEFAULT_STATE, action ) => { + if ( action.type === 'ADD_NOTICE' ) { + return [ ...state, action.notice ]; + } + + return state; +}; + +export default { + notices, +}; diff --git a/client/store/notices/selectors.js b/client/store/notices/selectors.js new file mode 100644 index 00000000000..5b805ba1109 --- /dev/null +++ b/client/store/notices/selectors.js @@ -0,0 +1,9 @@ +/** @format */ + +const getNotices = state => { + return state.notices; +}; + +export default { + getNotices, +}; diff --git a/client/store/notices/test/fixtures/index.js b/client/store/notices/test/fixtures/index.js new file mode 100644 index 00000000000..4f5e29c4190 --- /dev/null +++ b/client/store/notices/test/fixtures/index.js @@ -0,0 +1,7 @@ +/** @format */ + +export const DEFAULT_STATE = { + notices: [], +}; + +export const testNotice = { message: 'Test notice' }; diff --git a/client/store/notices/test/index.js b/client/store/notices/test/index.js new file mode 100644 index 00000000000..e495b015751 --- /dev/null +++ b/client/store/notices/test/index.js @@ -0,0 +1,55 @@ +/** @format */ +/** + * Internal dependencies + */ +import actions from '../actions'; +import { DEFAULT_STATE, testNotice } from './fixtures'; +import reducers from '../reducers'; +import selectors from '../selectors'; + +describe( 'actions', () => { + test( 'should create an add notice action', () => { + const expectedAction = { + type: 'ADD_NOTICE', + notice: testNotice, + }; + + expect( actions.addNotice( testNotice ) ).toEqual( expectedAction ); + } ); +} ); + +describe( 'selectors', () => { + const notices = [ testNotice ]; + const updatedState = { ...DEFAULT_STATE, ...{ notices } }; + + test( 'should return an emtpy initial state', () => { + expect( selectors.getNotices( DEFAULT_STATE ) ).toEqual( [] ); + } ); + + test( 'should have an array length matching number of notices', () => { + expect( selectors.getNotices( updatedState ).length ).toEqual( 1 ); + } ); + + test( 'should return the message content', () => { + expect( selectors.getNotices( updatedState )[ 0 ].message ).toEqual( 'Test notice' ); + } ); +} ); + +describe( 'reducers', () => { + test( 'should return an emtpy initial state', () => { + expect( reducers.notices( DEFAULT_STATE.notices, {} ) ).toEqual( [] ); + } ); + + test( 'should return the added notice', () => { + expect( + reducers.notices( DEFAULT_STATE.notices, { type: 'ADD_NOTICE', notice: testNotice } ) + ).toEqual( [ testNotice ] ); + } ); + + const initialNotices = [ { message: 'Initial notice' } ]; + test( 'should return the initial notice and the added notice', () => { + expect( + reducers.notices( initialNotices, { type: 'ADD_NOTICE', notice: testNotice } ) + ).toEqual( [ ...initialNotices, testNotice ] ); + } ); +} ); diff --git a/client/stylesheets/abstracts/_breakpoints.scss b/client/stylesheets/abstracts/_breakpoints.scss index a600db74add..763010dbd9c 100644 --- a/client/stylesheets/abstracts/_breakpoints.scss +++ b/client/stylesheets/abstracts/_breakpoints.scss @@ -22,15 +22,13 @@ $breakpoints: 320px, 400px, 600px, 782px, 960px, 1280px, 1440px; @media (max-width: $breakpoint) { @content; } - } - @else { + } @else { @if $size == $and-larger { $approved-value: 2; @media (min-width: $breakpoint + 1) { @content; } - } - @else { + } @else { @each $breakpoint-end in $breakpoints { $range: $breakpoint + '-' + $breakpoint-end; @if $size == $range { @@ -50,8 +48,7 @@ $breakpoints: 320px, 400px, 600px, 782px, 960px, 1280px, 1440px; } @warn 'ERROR in breakpoint( #{ $size } ) : You can only use these sizes[ #{$sizes} ] using the following syntax [ <#{ nth( $breakpoints, 1 ) } >#{ nth( $breakpoints, 1 ) } #{ nth( $breakpoints, 1 ) }-#{ nth( $breakpoints, 2 ) } ]'; } - } - @else { + } @else { $sizes: ''; @each $breakpoint in $breakpoints { $sizes: $sizes + ' ' + $breakpoint; diff --git a/client/stylesheets/abstracts/_mixins.scss b/client/stylesheets/abstracts/_mixins.scss index e7d7e7fa2a3..e33caae0882 100644 --- a/client/stylesheets/abstracts/_mixins.scss +++ b/client/stylesheets/abstracts/_mixins.scss @@ -37,7 +37,7 @@ } } -// Gutenberg mixins. These are temporary until Gutenberg's mixins are exposed. +// Gutenberg Button variables. These are temporary until Gutenberg's variables are exposed. @mixin button-style__focus-active() { background-color: $white; color: $dark-gray-900; @@ -48,31 +48,6 @@ outline-offset: -2px; } -@mixin reduce-motion($property: '') { - @if $property == 'transition' { - @media (prefers-reduced-motion: reduce) { - transition-duration: 0s; - } - } - @else if $property == 'animation' { - @media (prefers-reduced-motion: reduce) { - animation-duration: 1ms; - } - } - @else { - @media (prefers-reduced-motion: reduce) { - transition-duration: 0s; - animation-duration: 1ms; - } - } -} - -@mixin break-small() { - @media (min-width: #{ ($break-small) }) { - @content; - } -} - // Sets positions for children of grid elements @mixin set-grid-item-position( $wrap-after, $number-of-items ) { @for $i from 1 through $number-of-items { diff --git a/client/stylesheets/abstracts/_variables.scss b/client/stylesheets/abstracts/_variables.scss index 0688998ea2c..758fbc6a280 100644 --- a/client/stylesheets/abstracts/_variables.scss +++ b/client/stylesheets/abstracts/_variables.scss @@ -19,20 +19,14 @@ $header-height: 56px; // @todo Remove this spacing variable $spacing: 16px; -// Gutenberg variables. These are temporary until Gutenberg's variables are exposed. -$default-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, - 'Helvetica Neue', sans-serif; -$default-font-size: 13px; -$default-line-height: 1.4; -$break-small: 600px; +// Gutenberg Button variables. These are temporary until Gutenberg's variables are exposed. $border-width: 1px; +$default-font-size: 13px; $blue-medium-focus: #007cba; $light-gray-500: $core-grey-light-500; $dark-gray-300: $core-grey-dark-300; -$dark-gray-700: $core-grey-dark-700; $dark-gray-900: $core-grey-dark-900; $alert-red: $error-red; -$radius-round-rectangle: 4px; // WordPress defaults $adminbar-height: 32px; diff --git a/client/stylesheets/shared/_gutenberg-components.scss b/client/stylesheets/shared/_gutenberg-components.scss index f4f4b705d06..a776e9ccaf5 100644 --- a/client/stylesheets/shared/_gutenberg-components.scss +++ b/client/stylesheets/shared/_gutenberg-components.scss @@ -5,4 +5,3 @@ Import Gutenberg component SCSS so webpack's postcss process can handle theme-in allows Woo themed components based on the config found in postcss.config.js */ @import 'gutenberg-components/button/style.scss'; -@import 'gutenberg-components/snackbar/style.scss'; diff --git a/client/wc-api/items/mutations.js b/client/wc-api/items/mutations.js index dcc7b04c48a..015ccee0d14 100644 --- a/client/wc-api/items/mutations.js +++ b/client/wc-api/items/mutations.js @@ -11,7 +11,7 @@ import { dispatch } from '@wordpress/data'; import { getResourceName } from '../utils'; const updateProductStock = operations => async ( product, newStock ) => { - const { createNotice } = dispatch( 'core/notices' ); + const { addNotice } = dispatch( 'wc-admin' ); const oldStockQuantity = product.stock_quantity; const resourceName = getResourceName( 'items-query-products-item', product.id ); @@ -30,16 +30,16 @@ const updateProductStock = operations => async ( product, newStock ) => { } ); const response = result[ 0 ][ resourceName ]; if ( response && response.data ) { - createNotice( - 'success', - sprintf( __( '%s stock updated.', 'woocommerce-admin' ), product.name ) - ); + addNotice( { + status: 'success', + message: sprintf( __( '%s stock updated.', 'woocommerce-admin' ), product.name ), + } ); } if ( response && response.error ) { - createNotice( - 'error', - sprintf( __( '%s stock could not be updated.', 'woocommerce-admin' ), product.name ) - ); + addNotice( { + status: 'error', + message: sprintf( __( '%s stock could not be updated.', 'woocommerce-admin' ), product.name ), + } ); // Revert local changes if the operation failed in the server operations.updateLocally( [ resourceName ], { [ resourceName ]: { ...product, stock_quantity: oldStockQuantity }, diff --git a/package-lock.json b/package-lock.json index ff57036cc48..e3b36a1dfdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3106,20 +3106,21 @@ "dev": true }, "@wordpress/components": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-8.0.0.tgz", - "integrity": "sha512-8Lo9VHcNME+0s8RGQ0FaTYbSw7UdoIPZYK8+OB+jZgwM88kbVW6SkgCM45QdxSLtaxkCYdWX1IF/DRJzI22WyQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-7.4.0.tgz", + "integrity": "sha512-riVey0Z5835YdPZLWFSAs/4Hzo0nr7WA/393mRXwGuUtkqdk7ia++5emKfhyaCLYbinVBd6366xFFfiFxxcsCA==", "requires": { "@babel/runtime": "^7.4.4", - "@wordpress/a11y": "^2.4.0", - "@wordpress/compose": "^3.4.0", + "@wordpress/a11y": "^2.3.0", + "@wordpress/api-fetch": "^3.2.0", + "@wordpress/compose": "^3.3.0", "@wordpress/dom": "^2.3.0", - "@wordpress/element": "^2.5.0", - "@wordpress/hooks": "^2.4.0", - "@wordpress/i18n": "^3.5.0", - "@wordpress/is-shallow-equal": "^1.4.0", - "@wordpress/keycodes": "^2.4.0", - "@wordpress/rich-text": "^3.4.0", + "@wordpress/element": "^2.4.0", + "@wordpress/hooks": "^2.3.0", + "@wordpress/i18n": "^3.4.0", + "@wordpress/is-shallow-equal": "^1.3.0", + "@wordpress/keycodes": "^2.3.0", + "@wordpress/rich-text": "^3.3.0", "@wordpress/url": "^2.6.0", "classnames": "^2.2.5", "clipboard": "^2.0.1", @@ -3132,53 +3133,34 @@ "re-resizable": "^4.7.1", "react-click-outside": "^3.0.0", "react-dates": "^17.1.1", - "react-spring": "^8.0.20", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", "uuid": "^3.3.2" }, "dependencies": { - "@wordpress/element": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.5.0.tgz", - "integrity": "sha512-gqtNcBkVtF//OHKJQAn4c7nwfDqsIy0UMq7ed9l6lpXOKS5ic8cw7BgNtdx4PiicV46ev9s/yHieFKWZNFpfIw==", - "requires": { - "@babel/runtime": "^7.4.4", - "@wordpress/escape-html": "^1.4.0", - "lodash": "^4.17.11", - "react": "^16.8.4", - "react-dom": "^16.8.4" - } - }, - "@wordpress/hooks": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-2.4.0.tgz", - "integrity": "sha512-z3+8Yq4IQ3DOvUF4VsXOfntdyk1fJ8RZBYlZTW8WfmHV7VKTDbX3LfHXmC2VCjXhVkeWQVKozi2weoEYopfGKA==", - "requires": { - "@babel/runtime": "^7.4.4" - } - }, - "@wordpress/i18n": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.5.0.tgz", - "integrity": "sha512-QAYFsHe/raG0MPUX32gCmuSM6UN/5gWCVSFWxOVSuXVuzPP5WnL78CgpuXtUresDZXTJm3FrFcB9IKvh93e1DQ==", - "requires": { - "@babel/runtime": "^7.4.4", - "gettext-parser": "^1.3.1", - "lodash": "^4.17.11", - "memize": "^1.0.5", - "sprintf-js": "^1.1.1", - "tannin": "^1.0.1" - } - }, - "@wordpress/keycodes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-2.4.0.tgz", - "integrity": "sha512-qGRBKcDroYzUffAGMzotyos2k+FA89IjYYFjWlUrDp4TVaadCHK1ESPcmX6Y87ExgcsJKXDkNVZfsCPvh7V7Kw==", + "@wordpress/api-fetch": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-3.3.0.tgz", + "integrity": "sha512-EddkSzj2csdDWhIZueBthue8er5akGDBAnOtUQ5d8cdOQd1RrQbAgHmpKZtNHD8qXbfS40Ay1K42w7Si0uOksw==", "requires": { "@babel/runtime": "^7.4.4", "@wordpress/i18n": "^3.5.0", - "lodash": "^4.17.11" + "@wordpress/url": "^2.6.0" + }, + "dependencies": { + "@wordpress/i18n": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.5.0.tgz", + "integrity": "sha512-QAYFsHe/raG0MPUX32gCmuSM6UN/5gWCVSFWxOVSuXVuzPP5WnL78CgpuXtUresDZXTJm3FrFcB9IKvh93e1DQ==", + "requires": { + "@babel/runtime": "^7.4.4", + "gettext-parser": "^1.3.1", + "lodash": "^4.17.11", + "memize": "^1.0.5", + "sprintf-js": "^1.1.1", + "tannin": "^1.0.1" + } + } } } } @@ -3394,50 +3376,6 @@ "lodash": "^4.17.11" } }, - "@wordpress/notices": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-1.5.0.tgz", - "integrity": "sha512-aKCVDYPVVzYBm2cW4pQzAsshLOZx0f9AZiBZOV+AD9D+eSAscF0SBcvmbduFFQ4KhIgj76IXz5cxXIrafFXaDQ==", - "requires": { - "@babel/runtime": "^7.4.4", - "@wordpress/a11y": "^2.4.0", - "@wordpress/data": "^4.6.0", - "lodash": "^4.17.11" - }, - "dependencies": { - "@wordpress/data": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-4.6.0.tgz", - "integrity": "sha512-42BNtWtN0ppnzbnxz0tA72KNtuEKjrIRr4PbVCw3JVrHN2Nfhbo9SiCUutReKAXGQaT4LPnO7gAGmIsdC/mBUw==", - "requires": { - "@babel/runtime": "^7.4.4", - "@wordpress/compose": "^3.4.0", - "@wordpress/deprecated": "^2.4.0", - "@wordpress/element": "^2.5.0", - "@wordpress/is-shallow-equal": "^1.4.0", - "@wordpress/priority-queue": "^1.2.0", - "@wordpress/redux-routine": "^3.4.0", - "equivalent-key-map": "^0.2.2", - "is-promise": "^2.1.0", - "lodash": "^4.17.11", - "redux": "^4.0.0", - "turbo-combine-reducers": "^1.0.2" - } - }, - "@wordpress/element": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-2.5.0.tgz", - "integrity": "sha512-gqtNcBkVtF//OHKJQAn4c7nwfDqsIy0UMq7ed9l6lpXOKS5ic8cw7BgNtdx4PiicV46ev9s/yHieFKWZNFpfIw==", - "requires": { - "@babel/runtime": "^7.4.4", - "@wordpress/escape-html": "^1.4.0", - "lodash": "^4.17.11", - "react": "^16.8.4", - "react-dom": "^16.8.4" - } - } - } - }, "@wordpress/npm-package-json-lint-config": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-1.3.0.tgz", @@ -13780,66 +13718,6 @@ "requires": { "@wordpress/components": "^7.3.1", "@wordpress/element": "^2.3.0" - }, - "dependencies": { - "@wordpress/api-fetch": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-3.3.0.tgz", - "integrity": "sha512-EddkSzj2csdDWhIZueBthue8er5akGDBAnOtUQ5d8cdOQd1RrQbAgHmpKZtNHD8qXbfS40Ay1K42w7Si0uOksw==", - "requires": { - "@babel/runtime": "^7.4.4", - "@wordpress/i18n": "^3.5.0", - "@wordpress/url": "^2.6.0" - }, - "dependencies": { - "@wordpress/i18n": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-3.5.0.tgz", - "integrity": "sha512-QAYFsHe/raG0MPUX32gCmuSM6UN/5gWCVSFWxOVSuXVuzPP5WnL78CgpuXtUresDZXTJm3FrFcB9IKvh93e1DQ==", - "requires": { - "@babel/runtime": "^7.4.4", - "gettext-parser": "^1.3.1", - "lodash": "^4.17.11", - "memize": "^1.0.5", - "sprintf-js": "^1.1.1", - "tannin": "^1.0.1" - } - } - } - }, - "@wordpress/components": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-7.4.0.tgz", - "integrity": "sha512-riVey0Z5835YdPZLWFSAs/4Hzo0nr7WA/393mRXwGuUtkqdk7ia++5emKfhyaCLYbinVBd6366xFFfiFxxcsCA==", - "requires": { - "@babel/runtime": "^7.4.4", - "@wordpress/a11y": "^2.3.0", - "@wordpress/api-fetch": "^3.2.0", - "@wordpress/compose": "^3.3.0", - "@wordpress/dom": "^2.3.0", - "@wordpress/element": "^2.4.0", - "@wordpress/hooks": "^2.3.0", - "@wordpress/i18n": "^3.4.0", - "@wordpress/is-shallow-equal": "^1.3.0", - "@wordpress/keycodes": "^2.3.0", - "@wordpress/rich-text": "^3.3.0", - "@wordpress/url": "^2.6.0", - "classnames": "^2.2.5", - "clipboard": "^2.0.1", - "diff": "^3.5.0", - "dom-scroll-into-view": "^1.2.1", - "lodash": "^4.17.11", - "memize": "^1.0.5", - "moment": "^2.22.1", - "mousetrap": "^1.6.2", - "re-resizable": "^4.7.1", - "react-click-outside": "^3.0.0", - "react-dates": "^17.1.1", - "rememo": "^3.0.0", - "tinycolor2": "^1.4.1", - "uuid": "^3.3.2" - } - } } }, "nice-try": { @@ -18675,15 +18553,6 @@ "tiny-warning": "^1.0.0" } }, - "react-spring": { - "version": "8.0.27", - "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.27.tgz", - "integrity": "sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==", - "requires": { - "@babel/runtime": "^7.3.1", - "prop-types": "^15.5.8" - } - }, "react-test-renderer": { "version": "16.8.6", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz", diff --git a/package.json b/package.json index 6e8cc721234..39eb99dcf3a 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "dependencies": { "@fresh-data/framework": "0.6.1", "@wordpress/api-fetch": "2.2.8", - "@wordpress/components": "8.0.0", + "@wordpress/components": "7.4.0", "@wordpress/data": "4.5.0", "@wordpress/date": "3.3.0", "@wordpress/element": "2.4.0", @@ -75,7 +75,6 @@ "@wordpress/html-entities": "2.3.0", "@wordpress/i18n": "3.4.0", "@wordpress/keycodes": "2.3.0", - "@wordpress/notices": "1.5.0", "@wordpress/scripts": "3.2.1", "@wordpress/viewport": "2.4.0", "browser-filesaver": "1.1.1", diff --git a/packages/components/src/search-list-control/test/__snapshots__/index.js.snap b/packages/components/src/search-list-control/test/__snapshots__/index.js.snap index e4d947cf2da..8d52c4efad2 100644 --- a/packages/components/src/search-list-control/test/__snapshots__/index.js.snap +++ b/packages/components/src/search-list-control/test/__snapshots__/index.js.snap @@ -43,7 +43,6 @@ exports[`SearchListControl should render a search box and list of hierarchical o className="woocommerce-search-list__list components-menu-group" >