Skip to content

Instantly share code, notes, and snippets.

@dantman
Last active July 10, 2017 14:57
Show Gist options
  • Save dantman/7dd9a78250c6a3bf6a4220afd4d75b88 to your computer and use it in GitHub Desktop.
Save dantman/7dd9a78250c6a3bf6a4220afd4d75b88 to your computer and use it in GitHub Desktop.
React Native Material Design/Kit merge plan

React Native Material Design/Kit merge plan

Take components from react-native-material-design and react-native-material-kit and create a new library to replace both.

Ripple

Deciding how to handle ripples will be very important as both do it differently.

rnmd does it using a Ripple polyfill:

  • Pro: The native touchable feedback is used where available.
  • Con: The polyfill needs to split styles into inner and outer styles (broken for StyleSheets) and has nested views that completely break view flexing
  • Pro/Con: There is a nice array elevation syntax for raising elevation on touch, however it doesn't work cross platform
  • Con: Polyfill does not support borderless ripples

mdk does it using an internal touchable and a masked ripple:

  • Pro: MKTouchable is not supposed to interfere with press and responder handlers
  • Pro: Ripple doesn't have any view nesting, so there are no bugs with StyleSheets or flexing
  • Pro: Ripple does seem to support borderless ripples
  • Con: MKTouchable doesn't seem to be sending the DOWN event properly on Android when I test it breaking it
  • Con: There appear to be some small bugs in zIndexing when the ripple is borderless
  • Con: Some extra masking props appear to be required and there appear to be some open bugs

Current plan:

  • The MKTouchable idea is nice, however it appears that in practice ripples are well linked to responders and don't simply react to casual touches.
    • When you move your finger off a button the ripple ends immediately signifying that letting go will no longer trigger a button press.
    • On a long press a whole list item gets tinted at a fair speed, then a ripple happens at the same moment the long press event is triggered
  • Considering this I think the plan should be to use Touchable.Mixin on a View or extend TouchableWithoutFeedback to implement a MaterialTouchable (HOC, Component, or some other sort of internal integration function) that can be used to implement Ripple (this wouldn't be part of Ripple directly because other components like ActionButton/IconToggle/etc need an implementation that uses the same MaterialTouchable behaviour but affects a centered circle instead of dealing with complex masked ripple behaviour)
  • The Ripple implementation will probably be based on react-native-material-kit's implementation. At this level of complexity control and ensuring that this actually works cross-platform reliability is going to be very important so TouchableNativeFeedback will not be used. rnmd's polyfill also does not support borderless ripples. MK's implementation is unfortunate in its masking issues, rnmd's flex and style issues are really really problematic in real development and are not simple to workaround cleanly. However due to the need to specify masking information instead of providing Ripple as a component that can be used directly we should probably discourage use of it directly and provide a variety of very simple low level components that implement the right kind of ripple and masking behaviour for various situations (list items, basic buttons, regions of a card, etc...) that can be used to construct things.

Icons

RN already has a very well known and well maintained icon library, we should not do anything of our own. We should require installation of that library to use icons in ours.

However it may be reasonable to implement some sort of wrapper component that defaults the size parameter to the Material Design standard of 24 and provide an easy way to apply the material design style colors to the icon, even implicit theming. eg: The theme could implicitly know if it's in light or dark theme mode and the icon could accept a simple disabled boolean prop (much simpler than explicitly passing colors).

We do however have to keep in mind that there are actually 2 relevant icon components, MaterialIcons and MaterialCommunityIcons. We also have to consider that there is reason for users to create their material-style icons for their own apps and generate a new icon component for their icons. We'll probably want to export 3 things, Icon (the wrapper for MaterialIcons with our modifications), CommunityIcon (the wrapper for MaterialCommunityIcons), and createIconComponent (the function that does the wrapping for the others and can be applied to custom icon components).

Themes

Material UI and react-native-material-ui should be used as inspiration for handling themes, which neither rnmd or material-kit currently do. But some modifications may be desirable.

  • Make use of context
  • Don't use plain objects, make sure that the theme is frozen and cannot be improperly modified at runtime
  • Allow the theme to be extended and passed down (ie: a component should be able to intercept the theme from the component, extend it, and then pass that extension down to its children)
  • Make it easy to use a light or dark theme in an app without a lot of passing things (and ideally better than the "light is default, merge with dark overrides to get a dark theme" pattern)
  • Make it easy to momentarily switch light/dark for some things (eg: in a toolbar all the buttons, etc... that are normally dark expecting a light bg turn light when dark enough solid primary color header background is used)
  • Likewise some things like raised buttons have special types of light/dark theme (dark=primary (or accent) color bg, dark text (or light in special cases when the color is light); light=white bg for the overall light theme and dark text)
    • We can probably automatically choose the text color by using the wcag-contrast package
      • If font size is >=24px or >=18.5px and bold wcag.hex(primary, '#fff') >= 3 ? color.light.primaryText /* white */ : color.dark.primaryText /* black */
      • For normal text use, wcag.hex(primary, '#fff') >= 4.5 ? color.light.primaryText /* white */ : color.dark.primaryText /* black */
  • Do not require the theme, if one is not found in context there should be no warnings or errors. Components should just fall back to the default styles like they do in our base libraries.
  • Always allow all the themed styles to be overridden directly on the components with properties.
  • Provide a module with all the values of the material palette, but don't do it in the rnmd way ("paper" prefixes alongside "google" colors with {color: ...} values). But also provide colors with the various other important things (light/dark, primary/secondary/disabled/hint text, dividers, icons, icon info as opacity value, statusBar opacity values and rgba color)

Builder interface

material-kit's builder interface really doesn't fit into the React pattern well. However I expect their users will not be happy with it simply being dropped.

As an alternative I propose that all components be written as normal React (Native) components but they expose a little bit of necessary metadata about their props and we implement an optional builder interface that uses that metadata to generate a builder interface.

import {Button} from '???';
import M from '???/builder';

const SigninButton = M(Button)
    .withRaised(true)
    .withText('SIGN IN')
    .build();

The builder function would automatically construct the builder for this component from its metadata and the final .build() would return a HOC that implicitly passes all the relevant props to the component and proxies all the static and instance methods.

Platform handling

Material Design recommends that some things adapt to the platform (ie: are native on iOS) https://material.io/guidelines/platforms/platform-adaptation.html#platform-adaptation-platform-recommendations

  • Typography values should switch to the System/SanFransisco font on iOS instead of hardcoding Roboto (which doesn't work anyways unless the font is explicitly added)
  • React Native's built-in <Switch> already implements MD on Android and Apple style on iOS, Switch should probably be a low-priority component and we should recommend use of the native one. We may want to add one simply for the theme integration and customization that may not be available in the native version. Depending on users we may also have to add some sort of override to force the Android Material Design style on iOS.
  • We may want to implement an iOS style checkmark component for lists and a few helper components that use the correct platform component in various situations (ie: Android and everyone else vs iOSa: A general helper that uses a Checkbox or Switch, a Radio list helper that uses a Radio or a Checkmark, a Check list helper that uses a Checkbox or a Checkmark)

Other notes

  • Don't bother implementing very specific fancy animations like react-native-material-ui has. These take time to implement and are very specific in their use. However there should be some room considered in making sure we separate out enough low-level implementation pieces of our components that someone can use them as building blocks to implement these more complex animated components.
  • Don't bother implementing toolbars or tab bars. These are largely the responsibility of the navigators like react-navigation and so realistically our implementations wouldn't normally be used. We should though implement something equivalent to the IconToggle that some of the old MD libraries have so that it's easy to do material style toolbar actions.
    • In the future there may be room to consider some integrations with react-navigation (offering compatible header components that extend and integrate in to react-navigation's header component; or making it so that react-navigation's header and tab bars make use of our theme) and room to consider how we can make it easy/possible to implement things like the header to search bar transformation.
  • Use props like labelStyle={} instead of the styles={{label: ...}} that rnmd uses, the latter makes it more difficult for PureComponent optimizations to work
  • rnmd implements an IconToggle that is an ActionBar ready component with a badge, material-kit implements an IconToggle that is an actual checkbox like icon toggle (grey when off, colored when on).
    • We should implement an IconToggle that works according to Material Design's pattern for icon toggles, which is mostly material-kit's design but with a different handling of ripple coloring
    • A simple ActionButton (may need a rename) should implement the action-bar ready icon button behavior instead of being miss-named an IconToggle
    • Additionally instead of being merged into that component, we should have a separate IconBadge (or just Badge?) that can be wrapped/nested with an ActionButton to add an optional badge to it (it should not be part of the component itself, that makes it unnecessarily heavy and also adds something (badges) which aren't actually documented in material design)
  • On Android material design apps seem to have a pattern where when you press down on the dot menu button and then move your finger off the button, the menu opens up and any item under your finger is focused as you move your finger around and considered pressed on release (unlike when you tap the button, then putting your finger on a menu item and moving to another does not change focus it just terminates the touch)
  • Make sure our playground app is Expo compatible so it is easy for users to install and try out; but also make sure that it also runs on its own so it's easy to run in the simulator without installing Expo.
  • Just like react-navigation use flow types and use prop-types to create propTypes automatically based on flow
  • The checkbox should not have a built-in label like rnmd's does. We should have a low-level checkbox, and perhaps some helpers for when it is used in different situations. (As part of a list item, as a checkable list item, as a labeled checkbox with the same label keyline as lists, and an inline labeled checkbox like you'd use in a login form)

Maintenance ideas

  • Codeship may be able to automate publishing to npm (if I'm not happy with how Travis could do it) or deploying docs without me needing to run commands locally

Docs

These packages to automatically generate good documentation for the components so they are better documented than rnmd and material-kit's components.

Depending on the output and size documentation can be put in one of multiple locations:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment