This package has been deprecated

Author message:

Not maintained

@kiwicom/orbit-react-native

0.7.3 • Public • Published

Orbit React Native Components

Component library compatible with Expo/React Native projects.

Mission of the project - Short introduction

  • The main mission of the project is to implement the Orbit Design System in React Native.
  • Create an equivalent of every Orbit Component that makes sense in the context of React Native.
  • Have complete control over every component that will be used by developers (including View and Text) to make sure the whole page follows not only the visual style of Orbit but also the UX.
  • Provide sensible defaults and make is easy to create beautiful and accessible pages and apps fast.

Use in your project

  1. Add it to your project:
yarn add @kiwicom/orbit-react-native
  1. Import the Roboto font (Roboto-Regular, Roboto-RegularItalic, Roboto-Medium, Roboto-MediumItalic, Roboto-Bold, Roboto-BoldItalic) to your project.

Also, make sure to import the orbit-icons font, which you can find here: https://orbit.kiwi/visual-style/icons/

How to do it in Expo: https://docs.expo.io/versions/latest/guides/using-custom-fonts/

Make sure that the fonts are loaded, before you start using any ORN components!

  1. (optional) If you want to use another font (eg: CircularPro), make sure that the appropriate weights and styles are imported in your environment, and the theme is defined on the app level:
import { ThemeProvider, defaultTheme } from '@kiwicom/orbit-react-native';

function MyApp() {
  return (
    <ThemeProvider
      theme={{
        ...defaultTheme,
        fontFamily: {
          '400-normal': 'CircularPro-Book',
          '400-italic': 'CircularPro-BookItalic',
          '500-normal': 'CircularPro-Medium',
          '500-italic': 'CircularPro-MediumItalic',
          '700-normal': 'CircularPro-Bold',

          // Map bold & italic text to be just bold for readability
          '700-italic': 'CircularPro-Bold',
        },
      }}
    >
      {/* Your code using CircularPro goes here */}
    </ThemeProvider>
  );
}
  1. Use ORN in your project:
import { Card } from '@kiwicom/orbit-react-native';

function MyComponent() {
  return (
    <Card title="Cool" onPress={pressHandler}>
      Cool component
    </Card>
  );
}

Notes for ORN developers

Running the project

  1. Set up the monorepo - https://kiwi.wiki/incubator/universe/docs/monorepo/installation
  2. Make sure all packages are up-to-date - yarn
  3. Run Storybook with Expo yarn workspace @kiwicom/orbit-react-native-docs start
  4. Write code as usual

Component directory/file structure

  • In the src folder, there should be a single directory for each public component.
    • Folders for components are UpperCamelCase,
    • Folders for everything else are lowercase-with-dashes,
    • Component names are UpperCamelCase.
  • In a component's folder, the main file should be index.js for each component.
    • The main file for each component should always have a named export which corresponds to the component name.
    • Test files should be named the same as the exported name of the component they are testing.
    • Files that export components should be UpperCamelCase,
  • Public components should be in root folder of the component (and private components should be in components sub-folder)
orbit-react-native/src/
├── index.js                         # All public components are re-exported here (by hand)
└── Card/                            # Single component folder of the Card
    ├── components/                  # Folder with private components
    │   └── Circle.js                # Private Circle component
    ├── __tests__/                   # Jest unit tests go here
    │   └── Card.test.js             # Test file for Card
    ├── index.js                     # File for exporting all public components
    ├── README.md                    # Information about component for users
    ├── CardHeader.js                # Public component, exported as { CardHeader }
    ├── Card.js                      # Public component (it should be the main one), exported as { Card }
    ├── Card.helpers.js              # Helper functions
    ├── Card.stories.js              # Defines stories for Storybook
    └── Card.types.js                # All types for whole component bundle (which means Card, CardHeader and Circle in this case)

Auto-generating files

Some components require files to be generated based on some external data, eg: list of icons.

We have a simple mechanism to help with the generation, just run:

yarn workspace @kiwicom/orbit-react-native generate

What this script does, is the following:

  • It finds all folders named __templates__ within src/
  • It imports them, expecting the following to be exported:
{
  output: string /* path relative to package root */,
  input?: string /* path relative to package root */,
  generate: (
    anythingThatInputExports,
    HEADER /* block comment to be inserted at the top of each generated file */
  ) => string
}
  • It will then require() whatever is in input and feed it to generate()
  • It will take the output of generate() and write it to output

Rules about generator files

  • All template generator files should:
    • be inside a __templates__ folder,
    • be named *.template.js,
    • pass Flow and ESLint checks
      • but use Flow Comment Types to avoid problems with running in NodeJS.

Rules about generated files

  • All generated files should:
    • be named *.generated.js
    • have a block comment at the top of the file explaining
      • that it's been generated,
      • which command generated it.
    • pass ESLint and Flow checks without ignores,
    • be checked in to Git.

Release process

  1. Get your code ready for release
    • Make sure the code you want to release has been merged to master and all the tests have passed.
  2. Bump the version
  3. Create a release branch
    • Make a new branch with the version as the name: orbit-react-native/4.2.0 or orn/4.2.0.
  4. Create the CHANGELOG
  5. Create the MR
    • Target branch should be master,
    • Title should be ORN: Release x.x.x,
    • Description should be the contents of the CHANGELOG and an Expo preview URL,
      • You can find the Expo URL at the end of the deploy hash orn expo build pipeline job.
  6. Create the release announcement
    • Create a short announcement for Slack that contains the version number, the changelog and the Expo URL,
    • Summarize what is in the release for non-developer stakeholders and add it to the message.
  7. Final review
    • Post the release message to #orbit-secrets and cc @vepor @honza @will for a final review,
    • Fix anything that needs fixing or move to the next step.
  8. Merge the MR
    • Merge the MR to master.
    • Wait for the pipeline to finish the deploy step,
    • Make sure that the release is pushed to the npm registry and the version is tagged as latest.
  9. Announce the release in #orbit-react-native & #orbit-announcements
    • Use the announcement that you wrote a few steps ago, without the extra cc part.

Tech stack

  • Universe Monorepo
    • Flow
    • babel-preset-kiwicom
    • eslint-config-kiwicom
    • monorepo-npm-publisher
  • orbit-react-native
    • react-native
    • @adeira/js (used for invariant(), should be replaced by @kiwicom/js - can't yet due to webpack/babel/monorepo issues with RN)
  • orbit-react-native-docs
    • Expo
    • Storybook

Storybook

The Storybook installation related to this project is stored in a several package and folder. There is a very simple reason for this, the monorepo-npm-publisher that comes with the Universe Monorepo has some restrictions that we couldn't get around without crazy hacks:

  • monorepo-npm-publisher requires main to be set to src/index in package.json
  • Expo requires main to be set to AppEntry.

Another simple reason is that we didn't want to bundle any of Storybook's or Expo's dependencies with the library, that is the responsibility of the app that uses ORN.

There is one crazy hack in place: orbit-react-native/src/**/*.stories.js files actually belong to orbit-react-native-docs and are imported using the dreaded ../../orbit-react-native/src/... technique. This way we have the stories of each component together with their code, but still get around the limitations of the npm published.

Coding conventions

  • Use Functional Components with React Hooks instead of HOCs.

React is evolving we should evolve with it, always try to use the latest recommended patterns to make sure the code will be easy to maintain for future developers.

  • Always use named exports in favor of defaults.

If we follow this same convention everywhere, the exported names should already contain some information on what is being exported, instead of generic names.

  • Merge enum consts and Flow definitions, DRY.
/**
 * Example on how to have an enum used for Flow types,
 * and JS code as well.
 */

// This object will not be used directly outside of the definitions
const spacingsObj = Object.freeze({
  none: 0,
  extraTight: 1,
  tight: 2,
  condensed: 3,
  compact: 4,
  natural: 5,
  comfy: 6,
  loose: 7,
  extraLoose: 8,
});

// Array of possible values
const spacings: string[] = Object.keys(spacingsObj);

// Flow type that can be used for argument validation
type spacingType = $Keys<typeof spacingsObj>;
  • Avoid importing external libraries

When working on a library (as opposed to an application), we have to make sure to minimize dependencies on external projects:

  • More control over what is tested (some libraries are not tested at all),
  • In case of vulnerabilities in the imported projects, upgrading could be a hassle (or worse, the dependency is abandoned, and will never be fixed),
  • Keep the bundle size in check.

StyleSheet.create()

We were thinking about how to pre-generate our styles for Orbit React Native, so that everything gets nicely cached and we have a sane API.

Finding #1 - StyleSheet.create() is basically the same as (styles) => styles

There is no caching going on here, no StyleRegistry and no IDs returned. See the YOLO-prefixed commit that was merged in 2018 April, over a year ago... - https://github.com/facebook/react-native/commit/a8e3c7f5780516eb0297830632862484ad032c10

Finding #2 - Passing style objects or references to style objects are just as fast.

We have a small benchmark, comparing styled-components to StyleSheet, and with a quick modification, we can see the difference between rendering something using the 'proper-way' StyleSheet.create() vs just passing the inline objects - https://github.com/sampi/expo-stylesheet-vs-styled-components/tree/full-inline

Our findings were that:

  • StyleSheet.create() is about 0-10% faster on RN than inline styles,
  • Inline styles are about 0-10% faster on RNW than StyleSheet.create() is,
  • styled-components are 10-100% slower on both RN/RNW.

Conclusion - It's ok to use inline style objects in RN.

If we don't have to pre-generate the styles, then we can just write code, like we would do it normally for web or any native mobile app, instead of having to pre-generate the styles, and make sure everything is ready before we can use it. It also means (especially for Orbit React Native), that we don't need convoluted APIs all over the place, just so we can handle buttons, that could be 10 different types, with 2 separate boolean switches (that's a lot of pre-generated styles taking up memory.)

Later we found out that RNW does actually cache the values for each call to StyleSheet.create() but we are not using it in this project, so that is not an issue right now.

Orbit Design Tokens

When working with Orbit Design Tokens, some values are not the correct type required by RN, we have some helpers available for this.

// cwd: orbit-react-native/src/SomeComponent
import {
  ucCase, //convert dash-case to UpperCamelCase
  capitalize, // capitalize the first character of any string
  secondsToMs, // convert a second duration string to millisecond number
  msToSeconds, // convert a millisecond number to a second duration string
} from '../common/utils';

When working with other units, we recommend using parseFloat() to make them compatible with RN.

When working with colors, RN accepts rrggbbaa format, so it's easy to add opacity to existing colors.

Font weights

To use different font weights, we have some helpers available.

// cwd: orbit-react-native/src/SomeComponent
import { getTextStyles } from '../styles';

function getStyles({ theme }) {
  return {
    smallText: { ...getTextStyles({ theme, fontSize: 'small' }) },
    smallItalicText: { ...getTextStyles({ theme, fontSize: 'small', italic: true }) },

    smallBoldText: { ...getTextStyles({ theme, fontSize: 'small', fontWeight: 'bold' }) },
    smallBoldItalicText: {
      ...getTextStyles({ theme, fontSize: 'small', fontWeight: 'bold', italic: true }),
    },

    normalText: { ...getTextStyles({ theme }) },
    normalItalicText: { ...getTextStyles({ theme, italic: true }) },

    boldText: { ...getTextStyles({ theme, fontWeight: 'bold' }) },
    boldItalicText: { ...getTextStyles({ theme, fontWeight: 'bold', italic: true }) },

    largeText: { ...getTextStyles({ theme, fontSize: 'large' }) },
    largeItalicText: { ...getTextStyles({ theme, fontSize: 'large', italic: true }) },

    largeBoldText: { ...getTextStyles({ theme, fontSize: 'large', fontWeight: 'bold' }) },
    largeBoldItalicText: {
      ...getTextStyles({ theme, fontSize: 'large', fontWeight: 'bold', italic: true }),
    },
  };
}

Sharing code

If you want to share code between components, first make sure that it is actually something that is being shared. Don't try to optimize in advance.

Place the shared code in these folders:

  • Shared components: orbit-react-native/src/common/components/,
  • Shared helper functions: orbit-react-native/src/common/helpers/,
  • Shared React Hooks: orbit-react-native/src/common/hooks/,
  • Shared style helpers (temporary folder): orbit-react-native/src/common/styles/, (TODO: merge with other style helpers),
  • Shared style helpers: orbit-react-native/src/styles/,
  • Other shared code: orbit-react-native/src/common/.

Known issues

  • Only a very minimal unit test are written.

The plan was to do minimal unit testing, and cover most of our use cases using cross-platform screenshot testing. (still in TODO)

  • getStyles() is called on each render.

We decided that the performance overhead of generating inline styles on each render is not going to be a bottleneck. If the project wants to enable RNW usage again, this will be a major refactoring point.

  • RNW code remnants might be still in the code.

We tried our best to remove all RNW code from the repo but some components might still have references to web versions or maybe some code is still structured in a way that enables different rendering for different platforms.

History

Why we step out from React Native Web

  • Rewriting all Orbit components to RN & RNW, instead of using stable Orbit components in React.
  • Mixing native & web APIs – not everything is needed on all platforms, but for developing it once, we need to cover all cases on all, instead of implementing only what is needed. With more complicated components, code gets unnecessarily complex.
  • Hard maintenance – testing RN is tricky on Android on its own, and I think no one tested RNW on Internet Explorer yet.
  • RNW maturity – the project itself is not there yet and it’s causing serious issues, we are also not sure that RNW approach is the right approach for code sharing.
  • Hacking web styles with javascript – lack of media queries, focus/hover states, … It’s possible to do it somehow with JS, but it takes much more time to do it, code is more complex and it can have an impact on performance. For example, media queries are possible, but it results in jumping UI sometimes.
  • Harder to achieve the correct HTML markup - everything is div by default, there is then some conversion for the web through RNW, but sometimes it gets complicated – e.g. there is nothing like <a> inside of <p> in RN, so it needs to be hacked somehow. For example, even small, simple components are wrapped in multiple divs, even button is basically a bunch of divs.
  • Incompatible tech stacks of Universe monorepo and other repositories.

Future plans

  • Remove Expo – Expo doesn't bring real value in this project. The only reason why we started with Expo was the possibility of easy sharing of the app. Expo is unnecessary layer and you can't have up-to-dated version of React Native while using Expo.
  • SVG – It would be very handy if we could use SVG in Orbit React Native. But first we have to analyse impact on the mobile device's battery.
  • Orbit monorepo – This project should be moved to new monorepo from Orbit when it will be ready.
  • Accessibility – Whole accessibility in Orbit needs to define first what is important and how it should be implemented.
  • Regression tests – It's hard to test UI components wisely. Regression testing could be very useful in this case.

Useful links

Package Sidebar

Install

npm i @kiwicom/orbit-react-native

Weekly Downloads

1

Version

0.7.3

License

MIT

Unpacked Size

655 kB

Total Files

438

Last publish

Collaborators

  • mvidalgarcia
  • dsil
  • jakubzaba
  • robincsl_kiwi
  • kiwicom.platform
  • dinodsaurus