-
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
Implement toolbar on React Native #9012
Conversation
* 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
Updated to latest master and after resolving conflicts it seems there is no need for nativizing |
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.
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; |
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.
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 |
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 do we need this line: eslint-disable-next-line camelcase
?
@@ -0,0 +1,9 @@ | |||
const ToolbarButtonContainer = ( props ) => ( | |||
<div | |||
key={ props.keyProp } |
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.
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 ); |
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 doesn't look like a tooltip implementation at all :)
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.
@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 ) { |
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.
Can we keep the original name?
We can re-export it instead of aliasing:
export const isMacOS = isAppleOS;`
// vs
export * from './platform';
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'm a bit confused, do we to keep the function name isAppleOS
and only add an extra export with the name isMacOS
?
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'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 ) { |
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.
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; |
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.
You can use lodash.get
to simplify this code:
return get( this.state, [ 'formats', format, 'isActive' ], false );
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 this was copy paste from the original code at the time.
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.
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 ) |
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.
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?
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.
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?
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 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() } |
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.
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 ) }> |
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.
Can we convert div
into View
abstract component instead of reimplementing every div
as a separate component?
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.
👋 @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 div
s we implement like that, and we'll start consolidating them into more general implementations, or better, indeed move them to general View+SCSS.
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.
Can you create a follow up issue and leave inline comments so we make sure we clean it up when those patterns are established?
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'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?
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.
A follow-up issue is fine, too. We can handle it, no worries @hypest.
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.
Looping back, here's the ticket: #9284
* | ||
* @return {boolean} True if MacOS; false otherwise. | ||
*/ | ||
export function isMacOS( _window = window ) { |
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.
It looks like this method targets desktop Apple devices:
https://stackoverflow.com/questions/19877924/what-is-the-list-of-possible-values-for-navigator-platform-as-of-today
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.
But window will not exist on a RN context correct?
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, you should omit it in RN override. I think it's done this way to be able to mock it in unit tests.
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 |
@@ -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'; |
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.
SVG
and Path
are internal dependencies.
@@ -0,0 +1,4 @@ | |||
import React from 'react'; |
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.
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 |
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 comment should follow convention and start with a capital letter and end with .
.
|
||
const ToolbarContainer = ( props ) => ( | ||
<View | ||
style={ {flex: 1} } |
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.
Nit: there should be spaces inside inner curly braces
@@ -0,0 +1,10 @@ | |||
import { View } from 'react-native' |
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.
Comment is missing:
/**
* External dependencies
*/
@@ -0,0 +1 @@ | |||
export const LinkContainer = () => {} |
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.
default
keyword is missing in the export statement
@@ -0,0 +1,7 @@ | |||
import Svg from 'react-native-svg'; |
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.
Comment is missing:
/**
* External dependencies
*/
@@ -0,0 +1,3 @@ | |||
import { Path } from 'react-native-svg'; |
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.
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'; |
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.
Both SVG
and Path
are internal dependencies
); | ||
} | ||
|
||
export default forwardRef( Button ); |
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.
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() } |
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.
Nit: empty lines at the end of the line should be removed.
|
||
export default ( props ) => ( | ||
<View | ||
style={ props.className } |
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.
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": { |
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'm not quite sure why package-json.lock
got updated here.
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 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
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.
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?
…s to the lock file
…berg into rnmobile/toolbar
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
Types of changes
This adds RN version of the following components:
Checklist: