├── .eslintrc.js
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── index.js
├── package.json
└── src
├── components
├── BottomNavigation.js
├── BottomNavigationItem.js
├── Button.js
├── Card.js
├── Divider.js
├── Icon.js
├── Image.js
├── NativeModules.js
├── PropTypes.js
├── PureComponent.js
├── Ripple.js
├── StatusBar.js
├── StyleSheet.js
├── TabItem.js
├── Tabs.js
├── Text.js
├── ViewPager.js
├── index.js
└── internal
│ ├── Button.js
│ └── ViewPager.js
├── index.js
├── libs
├── immutable.js
├── resolver.js
└── utils.js
├── providers
├── loader.js
└── theme.js
└── theme
├── default.js
└── index.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "globals": {
9 | "__DEV__": true
10 | },
11 | "parser": "babel-eslint",
12 | "parserOptions": {
13 | "ecmaFeatures": {
14 | "experimentalObjectRestSpread": true,
15 | "jsx": true
16 | },
17 | "sourceType": "module"
18 | },
19 | "plugins": [
20 | "react",
21 | "react-native"
22 | ],
23 | "rules": {
24 | "indent": [
25 | "error",
26 | 2,
27 | {
28 | "SwitchCase": 1
29 | }
30 | ],
31 | "linebreak-style": [
32 | "error",
33 | "unix"
34 | ],
35 | "no-empty": [
36 | "error",
37 | {
38 | "allowEmptyCatch": true
39 | }
40 | ],
41 | "no-unused-vars": [
42 | "error",
43 | {
44 | "args": "after-used",
45 | "vars": "all",
46 | "ignoreRestSiblings": true,
47 | "argsIgnorePattern": "^_"
48 | }
49 | ],
50 | "quotes": [
51 | "error",
52 | "single"
53 | ],
54 | "react-native/no-unused-styles": 2,
55 | "react-native/split-platform-components": 2,
56 | "react-native/no-inline-styles": 0,
57 | "react-native/no-color-literals": 2,
58 | "react/jsx-uses-react": "error",
59 | "react/jsx-uses-vars": "error",
60 | "semi": [
61 | "error",
62 | "never"
63 | ]
64 | }
65 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 | package-lock.json
38 |
39 | # Optional REPL history
40 | .node_repl_history
41 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.enable": true
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-mdcore
2 |
3 | Material Design for both Android and iOS. `ThemeProvider` has to be set at root of component tree. All children components can access theme properties via `context`. Features:
4 |
5 | - Provide [default theme properties](src/libs/theme/default.js) for all material components.
6 | - Easy to customize theme.
7 | - Provide most of material components to build cross platform apps with consistent APIs.
8 |
9 |
10 | ## Installation
11 |
12 | ```node
13 | npm install react-native-mdcore --save
14 | ```
15 |
16 |
17 | ## Sample app
18 |
19 | Checkout [https://github.com/henrytao-me/react-native-workshop](https://github.com/henrytao-me/react-native-workshop)
20 |
21 |
22 | ## Usages
23 |
24 | ### Setup ThemeProvider
25 |
26 | Use default theme:
27 |
28 | ```js
29 | import React from 'react'
30 | import {
31 | PureComponent,
32 | ThemeProvider
33 | } from 'react-native-mdcore'
34 |
35 | export default class Main extends PureComponent {
36 |
37 | render() {
38 | return (
39 |
40 |
41 |
42 | )
43 | }
44 | }
45 | ```
46 |
47 | Use custom theme:
48 |
49 | ```js
50 | import React from 'react'
51 | import {
52 | PureComponent,
53 | Theme,
54 | ThemeProvider
55 | } from 'react-native-mdcore'
56 |
57 | cons CUSTOM_THEME = Theme.extend({
58 | palette: {
59 | primary: '#006f7b',
60 | primaryDark: '#005a64',
61 | primaryLight: '#7fb7bd',
62 | }
63 | })
64 |
65 | export default class Main extends PureComponent {
66 |
67 | render() {
68 | return (
69 |
70 |
71 |
72 | )
73 | }
74 | }
75 | ```
76 |
77 | Access theme:
78 |
79 | ```js
80 | import React from 'react'
81 | import {
82 | PropTypes,
83 | PureComponent,
84 | Text,
85 | View
86 | } from 'react-native-mdcore'
87 |
88 | export default class HomeComponent extends PureComponent {
89 |
90 | static contextTypes = {
91 | theme: PropTypes.any
92 | }
93 |
94 | render() {
95 | const { theme } = this.context
96 | return (
97 |
98 | Home
99 |
100 | )
101 | }
102 | }
103 | ```
104 |
105 | ### All-in-one place
106 |
107 | All components are now in `react-native-mdcore` that makes it easiest to remember and use.
108 |
109 | ```js
110 | import React from 'react'
111 | import {
112 | AppRegistry, AppState, Animated,
113 | Easing,
114 | FlatList,
115 | InteractionManager,
116 | ListView,
117 | PixelRatio,
118 | Platform,
119 | ScrollView, SectionList, StatusBar,
120 | TouchableHighlight, TouchableOpacity, TouchableNativeFeedback, TouchableWithoutFeedback,
121 | View, VirtualizedList,
122 | WebView,
123 |
124 | BottomNavigation,
125 | BottomNavigationItem,
126 | Button,
127 | Card,
128 | Divider,
129 | Icon,
130 | Image,
131 | NativeModules,
132 | PropTypes,
133 | PureComponent,
134 | Ripple,
135 | StyleSheet,
136 | TabItem,
137 | Tabs,
138 | Text,
139 | ViewPager,
140 | } from 'react-native-mdcore'
141 |
142 | export default class CustomComponent extends PureComponent {
143 |
144 | render() {
145 | return null
146 | }
147 | }
148 | ```
149 |
150 |
151 | ## License
152 |
153 | Copyright 2017 "Henry Tao "
154 |
155 | Licensed under the Apache License, Version 2.0 (the "License");
156 | you may not use this file except in compliance with the License.
157 | You may obtain a copy of the License at
158 |
159 | http://www.apache.org/licenses/LICENSE-2.0
160 |
161 | Unless required by applicable law or agreed to in writing, software
162 | distributed under the License is distributed on an "AS IS" BASIS,
163 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
164 | See the License for the specific language governing permissions and
165 | limitations under the License.
166 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export * from './src'
2 | import * as mdcore from './src'
3 | export default mdcore
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-mdcore",
3 | "version": "0.1.9",
4 | "description": "Material Design for both Android and iOS",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/henrytao-me/react-native-mdcore.git"
12 | },
13 | "keywords": [
14 | "android",
15 | "appcompat",
16 | "design",
17 | "ios",
18 | "material",
19 | "material-design",
20 | "mdcore",
21 | "palette",
22 | "react",
23 | "support",
24 | "theme"
25 | ],
26 | "author": "Henry Tao ",
27 | "license": "Apache-2.0",
28 | "bugs": {
29 | "url": "https://github.com/henrytao-me/react-native-mdcore/issues"
30 | },
31 | "homepage": "https://github.com/henrytao-me/react-native-mdcore#readme",
32 | "devDependencies": {
33 | "babel-eslint": "7.2.3",
34 | "babel-jest": "20.0.3",
35 | "babel-preset-react-native": "1.9.2",
36 | "eslint": "4.0.0",
37 | "eslint-plugin-react": "7.0.1",
38 | "eslint-plugin-react-native": "2.3.2",
39 | "react": "16.0.0-alpha.12",
40 | "react-native": "0.45.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/BottomNavigation.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { TouchableWithoutFeedback, View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import StyleSheet from './StyleSheet'
7 |
8 | export default class BottomNavigation extends PureComponent {
9 |
10 | static contextTypes = {
11 | theme: PropTypes.any
12 | }
13 |
14 | static propTypes = {
15 | backgroundColor: PropTypes.color,
16 | initialItem: PropTypes.number,
17 | onItemSelected: PropTypes.func
18 | }
19 |
20 | static defaultProps = {
21 | initialItem: 0,
22 | onItemSelected: (_options = { index: 0 }) => { }
23 | }
24 |
25 | state = {
26 | index: undefined
27 | }
28 |
29 | render() {
30 | const { theme } = this.context
31 | const styles = Styles.get(theme, this.props)
32 | return (
33 |
34 | {this.props.children && this.props.children.map(this._renderItem)}
35 |
36 | )
37 | }
38 |
39 | setItem = (index) => {
40 | if (index === this.state.index) {
41 | return
42 | }
43 | this.setState({ index })
44 | }
45 |
46 | _getIndex = () => {
47 | return this.state.index !== undefined ? this.state.index : this.props.initialItem
48 | }
49 |
50 | _onItemPress = (index) => {
51 | this.setState({ index })
52 | this.props.onItemSelected({ index })
53 | }
54 |
55 | _renderItem = (item, index) => {
56 | const { theme } = this.context
57 | const styles = Styles.get(theme, this.props)
58 | const active = index === this._getIndex()
59 | item = item.props.active === active ? item : React.cloneElement(item, { active })
60 | return (
61 | this._onItemPress(index)}>
62 |
63 | {item}
64 |
65 |
66 | )
67 | }
68 | }
69 |
70 | const Styles = StyleSheet.create((theme, { backgroundColor, style }) => {
71 | const container = {
72 | backgroundColor,
73 | flexDirection: 'row',
74 | ...style
75 | }
76 | const item = {
77 | flex: 1,
78 | flexDirection: 'row',
79 | alignItems: 'center',
80 | justifyContent: 'center'
81 | }
82 | return { container, item }
83 | })
84 |
--------------------------------------------------------------------------------
/src/components/BottomNavigationItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from 'react-native'
3 |
4 | import Icon from './Icon'
5 | import PropTypes from './PropTypes'
6 | import PureComponent from './PureComponent'
7 | import StyleSheet from './StyleSheet'
8 | import Text from './Text'
9 |
10 | import * as Utils from '../libs/utils'
11 |
12 | export default class BottomNavigationItem extends PureComponent {
13 |
14 | static contextTypes = {
15 | theme: PropTypes.any
16 | }
17 |
18 | static propTypes = {
19 | active: PropTypes.bool,
20 | color: PropTypes.color,
21 | iconName: PropTypes.string,
22 | iconSet: PropTypes.string,
23 | palette: PropTypes.palette,
24 | title: PropTypes.text,
25 | titleStyle: PropTypes.style
26 | }
27 |
28 | static defaultProps = {
29 | active: false,
30 | palette: 'background'
31 | }
32 |
33 | render() {
34 | const { theme } = this.context
35 | const color = this._getColor()
36 | const styles = Styles.get(theme)
37 | return (
38 |
39 | {this.props.iconName && }
46 | {this.props.title &&
52 | {this._getTitle()}
53 | }
54 |
55 | )
56 | }
57 |
58 | _getColor = () => {
59 | if (Utils.isFunction(this.props.color)) {
60 | return this.props.color(this.props)
61 | }
62 | return this.props.color
63 | }
64 |
65 | _getTitle = () => {
66 | return Utils.isString(this.props.title) ? this.props.title.toUpperCase() : this.props.title
67 | }
68 | }
69 |
70 | const Styles = StyleSheet.create((theme) => {
71 | const container = {
72 | alignItems: 'center',
73 | justifyContent: 'center',
74 | flex: 1,
75 | height: theme.bottomNavigation.height
76 | }
77 | return { container }
78 | })
79 |
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import RNButton from './internal/Button'
7 |
8 | export default class Button extends PureComponent {
9 |
10 | static contextTypes = {
11 | theme: PropTypes.any
12 | }
13 |
14 | static propTypes = {
15 | palette: PropTypes.palette,
16 | title: PropTypes.string.isRequired,
17 | type: PropTypes.oneOf(['default', 'borderless']),
18 | onPress: PropTypes.func.isRequired
19 | }
20 |
21 | static defaultProps = {
22 | palette: 'background',
23 | type: 'default'
24 | }
25 |
26 | render() {
27 | const { theme } = this.context
28 | const fontFamily = theme.fontFamily.medium
29 | let { palette, type } = this.props
30 | let backgroundColor, textColor, textColorBorderless
31 | switch (palette) {
32 | case 'primary':
33 | backgroundColor = theme.palette.primary
34 | textColor = theme.textColor.primary.primary
35 | textColorBorderless = theme.palette.primary
36 | break
37 | case 'accent':
38 | backgroundColor = theme.palette.accent
39 | textColor = theme.textColor.primary.accent
40 | textColorBorderless = theme.palette.accent
41 | break
42 | case 'warn':
43 | backgroundColor = theme.palette.warn
44 | textColor = theme.textColor.primary.warn
45 | textColorBorderless = theme.palette.warn
46 | break
47 | default:
48 | backgroundColor = theme.palette.background
49 | textColor = theme.textColor.primary.background
50 | textColorBorderless = theme.textColor.primary.background
51 | break
52 | }
53 | return (
54 |
55 |
67 |
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/Card.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import StyleSheet from './StyleSheet'
7 |
8 | export default class Card extends PureComponent {
9 |
10 | static contextTypes = {
11 | theme: PropTypes.any
12 | }
13 |
14 | render() {
15 | const { theme } = this.context
16 | const styles = Styles.get(theme)
17 | return (
18 |
19 | {this.props.children}
20 |
21 | )
22 | }
23 | }
24 |
25 | const Styles = StyleSheet.create((theme) => {
26 | const container = {
27 | borderRadius: 2,
28 | shadowColor: '#000000',
29 | shadowOpacity: 1,
30 | shadowRadius: 10,
31 | shadowOffset: {
32 | height: 1,
33 | width: 0.3,
34 | }
35 | }
36 | return { container }
37 | })
38 |
--------------------------------------------------------------------------------
/src/components/Divider.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import StyleSheet from './StyleSheet'
7 |
8 | export default class Divider extends PureComponent {
9 |
10 | static contextTypes = {
11 | theme: PropTypes.any
12 | }
13 |
14 | static propTypes = {
15 | largePadding: PropTypes.bool
16 | }
17 |
18 | render() {
19 | const { theme } = this.context
20 | const styles = Styles.get(theme, this.props)
21 | return (
22 |
23 | )
24 | }
25 | }
26 |
27 | const Styles = StyleSheet.create((theme, opts = {}) => {
28 | let { largePadding } = opts
29 | const container = {
30 | backgroundColor: theme.divider.color,
31 | height: theme.divider.size,
32 | marginLeft: largePadding ? (2 * theme.list.paddingLeft + theme.list.avatarSize) : 0
33 | }
34 | return { container }
35 | })
36 |
--------------------------------------------------------------------------------
/src/components/Icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { createIconSet } from 'react-native-vector-icons'
4 |
5 | import Entypo from 'react-native-vector-icons/Entypo'
6 | import EvilIcons from 'react-native-vector-icons/EvilIcons'
7 | import FontAwesome from 'react-native-vector-icons/FontAwesome'
8 | import Foundation from 'react-native-vector-icons/Foundation'
9 | import Ionicons from 'react-native-vector-icons/Ionicons'
10 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'
11 | import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
12 | import Octicons from 'react-native-vector-icons/Octicons'
13 | import Zocial from 'react-native-vector-icons/Zocial'
14 | import SimpleLineIcons from 'react-native-vector-icons/SimpleLineIcons'
15 |
16 | import PropTypes from './PropTypes'
17 | import PureComponent from './PureComponent'
18 |
19 | import * as Utils from '../libs/utils'
20 |
21 | export default class Icon extends PureComponent {
22 |
23 | static DEFAULT_ICON_SET = 'MaterialIcons'
24 |
25 | static ICON_SETS = { Entypo, EvilIcons, FontAwesome, Foundation, Ionicons, MaterialIcons, MaterialCommunityIcons, Octicons, Zocial, SimpleLineIcons }
26 |
27 | static contextTypes = {
28 | theme: PropTypes.any
29 | }
30 |
31 | static propTypes = {
32 | active: PropTypes.bool,
33 | color: PropTypes.color,
34 | focus: PropTypes.bool,
35 | name: PropTypes.string.isRequired,
36 | palette: PropTypes.palette,
37 | set: PropTypes.string,
38 | size: PropTypes.number
39 | }
40 |
41 | static defaultProps = {
42 | active: true,
43 | focus: false,
44 | palette: 'background'
45 | }
46 |
47 | static addIconSet = (iconSet, glyphMap, fontFamily, fontFile) => {
48 | Icon.ICON_SETS[iconSet] = createIconSet(glyphMap, fontFamily, fontFile)
49 | }
50 |
51 | static setDefaultIconSet = (iconSet) => {
52 | Icon.DEFAULT_ICON_SET = iconSet
53 | }
54 |
55 | render() {
56 | const { theme } = this.context
57 | const color = this._getColor()
58 | const size = this.props.size || theme.icon.size
59 | const IconView = Icon.ICON_SETS[this.props.set || Icon.DEFAULT_ICON_SET]
60 | return (
61 |
65 | )
66 | }
67 |
68 | _getColor = () => {
69 | if (Utils.isFunction(this.props.color)) {
70 | return this.props.color(this.props)
71 | }
72 | const { theme } = this.context
73 | const state = this.props.focus ? 'focused' : (this.props.active ? 'active' : 'inactive')
74 | return this.props.color || theme.iconColor[state][this.props.palette]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/Image.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Image as RNImage, View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 |
7 | import * as Utils from '../libs/utils'
8 |
9 | export default class Image extends PureComponent {
10 |
11 | static propTypes = {
12 | height: PropTypes.number,
13 | placeholder: PropTypes.imageSource,
14 | radius: PropTypes.imageRadius,
15 | resizeMode: PropTypes.imageResizeMode,
16 | scaleType: PropTypes.imageScaleType,
17 | source: PropTypes.imageSource,
18 | width: PropTypes.number
19 | }
20 |
21 | static defaultProps = {
22 | radius: 'none',
23 | resizeMode: 'contain',
24 | scaleType: 'width'
25 | }
26 |
27 | state = {
28 | width: 0,
29 | height: 0
30 | }
31 |
32 | _mounted = false
33 |
34 | componentDidMount() {
35 | this._mounted = true
36 | }
37 |
38 | componentWillUnmount() {
39 | this._mounted = false
40 | }
41 |
42 | render() {
43 | const image = this._getImage()
44 | const radius = this._getRadius()
45 | return (
46 |
47 |
50 |
51 | )
52 | }
53 |
54 | _getHeight = () => {
55 | return this.props.height || this.state.height
56 | }
57 |
58 | _getImage = () => {
59 | const source = this._getSource()
60 | const placeholder = this._getPlaceholder()
61 | return (!!source && !!source.uri ? source : null) || (!!placeholder && !!placeholder.uri ? placeholder : null)
62 | }
63 |
64 | _getPlaceholder = () => {
65 | const placeholder = Utils.isString(this.props.placeholder) ? { uri: this.props.placeholder } : this.props.placeholder
66 | return RNImage.resolveAssetSource(placeholder) || { uri: undefined, width: undefined, height: undefined }
67 | }
68 |
69 | _getRadius = () => {
70 | const { width, height } = this.state
71 | const { radius } = this.props
72 | switch (radius) {
73 | case 'none':
74 | return undefined
75 | case 'auto':
76 | return Math.min(width, height) / 2
77 | default:
78 | return radius
79 | }
80 | }
81 |
82 | _getSource = () => {
83 | const source = Utils.isString(this.props.source) ? { uri: this.props.source } : this.props.source
84 | return RNImage.resolveAssetSource(source) || { uri: undefined, width: undefined, height: undefined }
85 | }
86 |
87 | _getWidth = () => {
88 | return this.props.width || this.state.width
89 | }
90 |
91 | _onLayout = (e) => {
92 | const { scaleType } = this.props
93 | const { layout } = e.nativeEvent
94 | const image = this._getImage()
95 | if (!!image && !!image.uri) {
96 | if (!!image.width && !!image.height) {
97 | this._computeImageSize(scaleType, layout, image.width, image.height)
98 | } else {
99 | RNImage.getSize(image.uri, (width, height) => {
100 | this._computeImageSize(scaleType, layout, width, height)
101 | })
102 | }
103 | }
104 | }
105 |
106 | _computeImageSize = (scaleType, layout, width, height) => {
107 | if (!this._mounted) {
108 | return
109 | }
110 | this.setState({
111 | width: scaleType === 'width' || scaleType === 'none' ? layout.width : layout.height * width / height,
112 | height: scaleType === 'height' || scaleType === 'none' ? layout.height : layout.width * height / width
113 | })
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/NativeModules.js:
--------------------------------------------------------------------------------
1 | import { NativeModules, Platform } from 'react-native'
2 | const { StatusBarManager } = NativeModules
3 |
4 | StatusBarManager._getHeight = StatusBarManager.getHeight
5 | StatusBarManager.getHeight = (callback = (_data) => { }) => {
6 | switch (Platform.OS) {
7 | case 'ios':
8 | StatusBarManager._getHeight(callback)
9 | break
10 | case 'android':
11 | callback({ height: StatusBarManager.HEIGHT })
12 | break
13 | }
14 | }
15 |
16 | export default NativeModules
17 |
--------------------------------------------------------------------------------
/src/components/PropTypes.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default Object.assign({}, PropTypes, {
4 | color: PropTypes.oneOfType([
5 | PropTypes.string,
6 | PropTypes.func
7 | ]),
8 | ellipsizeMode: PropTypes.oneOf(['head', 'middle', 'tail', 'clip']),
9 | imageRadius: PropTypes.oneOfType([
10 | PropTypes.number,
11 | PropTypes.oneOf(['auto', 'none'])
12 | ]),
13 | imageResizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'center']),
14 | imageScaleType: PropTypes.oneOf(['width', 'height', 'none']),
15 | imageSource: PropTypes.oneOfType([
16 | PropTypes.number,
17 | PropTypes.shape({
18 | uri: PropTypes.string
19 | }),
20 | PropTypes.string,
21 | ]),
22 | opacity: PropTypes.number,
23 | palette: PropTypes.oneOf(['primary', 'primaryDark', 'primaryLight', 'accent', 'accentDark', 'accentLight', 'warn', 'warnDark', 'warnLight', 'background', 'backgroundDark', 'backgroundLight']),
24 | style: PropTypes.oneOfType([
25 | PropTypes.object,
26 | PropTypes.arrayOf(PropTypes.object)
27 | ]),
28 | text: PropTypes.oneOfType([
29 | PropTypes.element,
30 | PropTypes.string
31 | ]),
32 | textSubType: PropTypes.oneOf(['primary', 'secondary', 'hint']),
33 | textType: PropTypes.oneOf(['button', 'caption', 'body1', 'body2', 'subhead1', 'subhead2', 'title', 'headline', 'display1', 'display2', 'display3', 'display4']),
34 | view: PropTypes.oneOfType([
35 | PropTypes.element,
36 | PropTypes.func
37 | ])
38 | })
39 |
--------------------------------------------------------------------------------
/src/components/PureComponent.js:
--------------------------------------------------------------------------------
1 | import { PureComponent as RNPureComponent } from 'react'
2 |
3 | export default class PureComponent extends RNPureComponent {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/Ripple.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { TouchableWithoutFeedback, View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import StyleSheet from './StyleSheet'
7 |
8 | export default class Ripple extends PureComponent {
9 |
10 | static contextTypes = {
11 | theme: PropTypes.any
12 | }
13 |
14 | static propTypes = {
15 | color: PropTypes.color,
16 | duration: PropTypes.number,
17 | opacity: PropTypes.opacity,
18 | size: PropTypes.number,
19 | onPress: PropTypes.func
20 | }
21 |
22 | static defaultProps = {
23 | opPress: () => { }
24 | }
25 |
26 | state = {
27 | ripples: []
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
34 | {this.props.children}
35 |
36 |
37 | )
38 | }
39 | }
40 |
41 | const Styles = StyleSheet.create((theme) => {
42 | return {}
43 | })
44 |
--------------------------------------------------------------------------------
/src/components/StatusBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | Platform,
4 | StatusBar as RNStatusBar,
5 | View
6 | } from 'react-native'
7 |
8 | import NativeModules from './NativeModules'
9 | import PropTypes from './PropTypes'
10 | import PureComponent from './PureComponent'
11 |
12 | export default class StatusBar extends PureComponent {
13 |
14 | static contextTypes = {
15 | theme: PropTypes.any
16 | }
17 |
18 | static propTypes = {
19 | backgroundColor: PropTypes.color,
20 | barStyle: PropTypes.oneOf(['light-content', 'dark-content', 'default'])
21 | }
22 |
23 | state = {
24 | statusBarHeight: 0
25 | }
26 |
27 | componentDidMount() {
28 | NativeModules.StatusBarManager.getHeight(({ height }) => this.setState({ statusBarHeight: height }))
29 | }
30 |
31 | render() {
32 | const backgroundColor = this._getBackgroundColor()
33 | const barStyle = this._getBarStyle()
34 | const height = this.state.statusBarHeight
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | _getBackgroundColor = () => {
43 | const { theme } = this.context
44 | const backgroundColor = this.props.backgroundColor || (Platform.OS === 'ios' ? 'transparent' : theme.palette.backgroundDark)
45 | return theme.palette[backgroundColor] || backgroundColor
46 | }
47 |
48 | _getBarStyle = () => {
49 | return this.props.barStyle || (Platform.OS === 'ios' ? 'dark-content' : 'light-content')
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/StyleSheet.js:
--------------------------------------------------------------------------------
1 | import * as Utils from '../libs/utils'
2 |
3 | export default class StyleSheet {
4 |
5 | static create(creator = (_theme, ..._args) => { }) {
6 | return new StyleSheet(Utils.isFunction(creator) ? creator : () => creator)
7 | }
8 |
9 | static _getThemeId(theme) {
10 | return theme ? theme.__id : null
11 | }
12 |
13 | constructor(creator) {
14 | this._creator = creator
15 | this._theme = undefined
16 | this._themeId = undefined
17 | this._args = undefined
18 | this._style = undefined
19 | }
20 |
21 | get(theme = null, ...args) {
22 | if (this._shouldRenewCache(theme, args)) {
23 | this._theme = theme
24 | this._themeId = this.constructor._getThemeId(theme)
25 | this._args = args
26 | this._style = this._creator(this._theme, ...this._args)
27 | }
28 | return this._style
29 | }
30 |
31 | _shouldRenewCache(theme = null, args) {
32 | return theme !== this._theme || this.constructor._getThemeId(theme) !== this._themeId || !Utils.deepEqual(args, this._args)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/TabItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { View } from 'react-native'
3 |
4 | import Icon from './Icon'
5 | import PropTypes from './PropTypes'
6 | import PureComponent from './PureComponent'
7 | import StyleSheet from './StyleSheet'
8 | import Text from './Text'
9 |
10 | import * as Utils from '../libs/utils'
11 |
12 | export default class TabItem extends PureComponent {
13 |
14 | static contextTypes = {
15 | theme: PropTypes.any
16 | }
17 |
18 | static propTypes = {
19 | active: PropTypes.bool,
20 | color: PropTypes.color,
21 | iconName: PropTypes.string,
22 | iconSet: PropTypes.string,
23 | palette: PropTypes.palette,
24 | title: PropTypes.text,
25 | titleStyle: PropTypes.style
26 | }
27 |
28 | static defaultProps = {
29 | active: false,
30 | palette: 'primary'
31 | }
32 |
33 | render() {
34 | const { theme } = this.context
35 | const color = this._getColor()
36 | const styles = Styles.get(theme, this.props)
37 | return (
38 |
39 | {this.props.iconName && }
46 | {this.props.title &&
52 | {this._getTitle()}
53 | }
54 |
55 | )
56 | }
57 |
58 | _getColor = () => {
59 | if (Utils.isFunction(this.props.color)) {
60 | return this.props.color(this.props)
61 | }
62 | return this.props.color
63 | }
64 |
65 | _getTitle = () => {
66 | return Utils.isString(this.props.title) ? this.props.title.toUpperCase() : this.props.title
67 | }
68 | }
69 |
70 | const Styles = StyleSheet.create((theme, { iconName, title }) => {
71 | const container = {
72 | alignItems: 'center',
73 | justifyContent: 'center',
74 | flex: 1,
75 | height: iconName && title ? theme.tab.iconWithTextHeight : (iconName ? theme.tab.iconOnlyHeight : theme.tab.textOnlyHeight)
76 | }
77 | const tabTitle = {
78 | marginTop: iconName && title ? theme.tab.spacing : 0
79 | }
80 | return { container, title: tabTitle }
81 | })
82 |
--------------------------------------------------------------------------------
/src/components/Tabs.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { TouchableWithoutFeedback, View } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import StyleSheet from './StyleSheet'
7 |
8 | export default class Tabs extends PureComponent {
9 |
10 | static contextTypes = {
11 | theme: PropTypes.any
12 | }
13 |
14 | static propTypes = {
15 | backgroundColor: PropTypes.color,
16 | indicatorEnabled: PropTypes.bool,
17 | indicatorStyle: PropTypes.style,
18 | initialItem: PropTypes.number,
19 | onItemSelected: PropTypes.func
20 | }
21 |
22 | static defaultProps = {
23 | indicatorEnabled: true,
24 | initialItem: 0,
25 | onItemSelected: (_options = { index: 0 }) => { }
26 | }
27 |
28 | state = {
29 | index: undefined
30 | }
31 |
32 | render() {
33 | const { theme } = this.context
34 | const styles = Styles.get(theme, this.props)
35 | return (
36 |
37 | {this.props.children && this.props.children.map(this._renderItem)}
38 |
39 | )
40 | }
41 |
42 | setItem = (index) => {
43 | this.setState({ index })
44 | }
45 |
46 | _getIndex = () => {
47 | return this.state.index !== undefined ? this.state.index : this.props.initialItem
48 | }
49 |
50 | _onItemPress = (index) => {
51 | this.setState({ index })
52 | this.props.onItemSelected({ index })
53 | }
54 |
55 | _renderItem = (item, index) => {
56 | const { theme } = this.context
57 | const styles = Styles.get(theme, this.props)
58 | const active = index === this._getIndex()
59 | item = item.props.active === active ? item : React.cloneElement(item, { active })
60 | return (
61 | this._onItemPress(index)}>
62 |
63 | {this.props.indicatorEnabled && active && }
64 | {item}
65 |
66 |
67 | )
68 | }
69 | }
70 |
71 | const Styles = StyleSheet.create((theme, { backgroundColor }) => {
72 | const container = {
73 | backgroundColor: backgroundColor || theme.palette.primary,
74 | flexDirection: 'row'
75 | }
76 | const indicator = {
77 | position: 'absolute',
78 | left: 0,
79 | right: 0,
80 | bottom: 0,
81 | backgroundColor: theme.palette.accent,
82 | height: theme.tab.indicatorHeight
83 | }
84 | const item = {
85 | flex: 1,
86 | flexDirection: 'row',
87 | alignItems: 'center',
88 | justifyContent: 'center'
89 | }
90 | return { container, indicator, item }
91 | })
92 |
--------------------------------------------------------------------------------
/src/components/Text.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Text as RNText } from 'react-native'
3 |
4 | import PropTypes from './PropTypes'
5 | import PureComponent from './PureComponent'
6 | import StyleSheet from './StyleSheet'
7 |
8 | import * as Utils from '../libs/utils'
9 |
10 | export default class Text extends PureComponent {
11 |
12 | static contextTypes = {
13 | theme: PropTypes.any
14 | }
15 |
16 | static propTypes = {
17 | color: PropTypes.color,
18 | ellipsizeMode: PropTypes.ellipsizeMode,
19 | enable: PropTypes.bool,
20 | numberOfLines: PropTypes.number,
21 | palette: PropTypes.palette,
22 | subType: PropTypes.textSubType,
23 | type: PropTypes.textType,
24 | value: PropTypes.text
25 | }
26 |
27 | static defaultProps = {
28 | ellipsizeMode: 'tail',
29 | enable: true,
30 | palette: 'background',
31 | subType: 'primary',
32 | type: 'body1'
33 | }
34 |
35 | render() {
36 | const color = this._getColor()
37 | const defaultStyle = this._getDefaultStyle()
38 | const ellipsizeMode = this.props.ellipsizeMode
39 | const numberOfLines = this._getNumberOfLines()
40 | return (
41 |
44 | {this.props.children || this.props.value}
45 |
46 | )
47 | }
48 |
49 | _getColor = () => {
50 | if (Utils.isFunction(this.props.color)) {
51 | return this.props.color(this.props)
52 | }
53 | const { theme } = this.context
54 | const subType = this.props.enable ? this.props.subType : 'hint'
55 | const color = this.props.color || theme.textColor[subType][this.props.palette]
56 | return color
57 | }
58 |
59 | _getDefaultStyle = () => {
60 | const { theme } = this.context
61 | const styles = Styles.get(theme)
62 | return styles[this.props.type]
63 | }
64 |
65 | _getNumberOfLines = () => {
66 | let numberOfLines = undefined
67 | switch (this.props.type) {
68 | case 'button':
69 | case 'caption':
70 | case 'title':
71 | case 'display3':
72 | case 'display4':
73 | numberOfLines = 1
74 | break
75 | default:
76 | numberOfLines = undefined
77 | break
78 | }
79 | numberOfLines = this.props.numberOfLines || numberOfLines
80 | numberOfLines = numberOfLines === 0 ? undefined : numberOfLines
81 | return numberOfLines
82 | }
83 | }
84 |
85 | const Styles = StyleSheet.create((theme) => {
86 | return {
87 | button: {
88 | fontFamily: theme.fontFamily.medium,
89 | fontSize: theme.fontSize.button,
90 | lineHeight: theme.lineHeight.button
91 | },
92 | caption: {
93 | fontFamily: theme.fontFamily.regular,
94 | fontSize: theme.fontSize.caption,
95 | lineHeight: theme.lineHeight.caption
96 | },
97 | body1: {
98 | fontFamily: theme.fontFamily.regular,
99 | fontSize: theme.fontSize.body1,
100 | lineHeight: theme.lineHeight.body1
101 | },
102 | body2: {
103 | fontFamily: theme.fontFamily.medium,
104 | fontSize: theme.fontSize.body2,
105 | lineHeight: theme.lineHeight.body2
106 | },
107 | subhead1: {
108 | fontFamily: theme.fontFamily.regular,
109 | fontSize: theme.fontSize.subhead1,
110 | lineHeight: theme.lineHeight.subhead1
111 | },
112 | subhead2: {
113 | fontFamily: theme.fontFamily.regular,
114 | fontSize: theme.fontSize.subhead2,
115 | lineHeight: theme.lineHeight.subhead2
116 | },
117 | title: {
118 | fontFamily: theme.fontFamily.medium,
119 | fontSize: theme.fontSize.title,
120 | lineHeight: theme.lineHeight.title
121 | },
122 | headline: {
123 | fontFamily: theme.fontFamily.regular,
124 | fontSize: theme.fontSize.headline,
125 | lineHeight: theme.lineHeight.headline
126 | },
127 | display1: {
128 | fontFamily: theme.fontFamily.regular,
129 | fontSize: theme.fontSize.display1,
130 | lineHeight: theme.lineHeight.display1
131 | },
132 | display2: {
133 | fontFamily: theme.fontFamily.regular,
134 | fontSize: theme.fontSize.display2,
135 | lineHeight: theme.lineHeight.display2
136 | },
137 | display3: {
138 | fontFamily: theme.fontFamily.regular,
139 | fontSize: theme.fontSize.display3,
140 | lineHeight: theme.lineHeight.display3
141 | },
142 | display4: {
143 | fontFamily: theme.fontFamily.light,
144 | fontSize: theme.fontSize.display4,
145 | lineHeight: theme.lineHeight.display4
146 | }
147 | }
148 | })
149 |
--------------------------------------------------------------------------------
/src/components/ViewPager.js:
--------------------------------------------------------------------------------
1 | import RNViewPager from './internal/ViewPager'
2 | export default RNViewPager
3 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 | export { Component }
3 |
4 | import {
5 | AppRegistry, AppState, Animated,
6 | Easing,
7 | FlatList,
8 | InteractionManager,
9 | ListView,
10 | PixelRatio,
11 | Platform,
12 | ScrollView, SectionList,
13 | TouchableHighlight, TouchableOpacity, TouchableNativeFeedback, TouchableWithoutFeedback,
14 | View, VirtualizedList,
15 | WebView
16 | } from 'react-native'
17 | export {
18 | AppRegistry, AppState, Animated,
19 | Easing,
20 | FlatList,
21 | InteractionManager,
22 | ListView,
23 | PixelRatio,
24 | Platform,
25 | ScrollView, SectionList,
26 | TouchableHighlight, TouchableOpacity, TouchableNativeFeedback, TouchableWithoutFeedback,
27 | View, VirtualizedList,
28 | WebView
29 | }
30 |
31 | export { default as BottomNavigation } from './BottomNavigation'
32 | export { default as BottomNavigationItem } from './BottomNavigationItem'
33 | export { default as Button } from './Button'
34 | export { default as Card } from './Card'
35 | export { default as Divider } from './Divider'
36 | export { default as Icon } from './Icon'
37 | export { default as Image } from './Image'
38 | export { default as NativeModules } from './NativeModules'
39 | export { default as PropTypes } from './PropTypes'
40 | export { default as PureComponent } from './PureComponent'
41 | export { default as Ripple } from './Ripple'
42 | export { default as StatusBar } from './StatusBar'
43 | export { default as StyleSheet } from './StyleSheet'
44 | export { default as TabItem } from './TabItem'
45 | export { default as Tabs } from './Tabs'
46 | export { default as Text } from './Text'
47 | export { default as ViewPager } from './ViewPager'
48 |
--------------------------------------------------------------------------------
/src/components/internal/Button.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | Platform,
4 | StyleSheet,
5 | Text,
6 | TouchableNativeFeedback,
7 | TouchableOpacity,
8 | View
9 | } from 'react-native'
10 | import PropTypes from 'prop-types'
11 |
12 | export default class Button extends Component {
13 |
14 | static propTypes = {
15 | /**
16 | * Text to display inside the button
17 | */
18 | title: PropTypes.string.isRequired,
19 | /**
20 | * Text to display for blindness accessibility features
21 | */
22 | accessibilityLabel: PropTypes.string,
23 | /**
24 | * Color of the text (iOS), or background color of the button (Android)
25 | */
26 | color: PropTypes.any,
27 | /**
28 | * If true, disable all interactions for this component.
29 | */
30 | disabled: PropTypes.bool,
31 | /**
32 | * Handler to be called when the user taps the button
33 | */
34 | onPress: PropTypes.func.isRequired,
35 | /**
36 | * Used to locate this view in end-to-end tests.
37 | */
38 | testID: PropTypes.string,
39 |
40 | buttonStyle: React.PropTypes.object,
41 | textStyle: React.PropTypes.object
42 | }
43 |
44 | render() {
45 | const {
46 | accessibilityLabel,
47 | color,
48 | onPress,
49 | disabled,
50 | testID,
51 | } = this.props
52 | const title = `${this.props.title}`
53 | const buttonStyles = [styles.button]
54 | const textStyles = [styles.text]
55 | const Touchable = Platform.OS === 'android' ? TouchableNativeFeedback : TouchableOpacity
56 | if (color && Platform.OS === 'ios') {
57 | textStyles.push({ color: color })
58 | } else if (color) {
59 | buttonStyles.push({ backgroundColor: color })
60 | }
61 | if (disabled) {
62 | buttonStyles.push(styles.buttonDisabled)
63 | textStyles.push(styles.textDisabled)
64 | }
65 | const formattedTitle = Platform.OS === 'android' ? title.toUpperCase() : title
66 | const accessibilityTraits = ['button']
67 | if (disabled) {
68 | accessibilityTraits.push('disabled')
69 | }
70 | buttonStyles.push(this.props.buttonStyle)
71 | textStyles.push(this.props.textStyle)
72 | return (
73 |
80 |
81 | {formattedTitle}
82 |
83 |
84 | )
85 | }
86 | }
87 |
88 | // Material design blue from https://material.google.com/style/color.html#color-color-palette
89 | let defaultBlue = '#2196F3'
90 | if (Platform.OS === 'ios') {
91 | // Measured default tintColor from iOS 10
92 | defaultBlue = '#007AFF'
93 | }
94 |
95 | const styles = StyleSheet.create({
96 | button: Platform.select({
97 | ios: {
98 | elevation: 4,
99 | backgroundColor: defaultBlue,
100 | borderRadius: 2,
101 | },
102 | android: {
103 | elevation: 4,
104 | backgroundColor: defaultBlue,
105 | borderRadius: 2,
106 | },
107 | }),
108 | text: Platform.select({
109 | ios: {
110 | color: defaultBlue,
111 | textAlign: 'center',
112 | padding: 8,
113 | fontSize: 18,
114 | },
115 | android: {
116 | textAlign: 'center',
117 | color: 'white',
118 | padding: 8,
119 | fontWeight: '500',
120 | },
121 | }),
122 | buttonDisabled: Platform.select({
123 | ios: {},
124 | android: {
125 | elevation: 0,
126 | backgroundColor: '#dfdfdf',
127 | }
128 | }),
129 | textDisabled: Platform.select({
130 | ios: {
131 | color: '#cdcdcd',
132 | },
133 | android: {
134 | color: '#a1a1a1',
135 | }
136 | }),
137 | })
138 |
--------------------------------------------------------------------------------
/src/components/internal/ViewPager.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | Platform,
4 | ScrollView,
5 | StyleSheet,
6 | View,
7 | ViewPagerAndroid
8 | } from 'react-native'
9 | import PropTypes from 'prop-types'
10 |
11 | export default class ViewPager extends Component {
12 |
13 | static propTypes = {
14 | bounces: PropTypes.any,
15 | count: PropTypes.number,
16 | onSelectedIndexChange: PropTypes.func,
17 | selectedIndex: PropTypes.number
18 | }
19 |
20 | static defaultProps = {
21 | onSelectedIndexChange: (_index) => { }
22 | }
23 |
24 | constructor(props) {
25 | super(props)
26 | this.state = {
27 | width: 0,
28 | height: 0,
29 | selectedIndex: this.props.selectedIndex,
30 | initialSelectedIndex: this.props.selectedIndex || 0,
31 | scrollingTo: null
32 | }
33 | this.handleHorizontalScroll = this.handleHorizontalScroll.bind(this)
34 | this.adjustCardSize = this.adjustCardSize.bind(this)
35 | }
36 |
37 | render() {
38 | if (Platform.OS === 'ios') {
39 | return this.renderIOS()
40 | } else {
41 | return this.renderAndroid()
42 | }
43 | }
44 |
45 | renderIOS() {
46 | return (
47 |
67 | {this.renderContent()}
68 |
69 | )
70 | }
71 |
72 | renderAndroid() {
73 | return (
74 |
80 | {this.renderContent()}
81 |
82 | )
83 | }
84 |
85 | adjustCardSize(e) {
86 | this.setState({
87 | width: e.nativeEvent.layout.width,
88 | height: e.nativeEvent.layout.height,
89 | })
90 | }
91 |
92 | componentWillReceiveProps(nextProps) {
93 | if (nextProps.selectedIndex !== this.state.selectedIndex) {
94 | if (Platform.OS === 'ios') {
95 | this.refs.scrollview.scrollTo({
96 | x: nextProps.selectedIndex * this.state.width,
97 | animated: true,
98 | })
99 | this.setState({ scrollingTo: nextProps.selectedIndex })
100 | } else {
101 | this.refs.scrollview.setPage(nextProps.selectedIndex)
102 | this.setState({ selectedIndex: nextProps.selectedIndex })
103 | }
104 | }
105 | }
106 |
107 | renderContent() {
108 | const { width, height } = this.state
109 | const style = Platform.OS === 'ios' && styles.card
110 | if (!this.props.children) {
111 | return null
112 | }
113 | return this.props.children.map((child, i) => (
114 |
115 | {child}
116 |
117 | ))
118 | }
119 |
120 | handleHorizontalScroll(e) {
121 | let selectedIndex = e.nativeEvent.position
122 | if (selectedIndex === undefined) {
123 | selectedIndex = Math.round(
124 | e.nativeEvent.contentOffset.x / this.state.width,
125 | )
126 | }
127 | if (selectedIndex < 0 || selectedIndex >= this.props.count) {
128 | return
129 | }
130 | if (this.state.scrollingTo !== null && this.state.scrollingTo !== selectedIndex) {
131 | return
132 | }
133 | if (this.props.selectedIndex !== selectedIndex || this.state.scrollingTo !== null) {
134 | this.setState({ selectedIndex, scrollingTo: null })
135 | const { onSelectedIndexChange } = this.props
136 | onSelectedIndexChange && onSelectedIndexChange(selectedIndex)
137 | }
138 | }
139 |
140 | setPage = (index) => {
141 | if (Platform.OS === 'ios') {
142 | this.refs.scrollview.scrollTo({
143 | x: index * this.state.width,
144 | animated: true,
145 | })
146 | this.setState({ scrollingTo: index })
147 | } else {
148 | this.refs.scrollview.setPage(index)
149 | }
150 | }
151 | }
152 |
153 | const styles = StyleSheet.create({
154 | container: {
155 | flex: 1,
156 | },
157 | scrollview: {
158 | flex: 1,
159 | backgroundColor: 'transparent',
160 | },
161 | card: {
162 | backgroundColor: 'transparent',
163 | }
164 | })
165 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './components'
2 |
3 | import Theme from './theme'
4 | import ThemeProvider from './providers/theme'
5 | import * as ThemeUtils from './libs/utils'
6 |
7 | export {
8 | Theme,
9 | ThemeProvider,
10 | ThemeUtils
11 | }
12 |
--------------------------------------------------------------------------------
/src/libs/immutable.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * Copyright (c) 2014-2015, Facebook, Inc.
4 | * All rights reserved.
5 | *
6 | * This source code is licensed under the BSD-style license found in the
7 | * LICENSE file in the root directory of this source tree. An additional grant
8 | * of patent rights can be found in the PATENTS file in the same directory.
9 | */
10 |
11 | export function isImmutable(maybeImmutable) {
12 | return (isCollection(maybeImmutable) || isRecord(maybeImmutable)) &&
13 | !maybeImmutable.__ownerID;
14 | }
15 |
16 | export function isCollection(maybeCollection) {
17 | return !!(maybeCollection && maybeCollection[IS_ITERABLE_SENTINEL]);
18 | }
19 |
20 | export function isKeyed(maybeKeyed) {
21 | return !!(maybeKeyed && maybeKeyed[IS_KEYED_SENTINEL]);
22 | }
23 |
24 | export function isIndexed(maybeIndexed) {
25 | return !!(maybeIndexed && maybeIndexed[IS_INDEXED_SENTINEL]);
26 | }
27 |
28 | export function isAssociative(maybeAssociative) {
29 | return isKeyed(maybeAssociative) || isIndexed(maybeAssociative);
30 | }
31 |
32 | export function isOrdered(maybeOrdered) {
33 | return !!(maybeOrdered && maybeOrdered[IS_ORDERED_SENTINEL]);
34 | }
35 |
36 | export function isRecord(maybeRecord) {
37 | return !!(maybeRecord && maybeRecord[IS_RECORD_SENTINEL]);
38 | }
39 |
40 | export function isValueObject(maybeValue) {
41 | return !!(maybeValue &&
42 | typeof maybeValue.equals === 'function' &&
43 | typeof maybeValue.hashCode === 'function');
44 | }
45 |
46 | export const IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@';
47 | export const IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@';
48 | export const IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@';
49 | export const IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@';
50 | export const IS_RECORD_SENTINEL = '@@__IMMUTABLE_RECORD__@@';
--------------------------------------------------------------------------------
/src/libs/resolver.js:
--------------------------------------------------------------------------------
1 | import * as Utils from './utils'
2 |
3 | export default class Resolver {
4 |
5 | static create(defaultValue, values) {
6 | return new Resolver(Object.assign({}, defaultValue), Object.assign({}, values))
7 | }
8 |
9 | constructor(defaultValue = {}, values = {}) {
10 | this._defaultValue = defaultValue
11 | this._values = values
12 | // Ex: [{ group: 1, key: 'land', regex: ... }]
13 | this._regexMap = Object
14 | .keys(values)
15 | .map(key => {
16 | const keys = key.split('-').sort()
17 | const regex = new RegExp(`.*-${keys.join('-.*-')}-.*`)
18 | return { group: keys.length, key, regex }
19 | })
20 | .sort((a, b) => a.group - b.group)
21 | }
22 |
23 | clone() {
24 | return Resolver.create(this._defaultValue, this._valuesMap)
25 | }
26 |
27 | extend(defaultValue, values) {
28 | return Resolver.create(Utils.merge({}, this._defaultValue, defaultValue), Utils.merge({}, this._values, values))
29 | }
30 |
31 | getKeys() {
32 | return Object.keys(this._values)
33 | }
34 |
35 | getOrderedKeys() {
36 | return this.getKeys().sort()
37 | }
38 |
39 | resolve(keys = []) {
40 | keys = Utils.isArray(keys) ? keys : keys.split('-')
41 | const requestKey = `-${keys.filter(key => !!key).sort().join('-')}-`
42 | return this._regexMap.reduce((acc, { key, regex }) => {
43 | if (requestKey && regex.test(requestKey)) {
44 | Utils.merge(acc, this._values[key])
45 | }
46 | return acc
47 | }, Utils.merge({}, this._defaultValue))
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/libs/utils.js:
--------------------------------------------------------------------------------
1 | import { isImmutable } from './immutable'
2 |
3 | export const deepEqual = (a, b, comparator = _comparator) => {
4 | if (isImmutable(a) && isImmutable(b)) {
5 | return a === b
6 | }
7 | if (a instanceof Date && b instanceof Date) {
8 | return _comparator(a, b)
9 | }
10 | if (isArray(a) && isArray(b)) {
11 | if (a.length !== b.length) {
12 | return false
13 | }
14 | for (let i = 0; i < a.length; i++) {
15 | if (!deepEqual(a[i], b[i], comparator)) {
16 | return false
17 | }
18 | }
19 | return true
20 | }
21 | if (isObject(a) && isObject(b)) {
22 | const aKeys = Object.keys(a)
23 | const bKeys = Object.keys(b)
24 | if (aKeys.length !== bKeys.length || aKeys.sort().join(',') !== bKeys.sort().join(',')) {
25 | return false
26 | }
27 | const keys = aKeys
28 | for (let i = 0; i < keys.length; i++) {
29 | if (!deepEqual(a[keys[i]], b[keys[i]], comparator)) {
30 | return false
31 | }
32 | }
33 | return true
34 | }
35 | return comparator(a, b)
36 | }
37 |
38 | export const flattenObject = (obj, prefix) => {
39 | if (!isObject(obj)) {
40 | return {}
41 | }
42 | prefix = prefix ? `${prefix}.` : ''
43 | return Object.keys(obj).reduce((acc, key) => {
44 | let value = obj[key]
45 | if (!isObject(value)) {
46 | acc[prefix + key] = isArray(value) ? JSON.stringify(value) : value
47 | } else {
48 | let children = flattenObject(value, key)
49 | Object.keys(children).forEach(k => {
50 | acc[prefix + k] = children[k]
51 | })
52 | }
53 | return acc
54 | }, {})
55 | }
56 |
57 | export const hook = (target, action, onError = (_error, _index) => { }, callback = (_next, _resolve, _reject) => _next()) => {
58 | callback = isObject(callback) ? callback : { retry: callback }
59 | action = action.bind(target)
60 | onError = onError.bind(target)
61 | const callbacks = Object.keys(callback).reduce((acc, key) => {
62 | acc[key] = callback[key].bind(target)
63 | return acc
64 | }, {})
65 | const options = {
66 | args: [],
67 | finished: false,
68 | index: 0,
69 | reject: null,
70 | resolve: null
71 | }
72 | const fn = (...args) => {
73 | options.args = args
74 | options.finished = false
75 | options.index = 0
76 | return new Promise((resolve, reject) => {
77 | options.reject = reject
78 | options.resolve = resolve
79 | next()
80 | })
81 | }
82 | const next = (() => {
83 | try {
84 | const res = action(...options.args)
85 | if (res instanceof Promise) {
86 | return res
87 | .then(data => resolve(data))
88 | .catch(error => onError(error, options.index++))
89 | } else {
90 | resolve(res)
91 | }
92 | } catch (error) {
93 | onError(error, options.index++)
94 | }
95 | }).bind(target)
96 | const reject = ((error) => {
97 | const { finished, reject } = options
98 | if (!finished && !!reject) {
99 | options.finished = true
100 | reject(error)
101 | }
102 | }).bind(target)
103 | const resolve = ((data) => {
104 | const { finished, resolve } = options
105 | if (!finished && !!resolve) {
106 | options.finished = true
107 | resolve(data)
108 | }
109 | }).bind(target)
110 | Object.keys(callbacks).forEach(key => fn[key] = () => {
111 | callbacks[key](next, resolve, reject)
112 | })
113 | return fn
114 | }
115 |
116 | export const idx = (obj, callback = (_obj) => { }) => {
117 | try {
118 | return callback(obj)
119 | } catch (ignore) {
120 | }
121 | return undefined
122 | }
123 |
124 | export const isArray = (items) => {
125 | return Array.isArray(items)
126 | }
127 |
128 | export const isFunction = (obj = null) => {
129 | return obj !== null && obj instanceof Function
130 | }
131 |
132 | export const isObject = (item = null) => {
133 | return item !== null && typeof item === 'object' && !isArray(item)
134 | }
135 |
136 | export const isString = (item = null) => {
137 | return item !== null && typeof item === 'string'
138 | }
139 |
140 | export const merge = (...args) => {
141 | const target = args[0]
142 | args.filter((value, key) => key > 0).forEach(value => _mergeAPair(target, value))
143 | return target
144 | }
145 |
146 | export const shallowEqual = (a, b, comparator = _comparator, excludes = []) => {
147 | if (isImmutable(a) && isImmutable(b)) {
148 | return a === b
149 | }
150 | if (a instanceof Date && b instanceof Date) {
151 | return comparator(a, b)
152 | }
153 | if (isArray(a) && isArray(b)) {
154 | if (a.length !== b.length) {
155 | return false
156 | }
157 | for (let i = 0; i < a.length; i++) {
158 | if (!comparator(a[i], b[i])) {
159 | return false
160 | }
161 | }
162 | return true
163 | }
164 | if (isObject(a) && isObject(b)) {
165 | const aKeys = Object.keys(a)
166 | const bKeys = Object.keys(b)
167 | if (aKeys.length !== bKeys.length || aKeys.sort().join(',') !== bKeys.sort().join(',')) {
168 | return false
169 | }
170 | const keys = aKeys.filter(key => excludes.indexOf(key) < 0)
171 | for (let i = 0; i < keys.length; i++) {
172 | if (!comparator(a[keys[i]], b[keys[i]])) {
173 | return false
174 | }
175 | }
176 | return true
177 | }
178 | return comparator(a, b)
179 | }
180 |
181 | const _comparator = (a, b) => {
182 | if (a instanceof Date && b instanceof Date) {
183 | return a.getTime() === b.getTime()
184 | }
185 | if (a instanceof Function && b instanceof Function) {
186 | return true
187 | }
188 | return a === b
189 | }
190 |
191 | const _mergeAPair = (target, source) => {
192 | if (isObject(target) && isObject(source)) {
193 | Object.keys(source).forEach(key => {
194 | if (isObject(source[key])) {
195 | if (!target[key]) {
196 | Object.assign(target, { [key]: {} })
197 | }
198 | _mergeAPair(target[key], source[key])
199 | } else {
200 | Object.assign(target, { [key]: source[key] })
201 | }
202 | })
203 | }
204 | return target
205 | }
206 |
--------------------------------------------------------------------------------
/src/providers/loader.js:
--------------------------------------------------------------------------------
1 | import { PureComponent } from 'react'
2 |
3 | let READY = true
4 |
5 | const LISTENERS = []
6 | const addListener = (listener) => {
7 | if (READY) {
8 | listener()
9 | } else {
10 | LISTENERS.push(listener)
11 | }
12 | }
13 | const removeListener = (listener) => {
14 | const index = LISTENERS.indexOf(listener)
15 | if (index >= 0) {
16 | LISTENERS.slice(index, 1)
17 | }
18 | }
19 |
20 | export default class Loader extends PureComponent {
21 |
22 | static defer = () => {
23 | READY = false
24 | LISTENERS.splice(0, LISTENERS.length)
25 | }
26 |
27 | static isReady = () => {
28 | return READY
29 | }
30 |
31 | static ready = () => {
32 | READY = true
33 | LISTENERS.forEach(listener => listener())
34 | LISTENERS.splice(0, LISTENERS.length)
35 | }
36 |
37 | state = {
38 | ready: READY
39 | }
40 |
41 | componentDidMount() {
42 | addListener(this._onReady)
43 | }
44 |
45 | componentWillUnmount() {
46 | removeListener(this._onReady)
47 | }
48 |
49 | render() {
50 | const { ready } = this.state
51 | if (!ready) {
52 | return null
53 | } else {
54 | return this.props.children
55 | }
56 | }
57 |
58 | _onReady = () => {
59 | this.setState({
60 | ready: true
61 | })
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/providers/theme.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import { AppState, I18nManager, View } from 'react-native'
3 |
4 | import {
5 | PropTypes,
6 | StyleSheet
7 | } from '../components'
8 | import Theme from '../theme'
9 | import * as Utils from '../libs/utils'
10 |
11 | import Loader from './loader'
12 |
13 | const LAND = 'land'
14 | const LDLTR = 'ldltr'
15 | const LDRTL = 'ldrtl'
16 | const PORT = 'port'
17 |
18 | export default class ThemeProvider extends PureComponent {
19 |
20 | static childContextTypes = {
21 | theme: PropTypes.any
22 | }
23 |
24 | static propTypes = {
25 | layoutDirection: PropTypes.string,
26 | orientation: PropTypes.string,
27 | theme: PropTypes.instanceOf(Theme.constructor),
28 | smallestWidth: PropTypes.string,
29 | onConfigChange: PropTypes.func
30 | }
31 |
32 | static defaultProps = {
33 | layoutDirection: '',
34 | orientation: '',
35 | theme: Theme,
36 | smallestWidth: '',
37 | onConfigChange: (_config = {}) => { }
38 | }
39 |
40 | static defer = () => {
41 | Loader.defer()
42 | }
43 |
44 | static ready = () => {
45 | Loader.ready()
46 | }
47 |
48 | state = {
49 | layoutDirection: undefined,
50 | orientation: undefined,
51 | smallestWidth: undefined
52 | }
53 |
54 | _theme = {}
55 |
56 | componentDidMount() {
57 | this._mounted = true
58 | AppState.addEventListener('change', this._onAppStateChange)
59 | }
60 |
61 | componentDidUpdate() {
62 | if (Loader.isReady()) {
63 | this.props.onConfigChange(this._getConfigProps())
64 | }
65 | }
66 |
67 | componentWillUnmount() {
68 | this._mounted = false
69 | AppState.removeEventListener('change', this._onAppStateChange)
70 | }
71 |
72 | getChildContext() {
73 | return { theme: this._theme }
74 | }
75 |
76 | render() {
77 | const theme = this._theme
78 | theme.__id = (theme.__id || new Date().getTime()) + 1
79 | Object.assign(theme, this.props.theme.resolve(Object.values(this._getConfigProps())))
80 | const styles = Styles.get(theme, this.props)
81 | return (
82 |
83 | {this.state._ready && {this.props.children}}
84 |
85 | )
86 | }
87 |
88 | _getConfigProps = () => {
89 | return Object.keys(this.props)
90 | .filter(prop => Utils.isString(this.props[prop]))
91 | .reduce((acc, prop) => {
92 | acc[prop] = this.props[prop] || this.state[prop]
93 | return acc
94 | }, {})
95 | }
96 |
97 | _onAppStateChange = () => {
98 | if (I18nManager.isRTL && this.state.layoutDirection !== LDRTL) {
99 | this.setState({ layoutDirection: LDRTL })
100 | } else if (!I18nManager.isRTL && this.state.layoutDirection !== LDLTR) {
101 | this.setState({ layoutDirection: LDLTR })
102 | }
103 | }
104 |
105 | _onLayout = ({ nativeEvent: { layout: { width, height } } }) => {
106 | const keys = this.props.theme.getOrderedKeys()
107 | const newState = Object.assign({ _ready: true }, this.state, {
108 | smallestWidth: undefined
109 | })
110 | keys.forEach(key => {
111 | const smallestWidth = Utils.idx(key, key => parseInt(/^sw([0-9]+)$/.exec(key)[1]))
112 | if (smallestWidth && width >= smallestWidth) {
113 | newState.smallestWidth = key
114 | }
115 | })
116 | if (width > height && this.state.orientation !== LAND) {
117 | newState.orientation = LAND
118 | } else if (width <= height && this.state.orientation !== PORT) {
119 | newState.orientation = PORT
120 | }
121 | this.setState(newState)
122 | }
123 | }
124 |
125 | const Styles = StyleSheet.create((theme, { style }) => {
126 | const container = {
127 | backgroundColor: theme.palette.background,
128 | flex: 1,
129 | ...style
130 | }
131 | return { container }
132 | })
133 |
--------------------------------------------------------------------------------
/src/theme/default.js:
--------------------------------------------------------------------------------
1 | // Dark text on light background: 87%:DE 54%:8A 38%:61 12%:1E
2 | // White text on dark background: 100%:FF 70%:B3 50%:80 12%:1E
3 | // Icon on light background: 54%:8A 38%:61
4 | // Icon on dark background: 100%:FF 50%:80
5 |
6 | export default {
7 | bottomNavigation: {
8 | height: 56
9 | },
10 | button: {
11 | minWidth: 88
12 | },
13 | card: {
14 | borderRadius: 2,
15 | shadowColor: '#000000',
16 | shadowOpacity: 0.8,
17 | shadowRadius: 10,
18 | shadowOffset: {
19 | height: 1,
20 | width: 0.3,
21 | }
22 | },
23 | dialog: {
24 | background: '#303030B3',
25 | spacing: 24
26 | },
27 | divider: {
28 | color: '#0000001E',
29 | size: 0.87
30 | },
31 | fontFamily: {
32 | bold: undefined,
33 | light: undefined,
34 | medium: undefined,
35 | regular: undefined
36 | },
37 | fontSize: {
38 | button: 14,
39 | caption: 12,
40 | body1: 14,
41 | body2: 14,
42 | subhead1: 16,
43 | subhead2: 16,
44 | title: 20,
45 | headline: 24,
46 | display1: 34,
47 | display2: 45,
48 | display3: 56,
49 | display4: 112
50 | },
51 | fontWeight: {
52 | button: undefined,
53 | caption: undefined,
54 | body1: undefined,
55 | body2: undefined,
56 | subhead1: undefined,
57 | subhead2: undefined,
58 | title: undefined,
59 | headline: undefined,
60 | display1: undefined,
61 | display2: undefined,
62 | display3: undefined,
63 | display4: undefined
64 | },
65 | icon: {
66 | size: 24,
67 | sizeSm: 16,
68 | activeColor: '#0000008A',
69 | inactiveColor: '#00000061'
70 | },
71 | iconColor: {
72 | active: {
73 | primary: '#FFFFFFB3',
74 | primaryDark: '#FFFFFFB3',
75 | primaryLight: '#0000008A',
76 | accent: '#0000008A',
77 | accentDark: '#0000008A',
78 | accentLight: '#0000008A',
79 | warn: '#FFFFFFB3',
80 | warnDark: '#FFFFFFB3',
81 | warnLight: '#0000008A',
82 | background: '#0000008A',
83 | backgroundDark: '#0000008A',
84 | backgroundLight: '#0000008A'
85 | },
86 | focused: {
87 | primary: '#FFFFFFFF',
88 | primaryDark: '#FFFFFFFF',
89 | primaryLight: '#000000DE',
90 | accent: '#000000DE',
91 | accentDark: '#000000DE',
92 | accentLight: '#000000DE',
93 | warn: '#FFFFFFFF',
94 | warnDark: '#FFFFFFFF',
95 | warnLight: '#000000DE',
96 | background: '#000000DE',
97 | backgroundDark: '#000000DE',
98 | backgroundLight: '#000000DE'
99 | },
100 | inactive: {
101 | primary: '#FFFFFF80',
102 | primaryDark: '#FFFFFF80',
103 | primaryLight: '#00000061',
104 | accent: '#00000061',
105 | accentDark: '#00000061',
106 | accentLight: '#00000061',
107 | warn: '#FFFFFF80',
108 | warnDark: '#FFFFFF80',
109 | warnLight: '#00000061',
110 | background: '#00000061',
111 | backgroundDark: '#00000061',
112 | backgroundLight: '#00000061'
113 | }
114 | },
115 | iconToggle: {
116 | size: 48
117 | },
118 | layout: {
119 | spacing: 16,
120 | spacingLg: 32,
121 | spacingSm: 8,
122 | spacingXs: 4
123 | },
124 | lineHeight: {
125 | button: 20,
126 | caption: 18,
127 | body1: 20,
128 | body2: 24,
129 | subhead1: 24,
130 | subhead2: 28,
131 | title: 30,
132 | headline: 32,
133 | display1: 40,
134 | display2: 48,
135 | display3: 60,
136 | display4: 120
137 | },
138 | list: {
139 | avatarSize: 40,
140 | paddingTop: 8,
141 | paddingBottom: 8,
142 | paddingLeft: 16,
143 | paddingRight: 16,
144 | singleLineTextOnlyHeight: 48,
145 | singleLineIconWithTextHeight: 48,
146 | singleLineAvatarWithTextHeight: 56,
147 | singleLineAvatarWithTextAndIconHeight: 56,
148 | twoLineTextOnlyHeight: 72,
149 | twoLineIconWithTextHeight: 72,
150 | twoLineAvatarWithTextHeight: 72,
151 | twoLineAvatarWithTextAndIconHeight: 72,
152 | threeLineTextOnlyHeight: 88,
153 | threeLineIconWithTextHeight: 88,
154 | threeLineAvatarWithTextHeight: 88,
155 | threeLineAvatarWithTextAndIconHeight: 88
156 | },
157 | palette: {
158 | transparent: 'transparent',
159 | primary: '#03A9F4',
160 | primaryDark: '#0288D1',
161 | primaryLight: '#B3E5FC',
162 | accent: '#FF4081',
163 | accentDark: '#F50057',
164 | accentLight: '#FF80AB',
165 | warn: '#EC4058',
166 | warnDark: '#D91631',
167 | warnLight: '#F27889',
168 | background: '#FAFAFA',
169 | backgroundDark: '#EEEEEE',
170 | backgroundLight: '#FFFFFF'
171 | },
172 | tab: {
173 | iconOnlyHeight: 48,
174 | iconWithTextHeight: 72,
175 | indicatorHeight: 2,
176 | spacing: 8,
177 | textOnlyHeight: 48
178 | },
179 | textColor: {
180 | primary: {
181 | primary: '#FFFFFFFF',
182 | primaryDark: '#FFFFFFFF',
183 | primaryLight: '#000000DE',
184 | accent: '#000000DE',
185 | accentDark: '#000000DE',
186 | accentLight: '#000000DE',
187 | warn: '#FFFFFFFF',
188 | warnDark: '#FFFFFFFF',
189 | warnLight: '#000000DE',
190 | background: '#000000DE',
191 | backgroundDark: '#000000DE',
192 | backgroundLight: '#000000DE'
193 | },
194 | secondary: {
195 | primary: '#FFFFFFB3',
196 | primaryDark: '#FFFFFFB3',
197 | primaryLight: '#0000008A',
198 | accent: '#0000008A',
199 | accentDark: '#0000008A',
200 | accentLight: '#0000008A',
201 | warn: '#FFFFFFB3',
202 | warnDark: '#FFFFFFB3',
203 | warnLight: '#0000008A',
204 | background: '#0000008A',
205 | backgroundDark: '#0000008A',
206 | backgroundLight: '#0000008A'
207 | },
208 | hint: {
209 | primary: '#FFFFFF80',
210 | primaryDark: '#FFFFFF80',
211 | primaryLight: '#00000061',
212 | accent: '#00000061',
213 | accentDark: '#00000061',
214 | accentLight: '#00000061',
215 | warn: '#FFFFFF80',
216 | warnDark: '#FFFFFF80',
217 | warnLight: '#00000061',
218 | background: '#00000061',
219 | backgroundDark: '#00000061',
220 | backgroundLight: '#00000061'
221 | }
222 | },
223 | toolbar: {
224 | minHeight: 56
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../libs/resolver'
2 |
3 | import theme from './default'
4 |
5 | export default Resolver.create(theme)
6 |
--------------------------------------------------------------------------------