Skip to content

Commit

Permalink
Add native block inserter onboarding tooltip (#32001)
Browse files Browse the repository at this point in the history
* [wip] Add block inserter tooltip

* Fix linter warning

* [wip] Initial native Tooltip component

Needs more accurate positioning, more styling, and better position prop
support.

* [wip] Tooltip is seemingly aligning correctly

* Reintroduce animation

* Simplify Tooltip code

* Allow Tooltip Slot to dismiss tooltips when touch occurs anywhere

The Tooltip Slot is currently successfully invoking the callback, but
the state is not triggering a re-render to hide the Tooltip.

* Fix tooltip dismissal

Previously, _all_ tooltips registered a callback regardless if they were
visible. This meant that whichever tooltip was last was the only tooltip
registered. Often times this meant the currently visible tooltip would
remain left with a stale visibility as its callback had been cleared.

* Only run tooltip animation when necessary

Previously, the animation ran anytime the visible prop changed, even if
it was the same value as the previous render.

* Add note about performance considerations

* Add documentation for `visible` prop

The `visible` prop was added as the hover/focus trigger used for web is
not relevant for touch devices. The ability to display the tooltip on
initial render is required for the current use case of an onboarding
experience.

* Avoid tracking unused values

* Clarify reference measuring timeout

Use a ref to avoid losing the timeout value. Rename the value to
increase clarity of intent.

* Name ref explicitly

Include the phrase "ref" in the name to communicate the actual value.

* Fix tooltip exit animation

* Remove unused styles

* Simplify render for hidden tooltip

* Memoize context value to avoid unnecessary rerenders

Memoize object creation to avoid unnnecessary rerenders of the
Provider's children.

* Adjust tooltip styles

* Restrict editor onboarding tooltip visibility

Only display the editor onboarding tooltip for users who are first
launching the editor.

* Refactor away redundant select invocations

Combing `select` calls to avoid multiple subscriptions that may(?)
degrade performance.

* Revert "Refactor away redundant select invocations"

This reverts commit e4f091d.

* Relocate block inserter tooltip further down tree

To avoid the need to render a wrapping `View` or forward a `ref` down
the tree, the tooltip was moved further down the tree.

* Leverage existing Tooltip within Button component

Rather than wrapping the block inserter button, we can leverage the
Tooltip component that is included inside of Button. In order to do so,
we must set `visible` whenever `showTooltip` is explicitly `true`.

* Replace setTimeout with requestAnimationFrame

`requestAnimationFrame` feels more appropriate than `setTimeout` given
we are awaiting a render. This is required to await the render before
calculating the layout values to position the tooltip.

* Reposition tooltip on keyboard visibility change

We must reposition the tooltip when the keyboard position changes to
avoid collisions.

* Improve tooltip keyboard awareness

Reposition or hide the tooltip depending on the keyboard visibility.

* Disable default tooltip visibility for native dropdown menu buttons

Enabling `showTooltip` within the dropdown menu was likely a copy from
the web implementation. Previously the mobile implementation of tooltip
was a no-op, rendering the `showTooltip` value moot. Now that tooltip
renders UI, we must disable `showTooltip` on mobile, otherwise every
dropdown menu button would display its tooltip on initial render.

* Improve layout calculation resiliency

Now that we rely upon `requestAnimationFrame` to ensure the render
completes before calculation, there are times where the ref is no longer
present once the callback completes. This change ensures we check within
the `requestAnimationFrame` callback.

* Improve visibility animation management

Add documentation and remove unnecessary hook dependency.

* Manage tooltip position when keyboard frame changes

The keyboard frame (dimensions) can change when the current keyboard is
changed, the device rotates, dictation is toggled, etc. We must
reposition the tooltip when the frame updates.

* Refactor tooltip styles

Hoist all styles underneath a `tooltip` selector. Prefer styles in Sass
files rather than JavaScript.

* Avoid unnecessary touch handler when no tooltips displayed

Rather running a no-op anytime a touch is started, we can remove the
callback if no Tooltips are displayed.

* Apply minor refactors

Done purely for stylistic preference.

* Revert refactor that caused error

An error occurred during creating or cleaning up the Hook.

* Rename class member to differentiate it from prop

The class member and prop were named identically, which may following
the flow a bit difficult.

* Remove duplicative select

The `getSettings` value is already destructured earlier in the routine.

* Rename getSettings to getBlockEditorSettings

Renamed to avoid ambiguity.
  • Loading branch information
dcalhoun committed May 26, 2021
1 parent 23d84b3 commit 2436759
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 60 deletions.
31 changes: 23 additions & 8 deletions packages/block-editor/src/components/inserter/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,28 @@ import { store as blockEditorStore } from '../../store';

const VOICE_OVER_ANNOUNCEMENT_DELAY = 1000;

const defaultRenderToggle = ( { onToggle, disabled, style, onLongPress } ) => (
const defaultRenderToggle = ( {
canViewEditorOnboarding,
onToggle,
disabled,
style,
onLongPress,
} ) => (
<ToolbarButton
title={ __( 'Add block' ) }
title={
canViewEditorOnboarding
? __( 'Tap to add content' )
: __( 'Add block' )
}
icon={
<Icon
icon={ plusCircleFilled }
style={ style }
color={ style.color }
/>
}
showTooltip={ canViewEditorOnboarding }
tooltipPosition="top right"
onClick={ onToggle }
extraProps={ {
hint: __( 'Double tap to add a block' ),
Expand All @@ -58,7 +70,7 @@ export class Inserter extends Component {
super( ...arguments );

this.onToggle = this.onToggle.bind( this );
this.renderToggle = this.renderToggle.bind( this );
this.renderInserterToggle = this.renderInserterToggle.bind( this );
this.renderContent = this.renderContent.bind( this );
}

Expand Down Expand Up @@ -186,8 +198,9 @@ export class Inserter extends Component {
*
* @return {WPElement} Dropdown toggle element.
*/
renderToggle( { onToggle, isOpen } ) {
renderInserterToggle( { onToggle, isOpen } ) {
const {
canViewEditorOnboarding,
disabled,
renderToggle = defaultRenderToggle,
getStylesFromColorScheme,
Expand Down Expand Up @@ -234,6 +247,7 @@ export class Inserter extends Component {
return (
<>
{ renderToggle( {
canViewEditorOnboarding,
onToggle: onPress,
isOpen,
disabled,
Expand Down Expand Up @@ -286,7 +300,7 @@ export class Inserter extends Component {
<Dropdown
onToggle={ this.onToggle }
headerTitle={ __( 'Add a block' ) }
renderToggle={ this.renderToggle }
renderToggle={ this.renderInserterToggle }
renderContent={ this.renderContent }
/>
);
Expand All @@ -301,6 +315,7 @@ export default compose( [
getBlockOrder,
getBlockIndex,
getBlock,
getSettings: getBlockEditorSettings,
} = select( blockEditorStore );

const end = getBlockSelectionEnd();
Expand All @@ -321,11 +336,9 @@ export default compose( [
: undefined;

function getDefaultInsertionIndex() {
const { getSettings } = select( blockEditorStore );

const {
__experimentalShouldInsertAtTheTop: shouldInsertAtTheTop,
} = getSettings();
} = getBlockEditorSettings();

// if post title is selected insert as first block
if ( shouldInsertAtTheTop ) {
Expand Down Expand Up @@ -365,6 +378,8 @@ export default compose( [
const insertionIndexEnd = endOfRootIndex;

return {
canViewEditorOnboarding: getBlockEditorSettings()
.canViewEditorOnboarding,
destinationRootClientId,
insertionIndexDefault: getDefaultInsertionIndex(),
insertionIndexBefore,
Expand Down
16 changes: 10 additions & 6 deletions packages/components/src/button/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import { isArray } from 'lodash';
* WordPress dependencies
*/
import { Children, cloneElement } from '@wordpress/element';
import { withPreferredColorScheme } from '@wordpress/compose';
import {
usePreferredColorScheme,
usePreferredColorSchemeStyle,
} from '@wordpress/compose';

/**
* Internal dependencies
Expand Down Expand Up @@ -80,7 +83,6 @@ export function Button( props ) {
disabled,
hint,
fixedRatio = true,
getStylesFromColorScheme,
isPressed,
'aria-disabled': ariaDisabled,
'data-subscript': subscript,
Expand All @@ -92,6 +94,7 @@ export function Button( props ) {
shortcut,
tooltipPosition,
} = props;
const preferredColorScheme = usePreferredColorScheme();

const isDisabled = ariaDisabled || disabled;

Expand All @@ -110,15 +113,15 @@ export function Button( props ) {
states.push( 'disabled' );
}

const subscriptInactive = getStylesFromColorScheme(
const subscriptInactive = usePreferredColorSchemeStyle(
styles.subscriptInactive,
styles.subscriptInactiveDark
);

const newChildren = Children.map( children, ( child ) => {
return child
? cloneElement( child, {
colorScheme: props.preferredColorScheme,
colorScheme: preferredColorScheme,
isPressed,
} )
: child;
Expand All @@ -141,7 +144,7 @@ export function Button( props ) {

const newIcon = icon
? cloneElement( <Icon icon={ icon } size={ iconSize } />, {
colorScheme: props.preferredColorScheme,
colorScheme: preferredColorScheme,
isPressed,
} )
: null;
Expand Down Expand Up @@ -189,10 +192,11 @@ export function Button( props ) {
text={ label }
shortcut={ shortcut }
position={ tooltipPosition }
visible={ showTooltip === true }
>
{ element }
</Tooltip>
);
}

export default withPreferredColorScheme( Button );
export default Button;
1 change: 0 additions & 1 deletion packages/components/src/dropdown-menu/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ function DropdownMenu( {
aria-haspopup="true"
aria-expanded={ isOpen }
label={ label }
showTooltip
>
{ mergedToggleProps.children }
</Button>
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { default as __experimentalToolbarContext } from './toolbar-context';
export { default as ToolbarGroup } from './toolbar-group';
export { default as ToolbarItem } from './toolbar-item';
export { default as ToolbarDropdownMenu } from './toolbar-dropdown-menu';
export { default as Tooltip } from './tooltip';
export { default as Icon } from './icon';
export { default as Spinner } from './spinner';
export {
Expand Down
8 changes: 8 additions & 0 deletions packages/components/src/tooltip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ The tooltip text to show on focus or hover.

- Type: `String`
- Required: No

### visible (native only)

Whether the tooltip should be displayed on initial render. This prop is currently only available for the native mobile app built with React Native.

- Type: `Boolean`
- Required: No
- Default: `false`

0 comments on commit 2436759

Please sign in to comment.