Present Inbox notes in a panel with header (https://github.com/woocommerce/woocommerce-admin/pull/7864)
* Wrap notes with Card component -- Added header, badge, and cllipsis menu * CSS changes to adjust padding and margin * Change buttons to link style * Render image only when the layout is thumbnail * Add hover state * Update text and classname * Dismiss note immediately without prompting a confirmation (https://github.com/woocommerce/woocommerce-admin/pull/7868) * use woocommerce-admin-dismiss-notification class to target the dissmiss btn * Add changelog * Specify CSS styles so tha they dont get overridden by other plugins * Update changelog * Remove message gap * Remove unwanted changes from package-lock.json * Update changelog
This commit is contained in:
parent
a6828f915c
commit
a70d331cdf
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: Update
|
||||
|
||||
Update the inbox panel with the new design #7864
|
|
@ -1,8 +1,4 @@
|
|||
.woocommerce-layout__activity-panel-content {
|
||||
.woocommerce-inbox-message,
|
||||
.woocommerce-notification-panels > div {
|
||||
margin-top: $gap-large;
|
||||
}
|
||||
|
||||
.woocommerce-abbreviated-notifications {
|
||||
border-top: 1px solid $gray-200;
|
||||
|
|
|
@ -3,7 +3,13 @@
|
|||
*/
|
||||
import { __, _n } from '@wordpress/i18n';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { EmptyContent, Section } from '@woocommerce/components';
|
||||
import {
|
||||
EmptyContent,
|
||||
Section,
|
||||
Badge,
|
||||
EllipsisMenu,
|
||||
} from '@woocommerce/components';
|
||||
import { Card, CardHeader, Button } from '@wordpress/components';
|
||||
import {
|
||||
NOTES_STORE_NAME,
|
||||
useUserPreferences,
|
||||
|
@ -14,8 +20,8 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import {
|
||||
InboxNoteCard,
|
||||
InboxDismissConfirmationModal,
|
||||
InboxNotePlaceholder,
|
||||
Text,
|
||||
} from '@woocommerce/experimental';
|
||||
|
||||
/**
|
||||
|
@ -53,7 +59,7 @@ const renderNotes = ( {
|
|||
isBatchUpdating,
|
||||
lastRead,
|
||||
notes,
|
||||
onDismiss,
|
||||
dismissNote,
|
||||
onNoteActionClick,
|
||||
} ) => {
|
||||
if ( isBatchUpdating ) {
|
||||
|
@ -78,31 +84,51 @@ const renderNotes = ( {
|
|||
const notesArray = Object.keys( notes ).map( ( key ) => notes[ key ] );
|
||||
|
||||
return (
|
||||
<TransitionGroup role="menu">
|
||||
{ notesArray.map( ( note ) => {
|
||||
const { id: noteId, is_deleted: isDeleted } = note;
|
||||
if ( isDeleted ) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CSSTransition
|
||||
key={ noteId }
|
||||
timeout={ 500 }
|
||||
classNames="woocommerce-inbox-message"
|
||||
>
|
||||
<InboxNoteCard
|
||||
<Card size="large">
|
||||
<CardHeader size="medium">
|
||||
<div className="wooocommerce-inbox-card__header">
|
||||
<Text size="20" lineHeight="28px" variant="title.small">
|
||||
{ __( 'Inbox', 'woocommerce-admin' ) }
|
||||
</Text>
|
||||
<Badge count={ notesArray.length } />
|
||||
</div>
|
||||
<EllipsisMenu
|
||||
label={ __( 'Inbox Notes Options', 'woocommerce-admin' ) }
|
||||
renderContent={ ( {} ) => (
|
||||
<div className="woocommerce-inbox-card__section-controls">
|
||||
<Button>
|
||||
{ __( 'Hide this', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
</CardHeader>
|
||||
<TransitionGroup role="menu">
|
||||
{ notesArray.map( ( note ) => {
|
||||
const { id: noteId, is_deleted: isDeleted } = note;
|
||||
if ( isDeleted ) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CSSTransition
|
||||
key={ noteId }
|
||||
note={ note }
|
||||
lastRead={ lastRead }
|
||||
onDismiss={ onDismiss }
|
||||
onNoteActionClick={ onNoteActionClick }
|
||||
onBodyLinkClick={ onBodyLinkClick }
|
||||
onNoteVisible={ onNoteVisible }
|
||||
/>
|
||||
</CSSTransition>
|
||||
);
|
||||
} ) }
|
||||
</TransitionGroup>
|
||||
timeout={ 500 }
|
||||
classNames="woocommerce-inbox-message"
|
||||
>
|
||||
<InboxNoteCard
|
||||
key={ noteId }
|
||||
note={ note }
|
||||
lastRead={ lastRead }
|
||||
onDismiss={ dismissNote }
|
||||
onNoteActionClick={ onNoteActionClick }
|
||||
onBodyLinkClick={ onBodyLinkClick }
|
||||
onNoteVisible={ onNoteVisible }
|
||||
/>
|
||||
</CSSTransition>
|
||||
);
|
||||
} ) }
|
||||
</TransitionGroup>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -131,13 +157,9 @@ const INBOX_QUERY = {
|
|||
|
||||
const InboxPanel = () => {
|
||||
const { createNotice } = useDispatch( 'core/notices' );
|
||||
const {
|
||||
batchUpdateNotes,
|
||||
removeAllNotes,
|
||||
removeNote,
|
||||
updateNote,
|
||||
triggerNoteAction,
|
||||
} = useDispatch( NOTES_STORE_NAME );
|
||||
const { removeNote, updateNote, triggerNoteAction } = useDispatch(
|
||||
NOTES_STORE_NAME
|
||||
);
|
||||
const { isError, isResolvingNotes, isBatchUpdating, notes } = useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
|
@ -159,7 +181,6 @@ const InboxPanel = () => {
|
|||
);
|
||||
const { updateUserPreferences, ...userPrefs } = useUserPreferences();
|
||||
const [ lastRead ] = useState( userPrefs.activity_panel_inbox_last_read );
|
||||
const [ dismiss, setDismiss ] = useState();
|
||||
|
||||
useEffect( () => {
|
||||
const mountTime = Date.now();
|
||||
|
@ -170,80 +191,46 @@ const InboxPanel = () => {
|
|||
updateUserPreferences( userDataFields );
|
||||
}, [] );
|
||||
|
||||
const onDismiss = ( note, type ) => {
|
||||
setDismiss( { note, type } );
|
||||
};
|
||||
|
||||
const closeDismissModal = async ( confirmed = false ) => {
|
||||
const noteNameDismissAll = dismiss.type === 'all' ? true : false;
|
||||
const dismissNote = ( note ) => {
|
||||
const screen = getScreenName();
|
||||
|
||||
recordEvent( 'inbox_action_dismiss', {
|
||||
note_name: dismiss.note.name,
|
||||
note_title: dismiss.note.title,
|
||||
note_name_dismiss_all: noteNameDismissAll,
|
||||
note_name_dismiss_confirmation: confirmed,
|
||||
note_name: note.name,
|
||||
note_title: note.title,
|
||||
note_name_dismiss_all: false,
|
||||
note_name_dismiss_confirmation: true,
|
||||
screen,
|
||||
} );
|
||||
|
||||
if ( confirmed ) {
|
||||
const noteId = dismiss.note.id;
|
||||
const removeAll = ! noteId || noteNameDismissAll;
|
||||
try {
|
||||
let notesRemoved = [];
|
||||
if ( removeAll ) {
|
||||
notesRemoved = await removeAllNotes( {
|
||||
status: INBOX_QUERY.status,
|
||||
} );
|
||||
} else {
|
||||
const noteRemoved = await removeNote( noteId );
|
||||
notesRemoved = [ noteRemoved ];
|
||||
}
|
||||
setDismiss( undefined );
|
||||
createNotice(
|
||||
'success',
|
||||
notesRemoved.length > 1
|
||||
? __( 'All messages dismissed', 'woocommerce-admin' )
|
||||
: __( 'Message dismissed', 'woocommerce-admin' ),
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: __( 'Undo', 'woocommerce-admin' ),
|
||||
onClick: () => {
|
||||
if ( notesRemoved.length > 1 ) {
|
||||
batchUpdateNotes(
|
||||
notesRemoved.map(
|
||||
( note ) => note.id
|
||||
),
|
||||
{
|
||||
is_deleted: 0,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
updateNote( noteId, {
|
||||
is_deleted: 0,
|
||||
} );
|
||||
}
|
||||
},
|
||||
const noteId = note.id;
|
||||
try {
|
||||
removeNote( noteId );
|
||||
createNotice(
|
||||
'success',
|
||||
__( 'Message dismissed', 'woocommerce-admin' ),
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: __( 'Undo', 'woocommerce-admin' ),
|
||||
onClick: () => {
|
||||
updateNote( noteId, {
|
||||
is_deleted: 0,
|
||||
} );
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
} catch ( e ) {
|
||||
const numberOfNotes = removeAll ? notes.length : 1;
|
||||
createNotice(
|
||||
'error',
|
||||
_n(
|
||||
'Message could not be dismissed',
|
||||
'Messages could not be dismissed',
|
||||
numberOfNotes,
|
||||
'woocommerce-admin'
|
||||
)
|
||||
);
|
||||
setDismiss( undefined );
|
||||
}
|
||||
} else {
|
||||
setDismiss( undefined );
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
} catch ( e ) {
|
||||
createNotice(
|
||||
'error',
|
||||
_n(
|
||||
'Message could not be dismissed',
|
||||
'Messages could not be dismissed',
|
||||
1,
|
||||
'woocommerce-admin'
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -292,16 +279,10 @@ const InboxPanel = () => {
|
|||
isBatchUpdating,
|
||||
lastRead,
|
||||
notes,
|
||||
onDismiss,
|
||||
dismissNote,
|
||||
onNoteActionClick,
|
||||
} ) }
|
||||
</Section>
|
||||
{ dismiss && (
|
||||
<InboxDismissConfirmationModal
|
||||
onClose={ closeDismissModal }
|
||||
onDismiss={ () => closeDismissModal( true ) }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -34,3 +34,9 @@
|
|||
transform: translateX(50%);
|
||||
transition: opacity 500ms, transform 500ms, max-height 500ms;
|
||||
}
|
||||
|
||||
.wooocommerce-inbox-card__header {
|
||||
.woocommerce-badge {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Unreleased
|
||||
|
||||
- Renaming remindMeLater() to onSnooze() for consistency. #7616
|
||||
- Applied new Inbox 2.0 design to the inbox-note component. #7864
|
||||
|
||||
# 2.0.3
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ export const InboxNoteActionButton: React.FC< InboxNoteActionProps > = ( {
|
|||
|
||||
return (
|
||||
<Button
|
||||
isSecondary
|
||||
isLink={ true }
|
||||
isBusy={ inAction }
|
||||
disabled={ inAction }
|
||||
href={ href }
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createElement, Fragment, useState, useRef } from '@wordpress/element';
|
||||
import { Button, Dropdown, Popover } from '@wordpress/components';
|
||||
import { Button } from '@wordpress/components';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
import moment from 'moment';
|
||||
import classnames from 'classnames';
|
||||
|
@ -51,17 +51,13 @@ type InboxNote = {
|
|||
type InboxNoteProps = {
|
||||
note: InboxNote;
|
||||
lastRead: number;
|
||||
onDismiss?: ( note: InboxNote, type: 'all' | 'note' ) => void;
|
||||
onDismiss?: ( note: InboxNote ) => void;
|
||||
onNoteActionClick?: ( note: InboxNote, action: InboxNoteAction ) => void;
|
||||
onBodyLinkClick?: ( note: InboxNote, link: string ) => void;
|
||||
onNoteVisible?: ( note: InboxNote ) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const DropdownWithPopoverProps = Dropdown as React.ComponentType<
|
||||
Dropdown.Props & { popoverProps: Omit< Popover.Props, 'children' > }
|
||||
>;
|
||||
|
||||
const InboxNoteCard: React.FC< InboxNoteProps > = ( {
|
||||
note,
|
||||
lastRead,
|
||||
|
@ -73,7 +69,6 @@ const InboxNoteCard: React.FC< InboxNoteProps > = ( {
|
|||
} ) => {
|
||||
const [ clickedActionText, setClickedActionText ] = useState( false );
|
||||
const hasBeenSeen = useRef( false );
|
||||
const toggleButtonRef = useRef< HTMLButtonElement >( null );
|
||||
const linkCallbackRef = useCallbackOnLinkClick( ( innerLink ) => {
|
||||
if ( onBodyLinkClick ) {
|
||||
onBodyLinkClick( note, innerLink );
|
||||
|
@ -91,101 +86,18 @@ const InboxNoteCard: React.FC< InboxNoteProps > = ( {
|
|||
}
|
||||
};
|
||||
|
||||
const handleBlur = ( event: React.FocusEvent, onClose: () => void ) => {
|
||||
const dropdownClasses = [
|
||||
'woocommerce-admin-dismiss-notification',
|
||||
'components-popover__content',
|
||||
'components-dropdown__content',
|
||||
];
|
||||
// This line is for IE compatibility.
|
||||
let relatedTarget: EventTarget | Element | null = null;
|
||||
if ( event.relatedTarget ) {
|
||||
relatedTarget = event.relatedTarget;
|
||||
} else if ( toggleButtonRef.current ) {
|
||||
const ownerDoc = toggleButtonRef.current.ownerDocument;
|
||||
relatedTarget = ownerDoc ? ownerDoc.activeElement : null;
|
||||
}
|
||||
let isClickOutsideDropdown = false;
|
||||
if ( relatedTarget && 'className' in relatedTarget ) {
|
||||
const classNames = relatedTarget.className;
|
||||
isClickOutsideDropdown = dropdownClasses.some( ( cName ) =>
|
||||
classNames.includes( cName )
|
||||
);
|
||||
}
|
||||
if ( isClickOutsideDropdown ) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const onDropdownDismiss = (
|
||||
type: 'note' | 'all',
|
||||
onToggle: () => void
|
||||
) => {
|
||||
if ( onDismiss ) {
|
||||
onDismiss( note, type );
|
||||
}
|
||||
onToggle();
|
||||
};
|
||||
|
||||
const renderDismissButton = () => {
|
||||
if ( clickedActionText ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownWithPopoverProps
|
||||
contentClassName="woocommerce-admin-dismiss-dropdown"
|
||||
position="bottom right"
|
||||
renderToggle={ ( { onClose, onToggle } ) => (
|
||||
<Button
|
||||
isTertiary
|
||||
onClick={ ( event: React.MouseEvent ) => {
|
||||
( event.target as HTMLElement ).focus();
|
||||
onToggle();
|
||||
} }
|
||||
ref={ toggleButtonRef }
|
||||
onBlur={ ( event: React.FocusEvent ) =>
|
||||
handleBlur( event, onClose )
|
||||
}
|
||||
>
|
||||
{ __( 'Dismiss', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
focusOnMount={ false }
|
||||
popoverProps={ { noArrow: true } }
|
||||
renderContent={ ( { onToggle } ) => (
|
||||
<ul>
|
||||
<li>
|
||||
<Button
|
||||
className="woocommerce-admin-dismiss-notification"
|
||||
onClick={ () =>
|
||||
onDropdownDismiss( 'note', onToggle )
|
||||
}
|
||||
>
|
||||
{ __(
|
||||
'Dismiss this message',
|
||||
'woocommerce-admin'
|
||||
) }
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
className="woocommerce-admin-dismiss-notification"
|
||||
onClick={ () =>
|
||||
onDropdownDismiss( 'all', onToggle )
|
||||
}
|
||||
>
|
||||
{ __(
|
||||
'Dismiss all messages',
|
||||
'woocommerce-admin'
|
||||
) }
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
) }
|
||||
/>
|
||||
<Button
|
||||
className="woocommerce-admin-dismiss-notification"
|
||||
onClick={ () => onDismiss && onDismiss( note ) }
|
||||
>
|
||||
{ __( 'Dismiss', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -249,7 +161,7 @@ const InboxNoteCard: React.FC< InboxNoteProps > = ( {
|
|||
! dateCreatedGmt ||
|
||||
new Date( dateCreatedGmt + 'Z' ).getTime() > lastRead;
|
||||
const date = dateCreated;
|
||||
const hasImage = layout !== 'plain' && layout !== '';
|
||||
const hasImage = layout === 'thumbnail';
|
||||
const cardClassName = classnames(
|
||||
'woocommerce-inbox-message',
|
||||
className,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
background: $studio-white;
|
||||
border-radius: 2px;
|
||||
@include font-size( 13 );
|
||||
margin: 0 0 $gap-large;
|
||||
margin: 0 0;
|
||||
-ms-box-orient: horizontal;
|
||||
&.banner {
|
||||
-webkit-flex-direction: column;
|
||||
|
@ -22,13 +22,21 @@
|
|||
height: 100%;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: $gray-100;
|
||||
cursor: pointer;
|
||||
.woocommerce-inbox-message__actions button.woocommerce-admin-dismiss-notification {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-homepage-column & {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
&:not(.is-placeholder) {
|
||||
border: 1px solid $gray-200;
|
||||
border: 0;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
.line {
|
||||
|
@ -91,6 +99,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-inbox-message__wrapper .woocommerce-inbox-message__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.woocommerce-inbox-message__text {
|
||||
color: $gray-700;
|
||||
font-style: normal;
|
||||
|
@ -114,9 +126,6 @@
|
|||
}
|
||||
|
||||
.woocommerce-inbox-message__actions {
|
||||
padding-top: $gap;
|
||||
border-top: 1px solid $gray-200;
|
||||
|
||||
// Ensures any immediate child with a sibling has space between the items
|
||||
& > * + * {
|
||||
margin-left: 0.5em;
|
||||
|
@ -125,7 +134,21 @@
|
|||
a,
|
||||
button {
|
||||
cursor: pointer;
|
||||
&.is-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
border-top: 0;
|
||||
|
||||
button.woocommerce-admin-dismiss-notification {
|
||||
color: $gray-700;
|
||||
&:hover {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.components-dropdown {
|
||||
display: inline;
|
||||
|
||||
|
@ -150,6 +173,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.woocommerce-inbox-message__wrapper .woocommerce-inbox-message__actions {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.woocommerce-inbox-dismiss-confirmation_modal {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ describe( 'InboxNoteCard', () => {
|
|||
} );
|
||||
|
||||
describe( 'callbacks', () => {
|
||||
it( 'should call onDismiss with note type when "Dismiss this message" is clicked', () => {
|
||||
it( 'should call onDismiss with note when "Dismiss this message" is clicked', () => {
|
||||
const onDismiss = jest.fn();
|
||||
const { getByText } = render(
|
||||
<InboxNoteCard
|
||||
|
@ -166,23 +166,7 @@ describe( 'InboxNoteCard', () => {
|
|||
/>
|
||||
);
|
||||
userEvent.click( getByText( 'Dismiss' ) );
|
||||
userEvent.click( getByText( 'Dismiss this message' ) );
|
||||
expect( onDismiss ).toHaveBeenCalledWith( note, 'note' );
|
||||
} );
|
||||
|
||||
it( 'should call onDismiss with all type when "Dismiss all messages" is clicked', () => {
|
||||
const onDismiss = jest.fn();
|
||||
const { getByText } = render(
|
||||
<InboxNoteCard
|
||||
key={ note.id }
|
||||
note={ note }
|
||||
lastRead={ lastRead }
|
||||
onDismiss={ onDismiss }
|
||||
/>
|
||||
);
|
||||
userEvent.click( getByText( 'Dismiss' ) );
|
||||
userEvent.click( getByText( 'Dismiss all messages' ) );
|
||||
expect( onDismiss ).toHaveBeenCalledWith( note, 'all' );
|
||||
expect( onDismiss ).toHaveBeenCalledWith( note );
|
||||
} );
|
||||
|
||||
it( 'should call onNoteActionClick with specific action when action is clicked', () => {
|
||||
|
|
Loading…
Reference in New Issue