1.0.1 ā€¢ Published 4 years ago

@backpackerds/primitives v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

primitives

Foundational components for React Native UI

npm version Build Coverage Status code style: prettier

  1. Install
  2. Usage
  3. What are primitives
  4. How to adopt in your project
  5. Primitives API
    1. Layout
    2. Typography
    3. Spacer
    4. Touchable
    5. Expandable
    6. Modal
  6. Custom themes
  7. Contributing

Install

yarn add @backpackerds/primitives

Usage

/*
  Wrap your app Root with the `ConfigProvider` component
*/

// root.js
import { ConfigProvider } from '@backpackerds/primitives';

const Root = () => {
  return (
    <ConfigProvider>
      <App />
    </ConfigProvider>
  );
};

/*
  Then use primitives anywhere you want
*/

// screens/hello.js
import { Row, H1, H3, Spacer, Touchable } from '@backpackerds/primitives';

const MyComponent = (props) => {
  const sayHello = () => console.log('Hi! šŸ‘‹');

  return (
    <Touchable onPress={sayHello}>
      <Row>
        <H1 uppercase>Hello!</H1>
        <Spacer size='l' />
        <H3>Tap here</H3>
      </Row>
    </Touchable>
  );
};

What are primitives

A primitive is the most basic UI component in the system (an atom from Atomic Design) that ensures a consistent foundation for all UI layers.

In fact, all the other UI layers - components and screens are built with primitives.

To change a primitive's appearance (or behavior), you can pass props - both style and behavioral props. Going with this approach, the style and the "markup" of the UI are merged together. I know that this might not be everyone's cup of tea, but I found that it helps having all the information about that UI in one place: what components are used, how they look, how they behave. It also helps with brevity (you no longer need Stylesheets) and removes style duplication. As you'll see in the following example, I only have to write a style once.

This approach opens a new way of having built-in styles. If I want, I can now merge justifyContent: center and alignItems: center into a single prop, like this:

<Column center />

This enables writing styles very DRY, since now this center modifier will be available for all my layout primitives. I only have to write it once. All primitives come with a couple of built-in modifiers, a list that can obviously be extended.

How to adopt in your project

Even if you don't use this package, you can still use the principles to build a custom set of primitives for your project and use this way of thinking about your UI.

The benefits are:

  • UI consistency
  • development speed (primitive reuse is much easier than component reuse)
  • write-it-once approach (never have to duplicate styles - they will be built in the primitives themselves most of the time)

From my experience, a good practice is to create a new directory called primitives and start building the project's own primitives there. Some of them might be imported directly from this library, others might not.

ā”œā”€ā”€ src
ā”‚Ā Ā  ā”œā”€ā”€ screens
ā”‚Ā Ā  ā”œā”€ā”€ components
ā”‚Ā Ā  ā”œā”€ā”€ primitives
ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ ...
ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ layout
ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ typography
ā”‚Ā Ā  ā”‚Ā Ā  ā”œā”€ā”€ spacer
ā”‚Ā Ā  ā”‚Ā Ā  ā””ā”€ā”€ ...

The important part is to keep a clear separation between primitives, components and screens. This is a discipline that helps on the long run. You might end up with components inside your primitives directory. That's fine, but as soon as you realize that, your job is to move them out of the directory and set them where they belong.

Primitives API

Layout

import { Column, Row, Float } from '@backpackerds/primitives'

<Column>
  <Row center></Row>
  <Float></Float>
</Column>

Float has position: 'absolute' and zIndex: 1. The others are self-explanatory. The modifier props are:

propdescription
centerjustifyContent: 'center', alignItems: 'center'
āˆžAny other View style props

Typography

import { H1, H2, H3, H4, H5, H6, H7, H8 } from '@backpackerds/primitives'

<H3 uppercase semibold>Hello!</H3>

Typography primitives take the fontSize from the config object (via a theme) and accept any text style props. There are a couple of built-in modifiers as well:

propdescription
uppercasetextTransform: 'uppercase'
underlinetextDecorationLine: 'underline'
strikeouttextDecorationLine: 'line-through'
centertextAlign: 'center'
semiboldfontWeight: weights.semibold; Same for all the other font weights. All of them can be found in config
āˆžAny other Text style props

Spacer

import { Spacer } from '@backpackerds/primitives'

<Spacer size='l' />

The Spacer is just a View with equal height and width. The size values are provided through the config object, via a theme. A good UI can be expressed with a limited set of "spacer" values - this gives consistency.

propdescription
sizeDefault is m

Touchable

import { Touchable } from '@backpackerds/primitives'

<Touchable onPress={() => console.log('Hi!')}>
  ...
</Touchable>

The Touchable primitive is a wrapper around the TouchableOpacity component.

propdescription
activeOpacityDefault is 0.5, but can be changed via this props. This ensures that we have a single source of truth for the Touchable activeOpacity
onPressA func, default is undefined
isDisabledA bool, default is false
āˆžAny other TouchableOpacity style props

Expandable

import { Expandable } from '@backpackerds/primitives'

<Expandable
  renderHeader={({ isVisible }) => <Row><H1>Expand me!</H1></Row>}>
  ...
</Expandable>

The Expandable primitive uses LayoutAnimation for a smooth expand/collapse of the content.

propdescription
initialStateA bool, default is false
renderHeaderA render func, default is undefined
onShowA func, default is noop
onHideA func, default is noop
layoutAnimationPresetA LayoutAnimation preset; default is LayoutAnimation.Presets.easeInEaseOut

Modal

import { Modal } from '@backpackerds/primitives'

const modalRef = useRef(null);

<Modal ref={modalRef}>
  ...
</Modal>

// Then it can be shown
modalRef.current.show()
// or hidden
modalRef.current.hide()

The Modal primitive is a wrapper for the default RN Modal.

propdescription
animationTypeDefault is fade
onShowA func, default is noop
onHideA func, default is noop

Custom themes

To create a custom theme, we take the config object and extend it using a new key (or overwrite an existing key). For example, let's say I want to add a new dark theme. I'll call it dark2. And I want to change the spacer values too.

import { defaultTheme } from '@backpackerds/primitives'

const dark2 = {
  ...defaultTheme,
  spacer: {
    xs: 5,
    s: 10,
    m: 15,
    l: 20,
    xl: 25
  },
  colors: {
    ...defaultTheme.colors,
    background: '#000',
    text: '#FFF'
  },
  isDark: true
}

Then, I can pass it to the ConfigProvider, in root.js:

// root.js
import { ConfigProvider } from '@backpackerds/primitives';

const customConfig = {
  dark2
}

const Root = () => {
  return (
    <ConfigProvider config={customConfig}>
      <App />
    </ConfigProvider>
  );
};

You'll always want to change the spacer or fontSize values from the default theme anyway.

Contributing

Before contributing to this repo, please open an issue to discuss the changes and the proposed solution. Communication is key.

Here are a couple of principles any new primitive added to the repo should follow:

  • A primitive should contain as little UI decisions as possible, to have them easily adopted in various projects
  • Any change must have unit tests to support it
  • Try to keep a consistent API across the board
1.0.1

4 years ago

1.0.0

4 years ago

0.1.0

4 years ago