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

[Try] Customizable toolbar contents #23613

Merged
merged 9 commits into from
Jul 7, 2020
Merged

Conversation

adamziel
Copy link
Contributor

@adamziel adamziel commented Jul 1, 2020

Description

This PR makes the block toolbar contents customizable via a slot and is a first step towards resolving #23375.

It adds the way to customize toolbar contents, essentially replacing all the default toolbar contents. After implementing the transition between the normal and the customized state, I thought it would be cool if selecting another block would also resize the toolbar smoothly - so now it does that too:

2020-07-01 15-52-37 2020-07-01 15_53_23

How has this been tested?

This PR only adds the infrastructure. To actually test, you will need to apply both this PR and #23614. Then:

  1. Go to experimental navigation screen
  2. Select a link block, switch to another one, select just the text contents, select an entire block.
  3. Confirm that the animation worked well.
  4. Click on the "Edit Link" button while a link block is selected.
  5. Confirm toolbar contents were replaced, the animation was smooth, and the keyboard navigation works.
  6. Go off the script, play with it, confirm it works as you would expect it to.

Screenshots

Types of changes

New feature (non-breaking change which adds functionality)

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@adamziel adamziel self-assigned this Jul 1, 2020
@adamziel adamziel added [Feature] Blocks Overall functionality of blocks [Feature] Navigation Screen labels Jul 1, 2020
@github-actions
Copy link

github-actions bot commented Jul 1, 2020

Size Change: +6.04 kB (0%)

Total Size: 1.14 MB

Filename Size Change
build/annotations/index.js 3.62 kB -6 B (0%)
build/api-fetch/index.js 3.39 kB -7 B (0%)
build/autop/index.js 2.82 kB -1 B
build/block-directory/index.js 7.16 kB -255 B (3%)
build/block-directory/style-rtl.css 952 B +11 B (1%)
build/block-directory/style.css 951 B +9 B (0%)
build/block-editor/index.js 114 kB +5.3 kB (4%)
build/block-editor/style-rtl.css 10.8 kB +106 B (0%)
build/block-editor/style.css 10.8 kB +104 B (0%)
build/block-library/editor-rtl.css 7.63 kB +194 B (2%)
build/block-library/editor.css 7.64 kB +195 B (2%)
build/block-library/index.js 130 kB +342 B (0%)
build/block-library/style-rtl.css 7.79 kB -250 B (3%)
build/block-library/style.css 7.79 kB -256 B (3%)
build/block-serialization-default-parser/index.js 1.88 kB -3 B (0%)
build/components/index.js 198 kB +257 B (0%)
build/components/style-rtl.css 15.9 kB -47 B (0%)
build/components/style.css 15.8 kB -48 B (0%)
build/compose/index.js 9.65 kB +6 B (0%)
build/core-data/index.js 11.4 kB +8 B (0%)
build/data-controls/index.js 1.29 kB +2 B (0%)
build/data/index.js 8.46 kB +11 B (0%)
build/date/index.js 5.47 kB -2 B (0%)
build/dom/index.js 3.19 kB -1 B
build/edit-navigation/index.js 10 kB +124 B (1%)
build/edit-post/index.js 304 kB +100 B (0%)
build/edit-post/style-rtl.css 5.57 kB +54 B (0%)
build/edit-post/style.css 5.56 kB +53 B (0%)
build/edit-site/index.js 16.7 kB +11 B (0%)
build/edit-site/style-rtl.css 3.02 kB +25 B (0%)
build/edit-site/style.css 3.02 kB +26 B (0%)
build/edit-widgets/index.js 9.33 kB +7 B (0%)
build/edit-widgets/style-rtl.css 2.45 kB +26 B (1%)
build/edit-widgets/style.css 2.45 kB +27 B (1%)
build/editor/index.js 44.8 kB -40 B (0%)
build/editor/style-rtl.css 3.82 kB -36 B (0%)
build/editor/style.css 3.82 kB -40 B (1%)
build/element/index.js 4.65 kB -1 B
build/format-library/index.js 7.72 kB -9 B (0%)
build/hooks/index.js 2.13 kB +3 B (0%)
build/is-shallow-equal/index.js 709 B -1 B
build/keyboard-shortcuts/index.js 2.51 kB -4 B (0%)
build/keycodes/index.js 1.94 kB +1 B
build/list-reusable-blocks/index.js 3.12 kB -1 B
build/list-reusable-blocks/style-rtl.css 476 B +26 B (5%) 🔍
build/list-reusable-blocks/style.css 476 B +25 B (5%) 🔍
build/media-utils/index.js 5.3 kB +5 B (0%)
build/notices/index.js 1.79 kB +4 B (0%)
build/nux/index.js 3.4 kB +2 B (0%)
build/plugins/index.js 2.56 kB -1 B
build/primitives/index.js 1.49 kB -7 B (0%)
build/priority-queue/index.js 789 B +1 B
build/redux-routine/index.js 2.85 kB -4 B (0%)
build/rich-text/index.js 14 kB +1 B
build/shortcode/index.js 1.7 kB +1 B
build/token-list/index.js 1.27 kB -2 B (0%)
build/url/index.js 4.06 kB -3 B (0%)
build/warning/index.js 1.13 kB -4 B (0%)
build/wordcount/index.js 1.17 kB -1 B
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/blob/index.js 620 B 0 B
build/block-library/theme-rtl.css 730 B 0 B
build/block-library/theme.css 732 B 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/blocks/index.js 48.2 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 569 B 0 B
build/edit-navigation/style-rtl.css 1.02 kB 0 B
build/edit-navigation/style.css 1.02 kB 0 B
build/editor/editor-styles-rtl.css 537 B 0 B
build/editor/editor-styles.css 539 B 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/html-entities/index.js 622 B 0 B
build/i18n/index.js 3.56 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/server-side-render/index.js 2.67 kB 0 B
build/viewport/index.js 1.85 kB 0 B

compressed-size-action

@youknowriad
Copy link
Contributor

Can you clarify the API? How does a block author use it? Why it's different from BlockControls...?

@adamziel
Copy link
Contributor Author

adamziel commented Jul 2, 2020

@youknowriad Sure! The testing PR (#23614) provides an actual usage:

https://github.com/WordPress/gutenberg/blob/cba4afc287f9d16404383abd0e43d82cf1415818/packages/block-library/src/navigation-link/edit.js#L142-L177

The difference is as follows:

Example

A possible future for this could be the concept of programmable "Toolbar modes" with the default mode being the current toolbar (with the drag handle, movers, custom controls, and so on).

@youknowriad
Copy link
Contributor

What happens if someone uses multiple "BlockContentToolbar" components? How do you decide which one to show? How does it work, does it remove the remaining "BlockControls" that are outside it?

@adamziel
Copy link
Contributor Author

adamziel commented Jul 2, 2020

What happens if someone uses multiple "BlockContentToolbar" components? How do you decide which one to show?

Good question! Currently it would show all of them which means the block needs to be careful about only returning BlockContentToolbar when it's selected - this is something that could be handled automatically at the slot level. Then, when the selected block renders multiple instances of BlockContentToolbar, I think it would make sense to only display the first one and ignore any other ones.

How does it work, does it remove the remaining "BlockControls" that are outside it?

This logic is responsible for choosing what gets displayed inside of the BlockToolbar:

<TransitionGroup>
{ fills.length ? (
<CSSTransition
key="fills"
timeout={ 300 }
classNames="block-editor-block-toolbar-content"
>
<div className={ className } ref={ fillsRef }>
{ fills }
</div>
</CSSTransition>
) : (
<CSSTransition
key="default"
timeout={ 300 }
classNames="block-editor-block-toolbar-content"
>
<div className={ className } ref={ toolbarRef }>
{ children }
</div>
</CSSTransition>
) }
</TransitionGroup>

If there are any BlockContentToolbar fills registered, they are displayed, otherwise the regular toolbar content is displayed (movers, BlockControls, and others).

@youknowriad
Copy link
Contributor

Maybe a better API is something like isExpanded or isFocused in BlockControls instead of a separate slot and throwing a warning when the slot detects more than one.

@adamziel
Copy link
Contributor Author

adamziel commented Jul 2, 2020

Hm maybe? I'm digesting if it's weird to have it configurable as a flag on a slot, it substantially changes the behavior - but maybe within the scope of what's reasonable here? I'm not sure - let's try it and reevaluate.

@adamziel
Copy link
Contributor Author

adamziel commented Jul 2, 2020

@youknowriad I just updated this PR to use BlockControls, it's not bad actually.

@@ -48,6 +54,9 @@ function BlockControlsFill( { controls, children } ) {
);
}

const buildSlotName = ( isExpanded ) =>
`BlockControls${ isExpanded ? '-expanded' : '' }`;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why you had to use a different slot name though?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was suggesting to use the exact same slot and fills but have some logic on the wrapper components to avoid rendering if there is already an expanded fill on the slot or something like that. Would that be possible?

Copy link
Contributor Author

@adamziel adamziel Jul 2, 2020

Choose a reason for hiding this comment

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

How else would I make sure that expanded fills don't land inside the regular slot OR keep the non-expanded ones from replacing the entire content?

Copy link
Contributor Author

@adamziel adamziel Jul 2, 2020

Choose a reason for hiding this comment

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

I tried inspecting the list of fills and looking at the isExpanded prop, but then I realized it's just reinventing the slot name.

Copy link
Contributor Author

@adamziel adamziel Jul 2, 2020

Choose a reason for hiding this comment

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

Something's wrong with GitHub, I only saw your second comment a few minutes after posting mine.

I was suggesting to use the exact same slot and fills but have some logic on the wrapper components to avoid rendering if there is already an expanded fill on the slot or something like that. Would that be possible?

I agree this would be the perfect solution. At the moment accessing fills outside of the slot is not possible and some refactoring would be required first so I went for the "wrapping slot" approach. Let me see what would it take to make it happen, it seems like virtually bubbling slots have a hook so maybe it wouldn't be that hard after all?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to trying to get rid of the buildSlotName, as it implies there are countless options, when in fact we have two

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I'm not sure either whether it's better or not, the current implementation is also decent. It's just that it feels more conceptually correct. (shouldn't block this PR though)

Copy link
Contributor Author

@adamziel adamziel Jul 3, 2020

Choose a reason for hiding this comment

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

A small refactoring of bubbles-virtually/fill would make it possible to detect the presence of expanded fills outside of slot:

Before:

export default function Fill( { name, children } ) {
const slot = useSlot( name );
const ref = useRef( { rerender: useForceUpdate() } );

After:

export default function Fill( { name, children, refProps } ) {
	const slot = useSlot( name );
	const ref = useRef( { rerender: useForceUpdate(), ...refProps } );

With that in place, the following would be possible:

	const { fills } = useSlot( 'BlockControls' );
	const expandedFills = fills.filter( ( ref ) => ref.current?.isExpanded  );

So far so good - so we can use different logic for both cases. The problem is rendering only expanded or non-expanded fills here:

	<BlockControls.Slot
		bubblesVirtually
		className="block-editor-block-toolbar__slot"
	/>

We don't have a granular control over fills at the slot level - bubblesVirtually fill renders itself using portals. So either:

  • All BlockControls fills would have to keep track of all other BlockControl fills and handle that internally (sounds awful), OR
  • Expanded and non-expanded fills would have to create portals with different target element, for example based on slot name

So we're back to square one in regard to using slots and fills names. There would be a small win here in that we could get rid of the wrapping slot and make it a sibling of the default content instead, but it would still be a separate slot.

Copy link
Contributor

@draganescu draganescu left a comment

Choose a reason for hiding this comment

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

As this is exposing an experimental prop and also just laying the foundation for #23375 's incoming series of PRs I am approving this with some comments.

Most of the comments are related to how we think of this change. The goal is to allow a component to:

  • entirely alter the contents of the block controls
  • animate the transition to the new contents

My concerns with the naming (__experimentalCustomizableContent) might seem minor but the content of BlockControls is already customizable. What we do is introduce an optional and possible expanded mode for a control.

cc @youknowriad

@@ -48,6 +54,9 @@ function BlockControlsFill( { controls, children } ) {
);
}

const buildSlotName = ( isExpanded ) =>
`BlockControls${ isExpanded ? '-expanded' : '' }`;
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to trying to get rid of the buildSlotName, as it implies there are countless options, when in fact we have two


const fillsPropRef = useRef();
fillsPropRef.current = fills;
const resize = useCallback(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think maybe we should name this to resizeToolbarChild or something as it too generic right now.

}
elem.style.position = 'absolute';
elem.style.width = 'auto';
const css = window.getComputedStyle( elem, null );
Copy link
Contributor

Choose a reason for hiding this comment

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

Like the resize comment above css and elem should point to what they are when resize is used. The reason is that this function is only used in one place and it's specific to what we do here so reading it should lend value to that aspect.

);

useEffect( () => {
// Create an observer instance linked to the callback function
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is describing what the next line does, we can remove it

}
} );

// Start observing the target node for configured mutations
Copy link
Contributor

Choose a reason for hiding this comment

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

same, this comment is describing what the next line does, we can remove it

classNames="block-editor-block-toolbar-content"
>
<div className={ className } ref={ fillsRef }>
{ fills[ 0 ] }
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit pick, maybe make a const displayFill = fills[ 0 ] higher in the code and then use it here?

export default function BlockToolbar( { hideDragHandle } ) {
export default function BlockToolbar( {
hideDragHandle,
__experimentalCustomizableContent = false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Considering what it does __experimentalCustomizableContent could be named __experimentalExpandedControl

@youknowriad
Copy link
Contributor

Let's keep this PR for after beta1 (next release)

@adamziel
Copy link
Contributor Author

adamziel commented Jul 7, 2020

Since packages for beta1 were just published, I'm going to merge this PR

@adamziel adamziel merged commit 5078c43 into master Jul 7, 2020
@adamziel adamziel deleted the add/customizable-toolbar-content branch July 7, 2020 11:56
@github-actions github-actions bot added this to the Gutenberg 8.6 milestone Jul 7, 2020
@mcsf mcsf added the [Type] Experimental Experimental feature or API. label Jul 21, 2020
talldan added a commit that referenced this pull request Sep 8, 2020
talldan added a commit that referenced this pull request Sep 9, 2020
@talldan talldan mentioned this pull request Sep 9, 2020
6 tasks
talldan added a commit that referenced this pull request Sep 22, 2020
talldan added a commit that referenced this pull request Oct 6, 2020
talldan added a commit that referenced this pull request Oct 7, 2020
talldan added a commit that referenced this pull request Nov 26, 2020
adamziel pushed a commit that referenced this pull request Nov 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Blocks Overall functionality of blocks [Type] Experimental Experimental feature or API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants