diff --git a/plugins/woocommerce-admin/packages/components/CHANGELOG.md b/plugins/woocommerce-admin/packages/components/CHANGELOG.md index e8c017d386d..7ce42d9293f 100644 --- a/plugins/woocommerce-admin/packages/components/CHANGELOG.md +++ b/plugins/woocommerce-admin/packages/components/CHANGELOG.md @@ -1,4 +1,5 @@ # 5.0.0 (Unreleased) +- Added `` component. - Added `` component. - Style form components for WordPress 5.3. - Fix CompareFilter options format (key prop vs. id). diff --git a/plugins/woocommerce-admin/packages/components/src/index.js b/plugins/woocommerce-admin/packages/components/src/index.js index 5a0e7c60efe..fcf1c3f01c8 100644 --- a/plugins/woocommerce-admin/packages/components/src/index.js +++ b/plugins/woocommerce-admin/packages/components/src/index.js @@ -57,6 +57,7 @@ export { default as TableSummary } from './table/summary'; export { default as Tag } from './tag'; export { default as TextControl } from './text-control'; export { default as TextControlWithAffixes } from './text-control-with-affixes'; +export { default as Timeline } from './timeline'; export { default as useFilters } from './higher-order/use-filters'; export { default as ViewMoreList } from './view-more-list'; export { default as WebPreview } from './web-preview'; diff --git a/plugins/woocommerce-admin/packages/components/src/style.scss b/plugins/woocommerce-admin/packages/components/src/style.scss index 209ae6ff892..329eef51470 100644 --- a/plugins/woocommerce-admin/packages/components/src/style.scss +++ b/plugins/woocommerce-admin/packages/components/src/style.scss @@ -35,5 +35,6 @@ @import 'tag/style.scss'; @import 'text-control/style.scss'; @import 'text-control-with-affixes/style.scss'; +@import 'timeline/style.scss'; @import 'view-more-list/style.scss'; @import 'web-preview/style.scss'; diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/README.md b/plugins/woocommerce-admin/packages/components/src/timeline/README.md new file mode 100644 index 00000000000..f27300da7bc --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/README.md @@ -0,0 +1,70 @@ +Timeline +=== + +This is a timeline for displaying data, such as events, in chronological order. +It accepts `items` for the timeline content and will order the data for you. + +## Usage + +```jsx +import Timeline from './Timeline'; +import { orderByOptions, groupByOptions } from './Timeline'; +import GridIcon from 'gridicons'; + +const items = [ + { + date: new Date( 2019, 9, 28, 9, 0 ), + icon: , + headline: 'A payment of $90.00 was successfully charged', + body: [ +

{ 'Fee: $2.91 ( 2.9% + $0.30 )' }

, +

{ 'Net deposit: $87.09' }

, + ], + }, + { + date: new Date( 2019, 9, 28, 9, 32 ), + icon: , + headline: '$94.16 was added to your October 29, 2019 deposit', + body: [], + }, + { + date: new Date( 2019, 9, 27, 20, 9 ), + icon: , + headline: 'A payment of $90.00 was successfully authorized', + body: [], + }, +] + + +``` + +### Props + +Name | Type | Default | Description +--- | --- | --- | --- +`className` | String | `''` | Additional class names that can be applied for styling purposes +`items` | Array | `[]` | An array of items to be displayed on the timeline +`orderBy` | String | `'asc'` | How the items should be ordered, either `'asc'` or `'desc'` +`groupBy` | String | `'day'` | How the items should be grouped, one of `'day'`, `'week'`, or `'month'` +`dateFormat` | String | `'F j, Y'` | PHP date format string used to format dates, see php.net/date +`clockFormat` | String | `'g:ia'` | PHP clock format string used to format times, see php.net/date + + +### `items` structure + +A list of items with properties: + +Name | Type | Default | Description +--- | --- | --- | --- +`date` | Date | Required | JavaScript Date object set to when this event happened +`icon` | Element | Required | The element used to represent the icon for this event +`headline` | Element | Required | The element used to represent the title of this event +`body` | Array | `[]` | Elements that contain details pertaining to this event +`hideTimestamp` | Bool | `false` | Allows the user to hide the timestamp associated with this event + +Icon color can be customized by adding 1 of 3 classes to the icon element: `is-success` (green), `is-warning` (yellow), and `is-error` (red) + - If no class is provided the icon will be gray diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/__mocks__/timeline-mock-data.js b/plugins/woocommerce-admin/packages/components/src/timeline/__mocks__/timeline-mock-data.js new file mode 100644 index 00000000000..0e6902da18a --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/__mocks__/timeline-mock-data.js @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import GridIcon from 'gridicons'; + +export default [ + { + date: new Date( 2020, 0, 20, 1, 30 ), + body: [

{ 'p element in body' }

, 'string in body' ], + headline:

{ 'p tag in headline' }

, + icon: ( + + ), + hideTimestamp: true, + }, + { + date: new Date( 2020, 0, 20, 23, 45 ), + body: [], + headline: { 'span in headline' }, + icon: ( + + ), + }, + { + date: new Date( 2020, 0, 22, 15, 13 ), + body: [ { 'span in body' } ], + headline: 'string in headline', + icon: ( + + ), + }, + { + date: new Date( 2020, 0, 17, 1, 45 ), + headline: 'undefined body and string headline', + icon: , + }, +]; diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/index.js b/plugins/woocommerce-admin/packages/components/src/timeline/index.js new file mode 100644 index 00000000000..a6540d90a7e --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/index.js @@ -0,0 +1,131 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import PropTypes from 'prop-types'; +import { __ } from '@wordpress/i18n'; +import { format } from '@wordpress/date'; + +/** + * Internal dependencies + */ +import TimelineGroup from './timeline-group'; +import { sortByDateUsing, groupItemsUsing } from './util'; + +const Timeline = ( props ) => { + const { + className, + items, + groupBy, + orderBy, + dateFormat, + clockFormat, + } = props; + const timelineClassName = classnames( 'woocommerce-timeline', className ); + + // Early return in case no data was passed to the component. + if ( ! items || items.length === 0 ) { + return ( +
+

+ { __( 'No data to display', 'woocommerce-admin' ) } +

+
+ ); + } + + const addGroupTitles = ( group ) => { + return { + ...group, + title: format( dateFormat, group.date ), + }; + }; + + return ( +
+
    + { items + .reduce( groupItemsUsing( groupBy ), [] ) + .map( addGroupTitles ) + .sort( sortByDateUsing( orderBy ) ) + .map( ( group ) => ( + + ) ) } +
+
+ ); +}; + +Timeline.propTypes = { + /** + * Additional CSS classes. + */ + className: PropTypes.string, + /** + * An array of list items. + */ + items: PropTypes.arrayOf( + PropTypes.shape( { + /** + * Date for the timeline item. + */ + date: PropTypes.instanceOf( Date ).isRequired, + /** + * Icon for the Timeline item. + */ + icon: PropTypes.element.isRequired, + /** + * Headline displayed for the list item. + */ + headline: PropTypes.oneOfType( [ + PropTypes.element, + PropTypes.string, + ] ).isRequired, + /** + * Body displayed for the list item. + */ + body: PropTypes.arrayOf( + PropTypes.oneOfType( [ PropTypes.element, PropTypes.string ] ) + ), + /** + * Allows users to toggle the timestamp on or off. + */ + hideTimestamp: PropTypes.bool, + } ) + ).isRequired, + /** + * Defines how items should be grouped together. + */ + groupBy: PropTypes.oneOf( [ 'day', 'week', 'month' ] ), + /** + * Defines how groups should be ordered. + */ + orderBy: PropTypes.oneOf( [ 'asc', 'desc' ] ), + /** + * The PHP date format string used to format dates, see php.net/date. + */ + dateFormat: PropTypes.string, + /** + * The PHP clock format string used to format times, see php.net/date. + */ + clockFormat: PropTypes.string, +}; + +Timeline.defaultProps = { + className: '', + items: [], + groupBy: 'day', + orderBy: 'desc', + /* translators: PHP date format string used to display dates, see php.net/date. */ + dateFormat: __( 'F j, Y', 'woocommerce-admin' ), + /* translators: PHP clock format string used to display times, see php.net/date. */ + clockFormat: __( 'g:ia', 'woocommerce-admin' ), +}; + +export { orderByOptions, groupByOptions } from './util'; +export default Timeline; diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/stories/index.js b/plugins/woocommerce-admin/packages/components/src/timeline/stories/index.js new file mode 100644 index 00000000000..a995ea3fd18 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/stories/index.js @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import { date, text } from '@storybook/addon-knobs'; +import GridIcon from 'gridicons'; + +/** + * Internal dependencies + */ +import Timeline, { orderByOptions } from '../'; + +export default { + title: 'WooCommerce Admin/components/Timeline', + component: Timeline, +}; + +export const Empty = () => ; + +const itemDate = ( label, value ) => { + const d = date( label, value ); + return new Date( d ); +}; + +export const Filled = () => ( + + { text( 'event 1, first event', 'p element in body' ) } +

, + text( 'event 1, second event', 'string in body' ), + ], + headline: ( +

{ text( 'event 1, headline', 'p tag in headline' ) }

+ ), + icon: ( + + ), + hideTimestamp: true, + }, + { + date: itemDate( + 'event 2 date', + new Date( 2020, 0, 20, 23, 45 ) + ), + body: [], + headline: ( + + { text( 'event 2, headline', 'span in headline' ) } + + ), + icon: ( + + ), + }, + { + date: itemDate( + 'event 3 date', + new Date( 2020, 0, 22, 15, 13 ) + ), + body: [ + + { text( 'event 3, second event', 'span in body' ) } + , + ], + headline: text( 'event 3, headline', 'string in headline' ), + icon: ( + + ), + }, + { + date: itemDate( + 'event 4 date', + new Date( 2020, 0, 17, 1, 45 ) + ), + headline: text( + 'event 4, headline', + 'undefined body and string headline' + ), + icon: ( + + ), + }, + ] } + /> +); diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/style.scss b/plugins/woocommerce-admin/packages/components/src/timeline/style.scss new file mode 100644 index 00000000000..c51bff8cfe5 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/style.scss @@ -0,0 +1,122 @@ + +.woocommerce-timeline { + ul { + margin: 0; + padding-left: 0; + list-style-type: none; + + li { + margin-bottom: 0; + } + } + + .woocommerce-timeline-group { + .woocommerce-timeline-group__title { + color: $studio-gray-90; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + + margin: 0 0 $gap 0; + + // Overrides the default `display: block` for p elements in the title. + // This is done to prevent soft line breaks from appearing in shorter + // titles. + display: inline-block; + } + + hr { + float: right; + width: calc(100% - #{ $gap-largest }); + + margin-bottom: $gap; + + // Color is according to design, we should probably find a suitable color variable. + border: 0.5px solid #e3dfe2; + } + } + + .woocommerce-timeline-item { + .woocommerce-timeline-item__top-border { + min-height: 16px; + border-left: 1px solid $studio-gray-10; + margin: 0 $gap-small; + } + + .woocommerce-timeline-item__title { + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-between; + color: $studio-gray-80; + + * { + font-size: 16px; + } + } + + .woocommerce-timeline-item__headline { + display: flex; + align-items: center; + flex-direction: row; + margin: $gap-smaller 0; + + * { + margin: 0; + } + & > * { + padding: 0 $gap; + } + + svg { + fill: $studio-white; + padding: $gap-smallest; + background: $studio-gray-10; + border-radius: 9999px; + box-sizing: content-box; + // We hard code the size to maintain consistent styling and spacing. + width: 16px; + height: 16px; + + &.is-success { + background: $valid-green; + } + + &.is-warning { + background: $notice-yellow; + } + + &.is-error { + background: $error-red; + } + } + } + + .woocommerce-timeline-item__timestamp { + font-size: 14px; + line-height: 16px; + } + + .woocommerce-timeline-item__body { + display: flex; + flex-direction: column; + + color: $studio-gray-60; + + margin: 0 $gap-small; + padding: $gap-smaller $gap-larger; + border-left: 1px solid $studio-gray-10; + + // Make sure child elements fit tightly together. + * { + margin: 0; + font-size: 14px; + } + } + } +} + +// Hide last
element. +.woocommerce-timeline ul :last-child.woocommerce-timeline-group hr:last-child { + display: none; +} diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/test/__snapshots__/index.js.snap b/plugins/woocommerce-admin/packages/components/src/timeline/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000..40da12e54b3 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/test/__snapshots__/index.js.snap @@ -0,0 +1,231 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Timeline Empty snapshot 1`] = ` +
+

+ No data to display +

+
+`; + +exports[`Timeline With data snapshot 1`] = ` +
+
    +
  • +

    + January 22, 2020 +

    +
      +
    • +
      +
      +
      + + + + + + + string in headline + +
      + + 3:13pm + +
      +
      + + + span in body + + +
      +
    • +
    +
    +
  • +
  • +

    + January 20, 2020 +

    +
      +
    • +
      +
      +
      + + + + + + + + span in headline + + +
      + + 11:45pm + +
      +
      +
    • +
    • +
      +
      +
      + + + + + + +

      + p tag in headline +

      +
      +
      + +
      +
      + +

      + p element in body +

      +
      + + string in body + +
      +
    • +
    +
    +
  • +
  • +

    + January 17, 2020 +

    +
      +
    • +
      +
      +
      + + + + + + + undefined body and string headline + +
      + + 1:45am + +
      +
      +
    • +
    +
    +
  • +
+
+`; diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/test/index.js b/plugins/woocommerce-admin/packages/components/src/timeline/test/index.js new file mode 100644 index 00000000000..ba75b1f5157 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/test/index.js @@ -0,0 +1,189 @@ +/* eslint-disable jest/no-mocks-import */ +/** + * External dependencies + */ +import { shallow, mount } from 'enzyme'; +import renderer from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import Timeline from '..'; +import mockData from '../__mocks__/timeline-mock-data'; +import { groupItemsUsing, sortByDateUsing } from '../util.js'; + +describe( 'Timeline', () => { + test( 'Renders empty correctly', () => { + const timeline = shallow( ); + expect( timeline.find( '.timeline_no_events' ).length ).toBe( 1 ); + expect( + timeline + .find( '.timeline_no_events' ) + .first() + .contains( 'No data to display' ) + ).toBe( true ); + } ); + + test( 'Renders data correctly', () => { + const timeline = mount( ); + + // Ensure correct divs are loaded. + expect( timeline.find( '.timeline_no_events' ).length ).toBe( 0 ); + expect( timeline.find( '.woocommerce-timeline' ).length ).toBe( 1 ); + + // Ensure groups have the correct number of items. + expect( timeline.find( '.woocommerce-timeline-group' ).length ).toBe( + 3 + ); + expect( + timeline + .find( '.woocommerce-timeline-group ul' ) + .at( 0 ) + .children().length + ).toBe( 1 ); + expect( + timeline + .find( '.woocommerce-timeline-group ul' ) + .at( 1 ) + .children().length + ).toBe( 2 ); + expect( + timeline + .find( '.woocommerce-timeline-group ul' ) + .at( 2 ) + .children().length + ).toBe( 1 ); + + // Ensure dates are correctly rendered. + expect( + timeline + .find( '.woocommerce-timeline-group__title' ) + .first() + .text() + ).toBe( 'January 22, 2020' ); + expect( + timeline + .find( '.woocommerce-timeline-group__title' ) + .last() + .text() + ).toBe( 'January 17, 2020' ); + + // Ensure the correct number of items is rendered. + expect( timeline.find( '.woocommerce-timeline-item' ).length ).toBe( + 4 + ); + + // Ensure hidden timestamps are actually hidden and vice versa. + expect( + timeline + .find( '.woocommerce-timeline-item__timestamp' ) + .at( 2 ) + .text() + ).toBe( '' ); + expect( + timeline + .find( '.woocommerce-timeline-item__timestamp' ) + .at( 1 ) + .text() + ).not.toBe( '' ); + } ); + + test( 'Empty snapshot', () => { + const tree = renderer.create( ).toJSON(); + expect( tree ).toMatchSnapshot(); + } ); + + test( 'With data snapshot', () => { + const tree = renderer + .create( ) + .toJSON(); + expect( tree ).toMatchSnapshot(); + } ); + + describe( 'Timeline utilities', () => { + test( 'Sorts correctly', () => { + const jan21 = new Date( 2020, 0, 21 ); + const jan22 = new Date( 2020, 0, 22 ); + const jan23 = new Date( 2020, 0, 23 ); + + const data = [ + { id: 0, date: jan22 }, + { id: 1, date: jan21 }, + { id: 2, date: jan23 }, + ]; + const expectedAsc = [ + { id: 1, date: jan21 }, + { id: 0, date: jan22 }, + { id: 2, date: jan23 }, + ]; + const expectedDesc = [ + { id: 2, date: jan23 }, + { id: 0, date: jan22 }, + { id: 1, date: jan21 }, + ]; + + expect( data.sort( sortByDateUsing( 'asc' ) ) ).toStrictEqual( + expectedAsc + ); + expect( data.sort( sortByDateUsing( 'desc' ) ) ).toStrictEqual( + expectedDesc + ); + } ); + + test( "Empty item list doesn't break sort", () => { + expect( [].sort( sortByDateUsing( 'asc' ) ) ).toStrictEqual( [] ); + } ); + + test( "Single item doesn't change on sort", () => { + const items = [ { date: new Date( 2020, 0, 1 ) } ]; + expect( items.sort( sortByDateUsing( 'asc' ) ) ).toBe( items ); + } ); + + test( 'Groups correctly', () => { + const jan22 = new Date( 2020, 0, 22 ); + const jan23 = new Date( 2020, 0, 23 ); + const items = [ + { id: 0, date: jan22 }, + { id: 1, date: jan23 }, + { id: 2, date: jan22 }, + ]; + const expected = [ + { + date: jan22, + items: [ + { id: 0, date: jan22 }, + { id: 2, date: jan22 }, + ], + }, + { + date: jan23, + items: [ { id: 1, date: jan23 } ], + }, + ]; + + expect( + items.reduce( groupItemsUsing( 'days' ), [] ) + ).toStrictEqual( expected ); + } ); + + test( "Empty item list doesn't break grouping", () => { + expect( [].reduce( groupItemsUsing( 'days' ), [] ) ).toStrictEqual( + [] + ); + } ); + + test( 'Single item grouped correctly', () => { + const jan22 = new Date( 2020, 0, 22 ); + const items = [ { id: 0, date: jan22 } ]; + const expected = [ + { + date: jan22, + items: [ { id: 0, date: jan22 } ], + }, + ]; + expect( + items.reduce( groupItemsUsing( 'days' ), [] ) + ).toStrictEqual( expected ); + } ); + } ); +} ); diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/timeline-group.js b/plugins/woocommerce-admin/packages/components/src/timeline/timeline-group.js new file mode 100644 index 00000000000..717d984c5e2 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/timeline-group.js @@ -0,0 +1,113 @@ +/** + * External dependencies + */ +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import TimelineItem from './timeline-item'; +import { sortByDateUsing } from './util'; + +const TimelineGroup = ( props ) => { + const { group, className, orderBy, clockFormat } = props; + const groupClassName = classnames( + 'woocommerce-timeline-group', + className + ); + const itemsToTimlineItem = ( item, itemIndex ) => { + const itemKey = group.title + '-' + itemIndex; + return ( + + ); + }; + + return ( +
  • +

    + { group.title } +

    +
      + { group.items + .sort( sortByDateUsing( orderBy ) ) + .map( itemsToTimlineItem ) } +
    +
    +
  • + ); +}; + +TimelineGroup.propTypes = { + /** + * Additional CSS classes. + */ + className: PropTypes.string, + /** + * The group to render. + */ + group: PropTypes.shape( { + /** + * The group title. + */ + title: PropTypes.string, + /** + * An array of list items. + */ + items: PropTypes.arrayOf( + PropTypes.shape( { + /** + * Date for the timeline item. + */ + date: PropTypes.instanceOf( Date ).isRequired, + /** + * Icon for the Timeline item. + */ + icon: PropTypes.element.isRequired, + /** + * Headline displayed for the list item. + */ + headline: PropTypes.oneOfType( [ + PropTypes.element, + PropTypes.string, + ] ).isRequired, + /** + * Body displayed for the list item. + */ + body: PropTypes.arrayOf( + PropTypes.oneOfType( [ + PropTypes.element, + PropTypes.string, + ] ) + ), + /** + * Allows users to toggle the timestamp on or off. + */ + hideTimestamp: PropTypes.bool, + } ) + ).isRequired, + } ).isRequired, + /** + * Defines how items should be ordered. + */ + orderBy: PropTypes.oneOf( [ 'asc', 'desc' ] ), + /** + * The PHP clock format string used to format times, see php.net/date. + */ + clockFormat: PropTypes.string, +}; + +TimelineGroup.defaultProps = { + className: '', + group: { + title: '', + items: [], + }, + orderBy: 'desc', +}; + +export default TimelineGroup; diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/timeline-item.js b/plugins/woocommerce-admin/packages/components/src/timeline/timeline-item.js new file mode 100644 index 00000000000..43dc83ebe39 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/timeline-item.js @@ -0,0 +1,81 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { format } from '@wordpress/date'; +import PropTypes from 'prop-types'; + +const TimelineItem = ( props ) => { + const { item, className, clockFormat } = props; + + const itemClassName = classnames( 'woocommerce-timeline-item', className ); + const itemTimeString = format( clockFormat, item.date ); + + return ( +
  • +
    +
    +
    + { item.icon } + { item.headline } +
    + + { item.hideTimestamp || false ? null : itemTimeString } + +
    +
    + { ( item.body || [] ).map( ( bodyItem, index ) => ( + + { bodyItem } + + ) ) } +
    +
  • + ); +}; + +TimelineItem.propTypes = { + /** + * Additional CSS classes. + */ + className: PropTypes.string, + /** + * An array of list items. + */ + item: PropTypes.shape( { + /** + * Date for the timeline item. + */ + date: PropTypes.instanceOf( Date ).isRequired, + /** + * Icon for the Timeline item. + */ + icon: PropTypes.element.isRequired, + /** + * Headline displayed for the list item. + */ + headline: PropTypes.oneOfType( [ PropTypes.element, PropTypes.string ] ) + .isRequired, + /** + * Body displayed for the list item. + */ + body: PropTypes.arrayOf( + PropTypes.oneOfType( [ PropTypes.element, PropTypes.string ] ) + ), + /** + * Allows users to toggle the timestamp on or off. + */ + hideTimestamp: PropTypes.bool, + /** + * The PHP clock format string used to format times, see php.net/date. + */ + clockFormat: PropTypes.string, + } ).isRequired, +}; + +TimelineItem.defaultProps = { + className: '', + item: {}, +}; + +export default TimelineItem; diff --git a/plugins/woocommerce-admin/packages/components/src/timeline/util.js b/plugins/woocommerce-admin/packages/components/src/timeline/util.js new file mode 100644 index 00000000000..5f58b71cfc9 --- /dev/null +++ b/plugins/woocommerce-admin/packages/components/src/timeline/util.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import moment from 'moment'; + +const orderByOptions = { + ASC: 'asc', + DESC: 'desc', +}; + +const groupByOptions = { + DAY: 'day', + WEEK: 'week', + MONTH: 'month', +}; + +const sortAscending = ( groupA, groupB ) => + groupA.date.getTime() - groupB.date.getTime(); +const sortDescending = ( groupA, groupB ) => + groupB.date.getTime() - groupA.date.getTime(); + +const sortByDateUsing = ( orderBy ) => { + switch ( orderBy ) { + case orderByOptions.ASC: + return sortAscending; + case orderByOptions.DESC: + default: + return sortDescending; + } +}; + +const groupItemsUsing = ( groupBy ) => ( groups, newItem ) => { + // Helper functions defined to make the logic a bit more readable. + const hasSameMoment = ( group, item ) => { + return moment( group.date ).isSame( moment( item.date ), groupBy ); + }; + const groupIndexExists = ( index ) => index >= 0; + const groupForItem = groups.findIndex( ( group ) => + hasSameMoment( group, newItem ) + ); + + if ( ! groupIndexExists( groupForItem ) ) { + // Create new group for newItem. + return [ + ...groups, + { + date: newItem.date, + items: [ newItem ], + }, + ]; + } + + groups[ groupForItem ].items.push( newItem ); + return groups; +}; + +export { groupByOptions, groupItemsUsing, orderByOptions, sortByDateUsing };