-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Enhance: Added Dialog Wrapper for EntitiesSavedStatesExtensible
#67352
Enhance: Added Dialog Wrapper for EntitiesSavedStatesExtensible
#67352
Conversation
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @Mayank-Tripathi32! In case you missed it, we'd love to have you join us in our Slack community. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
EntitiesSavedStatesExtensible
@youknowriad Please have a look when you get time 🤞 Thanks! |
Thanks for the PR this is close to what I had in mind. There's some consideration to check about backwards compatibility of the component ... but important thing for now is to get some reviews for folks. |
A few considerations: Please have a look at a simpler approach in #67351. You will notice a couple things:
More importantly, while the wrapper approach may seem nicer, I think it makes more difficult to reuse the uniwue IDs generated for the In this PR these IDs are now generated in the In fact, the new wrapper element that is rendered around the element with class Lastly in this PR the save panel in the Thene preview doesn't open at all (because it misses the wrapper). |
@@ -160,16 +130,13 @@ export function EntitiesSavedStatesExtensible( { | |||
</Flex> | |||
|
|||
<div className="entities-saved-states__text-prompt"> | |||
<div | |||
className="entities-saved-states__text-prompt--header-wrapper" | |||
id={ renderDialog ? dialogLabel : undefined } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removing this id will make the dialog labeling fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that might be the case, Might have to look into an alternate way to pass down Ids maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes but I would argue that given we need to pass down the unique ID, how this would be better than using the current approach that passed down the renderDialog
prop? The only difference would be that the logic is abstracted in a wrapper component but I'm not sure that would be a substantial improvement code-wise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but looking at the original code, it seems that the aria tags were missing anyway. I’m not entirely sure how much impact the aria tags would have, but we could consider #67351
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, they were missing because the renderDialog
prop was not passe down correctly. Looking at the original PR that introduced thie behavior #59622, the intent is to make all 'Save panels' behave like a sort of modal dialog and use the useDialog
props and also the IDs for aria-labelledby and aria-describedby.
<strong className="entities-saved-states__text-prompt--header"> | ||
{ __( 'Are you ready to save?' ) } | ||
</strong> | ||
{ additionalPrompt } | ||
</div> | ||
<p id={ renderDialog ? dialogDescription : undefined }> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removing this id will make the dialog aria-describedby fail
* | ||
* @return {React.Element} The rendered dialog element with children. | ||
*/ | ||
export function EntitieDialogWrapper( { children, close } ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this new wrapper should be used in other palces as well, for example for the save panel in the theme preview, see EntitiesSavedStatesForPreview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of the component should make clear it is meant as a wrapper for EntitiesSavedStates
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted. I only worked on save panel as If it was acceptable solution then probably can add to theme preview aswell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few points to address
aria-labelledby={ dialogLabel } | ||
aria-describedby={ dialogDescription } | ||
> | ||
{ ' ' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this empty space?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems to be added by npm run format
, will remove it now. Thanks
<EntitiesSavedStatesExtensible
{ ...{
...isDirtyProps,
additionalPrompt,
close: onClose,
onSave,
saveEnabled: true,
saveLabel: activateSaveLabel,
} }
/>
Also, It might make more sense to clean out components a little as well. cc: @afercia
|
… updated preview panel to use wrapper
My feeling is that a wrapper would be 'nicer' from a code style perspecitve but in order to fix all the use cases the whole implementation should be cleaned out a little and better abstracted, as you pointed out. That would require soe larger refacttoring and I'm not sure it is worth it. At the end we would have a wrapper but still we would need to pass down the unique IDs to the wrapped component or find an alternative way (?) to render those IDs correctly. Basically a wrapper cannot 'attach' all the behaviors and props we need automatically and will still need to pass down props. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
If I make some changes like below I should be able to apply the id correctly but I'm not sure if this is the right approach 😅
Diff
diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js
index b4fdafacf0..d207a5a649 100644
--- a/packages/editor/src/components/entities-saved-states/index.js
+++ b/packages/editor/src/components/entities-saved-states/index.js
@@ -4,8 +4,10 @@
import { Button, Flex, FlexItem } from '@wordpress/components';
import { __, _n, sprintf } from '@wordpress/i18n';
import {
+ Children,
useCallback,
useRef,
+ cloneElement,
createInterpolateElement,
} from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
@@ -34,10 +36,14 @@ function identity( values ) {
*
* @return {JSX.Element} The rendered component.
*/
-export default function EntitiesSavedStates( { close } ) {
+export default function EntitiesSavedStates( { close, ...additionalProps } ) {
const isDirtyProps = useIsDirty();
return (
- <EntitiesSavedStatesExtensible close={ close } { ...isDirtyProps } />
+ <EntitiesSavedStatesExtensible
+ close={ close }
+ { ...additionalProps }
+ { ...isDirtyProps }
+ />
);
}
@@ -54,7 +60,8 @@ export default function EntitiesSavedStates( { close } ) {
* @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 props.labelId
+ * @param props.descriptionId
* @return {JSX.Element} The rendered component.
*/
export function EntitiesSavedStatesExtensible( {
@@ -67,6 +74,8 @@ export function EntitiesSavedStatesExtensible( {
isDirty,
setUnselectedEntities,
unselectedEntities,
+ labelId,
+ descriptionId,
} ) {
const saveButtonRef = useRef();
const { saveDirtyEntities } = unlock( useDispatch( editorStore ) );
@@ -134,13 +143,16 @@ export function EntitiesSavedStatesExtensible( {
</Flex>
<div className="entities-saved-states__text-prompt">
- <div className="entities-saved-states__text-prompt--header-wrapper">
+ <div
+ className="entities-saved-states__text-prompt--header-wrapper"
+ id={ labelId }
+ >
<strong className="entities-saved-states__text-prompt--header">
{ __( 'Are you ready to save?' ) }
</strong>
{ additionalPrompt }
</div>
- <p>
+ <p id={ descriptionId }>
{ isDirty
? createInterpolateElement(
sprintf(
@@ -193,6 +205,13 @@ export function EntitiesSavedStatesDialogWrapper( { children, close } ) {
'description'
);
+ const childrenWithId = Children.map( children, ( child ) => {
+ return cloneElement( child, {
+ labelId: dialogLabel,
+ descriptionId: dialogDescription,
+ } );
+ } );
+
return (
<div
ref={ saveDialogRef }
@@ -201,7 +220,7 @@ export function EntitiesSavedStatesDialogWrapper( { children, close } ) {
aria-labelledby={ dialogLabel }
aria-describedby={ dialogDescription }
>
- { children }
+ { childrenWithId }
</div>
);
}
@@ -11,6 +11,7 @@ import { | |||
EntitiesSavedStates, | |||
useEntitiesSavedStatesIsDirty, | |||
privateApis, | |||
EntitiesSavedStatesDialogWrapper, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should expose it as a private API initially, So, define it here:
import { EntitiesSavedStatesExtensible } from './components/entities-saved-states'; |
Looks a little overkill to me. Wouldn't a simpler approach be better? |
Thanks for the PR! I'm looking for a bit the issue, previous attempts and current suggestions (this one and the alternative). While I also think the wrapper is better semantically I'm not sure it's worth it to add the complexity for also passing |
Attempt to resolve #67313, Reference PR: #67327
Alternate PR to consider: #67351
What?
This PR removes the renderDialog prop from EntitiesSavedStates and EntitiesSavedStatesExtensible, and moves the dialog functionality into a new wrapper function, EntitieDialogWrapper.
Why?
The issue occurs when clicking on the 'padding area' of the Entity saved modal dialog, causing the dialog to close unexpectedly.
Animated GIF to illustrate:
How?
This PR introduces a new wrapper function,
EntitieDialogWrapper
, to handle the dialog functionality inside the editor's "save-publish-panel". Additionally, the renderDialog prop has been removed fromEntitiesSavedStates
andEntitiesSavedStatesExtensible
, as it is no longer necessary. This ensures thatEntitiesSavedStatesExtensible
can be used with Modal in site-editor without any dialog logic.Testing Instructions