Update task list component to make use of new experimental list (https://github.com/woocommerce/woocommerce-admin/pull/6849)

* Add initial task item component with the new task list

* Switch components to functional components

* Some minor updates from the last rebase

* Fix missing variables.

* Fix tests

* Add animation for the experimental list

* Fix lint error

* Add changelog

* Fix E2E tests

* Update PR suggestions and export list from experimental package

* Fix dismiss styling issue

Co-authored-by: Jeff Stieler <jeff.m.stieler@gmail.com>
This commit is contained in:
louwie17 2021-04-27 12:23:34 -03:00 committed by GitHub
parent 93e78028a0
commit 071a68b950
14 changed files with 665 additions and 649 deletions

View File

@ -3,10 +3,9 @@
*/
import { __ } from '@wordpress/i18n';
import { MenuGroup, MenuItem } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import { check } from '@wordpress/icons';
import { useState, useEffect } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import {
ONBOARDING_STORE_NAME,
OPTIONS_STORE_NAME,
@ -24,25 +23,78 @@ import { getAllTasks } from './tasks';
import { getCountryCode } from '../dashboard/utils';
import TaskList from './list';
import { DisplayOption } from '../header/activity-panel/display-options';
import { TaskStep } from './task-step';
export class TaskDashboard extends Component {
constructor( props ) {
super( props );
this.state = {
isCartModalOpen: false,
};
const taskDashboardSelect = ( select ) => {
const { getProfileItems, getTasksStatus } = select( ONBOARDING_STORE_NAME );
const { getSettings } = select( SETTINGS_STORE_NAME );
const { getOption } = select( OPTIONS_STORE_NAME );
const {
getActivePlugins,
getInstalledPlugins,
isJetpackConnected,
} = select( PLUGINS_STORE_NAME );
const profileItems = getProfileItems();
this.toggleExtensionTaskList = this.toggleExtensionTaskList.bind(
this
);
}
componentDidMount() {
const trackedCompletedTasks =
getOption( 'woocommerce_task_list_tracked_completed_tasks' ) || [];
const { general: generalSettings = {} } = getSettings( 'general' );
const countryCode = getCountryCode(
generalSettings.woocommerce_default_country
);
const activePlugins = getActivePlugins();
const installedPlugins = getInstalledPlugins();
const onboardingStatus = getTasksStatus();
return {
activePlugins,
countryCode,
dismissedTasks: getOption( 'woocommerce_task_list_dismissed_tasks' ),
isExtendedTaskListComplete:
getOption( 'woocommerce_extended_task_list_complete' ) === 'yes',
isExtendedTaskListHidden:
getOption( 'woocommerce_extended_task_list_hidden' ) === 'yes',
isJetpackConnected: isJetpackConnected(),
isSetupTaskListHidden:
getOption( 'woocommerce_task_list_hidden' ) === 'yes',
isTaskListComplete:
getOption( 'woocommerce_task_list_complete' ) === 'yes',
installedPlugins,
onboardingStatus,
profileItems,
trackedCompletedTasks,
};
};
const TaskDashboard = ( { userPreferences, query } ) => {
const { createNotice } = useDispatch( 'core/notices' );
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
const {
trackedCompletedTasks,
activePlugins,
countryCode,
installedPlugins,
isJetpackConnected,
onboardingStatus,
profileItems,
isSetupTaskListHidden,
dismissedTasks,
isTaskListComplete,
isExtendedTaskListHidden,
isExtendedTaskListComplete,
} = useSelect( taskDashboardSelect );
const [ isCartModalOpen, setIsCartModalOpen ] = useState( false );
useEffect( () => {
document.body.classList.add( 'woocommerce-onboarding' );
document.body.classList.add( 'woocommerce-task-dashboard__body' );
}
}, [] );
getTaskStartedCount = ( taskName ) => {
const { userPreferences } = this.props;
const getTaskStartedCount = ( taskName ) => {
const trackedStartedTasks =
userPreferences.task_list_tracked_started_tasks;
if ( ! trackedStartedTasks || ! trackedStartedTasks[ taskName ] ) {
@ -51,8 +103,7 @@ export class TaskDashboard extends Component {
return trackedStartedTasks[ taskName ];
};
updateTrackStartedCount = ( taskName, newCount ) => {
const { userPreferences } = this.props;
const updateTrackStartedCount = ( taskName, newCount ) => {
const trackedStartedTasks =
userPreferences.task_list_tracked_started_tasks || {};
userPreferences.updateUserPreferences( {
@ -63,26 +114,24 @@ export class TaskDashboard extends Component {
} );
};
isTaskCompleted = ( taskName ) => {
const { trackedCompletedTasks } = this.props;
const isTaskCompleted = ( taskName ) => {
if ( ! trackedCompletedTasks ) {
return false;
}
return trackedCompletedTasks.includes( taskName );
};
onTaskSelect = ( taskName ) => {
const trackStartedCount = this.getTaskStartedCount( taskName );
const onTaskSelect = ( taskName ) => {
const trackStartedCount = getTaskStartedCount( taskName );
recordEvent( 'tasklist_click', {
task_name: taskName,
} );
if ( ! this.isTaskCompleted( taskName ) ) {
this.updateTrackStartedCount( taskName, trackStartedCount + 1 );
if ( ! isTaskCompleted( taskName ) ) {
updateTrackStartedCount( taskName, trackStartedCount + 1 );
}
};
toggleExtensionTaskList = () => {
const { isExtendedTaskListHidden, updateOptions } = this.props;
const toggleExtensionTaskList = () => {
const newValue = ! isExtendedTaskListHidden;
recordEvent(
@ -93,184 +142,121 @@ export class TaskDashboard extends Component {
} );
};
getAllTasks() {
const {
activePlugins,
countryCode,
createNotice,
installAndActivatePlugins,
installedPlugins,
isJetpackConnected,
onboardingStatus,
profileItems,
query,
} = this.props;
return getAllTasks( {
activePlugins,
countryCode,
createNotice,
installAndActivatePlugins,
installedPlugins,
isJetpackConnected,
onboardingStatus,
profileItems,
query,
toggleCartModal: this.toggleCartModal.bind( this ),
onTaskSelect: this.onTaskSelect,
} );
}
toggleCartModal() {
const { isCartModalOpen } = this.state;
const toggleCartModal = () => {
if ( ! isCartModalOpen ) {
recordEvent( 'tasklist_purchase_extensions' );
}
this.setState( { isCartModalOpen: ! isCartModalOpen } );
}
setIsCartModalOpen( ! isCartModalOpen );
};
render() {
const {
dismissedTasks,
isExtendedTaskListComplete,
isExtendedTaskListHidden,
isSetupTaskListHidden,
isTaskListComplete,
query,
trackedCompletedTasks,
} = this.props;
const { isCartModalOpen } = this.state;
const allTasks = this.getAllTasks();
const { extension, setup: setupTasks } = allTasks;
const getCurrentTask = ( tasks ) => {
const { task } = query;
const currentTask = tasks.find( ( s ) => s.key === task );
const extensionTasks =
Array.isArray( extension ) &&
extension.sort( ( a, b ) => {
if ( Boolean( a.completed ) === Boolean( b.completed ) ) {
return 0;
}
if ( ! currentTask ) {
return null;
}
return a.completed ? 1 : -1;
} );
return currentTask;
};
const allTasks = getAllTasks( {
activePlugins,
countryCode,
createNotice,
installAndActivatePlugins,
installedPlugins,
isJetpackConnected,
onboardingStatus,
profileItems,
query,
toggleCartModal,
onTaskSelect,
} );
const { extension, setup: setupTasks } = allTasks;
const { task } = query;
const extensionTasks =
Array.isArray( extension ) &&
extension.sort( ( a, b ) => {
if ( Boolean( a.completed ) === Boolean( b.completed ) ) {
return 0;
}
return a.completed ? 1 : -1;
} );
const currentTask = getCurrentTask( [
...( extensionTasks || [] ),
...( setupTasks || [] ),
] );
if ( task && ! currentTask ) {
return null;
}
if ( currentTask ) {
return (
<>
{ setupTasks && ( ! isSetupTaskListHidden || task ) && (
<TaskList
dismissedTasks={ dismissedTasks || [] }
isComplete={ isTaskListComplete }
query={ query }
tasks={ setupTasks }
title={ __(
'Get ready to start selling',
'woocommerce-admin'
) }
trackedCompletedTasks={ trackedCompletedTasks || [] }
/>
) }
{ extensionTasks && (
<DisplayOption>
<MenuGroup
className="woocommerce-layout__homescreen-display-options"
label={ __( 'Display', 'woocommerce-admin' ) }
>
<MenuItem
className="woocommerce-layout__homescreen-extension-tasklist-toggle"
icon={ ! isExtendedTaskListHidden && check }
isSelected={ ! isExtendedTaskListHidden }
role="menuitemcheckbox"
onClick={ this.toggleExtensionTaskList }
>
{ __(
'Show things to do next',
'woocommerce-admin'
) }
</MenuItem>
</MenuGroup>
</DisplayOption>
) }
{ extensionTasks && ! isExtendedTaskListHidden && (
<TaskList
dismissedTasks={ dismissedTasks || [] }
isComplete={ isExtendedTaskListComplete }
name={ 'extended_task_list' }
query={ query }
tasks={ extensionTasks }
title={ __( 'Things to do next', 'woocommerce-admin' ) }
trackedCompletedTasks={ trackedCompletedTasks || [] }
/>
) }
{ isCartModalOpen && (
<CartModal
onClose={ () => this.toggleCartModal() }
onClickPurchaseLater={ () => this.toggleCartModal() }
/>
) }
</>
<TaskStep taskContainer={ currentTask.container } query={ query } />
);
}
}
export default compose(
withSelect( ( select ) => {
const { getProfileItems, getTasksStatus } = select(
ONBOARDING_STORE_NAME
);
const { getSettings } = select( SETTINGS_STORE_NAME );
const { getOption } = select( OPTIONS_STORE_NAME );
const {
getActivePlugins,
getInstalledPlugins,
isJetpackConnected,
} = select( PLUGINS_STORE_NAME );
const profileItems = getProfileItems();
return (
<>
{ setupTasks && ( ! isSetupTaskListHidden || task ) && (
<TaskList
dismissedTasks={ dismissedTasks || [] }
isComplete={ isTaskListComplete }
query={ query }
tasks={ setupTasks }
title={ __(
'Get ready to start selling',
'woocommerce-admin'
) }
trackedCompletedTasks={ trackedCompletedTasks || [] }
/>
) }
{ extensionTasks && (
<DisplayOption>
<MenuGroup
className="woocommerce-layout__homescreen-display-options"
label={ __( 'Display', 'woocommerce-admin' ) }
>
<MenuItem
className="woocommerce-layout__homescreen-extension-tasklist-toggle"
icon={ ! isExtendedTaskListHidden && check }
isSelected={ ! isExtendedTaskListHidden }
role="menuitemcheckbox"
onClick={ toggleExtensionTaskList }
>
{ __(
'Show things to do next',
'woocommerce-admin'
) }
</MenuItem>
</MenuGroup>
</DisplayOption>
) }
{ extensionTasks && ! isExtendedTaskListHidden && (
<TaskList
dismissedTasks={ dismissedTasks || [] }
isComplete={ isExtendedTaskListComplete }
name={ 'extended_task_list' }
query={ query }
tasks={ extensionTasks }
title={ __( 'Things to do next', 'woocommerce-admin' ) }
trackedCompletedTasks={ trackedCompletedTasks || [] }
/>
) }
{ isCartModalOpen && (
<CartModal
onClose={ () => toggleCartModal() }
onClickPurchaseLater={ () => toggleCartModal() }
/>
) }
</>
);
};
const trackedCompletedTasks =
getOption( 'woocommerce_task_list_tracked_completed_tasks' ) || [];
const { general: generalSettings = {} } = getSettings( 'general' );
const countryCode = getCountryCode(
generalSettings.woocommerce_default_country
);
const activePlugins = getActivePlugins();
const installedPlugins = getInstalledPlugins();
const onboardingStatus = getTasksStatus();
return {
activePlugins,
countryCode,
dismissedTasks: getOption(
'woocommerce_task_list_dismissed_tasks'
),
isExtendedTaskListComplete:
getOption( 'woocommerce_extended_task_list_complete' ) ===
'yes',
isExtendedTaskListHidden:
getOption( 'woocommerce_extended_task_list_hidden' ) === 'yes',
isJetpackConnected: isJetpackConnected(),
isSetupTaskListHidden:
getOption( 'woocommerce_task_list_hidden' ) === 'yes',
isTaskListComplete:
getOption( 'woocommerce_task_list_complete' ) === 'yes',
installedPlugins,
onboardingStatus,
profileItems,
trackedCompletedTasks,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
return {
createNotice,
installAndActivatePlugins,
updateOptions,
};
} )
)( TaskDashboard );
export default TaskDashboard;

View File

@ -2,55 +2,62 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component, cloneElement } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import classNames from 'classnames';
import { useEffect, useRef } from '@wordpress/element';
import { Button, Card, CardBody, CardHeader } from '@wordpress/components';
import { withDispatch, withSelect } from '@wordpress/data';
import { Icon, check } from '@wordpress/icons';
import { List, EllipsisMenu, Badge } from '@woocommerce/components';
import { updateQueryString } from '@woocommerce/navigation';
import { useSelect, useDispatch } from '@wordpress/data';
import {
PLUGINS_STORE_NAME,
OPTIONS_STORE_NAME,
ONBOARDING_STORE_NAME,
SETTINGS_STORE_NAME,
} from '@woocommerce/data';
EllipsisMenu,
Badge,
__experimentalList as List,
} from '@woocommerce/components';
import { updateQueryString } from '@woocommerce/navigation';
import { OPTIONS_STORE_NAME, ONBOARDING_STORE_NAME } from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
import { Text } from '@woocommerce/experimental';
/**
* Internal dependencies
*/
import { recordTaskViewEvent } from './tasks';
import { getCountryCode } from '../dashboard/utils';
import sanitizeHTML from '../lib/sanitize-html';
import { TaskItem } from './task-item';
export class TaskList extends Component {
componentDidMount() {
this.recordTaskView();
this.recordTaskListView();
this.possiblyCompleteTaskList();
this.possiblyTrackCompletedTasks();
}
export const TaskList = ( {
query,
name = 'task_list',
isComplete,
dismissedTasks,
tasks,
trackedCompletedTasks: totalTrackedCompletedTasks,
title: listTitle,
} ) => {
const { createNotice } = useDispatch( 'core/notices' );
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
const { profileItems } = useSelect( ( select ) => {
const { getProfileItems } = select( ONBOARDING_STORE_NAME );
componentDidUpdate( prevProps ) {
const { query } = this.props;
const { query: prevQuery } = prevProps;
const { task: prevTask } = prevQuery;
return {
profileItems: getProfileItems(),
};
} );
const prevQueryRef = useRef( query );
useEffect( () => {
recordTaskListView();
}, [] );
useEffect( () => {
const { task: prevTask } = prevQueryRef.current;
const { task } = query;
if ( prevTask !== task ) {
window.document.documentElement.scrollTop = 0;
this.recordTaskView();
prevQueryRef.current = query;
}
this.possiblyCompleteTaskList();
this.possiblyTrackCompletedTasks();
}
possiblyCompleteTaskList();
possiblyTrackCompletedTasks();
}, [ query ] );
possiblyCompleteTaskList() {
const { isComplete, name = 'task_list', updateOptions } = this.props;
const possiblyCompleteTaskList = () => {
const taskListVariableName = `woocommerce_${ name }_complete`;
const taskListToComplete = isComplete
? { [ taskListVariableName ]: 'no' }
@ -61,119 +68,78 @@ export class TaskList extends Component {
}
if (
( ! this.getIncompleteTasks().length && ! isComplete ) ||
( this.getIncompleteTasks().length && isComplete )
( ! getIncompleteTasks().length && ! isComplete ) ||
( getIncompleteTasks().length && isComplete )
) {
updateOptions( {
...taskListToComplete,
} );
}
}
};
getCompletedTaskKeys() {
return this.getVisibleTasks()
const getCompletedTaskKeys = () => {
return getVisibleTasks()
.filter( ( task ) => task.completed )
.map( ( task ) => task.key );
}
};
getIncompleteTasks() {
const { dismissedTasks, tasks } = this.props;
const getIncompleteTasks = () => {
return tasks.filter(
( task ) =>
task.visible &&
! task.completed &&
! dismissedTasks.includes( task.key )
);
}
};
shouldUpdateCompletedTasks( tasks, untrackedTasks, completedTasks ) {
if ( untrackedTasks.length > 0 ) {
return true;
}
if ( completedTasks.length === 0 ) {
return false;
}
return ! completedTasks.every(
( taskName ) => tasks.indexOf( taskName ) >= 0
);
}
getTrackedCompletedTasks( completedTasks, trackedTasks ) {
if ( ! trackedTasks ) {
return [];
}
return completedTasks.filter( ( taskName ) =>
trackedTasks.includes( taskName )
);
}
getTrackedIncompletedTasks( partialCompletedTasks, allTrackedTask ) {
return this.getVisibleTasks()
const getTrackedIncompletedTasks = (
partialCompletedTasks,
allTrackedTask
) => {
return getVisibleTasks()
.filter(
( task ) =>
allTrackedTask.includes( task.key ) &&
! partialCompletedTasks.includes( task.key )
)
.map( ( task ) => task.key );
}
};
getTasksForUpdate(
completedTaskKeys,
totalTrackedCompletedTasks,
trackedIncompleteTasks
) {
const mergedLists = [
...new Set( [
...completedTaskKeys,
...totalTrackedCompletedTasks,
] ),
];
return mergedLists.filter(
( taskName ) => ! trackedIncompleteTasks.includes( taskName )
);
}
possiblyTrackCompletedTasks() {
const {
trackedCompletedTasks: totalTrackedCompletedTasks,
updateOptions,
} = this.props;
const completedTaskKeys = this.getCompletedTaskKeys();
const trackedCompletedTasks = this.getTrackedCompletedTasks(
const possiblyTrackCompletedTasks = () => {
const completedTaskKeys = getCompletedTaskKeys();
const trackedCompletedTasks = getTrackedCompletedTasks(
completedTaskKeys,
totalTrackedCompletedTasks
);
const trackedIncompleteTasks = this.getTrackedIncompletedTasks(
const trackedIncompleteTasks = getTrackedIncompletedTasks(
trackedCompletedTasks,
totalTrackedCompletedTasks
);
if (
this.shouldUpdateCompletedTasks(
shouldUpdateCompletedTasks(
trackedCompletedTasks,
trackedIncompleteTasks,
completedTaskKeys
)
) {
updateOptions( {
woocommerce_task_list_tracked_completed_tasks: this.getTasksForUpdate(
woocommerce_task_list_tracked_completed_tasks: getTasksForUpdate(
completedTaskKeys,
totalTrackedCompletedTasks,
trackedIncompleteTasks
),
} );
}
}
dismissTask( { key, onDismiss } ) {
const { createNotice, dismissedTasks, updateOptions } = this.props;
};
const dismissTask = ( { key, onDismiss } ) => {
createNotice( 'success', __( 'Task dismissed' ), {
actions: [
{
label: __( 'Undo', 'woocommerce-admin' ),
onClick: () => this.undoDismissTask( key ),
onClick: () => undoDismissTask( key ),
},
],
} );
@ -186,11 +152,9 @@ export class TaskList extends Component {
if ( onDismiss ) {
onDismiss();
}
}
undoDismissTask( key ) {
const { dismissedTasks, updateOptions } = this.props;
};
const undoDismissTask = ( key ) => {
const updatedDismissedTasks = dismissedTasks.filter(
( task ) => task !== key
);
@ -198,55 +162,31 @@ export class TaskList extends Component {
updateOptions( {
woocommerce_task_list_dismissed_tasks: updatedDismissedTasks,
} );
}
};
getVisibleTasks() {
const { dismissedTasks, tasks } = this.props;
const getVisibleTasks = () => {
return tasks.filter(
( task ) => task.visible && ! dismissedTasks.includes( task.key )
);
}
};
recordTaskView() {
const {
isJetpackConnected,
activePlugins,
installedPlugins,
query,
} = this.props;
const { task: taskName } = query;
if ( ! taskName ) {
const recordTaskListView = () => {
if ( query.task ) {
return;
}
recordTaskViewEvent(
taskName,
isJetpackConnected,
activePlugins,
installedPlugins
);
}
recordTaskListView() {
if ( this.getCurrentTask() ) {
return;
}
const { name = 'task_list', profileItems } = this.props;
const isCoreTaskList = name === 'task_list';
const taskListName = isCoreTaskList ? 'tasklist' : 'extended_tasklist';
const tasks = this.getVisibleTasks();
const visibleTasks = getVisibleTasks();
recordEvent( `${ taskListName }_view`, {
number_tasks: tasks.length,
number_tasks: visibleTasks.length,
store_connected: profileItems.wccom_connected,
} );
}
};
hideTaskCard( action ) {
const { name = 'task_list', updateOptions } = this.props;
const hideTaskCard = ( action ) => {
const isCoreTaskList = name === 'task_list';
const taskListName = isCoreTaskList ? 'tasklist' : 'extended_tasklist';
const updateOptionsParams = {
@ -260,27 +200,15 @@ export class TaskList extends Component {
recordEvent( `${ taskListName }_completed`, {
action,
completed_task_count: this.getCompletedTaskKeys().length,
incomplete_task_count: this.getIncompleteTasks().length,
completed_task_count: getCompletedTaskKeys().length,
incomplete_task_count: getIncompleteTasks().length,
} );
updateOptions( {
...updateOptionsParams,
} );
}
};
getCurrentTask() {
const { query, tasks } = this.props;
const { task } = query;
const currentTask = tasks.find( ( s ) => s.key === task );
if ( ! currentTask ) {
return null;
}
return currentTask;
}
renderMenu() {
const renderMenu = () => {
return (
<div className="woocommerce-card__menu woocommerce-card__header-item">
<EllipsisMenu
@ -288,9 +216,7 @@ export class TaskList extends Component {
renderContent={ () => (
<div className="woocommerce-task-card__section-controls">
<Button
onClick={ () =>
this.hideTaskCard( 'remove_card' )
}
onClick={ () => hideTaskCard( 'remove_card' ) }
>
{ __( 'Hide this', 'woocommerce-admin' ) }
</Button>
@ -299,162 +225,95 @@ export class TaskList extends Component {
/>
</div>
);
}
};
render() {
const { query, title: listTitle } = this.props;
const { task: theTask } = query;
const currentTask = this.getCurrentTask();
const listTasks = getVisibleTasks().map( ( task ) => {
if ( ! task.onClick ) {
task.onClick = ( e ) => {
if ( e.target.nodeName === 'A' ) {
// This is a nested link, so don't activate this task.
return false;
}
if ( theTask && ! currentTask ) {
return null;
updateQueryString( { task: task.key } );
};
}
const listTasks = this.getVisibleTasks().map( ( task ) => {
task.className = classNames(
task.completed ? 'is-complete' : null,
task.className
);
task.before = (
<div className="woocommerce-task__icon">
{ task.completed && <Icon icon={ check } /> }
</div>
);
return task;
} );
task.title = (
<Text
as="div"
variant={ task.completed ? 'body.small' : 'button' }
if ( ! listTasks.length ) {
return <div className="woocommerce-task-dashboard__container"></div>;
}
return (
<>
<div className="woocommerce-task-dashboard__container">
<Card
size="large"
className="woocommerce-task-card woocommerce-homescreen-card"
>
{ task.title }
{ task.additionalInfo && (
<div
className="woocommerce-task__additional-info"
dangerouslySetInnerHTML={ sanitizeHTML(
task.additionalInfo
) }
></div>
) }
{ task.time && ! task.completed && (
<div className="woocommerce-task__estimated-time">
{ task.time }
<CardHeader size="medium">
<div className="wooocommerce-task-card__header">
<Text variant="title.small">{ listTitle }</Text>
<Badge count={ getIncompleteTasks().length } />
</div>
) }
</Text>
);
{ renderMenu() }
</CardHeader>
<CardBody>
<List animation="slide-right">
{ listTasks.map( ( task ) => (
<TaskItem
key={ task.key }
title={ task.title }
completed={ task.completed }
content={ task.content }
onClick={ task.onClick }
isDismissable={ task.isDismissable }
onDismiss={ () => dismissTask( task ) }
time={ task.time }
/>
) ) }
</List>
</CardBody>
</Card>
</div>
</>
);
};
if ( ! task.completed && task.isDismissable ) {
task.after = (
<Button
data-testid={ `${ task.key }-dismiss-button` }
isTertiary
onClick={ ( event ) => {
event.stopPropagation();
this.dismissTask( task );
} }
>
{ __( 'Dismiss', 'woocommerce-admin' ) }
</Button>
);
}
if ( ! task.onClick ) {
task.onClick = ( e ) => {
if ( e.target.nodeName === 'A' ) {
// This is a nested link, so don't activate this task.
return false;
}
updateQueryString( { task: task.key } );
};
}
return task;
} );
if ( ! listTasks.length ) {
return (
<div className="woocommerce-task-dashboard__container"></div>
);
}
return (
<>
<div className="woocommerce-task-dashboard__container">
{ currentTask ? (
cloneElement( currentTask.container, {
query,
} )
) : (
<>
<Card
size="large"
className="woocommerce-task-card woocommerce-homescreen-card"
>
<CardHeader size="medium">
<div className="wooocommerce-task-card__header">
<Text variant="title.small">
{ listTitle }
</Text>
<Badge
count={
this.getIncompleteTasks().length
}
/>
</div>
{ this.renderMenu() }
</CardHeader>
<CardBody>
<List items={ listTasks } />
</CardBody>
</Card>
</>
) }
</div>
</>
);
function shouldUpdateCompletedTasks( tasks, untrackedTasks, completedTasks ) {
if ( untrackedTasks.length > 0 ) {
return true;
}
if ( completedTasks.length === 0 ) {
return false;
}
return ! completedTasks.every(
( taskName ) => tasks.indexOf( taskName ) >= 0
);
}
export default compose(
withSelect( ( select ) => {
const { getProfileItems, getTasksStatus } = select(
ONBOARDING_STORE_NAME
);
const { getSettings } = select( SETTINGS_STORE_NAME );
const {
getActivePlugins,
getInstalledPlugins,
isJetpackConnected,
} = select( PLUGINS_STORE_NAME );
const profileItems = getProfileItems();
const { general: generalSettings = {} } = getSettings( 'general' );
const countryCode = getCountryCode(
generalSettings.woocommerce_default_country
);
function getTrackedCompletedTasks( completedTasks, trackedTasks ) {
if ( ! trackedTasks ) {
return [];
}
return completedTasks.filter( ( taskName ) =>
trackedTasks.includes( taskName )
);
}
const activePlugins = getActivePlugins();
const installedPlugins = getInstalledPlugins();
const onboardingStatus = getTasksStatus();
function getTasksForUpdate(
completedTaskKeys,
totalTrackedCompletedTasks,
trackedIncompleteTasks
) {
const mergedLists = [
...new Set( [ ...completedTaskKeys, ...totalTrackedCompletedTasks ] ),
];
return mergedLists.filter(
( taskName ) => ! trackedIncompleteTasks.includes( taskName )
);
}
return {
activePlugins,
countryCode,
isJetpackConnected: isJetpackConnected(),
installedPlugins,
onboardingStatus,
profileItems,
};
} ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( 'core/notices' );
const { updateOptions } = dispatch( OPTIONS_STORE_NAME );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
return {
createNotice,
installAndActivatePlugins,
updateOptions,
};
} )
)( TaskList );
export default TaskList;

View File

@ -0,0 +1,54 @@
$foreground-color: var(--wp-admin-theme-color);
.woocommerce-task-list__item {
.woocommerce-task-list__item-title {
color: $foreground-color;
}
.woocommerce-task-list__item-before {
margin-right: 20px;
display: flex;
align-items: center;
}
.woocommerce-task-list__item-after {
margin-left: $gap;
display: flex;
align-items: center;
margin-left: auto;
}
.woocommerce-task-list__item-before .woocommerce-task__icon {
border-radius: 50%;
width: 32px;
height: 32px;
}
.woocommerce-task-list__item-before .woocommerce-task__icon svg {
fill: $white;
position: relative;
top: 4px;
left: 5px;
}
.woocommerce-task-list__item-text {
.woocommerce-pill {
padding: 1px $gap-smaller;
margin-left: $gap-smaller;
}
}
&.is-complete {
.woocommerce-task__icon {
background-color: var(--wp-admin-theme-color);
}
.woocommerce-task-list__item-title {
color: $gray-700;
}
.woocommerce-task-list__item-content {
display: none;
}
}
}

View File

@ -0,0 +1,101 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Icon, check } from '@wordpress/icons';
import { Button } from '@wordpress/components';
import { Text } from '@woocommerce/experimental';
import { __experimentalListItem as ListItem } from '@woocommerce/components';
import classnames from 'classnames';
/**
* Internal dependencies
*/
import './task-item.scss';
import sanitizeHTML from '../lib/sanitize-html';
type TaskItemProps = {
title: string;
completed: boolean;
onClick: () => void;
isDismissable?: boolean;
onDismiss?: () => void;
additionalInfo?: string;
time?: string;
content?: string;
expanded?: boolean;
};
export const TaskItem: React.FC< TaskItemProps > = ( {
completed,
title,
isDismissable,
onDismiss,
onClick,
additionalInfo,
time,
content,
expanded = false,
} ) => {
const className = classnames( 'woocommerce-task-list__item', {
'is-complete': completed,
} );
return (
<ListItem
disableGutters={ false }
className={ className }
onClick={ onClick }
animation="slide-right"
>
<div className="woocommerce-task-list__item-before">
<div className="woocommerce-task__icon">
{ completed && <Icon icon={ check } /> }
</div>
</div>
<div className="woocommerce-task-list__item-text">
<span className="woocommerce-task-list__item-title">
<Text
as="div"
variant={ completed ? 'body.small' : 'button' }
>
{ title }
{ additionalInfo && (
<div
className="woocommerce-task__additional-info"
dangerouslySetInnerHTML={ sanitizeHTML(
additionalInfo
) }
></div>
) }
{ expanded && content && (
<div className="woocommerce-task-list__item-content">
{ content }
</div>
) }
{ time && ! completed && (
<div className="woocommerce-task__estimated-time">
{ time }
</div>
) }
</Text>
</span>
</div>
{ onDismiss && isDismissable && ! completed && (
<div className="woocommerce-task-list__item-after">
<Button
data-testid={ `dismiss-button` }
isTertiary
onClick={ (
event: React.MouseEvent | React.KeyboardEvent
) => {
event.stopPropagation();
onDismiss();
} }
>
{ __( 'Dismiss', 'woocommerce-admin' ) }
</Button>
</div>
) }
</ListItem>
);
};

View File

@ -0,0 +1,73 @@
/**
* External dependencies
*/
import { cloneElement, useRef, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { PLUGINS_STORE_NAME, WCDataSelector } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { recordTaskViewEvent } from './tasks';
type TaskStepProps = {
taskContainer?: React.ReactElement;
query: { task?: string };
};
export const TaskStep: React.FC< TaskStepProps > = ( {
taskContainer,
query,
} ) => {
const prevTaskRef = useRef< string >();
const { isJetpackConnected, activePlugins, installedPlugins } = useSelect(
( select: WCDataSelector ) => {
const {
getActivePlugins,
getInstalledPlugins,
isJetpackConnected: getIsJetpackConnected,
} = select( PLUGINS_STORE_NAME );
return {
activePlugins: getActivePlugins(),
isJetpackConnected: getIsJetpackConnected(),
installedPlugins: getInstalledPlugins(),
};
}
);
const recordTaskView = () => {
const { task: taskName } = query;
if ( ! taskName ) {
return;
}
recordTaskViewEvent(
taskName,
isJetpackConnected,
activePlugins,
installedPlugins
);
};
useEffect( () => {
const { task } = query;
if ( prevTaskRef.current !== task ) {
window.document.documentElement.scrollTop = 0;
}
prevTaskRef.current = task;
recordTaskView();
}, [ query ] );
if ( ! taskContainer || ! query.task ) {
return null;
}
return (
<div className="woocommerce-task-dashboard__container">
{ cloneElement( taskContainer, {
query,
} ) }
</div>
);
};

View File

@ -12,23 +12,45 @@ import userEvent from '@testing-library/user-event';
import apiFetch from '@wordpress/api-fetch';
import { SlotFillProvider } from '@wordpress/components';
import { recordEvent } from '@woocommerce/tracks';
import { useDispatch, useSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import { TaskDashboard } from '../index.js';
import TaskDashboard from '../';
import { TaskList } from '../list';
import { getAllTasks } from '../tasks';
import { DisplayOption } from '../../header/activity-panel/display-options';
jest.mock( '@wordpress/api-fetch' );
jest.mock( '../tasks' );
jest.mock( '../tasks', () => ( {
recordTaskViewEvent: jest.fn(),
getAllTasks: jest.fn(),
} ) );
jest.mock( '../../dashboard/components/cart-modal', () => null );
jest.mock( '@woocommerce/tracks', () => ( { recordEvent: jest.fn() } ) );
jest.mock( '@woocommerce/data', () => ( {} ) );
jest.mock( '@wordpress/data', () => ( {
...jest.requireActual( '@wordpress/data' ),
useSelect: jest.fn().mockReturnValue( {
profileItems: {},
} ),
useDispatch: jest.fn(),
} ) );
const TASK_LIST_HEADING = 'Get ready to start selling';
const EXTENDED_TASK_LIST_HEADING = 'Things to do next';
describe( 'TaskDashboard and TaskList', () => {
const updateOptions = jest.fn();
const createNotice = jest.fn();
beforeEach( () => {
useDispatch.mockImplementation( () => ( {
updateOptions,
createNotice,
installAndActivatePlugins: jest.fn(),
} ) );
} );
afterEach( () => jest.clearAllMocks() );
const MockTask = () => {
return <div>mock task</div>;
@ -124,14 +146,7 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'renders the "Get ready to start selling" and "Things to do next" tasks lists', async () => {
apiFetch.mockResolvedValue( {} );
getAllTasks.mockReturnValue( tasks );
const { container } = render(
<TaskDashboard
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
updateOptions={ () => {} }
/>
);
const { container } = render( <TaskDashboard query={ {} } /> );
// Wait for the setup task list to render.
expect(
@ -147,14 +162,7 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'renders a dismiss button for tasks that are optional and incomplete', async () => {
apiFetch.mockResolvedValue( {} );
getAllTasks.mockReturnValue( tasks );
const { container } = render(
<TaskDashboard
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
updateOptions={ () => {} }
/>
);
const { container } = render( <TaskDashboard query={ {} } /> );
// The `optional` task has a dismiss button.
expect(
@ -176,13 +184,7 @@ describe( 'TaskDashboard and TaskList', () => {
apiFetch.mockResolvedValue( {} );
getAllTasks.mockReturnValue( tasks );
const { container } = render(
<TaskDashboard
dismissedTasks={ [] }
isSetupTaskListHidden={ true }
profileItems={ {} }
query={ { task: 'optional' } }
updateOptions={ () => {} }
/>
<TaskDashboard query={ { task: 'optional' } } />
);
// Wait for the task to render.
@ -190,17 +192,14 @@ describe( 'TaskDashboard and TaskList', () => {
} );
it( 'renders only the extended task list', () => {
useSelect.mockImplementation( () => ( {
dismissedTasks: [],
isSetupTaskListHidden: true,
profileItems: {},
} ) );
apiFetch.mockResolvedValue( {} );
getAllTasks.mockReturnValue( tasks );
const { queryByText } = render(
<TaskDashboard
dismissedTasks={ [] }
isSetupTaskListHidden={ true }
profileItems={ {} }
query={ {} }
updateOptions={ () => {} }
/>
);
const { queryByText } = render( <TaskDashboard query={ {} } /> );
expect( queryByText( TASK_LIST_HEADING ) ).toBeNull();
@ -208,14 +207,11 @@ describe( 'TaskDashboard and TaskList', () => {
} );
it( 'sets homescreen layout default when dismissed', () => {
const updateOptions = jest.fn();
const { getByRole } = render(
<TaskList
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
dismissedTasks={ [] }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
tasks={ shorterTasksList }
/>
);
@ -232,15 +228,12 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets homescreen layout default when completed', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
act( () => {
render(
<TaskList
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
dismissedTasks={ [] }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
tasks={ shorterTasksList }
/>
);
@ -254,16 +247,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'hides the setup task list if there are no visible tasks', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { setup } = tasks;
const { queryByText } = render(
<TaskList
dismissedTasks={ [ 'optional', 'required', 'completed' ] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ [ ...setup, notVisibleTask ] }
/>
);
@ -273,16 +263,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'hides the extended task list if there are no visible tasks', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { extension } = tasks;
const { queryByText } = render(
<TaskList
dismissedTasks={ [ 'extension' ] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ [ ...extension, notVisibleTask ] }
/>
);
@ -292,16 +279,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets setup tasks list as completed', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
act( () => {
render(
<TaskList
dismissedTasks={ [] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
tasks={ shorterTasksList }
/>
);
@ -315,16 +299,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets extended tasks list as completed', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
act( () => {
render(
<TaskList
dismissedTasks={ [] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ shorterTasksList }
name={ 'extended_task_list' }
/>
@ -338,17 +319,14 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets setup tasks list (with only dismissed tasks) as completed', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { setup } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [ 'optional', 'required', 'completed' ] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ setup }
/>
);
@ -362,17 +340,14 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets extended tasks list (with only dismissed tasks) as completed', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { extension } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [ 'extension' ] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ extension }
name={ 'extended_task_list' }
/>
@ -386,17 +361,14 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets setup tasks list as incomplete', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { setup } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [] }
isComplete={ true }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
tasks={ [ ...setup ] }
/>
);
@ -410,17 +382,14 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'sets extended tasks list as incomplete', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { extension } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [] }
isComplete={ true }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ shorterTasksList }
updateOptions={ updateOptions }
tasks={ extension }
name={ 'extended_task_list' }
/>
@ -434,16 +403,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'adds an untracked completed task', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { setup, extension } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ [ ...setup, ...extension ] }
/>
);
@ -456,16 +422,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'removes an incomplete but already tracked task from tracked tasks list', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { setup, extension } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [ 'completed', 'extension' ] }
updateOptions={ updateOptions }
tasks={ [ ...setup, ...extension ] }
/>
);
@ -478,16 +441,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'adds an untracked completed task and removes an incomplete but already tracked task from tracked tasks list', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const { setup, extension } = tasks;
act( () => {
render(
<TaskList
dismissedTasks={ [] }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [ 'extension' ] }
updateOptions={ updateOptions }
tasks={ [ ...setup, ...extension ] }
/>
);
@ -500,15 +460,12 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'does not add untracked completed (but dismissed) tasks', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
act( () => {
render(
<TaskList
dismissedTasks={ [ 'completed-1' ] }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
tasks={ shorterTasksList }
/>
);
@ -521,18 +478,13 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'dismisses a task', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const createNotice = jest.fn();
const { extension } = tasks;
const { getByText } = render(
<TaskList
dismissedTasks={ [] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
createNotice={ createNotice }
tasks={ extension }
name={ 'extended_task_list' }
/>
@ -547,8 +499,6 @@ describe( 'TaskDashboard and TaskList', () => {
it( 'calls the "onDismiss" callback after dismissing a task', () => {
apiFetch.mockResolvedValue( {} );
const updateOptions = jest.fn();
const createNotice = jest.fn();
const callback = jest.fn();
const { extension } = tasks;
extension[ 0 ].onDismiss = callback;
@ -556,11 +506,8 @@ describe( 'TaskDashboard and TaskList', () => {
<TaskList
dismissedTasks={ [] }
isComplete={ false }
profileItems={ {} }
query={ {} }
trackedCompletedTasks={ [] }
updateOptions={ updateOptions }
createNotice={ createNotice }
tasks={ extension }
name={ 'extended_task_list' }
/>
@ -571,23 +518,22 @@ describe( 'TaskDashboard and TaskList', () => {
} );
it( 'sorts the extended task list tasks by completion status', () => {
useSelect.mockImplementation( () => ( {
dismissedTasks: [],
isSetupTaskListHidden: true,
profileItems: {},
} ) );
apiFetch.mockResolvedValue( {} );
getAllTasks.mockReturnValue( {
extension: [ completedExtensionTask, ...tasks.extension ],
} );
const { queryAllByRole, queryByText } = render(
<TaskDashboard
dismissedTasks={ [] }
isSetupTaskListHidden={ true }
profileItems={ {} }
query={ {} }
updateOptions={ () => {} }
/>
const { queryByText, container } = render(
<TaskDashboard query={ {} } />
);
const visibleTasks = container.querySelectorAll( 'li' );
expect( queryByText( EXTENDED_TASK_LIST_HEADING ) ).not.toBeNull();
const visibleTasks = queryAllByRole( 'menuitem' );
expect( visibleTasks ).toHaveLength( 2 );
expect( visibleTasks[ 0 ] ).toHaveTextContent(
'This task is an extension'
@ -598,18 +544,10 @@ describe( 'TaskDashboard and TaskList', () => {
} );
it( 'correctly toggles the extension task list', () => {
const updateOptions = jest.fn();
const { getByText } = render(
<SlotFillProvider>
<DisplayOption.Slot />
<TaskDashboard
dismissedTasks={ [] }
isSetupTaskListHidden={ true }
profileItems={ {} }
query={ {} }
updateOptions={ updateOptions }
/>
<TaskDashboard query={ {} } />
</SlotFillProvider>
);

View File

@ -8538,6 +8538,16 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
},
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
"dev": true,
"optional": true,
"requires": {
"@types/node": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz",
@ -9246,7 +9256,7 @@
"@wordpress/element": "2.19.0",
"@wordpress/i18n": "3.17.0",
"@wordpress/notices": "^2.11.0",
"classnames": "2.2.6",
"classnames": "2.3.1",
"prop-types": "15.7.2",
"react-transition-group": "4.4.1"
},
@ -9264,9 +9274,9 @@
}
},
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==",
"dev": true
}
}
@ -11992,12 +12002,6 @@
"fill-range": "^7.0.1"
}
},
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -12221,18 +12225,6 @@
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"dependencies": {
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
"dev": true,
"optional": true,
"requires": {
"@types/node": "*"
}
}
}
},
"fast-glob": {
@ -12878,30 +12870,6 @@
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dev": true,
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"dev": true,
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
}
}
}
},
"read-pkg": {
@ -37475,6 +37443,26 @@
}
}
},
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"dev": true,
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
},
"dependencies": {
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
}
}
},
"tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
@ -38288,6 +38276,16 @@
"which-boxed-primitive": "^1.0.2"
}
},
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"dev": true,
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
}
},
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",

View File

@ -27,8 +27,8 @@ export { default as ImageUpload } from './image-upload';
export { default as Link } from './link';
export {
default as List,
ExperimentalList,
ExperimentalListItem,
ExperimentalList as __experimentalList,
ExperimentalListItem as __experimentalListItem,
} from './list';
export { default as MenuItem } from './ellipsis-menu/menu-item';
export { default as MenuTitle } from './ellipsis-menu/menu-title';

View File

@ -49,6 +49,7 @@ export const ExperimentalList: React.FC< ListProps > = ( {
in={ inTransition }
enter={ enter }
exit={ exit }
classNames="woocommerce-list__item"
>
{ cloneElement( child, {
animation: animationProp,

View File

@ -26,7 +26,7 @@
"@wordpress/element": "2.19.0",
"@wordpress/i18n": "3.17.0",
"@wordpress/notices": "^2.11.0",
"classnames": "2.2.6",
"classnames": "2.3.1",
"prop-types": "15.7.2",
"react-transition-group": "4.4.1"
},

View File

@ -68,4 +68,5 @@ export type PluginSelectors = {
getActivePlugins: WPDataSelector< typeof getActivePlugins >;
getInstalledPlugins: WPDataSelector< typeof getInstalledPlugins >;
getRecommendedPlugins: WPDataSelector< typeof getRecommendedPlugins >;
isJetpackConnected: WPDataSelector< typeof isJetpackConnected >;
} & WPDataSelectors;

View File

@ -1,3 +1,7 @@
# Unreleased
- Export component ExperimentalList and ExperimentalListItem as List and ListItem.
# 1.0.0
- Initial package

View File

@ -119,6 +119,7 @@ Release and roadmap notes are available on the [WooCommerce Developers Blog](htt
- Update: Update choose niche note cta URL #6733
- Update: UI updates to Payment Task screen #6766
- Update: Adding setup required icon for non-configured payment methods #6811
- Update: Task list component with new Experimental Task list. #6849
== 2.2.0 3/30/2021 ==

View File

@ -25,11 +25,11 @@ export class WcHomescreen extends BasePage {
async getTaskList() {
await page.waitForSelector(
'.woocommerce-task-card .woocommerce-list__item-title'
'.woocommerce-task-card .woocommerce-task-list__item-title'
);
await waitForElementByText( 'p', 'Get ready to start selling' );
const list = await this.page.$$eval(
'.woocommerce-task-card .woocommerce-list__item-title',
'.woocommerce-task-card .woocommerce-task-list__item-title',
( items ) => items.map( ( item ) => item.textContent )
);
return list.map( ( item: string | null ) => {