Skip to content

Commit

Permalink
Improve the EntitiesSavedStates modal dialog design and labeling (Wor…
Browse files Browse the repository at this point in the history
…dPress#67792)

* Move buttons at the bottom when rendered withim a modal dialog.

* Refine styling.

* Make modal dialog header visible and fix labeling.

* Fix label and description when used with modal behavior.

* Try modal dialog small size.

* Adjust changes list margins.

* Use default font size and color for the changes items.

* Fix displaying of longer checkbox labels.

* Reduce changes count paragraph bottom margin.

* Use more generic variant prop.

* Polish.

* Update variant prop doc.

Co-authored-by: afercia <[email protected]>
Co-authored-by: carolinan <[email protected]>
Co-authored-by: jameskoster <[email protected]>
Co-authored-by: jasmussen <[email protected]>
Co-authored-by: fcoveram <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: dhruvang21 <[email protected]>
Co-authored-by: paaljoachim <[email protected]>
Co-authored-by: SaxonF <[email protected]>
  • Loading branch information
10 people authored Feb 13, 2025
1 parent ad5c6c1 commit 2b5e253
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 72 deletions.
24 changes: 16 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,
variant,
} ) => {
const isDirtyProps = useEntitiesSavedStatesIsDirty();
let activateSaveLabel;
if ( isDirtyProps.isDirty ) {
Expand Down Expand Up @@ -76,22 +80,28 @@ const EntitiesSavedStatesForPreview = ( { onClose, renderDialog } ) => {
saveEnabled: true,
saveLabel: activateSaveLabel,
renderDialog,
variant,
} }
/>
);
};

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

Expand Down Expand Up @@ -130,12 +140,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 } variant="inline" />
</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.variant_ `string`: Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.

_Returns_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export default function EntityRecordItem( { record, checked, onChange } ) {
}
checked={ checked }
onChange={ onChange }
className="entities-saved-states__change-control"
/>
</PanelRow>
{ hasPostMetaChanges && (
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
156 changes: 98 additions & 58 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 @@ -32,15 +37,21 @@ function identity( values ) {
* @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 {string} props.variant Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.
*
* @return {React.ReactNode} The rendered component.
*/
export default function EntitiesSavedStates( { close, renderDialog } ) {
export default function EntitiesSavedStates( {
close,
renderDialog,
variant,
} ) {
const isDirtyProps = useIsDirty();
return (
<EntitiesSavedStatesExtensible
close={ close }
renderDialog={ renderDialog }
variant={ variant }
{ ...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 {string} props.variant Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.
*
* @return {React.ReactNode} The rendered component.
*/
Expand All @@ -74,6 +86,7 @@ export function EntitiesSavedStatesExtensible( {
isDirty,
setUnselectedEntities,
unselectedEntities,
variant = 'default',
} ) {
const saveButtonRef = useRef();
const { saveDirtyEntities } = unlock( useDispatch( editorStore ) );
Expand Down Expand Up @@ -109,83 +122,100 @@ 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 isInline = variant === 'inline';

const actionButtons = (
<>
<FlexItem
isBlock={ isInline ? false : true }
as={ Button }
variant={ isInline ? 'tertiary' : 'secondary' }
size={ isInline ? undefined : 'compact' }
onClick={ dismissPanel }
>
{ __( 'Cancel' ) }
</FlexItem>
<FlexItem
isBlock={ isInline ? false : true }
as={ Button }
ref={ saveButtonRef }
variant="primary"
size={ isInline ? 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-inline': isInline,
} ) }
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>
{ ! isInline && (
<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 +228,16 @@ export function EntitiesSavedStatesExtensible( {
/>
);
} ) }

{ isInline && (
<Flex
direction="row"
justify="flex-end"
className="entities-saved-states__panel-footer"
>
{ actionButtons }
</Flex>
) }
</div>
);
}
45 changes: 40 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,49 @@
}
}

.entities-saved-states__description-heading {
font-size: $default-font-size;
.entities-saved-states__panel.is-inline {
.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-10;
}

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

.entities-saved-states__change-control {
flex: 1;
}

.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

0 comments on commit 2b5e253

Please sign in to comment.