├── .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 | [](https://badge.fury.io/js/reactors)
6 | [](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 ;
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";
--------------------------------------------------------------------------------