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

Modal: add headerActions prop to render buttons in the header #53328

Merged
merged 7 commits into from
Aug 9, 2023

Conversation

ramonjd
Copy link
Member

@ramonjd ramonjd commented Aug 4, 2023

What?

Not sure about this one yet. I would value some input 🙇🏻

This is an experiment to add a new prop headerActions: ReacNode to the <Modal /> component.

The prop allows folks to inject buttons and other elements into the header.

Why?

See: #53258 (comment)

Some folks working in the classic block require things to be roomier, and it'd be nice to be able to toggle fullscreen mode in the modal. Why? There might be a lot of complex content in there, for which it would be helpful to have more real estate to work.

How?

Adding a new prop.

TODO

  • add stories

Testing Instructions

A good place to test this is by applying the new prop to the Class block modal:

Example diff
diff --git a/packages/block-library/src/freeform/editor.scss b/packages/block-library/src/freeform/editor.scss
index a3be7ffc5e..c09cf9aace 100644
--- a/packages/block-library/src/freeform/editor.scss
+++ b/packages/block-library/src/freeform/editor.scss
@@ -376,14 +376,16 @@ div[data-type="core/freeform"] {
 }
 
 .block-editor-freeform-modal {
-	.components-modal__frame {
+	.block-editor-freeform-modal__content {
 		// On large screens, make the TinyMCE edit area grow to take all the
 		// available height so that the Cancel/Save buttons are always into the
 		// view. On smaller screens, the modal content is scrollable.
 		@include break-large() {
 			// On medium and large screens, the modal component sets a max-height.
 			// We want the modal to be as tall as possible also when the content is short.
-			height: 9999rem;
+			&:not(.is-full-screen) {
+				height: 9999rem;
+			}
 
 			.components-modal__header + div {
 				height: 100%;
diff --git a/packages/block-library/src/freeform/modal.js b/packages/block-library/src/freeform/modal.js
index 9f7b20460c..61a6b89415 100644
--- a/packages/block-library/src/freeform/modal.js
+++ b/packages/block-library/src/freeform/modal.js
@@ -13,6 +13,29 @@ import {
 import { useEffect, useState, RawHTML } from '@wordpress/element';
 import { __ } from '@wordpress/i18n';
 import { useSelect } from '@wordpress/data';
+import { fullscreen } from '@wordpress/icons';
+import { useViewportMatch } from '@wordpress/compose';
+
+function ModalAuxiliaryActions( { onClick, isModalFullScreen } ) {
+	// 'small' to match the rules in editor.scss.
+	const isMobileViewport = useViewportMatch( 'small', '<' );
+	if ( isMobileViewport ) {
+		return null;
+	}
+
+	return (
+		<Button
+			onClick={ onClick }
+			icon={ fullscreen }
+			isPressed={ isModalFullScreen }
+			label={
+				isModalFullScreen
+					? __( 'Exit fullscreen' )
+					: __( 'Enter fullscreen' )
+			}
+		/>
+	);
+}
 
 function ClassicEdit( props ) {
 	const styles = useSelect(
@@ -58,6 +81,7 @@ export default function ModalEdit( props ) {
 		onReplace,
 	} = props;
 	const [ isOpen, setOpen ] = useState( false );
+	const [ isModalFullScreen, setIsModalFullScreen ] = useState( true );
 	const id = `editor-${ clientId }`;
 
 	const onClose = () => ( content ? setOpen( false ) : onReplace( [] ) );
@@ -78,6 +102,16 @@ export default function ModalEdit( props ) {
 					onRequestClose={ onClose }
 					shouldCloseOnClickOutside={ false }
 					overlayClassName="block-editor-freeform-modal"
+					isFullScreen={ isModalFullScreen }
+					className="block-editor-freeform-modal__content"
+					headerActions={
+						<ModalAuxiliaryActions
+							onClick={ () =>
+								setIsModalFullScreen( ! isModalFullScreen )
+							}
+							isModalFullScreen={ isModalFullScreen }
+						/>
+					}
 				>
 					<ClassicEdit id={ id } defaultValue={ content } />
 					<Flex

Also checkout the Story Book variation: npm run storybook:dev

Screenshots or screencast

2023-08-07.11.40.12.mp4
2023-08-09.11.09.01.mp4

✍️ Dev note

Thanks to a new headerActions prop, consumers of Modal can inject buttons (and other elements) into the Modal's header, next to the close button.

@ramonjd ramonjd added [Type] Enhancement A suggestion for improvement. Needs Design Needs design efforts. [Package] Components /packages/components labels Aug 4, 2023
@ramonjd ramonjd requested a review from ajitbohra as a code owner August 4, 2023 06:04
@ramonjd ramonjd requested review from mirka, afercia and t-hamano August 4, 2023 06:04
@ramonjd ramonjd force-pushed the try/modal-component-toggle-fullscreen branch from 92566c7 to d3fb67e Compare August 4, 2023 06:08
@t-hamano
Copy link
Contributor

t-hamano commented Aug 4, 2023

Thanks for the review, @ramonjd!

I've pulled the icons from the available ones in the library. They don't quite work. I think we need a minimize icon (or the opposite of resizeCornerNE)?

How about using only the fullscreen icon and toggling the isPressed prop?

is-pressed

Then, it might be better to have a gap of about 8px between the buttons:

gap

Also, when in mobile view, the modal is always in full screen, so it is better to hide this button

mobile

Another approach is to make the Classic Editor modal full-screen from the start, instead of switching to full-screen status. When a user presses the Edit button in the Classic block, would they be surprised to see a large modal? 🤔 I would like to hear other opinions on this point.

Copy link
Member

@mirka mirka left a 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 tweak the architecture a bit. Are we expecting this "full screen mode" modal to have different styling, depending on the use case? I'm asking because in the example, the styling is added via the block-library package.

We either want the fullscreen styling built into the Modal component itself, or else support this feature in a more generic way. For example, we could enhance Modal so that a consumer can inject auxiliary buttons next to the Close button. Then, the Freeform block would inject a Fullscreen button, and do its own styling via the className prop.

What do you think?

@ramonjd
Copy link
Member Author

ramonjd commented Aug 6, 2023

Are we expecting this "full screen mode" modal to have different styling, depending on the use case? I'm asking because in the example, the styling is added via the block-library package.

I suppose that's a possibility. The freeform block does overwrite the modal styles (.components-modal__frame) at the moment so I expect it would be more sustainable if it didn't have to.

We either want the fullscreen styling built into the Modal component itself, or else support this feature in a more generic way. For example, we could enhance Modal so that a consumer can inject auxiliary buttons next to the Close button. Then, the Freeform block would inject a Fullscreen button, and do its own styling via the className prop.

Oh yeah, that's an interesting idea. Thanks! I wasn't too sure about this one when I pushed it so I'll play around. 🙇🏻

How about using only the fullscreen icon and toggling the isPressed prop?
Also, when in mobile view, the modal is always in full screen, so it is better to hide this button

Good ideas, thank you!

@ramonjd
Copy link
Member Author

ramonjd commented Aug 7, 2023

For example, we could enhance Modal so that a consumer can inject auxiliary buttons next to the Close button

I was thinking about this. What's the preferred way of defining these props? Do we need to be strict about what is passed?

I had started with a simple ReactNode for maximum flexibility, which would mean users could inject anything. Example:

<Modal
   auxiliaryActions={ <ButtonGroup /> }
/>

But I suppose there's also the option of reining it into an array of buttonProps objects each of which would render a <Button ... />.

<Modal
   auxiliaryActions={ [
       {
          ...someButtonProps,
       }
    ] }
/>

@ramonjd ramonjd changed the title Modal component: allow toggling fullscreen Modal component: add auxiliary actions prop to render buttons in the header Aug 7, 2023
@ramonjd ramonjd self-assigned this Aug 7, 2023
@ramonjd ramonjd added the [Status] In Progress Tracking issues with work in progress label Aug 7, 2023
@ramonjd ramonjd force-pushed the try/modal-component-toggle-fullscreen branch from d3fb67e to a04be14 Compare August 7, 2023 01:41
@ramonjd
Copy link
Member Author

ramonjd commented Aug 7, 2023

I had started with a simple ReactNode for maximum flexibility, which would mean users could inject anything

I've done this for now, just as a placeholder. Happy to implement further advice/ideas. Thank you @t-hamano and @mirka !!

@andrewserong
Copy link
Contributor

I had started with a simple ReactNode for maximum flexibility, which would mean users could inject anything.

With Modal being fairly generic, perhaps allowing folks to inject anything might be preferable?

@ramonjd
Copy link
Member Author

ramonjd commented Aug 7, 2023

I've added a TODO to add a story once we've decided on an approach. 👍🏻

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

This is testing very nicely for me!

2023-08-08.16.24.57.mp4

In terms of naming, I don't feel too strongly about it, but I was wondering if secondaryActions might work? Mostly because auxiliary can be hard to spell 😄. On the other hand, since there's no primary or actions prop, maybe auxiliary is better.

Anyway, the injection and story all look good to me, looks like the changelog file might need a rebase, but once there's consensus on the naming, this one LGTM! ✨

packages/components/CHANGELOG.md Outdated Show resolved Hide resolved
@ramonjd ramonjd force-pushed the try/modal-component-toggle-fullscreen branch from 81aa3c5 to f5beb6e Compare August 8, 2023 06:53
@ramonjd
Copy link
Member Author

ramonjd commented Aug 8, 2023

I don't feel too strongly about it, but I was wondering if secondaryActions might work? Mostly because auxiliary can be hard to spell

Thanks @andrewserong

You know, I probably had to fix my own typos about 20 times when typing it out. 😆

I'm also agnostic here. 'complementary'?

@andrewserong
Copy link
Contributor

I'm also agnostic here. 'complementary'?

Oh, that could work, too. And I only see one typo for complimentary in the repo 😁. The words auxiliary, secondary, and complementary appear to be used in at least one other place in the repo, so all sound like reasonable options to me. Let's see if @mirka or anyone else have any preferences 😄

@ramonjd
Copy link
Member Author

ramonjd commented Aug 8, 2023

And I only see one typo for complimentary in the repo 😁.

Nice find!

I couldn't help myself 😄

@ramonjd
Copy link
Member Author

ramonjd commented Aug 9, 2023

headerActions was suggested as an alternative. I like it. I'll update it to that and apologize later 😄

@noisysocks
Copy link
Member

I like actions or headerActions as a name because tertiary, secondary, auxiliary, etc. are a bit too abstract for my dumb brain.

Just flagging hat we have InspectorPopoverHeader which I added as a separate component because I wasn't sure back then whether we'd want something like this for all modals. How do you feel about deprecating/removing InspectorPopverHeader as part of this?

@ramonjd
Copy link
Member Author

ramonjd commented Aug 9, 2023

I like actions or headerActions as a name because tertiary, secondary, auxiliary, etc. are a bit too abstract for my dumb brain.

You got it!!! 👍🏻

Just flagging hat we have InspectorPopoverHeader which I added as a separate component because I wasn't sure back then whether we'd want something like this for all modals. How do you feel about deprecating/removing InspectorPopverHeader as part of this?

If you think we should, for sure.

I can do a follow up PR because it might involve extricating it from the PublishDateTimePicker

import InspectorPopoverHeader from '../inspector-popover-header';

@noisysocks
Copy link
Member

True. I guess those components need to contain the Popover. A little bit of refactoring required.

@ramonjd ramonjd changed the title Modal component: add auxiliary actions prop to render buttons in the header Modal: add headerActions prop to render buttons in the header Aug 9, 2023
@ramonjd ramonjd force-pushed the try/modal-component-toggle-fullscreen branch from f5beb6e to a29a81a Compare August 9, 2023 01:01
Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Looking good, and still tests nicely after the prop name switch 👍

image

LGTM! ✨

@ramonjd
Copy link
Member Author

ramonjd commented Aug 9, 2023

Thanks for re-testing @andrewserong 🙇🏻

I'll let this PR stew overnight in case there's further feedback and merge tomorrow.

Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

Btw I just noticed that Storybook isn't showing TypeScript data in the props tables anymore 😬 Something must have broke at some point. We'll look into that.

packages/components/src/modal/stories/index.tsx Outdated Show resolved Hide resolved
packages/components/src/modal/stories/index.tsx Outdated Show resolved Hide resolved
@@ -194,6 +194,13 @@ If this property is true, it will focus the first tabbable element rendered in t
- Required: No
- Default: `true`

#### headerActions
Copy link
Member

Choose a reason for hiding this comment

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

I think this name is good too 👍 We should definitely have "header" in the name. For a moment I thought maybe "extras" instead of "actions" would be more accurately generic. But on the other hand I think it's good to restrict/suggest that it's intended for button-ish actions, just for the sake of UI design consistency.

@ramonjd ramonjd enabled auto-merge (squash) August 9, 2023 03:28
@ramonjd ramonjd merged commit 0d36d5d into trunk Aug 9, 2023
@ramonjd ramonjd deleted the try/modal-component-toggle-fullscreen branch August 9, 2023 04:07
@github-actions github-actions bot added this to the Gutenberg 16.5 milestone Aug 9, 2023
@ramonjd
Copy link
Member Author

ramonjd commented Aug 9, 2023

True. I guess those components need to contain the Popover. A little bit of refactoring required.

Looking at this a bit more closely, InspectorPopverHeader is used in other contexts, e.g., the <Dropdown /> popover in the post template controls and page panel status. It still seems useful in those contexts and not sure there's a specific crossover with the <Modal /> component.

@noisysocks
Copy link
Member

Oh right I've mixed Modal and Popover up. I still think it might make sense for Modal, Popover and Dropdown to all have a headerActions prop which works consistently.

@properlypurple properlypurple mentioned this pull request Oct 11, 2023
10 tasks
@ciampo ciampo added has dev note when dev note is done (for upcoming WordPress release) and removed [Status] In Progress Tracking issues with work in progress labels Oct 12, 2023
@ciampo
Copy link
Contributor

ciampo commented Oct 12, 2023

Added a dev note

@ramonjd
Copy link
Member Author

ramonjd commented Oct 12, 2023

Thanks @ciampo 🙇🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has dev note when dev note is done (for upcoming WordPress release) Needs Design Needs design efforts. [Package] Components /packages/components [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants