Skip to content

Commit

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

* 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

1 comment on commit 2b5e253

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 2b5e253.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/13307717532
📝 Reported issues:

Please sign in to comment.