├── .eslintrc
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── lib
├── Avatar.js
├── Button.js
├── Card
│ ├── Actions.js
│ ├── Body.js
│ ├── Media.js
│ └── index.js
├── Checkbox.js
├── CheckboxGroup.js
├── Divider.js
├── Drawer
│ ├── Header.js
│ ├── Section.js
│ └── index.js
├── Icon.js
├── IconToggle.js
├── List.js
├── RadioButton.js
├── RadioButtonGroup.js
├── Ripple.js
├── Subheader.js
├── Toolbar.js
├── VectorIconComponent.js
├── config.js
├── helpers.js
├── index.js
└── polyfill
│ ├── Elevation.js
│ └── Ripple.js
└── package.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": [
4 | "react"
5 | ],
6 | "ecmaFeatures": {
7 | "jsx": true
8 | },
9 | "rules": {
10 | // Strict mode
11 | "strict": [2, "never"],
12 |
13 | // Code style
14 | "indent": 2,
15 | "quotes": [2, "single"],
16 | "no-unused-vars": 1,
17 | "no-undef": 1,
18 | "object-curly-spacing": [2, "always"],
19 |
20 | // JSX
21 | "jsx-quotes": 1,
22 |
23 | // React
24 | "react/display-name": 0,
25 | "react/jsx-boolean-value": 1,
26 | "react/jsx-closing-bracket-location": 1,
27 | "react/jsx-curly-spacing": 1,
28 | "react/jsx-max-props-per-line": 0,
29 | "react/jsx-indent-props": 0,
30 | "react/jsx-no-duplicate-props": 1,
31 | "react/jsx-no-undef": 1,
32 | "react/jsx-sort-prop-types": 0,
33 | "react/jsx-sort-props": 0,
34 | "react/jsx-uses-react": 1,
35 | "react/jsx-uses-vars": 1,
36 | "react/no-danger": 0,
37 | "react/no-set-state": 0,
38 | "react/no-did-mount-set-state": 1,
39 | "react/no-did-update-set-state": 1,
40 | "react/no-multi-comp": 1,
41 | "react/no-unknown-property": 1,
42 | "react/require-extension": 1,
43 | "react/self-closing-comp": 1,
44 | "react/wrap-multilines": 1
45 | }
46 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node template
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
29 | node_modules
30 |
31 | .idea
32 | .DS_Store
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/invertase/react-native-material-design/e3909c4e5deaeb45bcde5d0338c4e8a05dace78f/CONTRIBUTING.md
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present Invertase Limited
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > This library is **no longer maintained**. As an alternative we highly recommend checking out [React Native Paper](https://github.com/callstack/react-native-paper) by the awesome folks over at @callstack. ✨
2 |
3 | # React Native Material Design
4 |
5 | [](https://www.npmjs.com/package/react-native-material-design)
6 | [](https://david-dm.org/react-native-material-design/react-native-material-design.svg)
7 | [](https://github.com/react-native-material-design/react-native-material-design/issues)
8 | [](https://raw.githubusercontent.com/react-native-material-design/react-native-material-design/master/LICENSE)
9 | [](https://gitter.im/react-native-material-design/react-native-material-design?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
10 |
11 | React Native components which implement [Material Design](https://www.google.com/design/spec/material-design/introduction.html).
12 |
13 | This repository has been heavily developed on top of the [mrn](https://github.com/binggg/mrn) project started by **@binggg**. Improvements include support for the latest React Native versions,
14 | many bug fixes, extra components, backward compatibility to Android SDK API 16 and more.
15 |
16 | > Please keep in mind this is still a work in progress. The master branch is subject to breaking changes.
17 |
18 | Looking for a demo app? [Check out the repo](https://github.com/react-native-material-design/demo-app).
19 |
20 | Or view it online [here](https://appetize.io/app/hyp1m20y515c16cj5yw2karcjg)! (Credits to Appetize for free hosting).
21 |
22 | ## Installation
23 |
24 | ```
25 | npm i react-native-material-design --save
26 | ```
27 |
28 | Follow the [Android installation instructions](https://github.com/oblador/react-native-vector-icons#android) on the [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons) library, which consists of adding the following to your `./android/app/build.gradle`:
29 |
30 | ```gradle
31 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
32 | ```
33 |
34 | _Note: It's possible that the instructions on the react-native-vector-icons page differ from what is above. If that's the case, follow those instructions, and please file an issue with us to update it here._
35 |
36 | Import any required components into your project, for example:
37 |
38 | ```
39 | import { Button, Card } from 'react-native-material-design';
40 | ```
41 |
42 | > You may need to restart your packager in order for the icons to render.
43 |
44 | ## Documentation
45 |
46 | Documentation & full installation instructions are available at http://react-native-material-design.github.io
47 |
48 | ## React Native 0.16+
49 |
50 | This library only works with React Native 0.16+ due to the breaking changes with Babel and font loading it introduced.
51 |
52 | ## Examples
53 |
54 | 
55 | 
56 | 
57 |
58 | ## Contributing
59 |
60 | Full contributing guidelines are to be written, however please ensure you follow the points when sending in PRs:
61 |
62 | - Ensure no lint warns occur via `npm run lint`.
63 | - Follow the Material Design [guidelines](https://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-baseline-grids).
64 |
--------------------------------------------------------------------------------
/lib/Avatar.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, Image, Text} from "react-native";
3 | import Icon from './Icon';
4 | import { getColor } from './helpers';
5 |
6 | export default class Avatar extends Component {
7 |
8 | static propTypes = {
9 | image: PropTypes.shape({ type: PropTypes.oneOf([Image]) }),
10 | icon: PropTypes.string,
11 | size: PropTypes.number,
12 | color: PropTypes.string,
13 | backgroundColor: PropTypes.string,
14 | text: PropTypes.string,
15 | borderRadius: PropTypes.number,
16 | borderColor: PropTypes.string,
17 | borderWidth: PropTypes.number
18 | };
19 |
20 | static defaultProps = {
21 | size: 40,
22 | color: '#ffffff',
23 | backgroundColor: getColor('paperGrey500'),
24 | borderRadius: 40 / 2,
25 | borderColor: 'rgba(0,0,0,.1)',
26 | borderWidth: 1
27 | };
28 |
29 | render() {
30 | const {
31 | image,
32 | icon,
33 | size,
34 | color,
35 | backgroundColor,
36 | text,
37 | borderRadius,
38 | borderColor,
39 | borderWidth
40 | } = this.props;
41 |
42 | if (image) {
43 | return React.cloneElement(image, {
44 | style: {
45 | width: size,
46 | height: size,
47 | borderRadius: borderRadius,
48 | borderColor: borderColor,
49 | borderWidth: borderWidth
50 | }
51 | });
52 | }
53 |
54 | if (icon) {
55 | return (
56 |
57 |
58 |
63 |
64 |
65 | );
66 | }
67 |
68 | if (text) {
69 | return (
70 |
71 |
72 | {text}
73 |
74 |
75 | );
76 | }
77 |
78 | return null;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/Button.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {ActivityIndicator, View, Text, TouchableNativeFeedback, Platform} from "react-native";
3 | import Ripple from './polyfill/Ripple';
4 | import { TYPO, PRIMARY, THEME_NAME, PRIMARY_COLORS } from './config';
5 | import { getColor, isCompatible } from './helpers';
6 |
7 | export default class Button extends Component {
8 |
9 | static propTypes = {
10 | text: PropTypes.string.isRequired,
11 | theme: PropTypes.oneOf(THEME_NAME),
12 | primary: PropTypes.oneOf(PRIMARY_COLORS),
13 | overrides: PropTypes.shape({
14 | textColor: PropTypes.string,
15 | backgroundColor: PropTypes.string,
16 | rippleColor: PropTypes.string
17 | }),
18 | disabled: PropTypes.bool,
19 | loading: PropTypes.bool,
20 | activityIndicatorColor: PropTypes.string,
21 | raised: PropTypes.bool,
22 | styles: PropTypes.shape({
23 | button: View.propTypes.style,
24 | disabled: View.propTypes.style,
25 | text: Text.propTypes.style
26 | }),
27 | onPress: PropTypes.func,
28 | onLongPress: PropTypes.func,
29 | };
30 |
31 | static defaultProps = {
32 | theme: 'light',
33 | primary: PRIMARY,
34 | disabled: false,
35 | loading: false,
36 | raised: false,
37 | activityIndicatorColor: 'black',
38 | styles: {
39 | disabled: {opacity: 0.5}
40 | }
41 | };
42 |
43 | constructor(props) {
44 | super(props);
45 | this.state = {
46 | elevation: 2
47 | };
48 | }
49 |
50 | shouldComponentUpdate(nextProps, nextState) {
51 | return (
52 | nextProps.text !== this.props.text ||
53 | nextProps.value !== this.props.value ||
54 | nextProps.loading !== this.props.loading ||
55 | nextProps.disabled !== this.props.disabled
56 | );
57 | }
58 |
59 | setElevation = () => {
60 | this.setState({
61 | elevation: 4
62 | });
63 | };
64 |
65 | removeElevation = () => {
66 | this.setState({
67 | elevation: 2
68 | });
69 | };
70 |
71 | render() {
72 | const { elevation } = this.state;
73 | const { text, value, theme, primary, overrides, disabled, loading, raised, styles, onPress, onLongPress } = this.props;
74 |
75 | const textStyleMap = {
76 | flat: {
77 | light: {
78 | normal: {
79 | color: getColor(primary)
80 | },
81 | disabled: {
82 | color: 'rgba(0,0,0,.26)'
83 | }
84 | },
85 | dark: {
86 | normal: {
87 | color: getColor(primary)
88 | },
89 | disabled: {
90 | color: 'rgba(255,255,255,.3)'
91 | }
92 | }
93 | },
94 | raised: {
95 | light: {
96 | normal: {
97 | color: getColor(primary)
98 | },
99 | disabled: {
100 | color: 'rgba(0,0,0,.26)'
101 | }
102 | },
103 | dark: {
104 | normal: {
105 | color: '#fff'
106 | },
107 | disabled: {
108 | color: 'rgba(255,255,255,.3)'
109 | }
110 | }
111 | }
112 | };
113 |
114 | const buttonStyleMap = {
115 | raised: {
116 | light: {
117 | normal: {
118 | backgroundColor: '#fff',
119 | borderColor: 'rgba(0,0,0,.12)',
120 | },
121 | disabled: {
122 | backgroundColor: 'rgba(0,0,0,.12)',
123 | borderWidth: 1,
124 | borderColor: 'rgba(0,0,0,.12)'
125 | }
126 | },
127 | dark: {
128 | normal: {
129 | backgroundColor: getColor(primary),
130 | borderWidth: 1,
131 | borderColor: 'rgba(0,0,0,.12)'
132 | },
133 | disabled: {
134 | backgroundColor: 'rgba(255,255,255,.12)',
135 | borderWidth: 1,
136 | borderColor: 'rgba(0,0,0,.12)'
137 | }
138 | }
139 | }
140 | };
141 |
142 | const rippleColorMap = {
143 | flat: {
144 | light: {
145 | normal: 'rgba(153,153,153,.4)',
146 | disabled: 'rgba(0,0,0,0.06)'
147 | },
148 | dark: {
149 | normal: 'rgba(204,204,204,.25)',
150 | disabled: 'rgba(255,255,255,0.06)'
151 | }
152 | },
153 | raised: {
154 | light: {
155 | normal: 'rgba(153,153,153,.4)',
156 | disabled: 'rgba(0,0,0,.06)'
157 | },
158 | dark: {
159 | normal: getColor(`${primary}700`),
160 | disabled: 'rgba(255,255,255,.06)'
161 | }
162 | }
163 | };
164 |
165 | const type = disabled ? 'disabled' : 'normal';
166 | const shape = raised ? 'raised' : 'flat';
167 |
168 | const textStyle = (() => {
169 | if (disabled || !(overrides && overrides.textColor)) {
170 | return textStyleMap[shape][theme][type];
171 | }
172 |
173 | return { color: getColor(overrides.textColor) };
174 | })();
175 |
176 | const buttonStyle = (() => {
177 | if (raised) {
178 | if (disabled || !(overrides && overrides.backgroundColor)) {
179 | return buttonStyleMap[shape][theme][type];
180 | }
181 |
182 | return Object.assign(buttonStyleMap[shape][theme][type], { backgroundColor: getColor(overrides.backgroundColor) });
183 | }
184 |
185 | return null;
186 | })();
187 |
188 | const rippleColor = (() => {
189 | if (disabled || !(overrides && overrides.rippleColor)) {
190 | return rippleColorMap[shape][theme][type];
191 | }
192 |
193 | return getColor(overrides.rippleColor)
194 | })();
195 |
196 | if (disabled || loading) {
197 | return (
198 |
208 | {loading ?
209 |
210 | :
211 |
212 | {text || value}
213 | {this.props.children}
214 |
215 | }
216 |
217 | );
218 | }
219 |
220 | if (!isCompatible('TouchableNativeFeedback')) {
221 | return (
222 |
238 |
239 | {text || value}
240 | {this.props.children}
241 |
242 |
243 | )
244 | }
245 |
246 | return (
247 |
254 |
263 |
264 | {text || value}
265 |
266 |
267 |
268 | );
269 | };
270 | }
271 |
272 | const componentStyles = {
273 | button: {
274 | alignItems: 'center',
275 | justifyContent: 'center',
276 | paddingVertical: 6,
277 | paddingHorizontal: 16,
278 | margin: 6,
279 | borderRadius: 2
280 | },
281 | text: {
282 | lineHeight: 20
283 | },
284 | spinner: {
285 | alignSelf: 'center'
286 | }
287 | };
288 |
--------------------------------------------------------------------------------
/lib/Card/Actions.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, View} from "react-native";
3 |
4 | export default class Actions extends Component {
5 |
6 | static propTypes = {
7 | position: PropTypes.oneOf(['left', 'right']),
8 | children: PropTypes.node.isRequired
9 | };
10 |
11 | static defaultProps = {
12 | position: 'left'
13 | };
14 |
15 | render() {
16 | const { position, children } = this.props;
17 |
18 | return (
19 |
20 |
21 | {children}
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | const styles = StyleSheet.create({
29 | container: {
30 | flex: 1,
31 | paddingRight: -16,
32 | paddingLeft: -16
33 | },
34 | actions: {
35 | flexDirection: 'row'
36 | }
37 | });
--------------------------------------------------------------------------------
/lib/Card/Body.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, View} from "react-native";
3 |
4 | export default class Body extends Component {
5 |
6 | static propTypes = {
7 | children: PropTypes.node.isRequired
8 | };
9 |
10 | render() {
11 | const { theme, children } = this.props;
12 |
13 | return (
14 |
15 | {children}
16 |
17 | );
18 | }
19 | }
20 |
21 | const styles = StyleSheet.create({
22 | container: {
23 | paddingTop: 16,
24 | paddingBottom: 16
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/lib/Card/Media.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, Image, View} from "react-native";
3 |
4 | export default class Media extends Component {
5 |
6 | static propTypes = {
7 | image: PropTypes.shape({ type: PropTypes.oneOf([Image]) }).isRequired,
8 | height: PropTypes.number,
9 | overlay: PropTypes.bool,
10 | children: PropTypes.node
11 | };
12 |
13 | static defaultProps = {
14 | height: 150,
15 | overlay: false
16 | };
17 |
18 | render() {
19 | const { image, height, overlay, children } = this.props;
20 |
21 | return (
22 |
23 | {React.cloneElement(image, {
24 | style: [styles.media, { height }]
25 | })}
26 | {children &&
27 |
28 | {children}
29 |
30 | }
31 |
32 | );
33 | }
34 |
35 | }
36 |
37 | const styles = StyleSheet.create({
38 | media: {
39 | position: 'absolute',
40 | left: -16,
41 | right: -16,
42 | },
43 | content: {
44 | position: 'absolute',
45 | left: -16,
46 | right: -16,
47 | bottom: 0,
48 | paddingTop: 24,
49 | paddingBottom: 16,
50 | paddingLeft: 16,
51 | paddingRight: 16
52 | }
53 | });
--------------------------------------------------------------------------------
/lib/Card/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, TouchableNativeFeedback} from "react-native";
3 | import Ripple from '../polyfill/Ripple';
4 | import { getColor, isCompatible } from '../helpers';
5 |
6 | import Media from './Media';
7 | import Body from './Body';
8 | import Actions from './Actions';
9 |
10 | import { COLOR } from '../config';
11 |
12 | export default class Card extends Component {
13 |
14 | static propTypes = {
15 | theme: PropTypes.string,
16 | overrides: PropTypes.shape({
17 | backgroundColor: PropTypes.string,
18 | rippleColor: PropTypes.string
19 | }),
20 | elevation: PropTypes.number,
21 | disabled: PropTypes.bool,
22 | onPress: PropTypes.func,
23 | children: PropTypes.node.isRequired,
24 | style: View.propTypes.style
25 | };
26 |
27 | static defaultProps = {
28 | elevation: 2,
29 | disabled: false
30 | };
31 |
32 | static Media = Media;
33 |
34 | static Body = Body;
35 |
36 | static Actions = Actions;
37 |
38 | render() {
39 | const { theme, overrides, elevation, disabled, onPress, children, style } = this.props;
40 |
41 | const cardStyle = (() => {
42 | return [
43 | styles.container, {
44 | elevation
45 | }, !isCompatible('elevation') && {
46 | borderWidth: 1,
47 | borderColor: 'rgba(0,0,0,.12)'
48 | }, theme && {
49 | backgroundColor: COLOR[theme].color
50 | }, overrides && overrides.backgroundColor && {
51 | backgroundColor: overrides.backgroundColor
52 | }, style
53 | ];
54 | })();
55 |
56 |
57 | if (onPress == null || disabled) {
58 | return (
59 |
60 | {children}
61 |
62 | );
63 | }
64 |
65 | const defaultRippleColor = 'rgba(153,153,153,.4)';
66 | const rippleColor = (() => {
67 | if (disabled || !(overrides && overrides.rippleColor)) {
68 | return defaultRippleColor;
69 | }
70 |
71 | return getColor(overrides.rippleColor)
72 | })();
73 |
74 | if (!isCompatible('TouchableNativeFeedback')) {
75 | return (
76 |
80 |
81 | {children}
82 |
83 |
84 | )
85 | }
86 |
87 | return (
88 |
92 |
93 | {children}
94 |
95 |
96 | );
97 |
98 | }
99 |
100 | }
101 |
102 | const styles = {
103 | container: {
104 | backgroundColor: '#ffffff',
105 | borderRadius: 2,
106 | margin: 8,
107 | paddingLeft: 16,
108 | paddingRight: 16
109 | }
110 | };
111 |
--------------------------------------------------------------------------------
/lib/Checkbox.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, Text, View, TouchableHighlight} from "react-native";
3 | import { TYPO, PRIMARY, COLOR, PRIMARY_COLORS, THEME_NAME } from './config';
4 | import Icon from './Icon';
5 | import IconToggle from './IconToggle';
6 |
7 | const typos = StyleSheet.create(TYPO);
8 |
9 | export default class Checkbox extends Component {
10 |
11 | static propTypes = {
12 | label: PropTypes.string,
13 | theme: PropTypes.oneOf(THEME_NAME),
14 | primary: PropTypes.oneOfType([PropTypes.oneOf(PRIMARY_COLORS), PropTypes.string]),
15 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
16 | checked: PropTypes.bool,
17 | disabled: PropTypes.bool,
18 | onCheck: PropTypes.func
19 | };
20 |
21 | static defaultProps = {
22 | theme: 'light',
23 | primary: PRIMARY,
24 | disabled: false
25 | };
26 |
27 | render() {
28 | const { theme, primary, checked, disabled, value, onCheck } = this.props;
29 |
30 | const status = (()=> {
31 | if (disabled) {
32 | return 'disabled'
33 | } else if (checked) {
34 | return 'checked'
35 | } else {
36 | return 'default'
37 | }
38 | })();
39 |
40 | const colorMap = {
41 | light: {
42 | disabled: '#000000',
43 | checked: (COLOR[`${primary}500`] || {}).color || primary,
44 | default: '#000000'
45 | },
46 | dark: {
47 | disabled: '#ffffff',
48 | checked: (COLOR[`${primary}500`] || {}).color || primary,
49 | default: '#ffffff'
50 | }
51 | };
52 |
53 | const opacityMap = {
54 | light: {
55 | checked: 1,
56 | default: 0.54,
57 | disabled: 0.26
58 | },
59 | dark: {
60 | checked: 1,
61 | default: 0.7,
62 | disabled: 0.3
63 | }
64 | };
65 |
66 | const underlayMap = {
67 | light: 'rgba(0,0,0,.12)',
68 | dark: 'rgba(255,255,255,.12)'
69 | }
70 |
71 | const labelColorMap = {
72 | light: '#000000',
73 | dark: '#ffffff'
74 | };
75 |
76 | const CURR_COLOR = colorMap[theme][status];
77 | const OPACITY = opacityMap[theme][status];
78 | const LABEL_COLOR = labelColorMap[theme];
79 | const UNDERLAY_COLOR = underlayMap[theme];
80 |
81 | return (
82 | { disabled ? null : onCheck(!checked, value) }}
84 | underlayColor={disabled ? 'rgba(0,0,0,0)' : UNDERLAY_COLOR}
85 | activeOpacity={1}
86 | >
87 |
88 | { disabled ? null : onCheck(!checked, value) }}
92 | >
93 |
102 |
103 | onCheck(!checked, value)}
108 | >
109 |
119 | {this.props.label}
120 |
121 |
122 |
123 |
124 | );
125 | };
126 | }
127 |
128 | const styles = StyleSheet.create({
129 | container: {
130 | flexDirection: 'row',
131 | alignItems: 'center',
132 | backgroundColor: 'rgba(0,0,0,0)'
133 | },
134 | label: {
135 | marginLeft: 16,
136 | opacity: COLOR.darkPrimaryOpacity.opacity,
137 | alignItems: 'center',
138 | flex: 1
139 | },
140 | labelContainer :{
141 | justifyContent: 'center',
142 | flexDirection:'row'
143 | }
144 | });
145 |
--------------------------------------------------------------------------------
/lib/CheckboxGroup.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View} from "react-native";
3 | import Checkbox from './Checkbox';
4 | import { THEME_NAME, PRIMARY, PRIMARY_COLORS } from './config';
5 |
6 | export default class CheckboxGroup extends Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | selected: []
12 | };
13 | }
14 |
15 | static propTypes = {
16 | theme: PropTypes.oneOf(THEME_NAME),
17 | primary: PropTypes.oneOf(PRIMARY_COLORS),
18 | onSelect: PropTypes.func,
19 | checked: PropTypes.array,
20 | items: PropTypes.arrayOf(PropTypes.shape({
21 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
22 | label: PropTypes.string,
23 | disabled: PropTypes.bool
24 | }))
25 | };
26 |
27 | static defaultProps = {
28 | theme: 'light',
29 | primary: PRIMARY
30 | };
31 |
32 | componentWillMount = () => {
33 | const { checked } = this.props;
34 |
35 | if (checked && checked.length) {
36 | this.value = checked;
37 | }
38 | };
39 |
40 | render() {
41 | const { items, theme, primary } = this.props;
42 | return (
43 |
44 | {
45 | items && items.length && items.map((item) => {
46 | const { value } = item;
47 | return (
48 |
58 | );
59 | })
60 | }
61 |
62 | );
63 | };
64 |
65 | _onChange = (checked, value) => {
66 | const { selected } = this.state;
67 |
68 | var newSelected;
69 | if (checked) {
70 | newSelected = [...selected, value]
71 | } else {
72 | let index = selected.indexOf(value);
73 | newSelected = [
74 | ...selected.slice(0, index),
75 | ...selected.slice(index + 1)
76 | ]
77 | }
78 |
79 | this.setState({
80 | selected: newSelected
81 | });
82 |
83 | const { onSelect } = this.props;
84 | onSelect && onSelect(newSelected);
85 | };
86 |
87 | /**
88 | * Get the value of checked Checkbox in CheckboxGroup. Often use in form.
89 | * @returns {Array}
90 | */
91 | get value() {
92 | return this.state.selected
93 | }
94 |
95 | /**
96 | * Make CheckboxGroup set some checkbox checked
97 | * @param {string[]} value - An array of values of some Checkbox in CheckboxGroup
98 | */
99 | set value(value) {
100 | this.setState({
101 | selected: value
102 | });
103 |
104 | const { onSelect } = this.props;
105 | onSelect && onSelect(value);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/Divider.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View} from "react-native";
3 | import { THEME_NAME } from './config';
4 |
5 | export default class Divider extends Component {
6 |
7 | static propTypes = {
8 | inset: PropTypes.bool,
9 | theme: PropTypes.oneOf(THEME_NAME),
10 | style: View.propTypes.style
11 | };
12 |
13 | static defaultProps = {
14 | inset: false,
15 | theme: 'light'
16 | };
17 |
18 | render() {
19 | const { inset, theme, style } = this.props;
20 |
21 | return (
22 |
31 | );
32 | }
33 | }
34 |
35 | const styles = {
36 | divider: {
37 | height: 1
38 | }
39 | };
--------------------------------------------------------------------------------
/lib/Drawer/Header.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, Image} from "react-native";
3 |
4 | export default class Header extends Component {
5 |
6 | static propTypes = {
7 | image: PropTypes.shape({ type: PropTypes.oneOf([Image]) }),
8 | backgroundColor: PropTypes.string,
9 | height: PropTypes.number,
10 | children: PropTypes.node
11 | };
12 |
13 | static defaultProps = {
14 | height: 150,
15 | backgroundColor: '#333333'
16 | };
17 |
18 | render() {
19 | const { image, height, backgroundColor, children } = this.props;
20 |
21 | if (image) {
22 | return React.cloneElement(image, {
23 | style: [styles.header, { height: height }]
24 | }, children);
25 | }
26 |
27 | return (
28 |
29 | {children}
30 |
31 | );
32 | }
33 | }
34 |
35 | const styles = {
36 | header: {
37 | flexWrap: 'wrap',
38 | justifyContent:'flex-start',
39 | paddingHorizontal: 16,
40 | marginBottom: 8
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/lib/Drawer/Section.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, Text, TouchableNativeFeedback} from "react-native";
3 | import Icon from '../Icon';
4 | import Ripple from '../polyfill/Ripple';
5 | import { TYPO } from '../config';
6 | import { isCompatible } from '../helpers';
7 |
8 | export default class Section extends Component {
9 |
10 | static propTypes = {
11 | title: PropTypes.string,
12 | items: PropTypes.arrayOf(PropTypes.shape({
13 | icon: PropTypes.string,
14 | value: PropTypes.string.isRequired,
15 | label: PropTypes.string,
16 | onPress: PropTypes.func,
17 | onLongPress: PropTypes.func,
18 | active: PropTypes.bool,
19 | disabled: PropTypes.bool
20 | }))
21 | };
22 |
23 | renderRow = (item, index, color) => {
24 | return (
25 |
29 | {item.icon &&
30 |
36 | }
37 |
38 |
39 | {item.value}
40 |
41 |
42 | {item.label &&
43 |
44 |
45 | {item.label}
46 |
47 |
48 | }
49 |
50 | );
51 | };
52 |
53 | render() {
54 | const { theme, title, items } = this.props;
55 |
56 | const textStyleMap = {
57 | light: {
58 | 'default': 'rgba(0,0,0,.87)',
59 | disabled: 'rgba(0,0,0,.38)'
60 | },
61 | dark: {
62 | 'default': '#ffffff',
63 | disabled: 'rgba(255,255,255,.30)'
64 | }
65 | };
66 |
67 | const subheaderStyleMap = {
68 | light: 'rgba(0,0,0,.54)',
69 | dark: 'rgba(255,255,255,.70)',
70 | };
71 |
72 | const activeStyleMap = {
73 | light: '#f5f5f5',
74 | dark: '#212121',
75 | };
76 |
77 | const TEXT_COLOR = textStyleMap[theme]['default'];
78 | const SUB_TEXT_COLOR = subheaderStyleMap[theme];
79 | const ACTIVE_COLOR = activeStyleMap[theme];
80 |
81 | return (
82 |
83 | {title &&
84 |
85 |
86 | {title}
87 |
88 |
89 | }
90 | {items && items.map((item, i) => {
91 | if (item.disabled) {
92 | return this.renderRow(item, i, textStyleMap[theme]['disabled']);
93 | }
94 |
95 | if (!isCompatible('TouchableNativeFeedback')) {
96 | return (
97 |
108 | {this.renderRow(item, i, TEXT_COLOR)}
109 |
110 | );
111 | }
112 |
113 | return (
114 |
120 |
121 | {this.renderRow(item, i, TEXT_COLOR)}
122 |
123 |
124 | );
125 | })}
126 |
127 | );
128 | }
129 | }
130 |
131 | const styles = {
132 | section: {
133 | marginTop: 8,
134 | flexWrap: 'wrap',
135 | justifyContent:'flex-start'
136 | },
137 | item: {
138 | flex: 1,
139 | flexDirection: 'row',
140 | alignItems: 'center',
141 | height: 48,
142 | paddingLeft: 16,
143 | flexWrap: 'wrap'
144 | },
145 | subheader: {
146 | flex: 1,
147 | flexWrap: 'wrap',
148 | justifyContent:'flex-start'
149 | },
150 | icon: {
151 | position: 'absolute',
152 | top: 13
153 | },
154 | value: {
155 | flex: 1,
156 | paddingLeft: 56,
157 | top: 2
158 | },
159 | label: {
160 | paddingRight: 16,
161 | top: 2
162 | }
163 | };
164 |
--------------------------------------------------------------------------------
/lib/Drawer/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {ScrollView} from "react-native";
3 | import { THEME_NAME, PRIMARY_COLORS } from '../config';
4 | import { getColor } from '../helpers';
5 |
6 | import Header from './Header';
7 | import Section from './Section';
8 |
9 | export default class Drawer extends Component {
10 |
11 | static propTypes = {
12 | theme: PropTypes.oneOf(THEME_NAME),
13 | primary: PropTypes.oneOf(PRIMARY_COLORS),
14 | overrides: PropTypes.shape({
15 | backgroundColor: PropTypes.string
16 | }),
17 | children: PropTypes.node.isRequired
18 | };
19 |
20 | static defaultProps = {
21 | theme: 'light',
22 | primary: 'paperGrey'
23 | };
24 |
25 | static Header = Header;
26 |
27 | static Section = Section;
28 |
29 | render() {
30 | const { theme, overrides, children } = this.props;
31 |
32 | const backgroundColorMap = {
33 | light: '#ffffff',
34 | dark: '#333333'
35 | };
36 |
37 | const backgroundColor = (() => {
38 | if (overrides && overrides.backgroundColor) {
39 | return getColor(overrides.backgroundColor);
40 | }
41 | return backgroundColorMap[theme];
42 | })();
43 |
44 | return (
45 |
46 | {React.Children.map(children, (child) => {
47 | return React.cloneElement(child, {
48 | theme
49 | });
50 | })}
51 |
52 | );
53 | }
54 |
55 | }
56 |
57 | const styles = {
58 | container: {
59 | flex: 1
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/lib/Icon.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View} from "react-native";
3 | import { getColor } from './helpers';
4 | import VectorIconComponent from './VectorIconComponent';
5 |
6 | export default class Icon extends Component {
7 |
8 | static propTypes = {
9 | name: PropTypes.string.isRequired,
10 | style: View.propTypes.style,
11 | size: PropTypes.number,
12 | color: PropTypes.string,
13 | allowFontScaling: PropTypes.bool
14 | };
15 |
16 | static defaultProps = {
17 | size: 30,
18 | color: '#757575',
19 | allowFontScaling: true
20 | };
21 |
22 | render() {
23 | const { name, style, size, color, allowFontScaling} = this.props;
24 | const VectorIcon = VectorIconComponent.get();
25 |
26 | return (
27 |
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/IconToggle.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {Text, View, Animated} from "react-native";
3 | import { getColor } from './helpers';
4 |
5 | export default class IconToggle extends Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | scaleValue: new Animated.Value(0.01),
11 | opacityValue: new Animated.Value(0.1),
12 | maxOpacity: props.opacity,
13 | size: null
14 | };
15 | this._responder = {
16 | onStartShouldSetResponder: (e) => true,
17 | onResponderGrant: this._highlight,
18 | onResponderRelease: this._handleResponderEnd,
19 | onResponderTerminate: this._unHighlight
20 | };
21 | }
22 |
23 | componentDidUpdate(prevProps) {
24 | if (this.props && this.props.badge) {
25 | if (prevProps.badge && this.props.badge.value !== prevProps.badge.value) {
26 | this._animateBadgeCounter();
27 | }
28 | }
29 | };
30 |
31 | static propTypes = {
32 | color: PropTypes.string.isRequired,
33 | opacity: PropTypes.number,
34 | disabled: PropTypes.bool,
35 | onPress: PropTypes.func,
36 | percent: PropTypes.number,
37 | children: PropTypes.element.isRequired,
38 | badge: PropTypes.shape({
39 | value: PropTypes.number,
40 | animate: PropTypes.bool,
41 | backgroundColor: PropTypes.string,
42 | textColor: PropTypes.string
43 | }),
44 | onLayout: PropTypes.func
45 | };
46 |
47 | static defaultProps = {
48 | opacity: .1,
49 | disabled: false,
50 | percent: 90
51 | };
52 |
53 | setSize = (event) => {
54 | const { width, height } = event.nativeEvent.layout;
55 |
56 | this.setState({
57 | size: width > height ? width : height
58 | });
59 | };
60 |
61 | render() {
62 | const { scaleValue, opacityValue, size } = this.state;
63 | const { color, onLayout, children } = this.props;
64 |
65 | let { badge } = this.props;
66 | let { percent } = this.props;
67 |
68 | if (percent < 0) {
69 | percent = 0;
70 | }
71 | if (percent > 100) {
72 | percent = 100;
73 | }
74 |
75 | percent = percent / 100;
76 |
77 | if (badge && typeof badge.value === 'number') {
78 | badge = Object.assign({},
79 | {value: badge.value},
80 | badge.backgroundColor ? {backgroundColor: badge.backgroundColor} : {backgroundColor: 'paperRed'},
81 | badge.textColor ? {textColor: badge.textColor} : {textColor: '#ffffff'}
82 | );
83 | }
84 |
85 | this.badgeAnim = this.badgeAnim || new Animated.Value(0);
86 |
87 | return (
88 |
89 |
90 | {size &&
91 |
104 | }
105 | { this.setSize(event) }}>
106 | {children}
107 |
108 | {size && badge && typeof badge.value === 'number' &&
109 |
135 |
136 | 99 ? { fontSize: 8 } : { fontSize: 10 }]}>
137 | {badge.value}
138 |
139 |
140 |
141 | }
142 |
143 |
144 | );
145 | };
146 |
147 | /**
148 | *
149 | * @private
150 | */
151 | _highlight = () => {
152 | if (!this.props.disabled) {
153 | Animated.timing(this.state.scaleValue, {
154 | toValue: 1,
155 | duration: 150
156 | }).start();
157 | Animated.timing(this.state.opacityValue, {
158 | toValue: this.state.maxOpacity,
159 | duration: 100
160 | }).start();
161 | }
162 | };
163 |
164 |
165 | /**
166 | *
167 | * @private
168 | */
169 | _unHighlight = () => {
170 | if (!this.props.disabled) {
171 | Animated.timing(this.state.scaleValue, {
172 | toValue: 0.01,
173 | duration: 1500
174 | }).start();
175 | Animated.timing(this.state.opacityValue, {
176 | toValue: 0
177 | }).start();
178 | }
179 | };
180 |
181 | /**
182 | *
183 | * @private
184 | */
185 | _animateBadgeCounter = () => {
186 | if (this.badgeAnim && this.props.badge && this.props.badge.animate !== false) {
187 | Animated.spring(this.badgeAnim, {
188 | toValue: 0, // Returns to the start
189 | velocity: 8, // Velocity makes it move
190 | tension: -5, // Slow
191 | friction: 1, // Oscillate a lot
192 | duration: 300
193 | }).start();
194 | }
195 | };
196 |
197 | /**
198 | *
199 | * @private
200 | */
201 | _setMainRef = (ref) => {
202 | this.main = ref;
203 | };
204 |
205 | /**
206 | *
207 | * @private
208 | */
209 | _handleResponderEnd = () => {
210 | const { disabled, onPress } = this.props;
211 |
212 | if (!disabled) {
213 | this._unHighlight();
214 | onPress && onPress();
215 | }
216 | };
217 |
218 | // Proxy the measurement methods from the main view
219 | measure = (...args) => this.main.measure(...args);
220 | measureInWindow = (...args) => this.main.measureInWindow(...args);
221 | measureLayout = (...args) => this.main.measureLayout(...args);
222 | }
223 |
224 | const styles = {
225 | badgeContainer: {
226 | position: 'absolute',
227 | borderRadius: 7.5,
228 | width: 15,
229 | height: 15
230 | },
231 | badgeText: {
232 | textAlign: 'center'
233 | }
234 | };
235 |
--------------------------------------------------------------------------------
/lib/List.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, View, Text, TouchableWithoutFeedback} from "react-native";
3 | import { TYPO } from './config';
4 |
5 | export default class List extends Component {
6 |
7 | static propTypes = {
8 | primaryText: PropTypes.string.isRequired,
9 | secondaryText: PropTypes.string,
10 | captionText: PropTypes.string,
11 | secondaryTextMoreLine: PropTypes.arrayOf(PropTypes.shape({
12 | text: PropTypes.string.isRequired,
13 | style: PropTypes.object
14 | })),
15 | leftIcon: PropTypes.element,
16 | rightIcon: PropTypes.element,
17 | leftAvatar: PropTypes.element,
18 | rightAvatar: PropTypes.element,
19 | lines: PropTypes.number,
20 | primaryColor: PropTypes.string,
21 | onPress: PropTypes.func,
22 | onLeftIconClick: PropTypes.func,
23 | onRightIconClick: PropTypes.func
24 | };
25 |
26 | static defaultProps = {
27 | lines: 1,
28 | primaryColor: 'rgba(0,0,0,.87)'
29 | };
30 |
31 | render() {
32 | const {
33 | primaryText,
34 | secondaryText,
35 | leftIcon,
36 | leftAvatar,
37 | rightAvatar,
38 | rightIcon,
39 | lines,
40 | onPress,
41 | primaryColor,
42 | onLeftIconClicked,
43 | onRightIconClicked,
44 | secondaryTextMoreLine,
45 | captionText
46 | } = this.props;
47 |
48 | return (
49 | 2 ? ((lines -1) * 16 + 56) : (secondaryText ? 72 : (leftAvatar || rightAvatar ) ? 56 : 48) }]}>
50 | {leftIcon &&
51 |
52 | 2 && { paddingTop: 16, alignSelf: 'flex-start' }]}>
53 | {leftIcon}
54 |
55 |
56 | }
57 | {leftAvatar &&
58 | 2 && { paddingTop: 16, alignSelf: 'flex-start' }]}>
59 | {leftAvatar}
60 |
61 | }
62 |
63 |
64 |
65 |
66 | {primaryText}
67 |
68 |
69 | {(lines > 2 && !!rightIcon) ||
70 |
71 |
72 | {captionText}
73 |
74 |
75 | }
76 |
77 | {secondaryText &&
78 |
79 | 2 && { height: 22 * (lines - 1) -4 }, styles.secondaryText]}>
80 | {secondaryText}
81 |
82 |
83 | }
84 | {secondaryTextMoreLine &&
85 | 2 && { height: 22 * (lines - 1) - 4 }]}>
86 | {secondaryTextMoreLine.map((line) => (
87 |
88 | {line.text}
89 |
90 | ))}
91 |
92 | }
93 |
94 |
95 | {rightAvatar &&
96 | 2 && {
98 | paddingTop: 16,
99 | alignSelf: 'flex-start'
100 | }]}
101 | >
102 | {rightAvatar}
103 |
104 | }
105 |
106 | {lines > 2 && !!rightIcon && !!captionText &&
107 |
108 | {captionText}
109 | }
110 |
111 | {rightIcon &&
112 |
113 | 2 && {
115 | paddingTop: 16,
116 | alignSelf: 'flex-end',
117 | justifyContent:'flex-end'
118 | }]}
119 | onPress={onRightIconClicked}
120 | >
121 | {rightIcon}
122 |
123 |
124 | }
125 |
126 |
127 | )
128 | }
129 | }
130 |
131 | const styles = StyleSheet.create({
132 | listContainer: {
133 | flexDirection: 'row',
134 | paddingLeft: 16,
135 | paddingRight: 16,
136 | height: 48,
137 | alignItems: 'center'
138 | },
139 | leftIcon: {
140 | width: 56,
141 | position: 'relative',
142 | top: -6
143 | },
144 | rightIcon: {
145 | paddingLeft: 16,
146 | position: 'relative',
147 | top: -3,
148 | left: -8
149 | },
150 | leftAvatar: {
151 | width: 56
152 | },
153 | rightAvatar: {
154 | width: 56
155 | },
156 | primaryText: Object.assign({}, TYPO.paperFontSubhead, { lineHeight: 24 }),
157 | secondaryText: Object.assign({}, TYPO.paperFontBody1, {
158 | lineHeight: 22,
159 | fontSize: 14,
160 | color: 'rgba(0,0,0,.54)'
161 | }),
162 | firstLine: {
163 | flexDirection: 'row'
164 | },
165 | primaryTextContainer: {
166 | flex: 1,
167 | paddingRight: 16
168 | },
169 | captionTextContainer: {
170 | alignSelf: 'flex-start',
171 | alignItems: 'flex-start'
172 | },
173 | captionText: Object.assign({}, TYPO.paperFontCaption),
174 | captionTextContainer2: {
175 | alignSelf: 'flex-end',
176 | alignItems: 'flex-end'
177 | }
178 | });
--------------------------------------------------------------------------------
/lib/RadioButton.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, Text, View, TouchableHighlight} from "react-native";
3 | import Icon from './Icon';
4 | import IconToggle from './IconToggle';
5 | import { TYPO, PRIMARY, COLOR, THEME_NAME, PRIMARY_COLORS } from './config';
6 |
7 | const typos = StyleSheet.create(TYPO);
8 |
9 | export default class RadioButton extends Component {
10 |
11 | static propTypes = {
12 | label: PropTypes.string,
13 | theme: PropTypes.oneOf(THEME_NAME),
14 | primary: PropTypes.oneOf(PRIMARY_COLORS),
15 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
16 | checked: PropTypes.bool,
17 | disabled: PropTypes.bool,
18 | onSelect: PropTypes.func
19 | };
20 |
21 | static defaultProps = {
22 | theme: 'light',
23 | primary: PRIMARY,
24 | disabled: false
25 | };
26 |
27 | render() {
28 | const { theme, primary, value, checked, disabled, onSelect } = this.props;
29 | const primaryColor = COLOR[`${primary}500`].color;
30 |
31 | let status = (()=> {
32 | if (disabled) {
33 | return 'disabled'
34 | } else if (checked) {
35 | return 'checked'
36 | } else {
37 | return 'default'
38 | }
39 | })();
40 |
41 | const colorMap = {
42 | light: {
43 | disabled: '#000',
44 | checked: primaryColor,
45 | default: '#000'
46 | },
47 | dark: {
48 | disabled: '#fff',
49 | checked: primaryColor,
50 | default: '#fff'
51 | }
52 | };
53 |
54 | const opacityMap = {
55 | light: {
56 | checked: 1,
57 | default: 0.54,
58 | disabled: 0.26
59 | },
60 | dark: {
61 | checked: 1,
62 | default: 0.7,
63 | disabled: 0.3
64 | }
65 | };
66 |
67 | const underlayMap = {
68 | light: 'rgba(0,0,0,.12)',
69 | dark: 'rgba(255,255,255,.12)'
70 | }
71 |
72 | const labelColorMap = {
73 | light: '#000',
74 | dark: '#fff'
75 | };
76 |
77 | const CURR_COLOR = colorMap[theme][status];
78 | const OPACITY = opacityMap[theme][status];
79 | const LABEL_COLOR = labelColorMap[theme];
80 | const UNDERLAY_COLOR = underlayMap[theme];
81 |
82 | return (
83 | { disabled && !checked ? null : onSelect(value) }}
85 | underlayColor={disabled ? 'rgba(0,0,0,0)' : UNDERLAY_COLOR}
86 | activeOpacity={1}
87 | >
88 |
89 | { disabled && !checked ? null : onSelect(value) }}
93 | >
94 |
102 |
103 |
104 |
114 | {this.props.label}
115 |
116 |
117 |
118 |
119 | );
120 | };
121 | }
122 |
123 | const styles = StyleSheet.create({
124 | container: {
125 | flexDirection: 'row',
126 | alignItems: 'center',
127 | backgroundColor: 'rgba(0,0,0,0)'
128 | },
129 | labelContainer: {
130 | alignItems: 'center',
131 | flexDirection: 'row',
132 | flex: 1
133 | },
134 | label: {
135 | marginLeft: 16,
136 | opacity: COLOR.darkPrimaryOpacity.opacity
137 | }
138 | });
139 |
--------------------------------------------------------------------------------
/lib/RadioButtonGroup.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View} from "react-native";
3 | import RadioButton from './RadioButton';
4 | import { PRIMARY, PRIMARY_COLORS, THEME_NAME } from './config';
5 |
6 | export default class RadioButtonGroup extends Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | selected: props.selected || null
12 | };
13 | }
14 |
15 | static propTypes = {
16 | theme: PropTypes.oneOf(THEME_NAME),
17 | primary: PropTypes.oneOf(PRIMARY_COLORS),
18 | onSelect: PropTypes.func,
19 | selected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
20 | items: PropTypes.arrayOf(PropTypes.shape({
21 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
22 | label: PropTypes.string,
23 | disabled: PropTypes.bool
24 | }))
25 | };
26 |
27 | static defaultProps = {
28 | theme: 'light',
29 | primary: PRIMARY
30 | };
31 |
32 | componentDidMount = () => {
33 | const { selected } = this.state;
34 |
35 | if (selected) {
36 | this.value = selected;
37 | }
38 | };
39 |
40 | render() {
41 | const { items, theme, primary } = this.props;
42 | return (
43 |
44 | {
45 | items && items.length && items.map((item) => {
46 | const { value } = item;
47 | return (
48 |
58 | );
59 | })
60 | }
61 |
62 | );
63 | };
64 |
65 | onSelect = (value) => {
66 | const { onSelect } = this.props;
67 |
68 | this.setState({
69 | selected: value
70 | });
71 |
72 | onSelect && onSelect(value);
73 | };
74 |
75 | /**
76 | * Get the value of checked RadioButton in RadioButtonGroup. Often use in form.
77 | * @returns {string}
78 | */
79 | get value() {
80 | return this.state.selected
81 | }
82 |
83 | /**
84 | * Specifies that which RadioButton should be pre-selected
85 | * @param {string} value
86 | */
87 | set value(value) {
88 | this.onSelect(value);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/Ripple.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, Text, TouchableNativeFeedback} from "react-native";
3 | import { default as PolyfillRipple } from './polyfill/Ripple';
4 | import { isCompatible } from './helpers';
5 |
6 | export default class Ripple extends Component {
7 |
8 | static propTypes = {
9 | color: PropTypes.string,
10 | onPress: PropTypes.func,
11 | onLongPress: PropTypes.func,
12 | children: PropTypes.node.isRequired
13 | };
14 |
15 | static defaultProps = {
16 | color: 'rgba(0,0,0,.2)'
17 | };
18 |
19 | render() {
20 | const { color, onPress, onLongPress, children, ...other } = this.props;
21 |
22 | if (!isCompatible('TouchableNativeFeedback')) {
23 | return (
24 |
30 | {/* Stops fatal crash with out of bounds animation */}
31 |
32 | {children}
33 |
34 |
35 | )
36 | }
37 |
38 | return (
39 |
44 |
45 | {children}
46 |
47 |
48 | );
49 | }
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/lib/Subheader.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {StyleSheet, View, Text} from "react-native";
3 | import { TYPO, THEME_NAME } from './config';
4 | import { getColor } from './helpers';
5 |
6 | export default class Subheader extends Component {
7 |
8 | static propTypes = {
9 | text: PropTypes.string.isRequired,
10 | color: PropTypes.string,
11 | inset: PropTypes.bool,
12 | theme: PropTypes.oneOf(THEME_NAME),
13 | lines: PropTypes.number
14 | };
15 |
16 | static defaultProps = {
17 | color: 'rgba(0,0,0,.54)',
18 | inset: false,
19 | theme: 'light',
20 | lines: 1
21 | };
22 |
23 | render() {
24 | const { text, color, inset, lines } = this.props;
25 |
26 | return (
27 |
32 |
39 | {text}
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | const styles = StyleSheet.create({
47 | container: {
48 | padding: 16
49 | },
50 | text: TYPO.paperFontBody1
51 | });
--------------------------------------------------------------------------------
/lib/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, Text} from "react-native";
3 | import { TYPO, PRIMARY, THEME_NAME, PRIMARY_COLORS } from './config';
4 | import { getColor } from './helpers';
5 | import Icon from './Icon';
6 | import IconToggle from './IconToggle';
7 |
8 | export default class Toolbar extends Component {
9 |
10 | static propTypes = {
11 | title: PropTypes.string,
12 | theme: PropTypes.oneOf(THEME_NAME),
13 | primary: PropTypes.oneOf(PRIMARY_COLORS),
14 | style: View.propTypes.style,
15 | leftIconStyle: PropTypes.object,
16 | rightIconStyle: PropTypes.object,
17 | elevation: PropTypes.number,
18 | overrides: PropTypes.shape({
19 | backgroundColor: PropTypes.string,
20 | titleColor: PropTypes.string,
21 | leftIconColor: PropTypes.string,
22 | rightIconColor: PropTypes.string
23 | }),
24 | icon: PropTypes.string,
25 | onIconPress: PropTypes.func,
26 | actions: PropTypes.arrayOf(PropTypes.shape({
27 | icon: PropTypes.string.isRequired,
28 | onPress: PropTypes.func,
29 | counter: PropTypes.shape(),
30 | onLayout: PropTypes.func
31 | })),
32 | onLayout: PropTypes.func
33 | };
34 |
35 | static defaultProps = {
36 | theme: 'dark',
37 | primary: PRIMARY,
38 | elevation: 4
39 | };
40 |
41 | render() {
42 | const { title, theme, primary, style, leftIconStyle, rightIconStyle, elevation, overrides, icon, onIconPress, actions, onLayout } = this.props;
43 |
44 | const themeMap = {
45 | light: {
46 | backgroundColor: '#ffffff',
47 | color: 'rgba(0,0,0,.87)',
48 | leftIconColor: 'rgba(0,0,0,.54)',
49 | rightIconColor: 'rgba(0,0,0,.54)'
50 | },
51 | dark: {
52 | backgroundColor: getColor(primary),
53 | color: 'rgba(255,255,255,.87)',
54 | leftIconColor: 'rgba(255,255,255,.87)',
55 | rightIconColor: 'rgba(255,255,255,.87)'
56 | }
57 | };
58 |
59 | const opacityMap = {
60 | light: .38,
61 | dark: .30
62 | };
63 |
64 | const styleMap = {
65 | backgroundColor: overrides && overrides.backgroundColor ? getColor(overrides.backgroundColor) : themeMap[theme].backgroundColor,
66 | color: overrides && overrides.color ? getColor(overrides.color) : themeMap[theme].color,
67 | leftIconColor: overrides && overrides.leftIconColor ? getColor(overrides.leftIconColor) : themeMap[theme].leftIconColor,
68 | rightIconColor: overrides && overrides.rightIconColor ? getColor(overrides.rightIconColor) : themeMap[theme].rightIconColor
69 | };
70 |
71 | return (
72 |
73 | {
74 | icon && (
75 |
79 |
84 |
85 | )
86 | }
87 | {
88 | !title ? this.props.children : (
89 |
96 | {title}
97 |
98 | )
99 | }
100 | {
101 | actions &&
102 | actions.map(function (action, i) {
103 | return (
104 |
112 |
117 |
118 | );
119 | })
120 | }
121 |
122 | );
123 | }
124 | }
125 |
126 | const styles = {
127 | toolbar: {
128 | position: 'absolute',
129 | top: 0,
130 | left: 0,
131 | right: 0,
132 | height: 56,
133 | flexDirection: 'row',
134 | alignItems: 'center'
135 | },
136 | title: {
137 | flex: 1,
138 | marginLeft: 16
139 | },
140 | leftIcon: {
141 | margin: 16
142 | },
143 | rightIcon: {
144 | margin: 16
145 | }
146 | };
147 |
--------------------------------------------------------------------------------
/lib/VectorIconComponent.js:
--------------------------------------------------------------------------------
1 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
2 |
3 | let _iconComponent = MaterialIcons;
4 |
5 | export default {
6 | set(component) {
7 | _iconComponent = component;
8 | },
9 |
10 | get() {
11 | return _iconComponent;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | import { typography, color } from 'react-native-material-design-styles';
2 |
3 | /**
4 | * Paper style color
5 | * @type {object}
6 | */
7 | export const COLOR = color;
8 |
9 | /**
10 | * Paper style typography
11 | * @type {object}
12 | */
13 | export const TYPO = typography;
14 |
15 | /**
16 | * Primary color name
17 | * @type {string}
18 | */
19 | export const PRIMARY = 'paperBlue';
20 |
21 | /**
22 | * Paper style color names
23 | * @type {string[]}
24 | */
25 | export const PRIMARY_COLORS = ['googleBlue', 'googleGreen', 'googleGrey', 'googleRed', 'googleYellow', 'paperAmber',
26 | 'paperBlue', 'paperBlueGrey', 'paperBrown', 'paperCyan', 'paperDeepOrange', 'paperDeepPurple', 'paperGreen',
27 | 'paperGrey', 'paperIndigo', 'paperLightBlue', 'paperLightGreen', 'paperLime', 'paperOrange', 'paperPink',
28 | 'paperPurple', 'paperRed', 'paperTeal', 'paperYellow'];
29 |
30 | /**
31 | * Themes
32 | * @type {string[]}
33 | */
34 | export const THEME_NAME = ['light', 'dark'];
35 |
--------------------------------------------------------------------------------
/lib/helpers.js:
--------------------------------------------------------------------------------
1 | import { Platform } from 'react-native';
2 | import { PRIMARY, COLOR } from './config';
3 |
4 | /**
5 | * Detect whether a color is a hex code/rgba or a paper element style
6 | * @param string
7 | * @returns {*}
8 | */
9 | export function getColor(string) {
10 | if (string) {
11 | if (string.indexOf('#') > -1 || string.indexOf('rgba') > -1) {
12 | return string;
13 | }
14 |
15 | if (COLOR[string]) {
16 | return COLOR[string].color;
17 | }
18 |
19 | if (COLOR[`${string}500`]) {
20 | return COLOR[`${string}500`].color;
21 | }
22 | }
23 |
24 | return COLOR[`${PRIMARY}500`].color;
25 | }
26 |
27 | /**
28 | * Detect whether a specific feature is compatible with the device
29 | * @param feature
30 | * @returns bool
31 | */
32 | export function isCompatible(feature) {
33 | const version = Platform.Version;
34 |
35 | switch (feature) {
36 | case 'TouchableNativeFeedback':
37 | return version >= 21;
38 | break;
39 | case 'elevation':
40 | return version >= 21;
41 | break;
42 | default:
43 | return true;
44 | break;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | export { COLOR, TYPO, PRIMARY_COLORS, THEME_NAME } from './config';
2 | export { default as Avatar } from './Avatar';
3 | export { default as Button } from './Button';
4 | export { default as Card } from './Card';
5 | export { default as Checkbox } from './Checkbox';
6 | export { default as CheckboxGroup } from './CheckboxGroup';
7 | export { default as Drawer } from './Drawer';
8 | export { default as Divider } from './Divider';
9 | export { default as Icon } from './Icon';
10 | export { default as IconToggle } from './IconToggle';
11 | //export { default as List } from './List';
12 | export { default as RadioButton } from './RadioButton';
13 | export { default as Ripple } from './Ripple';
14 | export { default as RadioButtonGroup } from './RadioButtonGroup';
15 | export { default as Subheader } from './Subheader';
16 | export { default as Toolbar } from './Toolbar';
17 | export { default as VectorIconComponent } from './VectorIconComponent';
18 |
--------------------------------------------------------------------------------
/lib/polyfill/Elevation.js:
--------------------------------------------------------------------------------
1 | import {Platform} from 'react-native'
2 |
3 | export default function (elevation){
4 | if(Platform.OS == 'ios'){
5 | if(elevation !== 0){
6 | return {
7 | shadowColor: "black",
8 | shadowOpacity: 0.3,
9 | shadowRadius: elevation,
10 | shadowOffset: {
11 | height: 2,
12 | width: 0
13 | }
14 | }
15 | }else{
16 | return {}
17 | }
18 | }else{
19 | return {
20 | elevation: elevation,
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/polyfill/Ripple.js:
--------------------------------------------------------------------------------
1 | import React, {Component, PropTypes} from "react";
2 | import {View, Animated, TouchableOpacity, Platform} from "react-native";
3 |
4 | import elevationPolyfill from './Elevation';
5 |
6 | export default class Ripple extends Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | scaleValue: new Animated.Value(0.001),
12 | fadeValue: new Animated.Value(0.001),
13 | pageX: null,
14 | pageY: null,
15 | rippling: false,
16 | size: null,
17 | location: null,
18 | longPress: false,
19 | elevation: props.elevation ? props.elevation[0] : null
20 | };
21 | }
22 |
23 | static propTypes = {
24 | rippleColor: PropTypes.string,
25 | elevation: PropTypes.array,
26 | onPress: PropTypes.func,
27 | onLongPress: PropTypes.func,
28 | style: PropTypes.oneOfType([View.propTypes.style, PropTypes.object, PropTypes.array]),
29 | children: PropTypes.element.isRequired
30 | };
31 |
32 | static defaultProps = {
33 | rippleColor: 'rgba(0,0,0,.2)',
34 | elevation: null
35 | };
36 |
37 | render() {
38 | const { rippleColor, onPress, onLongPress, children, style } = this.props;
39 | const { fadeValue ,size, pageX, pageY, rippling, scaleValue, location, elevation } = this.state;
40 | let outerStyle = {}, innerStyle = style;
41 |
42 | // Extract padding, margin from style since IOS overflow: hidden has issues with the shadow
43 | if(Platform.OS == 'ios') {
44 | ({outerStyle, innerStyle} = (style instanceof Array ? style : [style]).reduce((obj, styles) => {
45 | if (styles instanceof Object) {
46 | Object.entries(styles).forEach(
47 | ([name, value]) =>
48 | [
49 | 'marginLeft',
50 | 'marginRight',
51 | 'marginTop',
52 | 'marginBottom',
53 | 'marginHorizontal',
54 | 'marginVertical',
55 | 'margin',
56 | 'borderRadius',
57 | 'backgroundColor'
58 | ].indexOf(name) !== -1 ? obj.outerStyle[name] = value : obj.innerStyle[name] = value
59 | )
60 | }
61 | return obj
62 | }, {outerStyle: {}, innerStyle: {}}));
63 | }
64 |
65 | return (
66 |
73 |
76 |
77 | {children}
78 |
84 |
99 |
100 |
101 |
102 |
103 | );
104 | };
105 |
106 | _highlight = (e) => {
107 | const { elevation } = this.props;
108 |
109 | if (elevation) {
110 | this.setState({
111 | elevation: elevation[1]
112 | });
113 | }
114 |
115 | const { pageX, pageY } = e.nativeEvent;
116 |
117 | this.setState({
118 | rippling: true,
119 | pageX,
120 | pageY
121 | });
122 |
123 | this._getContainerDimensions(() => {
124 | const duration = (this.state.size / 100) * 110;
125 |
126 | Animated.timing(this.state.scaleValue, {
127 | toValue: 1,
128 | duration: duration < 500 || duration >= 1500 ? 500 : duration
129 | }).start();
130 |
131 | Animated.timing(this.state.fadeValue, {
132 | toValue: .2,
133 | duration: 200
134 | }).start();
135 | });
136 | };
137 |
138 | _unHighlight = () => {
139 | const { elevation } = this.props;
140 |
141 | if (elevation) {
142 | this.setState({
143 | elevation: elevation[0]
144 | });
145 | }
146 |
147 | this.setState({
148 | rippling: false
149 | });
150 |
151 | Animated.timing(this.state.scaleValue, {
152 | toValue: 0.001,
153 | duration: 100
154 | }).start();
155 |
156 | Animated.timing(this.state.fadeValue, {
157 | toValue: 0.001,
158 | duration: 100,
159 | }).start();
160 | };
161 |
162 | _getContainerDimensions = (next) => {
163 | this.refs.container.measure((x, y, width, height, pageX, pageY) => {
164 | this.setState({
165 | size: 3 * (width > height ? width : height),
166 | location: { pageX, pageY }
167 | }, next);
168 | })
169 | };
170 |
171 | }
172 |
173 | const styles = {
174 | container: {
175 | overflow: 'hidden'
176 | },
177 | background: {
178 | flex: 1,
179 | position: 'absolute',
180 | top: 0,
181 | right: 0,
182 | bottom: 0,
183 | left: 0
184 | },
185 | ripple: {
186 | position: 'absolute'
187 | }
188 | };
189 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-material-design",
3 | "version": "0.3.8",
4 | "description": "React Native Material Design Components",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "lint": "./node_modules/.bin/eslint ./lib"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/react-native-material-design/react-native-material-design"
13 | },
14 | "keywords": [
15 | "react-native",
16 | "material",
17 | "design",
18 | "ui",
19 | "components",
20 | "react-component",
21 | "android"
22 | ],
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/react-native-material-design/react-native-material-design/issues"
26 | },
27 | "homepage": "https://github.com/react-native-material-design/react-native-material-design",
28 | "dependencies": {
29 | "react-native-material-design-styles": "git+https://github.com/react-native-material-design/react-native-material-design-styles.git",
30 | "react-native-vector-icons": "^2.0.1"
31 | },
32 | "devDependencies": {
33 | "babel-eslint": "^4.1.6",
34 | "eslint": "^1.6.0",
35 | "eslint-plugin-react": "^3.5.1"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------