├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .flowconfig ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── __tests__ ├── API │ ├── Accessibility │ │ └── index.js │ ├── Core │ │ ├── guessPlatform.js │ │ └── props.js │ ├── Gesture │ │ └── index.js │ └── StyleSheet │ │ ├── Declaration │ │ └── index.js │ │ ├── Declarations │ │ └── index.js │ │ └── Properties │ │ └── Property │ │ ├── alignContent.js │ │ ├── borderBottomStyle.js │ │ ├── borderWidth.js │ │ ├── cursor.js │ │ ├── display.js │ │ ├── flexDirection.js │ │ ├── marginHorizontal.js │ │ ├── marginVertical.js │ │ ├── paddingHorizontal.js │ │ ├── paddingVertical.js │ │ ├── resizeMode.js │ │ └── transform.js └── Component │ ├── Image │ ├── desktop.js │ ├── index.js │ ├── mobile.js │ └── web.js │ └── View │ ├── desktop.js │ ├── index.js │ ├── mobile.js │ └── web.js ├── _test_ └── API │ ├── Core │ ├── index.spec.js │ └── props.spec.js │ └── platform.spec.js ├── app ├── API │ ├── Accessibility │ │ └── index.js │ ├── Animated │ │ ├── Value │ │ │ └── dom.js │ │ ├── View │ │ │ ├── dom.js │ │ │ ├── index.js │ │ │ └── mobile.js │ │ ├── index.js │ │ └── timing │ │ │ └── dom.js │ ├── Core │ │ ├── guessPlatform.js │ │ ├── index.js │ │ └── props.js │ ├── Dimensions │ │ └── index.js │ ├── Gesture │ │ └── index.js │ ├── Notifications │ │ ├── desktop.js │ │ └── index.js │ ├── Storage │ │ └── index.js │ └── StyleSheet │ │ ├── index.js │ │ └── transforms │ │ ├── border.js │ │ ├── borderWidth.js │ │ ├── boxShadow.js │ │ ├── cursor.js │ │ ├── display.js │ │ ├── flexDirection.js │ │ ├── marginHorizontal.js │ │ ├── marginVertical.js │ │ ├── resizeMode.js │ │ ├── transform.js │ │ └── transition.js ├── Component │ ├── Image │ │ ├── DOM.js │ │ ├── desktop.js │ │ ├── index.js │ │ └── mobile.js │ ├── Link │ │ ├── index.js │ │ ├── mobile.js │ │ └── web.js │ ├── ListView │ │ ├── index.js │ │ ├── mobile.js │ │ └── web.js │ ├── ScrollView │ │ ├── index.js │ │ ├── mobile.js │ │ └── web.js │ ├── Text │ │ ├── index.js │ │ ├── mobile.js │ │ └── web.js │ └── View │ │ ├── DOM.js │ │ ├── index.js │ │ └── mobile.js ├── config.js └── index.js ├── circle.yml ├── doc ├── API │ ├── Accessibility.md │ ├── Core.md │ ├── Dimensions.md │ ├── Gesture.md │ ├── Node.md │ └── StyleSheet.md ├── Components │ ├── Image.md │ ├── Link.md │ ├── ListView.md │ ├── ScrollView.md │ ├── Text.md │ └── View.md └── index.md ├── flow.js ├── flow ├── Image.js ├── Reactors.js ├── StyleSheet.js └── View.js ├── package.json ├── test └── API │ ├── Core │ ├── index.spec.js │ └── props.spec.js │ └── platform.spec.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015","react","stage-0"], 3 | "plugins": [ 4 | "transform-flow-strip-types", 5 | "syntax-async-functions", 6 | "transform-regenerator", 7 | ["transform-runtime", { 8 | "polyfill": false, 9 | "regenerator": true 10 | }] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /flow.js 2 | /flow 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 7, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true, 8 | "modules": true 9 | } 10 | }, 11 | "env": { 12 | "es6": true, 13 | "node": true 14 | }, 15 | "plugins": [ 16 | "react", 17 | "react-native", 18 | "flowtype" 19 | ], 20 | "rules": { 21 | "array-bracket-spacing": [ 22 | 1, 23 | "never" 24 | ], 25 | "array-callback-return": 2, 26 | "block-scoped-var": 2, 27 | "brace-style": 1, 28 | "camelcase": 0, 29 | "callback-return": 2, 30 | "comma-spacing": 1, 31 | "computed-property-spacing": 1, 32 | "consistent-this": [ 33 | 1, 34 | "self" 35 | ], 36 | "curly": 2, 37 | "default-case": 0, 38 | "dot-location": [ 39 | 2, 40 | "property" 41 | ], 42 | "dot-notation": 1, 43 | "eol-last": 1, 44 | "eqeqeq": [ 45 | 2, 46 | "smart" 47 | ], 48 | "guard-for-in": 0, 49 | "handle-callback-err": [ 50 | 2, 51 | "^(err|error)$" 52 | ], 53 | "id-length": 0, 54 | "indent": [ 55 | 1, 56 | 2 57 | ], 58 | "jsx-quotes": [ 59 | 1, 60 | "prefer-double" 61 | ], 62 | "key-spacing": 1, 63 | "keyword-spacing": 1, 64 | "linebreak-style": 2, 65 | "max-len": [1, 100], 66 | "new-cap": 1, 67 | "new-parens": 2, 68 | "no-array-constructor": 2, 69 | "no-case-declarations": 2, 70 | "no-cond-assign": [ 71 | 2, 72 | "always" 73 | ], 74 | "no-continue": 1, 75 | "no-control-regex": 2, 76 | "no-debugger": 2, 77 | "no-delete-var": 2, 78 | "no-div-regex": 2, 79 | "no-dupe-args": 2, 80 | "no-dupe-keys": 2, 81 | "no-duplicate-case": 2, 82 | "no-else-return": 1, 83 | "no-empty": 2, 84 | "no-empty-function": 1, 85 | "no-empty-pattern": 2, 86 | "no-eq-null": 2, 87 | "no-eval": 2, 88 | "no-extend-native": 2, 89 | "no-extra-bind": 1, 90 | "no-extra-boolean-cast": 1, 91 | "no-extra-parens": 0, 92 | "no-extra-semi": 2, 93 | "no-fallthrough": 2, 94 | "no-floating-decimal": 2, 95 | "no-func-assign": 2, 96 | "no-implicit-coercion": 2, 97 | "no-implicit-globals": 2, 98 | "no-implied-eval": 2, 99 | "no-inline-comments": 1, 100 | "no-inner-declarations": [ 101 | 2, 102 | "both" 103 | ], 104 | "no-invalid-regexp": 2, 105 | "no-irregular-whitespace": 2, 106 | "no-iterator": 2, 107 | "no-labels": 2, 108 | "no-lone-blocks": 2, 109 | "no-lonely-if": 1, 110 | "no-loop-func": 2, 111 | "no-mixed-spaces-and-tabs": 2, 112 | "no-multi-spaces": 1, 113 | "no-multi-str": 2, 114 | "no-multiple-empty-lines": 1, 115 | "no-negated-in-lhs": 2, 116 | "no-nested-ternary": 2, 117 | "no-new": 2, 118 | "no-new-func": 2, 119 | "no-new-object": 2, 120 | "no-new-wrappers": 2, 121 | "no-obj-calls": 2, 122 | "no-octal": 2, 123 | "no-octal-escape": 2, 124 | "no-param-reassign": 1, 125 | "no-proto": 2, 126 | "no-redeclare": [ 127 | 2, 128 | { 129 | "builtinGlobals": true 130 | } 131 | ], 132 | "no-regex-spaces": 2, 133 | "no-return-assign": 2, 134 | "no-self-assign": 2, 135 | "no-self-compare": 2, 136 | "no-sequences": 2, 137 | "no-shadow": 2, 138 | "no-shadow-restricted-names": 2, 139 | "no-spaced-func": 2, 140 | "no-sparse-arrays": 2, 141 | "no-throw-literal": 2, 142 | "no-trailing-spaces": 2, 143 | "no-undef": 2, 144 | "no-undef-init": 2, 145 | "no-undefined": 2, 146 | "no-unexpected-multiline": 2, 147 | "no-unmodified-loop-condition": 2, 148 | "no-unneeded-ternary": 1, 149 | "no-unreachable": 2, 150 | "no-unused-expressions": 1, 151 | "no-unused-vars": [ 152 | 1, 153 | { 154 | "argsIgnorePattern": "^_", 155 | "varsIgnorePattern": "^(React|_)$" 156 | } 157 | ], 158 | "no-use-before-define": 0, 159 | "no-useless-call": 1, 160 | "no-useless-concat": 1, 161 | "no-void": 2, 162 | "no-warning-comments": 1, 163 | "no-whitespace-before-property": 2, 164 | "no-with": 2, 165 | "object-curly-spacing": [ 166 | 1, 167 | "never" 168 | ], 169 | "one-var-declaration-per-line": [ 170 | 1, 171 | "initializations" 172 | ], 173 | "operator-assignment": 1, 174 | "padded-blocks": [ 175 | 1, 176 | "never" 177 | ], 178 | "quote-props": [ 179 | 1, 180 | "consistent-as-needed" 181 | ], 182 | "quotes": [ 183 | 1, 184 | "single", 185 | "avoid-escape" 186 | ], 187 | "radix": [ 188 | 2, 189 | "always" 190 | ], 191 | "semi": 1, 192 | "semi-spacing": 1, 193 | "sort-vars": 0, 194 | "space-in-parens": 1, 195 | "space-infix-ops": 1, 196 | "space-unary-ops": [ 197 | 1, 198 | { 199 | "words": true, 200 | "nonwords": false 201 | } 202 | ], 203 | "spaced-comment": 1, 204 | "strict": [ 205 | 1, 206 | "global" 207 | ], 208 | "use-isnan": 2, 209 | "valid-typeof": 2, 210 | "wrap-iife": 2, 211 | "yoda": 1, 212 | 213 | "react/display-name": 0, 214 | "react/no-did-mount-set-state": [ 215 | 1, 216 | "allow-in-func" 217 | ], 218 | "react/no-did-update-set-state": [ 219 | 1, 220 | "allow-in-func" 221 | ], 222 | "react/no-multi-comp": 0, 223 | "react/no-unknown-property": 0, 224 | "react/prop-types": 0, 225 | "react/react-in-jsx-scope": 0, 226 | "react/self-closing-comp": 1, 227 | "react/wrap-multilines": 0, 228 | 229 | "react/jsx-boolean-value": 0, 230 | "react/jsx-no-undef": 1, 231 | "react/jsx-sort-props": 0, 232 | "react/jsx-uses-react": 0, 233 | "react/jsx-uses-vars": 1, 234 | 235 | "flowtype/boolean-style": [ 236 | 2, 237 | "boolean" 238 | ], 239 | "flowtype/define-flow-type": 1, 240 | "flowtype/delimiter-dangle": [ 241 | 2, 242 | "always-multiline" 243 | ], 244 | "flowtype/generic-spacing": [ 245 | 2, 246 | "never" 247 | ], 248 | "flowtype/no-weak-types": 0, 249 | "flowtype/object-type-delimiter": [ 250 | 2, 251 | "comma" 252 | ], 253 | "flowtype/require-parameter-type": [ 254 | 0, 255 | { 256 | "excludeArrowFunctions": "expressionsOnly" 257 | } 258 | ], 259 | "flowtype/require-return-type": [ 260 | 0, 261 | "always", 262 | { 263 | "annotateUndefined": "never", 264 | "excludeArrowFunctions": "expressionsOnly" 265 | } 266 | ], 267 | "flowtype/require-valid-file-annotation": 2, 268 | "flowtype/semi": [ 269 | 2, 270 | "always" 271 | ], 272 | "flowtype/space-after-type-colon": [ 273 | 2, 274 | "always" 275 | ], 276 | "flowtype/space-before-generic-bracket": [ 277 | 2, 278 | "never" 279 | ], 280 | "flowtype/space-before-type-colon": [ 281 | 2, 282 | "never" 283 | ], 284 | "flowtype/type-id-match": [ 285 | 2, 286 | "^\\$" 287 | ], 288 | "flowtype/union-intersection-spacing": [ 289 | 2, 290 | "always" 291 | ], 292 | "flowtype/use-flow-type": 1, 293 | "flowtype/valid-syntax": 1 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | node_modules/react-native/Libraries/react-native/react-native-interface.js 7 | node_modules/react-native/flow 8 | flow/ 9 | 10 | [options] 11 | module.system=haste 12 | 13 | esproposal.class_static_fields=enable 14 | esproposal.class_instance_fields=enable 15 | 16 | munge_underscores=true 17 | 18 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 19 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub' 20 | 21 | suppress_type=$FlowIssue 22 | suppress_type=$FlowFixMe 23 | suppress_type=$FixMe 24 | 25 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 26 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 27 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/* 2 | /npm-debug.log 3 | /Example 4 | /dist 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.babelrc 2 | /node_modules/* 3 | !/dist/ 4 | /Example 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | reactors change log 2 | === 3 | 4 | # Versions 5 | 6 | ## v2.1.4 #202 Does not work on safari 7 | 8 | ## v2.1.3 9 | 10 | - Omit style on View if scrollable since style is passed down to container style 11 | 12 | ## v2.1.2 13 | 14 | - Remove `onPress` prop to mobile View 15 | 16 | ## v2.1.1 17 | 18 | - Remove `onPress` filter for mobile on Gesture 19 | 20 | ## v2.0.1 21 | 22 | - Reinstate gesture on props (+ tests) 23 | 24 | ## v1.5.33 25 | 26 | - `ListView` mobile refresh 27 | 28 | ## v1.5.32 29 | 30 | - `ListView` is now a class 31 | 32 | ## v1.5.31 33 | 34 | - Reinstall View if not scrollable for mobile 35 | 36 | ## v1.5.30 37 | 38 | - `mergeStyles(...styles)` 39 | 40 | ## v1.5.29 41 | 42 | - Pass mobile View style to `contentContainerStyle` 43 | - Skip `cursor` and `borderBottomStyle` for mobile 44 | 45 | ## v1.5.28 46 | 47 | - Add Storage support for web 48 | 49 | ## v1.5.27 50 | 51 | - Add `boolean View#scrollable` to mobile view to get ScrollView 52 | 53 | ## v1.5.26 54 | 55 | - Add `paddingHorizontal`, `paddingVertical` 56 | 57 | ## v1.5.25 58 | 59 | - Add `isAndroid`, `isios` 60 | 61 | ## v1.5.24 62 | 63 | - Remove RN properties on DOM View 64 | 65 | ## v1.5.23 66 | 67 | - Add `isMobile`, `isWeb`, `isDesktop`, `isDOM` 68 | 69 | ## v1.5.22 70 | 71 | - View for DOM is now a state component 72 | 73 | ## v1.5.21 74 | 75 | - Add accessibility support for DOM 76 | 77 | ## v1.5.20 78 | 79 | - Update Doc about Dimensions resize 80 | 81 | ## v1.5.19 82 | 83 | - Fix 'Reactors not found' in Dimensions 84 | 85 | ## v1.5.18 86 | 87 | - Add 'resize="cover"' style support for DOM-based platforms 88 | 89 | ## v1.5.17 90 | 91 | - Add 'window.resize' listeners on `Dimensions` for DOM-based platforms 92 | 93 | - [v0.1.17](https://github.com/co2-git/reactors/issues?q=milestone%3Av0.1.17) 94 | - [v0.1.16](https://github.com/co2-git/reactors/issues?q=milestone%3Av0.1.16) 95 | - [v0.1.15](https://github.com/co2-git/reactors/issues?q=milestone%3Av0.1.15) 96 | - [v0.1.14](https://github.com/co2-git/reactors/issues?q=milestone%3Av0.1.14) 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | reactors 2 | === 3 | 4 | 5 | [![npm version](https://badge.fury.io/js/reactors.svg)](https://badge.fury.io/js/reactors) 6 | [![GitHub version](https://badge.fury.io/gh/co2-git%2Freactors.png)](https://badge.fury.io/gh/co2-git%2Freactors) 7 | 8 | === 9 | 10 | Framework based on [React](https://facebook.github.io/react/) to build cross-platform apps that run web, mobile and desktop. 11 | 12 | **To create and run reactors apps, see [reactors-cli](https://github.com/co2-git/reactors-cli)** 13 | 14 | # Install 15 | 16 | ```bash 17 | npm install reactors 18 | ``` 19 | 20 | # Usage 21 | 22 | ```javascript 23 | import React from 'react'; 24 | import { 25 | ListView, 26 | Text, 27 | View, 28 | } from 'reactors'; 29 | 30 | export default function MyAwesomeComponent() { 31 | return ( 32 | 33 | One code to rule them all: 34 | {platform}} 44 | /> 45 | 46 | ); 47 | } 48 | ``` 49 | 50 | View a detailed example [here](https://github.com/co2-git/reactors-cli/blob/master/templates/app/App.js). 51 | 52 | # Core Components 53 | 54 | - [Image](doc/Components/Image.md) 55 | - [ListView](doc/Components/ListView.md) 56 | - [Link](doc/Components/Link.md) 57 | - [ScrollView](doc/Components/ScrollView.md) 58 | - [Text](doc/Components/Text.md) 59 | - [View](doc/Components/View.md) 60 | 61 | # Core APIs 62 | 63 | - [Core](doc/API/Core.md) 64 | - [Dimensions](doc/API/Dimensions.md) 65 | - [Gesture](doc/API/Gesture.md) 66 | - [Notifications](doc/Components/Notifications.md) 67 | - [Storage](doc/API/Storage.md) 68 | - [StyleSheet](doc/API/StyleSheet.md) 69 | 70 | # Platform dependent code 71 | 72 | You can code for a specific platform: 73 | 74 | ```javascript 75 | switch (Reactors.platform) { 76 | case 'mobile': 77 | // ... 78 | break; 79 | case 'web': 80 | // ... 81 | break; 82 | case 'desktop': 83 | // ... 84 | break; 85 | } 86 | ``` 87 | 88 | # Plugins 89 | 90 | Check out Reactors plugin in the `npm` registry. Look for packages starting by `reactors-`. 91 | 92 | Some plugins: 93 | 94 | - [reactors-file-dialog](https://www.npmjs.com/package/reactors-file-dialog) 95 | - [reactors-form](https://www.npmjs.com/package/reactors-form) 96 | - [reactors-grid](https://www.npmjs.com/package/reactors-grid) 97 | - [reactors-http-request](https://www.npmjs.com/package/reactors-http-request) 98 | - [reactors-router](https://www.npmjs.com/package/reactors-router) 99 | -------------------------------------------------------------------------------- /__tests__/API/Accessibility/index.js: -------------------------------------------------------------------------------- 1 | /* globals global describe expect test */ 2 | import Reactors from '../../../app/API/Core'; 3 | import Accessibility from '../../../app/API/Accessibility'; 4 | 5 | describe('API / Accessibility', () => { 6 | test('it should be a function', () => { 7 | expect(Accessibility).toBeInstanceOf(Function); 8 | }); 9 | 10 | test( 11 | 'it should transform aria-labelledby to accessibilityLabel in mobile', 12 | () => { 13 | Reactors.platform = 'mobile'; 14 | expect( 15 | Accessibility.transform( 16 | {['aria-labelledby']: 'hello'}, 17 | ) 18 | ) 19 | .toMatchObject({ 20 | added: [{accessibilityLabel: 'hello'}], 21 | removed: ['aria-labelledby'], 22 | }); 23 | }, 24 | ); 25 | 26 | test( 27 | 'it should transform accessibilityLabel to aria-labelledby in web', 28 | () => { 29 | Reactors.platform = 'web'; 30 | expect( 31 | Accessibility.transform( 32 | {['accessibilityLabel']: 'hello'}, 33 | ) 34 | ) 35 | .toMatchObject({ 36 | added: [{['aria-labelledby']: 'hello'}], 37 | removed: ['accessibilityLabel'], 38 | }); 39 | }, 40 | ); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/API/Core/guessPlatform.js: -------------------------------------------------------------------------------- 1 | /* globals global describe expect test */ 2 | import guessPlatform from '../../../app/API/Core/guessPlatform'; 3 | 4 | describe('API / Core / guessPlatform', () => { 5 | test('it should be a function', () => { 6 | expect(guessPlatform).toBeInstanceOf(Function); 7 | }); 8 | 9 | test('it should be node', () => { 10 | expect(guessPlatform()).toEqual('node'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /__tests__/API/Core/props.js: -------------------------------------------------------------------------------- 1 | /* globals global describe expect test */ 2 | import Reactors from '../../../app/API/Core'; 3 | import props from '../../../app/API/Core/props'; 4 | 5 | describe('API / Core / props', () => { 6 | test('it should be a function', () => { 7 | expect(props).toBeInstanceOf(Function); 8 | }); 9 | 10 | test('it should return correct props', () => { 11 | expect(props({foo: 1})).toEqual({foo: 1}); 12 | }); 13 | 14 | test('it should transform styles', () => { 15 | Reactors.platform = 'mobile'; 16 | const styles = props({ 17 | style: { 18 | display: 'flex', 19 | flexDirection: 'row', 20 | }, 21 | }); 22 | expect(styles.style).toEqual({ 23 | flexDirection: 'row', 24 | }); 25 | }); 26 | 27 | test('it should apply accessibility to mobile', () => { 28 | Reactors.platform = 'mobile'; 29 | const aria = props({ 30 | ['aria-labelledby']: 'hello', 31 | }); 32 | expect(aria).toEqual({ 33 | accessibilityLabel: 'hello', 34 | }); 35 | }); 36 | 37 | test('it should apply accessibility to web', () => { 38 | Reactors.platform = 'web'; 39 | const aria = props({ 40 | accessibilityLabel: 'hello', 41 | }); 42 | expect(aria).toEqual({ 43 | ['aria-labelledby']: 'hello', 44 | }); 45 | }); 46 | 47 | test('it should transform onPress gesture to onClick on web', () => { 48 | Reactors.platform = 'web'; 49 | const onClick = props({ 50 | onPress: 123, 51 | }); 52 | expect(onClick).toEqual({ 53 | ['onClick']: 123, 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /__tests__/API/Gesture/index.js: -------------------------------------------------------------------------------- 1 | /* globals global describe expect test */ 2 | import Reactors from '../../../app/API/Core'; 3 | import Gesture from '../../../app/API/Gesture'; 4 | 5 | function onPress() { 6 | return; 7 | } 8 | 9 | describe('API / Gesture', () => { 10 | test( 11 | 'it should transform onPress on web', 12 | () => { 13 | Reactors.platform = 'web'; 14 | expect( 15 | Gesture.transform({onPress}) 16 | ) 17 | .toMatchObject({ 18 | added: [{onClick: onPress}], 19 | removed: ['onPress'], 20 | }); 21 | }, 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Declaration/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import Declaration from '../../../../app/API/StyleSheet/Declaration'; 3 | import should from 'should'; 4 | import config from '../../../../app/config'; 5 | 6 | const widthDeclaration = new Declaration( 7 | 'width', 8 | 100, 9 | 'web', 10 | ); 11 | 12 | const alignContentDeclaration = new Declaration( 13 | 'alignContent', 14 | 'center', 15 | 'mobile', 16 | ); 17 | 18 | const borderWidthDeclaration = new Declaration( 19 | 'borderWidth', 20 | 1, 21 | 'web', 22 | ); 23 | 24 | describe('API / StyleSheet / Declaration', () => { 25 | test('it should be a function', () => { 26 | expect(Declaration).toBeInstanceOf(Function); 27 | }); 28 | 29 | test('it should construct a simple declaration', () => { 30 | expect(widthDeclaration).toMatchObject({ 31 | platform: 'web', 32 | property: 'width', 33 | value: 100, 34 | style: undefined, 35 | }); 36 | }); 37 | 38 | test('it should objectify a simple declaration', () => { 39 | expect(widthDeclaration.toObject()).toMatchObject({ 40 | width: 100, 41 | }); 42 | }); 43 | 44 | test('it should construct a stripped declaration', () => { 45 | expect(alignContentDeclaration).toMatchObject({ 46 | platform: 'mobile', 47 | property: 'alignContent', 48 | value: 'center', 49 | }); 50 | should(alignContentDeclaration).have.property('style') 51 | .which.is.an.Object(); 52 | expect(alignContentDeclaration.style.name) 53 | .toEqual('alignContent'); 54 | expect(alignContentDeclaration.style.mobile) 55 | .toBeInstanceOf(Function); 56 | }); 57 | 58 | test('it should objectify a stripped declaration', () => { 59 | expect(alignContentDeclaration.toObject()).toMatchObject({}); 60 | }); 61 | 62 | test('it should construct a joined declaration', () => { 63 | expect(borderWidthDeclaration).toMatchObject({ 64 | platform: 'web', 65 | property: 'borderWidth', 66 | value: 1, 67 | }); 68 | should(borderWidthDeclaration).have.property('style') 69 | .which.is.an.Object(); 70 | expect(borderWidthDeclaration.style.name) 71 | .toEqual('borderWidth'); 72 | expect(borderWidthDeclaration.style.desktop) 73 | .toBeInstanceOf(Function); 74 | expect(borderWidthDeclaration.style.web) 75 | .toBeInstanceOf(Function); 76 | }); 77 | 78 | test('it should objectify a joined declaration', () => { 79 | expect(borderWidthDeclaration.toObject()).toMatchObject({ 80 | borderWidth: 1, 81 | borderStyle: config.DEFAULT_BORDER_STYLE, 82 | borderColor: config.DEFAULT_BORDER_COLOR, 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Declarations/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import Declarations from '../../../../app/API/StyleSheet/Declarations'; 3 | import Declaration from '../../../../app/API/StyleSheet/Declaration'; 4 | 5 | const rawDeclarations = new Declarations({ 6 | width: 100, 7 | borderWidth: 2, 8 | }); 9 | 10 | describe('API / StyleSheet / Declaratiosn', () => { 11 | test('it should be a function', () => { 12 | expect(Declarations).toBeInstanceOf(Function); 13 | }); 14 | 15 | test('it should construct', () => { 16 | expect(rawDeclarations.declarations).toBeInstanceOf(Array); 17 | expect(rawDeclarations.declarations[0]) 18 | .toBeInstanceOf(Declaration); 19 | expect(rawDeclarations.declarations[1]) 20 | .toBeInstanceOf(Declaration); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/alignContent.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import alignContent 3 | from '../../../../../app/API/StyleSheet/Properties/Property/alignContent'; 4 | 5 | describe('API / StyleSheet / Properties / alignContent', () => { 6 | test('it should be an object', () => { 7 | expect(alignContent).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(alignContent).toMatchObject({ 12 | name: 'alignContent', 13 | }); 14 | }); 15 | 16 | test('it should have a mobile function', () => { 17 | expect(alignContent.mobile).toBeInstanceOf(Function); 18 | }); 19 | 20 | test('it should return an empty object on mobile', () => { 21 | expect(alignContent.mobile()).toEqual({}); 22 | }); 23 | 24 | test('it should not have a desktop function', () => { 25 | expect(alignContent.desktop).toBeUndefined(); 26 | }); 27 | 28 | test('it should not have a web function', () => { 29 | expect(alignContent.web).toBeUndefined(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/borderBottomStyle.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import borderBottomStyle from 3 | '../../../../../app/API/StyleSheet/Properties/Property/borderBottomStyle'; 4 | 5 | describe('API / StyleSheet / Properties / borderBottomStyle', () => { 6 | test('it should be an object', () => { 7 | expect(borderBottomStyle).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(borderBottomStyle).toMatchObject({ 12 | name: 'borderBottomStyle', 13 | }); 14 | }); 15 | 16 | test('it should have a mobile function', () => { 17 | expect(borderBottomStyle.mobile).toBeInstanceOf(Function); 18 | }); 19 | 20 | test('it should return an empty object on mobile', () => { 21 | expect(borderBottomStyle.mobile()).toEqual({}); 22 | }); 23 | 24 | test('it should not have a desktop function', () => { 25 | expect(borderBottomStyle.desktop).toBeUndefined(); 26 | }); 27 | 28 | test('it should not have a web function', () => { 29 | expect(borderBottomStyle.web).toBeUndefined(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/borderWidth.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import borderWidth 3 | from '../../../../../app/API/StyleSheet/Properties/Property/borderWidth'; 4 | import config from '../../../../../app/config'; 5 | 6 | const missingStyle = [ 7 | {property: 'borderColor', value: 'red'}, 8 | ]; 9 | 10 | const missingColor = [ 11 | {property: 'borderStyle', value: 'none'}, 12 | ]; 13 | 14 | describe('API / StyleSheet / Properties / borderWidth', () => { 15 | test('it should be an object', () => { 16 | expect(borderWidth).toBeInstanceOf(Object); 17 | }); 18 | 19 | test('it should have the right name', () => { 20 | expect(borderWidth).toMatchObject({ 21 | name: 'borderWidth', 22 | }); 23 | }); 24 | 25 | test('it should not have a mobile function', () => { 26 | expect(borderWidth.mobile).toBeUndefined(); 27 | }); 28 | 29 | test('it should have a desktop function', () => { 30 | expect(borderWidth.desktop).toBeInstanceOf(Function); 31 | }); 32 | 33 | test('it should return style and color if missing', () => { 34 | expect(borderWidth.desktop(10, {})).toMatchObject({ 35 | borderWidth: 10, 36 | borderColor: config.DEFAULT_BORDER_COLOR, 37 | borderStyle: config.DEFAULT_BORDER_STYLE, 38 | }); 39 | }); 40 | 41 | test('it should return style if missing', () => { 42 | expect(borderWidth.desktop(10, missingStyle)).toMatchObject({ 43 | borderWidth: 10, 44 | borderStyle: config.DEFAULT_BORDER_STYLE, 45 | }); 46 | }); 47 | 48 | test('it should return color if missing', () => { 49 | expect(borderWidth.desktop(10, missingColor)).toMatchObject({ 50 | borderWidth: 10, 51 | borderColor: config.DEFAULT_BORDER_COLOR, 52 | }); 53 | }); 54 | 55 | test('it should return only width if color and style are not missing', () => { 56 | expect(borderWidth.desktop(10, [...missingStyle, ...missingColor])) 57 | .toMatchObject({ 58 | borderWidth: 10, 59 | }); 60 | }); 61 | 62 | test('it should have a web function', () => { 63 | expect(borderWidth.web).toBeInstanceOf(Function); 64 | }); 65 | 66 | test('it should return style and color if missing', () => { 67 | expect(borderWidth.web(10, {})).toMatchObject({ 68 | borderWidth: 10, 69 | borderColor: config.DEFAULT_BORDER_COLOR, 70 | borderStyle: config.DEFAULT_BORDER_STYLE, 71 | }); 72 | }); 73 | 74 | test('it should return style if missing', () => { 75 | expect(borderWidth.web(10, missingStyle)).toMatchObject({ 76 | borderWidth: 10, 77 | borderStyle: config.DEFAULT_BORDER_STYLE, 78 | }); 79 | }); 80 | 81 | test('it should return color if missing', () => { 82 | expect(borderWidth.web(10, missingColor)).toMatchObject({ 83 | borderWidth: 10, 84 | borderColor: config.DEFAULT_BORDER_COLOR, 85 | }); 86 | }); 87 | 88 | test('it should return only width if color and style are not missing', () => { 89 | expect(borderWidth.web(10, [...missingStyle, ...missingColor])) 90 | .toMatchObject({ 91 | borderWidth: 10, 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/cursor.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import cursor 3 | from '../../../../../app/API/StyleSheet/Properties/Property/cursor'; 4 | 5 | describe('API / StyleSheet / Properties / cursor', () => { 6 | test('it should be an object', () => { 7 | expect(cursor).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(cursor).toMatchObject({ 12 | name: 'cursor', 13 | }); 14 | }); 15 | 16 | test('it should have a mobile function', () => { 17 | expect(cursor.mobile).toBeInstanceOf(Function); 18 | }); 19 | 20 | test('it should return an empty object on mobile', () => { 21 | expect(cursor.mobile()).toEqual({}); 22 | }); 23 | 24 | test('it should not have a desktop function', () => { 25 | expect(cursor.desktop).toBeUndefined(); 26 | }); 27 | 28 | test('it should not have a web function', () => { 29 | expect(cursor.web).toBeUndefined(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/display.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import display 3 | from '../../../../../app/API/StyleSheet/Properties/Property/display'; 4 | 5 | describe('API / StyleSheet / Properties / display', () => { 6 | test('it should be an object', () => { 7 | expect(display).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(display).toMatchObject({ 12 | name: 'display', 13 | }); 14 | }); 15 | 16 | test('it should have a mobile function', () => { 17 | expect(display.mobile).toBeInstanceOf(Function); 18 | }); 19 | 20 | test('it should return an empty object on mobile', () => { 21 | expect(display.mobile()).toEqual({}); 22 | }); 23 | 24 | test('it should not have a desktop function', () => { 25 | expect(display.desktop).toBeUndefined(); 26 | }); 27 | 28 | test('it should not have a web function', () => { 29 | expect(display.web).toBeUndefined(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/flexDirection.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import flexDirection 3 | from '../../../../../app/API/StyleSheet/Properties/Property/flexDirection'; 4 | import config from '../../../../../app/config'; 5 | 6 | describe('API / StyleSheet / Properties / flexDirection', () => { 7 | test('it should be an object', () => { 8 | expect(flexDirection).toBeInstanceOf(Object); 9 | }); 10 | 11 | test('it should have the right name', () => { 12 | expect(flexDirection).toMatchObject({ 13 | name: 'flexDirection', 14 | }); 15 | }); 16 | 17 | test('it should not have a mobile function', () => { 18 | expect(flexDirection.mobile).toBeUndefined(); 19 | }); 20 | 21 | test('it should have a desktop function', () => { 22 | expect(flexDirection.desktop).toBeInstanceOf(Function); 23 | }); 24 | 25 | test('it should return display if missing', () => { 26 | expect(flexDirection.desktop('row', [])).toMatchObject({ 27 | flexDirection: 'row', 28 | display: 'flex', 29 | }); 30 | }); 31 | 32 | test('it should return only flex direction if display is not missing', () => { 33 | expect( 34 | flexDirection.desktop('column', [{property: 'display', value: 'flex'}]) 35 | ) 36 | .toMatchObject({ 37 | flexDirection: 'column', 38 | }); 39 | }); 40 | 41 | test('it should have a web function', () => { 42 | expect(flexDirection.web).toBeInstanceOf(Function); 43 | }); 44 | 45 | test('it should return display if missing', () => { 46 | expect(flexDirection.web('row', [])).toMatchObject({ 47 | flexDirection: 'row', 48 | display: 'flex', 49 | }); 50 | }); 51 | 52 | test('it should return only flex direction if display is not missing', () => { 53 | expect( 54 | flexDirection.web('column', [{property: 'display', value: 'flex'}]) 55 | ) 56 | .toMatchObject({ 57 | flexDirection: 'column', 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/marginHorizontal.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import marginHorizontal from 3 | '../../../../../app/API/StyleSheet/Properties/Property/marginHorizontal'; 4 | 5 | describe('API / StyleSheet / Properties / marginHorizontal', () => { 6 | test('it should be an object', () => { 7 | expect(marginHorizontal).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(marginHorizontal).toMatchObject({ 12 | name: 'marginHorizontal', 13 | }); 14 | }); 15 | 16 | test('it should not have a mobile function', () => { 17 | expect(marginHorizontal.mobile).toBeUndefined(); 18 | }); 19 | 20 | test('it should have a desktop function', () => { 21 | expect(marginHorizontal.desktop).toBeInstanceOf(Function); 22 | }); 23 | 24 | test('it should return the right values', () => { 25 | expect(marginHorizontal.desktop(10)).toEqual({ 26 | marginLeft: 10, 27 | marginRight: 10, 28 | }); 29 | }); 30 | 31 | test('it should have a web function', () => { 32 | expect(marginHorizontal.web).toBeInstanceOf(Function); 33 | }); 34 | 35 | test('it should return the right values', () => { 36 | expect(marginHorizontal.web(10)).toEqual({ 37 | marginLeft: 10, 38 | marginRight: 10, 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/marginVertical.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import marginVertical from 3 | '../../../../../app/API/StyleSheet/Properties/Property/marginVertical'; 4 | 5 | describe('API / StyleSheet / Properties / marginVertical', () => { 6 | test('it should be an object', () => { 7 | expect(marginVertical).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(marginVertical).toMatchObject({ 12 | name: 'marginVertical', 13 | }); 14 | }); 15 | 16 | test('it should not have a mobile function', () => { 17 | expect(marginVertical.mobile).toBeUndefined(); 18 | }); 19 | 20 | test('it should have a desktop function', () => { 21 | expect(marginVertical.desktop).toBeInstanceOf(Function); 22 | }); 23 | 24 | test('it should return the right values', () => { 25 | expect(marginVertical.desktop(10)).toEqual({ 26 | marginTop: 10, 27 | marginBottom: 10, 28 | }); 29 | }); 30 | 31 | test('it should have a web function', () => { 32 | expect(marginVertical.web).toBeInstanceOf(Function); 33 | }); 34 | 35 | test('it should return the right values', () => { 36 | expect(marginVertical.web(10)).toEqual({ 37 | marginTop: 10, 38 | marginBottom: 10, 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/paddingHorizontal.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import paddingHorizontal from 3 | '../../../../../app/API/StyleSheet/Properties/Property/paddingHorizontal'; 4 | 5 | describe('API / StyleSheet / Properties / paddingHorizontal', () => { 6 | test('it should be an object', () => { 7 | expect(paddingHorizontal).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(paddingHorizontal).toMatchObject({ 12 | name: 'paddingHorizontal', 13 | }); 14 | }); 15 | 16 | test('it should not have a mobile function', () => { 17 | expect(paddingHorizontal.mobile).toBeUndefined(); 18 | }); 19 | 20 | test('it should have a desktop function', () => { 21 | expect(paddingHorizontal.desktop).toBeInstanceOf(Function); 22 | }); 23 | 24 | test('it should return the right values', () => { 25 | expect(paddingHorizontal.desktop(10)).toEqual({ 26 | paddingLeft: 10, 27 | paddingRight: 10, 28 | }); 29 | }); 30 | 31 | test('it should have a web function', () => { 32 | expect(paddingHorizontal.web).toBeInstanceOf(Function); 33 | }); 34 | 35 | test('it should return the right values', () => { 36 | expect(paddingHorizontal.web(10)).toEqual({ 37 | paddingLeft: 10, 38 | paddingRight: 10, 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/paddingVertical.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import paddingVertical from 3 | '../../../../../app/API/StyleSheet/Properties/Property/paddingVertical'; 4 | 5 | describe('API / StyleSheet / Properties / paddingVertical', () => { 6 | test('it should be an object', () => { 7 | expect(paddingVertical).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(paddingVertical).toMatchObject({ 12 | name: 'paddingVertical', 13 | }); 14 | }); 15 | 16 | test('it should not have a mobile function', () => { 17 | expect(paddingVertical.mobile).toBeUndefined(); 18 | }); 19 | 20 | test('it should have a desktop function', () => { 21 | expect(paddingVertical.desktop).toBeInstanceOf(Function); 22 | }); 23 | 24 | test('it should return the right values', () => { 25 | expect(paddingVertical.desktop(10)).toEqual({ 26 | paddingTop: 10, 27 | paddingBottom: 10, 28 | }); 29 | }); 30 | 31 | test('it should have a web function', () => { 32 | expect(paddingVertical.web).toBeInstanceOf(Function); 33 | }); 34 | 35 | test('it should return the right values', () => { 36 | expect(paddingVertical.web(10)).toEqual({ 37 | paddingTop: 10, 38 | paddingBottom: 10, 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/resizeMode.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import resizeMode from 3 | '../../../../../app/API/StyleSheet/Properties/Property/resizeMode'; 4 | 5 | describe('API / StyleSheet / Properties / resizeMode', () => { 6 | test('it should be an object', () => { 7 | expect(resizeMode).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(resizeMode).toMatchObject({ 12 | name: 'resizeMode', 13 | }); 14 | }); 15 | 16 | test('it should not have a mobile function', () => { 17 | expect(resizeMode.mobile).toBeUndefined(); 18 | }); 19 | 20 | test('it should have a desktop function', () => { 21 | expect(resizeMode.desktop).toBeInstanceOf(Function); 22 | }); 23 | 24 | test('it should return object-fit cover', () => { 25 | expect(resizeMode.desktop('cover')).toEqual({ 26 | objectFit: 'cover', 27 | }); 28 | }); 29 | 30 | test('it should return object-fit contain', () => { 31 | expect(resizeMode.desktop('contain')).toEqual({ 32 | objectFit: 'contain', 33 | }); 34 | }); 35 | 36 | test('it should return object-fit fill', () => { 37 | expect(resizeMode.desktop('stretch')).toEqual({ 38 | objectFit: 'fill', 39 | }); 40 | }); 41 | 42 | test('it should have a desktop function', () => { 43 | expect(resizeMode.desktop).toBeInstanceOf(Function); 44 | }); 45 | 46 | test('it should return object-fit cover', () => { 47 | expect(resizeMode.desktop('cover')).toEqual({ 48 | objectFit: 'cover', 49 | }); 50 | }); 51 | 52 | test('it should return object-fit contain', () => { 53 | expect(resizeMode.desktop('contain')).toEqual({ 54 | objectFit: 'contain', 55 | }); 56 | }); 57 | 58 | test('it should return object-fit fill', () => { 59 | expect(resizeMode.desktop('stretch')).toEqual({ 60 | objectFit: 'fill', 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /__tests__/API/StyleSheet/Properties/Property/transform.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import transform from 3 | '../../../../../app/API/StyleSheet/Properties/Property/transform'; 4 | 5 | describe('API / StyleSheet / Properties / transform', () => { 6 | test('it should be an object', () => { 7 | expect(transform).toBeInstanceOf(Object); 8 | }); 9 | 10 | test('it should have the right name', () => { 11 | expect(transform).toMatchObject({ 12 | name: 'transform', 13 | }); 14 | }); 15 | 16 | test('it should not have a mobile function', () => { 17 | expect(transform.mobile).toBeUndefined(); 18 | }); 19 | 20 | test('it should have a desktop function', () => { 21 | expect(transform.desktop).toBeInstanceOf(Function); 22 | }); 23 | 24 | test('it should return string to translate', () => { 25 | expect(transform.desktop([{translate: 100}])).toEqual({ 26 | transform: 'translate(100)', 27 | }); 28 | }); 29 | 30 | test('it should return string to translateX', () => { 31 | expect(transform.desktop([{translateX: 100}])).toEqual({ 32 | transform: 'translateX(100)', 33 | }); 34 | }); 35 | 36 | test('it should return string to translateY', () => { 37 | expect(transform.desktop([{translateY: 100}])).toEqual({ 38 | transform: 'translateY(100)', 39 | }); 40 | }); 41 | 42 | test('it should return string to scale', () => { 43 | expect(transform.desktop([{scale: 100}])).toEqual({ 44 | transform: 'scale(100)', 45 | }); 46 | }); 47 | 48 | test('it should return string to scaleX', () => { 49 | expect(transform.desktop([{scaleX: 100}])).toEqual({ 50 | transform: 'scaleX(100)', 51 | }); 52 | }); 53 | 54 | test('it should return string to scaleY', () => { 55 | expect(transform.desktop([{scaleY: 100}])).toEqual({ 56 | transform: 'scaleY(100)', 57 | }); 58 | }); 59 | 60 | test('it should return string to rotate', () => { 61 | expect(transform.desktop([{rotate: '0.5turn'}])).toEqual({ 62 | transform: 'rotate(0.5turn)', 63 | }); 64 | }); 65 | 66 | test('it should return string to skew', () => { 67 | expect(transform.desktop([{skew: '30deg, 20deg'}])).toEqual({ 68 | transform: 'skew(30deg, 20deg)', 69 | }); 70 | }); 71 | 72 | test('it should return string to skewX', () => { 73 | expect(transform.desktop([{skewX: '30deg'}])).toEqual({ 74 | transform: 'skewX(30deg)', 75 | }); 76 | }); 77 | 78 | test('it should return string to skewY', () => { 79 | expect(transform.desktop([{skewY: '30deg'}])).toEqual({ 80 | transform: 'skewY(30deg)', 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /__tests__/Component/Image/desktop.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import ReactorsViewDOM from '../../../app/Component/View/DOM'; 5 | import Reactors from '../../../app/API/Core'; 6 | 7 | describe('Component / View / Desktop', () => { 8 | test('it should return a section', () => { 9 | Reactors.platform = 'desktop'; 10 | const view = shallow( 11 | 12 | ); 13 | expect(view.find('section')).toHaveLength(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /__tests__/Component/Image/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import ReactorsImage from '../../../app/Component/Image'; 5 | import Reactors from '../../../app/API/Core'; 6 | 7 | describe('Component / Image', () => { 8 | // WEB 9 | 10 | test('it should return DOM Image for web', () => { 11 | Reactors.platform = 'web'; 12 | const view = shallow( 13 | 14 | ); 15 | expect(view.find('ReactorsImageDOM')).toHaveLength(1); 16 | }); 17 | 18 | // DESKTOP 19 | 20 | test('it should return DOM Image for desktop', () => { 21 | Reactors.platform = 'desktop'; 22 | const view = shallow( 23 | 24 | ); 25 | expect(view.find('ReactorsImageDesktop')).toHaveLength(1); 26 | }); 27 | 28 | // MOBILE 29 | 30 | test('it should return Mobile Image for mobile', () => { 31 | Reactors.platform = 'mobile'; 32 | const view = shallow( 33 | 34 | ); 35 | expect(view.find('ReactorsImageMobile')).toHaveLength(1); 36 | }); 37 | 38 | // NODE 39 | 40 | test('it should return DOM Image for node', () => { 41 | Reactors.platform = 'node'; 42 | const view = shallow( 43 | 44 | ); 45 | expect(view.find('ReactorsImageDOM')).toHaveLength(1); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /__tests__/Component/Image/mobile.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import {Image} from 'react-native'; 5 | import ReactorsImageMobile from '../../../app/Component/Image/Mobile'; 6 | import Reactors from '../../../app/API/Core'; 7 | 8 | describe('Component / Image / Mobile', () => { 9 | test('it should return a Image', () => { 10 | Reactors.platform = 'mobile'; 11 | const view = shallow( 12 | 13 | ); 14 | expect(view.type()).toEqual(Image); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /__tests__/Component/Image/web.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import ReactorsImageDOM from '../../../app/Component/Image/DOM'; 5 | import Reactors from '../../../app/API/Core'; 6 | 7 | describe('Component / Image / Web', () => { 8 | test('it should return a section', () => { 9 | Reactors.platform = 'web'; 10 | const view = shallow( 11 | 12 | ); 13 | expect(view.find('img')).toHaveLength(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /__tests__/Component/View/desktop.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import ReactorsViewDOM from '../../../app/Component/View/DOM'; 5 | import Reactors from '../../../app/API/Core'; 6 | 7 | describe('Component / View / Desktop', () => { 8 | test('it should return a section', () => { 9 | Reactors.platform = 'desktop'; 10 | const view = shallow( 11 | 12 | ); 13 | expect(view.find('section')).toHaveLength(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /__tests__/Component/View/index.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import ReactorsView from '../../../app/Component/View'; 5 | import Reactors from '../../../app/API/Core'; 6 | 7 | describe('Component / View', () => { 8 | // WEB 9 | 10 | test('it should return DOM View for web', () => { 11 | Reactors.platform = 'web'; 12 | const view = shallow( 13 | 14 | ); 15 | expect(view.find('ReactorsViewDOM')).toHaveLength(1); 16 | }); 17 | 18 | // DESKTOP 19 | 20 | test('it should return DOM View for desktop', () => { 21 | Reactors.platform = 'desktop'; 22 | const view = shallow( 23 | 24 | ); 25 | expect(view.find('ReactorsViewDOM')).toHaveLength(1); 26 | }); 27 | 28 | // MOBILE 29 | 30 | test('it should return Mobile View for mobile', () => { 31 | Reactors.platform = 'mobile'; 32 | const view = shallow( 33 | 34 | ); 35 | expect(view.find('ReactorsViewMobile')).toHaveLength(1); 36 | }); 37 | 38 | // NODE 39 | 40 | test('it should return DOM View for node', () => { 41 | Reactors.platform = 'node'; 42 | const view = shallow( 43 | 44 | ); 45 | expect(view.find('ReactorsViewDOM')).toHaveLength(1); 46 | }); 47 | 48 | // GENERAL 49 | 50 | test('it should have a measure', () => { 51 | const view = new ReactorsView({props: {}}); 52 | expect(view.measure).toBeInstanceOf(Function); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /__tests__/Component/View/mobile.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import {ScrollView, View} from 'react-native'; 5 | import ReactorsViewMobile from '../../../app/Component/View/Mobile'; 6 | import Reactors from '../../../app/API/Core'; 7 | 8 | describe('Component / View / Mobile', () => { 9 | test('it should return a View', () => { 10 | Reactors.platform = 'mobile'; 11 | const view = shallow( 12 | 13 | ); 14 | expect(view.type()).toEqual(View); 15 | }); 16 | 17 | test('it should return a ScrollView if scrollable', () => { 18 | const view = shallow( 19 | 20 | ); 21 | expect(view.type()).toEqual(ScrollView); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/Component/View/web.js: -------------------------------------------------------------------------------- 1 | /* globals describe expect test */ 2 | import React from 'react'; 3 | import {shallow} from 'enzyme'; 4 | import ReactorsViewDOM from '../../../app/Component/View/DOM'; 5 | import Reactors from '../../../app/API/Core'; 6 | 7 | describe('Component / View / Web', () => { 8 | test('it should return a section', () => { 9 | Reactors.platform = 'web'; 10 | const view = shallow( 11 | 12 | ); 13 | expect(view.find('section')).toHaveLength(1); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /_test_/API/Core/index.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Describe} from '@francoisv/describe-react'; 3 | 4 | export default () => ( 5 | 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /_test_/API/Core/props.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Describe, Expect, Run} from '@francoisv/describe-react'; 3 | 4 | import props from '../../../dist/API/Core/props'; 5 | import Reactors from '../../../dist/API/Core'; 6 | 7 | const setPlatformToMobile = () => { 8 | Reactors.platform = 'mobile'; 9 | }; 10 | 11 | const setPlatformToWeb = () => { 12 | Reactors.platform = 'web'; 13 | }; 14 | 15 | const style = styleProps => props({style: styleProps}); 16 | 17 | const onPress = () => {}; 18 | 19 | const testStylesMobile = [ 20 | { 21 | name: 'Border shorthand', 22 | in: {border: '1px solid #000'}, 23 | out: { 24 | borderWidth: 1, 25 | borderStyle: 'solid', 26 | borderColor: '#000', 27 | }, 28 | }, 29 | { 30 | name: 'Box Shadow', 31 | in: {boxShadow: '0 4px 4px 1px rgba(0, 0, 0, .2)'}, 32 | out: { 33 | shadowColor: 'rgba(0, 0, 0, .2)', 34 | shadowOffset: { 35 | width: 0, 36 | height: 4, 37 | }, 38 | shadowOpacity: 0.2, 39 | shadowRadius: 4, 40 | }, 41 | }, 42 | { 43 | name: 'Box Shadow', 44 | in: {boxShadow: '60px -16px teal'}, 45 | out: { 46 | shadowColor: 'teal', 47 | shadowOffset: { 48 | width: 60, 49 | height: -16, 50 | }, 51 | shadowOpacity: 1, 52 | }, 53 | }, 54 | { 55 | name: 'Box Shadow', 56 | in: {boxShadow: '10px 5px 5px black'}, 57 | out: { 58 | shadowColor: 'black', 59 | shadowOffset: { 60 | width: 10, 61 | height: 5, 62 | }, 63 | shadowOpacity: 1, 64 | shadowRadius: 5, 65 | }, 66 | }, 67 | { 68 | name: 'Box Shadow', 69 | in: {boxShadow: '2px 2px 2px 1px rgba(0, 0, 0, 0.2)'}, 70 | out: { 71 | shadowColor: 'rgba(0, 0, 0, 0.2)', 72 | shadowOffset: { 73 | width: 2, 74 | height: 2, 75 | }, 76 | shadowOpacity: 0.2, 77 | shadowRadius: 2, 78 | }, 79 | }, 80 | { 81 | name: 'Box Shadow(s)', 82 | in: {boxShadow: '3px 3px red, -1em 0 0.4em olive'}, 83 | out: { 84 | shadowColor: 'red', 85 | shadowOffset: { 86 | width: 3, 87 | height: 3, 88 | }, 89 | shadowOpacity: 1, 90 | }, 91 | }, 92 | { 93 | name: 'Box Shadow inset', 94 | in: {boxShadow: 'inset 5em 1em gold'}, 95 | out: { 96 | shadowColor: 'gold', 97 | shadowOffset: { 98 | width: -5, 99 | height: -1, 100 | }, 101 | shadowOpacity: 1, 102 | }, 103 | }, 104 | { 105 | name: 'Cursor', 106 | in: {cursor: 'pointer'}, 107 | out: {}, 108 | }, 109 | { 110 | name: 'Flex display with no direction', 111 | in: {display: 'flex'}, 112 | out: {flexDirection: 'row'}, 113 | }, 114 | { 115 | name: 'Flex display with column', 116 | in: {display: 'flex', flexDirection: 'column'}, 117 | out: {flexDirection: 'column'}, 118 | }, 119 | { 120 | name: 'Flex display with row', 121 | in: {display: 'flex', flexDirection: 'row'}, 122 | out: {flexDirection: 'row'}, 123 | }, 124 | { 125 | name: 'Transform matrix', 126 | in: {transform: 'matrix(1, 2, 3)'}, 127 | out: {transform: []}, 128 | }, 129 | { 130 | name: 'Transform translate', 131 | in: {transform: 'translate(120px, 50%)'}, 132 | out: {transform: [{translateX: 120}, {translateY: '50%'}]}, 133 | }, 134 | { 135 | name: 'Transform translate X', 136 | in: {transform: 'translateX(120px)'}, 137 | out: {transform: [{translateX: 120}]}, 138 | }, 139 | { 140 | name: 'Transform translate Y', 141 | in: {transform: 'translateY(120px)'}, 142 | out: {transform: [{translateY: 120}]}, 143 | }, 144 | { 145 | name: 'Remove transition', 146 | in: {transition: 'margin 1s'}, 147 | out: {}, 148 | }, 149 | ]; 150 | 151 | export default () => ( 152 | 153 | 154 | 155 | 156 | props({foo: 1})} return={{foo: 1}} /> 157 | 158 | 159 | 160 | 161 | 162 | 163 | props({onPress})} 165 | return={{onClick: onPress}} 166 | /> 167 | 168 | 169 | 170 | 171 | 172 | 173 | style([{margin: 10}, {padding: 5}, {padding: 4}])} 175 | return={{style: {margin: 10, padding: 4}}} 176 | /> 177 | 178 | 179 | 180 | 181 | 182 | {testStylesMobile.map(test => ( 183 | ${JSON.stringify(test.out)}` 187 | } 188 | > 189 | style(test.in)} 191 | return={{style: test.out}} 192 | /> 193 | 194 | ))} 195 | 196 | 197 | style({transform: 'perspective(1)'})} 199 | return={{style: {transform: [{perspective: 1}]}}} 200 | /> 201 | 202 | 203 | 204 | style({transform: 'rotate(0.5turn)'})} 206 | return={{style: {transform: [{rotate: '0.5turn'}]}}} 207 | /> 208 | 209 | 210 | 211 | style({transform: 'rotateX(0.5turn)'})} 213 | return={{style: {transform: [{rotateX: '0.5turn'}]}}} 214 | /> 215 | 216 | 217 | 218 | style({transform: 'rotateY(0.5turn)'})} 220 | return={{style: {transform: [{rotateY: '0.5turn'}]}}} 221 | /> 222 | 223 | 224 | 225 | style({transform: 'rotateZ(0.5turn)'})} 227 | return={{style: {transform: [{rotateZ: '0.5turn'}]}}} 228 | /> 229 | 230 | 231 | 232 | style({transform: 'scale(2, 0.5)'})} 234 | return={{style: {transform: [{scaleX: 2}, {scaleY: 0.5}]}}} 235 | /> 236 | 237 | 238 | 239 | style({transform: 'scaleX(2)'})} 241 | return={{style: {transform: [{scaleX: 2}]}}} 242 | /> 243 | 244 | 245 | 246 | style({transform: 'scaleY(2)'})} 248 | return={{style: {transform: [{scaleY: 2}]}}} 249 | /> 250 | 251 | 252 | 253 | style({transform: 'skew(30deg, 20deg)'})} 255 | return={{style: {transform: [{skewX: '30deg'}, {skewY: '20deg'}]}}} 256 | /> 257 | 258 | 259 | 260 | style({transform: 'skewX(20deg)'})} 262 | return={{style: {transform: [{skewX: '20deg'}]}}} 263 | /> 264 | 265 | 266 | 267 | style({transform: 'skewY(30deg)'})} 269 | return={{style: {transform: [{skewY: '30deg'}]}}} 270 | /> 271 | 272 | 273 | 274 | 275 | 276 | 277 | style({borderWidth: 1})} 279 | return={{style: {borderWidth: 1, borderStyle: 'solid', borderColor: 'black'}}} 280 | /> 281 | 282 | 283 | 284 | style({marginHorizontal: 10})} 286 | return={{style: {marginLeft: 10, marginRight: 10}}} 287 | /> 288 | 289 | 290 | 291 | style({marginVertical: 10})} 293 | return={{style: {marginTop: 10, marginBottom: 10}}} 294 | /> 295 | 296 | 297 | 298 | style({resizeMode: 'cover'})} 300 | return={{style: {objectFit: 'cover'}}} 301 | /> 302 | 303 | 304 | 305 | style({resizeMode: 'contain'})} 307 | return={{style: {objectFit: 'contain'}}} 308 | /> 309 | 310 | 311 | 312 | style({resizeMode: 'stretch'})} 314 | return={{style: {objectFit: 'fill'}}} 315 | /> 316 | 317 | 318 | 319 | style({transform: [{rotate: '20deg'}, {translateX: 120}]})} 321 | return={{style: {transform: 'rotate(20deg) translateX(120px)'}}} 322 | /> 323 | 324 | 325 | 326 | style({flexDirection: 'row'})} 328 | return={{style: {flexDirection: 'row', display: 'flex'}}} 329 | /> 330 | 331 | 332 | 333 | 334 | ); 335 | -------------------------------------------------------------------------------- /_test_/API/platform.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/co2-git/reactors/d188c6e85307f2c8ba2e458a94bd2c340725fc16/_test_/API/platform.spec.js -------------------------------------------------------------------------------- /app/API/Accessibility/index.js: -------------------------------------------------------------------------------- 1 | import Reactors from '../Core'; 2 | 3 | export const transformProps = (props = {}) => { 4 | const {platform} = Reactors; 5 | const mutatedProps = {}; 6 | for (const prop in props) { 7 | switch (prop) { 8 | default: 9 | mutatedProps[prop] = props[prop]; 10 | break; 11 | case 'aria-labelledby': 12 | switch (platform) { 13 | default: 14 | mutatedProps[prop] = props[prop]; 15 | break; 16 | case 'mobile': 17 | mutatedProps.accessibilityLabel = props[prop]; 18 | break; 19 | } 20 | break; 21 | case 'accessibilityLabel': 22 | switch (platform) { 23 | default: 24 | mutatedProps[prop] = props[prop]; 25 | break; 26 | case 'web': 27 | case 'desktop': 28 | mutatedProps['aria-labelledby'] = props[prop]; 29 | break; 30 | } 31 | break; 32 | } 33 | } 34 | return {...props, ...mutatedProps}; 35 | }; 36 | -------------------------------------------------------------------------------- /app/API/Animated/Value/dom.js: -------------------------------------------------------------------------------- 1 | export default class Value { 2 | constructor(value) { 3 | this._value = value; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/API/Animated/View/dom.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react'; 2 | import first from 'lodash/first'; 3 | import keys from 'lodash/keys'; 4 | import uniq from 'lodash/uniq'; 5 | 6 | import Reactors from '../../Core'; 7 | import StyleSheet from '../../StyleSheet'; 8 | import View from '../../../Component/View'; 9 | import Value from '../Value/dom'; 10 | import timing from '../timing/dom'; 11 | 12 | const parse = (styles, setState, getState) => { 13 | const style = StyleSheet.merge(styles); 14 | const parsed = {}; 15 | const transitions = []; 16 | 17 | for (const key in style) { 18 | if (style[key] instanceof Value) { 19 | style[key].change = ({value}) => { 20 | const nextState = { 21 | ...getState(), 22 | [key]: value, 23 | }; 24 | return setState(nextState); 25 | }; 26 | parsed[key] = style[key]._value; 27 | transitions.push('key'); 28 | } else if (key === 'transform' && Array.isArray(style.transform)) { 29 | parsed.transform = []; 30 | let index = 0; 31 | for (const transformer of style.transform) { 32 | const transformerName = first(keys(transformer)); 33 | const transformerValue = transformer[transformerName]; 34 | if (transformerValue instanceof Value) { 35 | styles.transform[index][transformerName].change = ({value}) => setState({ 36 | ...getState(), 37 | transform: getState().transform.map(item => { 38 | const itemKey = first(keys(item)); 39 | if (itemKey === transformerName) { 40 | return {[itemKey]: value}; 41 | } 42 | return item; 43 | }), 44 | }); 45 | parsed.transform.push({[transformerName]: transformerValue._value}); 46 | transitions.push('transform'); 47 | } else { 48 | parsed.transform.push({[transformerName]: transformerValue}); 49 | } 50 | index++; 51 | } 52 | } else { 53 | parsed[key] = style[key]; 54 | } 55 | } 56 | if (keys(transitions).length) { 57 | parsed.transition = uniq(transitions).map(transition => `${transition} 1s`).join(', '); 58 | } 59 | return StyleSheet.merge(parsed); 60 | }; 61 | 62 | export default class ReactorsAnimatedViewDOM extends PureComponent { 63 | static Value = Value; 64 | static timing = timing; 65 | state = {stateStyle: {}}; 66 | componentWillMount = () => { 67 | const stateStyle = parse( 68 | this.props.style, 69 | (state, cb) => this.setState({stateStyle: state}, cb), 70 | () => this.state.stateStyle, 71 | ); 72 | this.setState({stateStyle}); 73 | }; 74 | render = () => { 75 | const props = Reactors.props(this.props); 76 | props.style = this.state.stateStyle; 77 | return ( 78 | 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/API/Animated/View/index.js: -------------------------------------------------------------------------------- 1 | import Reactors from '../../Core'; 2 | 3 | let ReactorsAnimated; 4 | 5 | if (Reactors.platform === 'mobile') { 6 | ReactorsAnimated = require('./mobile').default; 7 | } else { 8 | ReactorsAnimated = require('./dom').default; 9 | } 10 | 11 | export default ReactorsAnimated; 12 | -------------------------------------------------------------------------------- /app/API/Animated/View/mobile.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react'; 2 | import {Animated} from 'react-native'; 3 | 4 | import Reactors from '../../Core'; 5 | import View from '../../../Component/View'; 6 | 7 | export default class ReactorsAnimatedViewMobile extends PureComponent { 8 | static Value = Animated.Value; 9 | static timing = Animated.timing; 10 | render = () => ( 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /app/API/Animated/index.js: -------------------------------------------------------------------------------- 1 | export {default as default} from './View'; 2 | -------------------------------------------------------------------------------- /app/API/Animated/timing/dom.js: -------------------------------------------------------------------------------- 1 | const timing = (animatedValue, {duration = '1s', toValue}) => ({ 2 | start: () => { 3 | animatedValue.change({value: toValue, duration}); 4 | }, 5 | }); 6 | 7 | export default timing; 8 | -------------------------------------------------------------------------------- /app/API/Core/guessPlatform.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /* globals process window */ 3 | 4 | export default function guessPlatform(): $ReactorsPlatform { 5 | if ( 6 | typeof window !== 'undefined' && 7 | typeof window.document !== 'undefined' && 8 | window.document.body 9 | ) { 10 | if (window.process) { 11 | return 'desktop'; 12 | } 13 | return 'web'; 14 | } 15 | if (typeof process === 'object') { 16 | if (process.env.USER) { 17 | return 'node'; 18 | } 19 | } 20 | return 'mobile'; 21 | } 22 | -------------------------------------------------------------------------------- /app/API/Core/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import includes from 'lodash/includes'; 7 | import guessPlatform from './guessPlatform'; 8 | import props from './props'; 9 | 10 | if (typeof __DEV__ === 'undefined') { 11 | global.__DEV__ = process.env.NODE_ENV !== 'production'; 12 | } 13 | 14 | export default class Reactors { 15 | static platform = guessPlatform(); 16 | 17 | static getOS = () => { 18 | if (Reactors.platform === 'mobile') { 19 | const RN = require('react-native'); 20 | return RN.Platform; 21 | } 22 | return {OS: Reactors.platform}; 23 | }; 24 | 25 | static isAndroid = () => { 26 | if (!Reactors.isMobile()) { 27 | return false; 28 | } 29 | const RN = require('react-native'); 30 | return RN.Platform.OS === 'android'; 31 | }; 32 | 33 | static isDesktop = () => Reactors.platform === 'desktop'; 34 | 35 | static isDOM = () => includes(['desktop', 'web'], Reactors.platform); 36 | 37 | static isiOS = () => { 38 | if (!Reactors.isMobile()) { 39 | return false; 40 | } 41 | const RN = require('react-native'); 42 | return RN.Platform.OS === 'ios'; 43 | }; 44 | 45 | static isMobile = () => Reactors.platform === 'mobile'; 46 | 47 | static isWeb = () => Reactors.platform === 'web'; 48 | 49 | static props = props; 50 | } 51 | 52 | // mergeStyles(...styles: any[]) { 53 | // if (this.isMobile()) { 54 | // const merged = []; 55 | // 56 | // for (const style of styles) { 57 | // if (Array.isArray(style)) { 58 | // merged.push(...style); 59 | // } else if (style && typeof style === 'object') { 60 | // // List style (from StyleSheet.create) 61 | // if (style[0]) { 62 | // merged.push(...Array.from(style)); 63 | // } else { 64 | // merged.push(style); 65 | // } 66 | // } 67 | // } 68 | // 69 | // return merged; 70 | // } 71 | // 72 | // let merged = {}; 73 | // 74 | // for (const style of styles) { 75 | // if (Array.isArray(style)) { 76 | // for (const item of style) { 77 | // merged = {...merged, item}; 78 | // } 79 | // } else if (style && typeof style === 'object') { 80 | // // List style (from StyleSheet.create) 81 | // if (style[0]) { 82 | // const arr = Array.from(style); 83 | // 84 | // for (const item of arr) { 85 | // merged = {...merged, item}; 86 | // } 87 | // } else { 88 | // merged = {...merged, ...style}; 89 | // } 90 | // } 91 | // } 92 | // 93 | // return merged; 94 | // } 95 | -------------------------------------------------------------------------------- /app/API/Core/props.js: -------------------------------------------------------------------------------- 1 | import * as accessibility from '../Accessibility'; 2 | import * as gesture from '../Gesture'; 3 | import StyleSheet from '../StyleSheet'; 4 | 5 | const makeReactorsProps = props => { 6 | let transformed = {...props}; 7 | transformed = accessibility.transformProps(transformed); 8 | transformed = gesture.transformProps(transformed); 9 | if (props.style) { 10 | transformed.style = StyleSheet.transform(props.style); 11 | } 12 | return transformed; 13 | }; 14 | 15 | export default makeReactorsProps; 16 | -------------------------------------------------------------------------------- /app/API/Dimensions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | /* globals CustomEvent requestAnimationFrame window */ 6 | import Reactors from '../Core'; 7 | 8 | export default class Dimensions { 9 | static __onResize = []; 10 | 11 | static resize() { 12 | const throttle = (type, name, obj = window) => { 13 | let running = false; 14 | const listener = () => { 15 | if (!running) { 16 | running = true; 17 | requestAnimationFrame(() => { 18 | obj.dispatchEvent(new CustomEvent(name)); 19 | running = false; 20 | }); 21 | } 22 | }; 23 | obj.addEventListener(type, listener); 24 | }; 25 | 26 | throttle('resize', 'optimizedResize'); 27 | 28 | window.addEventListener('optimizedResize', () => { 29 | for (const onResizeListener of this.__onResize) { 30 | onResizeListener(window.innerWidth, window.innerHeight); 31 | } 32 | }); 33 | } 34 | 35 | static get() { 36 | if (Reactors.platform === 'mobile') { 37 | const ReactNative = require('react-native'); 38 | const RN = ReactNative.default || ReactNative; 39 | const {Dimensions: RNDimensions} = RN; 40 | return RNDimensions.get('window'); 41 | } 42 | return { 43 | width: window.innerWidth, 44 | height: window.innerHeight, 45 | }; 46 | } 47 | 48 | static onResize(cb) { 49 | this.__onResize.push(cb); 50 | } 51 | } 52 | 53 | if (Reactors.isDOM()) { 54 | Dimensions.resize(); 55 | } 56 | -------------------------------------------------------------------------------- /app/API/Gesture/index.js: -------------------------------------------------------------------------------- 1 | import Reactors from '../Core'; 2 | 3 | export const transformProps = (props) => { 4 | const {platform} = Reactors; 5 | const mutatedProps = {}; 6 | for (const prop in props) { 7 | switch (prop) { 8 | default: 9 | mutatedProps[prop] = props[prop]; 10 | break; 11 | case 'onPress': 12 | switch (platform) { 13 | default: 14 | mutatedProps[prop] = props[prop]; 15 | break; 16 | case 'web': 17 | case 'desktop': 18 | mutatedProps.onClick = props[prop]; 19 | delete props.onPress; 20 | break; 21 | } 22 | break; 23 | case 'onClick': 24 | switch (platform) { 25 | default: 26 | mutatedProps[prop] = props[prop]; 27 | break; 28 | case 'mobile': 29 | mutatedProps.onPress = props[prop]; 30 | break; 31 | } 32 | break; 33 | } 34 | } 35 | return {...props, ...mutatedProps}; 36 | }; 37 | -------------------------------------------------------------------------------- /app/API/Notifications/desktop.js: -------------------------------------------------------------------------------- 1 | export default class Notifications { 2 | static push(title) { 3 | const electronNotifications = 'electron-notifications'; 4 | const notifier = require(electronNotifications); 5 | notifier.notify(title); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/API/Notifications/index.js: -------------------------------------------------------------------------------- 1 | import Reactors from 'reactors'; 2 | 3 | function notifyByPlatform() { 4 | switch (Reactors.platform) { 5 | 6 | default: { 7 | throw new Error(`Unknown platform ${Reactors.platform}`); 8 | } 9 | 10 | case 'web': 11 | case 'mobile': { 12 | return { 13 | push() { 14 | console.info('Web and mobile support coming soon'); 15 | }, 16 | }; 17 | } 18 | 19 | case 'desktop': { 20 | const desktopPath = 'desktop'; 21 | return require(`./${desktopPath}`).default; 22 | } 23 | 24 | } 25 | } 26 | 27 | export default notifyByPlatform(); 28 | -------------------------------------------------------------------------------- /app/API/Storage/index.js: -------------------------------------------------------------------------------- 1 | /* globals localStorage */ 2 | 3 | import Reactors from '../Core'; 4 | 5 | let Storage; 6 | 7 | if (Reactors.isMobile()) { 8 | Storage = require('react-native').AsyncStorage; 9 | } else { 10 | Storage = localStorage; 11 | } 12 | 13 | export default Storage; 14 | -------------------------------------------------------------------------------- /app/API/StyleSheet/index.js: -------------------------------------------------------------------------------- 1 | import compact from 'lodash/compact'; 2 | 3 | import border from './transforms/border'; 4 | import borderWidth from './transforms/borderWidth'; 5 | import boxShadow from './transforms/boxShadow'; 6 | import cursor from './transforms/cursor'; 7 | import display from './transforms/display'; 8 | import flexDirection from './transforms/flexDirection'; 9 | import marginHorizontal from './transforms/marginHorizontal'; 10 | import marginVertical from './transforms/marginVertical'; 11 | import Reactors from '../Core'; 12 | import resizeMode from './transforms/resizeMode'; 13 | import transform from './transforms/transform'; 14 | import transition from './transforms/transition'; 15 | 16 | export default class StyleSheet { 17 | static sheets = {}; 18 | 19 | static create(styles) { 20 | if (Reactors.isMobile()) { 21 | const RNSS = require('react-native').StyleSheet; 22 | const sheet = RNSS.create(styles); 23 | for (const selector in sheet) { 24 | const number = sheet[selector]; 25 | StyleSheet.sheets[number.toString()] = styles[selector]; 26 | } 27 | return sheet; 28 | } 29 | return new this(styles); 30 | } 31 | 32 | static merge = (styles) => { 33 | const array = []; 34 | if (Array.isArray(styles)) { 35 | array.push(...compact(styles)); 36 | } else { 37 | array.push(styles); 38 | } 39 | const transformed = {}; 40 | array.forEach(style => { 41 | if (typeof style === 'number') { 42 | Object.assign(transformed, StyleSheet.sheets[style.toString()]); 43 | } else { 44 | Object.assign(transformed, style); 45 | } 46 | }); 47 | return transformed; 48 | }; 49 | 50 | static transform = (styles) => { 51 | const transformers = [ 52 | border, 53 | borderWidth, 54 | boxShadow, 55 | cursor, 56 | display, 57 | flexDirection, 58 | marginHorizontal, 59 | marginVertical, 60 | resizeMode, 61 | transform, 62 | transition, 63 | ]; 64 | let transformed = StyleSheet.merge(styles); 65 | transformers.forEach(transformer => { 66 | transformed = transformer(transformed); 67 | }); 68 | return transformed; 69 | }; 70 | 71 | constructor(rules: {}) { 72 | for (const selector in rules) { 73 | this[selector] = rules[selector]; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/border.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | const toMobile = (str) => { 6 | const [borderWidth, borderStyle, borderColor] = str.split(/\s+/); 7 | return { 8 | borderWidth: parseInt(borderWidth, 10), 9 | borderStyle, 10 | borderColor, 11 | }; 12 | }; 13 | 14 | 15 | const border = (style) => { 16 | if (('border' in style) && Reactors.isMobile()) { 17 | return omit({ 18 | ...style, 19 | ...toMobile(style.border), 20 | }, ['border']); 21 | } 22 | return {...style}; 23 | }; 24 | 25 | export default border; 26 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/borderWidth.js: -------------------------------------------------------------------------------- 1 | import Reactors from '../../Core'; 2 | 3 | const borderWidth = (style) => { 4 | const transformed = {}; 5 | if (('borderWidth' in style) && Reactors.isDOM()) { 6 | if (!('borderStyle' in style)) { 7 | transformed.borderStyle = 'solid'; 8 | } 9 | if (!('borderColor' in style)) { 10 | transformed.borderColor = 'black'; 11 | } 12 | } 13 | return { 14 | ...style, 15 | ...transformed, 16 | }; 17 | }; 18 | 19 | export default borderWidth; 20 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/boxShadow.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | // boxShadow: '0 4px 4px 1px rgba(0, 0, 0, .2)', 6 | // 7 | // /* offset-x | offset-y | color */ 8 | // box-shadow: 60px -16px teal; 9 | // 10 | // /* offset-x | offset-y | blur-radius | color */ 11 | // box-shadow: 10px 5px 5px black; 12 | // 13 | // /* offset-x | offset-y | blur-radius | spread-radius | color */ 14 | // box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2); 15 | // 16 | // /* inset | offset-x | offset-y | color */ 17 | // box-shadow: inset 5em 1em gold; 18 | // 19 | // /* Any number of shadows, separated by commas */ 20 | // box-shadow: 3px 3px red, -1em 0 0.4em olive; 21 | 22 | const toMobile = (str) => { 23 | const bits1 = str.split(/\s+/); 24 | const bits = []; 25 | for (let i = 0; i < bits1.length; i++) { 26 | if (/^rgba\(/.test(bits1[i])) { 27 | bits.push(`${bits1[i]} ${bits1[i + 1]} ${bits1[i + 2]} ${bits1[i + 3]}`); 28 | i += 3; 29 | } else { 30 | bits.push(bits1[i]); 31 | } 32 | } 33 | let shadowIndex = 0; 34 | const shadows = []; 35 | for (const bit of bits) { 36 | if (!shadows[shadowIndex]) { 37 | shadows[shadowIndex] = []; 38 | } 39 | if (/,$/.test(bit)) { 40 | shadows[shadowIndex].push(bit.replace(/,$/, '')); 41 | shadowIndex++; 42 | } else { 43 | shadows[shadowIndex].push(bit); 44 | } 45 | } 46 | const all = shadows.map(shadow => { 47 | const inset = shadow[0] === 'inset'; 48 | if (inset) { 49 | shadow.shift(); 50 | } 51 | const color = shadow.pop(); 52 | const [offsetX, offsetY, radius] = shadow; 53 | let opacity = 1; 54 | if (/rgba/.test(color)) { 55 | color.replace(/, ([^,]+)\)$/, (matches, alpha) => { 56 | opacity = alpha.replace(/^\./, '0.'); 57 | }); 58 | } 59 | return {offsetX, offsetY, radius, color, opacity, inset}; 60 | }); 61 | const [rule] = all; 62 | const native = { 63 | shadowColor: rule.color, 64 | shadowOpacity: Number(rule.opacity), 65 | }; 66 | if (rule.radius) { 67 | native.shadowRadius = parseInt(rule.radius, 10); 68 | } 69 | if (rule.inset) { 70 | native.shadowOffset = { 71 | width: -(parseInt(rule.offsetX, 10)), 72 | height: -(parseInt(rule.offsetY, 10)), 73 | }; 74 | } else { 75 | native.shadowOffset = { 76 | width: parseInt(rule.offsetX, 10), 77 | height: parseInt(rule.offsetY, 10), 78 | }; 79 | } 80 | return native; 81 | }; 82 | 83 | 84 | const boxShadow = (style) => { 85 | if (('boxShadow' in style) && Reactors.isMobile()) { 86 | return omit({ 87 | ...style, 88 | ...toMobile(style.boxShadow), 89 | }, ['boxShadow']); 90 | } 91 | return {...style}; 92 | }; 93 | 94 | export default boxShadow; 95 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/cursor.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | const cursor = (style) => { 6 | if (Reactors.isMobile()) { 7 | return omit(style, ['cursor']); 8 | } 9 | return style; 10 | }; 11 | 12 | export default cursor; 13 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/display.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | const cursor = (style) => { 6 | if (Reactors.isMobile() && style.display === 'flex') { 7 | const transformed = {}; 8 | if (!('flexDirection' in style)) { 9 | transformed.flexDirection = 'row'; 10 | } 11 | return omit({...style, ...transformed}, ['display']); 12 | } 13 | return style; 14 | }; 15 | 16 | export default cursor; 17 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/flexDirection.js: -------------------------------------------------------------------------------- 1 | import Reactors from '../../Core'; 2 | 3 | const flexDirection = (style) => { 4 | if ( 5 | Reactors.isDOM() && 6 | ('flexDirection' in style) && 7 | style.display !== 'flex' 8 | ) { 9 | const transformed = {}; 10 | transformed.display = 'flex'; 11 | return {...style, ...transformed}; 12 | } 13 | return style; 14 | }; 15 | 16 | export default flexDirection; 17 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/marginHorizontal.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | const marginHorizontal = (style) => { 6 | if (('marginHorizontal' in style) && Reactors.isDOM()) { 7 | const transformed = {}; 8 | transformed.marginLeft = style.marginHorizontal; 9 | transformed.marginRight = style.marginHorizontal; 10 | return omit({...style, ...transformed}, ['marginHorizontal']); 11 | } 12 | return style; 13 | }; 14 | 15 | export default marginHorizontal; 16 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/marginVertical.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | const marginVertical = (style) => { 6 | if (('marginVertical' in style) && Reactors.isDOM()) { 7 | const transformed = {}; 8 | transformed.marginTop = style.marginVertical; 9 | transformed.marginBottom = style.marginVertical; 10 | return omit({...style, ...transformed}, ['marginVertical']); 11 | } 12 | return style; 13 | }; 14 | 15 | export default marginVertical; 16 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/resizeMode.js: -------------------------------------------------------------------------------- 1 | import omit from 'lodash/omit'; 2 | 3 | import Reactors from '../../Core'; 4 | 5 | const resizeMode = (style) => { 6 | if (style.resizeMode && Reactors.isDOM()) { 7 | const transformed = {}; 8 | switch (style.resizeMode) { 9 | case 'cover': 10 | transformed.objectFit = 'cover'; 11 | break; 12 | case 'contain': 13 | transformed.objectFit = 'contain'; 14 | break; 15 | case 'stretch': 16 | transformed.objectFit = 'fill'; 17 | break; 18 | } 19 | return omit({...style, ...transformed}, ['resizeMode']); 20 | } 21 | return style; 22 | }; 23 | 24 | export default resizeMode; 25 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/transform.js: -------------------------------------------------------------------------------- 1 | import Reactors from '../../Core'; 2 | 3 | const translateTransform = (values) => { 4 | const bits2 = values.split(/,\s+/); 5 | let translateX = bits2[0]; 6 | if (typeof translateX === 'string' && /px/.test(translateX)) { 7 | translateX = parseInt(translateX, 10); 8 | } 9 | let translateY = bits2[1]; 10 | if (typeof translateY === 'string' && /px/.test(translateY)) { 11 | translateY = parseInt(translateY, 10); 12 | } 13 | return [{translateX}, {translateY}]; 14 | }; 15 | 16 | const translateXTransform = (values) => { 17 | let translateX = values; 18 | if (typeof translateX === 'string' && /px/.test(translateX)) { 19 | translateX = parseInt(translateX, 10); 20 | } 21 | return {translateX}; 22 | }; 23 | 24 | const perspectiveTransform = values => ({perspective: Number(values)}); 25 | 26 | const translateYTransform = (values) => { 27 | let translateY = values; 28 | if (typeof translateY === 'string' && /px/.test(translateY)) { 29 | translateY = parseInt(translateY, 10); 30 | } 31 | return {translateY}; 32 | }; 33 | 34 | const scaleTransform = (values) => { 35 | const bits2 = values.split(/,\s+/); 36 | const scaleX = Number(bits2[0]); 37 | const scaleY = Number(bits2[1]); 38 | return [{scaleX}, {scaleY}]; 39 | }; 40 | 41 | const skewTransform = (values) => { 42 | const bits2 = values.split(/,\s+/); 43 | const skewX = bits2[0]; 44 | const skewY = bits2[1]; 45 | return [{skewX}, {skewY}]; 46 | }; 47 | 48 | const transform = (style) => { 49 | if (typeof style.transform === 'string' && Reactors.isMobile()) { 50 | const transformed = {}; 51 | const transformations = style.transform.split(/\)\s+/); 52 | transformed.transform = []; 53 | transformations.forEach(transformation => { 54 | if (!/\)$/.test(transformation)) { 55 | transformation += ')'; 56 | } 57 | const bits = transformation.split(/\(/); 58 | const key = bits.shift(); 59 | const values = bits.join('').replace(/\)$/, ''); 60 | switch (key) { 61 | case 'translate': 62 | transformed.transform.push(...translateTransform(values)); 63 | break; 64 | case 'translateX': 65 | transformed.transform.push(translateXTransform(values)); 66 | break; 67 | case 'translateY': 68 | transformed.transform.push(translateYTransform(values)); 69 | break; 70 | case 'perspective': 71 | transformed.transform.push(perspectiveTransform(values)); 72 | break; 73 | case 'rotate': 74 | transformed.transform.push({rotate: values}); 75 | break; 76 | case 'rotateX': 77 | transformed.transform.push({rotateX: values}); 78 | break; 79 | case 'rotateY': 80 | transformed.transform.push({rotateY: values}); 81 | break; 82 | case 'rotateZ': 83 | transformed.transform.push({rotateZ: values}); 84 | break; 85 | case 'scale': 86 | transformed.transform.push(...scaleTransform(values)); 87 | break; 88 | case 'scaleX': 89 | transformed.transform.push({scaleX: Number(values)}); 90 | break; 91 | case 'scaleY': 92 | transformed.transform.push({scaleY: Number(values)}); 93 | break; 94 | case 'skew': 95 | transformed.transform.push(...skewTransform(values)); 96 | break; 97 | case 'skewX': 98 | transformed.transform.push({skewX: (values)}); 99 | break; 100 | case 'skewY': 101 | transformed.transform.push({skewY: values}); 102 | break; 103 | } 104 | }); 105 | return {...style, ...transformed}; 106 | } 107 | if (Array.isArray(style.transform) && Reactors.isDOM()) { 108 | const transformed = {}; 109 | transformed.transform = style.transform.map((transformation) => { 110 | for (const key in transformation) { 111 | if (typeof transformation[key] === 'number') { 112 | return `${key}(${transformation[key]}px)`; 113 | } 114 | return `${key}(${transformation[key]})`; 115 | } 116 | return ''; 117 | }).join(' '); 118 | return {...style, ...transformed}; 119 | } 120 | return style; 121 | }; 122 | 123 | export default transform; 124 | -------------------------------------------------------------------------------- /app/API/StyleSheet/transforms/transition.js: -------------------------------------------------------------------------------- 1 | /* globals __DEV__ */ 2 | import omit from 'lodash/omit'; 3 | import Reactors from '../../Core'; 4 | 5 | const transition = (style) => { 6 | if (style.transition) { 7 | if (Reactors.isMobile()) { 8 | return omit(style, ['transition']); 9 | } 10 | } 11 | return style; 12 | }; 13 | 14 | export default transition; 15 | -------------------------------------------------------------------------------- /app/Component/Image/DOM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | import React, {Component} from 'react'; 6 | 7 | export default class ReactorsImageDOM extends Component { 8 | props: $ReactorsImageProps; 9 | 10 | render() { 11 | const webProps = {...this.props}; 12 | 13 | webProps.src = webProps.source; 14 | 15 | if (typeof webProps.src === 'object' && webProps.src.uri) { 16 | webProps.src = webProps.src.uri; 17 | } 18 | 19 | delete webProps.source; 20 | 21 | return ; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/Component/Image/desktop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | import React, {Component} from 'react'; 6 | 7 | export default class ReactorsImageDesktop extends Component { 8 | 9 | render() { 10 | const webProps = {...this.props}; 11 | 12 | webProps.src = webProps.source; 13 | 14 | if (typeof webProps.src === 'object' && webProps.src.uri) { 15 | webProps.src = webProps.src.uri.replace(/^\.\./, '.'); 16 | } else if (typeof webProps.src === 'string') { 17 | webProps.src = webProps.src.replace(/^\.\./, '.'); 18 | } 19 | 20 | delete webProps.source; 21 | 22 | return ; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/Component/Image/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | import Reactors from '../../API/Core'; 8 | 9 | export default class ReactorsImage extends Component { 10 | 11 | props: $ReactorsImageProps; 12 | 13 | render() { 14 | const props = Reactors.props(this.props); 15 | 16 | switch (Reactors.platform) { 17 | 18 | default: 19 | throw new Error('Unknown platform: ' + Reactors.platform); 20 | 21 | case 'mobile': { 22 | const ImageMobile = require('./Mobile').default; 23 | return ( 24 | 25 | ); 26 | } 27 | 28 | case 'web': 29 | case 'node': { 30 | const ImageWeb = require('./DOM').default; 31 | return ( 32 | 33 | ); 34 | } 35 | 36 | case 'desktop': { 37 | const ImageDesktop = require('./Desktop').default; 38 | return ( 39 | 40 | ); 41 | } 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Component/Image/mobile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React, {Component} from 'react'; 9 | import {Image} from 'react-native'; 10 | 11 | export default class ReactorsImageMobile extends Component { 12 | props: $ReactorsImageProps; 13 | 14 | render() { 15 | return ; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Component/Link/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {PropTypes} from 'react'; 7 | // $FlowFixMe This is by design 8 | import Reactors from 'reactors'; 9 | 10 | export 11 | type $props = $reactors$Core$props & { 12 | href?: string, 13 | }; 14 | 15 | export default function Link(props: $props) { 16 | switch (Reactors.platform) { 17 | default: 18 | throw new Error('Unknown platform: ' + Reactors.platform); 19 | case 'mobile': { 20 | const LinkMobile = require('./mobile').default; 21 | return ( 22 | 23 | ); 24 | } 25 | case 'web': 26 | case 'desktop': { 27 | const LinkWeb = require('./web').default; 28 | return ( 29 | /* $FlowFixMe This is by design */ 30 | 31 | ); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Component/Link/mobile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React from 'react'; 9 | // $FlowFixMe This is by design 10 | import Reactors from 'reactors'; 11 | // $FlowFixMe This is by design 12 | import {TouchableHighlight, Linking, View} from 'react-native'; 13 | import type {$props} from '.'; 14 | 15 | export default function ReactorsMobileLink(props: $props): TouchableHighlight { 16 | const mobileProps = Reactors.props(props); 17 | return ( 18 | /* $FlowFixMe This is by design */ 19 | Linking.openURL(props.href)} 21 | underlayColor="rgba(255, 255, 255, 0)" 22 | {...mobileProps} 23 | > 24 | 25 | {props.children} 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/Component/Link/web.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React, {Element} from 'react'; 9 | // $FlowFixMe This is by design 10 | import Reactors from 'reactors'; 11 | import type {$props} from '.'; 12 | 13 | export default function ReactorsWebLink(props: $props): Element<*> { 14 | const webProps = Reactors.props(props); 15 | return ( 16 | /* $FlowFixMe This is by design */ 17 | 18 | {props.children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/Component/ListView/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | import Reactors from 'reactors'; 8 | 9 | export 10 | type $props = $reactors$Core$props & { 11 | dataSource: Array, 12 | renderRow: (data: any) => React.Element<*>, 13 | }; 14 | 15 | export default class ReactorsListView extends Component { 16 | render() { 17 | const props = Reactors.props(this.props); 18 | 19 | if (Reactors.isMobile()) { 20 | const ListViewMobile = require('./mobile').default; 21 | return ( 22 | 23 | ); 24 | } 25 | 26 | if (Reactors.isDOM()) { 27 | const ListViewWeb = require('./web').default; 28 | return ( 29 | /* $FlowFixMe This is by design */ 30 | 31 | ); 32 | } 33 | 34 | throw new Error('Unknown platform: ' + Reactors.platform); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Component/ListView/mobile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | import {ListView} from 'react-native'; 8 | 9 | export default class RectorsListViewMobile extends Component { 10 | ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 11 | 12 | state = { 13 | dataSource: this.ds.cloneWithRows(this.props.dataSource), 14 | }; 15 | 16 | componentWillUpdate(nextProps, nextState) { 17 | nextState.dataSource = this.ds.cloneWithRows(nextProps.dataSource); 18 | } 19 | 20 | render() { 21 | const props = { 22 | ...this.props, 23 | dataSource: this.state.dataSource, 24 | }; 25 | 26 | if (!('enableEmptySections' in this.props)) { 27 | props.enableEmptySections = true; 28 | } 29 | // $FlowFixMe This is by design 30 | return ; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Component/ListView/web.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | 8 | export default class ReactorsListViewDOM extends Component { 9 | render() { 10 | const items: React.Element<*>[] = this.props.dataSource.map( 11 | (item: any, index: number) => ( 12 |
  • 13 | {this.props.renderRow(item)} 14 |
  • 15 | ) 16 | ); 17 | // $FlowFixMe This is by design 18 | return
      {items}
    ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Component/ScrollView/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React, {Element} from 'react'; 9 | // $FlowFixMe This is by design 10 | import Reactors from 'reactors'; 11 | 12 | export 13 | type $props = $reactors$Core$props & {}; 14 | 15 | export default function ReactorsScrollView(props: $props): Element<*> { 16 | switch (Reactors.platform) { 17 | default: 18 | throw new Error('Unknown platform: ' + Reactors.platform); 19 | case 'mobile': { 20 | const ScrollViewMobile = require('./mobile').default; 21 | return ( 22 | 23 | ); 24 | } 25 | case 'web': 26 | case 'desktop': { 27 | const ScrollViewWeb = require('./web').default; 28 | return ( 29 | /* $FlowFixMe This is by design */ 30 | 31 | ); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Component/ScrollView/mobile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React from 'react'; 9 | // $FlowFixMe This is by design 10 | import {ScrollView} from 'react-native'; 11 | // $FlowFixMe This is by design 12 | import Reactors from 'reactors'; 13 | import type {$props} from '.'; 14 | 15 | export default function ReactorsMobileScrollView(props: $props): ScrollView { 16 | const mobileProps = Reactors.props(props); 17 | // $FlowFixMe This is by design 18 | return {props.children}; 19 | } 20 | -------------------------------------------------------------------------------- /app/Component/ScrollView/web.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React, {Element} from 'react'; 9 | // $FlowFixMe This is by design 10 | import Reactors from 'reactors'; 11 | 12 | export default 13 | function ReactorsWebScrollView (props: $reactors$Core$props): Element<*> { 14 | const webProps = Reactors.props(props); 15 | const parentStyle = { 16 | overflow: 'auto', 17 | ...webProps.style, 18 | }; 19 | delete webProps.style; 20 | return ( 21 | /* $FlowFixMe This is by design */ 22 |
    23 |
    24 | {props.children} 25 |
    26 |
    27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /app/Component/Text/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name Text 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React, {Element} from 'react'; 9 | // $FlowFixMe This is by design 10 | import Reactors from 'reactors'; 11 | 12 | export 13 | type $props = $reactors$Core$props & {}; 14 | 15 | export default function ReactorsText(props: $props): Element<*> { 16 | switch (Reactors.platform) { 17 | default: 18 | throw new Error('Unknown platform: ' + Reactors.platform); 19 | case 'mobile': { 20 | const TextMobile = require('./mobile').default; 21 | return ( 22 | 23 | ); 24 | } 25 | case 'web': 26 | case 'desktop': { 27 | const TextWeb = require('./web').default; 28 | return ( 29 | /* $FlowFixMe This is by design */ 30 | 31 | ); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Component/Text/mobile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React from 'react'; 9 | // $FlowFixMe This is by design 10 | import {Text} from 'react-native'; 11 | // $FlowFixMe This is by design 12 | import Reactors from 'reactors'; 13 | import type {$props} from '.'; 14 | 15 | export default function ReactorsMobileText(props: $props): Text { 16 | const mobileProps = Reactors.props(props); 17 | // $FlowFixMe This is by design 18 | return {props.children}; 19 | } 20 | -------------------------------------------------------------------------------- /app/Component/Text/web.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @name ScrollView 4 | * @type Component 5 | * @flow 6 | **/ 7 | 8 | import React, {Element} from 'react'; 9 | // $FlowFixMe This is by design 10 | import Reactors from 'reactors'; 11 | import type {$props} from '.'; 12 | 13 | export default function ReactorsWebText(props: $props): Element<*> { 14 | const webProps = Reactors.props(props); 15 | // $FlowFixMe This is by design 16 | return
    {props.children}
    ; 17 | } 18 | -------------------------------------------------------------------------------- /app/Component/View/DOM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | 8 | export default class ReactorsViewDOM extends Component { 9 | 10 | render() { 11 | return ( 12 |
    13 | {this.props.children} 14 |
    15 | ); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/Component/View/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | import Reactors from '../../API/Core'; 8 | 9 | export default class ReactorsView extends Component { 10 | 11 | measure(cb: $ReactorsViewMeasureCallback) { 12 | return this.refs.__internalView.measure(cb); 13 | } 14 | 15 | render() { 16 | const props = Reactors.props(this.props); 17 | 18 | switch (Reactors.platform) { 19 | 20 | default: { 21 | throw new Error(`Unknown Reactors Platform: ${Reactors.platform}`); 22 | } 23 | 24 | case 'mobile': { 25 | const ReactorsViewMobile = require('./Mobile').default; 26 | return ; 27 | } 28 | 29 | case 'web': 30 | case 'desktop': 31 | case 'node': { 32 | const ReactorsViewDOM = require('./DOM').default; 33 | return ; 34 | } 35 | 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/Component/View/mobile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module reactors 3 | * @flow 4 | **/ 5 | 6 | import React, {Component} from 'react'; 7 | import {ScrollView, View} from 'react-native'; 8 | import omit from 'lodash/omit'; 9 | 10 | export default class ReactorsViewMobile extends Component { 11 | 12 | measure(cb: $ReactorsViewMeasureCallback) { 13 | return this.refs.__internalView.measure(cb); 14 | } 15 | 16 | render() { 17 | const props = {...this.props}; 18 | 19 | if (props.onPress) { 20 | props.onStartShouldSetResponder = props.onPress; 21 | delete props.onPress; 22 | } 23 | 24 | if (this.props.scrollable) { 25 | return ( 26 | 31 | {this.props.children} 32 | 33 | ); 34 | } 35 | 36 | return ( 37 | 41 | {this.props.children} 42 | 43 | ); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | DEFAULT_BORDER_STYLE: 'solid', 3 | DEFAULT_BORDER_COLOR: 'black', 4 | }; 5 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | ** @module reactors 3 | ** @flow 4 | **/ 5 | 6 | import Reactors from './API/Core'; 7 | 8 | export {Reactors as default}; 9 | 10 | // Components 11 | export {default as Image} from './Component/Image'; 12 | export {default as Link} from './Component/Link'; 13 | export {default as ListView} from './Component/ListView'; 14 | export {default as ScrollView} from './Component/ScrollView'; 15 | export {default as Text} from './Component/Text'; 16 | export {default as View} from './Component/View'; 17 | 18 | // API 19 | export {default as Animated} from './API/Animated'; 20 | export {default as Dimensions} from './API/Dimensions'; 21 | export {default as Gesture} from './API/Gesture'; 22 | export {default as Storage} from './API/Storage'; 23 | export {default as StyleSheet} from './API/StyleSheet'; 24 | 25 | const Platform = {OS: Reactors.getOS()}; 26 | 27 | export {Platform}; 28 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6.3.0 4 | 5 | test: 6 | pre: 7 | - npm install -g react-native-cli 8 | - npm run babel 9 | -------------------------------------------------------------------------------- /doc/API/Accessibility.md: -------------------------------------------------------------------------------- 1 | Accessibility 2 | === 3 | 4 | Basic support for accessibility (**in progress**). 5 | 6 | ```javascript 7 | 10 | ``` 11 | 12 | Will be translated to `aria-labelledby` in DOM (web and desktop). 13 | -------------------------------------------------------------------------------- /doc/API/Core.md: -------------------------------------------------------------------------------- 1 | Core API 2 | === 3 | 4 | # `platform: 'mobile'|'web'|'desktop'` 5 | 6 | The name of the platform. **Please note that, at this stage of development, this returns null during init time**. It needs for example to be wrapped into a function. 7 | -------------------------------------------------------------------------------- /doc/API/Dimensions.md: -------------------------------------------------------------------------------- 1 | Dimensions 2 | === 3 | 4 | Get window's dimensions. 5 | 6 | ```javascript 7 | import {Dimensions} from 'reactors'; 8 | 9 | const {width, height} = Dimensions.get('window'); 10 | ``` 11 | 12 | # Resize 13 | 14 | For DOM-based platform, you can listen on resize events: 15 | 16 | `Dimensions.onResize((width, height) => {});` 17 | -------------------------------------------------------------------------------- /doc/API/Gesture.md: -------------------------------------------------------------------------------- 1 | The Gesture API 2 | === 3 | 4 | The Gesture API exposes several gesture utilities. 5 | 6 | # Gestures 7 | 8 | - `press` a mouse press or a finger tap 9 | - `enter` button enter (or any return button from keyboard) pressed 10 | 11 | # Usage 12 | 13 | ```javascript 14 | import React from 'react'; 15 | import Reactors, {Gesture} from 'reactors'; 16 | 17 | function MyComponent(props) { 18 | return { 21 | // ... 22 | } 23 | })} /> 24 | } 25 | ``` 26 | 27 | Calling handlers with props will return these props with the handlers and remove the gestures. 28 | 29 | For example, calling `Gesture.handlers({onPress: () => {}, type: 'submit'})` on a web button will return: 30 | 31 | ```javascript 32 | { 33 | onClick: () => {}, 34 | type: 'submit', 35 | } 36 | ``` 37 | 38 | Here, `onPress` has been substituted for `onClick` and the other keys are returned as such. 39 | -------------------------------------------------------------------------------- /doc/API/Node.md: -------------------------------------------------------------------------------- 1 | reactors Node 2 | === 3 | 4 | Find the node of a `ref`. 5 | 6 | # Usage 7 | 8 | ```javascript 9 | import {Node} from 'reactors'; 10 | 11 | const node = Node.find(this.refs.myRef); 12 | const {x, y, width, height} = Node.measure(node); 13 | ``` 14 | -------------------------------------------------------------------------------- /doc/API/StyleSheet.md: -------------------------------------------------------------------------------- 1 | StyleSheet 2 | === 3 | 4 | `StyleSheet` unifies style conventions so you can pass it one style and it will be converted to its matching platform. 5 | 6 | All `reactors` core components already unify style. 7 | 8 | # Stylesheet 9 | 10 | ```javascript 11 | import {StyleSheet} from 'reactors'; 12 | 13 | const styles = new StyleSheet({ 14 | text: { 15 | color: 'blue', 16 | }, 17 | }); 18 | 19 | ; 20 | ``` 21 | 22 | # Mixins 23 | 24 | ```javascript 25 | import {StyleSheet} from 'reactors'; 26 | 27 | const { 28 | Style, 29 | color, 30 | font, 31 | line, 32 | } = StyleSheet; 33 | 34 | const styles = new StyleSheet({ 35 | container: { 36 | ...new Style.Dimensions(300, 150), 37 | ...new Style.Border(2, color('red'), line('solid')), 38 | ...new Style.Margin(10), 39 | }, 40 | text: { 41 | ...new Style.Font(14, font('trebuchet'), color('orange')), 42 | ...new Style.Margin(10, 20), 43 | }, 44 | }); 45 | ``` 46 | -------------------------------------------------------------------------------- /doc/Components/Image.md: -------------------------------------------------------------------------------- 1 | Image 2 | === 3 | 4 | Displays an image. 5 | 6 | **Don't forget to add dimension to your image**. 7 | 8 | # Props 9 | 10 | ## `source` 11 | 12 | ```javascript 13 | 14 | 15 | 16 | 17 | ``` 18 | 19 | ```javascript 20 | type $source = string | number | {uri: string}; 21 | ``` 22 | -------------------------------------------------------------------------------- /doc/Components/Link.md: -------------------------------------------------------------------------------- 1 | Link 2 | === 3 | 4 | Display a link of a resource openable in device's browser. 5 | 6 | # Usage 7 | 8 | ```javascript 9 | import React, {Component} from 'react'; 10 | import {Link} from 'reactors'; 11 | 12 | export default class MyComponent extends Component { 13 | render() { 14 | return ( 15 | 16 | Go to Google 17 | 18 | ); 19 | } 20 | } 21 | ``` 22 | 23 | # Props 24 | 25 | ## `href: string` 26 | 27 | The link to open in web browser. 28 | -------------------------------------------------------------------------------- /doc/Components/ListView.md: -------------------------------------------------------------------------------- 1 | ListView 2 | === 3 | 4 | Display a dynamic, scrollable, list of data items. 5 | 6 | # Usage 7 | 8 | ```javascript 9 | import React, {Component} from 'react'; 10 | import {ListView, Text} from 'reactors'; 11 | 12 | export default class MyComponent extends Component { 13 | render() { 14 | const data = [ 15 | {title: 'Foo'}, 16 | {title: 'Bar'}, 17 | ]; 18 | 19 | return ; 23 | } 24 | 25 | renderRow = (row, section_id, row_id) => { 26 | return {row.title}; 27 | }; 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /doc/Components/ScrollView.md: -------------------------------------------------------------------------------- 1 | View 2 | === 3 | 4 | Displays a `ScrollView` element on mobile, a `section` element on web. 5 | 6 | [Doc](https://facebook.github.io/react-native/docs/scrollview.html) 7 | -------------------------------------------------------------------------------- /doc/Components/Text.md: -------------------------------------------------------------------------------- 1 | Text 2 | === 3 | 4 | Displays a `Text` element on mobile, a `span` element on web. 5 | -------------------------------------------------------------------------------- /doc/Components/View.md: -------------------------------------------------------------------------------- 1 | View 2 | === 3 | 4 | Displays a `ScrollView` element on mobile, a `section` element on web. 5 | 6 | [Doc](https://facebook.github.io/react-native/docs/view.html) 7 | 8 | # Props 9 | 10 | ## **`accessibilityLabel`** string 11 | 12 | View [Accessibility API](../API/Accessibility.md). 13 | 14 | [Mobile](https://facebook.github.io/react-native/docs/view.html#accessibilitylabel) 15 | [Web](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute) 16 | 17 | ## **`scrollable`** boolean (default `true`) 18 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | Reactors Doc 2 | === 3 | 4 | # Components 5 | 6 | - [Image](Components/Image.md) 7 | - [Link](Components/Link.md) 8 | - [ListView](Components/ListView.md) 9 | - [ScrollView](Components/ScrollView.md) 10 | - [Text](Components/Text.md) 11 | - [View](Components/View.md) 12 | 13 | # API 14 | 15 | - [Gesture](API/Gesture.md) 16 | - [StyleSheet](API/StyleSheet.md) 17 | -------------------------------------------------------------------------------- /flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React from 'react'; 4 | 5 | // CORE API 6 | 7 | declare type $reactors$platform = 'desktop' 8 | | 'mobile' 9 | | 'web' 10 | | 'node' 11 | ; 12 | 13 | declare type $reactors$Core$Event = {}; 14 | 15 | declare type $reactors$Core$props = { 16 | children?: ?$React$Children | ?$React$Children[], 17 | onPress?: {[event: $reactors$Core$Event]: any}, 18 | style?: $reactors$StyleSheet$Rule, 19 | }; 20 | 21 | // STYLESHEET API 22 | 23 | declare type $reactors$StyleSheet$Rule = { 24 | [property: string]: any, 25 | }; 26 | 27 | declare type $reactors$styleSheet = { 28 | [selector: string]: $reactors$StyleSheet$Rule, 29 | }; 30 | 31 | declare type $reactors$StyleSheet$Transformer = { 32 | [key: string]: any, 33 | }; 34 | 35 | // GESTURE API 36 | 37 | declare type $reactors$Gesture$handlers = { 38 | onPress?: {[event: $reactors$Core$Event]: boolean}, 39 | onEnter?: {[event: $reactors$Core$Event]: boolean}, 40 | }; 41 | 42 | // REACT 43 | 44 | declare type $React$Children = null | string | React.Element; 45 | -------------------------------------------------------------------------------- /flow/Image.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare type $ReactorsImageProps = { 4 | source?: string | number | {uri: string}, 5 | src?: string, 6 | }; 7 | -------------------------------------------------------------------------------- /flow/Reactors.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare type $ReactorsPlatform = 'desktop' | 'mobile' | 'node' | 'web'; 4 | 5 | declare type $ReactorsPropsTransformers = { 6 | added: {[prop: string]: any}[], 7 | removed: string[], 8 | }; 9 | -------------------------------------------------------------------------------- /flow/StyleSheet.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare type $ReactorsStyleSheetRule = { 4 | [property: string]: string | number, 5 | }; 6 | 7 | declare type $ReactorsStyleSheet = { 8 | [selector: string]: $ReactorsStyleSheetRule 9 | | $ReactorsStyleSheetRule[], 10 | }; 11 | 12 | declare type $ReactorsStyleSheetProperty = { 13 | value: any[] | Function, 14 | desktop: (value: any) => {[property: string]: any}, 15 | mobile: (value: any) => {[property: string]: any}, 16 | web: (value: any) => {[property: string]: any}, 17 | }; 18 | 19 | declare type $ReactorsStyleSheetBorderStyle = 20 | 'solid' 21 | | 'none' 22 | ; 23 | 24 | declare type $ReactorsStyleSheetFlexDirection = 25 | 'column' 26 | | 'column-reverse' 27 | | 'row' 28 | | 'row-reverse' 29 | ; 30 | 31 | declare type $ReactorsStyleSheetResizeMode = 32 | 'cover' 33 | | 'contain' 34 | | 'stretch' 35 | | 'repeat' 36 | ; 37 | 38 | declare type $ReactorsStyleSheetObjectFit = 39 | 'fill' 40 | | 'cover' 41 | | 'contain' 42 | ; 43 | 44 | declare type $ReactorsStyleSheetTransformersMobile = [ 45 | {perspective: number}, 46 | {rotate: string}, 47 | {rotateX: string}, 48 | {rotateY: string}, 49 | {rotateZ: string}, 50 | {scale: number}, 51 | {scaleX: number}, 52 | {scaleY: number}, 53 | {translateX: number}, 54 | {translateY: number}, 55 | {skewX: string}, 56 | {skewY: string} 57 | ]; 58 | 59 | declare type $ReactorsStyleSheetTransformersDOM = string; 60 | -------------------------------------------------------------------------------- /flow/View.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare type $ReactorsViewMeasureCallback = ( 4 | x: number, 5 | y: number, 6 | width: number, 7 | height: number, 8 | pageX: number, 9 | pageY: number, 10 | ) => void; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactors", 3 | "version": "4.2.18", 4 | "description": "View components and APIs that work web, mobile and desktop!", 5 | "main": "dist/index.js", 6 | "keywords": [ 7 | "reactors", 8 | "react", 9 | "react-native", 10 | "app", 11 | "mobile", 12 | "web", 13 | "android", 14 | "ios", 15 | "mac osx", 16 | "window", 17 | "linux", 18 | "ubuntu", 19 | "desktop", 20 | "electron", 21 | "native", 22 | "hybrid", 23 | "awesome" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/co2-git/reactors" 28 | }, 29 | "scripts": { 30 | "babel": "rm -rf dist; babel --out-dir dist/ app/", 31 | "babel:watch": "rm -rf dist; babel --watch --out-dir dist/ app/", 32 | "eslint": "eslint app", 33 | "flow": "flow", 34 | "prepublishOnly": "npm run eslint && yarn babel && yarn test", 35 | "prepush": "npm run eslint && npm test", 36 | "push": "npm run babel && npm run prepush && npm publish", 37 | "jest": "jest --colors --notify --verbose", 38 | "test": "rm -rf test; babel --out-dir test _test_; describe-react test/API/*/*.spec.js" 39 | }, 40 | "author": { 41 | "name": "francois", 42 | "github": "https://github.com/co2-git" 43 | }, 44 | "license": "ISC", 45 | "devDependencies": { 46 | "babel-cli": "^6.9.0", 47 | "babel-eslint": "^6.0.4", 48 | "babel-plugin-syntax-async-functions": "^6.8.0", 49 | "babel-plugin-transform-regenerator": "^6.9.0", 50 | "babel-plugin-transform-runtime": "^6.9.0", 51 | "babel-polyfill": "^6.9.1", 52 | "babel-preset-es2015": "^6.9.0", 53 | "babel-preset-react": "^6.5.0", 54 | "babel-preset-stage-0": "^6.5.0", 55 | "enzyme": "^2.7.1", 56 | "eslint": "^2.11.1", 57 | "eslint-plugin-flowtype": "^2.29.1", 58 | "eslint-plugin-react": "^5.1.1", 59 | "eslint-plugin-react-native": "^1.1.0-beta", 60 | "flow-bin": "^0.36.0", 61 | "husky": "^0.13.1", 62 | "jest": "^18.1.0", 63 | "react-addons-test-utils": "^15.4.2", 64 | "react-dom": "^15.4.2", 65 | "react-test-renderer": "^15.4.2", 66 | "should": "^11.2.0", 67 | "sinon": "^1.17.7" 68 | }, 69 | "dependencies": { 70 | "@francoisv/describe-react": "^0.1.53", 71 | "css-property-parser": "^1.0.5", 72 | "electron-notifications": "^0.1.4", 73 | "nedb": "^1.8.0" 74 | }, 75 | "jest": { 76 | "preset": "react-native" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/API/Core/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _describeReact = require('@francoisv/describe-react'); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | exports.default = function () { 16 | return _react2.default.createElement(_describeReact.Describe, { label: 'Reactors index' }); 17 | }; -------------------------------------------------------------------------------- /test/API/Core/props.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _describeReact = require('@francoisv/describe-react'); 12 | 13 | var _props = require('../../../dist/API/Core/props'); 14 | 15 | var _props2 = _interopRequireDefault(_props); 16 | 17 | var _Core = require('../../../dist/API/Core'); 18 | 19 | var _Core2 = _interopRequireDefault(_Core); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | var setPlatformToMobile = function setPlatformToMobile() { 24 | _Core2.default.platform = 'mobile'; 25 | }; 26 | 27 | var setPlatformToWeb = function setPlatformToWeb() { 28 | _Core2.default.platform = 'web'; 29 | }; 30 | 31 | var style = function style(styleProps) { 32 | return (0, _props2.default)({ style: styleProps }); 33 | }; 34 | 35 | var onPress = function onPress() {}; 36 | 37 | var testStylesMobile = [{ 38 | name: 'Border shorthand', 39 | in: { border: '1px solid #000' }, 40 | out: { 41 | borderWidth: 1, 42 | borderStyle: 'solid', 43 | borderColor: '#000' 44 | } 45 | }, { 46 | name: 'Box Shadow', 47 | in: { boxShadow: '0 4px 4px 1px rgba(0, 0, 0, .2)' }, 48 | out: { 49 | shadowColor: 'rgba(0, 0, 0, .2)', 50 | shadowOffset: { 51 | width: 0, 52 | height: 4 53 | }, 54 | shadowOpacity: 0.2, 55 | shadowRadius: 4 56 | } 57 | }, { 58 | name: 'Box Shadow', 59 | in: { boxShadow: '60px -16px teal' }, 60 | out: { 61 | shadowColor: 'teal', 62 | shadowOffset: { 63 | width: 60, 64 | height: -16 65 | }, 66 | shadowOpacity: 1 67 | } 68 | }, { 69 | name: 'Box Shadow', 70 | in: { boxShadow: '10px 5px 5px black' }, 71 | out: { 72 | shadowColor: 'black', 73 | shadowOffset: { 74 | width: 10, 75 | height: 5 76 | }, 77 | shadowOpacity: 1, 78 | shadowRadius: 5 79 | } 80 | }, { 81 | name: 'Box Shadow', 82 | in: { boxShadow: '2px 2px 2px 1px rgba(0, 0, 0, 0.2)' }, 83 | out: { 84 | shadowColor: 'rgba(0, 0, 0, 0.2)', 85 | shadowOffset: { 86 | width: 2, 87 | height: 2 88 | }, 89 | shadowOpacity: 0.2, 90 | shadowRadius: 2 91 | } 92 | }, { 93 | name: 'Box Shadow(s)', 94 | in: { boxShadow: '3px 3px red, -1em 0 0.4em olive' }, 95 | out: { 96 | shadowColor: 'red', 97 | shadowOffset: { 98 | width: 3, 99 | height: 3 100 | }, 101 | shadowOpacity: 1 102 | } 103 | }, { 104 | name: 'Box Shadow inset', 105 | in: { boxShadow: 'inset 5em 1em gold' }, 106 | out: { 107 | shadowColor: 'gold', 108 | shadowOffset: { 109 | width: -5, 110 | height: -1 111 | }, 112 | shadowOpacity: 1 113 | } 114 | }, { 115 | name: 'Cursor', 116 | in: { cursor: 'pointer' }, 117 | out: {} 118 | }, { 119 | name: 'Flex display with no direction', 120 | in: { display: 'flex' }, 121 | out: { flexDirection: 'row' } 122 | }, { 123 | name: 'Flex display with column', 124 | in: { display: 'flex', flexDirection: 'column' }, 125 | out: { flexDirection: 'column' } 126 | }, { 127 | name: 'Flex display with row', 128 | in: { display: 'flex', flexDirection: 'row' }, 129 | out: { flexDirection: 'row' } 130 | }, { 131 | name: 'Transform matrix', 132 | in: { transform: 'matrix(1, 2, 3)' }, 133 | out: { transform: [] } 134 | }, { 135 | name: 'Transform translate', 136 | in: { transform: 'translate(120px, 50%)' }, 137 | out: { transform: [{ translateX: 120 }, { translateY: '50%' }] } 138 | }, { 139 | name: 'Transform translate X', 140 | in: { transform: 'translateX(120px)' }, 141 | out: { transform: [{ translateX: 120 }] } 142 | }, { 143 | name: 'Transform translate Y', 144 | in: { transform: 'translateY(120px)' }, 145 | out: { transform: [{ translateY: 120 }] } 146 | }, { 147 | name: 'Remove transition', 148 | in: { transition: 'margin 1s' }, 149 | out: {} 150 | }]; 151 | 152 | exports.default = function () { 153 | return _react2.default.createElement( 154 | _describeReact.Describe, 155 | { label: 'Reactors props' }, 156 | _react2.default.createElement(_describeReact.Expect, { value: _props2.default, isAFunction: true }), 157 | _react2.default.createElement( 158 | _describeReact.Describe, 159 | { label: 'it should return correct props' }, 160 | _react2.default.createElement(_describeReact.Expect, { 'function': function _function() { 161 | return (0, _props2.default)({ foo: 1 }); 162 | }, 'return': { foo: 1 } }) 163 | ), 164 | _react2.default.createElement( 165 | _describeReact.Describe, 166 | { label: 'Gestures' }, 167 | _react2.default.createElement( 168 | _describeReact.Describe, 169 | { label: 'DOM' }, 170 | _react2.default.createElement(_describeReact.Run, { script: setPlatformToWeb }), 171 | _react2.default.createElement( 172 | _describeReact.Describe, 173 | { label: 'onPress should be transformed to onClick' }, 174 | _react2.default.createElement(_describeReact.Expect, { 175 | 'function': function _function() { 176 | return (0, _props2.default)({ onPress: onPress }); 177 | }, 178 | 'return': { onClick: onPress } 179 | }) 180 | ) 181 | ) 182 | ), 183 | _react2.default.createElement( 184 | _describeReact.Describe, 185 | { label: 'Styles' }, 186 | _react2.default.createElement( 187 | _describeReact.Describe, 188 | { label: 'Merge styles - accept arrays and objects' }, 189 | _react2.default.createElement(_describeReact.Expect, { 190 | 'function': function _function() { 191 | return style([{ margin: 10 }, { padding: 5 }, { padding: 4 }]); 192 | }, 193 | 'return': { style: { margin: 10, padding: 4 } } 194 | }) 195 | ), 196 | _react2.default.createElement( 197 | _describeReact.Describe, 198 | { label: 'Mobile' }, 199 | _react2.default.createElement(_describeReact.Run, { script: setPlatformToMobile }), 200 | testStylesMobile.map(function (test) { 201 | return _react2.default.createElement( 202 | _describeReact.Describe, 203 | { 204 | label: test.name + ' --- ' + (JSON.stringify(test.in) + ' ==> ' + JSON.stringify(test.out)) 205 | }, 206 | _react2.default.createElement(_describeReact.Expect, { 207 | 'function': function _function() { 208 | return style(test.in); 209 | }, 210 | 'return': { style: test.out } 211 | }) 212 | ); 213 | }), 214 | _react2.default.createElement( 215 | _describeReact.Describe, 216 | { label: '{transform: perspective(1)} = {transform: [{perspective: 1}]}' }, 217 | _react2.default.createElement(_describeReact.Expect, { 218 | 'function': function _function() { 219 | return style({ transform: 'perspective(1)' }); 220 | }, 221 | 'return': { style: { transform: [{ perspective: 1 }] } } 222 | }) 223 | ), 224 | _react2.default.createElement( 225 | _describeReact.Describe, 226 | { label: '{transform: rotate(0.5turn)} = {transform: [{rotate: 0.5turn}]}' }, 227 | _react2.default.createElement(_describeReact.Expect, { 228 | 'function': function _function() { 229 | return style({ transform: 'rotate(0.5turn)' }); 230 | }, 231 | 'return': { style: { transform: [{ rotate: '0.5turn' }] } } 232 | }) 233 | ), 234 | _react2.default.createElement( 235 | _describeReact.Describe, 236 | { label: '{transform: rotateX(0.5turn)} = {transform: [{rotateX: 0.5turn}]}' }, 237 | _react2.default.createElement(_describeReact.Expect, { 238 | 'function': function _function() { 239 | return style({ transform: 'rotateX(0.5turn)' }); 240 | }, 241 | 'return': { style: { transform: [{ rotateX: '0.5turn' }] } } 242 | }) 243 | ), 244 | _react2.default.createElement( 245 | _describeReact.Describe, 246 | { label: '{transform: rotateY(0.5turn)} = {transform: [{rotateY: 0.5turn}]}' }, 247 | _react2.default.createElement(_describeReact.Expect, { 248 | 'function': function _function() { 249 | return style({ transform: 'rotateY(0.5turn)' }); 250 | }, 251 | 'return': { style: { transform: [{ rotateY: '0.5turn' }] } } 252 | }) 253 | ), 254 | _react2.default.createElement( 255 | _describeReact.Describe, 256 | { label: '{transform: rotateZ(0.5turn)} = {transform: [{rotateZ: 0.5turn}]}' }, 257 | _react2.default.createElement(_describeReact.Expect, { 258 | 'function': function _function() { 259 | return style({ transform: 'rotateZ(0.5turn)' }); 260 | }, 261 | 'return': { style: { transform: [{ rotateZ: '0.5turn' }] } } 262 | }) 263 | ), 264 | _react2.default.createElement( 265 | _describeReact.Describe, 266 | { label: '{transform: scale(2, 0.5} = {transform: [{scaleX: 2, scaleY: 0.5}]}' }, 267 | _react2.default.createElement(_describeReact.Expect, { 268 | 'function': function _function() { 269 | return style({ transform: 'scale(2, 0.5)' }); 270 | }, 271 | 'return': { style: { transform: [{ scaleX: 2 }, { scaleY: 0.5 }] } } 272 | }) 273 | ), 274 | _react2.default.createElement( 275 | _describeReact.Describe, 276 | { label: '{transform: scaleX(2} = {transform: [{scaleX: 2}]}' }, 277 | _react2.default.createElement(_describeReact.Expect, { 278 | 'function': function _function() { 279 | return style({ transform: 'scaleX(2)' }); 280 | }, 281 | 'return': { style: { transform: [{ scaleX: 2 }] } } 282 | }) 283 | ), 284 | _react2.default.createElement( 285 | _describeReact.Describe, 286 | { label: '{transform: scaleY(2} = {transform: [{scaleY: 2}]}' }, 287 | _react2.default.createElement(_describeReact.Expect, { 288 | 'function': function _function() { 289 | return style({ transform: 'scaleY(2)' }); 290 | }, 291 | 'return': { style: { transform: [{ scaleY: 2 }] } } 292 | }) 293 | ), 294 | _react2.default.createElement( 295 | _describeReact.Describe, 296 | { label: '{transform: skew(30deg, 20deg} = {transform: [{skewX: 30deg, skewY: 20deg}]}' }, 297 | _react2.default.createElement(_describeReact.Expect, { 298 | 'function': function _function() { 299 | return style({ transform: 'skew(30deg, 20deg)' }); 300 | }, 301 | 'return': { style: { transform: [{ skewX: '30deg' }, { skewY: '20deg' }] } } 302 | }) 303 | ), 304 | _react2.default.createElement( 305 | _describeReact.Describe, 306 | { label: '{transform: skewX(20deg} = {transform: [{skewX: 20deg}]}' }, 307 | _react2.default.createElement(_describeReact.Expect, { 308 | 'function': function _function() { 309 | return style({ transform: 'skewX(20deg)' }); 310 | }, 311 | 'return': { style: { transform: [{ skewX: '20deg' }] } } 312 | }) 313 | ), 314 | _react2.default.createElement( 315 | _describeReact.Describe, 316 | { label: '{transform: skewY(30deg} = {transform: [{skewY: 30deg}]}' }, 317 | _react2.default.createElement(_describeReact.Expect, { 318 | 'function': function _function() { 319 | return style({ transform: 'skewY(30deg)' }); 320 | }, 321 | 'return': { style: { transform: [{ skewY: '30deg' }] } } 322 | }) 323 | ) 324 | ), 325 | _react2.default.createElement( 326 | _describeReact.Describe, 327 | { label: 'Web' }, 328 | _react2.default.createElement(_describeReact.Run, { script: setPlatformToWeb }), 329 | _react2.default.createElement( 330 | _describeReact.Describe, 331 | { label: '{borderWidth: 1} = {borderWidth: 1, borderStyle: solid, borderColor: black}' }, 332 | _react2.default.createElement(_describeReact.Expect, { 333 | 'function': function _function() { 334 | return style({ borderWidth: 1 }); 335 | }, 336 | 'return': { style: { borderWidth: 1, borderStyle: 'solid', borderColor: 'black' } } 337 | }) 338 | ), 339 | _react2.default.createElement( 340 | _describeReact.Describe, 341 | { label: '{marginHorizontal: 10} = {marginLeft: 10, marginRight: 10}' }, 342 | _react2.default.createElement(_describeReact.Expect, { 343 | 'function': function _function() { 344 | return style({ marginHorizontal: 10 }); 345 | }, 346 | 'return': { style: { marginLeft: 10, marginRight: 10 } } 347 | }) 348 | ), 349 | _react2.default.createElement( 350 | _describeReact.Describe, 351 | { label: '{marginVertical: 10} = {marginTop: 10, marginBottom: 10}' }, 352 | _react2.default.createElement(_describeReact.Expect, { 353 | 'function': function _function() { 354 | return style({ marginVertical: 10 }); 355 | }, 356 | 'return': { style: { marginTop: 10, marginBottom: 10 } } 357 | }) 358 | ), 359 | _react2.default.createElement( 360 | _describeReact.Describe, 361 | { label: '{resizeMode: cover} = {objectFit: cover}' }, 362 | _react2.default.createElement(_describeReact.Expect, { 363 | 'function': function _function() { 364 | return style({ resizeMode: 'cover' }); 365 | }, 366 | 'return': { style: { objectFit: 'cover' } } 367 | }) 368 | ), 369 | _react2.default.createElement( 370 | _describeReact.Describe, 371 | { label: '{resizeMode: contain} = {objectFit: contain}' }, 372 | _react2.default.createElement(_describeReact.Expect, { 373 | 'function': function _function() { 374 | return style({ resizeMode: 'contain' }); 375 | }, 376 | 'return': { style: { objectFit: 'contain' } } 377 | }) 378 | ), 379 | _react2.default.createElement( 380 | _describeReact.Describe, 381 | { label: '{resizeMode: stretch} = {objectFit: fill}' }, 382 | _react2.default.createElement(_describeReact.Expect, { 383 | 'function': function _function() { 384 | return style({ resizeMode: 'stretch' }); 385 | }, 386 | 'return': { style: { objectFit: 'fill' } } 387 | }) 388 | ), 389 | _react2.default.createElement( 390 | _describeReact.Describe, 391 | { label: '{transform: [{rotate: \'20deg\'}, {translateX: 120}]} = {transform: \'rotate(20eg) translateX(120px)\'}' }, 392 | _react2.default.createElement(_describeReact.Expect, { 393 | 'function': function _function() { 394 | return style({ transform: [{ rotate: '20deg' }, { translateX: 120 }] }); 395 | }, 396 | 'return': { style: { transform: 'rotate(20deg) translateX(120px)' } } 397 | }) 398 | ), 399 | _react2.default.createElement( 400 | _describeReact.Describe, 401 | { label: '{flexDirection: \'row\' | \'column\'} = {display: \'flex\', flexDirection: \'row\' | \'column\'}' }, 402 | _react2.default.createElement(_describeReact.Expect, { 403 | 'function': function _function() { 404 | return style({ flexDirection: 'row' }); 405 | }, 406 | 'return': { style: { flexDirection: 'row', display: 'flex' } } 407 | }) 408 | ) 409 | ) 410 | ) 411 | ); 412 | }; -------------------------------------------------------------------------------- /test/API/platform.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; --------------------------------------------------------------------------------