npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@backpacker/primitives

v3.0.2

Published

UI primitives for React-Native

Downloads

65

Readme

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. API
  6. Custom themes

Install

yarn add @backpacker/primitives
# or
npm install --save @backpacker/primitives

Usage

/*
  Wrap your App's entry point with the `ConfigProvider` component
*/
import { ConfigProvider } from '@backpacker/primitives';

function Root() {
  return (
    <ConfigProvider>
      <App />
    </ConfigProvider>
  );
}
/*
  Then use primitives anywhere you want
*/
import {
  Column,
  Text,
  Spacer,
  Touchable,
  useTheme
} from '@backpacker/primitives';

function MyComponent(props) {
  const { theme, setTheme } = useTheme();

  const toggleTheme = () => setTheme(theme.isDark ? 'default' : 'dark');

  return (
    <Column flex={1} backgroundColor={theme.colors.background} center>
      <Touchable
        onPress={toggleTheme}
        padding={20}
        backgroundColor={theme.colors.brand}>
        <Text variant='body'>Switch theme</Text>
      </Touchable>

      <Spacer size={5} />

      <Text variant='caption1'>Sample text</Text>
    </Column>
  );
}

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
│   │   ├── text
│   │   ├── 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.

API

Layout

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

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

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

| prop | type | description | | -------- | ---- | ------------------------------------------------ | | center | bool | justifyContent: 'center', alignItems: 'center' | | ∞ | - | Any other View style props |

Text

import { Text } from '@backpacker/primitives';

<Text variant='title1' uppercase semibold>
  Sample Text
</Text>;

To configure the Text primitive, you need to change the following keys in the config file:

  • fontFamily - string, default undefined (default for each Platform).
  • textVariants - object, default config here.

You can have any number of textVariants, statically defined in the config. By default, the library implements the default typography styles from Human Interface Guidelines.

| prop | type | description | | ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | | variant | string | Any key defined in config > textVariants | | uppercase | bool | textTransform: 'uppercase' | | underline | bool | textDecorationLine: 'underline' | | strikeout | bool | textDecorationLine: 'line-through' | | center | bool | textAlign: 'center' | | semibold | bool | fontWeight: weights.semibold; Same for all the other font weights. All of them can be found in config | | testID | string | Test ID | | ∞ | - | Any other Text style props |

Spacer

import { Spacer } from '@backpacker/primitives';

<Spacer size={2} />;

The Spacer is just a View with equal height and width by default.

The height and the width values are computed using the size prop and the spacerUnit value which is provided through the config, via a theme. Formula is size * spacerUnit.

A good UI can be expressed with a controlled set of "spacer" values - this gives consistency.

| prop | type | description | | ------------ | ------ | ------------------------------------------------------------------------------- | | size | number | Default value is defaultSpacerSize, provided in the config | | fullWidth | bool | If true, the width of the spacer will be 100%. Default is false | | fullHeight | bool | If true, the height of the spacer will be 100%. Default is false | | ∞ | - | Any other View style props |

Touchable

import { Touchable } from '@backpacker/primitives';

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

The Touchable primitive is a wrapper around the TouchableOpacity component.

| prop | type | description | | --------------- | -------- | ---------------------------------------- | | activeOpacity | number | Default is 0.5 | | onPress | function | Default is undefined | | isDisabled | bool | Default is false | | ∞ | - | Any other TouchableOpacity style props |

Expandable

import { Expandable } from '@backpacker/primitives';

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

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

| prop | type | description | | ----------------------- | -------- | ------------------------------------------------------------------------------ | | initialState | bool | Default is false | | renderHeader | function | Default is undefined | | onShow | function | Default is noop | | onHide | function | Default is noop | | layoutAnimationPreset | object | A LayoutAnimation preset; default is LayoutAnimation.Presets.easeInEaseOut |

Modal

import { Modal } from '@backpacker/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.

| prop | type | description | | --------------- | -------- | ----------------- | | animationType | string | Default is fade | | onShow | function | Default is noop | | onHide | function | Default is noop |

useTheme

import { useTheme } from '@backpacker/primitives';

function MyComponent() {
  const { theme, setTheme } = useTheme;

  return (
    <Column backgroundColor={theme.colors.background} flex={1} center>
      <Text>Sample Text</Text>
    </Column>
  );
}

Custom themes

To create a custom theme, we take the config object and extend it using a new key (or overwrite an existing key).

By default, the library exposes two themes default and dark.

The following keys can be overwritten to create a new theme:

  • fontFamily - string, default is undefined.

    const fontFamily = 'Roboto';
  • fontWeights - object, configures all fontWeight variants.

    const fontWeights = {
      extralight: {},
      thin: {},
      light: {},
      normal: {},
      medium: {},
      semibold: {
        fontWeight: '600'
      },
      bold: {},
      heavy: {},
      extraheavy: {}
    };
  • textVariants - object, configures any Text variant.

    const textVariants = {
      title: {
        fontSize: 28,
        ...fontWeights.semibold
      },
      subtitle: {
        fontSize: 18
      }
    };
  • spacerUnit - number, configures default Spacer unit

    const spacerUnit = 5;
  • defaultSpacerSize - number, configures default Spacer size.

    const defaultSpacerSize = 2;

    This means <Spacer /> now has a size equal to 2*5.

  • colors - object, configures any colors that can be later consumed via useTheme hook.

    const colors = {
      colors: {
        background: '#000',
        text: '#FFF'
      }
    };

Now all I have to do is wrap everything together in a theme:

import { defaultTheme } from '@backpacker/primitives';

const myDarkTheme = {
  ...defaultTheme,
  fontFamily,
  fontWeights,
  textVariants,
  spacerUnit,
  defaultSpacerSize,
  colors,
  isDark: true
};

And pass it to the ConfigProvider, in my App's entry point:

import { ConfigProvider } from '@backpacker/primitives';

const customConfig = {
  myDarkTheme
};

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

To set the newly created myDarkTheme as default, use the defaultTheme prop on the ConfigProvider:

import { ConfigProvider } from '@backpacker/primitives';

function Root() {
  return (
    <ConfigProvider defaultTheme='myDarkTheme'>
      <App />
    </ConfigProvider>
  );
}

Or, to switch to this theme, call setTheme() inside any child component:

import { useTheme } from '@backpacker/primitives';

function MyComponent() {
  const { setTheme } = useTheme();

  const enableDarkMode = () => setTheme('myDarkTheme');

  return (
    ...
  )
}