• Stars
    star
    2,915
  • Rank 14,962 (Top 0.4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 8 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Extended StyleSheets for React Native

React Native Extended StyleSheet

Build Status Coverage Status npm version license

Drop-in replacement of React Native StyleSheet with media-queries, variables, dynamic themes, relative units, percents, math operations, scaling and other styling stuff.

Demo

Use this Expo snack to play with Extended StyleSheets right in the browser or in Expo app.

Installation

npm i react-native-extended-stylesheet --save

Usage

  1. Define styles using EStyleSheet.create() instead of StyleSheet.create():
/* component.js */
import EStyleSheet from 'react-native-extended-stylesheet';

// define extended styles 
const styles = EStyleSheet.create({
  column: {
    width: '80%'                                    // 80% of screen width
  },
  text: {
    color: '$textColor',                            // global variable $textColor
    fontSize: '1.5rem'                              // relative REM unit
  },
  '@media (min-width: 350) and (max-width: 500)': { // media queries
    text: {
      fontSize: '2rem',
    }
  }
});

// use styles as usual
class MyComponent extends React.Component {
  render() {
    return (
      <View style={styles.column}>
        <Text style={styles.text}>Hello</Text>
      </View>
    );
  }
}  
  1. In app entry point call EStyleSheet.build() to actually calculate styles:
/* app.js */
import EStyleSheet from 'react-native-extended-stylesheet';

EStyleSheet.build({ // always call EStyleSheet.build() even if you don't use global variables!
  $textColor: '#0275d8'
});

[top]

Features

Global variables

Global variables are passed to EStyleSheet.build() and available in all stylesheets.

// app entry: set global variables and calc styles
EStyleSheet.build({
  $textColor: '#0275d8'
});

// component: use global variables
const styles = EStyleSheet.create({
  text: {
    color: '$textColor'
  }
});

// global variable as inline style or as props to components
<View style = {{
  backgroundColor: EStyleSheet.value('$textColor')
}}>
...
</View>

[top]

Local variables

Local variables can be defined directly in sylesheet and have priority over global variables. To define local variable just start it with $:

const styles = EStyleSheet.create({
  $textColor: '#0275d8',
  text: {
    color: '$textColor'
  },
  icon: {
    color: '$textColor'
  },
});

Local variables are also available in result style: styles.$textColor.
[top]

Theming

Changing app theme contains two steps:

  1. re-build app styles
  2. re-render components tree with new styles

To re-build app styles you can call EStyleSheet.build() with new set of global variables:

EStyleSheet.build({
  $theme: 'light',  // required variable for caching!
  $bgColor: 'white',
});

Please note that special variable $theme is required for proper caching of calculated styles.

Re-rendering whole component tree is currently a bit tricky in React.
One option is to wrap app into component and re-mount it on theme change:

  toggleTheme() {
    const theme = EStyleSheet.value('$theme') === 'light' ? darkTheme : lightTheme;
    EStyleSheet.build(theme);
    this.setState({render: false}, () => this.setState({render: true}));
  }
  render() {
    return this.state.render ? <App/> : null;
  }

The caveat is that all components loss their state. In the future it may be possible with forceDeepUpdate() method (see facebook/react#7759).
The approach is open for discusison, feel free to share your ideas in #22, #47.

You can check out full theming code in examples/theming or in Expo snack.
[top]

Media queries

Media queries allows to have different styles for different screens, platform, direction and orientation. They are supported as properties with @media prefix (thanks for idea to @grabbou, #5).

Media queries can operate with the following values:

  • media type: ios|android
  • width, min-width, max-width
  • height, min-height, max-height
  • orientation (landscape|portrait)
  • aspect-ratio
  • direction (ltr|rtl)

You can use media queries on:

  • global level
  • sheet level
  • style level

Examples:

// global level
EStyleSheet.build({
  '@media ios': {
    $fontSize: 12,
  },
  '@media android': {
    $fontSize: 16,
  },
});

// sheet level
const styles = EStyleSheet.create({
  column: {
    width: '80%',
  },
  '@media (min-width: 350) and (max-width: 500)': {
    column: {
      width: '90%',
    }
  }
});

// style level
const styles = EStyleSheet.create({
  header: {
    '@media ios': {
      color: 'green',
    },
    '@media android': {
      color: 'blue',
    },
  }
});

You can check out full example code in examples/media-queries or in Expo snack.
[top]

Math operations

Any value can contain one of following math operations: *, /, +, -. Operands can be numbers, variables and percents.
For example, to render circle you may create style:

const styles = EStyleSheet.create({
  $size: 20,
  circle: {
    width: '$size',
    height: '$size',
    borderRadius: '0.5 * $size'
  }
});

[top]

REM units

Similar to CSS3 rem unit it allows to define any integer value as relative to the root element. In our case root value is special rem global variable that can be set in EStyleSheet.build(). It makes easy to scale app depending on screen size and other conditions. Default rem is 16.

// component
const styles = EStyleSheet.create({
  text: {
    fontSize: '1.5rem',
    marginHorizontal: '2rem'
  }
});
// app entry
let {height, width} = Dimensions.get('window');
EStyleSheet.build({
  $rem: width > 340 ? 18 : 16
});

You can check out full example code in examples/rem or in Expo snack.
[top]

Percents

Percent values are supported natively since React Native 0.43. EStyleSheet passes them through to original StyleSheet except cases, when you use calculations with percents, e.g. "100% - 20". Percents are calculated relative to screen width/height on application launch.

const styles = EStyleSheet.create({
  column: {
    width: '100% - 20'
  }
});

Percents in nested components
If you need sub-component with percent operations relative to parent component - you can achieve that with variables.
For example, to render 2 sub-columns with 30%/70% width of parent column:

render() {
  return (
    <View style={styles.column}>
      <View style={styles.subColumnLeft}></View>
      <View style={styles.subColumnRight}></View>
    </View>
  );
}

...

const styles = EStyleSheet.create({
  $columnWidth: '80%',
  column: {
    width: '$columnWidth',
    flexDirection: 'row'
  },
  subColumnLeft: {
    width: '0.3 * $columnWidth'
  },
  subColumnRight: {
    width: '0.7 * $columnWidth'
  }
});

[top]

Scaling

You can apply scale to components by setting special $scale variable.

const styles = EStyleSheet.create({
  $scale: 1.5,
  button: {
    width: 100,
    height: 20,
    marginLeft: 10
  }
});

This helps to create reusable components that could be scaled depending on prop:

class Button extends React.Component {
  static propTypes = {
    scale: React.PropTypes.number
  };
  render() {
    let style = getStyle(this.props.scale)
    return (
      <View style={style.button}>
      </View>
    );
  }
}

let getStyle = function (scale = 1) {
  return EStyleSheet.create({
    $scale: scale,
    button: {
      width: 100,
      height: 20,
      marginLeft: 10
    }
  });
}

To cache calculated styles please have a look on caching section.
[top]

Underscored styles

Original react-native stylesheets are calculated to integer numbers and original values are unavailable. But sometimes they are needed. Let's take an example:
You want to render text and icon with the same size and color. You can take this awesome icon library and see that <Icon> component has size and color props. It would be convenient to define style for text and keep icon's size/color in sync.

const styles = EStyleSheet.create({
  text: {
    fontSize: '1rem',
    color: 'gray'
  }
});

In runtime styles created with original react's StyleSheet will look like:

styles = {
  text: 0
}

But extended stylesheet saves calculated values under _text property:

styles = {
  text: 0,
  _text: {
    fontSize: 16,
    color: 'gray'
  }
}

To render icon we just take styles from _text:

return (
  <View>
    <Icon name="rocket" size={styles._text.fontSize} color={styles._text.color} />
    <Text style={styles.text}>Hello</Text>
  </View>
);

[top]

Pseudo classes (:nth-child)

Extended stylesheet supports 4 pseudo classes: :first-child, :nth-child-even, :nth-child-odd, :last-child. As well as in traditional CSS it allows to apply special styling for first/last items or render stripped rows.
To get style for appropriate index you should use EStyleSheet.child() method. It's signature: EStyleSheet.child(stylesObj, styleName, index, count).

const styles = EStyleSheet.create({
  row: {
    fontSize: '1.5rem',
    borderTopWidth: 1
  },
  'row:nth-child-even': {
    backgroundColor: 'gray' // make stripped
  },
  'row:last-child': {
    borderBottomWidth: 1 // render bottom edge for last row
  }
});
...
render() {
  return (
    <View>
      {items.map((item, index) => {
        return (
          <View key={index} style={EStyleSheet.child(styles, 'row', index, items.length)}></View>
        );
      })}
    </View>
  );
}

[top]

Value as a function

For the deepest customization you can specify any value as a function that will be executed on EStyleSheet build. For example, you may darken or lighten color of variable via npm color package:

import Color from 'color';
import EStyleSheet from 'react-native-extended-stylesheet';

const styles = EStyleSheet.create({
  button: {
    backgroundColor: () => Color('green').darken(0.1).hexString() // <-- value as a function
  }
});

render() {
  return (
    <TouchableHighlight style={styles.button}>
      ...
    </TouchableHighlight>
  );
}

The common pattern is to use EStyleSheet.value() inside the function to get access to global variables:

EStyleSheet.build({
  $prmaryColor: 'green'
});

const styles = EStyleSheet.create({
  button: {
    backgroundColor: () => Color(EStyleSheet.value('$prmaryColor')).darken(0.1).hexString()
  }
});

[top]

Caching

If you use dynamic styles depending on runtime prop or you are making reusable component with dynamic styling you may need stylesheet creation in every render() call. Let's take example from scaling section:

class Button extends React.Component {
  static propTypes = {
    scale: React.PropTypes.number
  };
  render() {
    let style = getStyle(this.props.scale)
    return (
      <View style={style.button}>
      </View>
    );
  }
}

let getStyle = function (scale = 1) {
  return EStyleSheet.create({
    $scale: scale,
    button: {
      width: 100,
      height: 20,
      marginLeft: 10
    }
  });
}

To avoid creating styles on every render you can use lodash.memoize: store result for particular parameters and returns it from cache when called with the same parameters. Updated example:

import memoize from 'lodash.memoize';

let getStyle = memoize(function (scale = 1) {
  return EStyleSheet.create({
    $scale: scale,
    button: {
      width: 100,
      height: 20,
      marginLeft: 10
    }
  });
});

Now if you call getStyle(1.5) 3 times actually style will be created on the first call and two other calls will get it from cache.
[top]

Outline for debug

It is possible to outline all components that are using EStyleSheet. For that set global $outline variable:

EStyleSheet.build({$outline: 1});

Note that components without styles will not be outlined, because RN does not support default component styling yet.

To outline particular component set local $outline variable:

const styles = EStyleSheet.create({
  $outline: 1,
  column: {
    width: '80%',
    flexDirection: 'row'
  },
  ...
});

[top]

Hot module reload

Hot module reload (HMR) allows you to change code and see live updates without loosing app state. It is very handy for tuning styles. EStyleSheet supports HMR with the following options:

  1. When you change style of component - the component is updated by HMR automatically without any effort from your side.
  2. When you change global variable or theme - you should use HMR API to force style re-calculation:
    // app.js
    EStyleSheet.build({
      $fontColor: 'black'
    });
    
    ...
    
    module.hot.accept(() => {
      EStyleSheet.clearCache();
      EStyleSheet.build(); // force style re-calculation
    });

See full example of HMR here.
[top]

EStyleSheet API

.create()

/**
 * Creates extended stylesheet object
 *
 * @param {Object} source style
 * @returns {Object} extended stylesheet object
 */
 create (source) {...}

[top]

.build()

/**
 * Calculates all stylesheets
 *
 * @param {Object} [globalVars] global variables for all stylesheets
 */
 build (globalVars) {...}

[top]

.value()

/**
 * Calculates particular expression.
 *
 * @param {*} value
 * @param {String} [prop] property for which value is calculated. For example, to calculate percent values 
 * the function should know is it 'width' or 'height' to use proper reference value.
 * @returns {*} calculated result
 */
 value (value, prop) {...}

Please note that in most cases EStyleSheet.value() should be used inside function, not directly:

const styles = EStyleSheet.create({
    button1: {
        width: () => EStyleSheet.value('$contentWidth') + 10 // <-- Correct!
    },
    button2: {
        width: EStyleSheet.value('$contentWidth') + 10 // <-- Incorrect. Because EStyleSheet.build() may occur later and $contentWidth will be undefined at this moment.
    }
});

[top]

.child()

/**
 * Returns styles with pseudo classes :first-child, :nth-child-even, :last-child according to index and count
 *
 * @param {Object} stylesheet
 * @param {String} styleName
 * @param {Number} index index of item for style
 * @param {Number} count total count of items
 * @returns {Object|Array} styles
 */
 child (styles, styleName, index, count) {...}

[top]

.subscribe()

/**
 * Subscribe to event. Currently only 'build' event is supported.
 *
 * @param {String} event
 * @param {Function} listener
 */
 subscribe (event, listener) {...}

This method is useful when you want to pre-render some component on init. As extended style is calculated after call of EStyleSheet.build(), it is not available instantly after creation so you should wrap pre-render info listener to build event:

const styles = EStyleSheet.create({
  button: {
    width: '80%',
  }
});

// this will NOT work as styles.button is not calculated yet
let Button = <View style={styles.button}></View>;

// but this will work
let Button;
EStyleSheet.subscribe('build', () => {
  Button = <View style={styles.button}></View>;
});

[top]

.unsubscribe()

/**
 * Unsubscribe from event. Currently only 'build' event is supported.
 *
 * @param {String} event
 * @param {Function} listener
 */
 unsubscribe (event, listener) {...}

Unsubscribe from event. [top]

Caveats

  1. Dynamic theme change is possible only with loosing components local state
    When theme styles are re-calculated - all components should be re-rendered. Currently it can be done via re-mounting components tree, please see #47.

    Note: it is not issue if you are using state container like Redux and can easily re-render app in the same state

  2. Dynamic orientation change is not supported
    Please see #9 for more details.

  3. Old RN versions (< 0.43) can crash the app with percent values
    RN >= 0.43 supports percent values natively (#32) and EStyleSheet since 0.5.0 just proxy percent values to RN as is (#77) to keep things simple. Older RN versions (< 0.43) can't process percents and EStyleSheet process such values. So if you are using RN < 0.43, you should stick to [email protected].

FAQ

  1. I'm getting error: "Unresolved variable: ..."
    • Ensure that you call EStyleSheet.build() in entry point of your app.
    • Ensure that $variable name without typos.
    • Ensure that you are not using EStyleSheet.value() before the styles are built. See #50 for details.

Changelog

Please see CHANGELOG.md

Feedback

If you have any ideas or something goes wrong feel free to open new issue.

License

MIT @ Vitaliy Potapov

[top]

* * *
If you love ❤️ JavaScript and would like to track new trending repositories,
have a look on vitalets/github-trending-repos.

More Repositories

1

x-editable

In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
JavaScript
6,477
star
2

github-trending-repos

Track GitHub trending repositories in your favorite programming language by native GitHub notifications!
HTML
2,619
star
3

angular-xeditable

Edit in place for AngularJS
HTML
1,915
star
4

checklist-model

AngularJS directive for list of checkboxes
HTML
1,053
star
5

awesome-smart-tv

⚡A curated list of awesome resources for building Smart TV apps
1,019
star
6

websocket-as-promised

A Promise-based API for WebSockets
JavaScript
571
star
7

bootstrap-editable

This plugin no longer supported! Please use x-editable instead!
JavaScript
558
star
8

await-timeout

A Promise-based API for setTimeout / clearTimeout
JavaScript
428
star
9

combodate

Dropdown date and time picker
JavaScript
208
star
10

playwright-bdd

BDD testing with Playwright runner
TypeScript
195
star
11

autotester

Chrome extension that allows to develop and run automation tests right in browser
JavaScript
169
star
12

clockface

Clockface timepicker for Twitter Bootstrap
CSS
168
star
13

awesome-browser-extensions-and-apps

⚡A curated list of awesome resources for building browser extensions and apps
125
star
14

babel-plugin-runtyper

⚡️ Runtime type-checker for JavaScript
JavaScript
117
star
15

x-editable-yii

Yii extension for creating editable elements
JavaScript
112
star
16

docker-tizen-webos-sdk

Docker image with Samsung Tizen CLI and LG webOS CLI
Dockerfile
82
star
17

bro-fs

Promise-based HTML5 Filesystem API similar to Node.js fs module
JavaScript
43
star
18

tinkoff-invest-api

Node.js SDK для работы с Tinkoff Invest API
HTML
42
star
19

yii-bootstrap-editable

Yii extension for Bootstrap-editable plugin
JavaScript
31
star
20

alice-renderer

Node.js библиотека для формирования ответов в навыках Яндекс Алисы.
JavaScript
29
star
21

alice-workshop

Воркшоп по разработке навыка для Алисы на Node.js
JavaScript
28
star
22

js-testrunners-bench

JavaScript test-runners benchmark
JavaScript
27
star
23

groupgridview

Yii extension to group data in your grid
PHP
24
star
24

lazy-model

AngularJS directive that works like `ng-model` but accept changes only when form is submitted (otherwise changes are cancelled)
JavaScript
21
star
25

chnl

JavaScript event channels compatible with Chrome extensions API
JavaScript
19
star
26

tinkoff-robot

Пример торгового робота для Tinkoff Invest API (Node.js)
TypeScript
19
star
27

playwright-bdd-example

Example project that uses playwright-bdd to run BDD tests
TypeScript
19
star
28

docker-stack-wait-deploy

A script waiting for docker stack deploy command to complete.
Shell
18
star
29

alice-tester

Библиотека для автоматического тестирования навыков Алисы на Node.js.
JavaScript
14
star
30

promise-controller

Advanced control of JavaScript promises
JavaScript
13
star
31

controlled-promise

Advanced control of JavaScript promises
JavaScript
13
star
32

mocha-es6-modules

Running Mocha tests in the browser with ES6 Modules support
JavaScript
12
star
33

yandex-cloud-deploy-fn

CLI для деплоя функций в Yandex Cloud на Node.js
TypeScript
11
star
34

tinkoff-local-broker

Локальный сервер для тестирования торговых роботов на Tinkoff Invest API
TypeScript
9
star
35

alice-cloud-proxy

Готовая облачная функция для развертывания своего прокси-навыка для Алисы
JavaScript
9
star
36

ydb-sdk-lite

Lightweight implementation of Yandex Database SDK for Node.js
JavaScript
5
star
37

throw-utils

Helpers for error throwing
TypeScript
5
star
38

wait-for-cmd

A pure shell script waiting for provided command to exit with zero code
Shell
5
star
39

selgridview

Yii extension to keep selected rows in CGridView when sorting and pagination
JavaScript
5
star
40

alice-protocol

JSON схемы запросов и ответов в навыках Алисы
JavaScript
5
star
41

alice-skill-starter

Быстрый старт навыка для Алисы на Node.js
JavaScript
5
star
42

flat-options

One-level options with default values and validation
JavaScript
4
star
43

yandex-cloud-fn

Хелперы для функций в Yandex Cloud (Node.js)
TypeScript
4
star
44

alice-testing-example

Пример функционального тестирования навыка для Яндекс Алисы на Node.js.
JavaScript
4
star
45

page-object

A Page Object pattern implementation library for JavaScript
JavaScript
4
star
46

fetchers

Semantic RESTful Fetch API wrappers
JavaScript
3
star
47

early-errors

A tiny script to catch webpage errors earlier.
JavaScript
3
star
48

json-micro-schema

Minimal JSON schema validation format
JavaScript
3
star
49

loggee

Zero-dependency JavaScript logger with namespaces
JavaScript
3
star
50

yc-serverless-live-debug-original

Live debug of Yandex cloud functions with local code on Node.js
TypeScript
3
star
51

npxd

Run NPX commands inside Docker container
Shell
2
star
52

selenium-fileserver

Public website serving Selenium self-test static pages
JavaScript
2
star
53

alice-types

Тайпинги для протокола Алисы.
TypeScript
2
star
54

marusya-types

Тайпинги для протокола Маруси.
TypeScript
2
star
55

micro-schema

JavaScript implementation of json-micro-schema validation format
JavaScript
2
star
56

sheeva

Concurrent Automation Test Runner
JavaScript
2
star
57

uni-skill

Универсальный интерфейс для разработки навыков голосовых ассистентов.
TypeScript
2
star
58

alice-asset-manager

Node.js API для загрузки изображений и звуков в навык Алисы.
JavaScript
2
star
59

pendings

[DEPRECATED] Better control of promises
JavaScript
2
star
60

skill-afisha-moscow

TypeScript
1
star
61

qrlink

HTML
1
star
62

yandex-cloud-fn-internals

Roff
1
star
63

retry

Retry async function with exponential delay, timeouts and abort signals
TypeScript
1
star
64

alice-dev

Инструменты разработчика для навыков Алисы
JavaScript
1
star
65

promised-map

A map of promises that can be resolved or rejected by key
TypeScript
1
star
66

logger

Pure typescript logger with levels and prefix support
TypeScript
1
star
67

skill-dev-proxy

Навык для Алисы, позволяющий отлаживать другие навыки прямо на устройстве
TypeScript
1
star
68

yandex-cloud-lite

Минимальный Node.js клиент для доступа к API сервисов Yandex Cloud по GRPC
JavaScript
1
star