Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the EntitiesSavedStates modal dialog design and labeling #67792

Open
wants to merge 7 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions packages/edit-site/src/components/save-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ const { EntitiesSavedStatesExtensible, NavigableRegion } =
unlock( privateApis );
const { useLocation } = unlock( routerPrivateApis );

const EntitiesSavedStatesForPreview = ( { onClose, renderDialog } ) => {
const EntitiesSavedStatesForPreview = ( {
onClose,
renderDialog,
isWithinModalDialog,
} ) => {
const isDirtyProps = useEntitiesSavedStatesIsDirty();
let activateSaveLabel;
if ( isDirtyProps.isDirty ) {
Expand Down Expand Up @@ -76,22 +80,32 @@ const EntitiesSavedStatesForPreview = ( { onClose, renderDialog } ) => {
saveEnabled: true,
saveLabel: activateSaveLabel,
renderDialog,
isWithinModalDialog,
} }
/>
);
};

const _EntitiesSavedStates = ( { onClose, renderDialog } ) => {
const _EntitiesSavedStates = ( {
onClose,
renderDialog,
isWithinModalDialog,
} ) => {
if ( isPreviewingTheme() ) {
return (
<EntitiesSavedStatesForPreview
onClose={ onClose }
renderDialog={ renderDialog }
isWithinModalDialog={ isWithinModalDialog }
/>
);
}
return (
<EntitiesSavedStates close={ onClose } renderDialog={ renderDialog } />
<EntitiesSavedStates
close={ onClose }
renderDialog={ renderDialog }
isWithinModalDialog={ isWithinModalDialog }
/>
);
};

Expand Down Expand Up @@ -130,12 +144,10 @@ export default function SavePanel() {
<Modal
className="edit-site-save-panel__modal"
onRequestClose={ onClose }
__experimentalHideHeader
contentLabel={ __(
'Save site, content, and template changes'
) }
title={ __( 'Review changes' ) }
size="small"
>
<_EntitiesSavedStates onClose={ onClose } />
<_EntitiesSavedStates onClose={ onClose } isWithinModalDialog />
</Modal>
) : null;
}
Expand Down
1 change: 1 addition & 0 deletions packages/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ _Parameters_
- _props_ `Object`: The component props.
- _props.close_ `Function`: The function to close the dialog.
- _props.renderDialog_ `boolean`: Whether to render the component with modal dialog behavior.
- _props.isWithinModalDialog_ `boolean`: Whether this component is rendered within a Modal component.

_Returns_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ export default function EntityTypeList( {
}

return (
<PanelBody title={ entityLabel } initialOpen>
<PanelBody
title={ entityLabel }
initialOpen
className="entities-saved-states__panel-body"
>
<EntityDescription record={ firstRecord } count={ count } />
{ list.map( ( record ) => {
return (
Expand Down
160 changes: 99 additions & 61 deletions packages/editor/src/components/entities-saved-states/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import clsx from 'clsx';

/**
* WordPress dependencies
*/
Expand Down Expand Up @@ -29,18 +34,24 @@ function identity( values ) {
/**
* Renders the component for managing saved states of entities.
*
* @param {Object} props The component props.
* @param {Function} props.close The function to close the dialog.
* @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior.
* @param {Object} props The component props.
* @param {Function} props.close The function to close the dialog.
* @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior.
* @param {boolean} props.isWithinModalDialog Whether this component is rendered within a Modal component.
*
* @return {React.ReactNode} The rendered component.
*/
export default function EntitiesSavedStates( { close, renderDialog } ) {
export default function EntitiesSavedStates( {
close,
renderDialog,
isWithinModalDialog,
} ) {
const isDirtyProps = useIsDirty();
return (
<EntitiesSavedStatesExtensible
close={ close }
renderDialog={ renderDialog }
isWithinModalDialog={ isWithinModalDialog }
{ ...isDirtyProps }
/>
);
Expand All @@ -60,6 +71,7 @@ export default function EntitiesSavedStates( { close, renderDialog } ) {
* @param {boolean} props.isDirty Flag indicating if there are dirty entities.
* @param {Function} props.setUnselectedEntities Function to set unselected entities.
* @param {Array} props.unselectedEntities Array of unselected entities.
* @param {boolean} props.isWithinModalDialog Whether this component is rendered within a Modal component.
*
* @return {React.ReactNode} The rendered component.
*/
Expand All @@ -74,6 +86,7 @@ export function EntitiesSavedStatesExtensible( {
isDirty,
setUnselectedEntities,
unselectedEntities,
isWithinModalDialog,
} ) {
const saveButtonRef = useRef();
const { saveDirtyEntities } = unlock( useDispatch( editorStore ) );
Expand Down Expand Up @@ -109,83 +122,98 @@ export function EntitiesSavedStatesExtensible( {
const [ saveDialogRef, saveDialogProps ] = useDialog( {
onClose: () => dismissPanel(),
} );
const dialogLabel = useInstanceId( EntitiesSavedStatesExtensible, 'label' );
const dialogDescription = useInstanceId(
const dialogLabelId = useInstanceId(
EntitiesSavedStatesExtensible,
'entities-saved-states__panel-label'
);
const dialogDescriptionId = useInstanceId(
EntitiesSavedStatesExtensible,
'description'
'entities-saved-states__panel-description'
);

const selectItemsToSaveDescription = !! dirtyEntityRecords.length
? __( 'Select the items you want to save.' )
: undefined;

const actionButtons = (
<>
<FlexItem
isBlock={ isWithinModalDialog ? false : true }
as={ Button }
variant={ isWithinModalDialog ? 'tertiary' : 'secondary' }
size={ isWithinModalDialog ? undefined : 'compact' }
onClick={ dismissPanel }
>
{ __( 'Cancel' ) }
</FlexItem>
<FlexItem
isBlock={ isWithinModalDialog ? false : true }
as={ Button }
ref={ saveButtonRef }
variant="primary"
size={ isWithinModalDialog ? undefined : 'compact' }
disabled={ ! saveEnabled }
accessibleWhenDisabled
onClick={ () =>
saveDirtyEntities( {
onSave,
dirtyEntityRecords,
entitiesToSkip: unselectedEntities,
close,
} )
}
className="editor-entities-saved-states__save-button"
>
{ saveLabel }
</FlexItem>
</>
);

return (
<div
ref={ renderDialog ? saveDialogRef : undefined }
{ ...( renderDialog && saveDialogProps ) }
className="entities-saved-states__panel"
className={ clsx( 'entities-saved-states__panel', {
'is-within-modal-dialog': isWithinModalDialog,
} ) }
role={ renderDialog ? 'dialog' : undefined }
aria-labelledby={ renderDialog ? dialogLabel : undefined }
aria-describedby={ renderDialog ? dialogDescription : undefined }
aria-labelledby={ renderDialog ? dialogLabelId : undefined }
aria-describedby={ renderDialog ? dialogDescriptionId : undefined }
>
<Flex className="entities-saved-states__panel-header" gap={ 2 }>
<FlexItem
isBlock
as={ Button }
variant="secondary"
size="compact"
onClick={ dismissPanel }
>
{ __( 'Cancel' ) }
</FlexItem>
<FlexItem
isBlock
as={ Button }
ref={ saveButtonRef }
variant="primary"
size="compact"
disabled={ ! saveEnabled }
accessibleWhenDisabled
onClick={ () =>
saveDirtyEntities( {
onSave,
dirtyEntityRecords,
entitiesToSkip: unselectedEntities,
close,
} )
}
className="editor-entities-saved-states__save-button"
>
{ saveLabel }
</FlexItem>
</Flex>
{ ! isWithinModalDialog && (
<Flex className="entities-saved-states__panel-header" gap={ 2 }>
{ actionButtons }
</Flex>
) }

<div className="entities-saved-states__text-prompt">
<div
className="entities-saved-states__text-prompt--header-wrapper"
id={ renderDialog ? dialogLabel : undefined }
>
<strong className="entities-saved-states__text-prompt--header">
<div className="entities-saved-states__text-prompt--header-wrapper">
<strong
id={ renderDialog ? dialogLabelId : undefined }
className="entities-saved-states__text-prompt--header"
>
{ __( 'Are you ready to save?' ) }
</strong>
{ additionalPrompt }
</div>
<p id={ renderDialog ? dialogDescription : undefined }>
{ isDirty
? createInterpolateElement(
sprintf(
/* translators: %d: number of site changes waiting to be saved. */
_n(
'There is <strong>%d site change</strong> waiting to be saved.',
'There are <strong>%d site changes</strong> waiting to be saved.',
<div id={ renderDialog ? dialogDescriptionId : undefined }>
{ additionalPrompt }
<p className="entities-saved-states__text-prompt--changes-count">
{ isDirty
? createInterpolateElement(
sprintf(
/* translators: %d: number of site changes waiting to be saved. */
_n(
'There is <strong>%d site change</strong> waiting to be saved.',
'There are <strong>%d site changes</strong> waiting to be saved.',
dirtyEntityRecords.length
),
dirtyEntityRecords.length
),
dirtyEntityRecords.length
),
{ strong: <strong /> }
)
: selectItemsToSaveDescription }
</p>
{ strong: <strong /> }
)
: selectItemsToSaveDescription }
</p>
</div>
</div>

{ sortedPartitionedSavables.map( ( list ) => {
Expand All @@ -198,6 +226,16 @@ export function EntitiesSavedStatesExtensible( {
/>
);
} ) }

{ isWithinModalDialog && (
<Flex
direction="row"
justify="flex-end"
className="entities-saved-states__panel-footer"
>
{ actionButtons }
</Flex>
) }
</div>
);
}
41 changes: 36 additions & 5 deletions packages/editor/src/components/entities-saved-states/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,45 @@
}
}

.entities-saved-states__description-heading {
font-size: $default-font-size;
.entities-saved-states__panel.is-within-modal-dialog {
.entities-saved-states__text-prompt {
padding: 0;
}

.entities-saved-states__panel-body {
padding-left: 0;
padding-right: 0;
border: 0;

> h2 {
margin-left: -1 * $grid-unit-20;
margin-right: -1 * $grid-unit-20;
margin-bottom: 0;

button {
font-size: $font-size-x-small;
text-transform: uppercase;
}
}
}

.entities-saved-states__text-prompt--header-wrapper {
display: none;
}

.entities-saved-states__text-prompt--changes-count {
margin-top: 0;
margin-bottom: $grid-unit-30;
}

.entities-saved-states__panel-footer {
margin-top: $grid-unit-20;
}
}

.entities-saved-states__changes {
color: $gray-700;
font-size: $helptext-font-size;
margin: $grid-unit-10 $grid-unit-20 0 $grid-unit-20;
font-size: $default-font-size;
margin: $grid-unit-05 $grid-unit-20 0 $grid-unit-30;
list-style: disc;

li {
Expand Down
Loading