├── .editorconfig ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE.md └── stale.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── examples ├── README.md ├── hmr │ ├── app.js │ └── component.js ├── jest-testing │ ├── App.js │ ├── __mocks__ │ │ └── react-native-extended-stylesheet.js │ ├── __tests__ │ │ └── app-test.js │ └── index.js ├── media-queries │ ├── app.js │ └── component.js ├── readme │ ├── app.js │ └── component.js ├── rem │ ├── app.js │ └── component.js ├── simple │ ├── app.js │ └── component.js └── theming │ ├── README.md │ ├── app.js │ ├── component.js │ ├── dark.js │ ├── demo.gif │ └── light.js ├── package-lock.json ├── package.json ├── src ├── .babelrc ├── __mocks__ │ └── react-native.js ├── __tests__ │ ├── api.test.js │ ├── child.test.js │ ├── sheet.test.js │ ├── style.test.js │ ├── utils.test.js │ └── value.test.js ├── api.js ├── child.js ├── index.js ├── replacers │ ├── __tests__ │ │ ├── media-queries.test.js │ │ ├── operation.test.js │ │ ├── percent.test.js │ │ ├── rem.test.js │ │ ├── scale.test.js │ │ └── vars.test.js │ ├── media-queries.js │ ├── operation.js │ ├── percent.js │ ├── rem.js │ ├── scale.js │ └── vars.js ├── sheet.js ├── style.js ├── utils.js └── value.js ├── tsconfig.json ├── types ├── index.d.ts └── test.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | insert_final_newline = true 7 | 8 | [*.{js,json}] 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parser": "babel-eslint", 4 | 5 | "env": { 6 | "es6": true, 7 | "node": true, 8 | "jest": true 9 | }, 10 | 11 | "plugins": [ 12 | "react", 13 | "react-native", 14 | "import" 15 | ], 16 | 17 | "rules": { 18 | "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], 19 | "comma-dangle": ["error", "only-multiline"], 20 | "no-multi-spaces": 2, 21 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 22 | "react/display-name": 0, 23 | "react/forbid-prop-types": 1, 24 | "react/jsx-boolean-value": 1, 25 | "react/jsx-closing-bracket-location": 1, 26 | "react/jsx-curly-spacing": 1, 27 | "react/jsx-handler-names": 1, 28 | "react/jsx-indent-props": [1, 2], 29 | "react/jsx-indent": [0, 2], 30 | "react/jsx-key": 1, 31 | "react/jsx-max-props-per-line": 0, 32 | "react/jsx-no-bind": 0, 33 | "react/jsx-no-duplicate-props": 1, 34 | "react/jsx-no-literals": 0, 35 | "react/jsx-no-undef": 1, 36 | "react/jsx-pascal-case": 1, 37 | "jsx-quotes": 1, 38 | "react/sort-prop-types": 1, 39 | "react/jsx-sort-props": 0, 40 | "react/jsx-uses-react": 1, 41 | "react/jsx-uses-vars": 1, 42 | "react/no-danger": 1, 43 | "react/no-deprecated": 1, 44 | "react/no-did-mount-set-state": 1, 45 | "react/no-did-update-set-state": 1, 46 | "react/no-direct-mutation-state": 1, 47 | "react/no-is-mounted": 1, 48 | "react/no-multi-comp": 1, 49 | "react/no-set-state": 0, 50 | "react/no-string-refs": 1, 51 | "react/no-unknown-property": 1, 52 | "react/prefer-es6-class": 1, 53 | "react/prop-types": [0, {"ignore": ["children"]}], 54 | "react/react-in-jsx-scope": 1, 55 | "import/extensions": 1, 56 | "react/self-closing-comp": 1, 57 | "react/sort-comp": 0, 58 | "react/jsx-wrap-multilines": 1, 59 | "react-native/no-unused-styles": 2, 60 | "react-native/split-platform-components": 2 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #### Steps to Reproduce 5 | 10 | 11 | #### Try in Expo 12 | 17 | 18 | #### Expected Behavior 19 | 20 | 21 | #### Actual Behavior 22 | 23 | 24 | #### Show the code 25 | 26 | 27 | #### Environment 28 | 29 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | # A .github/stale.yml file is required to enable the plugin. 3 | # The file can be empty, or it can override any of these default settings 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | 4 | # node.js 5 | node_modules/ 6 | npm-debug.log 7 | .idea 8 | 9 | # coverage 10 | /coverage 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # .babelrc in root breaks react-native packager see issue #1 2 | .babelrc 3 | .editorconfig 4 | .eslintrc 5 | .jscsrc 6 | .idea 7 | .travis.yml 8 | .github 9 | /coverage 10 | /hooks 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '8' 5 | - '10' 6 | 7 | script: npm run ci 8 | 9 | after_success: 10 | # send coverage for badge 11 | - npm run coveralls 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.12.0 (Jul 5, 2019) 2 | 3 | * Fix `.subscribe()` method and add `.unsubscribe()` ([#109]) 4 | 5 | ## 0.11.1 (Dec 17, 2018) 6 | 7 | * Fix array props ([#103]) 8 | 9 | ## 0.11.0 (Dec 11, 2018) 10 | 11 | * Inherit styles from `Object` instead of `null` ([#101]) 12 | * Support nested style props ([#99]) 13 | 14 | ## 0.10.0 (Sep 24, 2018) 15 | 16 | * Add Typescript declarations ([#56]) 17 | 18 | ## 0.9.0 (Aug 10, 2018) 19 | 20 | * Add (text) direction support ([@achipa] in [#90]) 21 | 22 | ## 0.8.1 (Jan 27, 2018) 23 | 24 | * Calc percents for variable inside operation ([#77]) 25 | 26 | ## 0.8.0 (Nov 1, 2017) 27 | 28 | * Style as a function ([#62]) 29 | * Support [hot module reload] ([#16]) 30 | 31 | ## 0.7.0 (Oct 9, 2017) 32 | 33 | * Allow create-react-app compile without plugins ([@grantbi] in [#54]) 34 | * Support dynamic theme change ([#47], [#53]) 35 | * Support media queries for global variables ([#25]) 36 | 37 | ## 0.6.0 (Jun 3, 2017) 38 | 39 | * Remove `EStyleSheet.memoize()` in favor of [lodash.memoize](https://www.npmjs.com/package/lodash.memoize) ([#45]) 40 | 41 | ## 0.5.0 (Jun 5, 2017) 42 | 43 | * Add [babel-plugin-runtyper](https://github.com/vitalets/babel-plugin-runtyper) 44 | * Use native percents when possible ([#32]) 45 | * Forward original StyleSheet properties ([#33]) 46 | 47 | ## 0.4.0 (Apr 13, 2017) 48 | 49 | * Add division operator ([@joemckie] in [#38]) 50 | * Improve Readme 51 | 52 | ## 0.3.2 (Mar 5, 2017) 53 | 54 | * Check for non-object when creating Style ([#14]) 55 | * Update examples 56 | 57 | ## 0.3.0 (Jul 4, 2016) 58 | 59 | * Remove unneeded rounding ([@Kerumen] in [#18]) 60 | 61 | ## 0.2.0 (Apr 25, 2016) 62 | 63 | * Support media-queries ([#8]) 64 | 65 | ## 0.1.3 (Feb 4, 2016) 66 | 67 | * Public release 68 | 69 | [@joemckie]: https://github.com/joemckie 70 | [@Kerumen]: https://github.com/Kerumen 71 | [@grantbi]: https://github.com/grantbi 72 | [@achipa]: https://github.com/achipa 73 | 74 | [hot module reload]: https://facebook.github.io/react-native/blog/2016/03/24/introducing-hot-reloading.html 75 | 76 | [#8]: https://github.com/vitalets/react-native-extended-stylesheet/pull/8 77 | [#14]: https://github.com/vitalets/react-native-extended-stylesheet/pull/14 78 | [#16]: https://github.com/vitalets/react-native-extended-stylesheet/pull/16 79 | [#18]: https://github.com/vitalets/react-native-extended-stylesheet/pull/18 80 | [#25]: https://github.com/vitalets/react-native-extended-stylesheet/pull/25 81 | [#38]: https://github.com/vitalets/react-native-extended-stylesheet/pull/38 82 | [#32]: https://github.com/vitalets/react-native-extended-stylesheet/pull/32 83 | [#33]: https://github.com/vitalets/react-native-extended-stylesheet/pull/33 84 | [#45]: https://github.com/vitalets/react-native-extended-stylesheet/pull/45 85 | [#47]: https://github.com/vitalets/react-native-extended-stylesheet/pull/47 86 | [#53]: https://github.com/vitalets/react-native-extended-stylesheet/pull/53 87 | [#54]: https://github.com/vitalets/react-native-extended-stylesheet/pull/54 88 | [#62]: https://github.com/vitalets/react-native-extended-stylesheet/pull/62 89 | [#77]: https://github.com/vitalets/react-native-extended-stylesheet/pull/77 90 | [#90]: https://github.com/vitalets/react-native-extended-stylesheet/pull/90 91 | [#56]: https://github.com/vitalets/react-native-extended-stylesheet/pull/56 92 | [#99]: https://github.com/vitalets/react-native-extended-stylesheet/pull/99 93 | [#103]: https://github.com/vitalets/react-native-extended-stylesheet/pull/103 94 | [#109]: https://github.com/vitalets/react-native-extended-stylesheet/pull/109 95 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vitaliy Potapov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Extended StyleSheet 2 | 3 | [![Build Status](https://travis-ci.org/vitalets/react-native-extended-stylesheet.svg?branch=master)](https://travis-ci.org/vitalets/react-native-extended-stylesheet) 4 | [![Coverage Status](https://coveralls.io/repos/github/vitalets/react-native-extended-stylesheet/badge.svg?branch=master)](https://coveralls.io/github/vitalets/react-native-extended-stylesheet?branch=master) 5 | [![npm version](https://img.shields.io/npm/v/react-native-extended-stylesheet.svg)](https://www.npmjs.com/package/react-native-extended-stylesheet) 6 | [![license](https://img.shields.io/npm/l/react-native-extended-stylesheet.svg)](https://www.npmjs.com/package/react-native-extended-stylesheet) 7 | 8 | > ### :warning: Deprecation notice 9 | > 10 | > This library is **deprecated** and will no longer receive any updates or support. 11 | > Please consider migrating to alternative solutions. See [#154](https://github.com/vitalets/react-native-extended-stylesheet/issues/154) for details. 12 | 13 | Drop-in replacement of [React Native StyleSheet](https://facebook.github.io/react-native/docs/stylesheet.html) with media-queries, variables, dynamic themes, 14 | relative units, percents, math operations, scaling and other styling stuff. 15 | 16 | 17 | 18 | - [Demo](#demo) 19 | - [Installation](#installation) 20 | - [Usage](#usage) 21 | - [Features](#features) 22 | - [global variables](#global-variables) 23 | - [local variables](#local-variables) 24 | - [theming](#theming) 25 | - [media queries](#media-queries) 26 | - [math operations](#math-operations) 27 | - [rem units](#rem-units) 28 | - [percents](#percents) 29 | - [scaling](#scaling) 30 | - [underscored styles](#underscored-styles) 31 | - [pseudo classes (:nth-child)](#pseudo-classes-nth-child) 32 | - [value as a function](#value-as-a-function) 33 | - [caching](#caching) 34 | - [outline for debug](#outline-for-debug) 35 | - [hot module reload](#hot-module-reload) 36 | - [API](#api) 37 | - [.create()](#create) 38 | - [.build()](#build) 39 | - [.value()](#value) 40 | - [.child()](#child) 41 | - [.subscribe()](#subscribe) 42 | - [.unsubscribe()](#unsubscribe) 43 | - [Caveats](#caveats) 44 | - [FAQ](#faq) 45 | - [Changelog](#changelog) 46 | - [Feedback](#feedback) 47 | - [License](#license) 48 | 49 | ## Demo 50 | Use this [Expo snack](https://snack.expo.io/@vitalets/extended-stylesheet-simple) to play with Extended StyleSheets 51 | right in the browser or in [Expo app](https://expo.io/tools#client). 52 | 53 | ## Installation 54 | ``` 55 | npm i react-native-extended-stylesheet --save 56 | ``` 57 | 58 | ## Usage 59 | 1. Define styles using `EStyleSheet.create()` instead of `StyleSheet.create()`: 60 | 61 | ```js 62 | /* component.js */ 63 | import EStyleSheet from 'react-native-extended-stylesheet'; 64 | 65 | // define extended styles 66 | const styles = EStyleSheet.create({ 67 | column: { 68 | width: '80%' // 80% of screen width 69 | }, 70 | text: { 71 | color: '$textColor', // global variable $textColor 72 | fontSize: '1.5rem' // relative REM unit 73 | }, 74 | '@media (min-width: 350) and (max-width: 500)': { // media queries 75 | text: { 76 | fontSize: '2rem', 77 | } 78 | } 79 | }); 80 | 81 | // use styles as usual 82 | class MyComponent extends React.Component { 83 | render() { 84 | return ( 85 | 86 | Hello 87 | 88 | ); 89 | } 90 | } 91 | ``` 92 | 93 | 2. In app entry point call `EStyleSheet.build()` to actually calculate styles: 94 | 95 | ```js 96 | /* app.js */ 97 | import EStyleSheet from 'react-native-extended-stylesheet'; 98 | 99 | EStyleSheet.build({ // always call EStyleSheet.build() even if you don't use global variables! 100 | $textColor: '#0275d8' 101 | }); 102 | ``` 103 | 104 | \[[top](#react-native-extended-stylesheet)\] 105 | 106 | ## Features 107 | ### Global variables 108 | Global variables are passed to `EStyleSheet.build()` and available in all stylesheets. 109 | ```js 110 | // app entry: set global variables and calc styles 111 | EStyleSheet.build({ 112 | $textColor: '#0275d8' 113 | }); 114 | 115 | // component: use global variables 116 | const styles = EStyleSheet.create({ 117 | text: { 118 | color: '$textColor' 119 | } 120 | }); 121 | 122 | // global variable as inline style or as props to components 123 | 126 | ... 127 | 128 | ``` 129 | \[[top](#react-native-extended-stylesheet)\] 130 | 131 | ### Local variables 132 | Local variables can be defined directly in sylesheet and have priority over global variables. 133 | To define local variable just start it with `$`: 134 | ```js 135 | const styles = EStyleSheet.create({ 136 | $textColor: '#0275d8', 137 | text: { 138 | color: '$textColor' 139 | }, 140 | icon: { 141 | color: '$textColor' 142 | }, 143 | }); 144 | ``` 145 | Local variables are also available in result style: `styles.$textColor`. 146 | \[[top](#react-native-extended-stylesheet)\] 147 | 148 | ### Theming 149 | Changing app theme contains two steps: 150 | 1. re-build app styles 151 | 2. re-render components tree with new styles 152 | 153 | To re-build app styles you can call `EStyleSheet.build()` with new set of global variables: 154 | ```js 155 | EStyleSheet.build({ 156 | $theme: 'light', // required variable for caching! 157 | $bgColor: 'white', 158 | }); 159 | ``` 160 | > Please note that special variable **`$theme` is required** for proper caching of calculated styles. 161 | 162 | Re-rendering whole component tree is currently a bit tricky in React. 163 | One option is to wrap app into component and re-mount it on theme change: 164 | ```js 165 | toggleTheme() { 166 | const theme = EStyleSheet.value('$theme') === 'light' ? darkTheme : lightTheme; 167 | EStyleSheet.build(theme); 168 | this.setState({render: false}, () => this.setState({render: true})); 169 | } 170 | render() { 171 | return this.state.render ? : null; 172 | } 173 | ``` 174 | The caveat is that all components loss their state. 175 | In the future it may be possible with `forceDeepUpdate()` method (see [facebook/react#7759](https://github.com/facebook/react/issues/7759)). 176 | The approach is open for discusison, feel free to share your ideas in [#22](https://github.com/vitalets/react-native-extended-stylesheet/issues/22), 177 | [#47](https://github.com/vitalets/react-native-extended-stylesheet/issues/47). 178 | 179 | You can check out full theming code in [examples/theming](examples/theming) or in [Expo snack](https://snack.expo.io/@vitalets/dynamic-themes-with-extended-stylesheets). 180 | \[[top](#react-native-extended-stylesheet)\] 181 | 182 | ### Media queries 183 | Media queries allows to have different styles for different screens, platform, direction and orientation. 184 | They are supported as properties with `@media` prefix (thanks for idea to [@grabbou](https://github.com/grabbou), 185 | [#5](https://github.com/vitalets/react-native-extended-stylesheet/issues/5)). 186 | 187 | Media queries can operate with the following values: 188 | 189 | * media type: `ios|android` 190 | * `width`, `min-width`, `max-width` 191 | * `height`, `min-height`, `max-height` 192 | * `orientation` (`landscape|portrait`) 193 | * `aspect-ratio` 194 | * `direction` (`ltr|rtl`) 195 | 196 | You can use media queries on: 197 | * global level 198 | * sheet level 199 | * style level 200 | 201 | Examples: 202 | ```js 203 | // global level 204 | EStyleSheet.build({ 205 | '@media ios': { 206 | $fontSize: 12, 207 | }, 208 | '@media android': { 209 | $fontSize: 16, 210 | }, 211 | }); 212 | 213 | // sheet level 214 | const styles = EStyleSheet.create({ 215 | column: { 216 | width: '80%', 217 | }, 218 | '@media (min-width: 350) and (max-width: 500)': { 219 | column: { 220 | width: '90%', 221 | } 222 | } 223 | }); 224 | 225 | // style level 226 | const styles = EStyleSheet.create({ 227 | header: { 228 | '@media ios': { 229 | color: 'green', 230 | }, 231 | '@media android': { 232 | color: 'blue', 233 | }, 234 | } 235 | }); 236 | ``` 237 | You can check out full example code in [examples/media-queries](examples/media-queries) or in [Expo snack](https://snack.expo.io/@gbhasha/media-queries-using-extended-stylesheets). 238 | \[[top](#react-native-extended-stylesheet)\] 239 | 240 | ### Math operations 241 | Any value can contain **one** of following math operations: `*`, `/`, `+`, `-`. Operands can be numbers, variables and percents. 242 | For example, to render circle you may create style: 243 | ```js 244 | const styles = EStyleSheet.create({ 245 | $size: 20, 246 | circle: { 247 | width: '$size', 248 | height: '$size', 249 | borderRadius: '0.5 * $size' 250 | } 251 | }); 252 | ``` 253 | \[[top](#react-native-extended-stylesheet)\] 254 | 255 | ### REM units 256 | Similar to [CSS3 rem unit](http://snook.ca/archives/html_and_css/font-size-with-rem) 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`. 257 | ```js 258 | // component 259 | const styles = EStyleSheet.create({ 260 | text: { 261 | fontSize: '1.5rem', 262 | marginHorizontal: '2rem' 263 | } 264 | }); 265 | // app entry 266 | let {height, width} = Dimensions.get('window'); 267 | EStyleSheet.build({ 268 | $rem: width > 340 ? 18 : 16 269 | }); 270 | ``` 271 | You can check out full example code in [examples/rem](examples/rem) or in [Expo snack](https://snack.expo.io/@gbhasha/using-rem-units-with-extended-stylesheet). 272 | \[[top](#react-native-extended-stylesheet)\] 273 | 274 | ### Percents 275 | Percent values are supported natively since React Native 0.43. 276 | EStyleSheet passes them through to original StyleSheet except cases, when you use calculations with percents, 277 | e.g. `"100% - 20"`. Percents are calculated relative to **screen width/height** on application launch. 278 | ```js 279 | const styles = EStyleSheet.create({ 280 | column: { 281 | width: '100% - 20' 282 | } 283 | }); 284 | ``` 285 | 286 | **Percents in nested components** 287 | If you need sub-component with percent operations relative to parent component - you can achieve that with variables. 288 | For example, to render 2 sub-columns with 30%/70% width of parent column: 289 | ```js 290 | render() { 291 | return ( 292 | 293 | 294 | 295 | 296 | ); 297 | } 298 | 299 | ... 300 | 301 | const styles = EStyleSheet.create({ 302 | $columnWidth: '80%', 303 | column: { 304 | width: '$columnWidth', 305 | flexDirection: 'row' 306 | }, 307 | subColumnLeft: { 308 | width: '0.3 * $columnWidth' 309 | }, 310 | subColumnRight: { 311 | width: '0.7 * $columnWidth' 312 | } 313 | }); 314 | ``` 315 | \[[top](#react-native-extended-stylesheet)\] 316 | 317 | ### Scaling 318 | You can apply scale to components by setting special `$scale` variable. 319 | ```js 320 | const styles = EStyleSheet.create({ 321 | $scale: 1.5, 322 | button: { 323 | width: 100, 324 | height: 20, 325 | marginLeft: 10 326 | } 327 | }); 328 | ``` 329 | This helps to create reusable components that could be scaled depending on prop: 330 | ```js 331 | class Button extends React.Component { 332 | static propTypes = { 333 | scale: React.PropTypes.number 334 | }; 335 | render() { 336 | let style = getStyle(this.props.scale) 337 | return ( 338 | 339 | 340 | ); 341 | } 342 | } 343 | 344 | let getStyle = function (scale = 1) { 345 | return EStyleSheet.create({ 346 | $scale: scale, 347 | button: { 348 | width: 100, 349 | height: 20, 350 | marginLeft: 10 351 | } 352 | }); 353 | } 354 | ``` 355 | To cache calculated styles please have a look on [caching](#caching) section. 356 | \[[top](#react-native-extended-stylesheet)\] 357 | 358 | ### Underscored styles 359 | Original react-native stylesheets are calculated to integer numbers and original values are unavailable. 360 | But sometimes they are needed. Let's take an example: 361 | You want to render text and icon with the same size and color. 362 | You can take this [awesome icon library](https://github.com/oblador/react-native-vector-icons) 363 | and see that `` component has `size` and `color` props. 364 | It would be convenient to define style for text and keep icon's size/color in sync. 365 | ```js 366 | const styles = EStyleSheet.create({ 367 | text: { 368 | fontSize: '1rem', 369 | color: 'gray' 370 | } 371 | }); 372 | ``` 373 | In runtime `styles` created with original react's `StyleSheet` will look like: 374 | ```js 375 | styles = { 376 | text: 0 377 | } 378 | ``` 379 | But extended stylesheet saves calculated values under `_text` property: 380 | ```js 381 | styles = { 382 | text: 0, 383 | _text: { 384 | fontSize: 16, 385 | color: 'gray' 386 | } 387 | } 388 | ``` 389 | To render icon we just take styles from `_text`: 390 | ```js 391 | return ( 392 | 393 | 394 | Hello 395 | 396 | ); 397 | ``` 398 | \[[top](#react-native-extended-stylesheet)\] 399 | 400 | ### Pseudo classes (:nth-child) 401 | 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. 402 | To get style for appropriate index you should use `EStyleSheet.child()` method. 403 | It's signature: `EStyleSheet.child(stylesObj, styleName, index, count)`. 404 | ```js 405 | const styles = EStyleSheet.create({ 406 | row: { 407 | fontSize: '1.5rem', 408 | borderTopWidth: 1 409 | }, 410 | 'row:nth-child-even': { 411 | backgroundColor: 'gray' // make stripped 412 | }, 413 | 'row:last-child': { 414 | borderBottomWidth: 1 // render bottom edge for last row 415 | } 416 | }); 417 | ... 418 | render() { 419 | return ( 420 | 421 | {items.map((item, index) => { 422 | return ( 423 | 424 | ); 425 | })} 426 | 427 | ); 428 | } 429 | ``` 430 | \[[top](#react-native-extended-stylesheet)\] 431 | 432 | ### Value as a function 433 | For the deepest customization you can specify any value as a function that will be executed on EStyleSheet build. 434 | For example, you may *darken* or *lighten* color of variable via [npm color package](https://www.npmjs.com/package/color): 435 | ```js 436 | import Color from 'color'; 437 | import EStyleSheet from 'react-native-extended-stylesheet'; 438 | 439 | const styles = EStyleSheet.create({ 440 | button: { 441 | backgroundColor: () => Color('green').darken(0.1).hexString() // <-- value as a function 442 | } 443 | }); 444 | 445 | render() { 446 | return ( 447 | 448 | ... 449 | 450 | ); 451 | } 452 | ``` 453 | 454 | The common pattern is to use [EStyleSheet.value()](#value) inside the function to get access to global variables: 455 | ```js 456 | 457 | EStyleSheet.build({ 458 | $prmaryColor: 'green' 459 | }); 460 | 461 | const styles = EStyleSheet.create({ 462 | button: { 463 | backgroundColor: () => Color(EStyleSheet.value('$prmaryColor')).darken(0.1).hexString() 464 | } 465 | }); 466 | ``` 467 | 468 | \[[top](#react-native-extended-stylesheet)\] 469 | 470 | ### Caching 471 | If you use dynamic styles depending on runtime prop or you are making reusable component with dynamic styling 472 | you may need stylesheet creation in every `render()` call. Let's take example from [scaling](#scaling) section: 473 | ```js 474 | class Button extends React.Component { 475 | static propTypes = { 476 | scale: React.PropTypes.number 477 | }; 478 | render() { 479 | let style = getStyle(this.props.scale) 480 | return ( 481 | 482 | 483 | ); 484 | } 485 | } 486 | 487 | let getStyle = function (scale = 1) { 488 | return EStyleSheet.create({ 489 | $scale: scale, 490 | button: { 491 | width: 100, 492 | height: 20, 493 | marginLeft: 10 494 | } 495 | }); 496 | } 497 | ``` 498 | To avoid creating styles on every render you can use [lodash.memoize](https://www.npmjs.com/package/lodash.memoize): 499 | store result for particular parameters and returns it from cache when called with the same parameters. 500 | Updated example: 501 | ```js 502 | import memoize from 'lodash.memoize'; 503 | 504 | let getStyle = memoize(function (scale = 1) { 505 | return EStyleSheet.create({ 506 | $scale: scale, 507 | button: { 508 | width: 100, 509 | height: 20, 510 | marginLeft: 10 511 | } 512 | }); 513 | }); 514 | ``` 515 | Now if you call `getStyle(1.5)` 3 times actually style will be created on the first call 516 | and two other calls will get it from cache. 517 | \[[top](#react-native-extended-stylesheet)\] 518 | 519 | ### Outline for debug 520 | It is possible to outline all components that are using EStyleSheet. For that set global `$outline` variable: 521 | ```js 522 | EStyleSheet.build({$outline: 1}); 523 | ``` 524 | > Note that components without styles will not be outlined, 525 | because RN [does not support](https://github.com/facebook/react-native/issues/1768) default component styling yet. 526 | 527 | To outline particular component set local `$outline` variable: 528 | ```js 529 | const styles = EStyleSheet.create({ 530 | $outline: 1, 531 | column: { 532 | width: '80%', 533 | flexDirection: 'row' 534 | }, 535 | ... 536 | }); 537 | ``` 538 | \[[top](#react-native-extended-stylesheet)\] 539 | 540 | ### Hot module reload 541 | [Hot module reload (HMR)](https://facebook.github.io/react-native/blog/2016/03/24/introducing-hot-reloading.html) 542 | allows you to change code and see live updates without loosing app state. It is very handy for tuning styles. 543 | EStyleSheet supports HMR with the following options: 544 | 545 | 1. When you change style of component - the component is updated by HMR automatically without any effort from your side. 546 | 2. When you change global variable or theme - you should use [HMR API](https://facebook.github.io/react-native/releases/next/#hmr-api) 547 | to force style re-calculation: 548 | ```js 549 | // app.js 550 | EStyleSheet.build({ 551 | $fontColor: 'black' 552 | }); 553 | 554 | ... 555 | 556 | module.hot.accept(() => { 557 | EStyleSheet.clearCache(); 558 | EStyleSheet.build(); // force style re-calculation 559 | }); 560 | ``` 561 | See full example of HMR [here](examples/hmr). 562 | \[[top](#react-native-extended-stylesheet)\] 563 | 564 | ## EStyleSheet API 565 | ### .create() 566 | ```js 567 | /** 568 | * Creates extended stylesheet object 569 | * 570 | * @param {Object} source style 571 | * @returns {Object} extended stylesheet object 572 | */ 573 | create (source) {...} 574 | ``` 575 | \[[top](#react-native-extended-stylesheet)\] 576 | 577 | ### .build() 578 | ```js 579 | /** 580 | * Calculates all stylesheets 581 | * 582 | * @param {Object} [globalVars] global variables for all stylesheets 583 | */ 584 | build (globalVars) {...} 585 | ``` 586 | \[[top](#react-native-extended-stylesheet)\] 587 | 588 | ### .value() 589 | ```js 590 | /** 591 | * Calculates particular expression. 592 | * 593 | * @param {*} value 594 | * @param {String} [prop] property for which value is calculated. For example, to calculate percent values 595 | * the function should know is it 'width' or 'height' to use proper reference value. 596 | * @returns {*} calculated result 597 | */ 598 | value (value, prop) {...} 599 | ``` 600 | **Please note** that in most cases `EStyleSheet.value()` should be used inside function, not directly: 601 | ```js 602 | const styles = EStyleSheet.create({ 603 | button1: { 604 | width: () => EStyleSheet.value('$contentWidth') + 10 // <-- Correct! 605 | }, 606 | button2: { 607 | width: EStyleSheet.value('$contentWidth') + 10 // <-- Incorrect. Because EStyleSheet.build() may occur later and $contentWidth will be undefined at this moment. 608 | } 609 | }); 610 | ``` 611 | \[[top](#react-native-extended-stylesheet)\] 612 | 613 | ### .child() 614 | ```js 615 | /** 616 | * Returns styles with pseudo classes :first-child, :nth-child-even, :last-child according to index and count 617 | * 618 | * @param {Object} stylesheet 619 | * @param {String} styleName 620 | * @param {Number} index index of item for style 621 | * @param {Number} count total count of items 622 | * @returns {Object|Array} styles 623 | */ 624 | child (styles, styleName, index, count) {...} 625 | ``` 626 | \[[top](#react-native-extended-stylesheet)\] 627 | 628 | ### .subscribe() 629 | ```js 630 | /** 631 | * Subscribe to event. Currently only 'build' event is supported. 632 | * 633 | * @param {String} event 634 | * @param {Function} listener 635 | */ 636 | subscribe (event, listener) {...} 637 | 638 | ``` 639 | This method is useful when you want to pre-render some component on init. 640 | As extended style is calculated after call of `EStyleSheet.build()`, 641 | it is not available instantly after creation so you should wrap pre-render 642 | info listener to `build` event: 643 | ```js 644 | const styles = EStyleSheet.create({ 645 | button: { 646 | width: '80%', 647 | } 648 | }); 649 | 650 | // this will NOT work as styles.button is not calculated yet 651 | let Button = ; 652 | 653 | // but this will work 654 | let Button; 655 | EStyleSheet.subscribe('build', () => { 656 | Button = ; 657 | }); 658 | ``` 659 | \[[top](#react-native-extended-stylesheet)\] 660 | 661 | ### .unsubscribe() 662 | ```js 663 | /** 664 | * Unsubscribe from event. Currently only 'build' event is supported. 665 | * 666 | * @param {String} event 667 | * @param {Function} listener 668 | */ 669 | unsubscribe (event, listener) {...} 670 | 671 | ``` 672 | Unsubscribe from event. 673 | \[[top](#react-native-extended-stylesheet)\] 674 | 675 | ## Caveats 676 | 1. **Dynamic theme change is possible only with loosing components local state** 677 | When theme styles are re-calculated - all components should be re-rendered. 678 | Currently it can be done via re-mounting components tree, please see [#47]. 679 | > Note: it is not issue if you are using state container like [Redux](https://github.com/reactjs/redux) 680 | and can easily re-render app in the same state 681 | 682 | 2. **Dynamic orientation change is not supported** 683 | Please see [#9] for more details. 684 | 685 | 3. **Old RN versions (< 0.43) can crash the app with percent values** 686 | 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. 687 | Older RN versions (< 0.43) can't process percents and EStyleSheet process such values. 688 | So if you are using RN < 0.43, you should stick to EStyleSheet@0.4.0. 689 | 690 | ## FAQ 691 | 1. **I'm getting error: `"Unresolved variable: ..."`** 692 | - Ensure that you call `EStyleSheet.build()` in entry point of your app. 693 | - Ensure that `$variable` name without typos. 694 | - Ensure that you are not using `EStyleSheet.value()` before the styles are built. See [#50](https://github.com/vitalets/react-native-extended-stylesheet/issues/50) for details. 695 | 696 | ## Changelog 697 | Please see [CHANGELOG.md](CHANGELOG.md) 698 | 699 | ## Feedback 700 | If you have any ideas or something goes wrong feel free to 701 | [open new issue](https://github.com/vitalets/react-native-extended-stylesheet/issues/new). 702 | 703 | ## License 704 | [MIT](LICENSE.md) @ [Vitaliy Potapov](https://github.com/vitalets) 705 | 706 | \[[top](#react-native-extended-stylesheet)\] 707 | 708 |
709 | * * *
710 | If you love :heart: JavaScript and would like to track new trending repositories,
711 | have a look on vitalets/github-trending-repos.
712 |
713 | 714 | [#9]: https://github.com/vitalets/react-native-extended-stylesheet/issues/9 715 | [#16]: https://github.com/vitalets/react-native-extended-stylesheet/issues/16 716 | [#47]: https://github.com/vitalets/react-native-extended-stylesheet/issues/47 717 | [#32]: https://github.com/vitalets/react-native-extended-stylesheet/issues/32 718 | [#77]: https://github.com/vitalets/react-native-extended-stylesheet/issues/77 719 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## How to run examples 2 | 3 | 1. Create new react-native project 4 | 5 | ```bash 6 | react-native init ExtendedStyleSheetExample 7 | cd ExtendedStyleSheetExample 8 | ``` 9 | 10 | 2. Install `react-native-extended-stylesheet` 11 | 12 | ```bash 13 | npm i react-native-extended-stylesheet 14 | ``` 15 | 16 | 3. Change `index.ios.js` and `index.android.js` to: 17 | 18 | ```js 19 | import {AppRegistry} from 'react-native'; 20 | import App from 'react-native-extended-stylesheet/examples/simple/app'; 21 | AppRegistry.registerComponent('ExtendedStyleSheetExample', () => App); 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /examples/hmr/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EStyleSheet from 'react-native-extended-stylesheet'; 3 | import MyComponent from './component'; 4 | 5 | /* 6 | 1. Run app 7 | 2. Enable hot module reload 8 | 3. Click a few times on "Click me" button to set component state 9 | 4. Change $fontColor value below 10 | 5. Watch that font color is changed and component state is saved 11 | */ 12 | 13 | EStyleSheet.build({ 14 | $fontColor: 'black' // change this to another color 15 | }); 16 | 17 | export default class extends React.Component { 18 | render() { 19 | return ( 20 | 21 | ); 22 | } 23 | } 24 | 25 | module.hot.accept(() => { 26 | EStyleSheet.clearCache(); 27 | EStyleSheet.build(); 28 | }); 29 | -------------------------------------------------------------------------------- /examples/hmr/component.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {View, Text, Button} from 'react-native'; 3 | import EStyleSheet from 'react-native-extended-stylesheet'; 4 | 5 | export default class extends React.Component { 6 | constructor() { 7 | super(); 8 | this.state = {count: 0}; 9 | } 10 | render() { 11 | return ( 12 | 13 | You clicked: {this.state.count} 14 |