-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #369 from react-native-training/rating_component
[NEW FEATURE] Ratings Component
- Loading branch information
Showing
14 changed files
with
612 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ node_modules | |
*.log | ||
site | ||
coverage | ||
jsconfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
An extendable Ratings components for React Native with gestures and an intuitive API | ||
|
||
> This component was inspired from [react-native-ratings](https://github.com/Monte9/react-native-ratings) by [Monte Thakkar](https://github.com/Monte9). | ||
### Demo | ||
|
||
![Demo gif](http://i.imgur.com/hpo67Dq.gifv) | ||
|
||
```js | ||
import { Rating } from 'react-native-elements'; | ||
|
||
ratingCompleted(rating) { | ||
console.log("Rating is: " + rating) | ||
} | ||
|
||
<Rating | ||
showRating | ||
onFinishRating={this.ratingCompleted} | ||
style={{ paddingVertical: 10 }} | ||
/> | ||
|
||
<Rating | ||
type='heart' | ||
ratingCount={3} | ||
imageSize={60} | ||
showRating | ||
onFinishRating={this.ratingCompleted} | ||
/> | ||
|
||
|
||
const WATER_IMAGE = require('./water.png') | ||
|
||
<Rating | ||
type='custom' | ||
ratingImage={WATER_IMAGE} | ||
ratingColor='#3498db' | ||
ratingBackgroundColor='#c8c7c8' | ||
ratingCount={10} | ||
imageSize={30} | ||
onFinishRating={this.ratingCompleted} | ||
style={{ paddingVertical: 10 }} | ||
/> | ||
``` | ||
|
||
#### Rating Props | ||
|
||
| prop | default | type | description | | ||
| ---- | ---- | ----| ---- | | ||
| **onFinishRating** | none | function | Callback method when the user finishes rating. Gives you the final rating value as a whole number **(required)** | | ||
| type | star | string | Choose one of the built-in types: `star`, `rocket`, `bell`, `heart` or use type `custom` to render a custom image (optional) | | ||
| ratingImage | star | string | Pass in a custom image source; use this along with `type='custom'` prop above (optional) | | ||
| ratingColor | #f1c40f | string (color) | Pass in a custom fill-color for the rating icon; use this along with `type='custom'` prop above (optional) | | ||
| ratingBackgroundColor | white | string (color) | Pass in a custom background-fill-color for the rating icon; use this along with `type='custom'` prop above (optional) | | ||
| ratingCount | 5 | number | The number of rating images to display (optional) | | ||
| imageSize | 50 | number | The size of each rating image (optional) | | ||
| showRating | none | boolean | Displays the Built-in Rating UI to show the rating value in real-time (optional) | | ||
| style | none | function | Exposes style prop to add additonal styling to the container view (optional) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"compilerOptions": { | ||
"allowSyntheticDefaultImports": true | ||
}, | ||
"exclude": [ | ||
"node_modules" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
var _ = require('lodash'); | ||
|
||
import PropTypes from 'prop-types'; | ||
import React, { Component } from 'react'; | ||
import { | ||
View, | ||
Animated, | ||
PanResponder, | ||
Dimensions, | ||
Image, | ||
StyleSheet, | ||
} from 'react-native'; | ||
|
||
import Text from '../text/Text'; | ||
|
||
const SCREEN_WIDTH = Dimensions.get('window').width; | ||
const SCREEN_HEIGHT = Dimensions.get('window').height; | ||
|
||
const STAR_IMAGE = require('./images/star.png'); | ||
const HEART_IMAGE = require('./images/heart.png'); | ||
const ROCKET_IMAGE = require('./images/rocket.png'); | ||
const BELL_IMAGE = require('./images/bell.png'); | ||
|
||
const STAR_WIDTH = 60; | ||
|
||
const TYPES = { | ||
star: { | ||
source: STAR_IMAGE, | ||
color: '#f1c40f', | ||
backgroundColor: 'white', | ||
}, | ||
heart: { | ||
source: HEART_IMAGE, | ||
color: '#e74c3c', | ||
backgroundColor: 'white', | ||
}, | ||
rocket: { | ||
source: ROCKET_IMAGE, | ||
color: '#2ecc71', | ||
backgroundColor: 'white', | ||
}, | ||
bell: { | ||
source: BELL_IMAGE, | ||
color: '#f39c12', | ||
backgroundColor: 'white', | ||
}, | ||
}; | ||
|
||
export default class Rating extends Component { | ||
static defaultProps = { | ||
type: 'star', | ||
ratingImage: require('./images/star.png'), | ||
ratingColor: '#f1c40f', | ||
ratingBackgroundColor: 'white', | ||
ratingCount: 5, | ||
imageSize: STAR_WIDTH, | ||
onFinishRating: () => console.log('Attach a function here.'), | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
|
||
const { onFinishRating } = this.props; | ||
|
||
const position = new Animated.ValueXY(); | ||
const newValue = new Animated.ValueXY(); | ||
newValue.setValue({ x: 0, y: 500 }); | ||
|
||
const panResponder = PanResponder.create({ | ||
onStartShouldSetPanResponder: () => true, | ||
onPanResponderMove: (event, gesture) => { | ||
position.setValue({ x: gesture.dx, y: gesture.dy }); | ||
this.setState({ value: gesture.dx }); | ||
}, | ||
onPanResponderRelease: (event, gesture) => { | ||
onFinishRating(this.getCurrentRating()); | ||
}, | ||
}); | ||
|
||
this.state = { panResponder, position }; | ||
} | ||
|
||
getPrimaryViewStyle() { | ||
const { position } = this.state; | ||
const { imageSize, ratingCount, type } = this.props; | ||
|
||
const color = TYPES[type].color; | ||
|
||
const width = position.x.interpolate({ | ||
inputRange: [ | ||
-ratingCount * (imageSize / 2), | ||
0, | ||
ratingCount * (imageSize / 2), | ||
], | ||
outputRange: [0, ratingCount * imageSize / 2, ratingCount * imageSize], | ||
extrapolate: 'clamp', | ||
}); | ||
|
||
return { | ||
backgroundColor: color, | ||
width, | ||
height: width ? imageSize : 0, | ||
}; | ||
} | ||
|
||
getSecondaryViewStyle() { | ||
const { position } = this.state; | ||
const { imageSize, ratingCount, type } = this.props; | ||
|
||
const backgroundColor = TYPES[type].backgroundColor; | ||
|
||
const width = position.x.interpolate({ | ||
inputRange: [ | ||
-ratingCount * (imageSize / 2), | ||
0, | ||
ratingCount * (imageSize / 2), | ||
], | ||
outputRange: [ratingCount * imageSize, ratingCount * imageSize / 2, 0], | ||
extrapolate: 'clamp', | ||
}); | ||
|
||
return { | ||
backgroundColor, | ||
width, | ||
height: width ? imageSize : 0, | ||
}; | ||
} | ||
|
||
renderRatings() { | ||
const { imageSize, ratingCount, type } = this.props; | ||
const source = TYPES[type].source; | ||
|
||
return _(ratingCount).times(index => ( | ||
<View key={index} style={styles.starContainer}> | ||
<Image | ||
source={source} | ||
style={{ width: imageSize, height: imageSize }} | ||
/> | ||
</View> | ||
)); | ||
} | ||
|
||
getCurrentRating() { | ||
const { value } = this.state; | ||
const { imageSize, ratingCount } = this.props; | ||
const startingValue = ratingCount / 2; | ||
let currentRating = 0; | ||
|
||
if (value > ratingCount * imageSize / 2) { | ||
currentRating = ratingCount; | ||
} else if (value > imageSize) { | ||
currentRating = Math.ceil(startingValue + value / imageSize); | ||
} else if (value < -ratingCount * imageSize / 2) { | ||
currentRating = 0; | ||
} else if (value < imageSize) { | ||
currentRating = Math.ceil(startingValue + value / imageSize); | ||
} else { | ||
currentRating = Math.ceil(startingValue); | ||
} | ||
|
||
return currentRating; | ||
} | ||
|
||
displayCurrentRating() { | ||
const { ratingCount, type } = this.props; | ||
|
||
const color = TYPES[type].color; | ||
|
||
return ( | ||
<View style={styles.ratingView}> | ||
<Text style={styles.ratingText}>Rating: </Text> | ||
<Text style={[styles.currentRatingText, { color }]}> | ||
{this.getCurrentRating()} | ||
</Text> | ||
<Text style={styles.maxRatingText}>/{ratingCount}</Text> | ||
</View> | ||
); | ||
} | ||
|
||
render() { | ||
const { | ||
type, | ||
ratingImage, | ||
ratingColor, | ||
ratingBackgroundColor, | ||
style, | ||
showRating, | ||
} = this.props; | ||
|
||
if (type === 'custom') { | ||
custom = { | ||
source: ratingImage, | ||
color: ratingColor, | ||
backgroundColor: ratingBackgroundColor, | ||
}; | ||
TYPES.custom = custom; | ||
} | ||
|
||
return ( | ||
<View style={style}> | ||
{showRating && this.displayCurrentRating()} | ||
<View | ||
style={styles.starsWrapper} | ||
{...this.state.panResponder.panHandlers} | ||
> | ||
<View style={styles.starsInsideWrapper}> | ||
<Animated.View style={this.getPrimaryViewStyle()} /> | ||
<Animated.View style={this.getSecondaryViewStyle()} /> | ||
</View> | ||
{this.renderRatings()} | ||
</View> | ||
</View> | ||
); | ||
} | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
starsWrapper: { | ||
flexDirection: 'row', | ||
}, | ||
starsInsideWrapper: { | ||
position: 'absolute', | ||
top: 0, | ||
left: 0, | ||
flexDirection: 'row', | ||
}, | ||
ratingView: { | ||
flexDirection: 'row', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
paddingBottom: 5, | ||
}, | ||
ratingText: { | ||
fontSize: 15, | ||
textAlign: 'center', | ||
fontFamily: 'Trebuchet MS', | ||
color: '#34495e', | ||
}, | ||
currentRatingText: { | ||
fontSize: 30, | ||
textAlign: 'center', | ||
fontFamily: 'Trebuchet MS', | ||
}, | ||
maxRatingText: { | ||
fontSize: 18, | ||
textAlign: 'center', | ||
fontFamily: 'Trebuchet MS', | ||
color: '#34495e', | ||
}, | ||
}); | ||
|
||
Rating.propTypes = { | ||
type: PropTypes.string, | ||
ratingImage: Image.propTypes.source, | ||
ratingColor: PropTypes.string, | ||
ratingBackgroundColor: PropTypes.string, | ||
ratingCount: PropTypes.number, | ||
imageSize: PropTypes.number, | ||
onFinishRating: PropTypes.func, | ||
showRating: PropTypes.bool, | ||
style: PropTypes.any, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import toJson from 'enzyme-to-json'; | ||
import Rating from '../Rating'; | ||
|
||
describe('Rating Component', () => { | ||
it('should render without issues', () => { | ||
const component = shallow(<Rating />); | ||
|
||
expect(component.length).toBe(1); | ||
expect(toJson(component)).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.