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

Implement toolbar on React Native #9012

Merged
merged 31 commits into from
Aug 22, 2018
Merged

Implement toolbar on React Native #9012

merged 31 commits into from
Aug 22, 2018

Conversation

SergioEstevao
Copy link
Contributor

Description

This PR adds support for the Toolbar component on React Native.

At the moment it only renders the toolbar for the RichText component.

How has this been tested?

Using this PR on the gutenberg mobile repo.

You can see that for each RichText block we get the basic format interface.

Screenshots

screen shot 2018-08-15 at 10 21 33

Types of changes

This adds RN version of the following components:

  • Toolbar
  • Rich Text / Format-Toolbar
  • DashIcon
  • IconButton

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.

@hypest hypest added the Mobile App - i.e. Android or iOS Native mobile impl of the block editor. (Note: used in scripts, ping mobile folks to change) label Aug 16, 2018
* Dedicated components for SVG, Path

* Fix lint errors

* Dedicated native components for button, icon-button, tooltip

* Dedicated native mobile comps for Toolbar containers

* Small code styling fix

* Fix double "key" typo in prop name
@gziolo gziolo requested review from youknowriad, aduth and a team August 17, 2018 10:11
@gziolo gziolo added the Framework Issues related to broader framework topics, especially as it relates to javascript label Aug 17, 2018
@hypest
Copy link
Contributor

hypest commented Aug 17, 2018

Hmm, the conflicts with the snapshot files surface the need to componentize and nativize yet another component, the <g> element.

Updated to latest master and after resolving conflicts it seems there is no need for nativizing <g>.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

Should we group all primitives in one folder inside components package? At the moment we have:

  • View
  • Button
  • SVG
  • Path

I'm sure we will have more components like that:

  • Text
  • Link
  • Image
  • ...

import { forwardRef } from '@wordpress/element';

export function Button( props ) {
const { onClick } = props;
Copy link
Member

Choose a reason for hiding this comment

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

You can also use deconstructing with aria-label:

const { children, onClick, 'aria-label': ariaLabel } = props;

and include children props, too.

@@ -0,0 +1,4 @@
// Components
// eslint-disable-next-line camelcase
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this line: eslint-disable-next-line camelcase?

@@ -0,0 +1,9 @@
const ToolbarButtonContainer = ( props ) => (
<div
key={ props.keyProp }
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to pass it down, it is used only by React for its internal optimizations. See related React doc: https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys.

import React from 'react';

// for the native mobile, just shortcircuit the Tooltip to return its child
export default ( props ) => React.Children.only( props.children );
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look like a tooltip implementation at all :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hypest did this changes I think it's just a stub implementation to avoid changes on the format-toolbar component that uses this. To be honest tooltips don't make a lot of sense on native apps. You are not hovering any elements on them...

*
* @return {boolean} True if MacOS; false otherwise.
*/
export function isAppleOS( _window = window ) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we keep the original name?

We can re-export it instead of aliasing:

export const isMacOS = isAppleOS;`
// vs
export * from './platform';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm a bit confused, do we to keep the function name isAppleOS and only add an extra export with the name isMacOS?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure why we needed isAppleOS in the first place. If we can remove it then sure.

* @return {boolean} True if iOS; false otherwise.
*/
// eslint-disable-next-line no-unused-vars
export function isAppleOS( _window = window ) {
Copy link
Member

Choose a reason for hiding this comment

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

To make it even simpler you can do the following:

FIle name: platform.ios.js:

export const isMacOs = () => true;

FIle name: platform.android.js:

export const isMacOs = () => false;

@@ -69,25 +84,68 @@ export class RichText extends Component {
return true;
}

isFormatActive( format ) {
return this.state.formats[ format ] && this.state.formats[ format ].isActive;
Copy link
Member

Choose a reason for hiding this comment

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

You can use lodash.get to simplify this code:

return get( this.state, [ 'formats', format, 'isActive' ], false );

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this was copy paste from the original code at the time.

Copy link
Member

Choose a reason for hiding this comment

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

Another reason, to split this file into two chunks :)

// const { linkValue, settingsVisible, opensInNewWindow } = this.state;
// const isAddingLink = formats.link && formats.link.isAdding;

const toolbarControls = FORMATTING_CONTROLS.concat( customControls )
Copy link
Member

Choose a reason for hiding this comment

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

It looks like all the difference between native and web in this file boils down to the render implementation. Can we extract another component instead and get rid of all this code duplication which quickly will become hard to maintain?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is some import that are commented out as you can see on the top of the file. Should we go ahead and and create empty native version of those?

Copy link
Member

Choose a reason for hiding this comment

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

I would extract render method to its own component first and then see if there is anything else to override in the original file. My assumption is that all overrides happen inside render.

key={ [ indexOfSet, indexOfControl ].join() }
keyProp={ [ indexOfSet, indexOfControl ].join() }
Copy link
Member

Choose a reason for hiding this comment

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

keyProp is not needed in here, see my other comment.

@@ -54,11 +56,12 @@ function Toolbar( { controls = [], children, className } ) {
}

return (
<div className={ classnames( 'components-toolbar', className ) }>
<ToolbarContainer className={ classnames( 'components-toolbar', className ) }>
Copy link
Member

Choose a reason for hiding this comment

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

Can we convert div into View abstract component instead of reimplementing every div as a separate component?

Copy link
Contributor

Choose a reason for hiding this comment

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

👋 @gziolo, I deliberately avoided subplanting div with View because it would move the whole problem of mirroring a particular div's behavior to SCSS, and I don't think we should do that yet. SCSS support is still highly experimental in the RN app. So instead, I opted for implementing super specific components that are well defined in behavior, will be easy to reason about and will help us move along.

I actually expect patterns to start emerging the more divs we implement like that, and we'll start consolidating them into more general implementations, or better, indeed move them to general View+SCSS.

Copy link
Member

@gziolo gziolo Aug 21, 2018

Choose a reason for hiding this comment

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

Can you create a follow up issue and leave inline comments so we make sure we clean it up when those patterns are established?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm AFK without a laptop for a couple of days (😱). I can do the inline comments in a few days unless someone else does it in the meantime. Feel free to do it @SergioEstevao or @gziolo?

Copy link
Member

Choose a reason for hiding this comment

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

A follow-up issue is fine, too. We can handle it, no worries @hypest.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looping back, here's the ticket: #9284

*
* @return {boolean} True if MacOS; false otherwise.
*/
export function isMacOS( _window = window ) {
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But window will not exist on a RN context correct?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, you should omit it in RN override. I think it's done this way to be able to mock it in unit tests.

@gziolo
Copy link
Member

gziolo commented Aug 22, 2018

I have just merged #9192 which should simplify this PR. There was related discussion if the mobile part is concerned at all with the link editing logic at the moment. We would like to move it to the LinkContainer component.

@@ -9,6 +9,8 @@ OR if you're looking to change now SVGs get output, you'll need to edit strings
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import SVG from './svg';
Copy link
Member

Choose a reason for hiding this comment

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

SVG and Path are internal dependencies.

@@ -0,0 +1,4 @@
import React from 'react';
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't be referencing React directly. We should rather import Children from @wordpress/element.

@@ -0,0 +1,4 @@
import React from 'react';

// for the native mobile, just shortcircuit the Tooltip to return its child
Copy link
Member

Choose a reason for hiding this comment

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

This comment should follow convention and start with a capital letter and end with ..


const ToolbarContainer = ( props ) => (
<View
style={ {flex: 1} }
Copy link
Member

Choose a reason for hiding this comment

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

Nit: there should be spaces inside inner curly braces

@@ -0,0 +1,10 @@
import { View } from 'react-native'
Copy link
Member

Choose a reason for hiding this comment

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

Comment is missing:

/**
 * External dependencies
 */

@@ -0,0 +1 @@
export const LinkContainer = () => {}
Copy link
Member

Choose a reason for hiding this comment

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

default keyword is missing in the export statement

@@ -0,0 +1,7 @@
import Svg from 'react-native-svg';
Copy link
Member

Choose a reason for hiding this comment

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

Comment is missing:

/**
 * External dependencies
 */

@@ -0,0 +1,3 @@
import { Path } from 'react-native-svg';
Copy link
Member

Choose a reason for hiding this comment

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

Comment is missing:

/**
 * External dependencies
 */

@@ -9,6 +9,8 @@ OR if you're looking to change now SVGs get output, you'll need to edit strings
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import SVG from './svg';
Copy link
Member

Choose a reason for hiding this comment

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

Both SVG and Path are internal dependencies

);
}

export default forwardRef( Button );
Copy link
Member

Choose a reason for hiding this comment

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

forwardRef won't work this way, besides it is not used inside the component. I would simply remove it from native implementation.

<div
key={ [ indexOfSet, indexOfControl ].join() }
<ToolbarButtonContainer
key={ [ indexOfSet, indexOfControl ].join() }
Copy link
Member

Choose a reason for hiding this comment

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

Nit: empty lines at the end of the line should be removed.


export default ( props ) => (
<View
style={ props.className }
Copy link
Member

@gziolo gziolo Aug 22, 2018

Choose a reason for hiding this comment

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

Will it work on mobile? It's a string but in other places I see objects.

@@ -2652,6 +2653,13 @@
"lodash": "^4.17.10"
}
},
"@wordpress/token-list": {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not quite sure why package-json.lock got updated here.

Copy link
Member

@gziolo gziolo Aug 22, 2018

Choose a reason for hiding this comment

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

I know what happened, @wordpress/token-list wasn't included in the package.json and it caused package-lock.json to be regenerated on 2nd npm install call...

/cc @aduth

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

There are some minor questions to be answered or confirmed before merging but otherwise it looks good. Great work on this one. It was quite complex refactoring 👍

Can you retest with the Gutenberg mobile to ensure I didn't break anything with my recent commits?

@gziolo gziolo added this to the 3.7 milestone Aug 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Framework Issues related to broader framework topics, especially as it relates to javascript Mobile App - i.e. Android or iOS Native mobile impl of the block editor. (Note: used in scripts, ping mobile folks to change)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants