react-native-star-rating-widget
Version:
A star rating widget for react native
195 lines (194 loc) • 6.98 kB
JavaScript
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React from 'react';
import { PanResponder, StyleSheet, View, Animated, Easing, I18nManager, AccessibilityInfo } from 'react-native';
import StarIcon from './StarIcon';
import { getStars } from './utils';
const defaultColor = '#fdd835';
const defaultAnimationConfig = {
easing: Easing.elastic(2),
duration: 300,
scale: 1.2,
delay: 300
};
const StarRating = _ref => {
let {
rating,
maxStars = 5,
starSize = 32,
onChange,
color = defaultColor,
emptyColor = color,
enableHalfStar = true,
enableSwiping = true,
onRatingStart,
onRatingEnd,
animationConfig = defaultAnimationConfig,
style,
starStyle,
StarIconComponent = StarIcon,
testID,
accessibilityLabel = 'star rating. %value% stars. use custom actions to set rating.',
accessabilityIncrementLabel = 'increment',
accessabilityDecrementLabel = 'decrement',
accessabilityActivateLabel = 'activate (default)',
accessibilityAdjustmentLabel = '%value% stars'
} = _ref;
const width = React.useRef();
const [isInteracting, setInteracting] = React.useState(false);
const [stagedRating, setStagedRating] = React.useState(rating);
const panResponder = React.useMemo(() => {
const calculateRating = function (x) {
let isRTL = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : I18nManager.isRTL;
if (!width.current) return rating;
if (isRTL) {
return calculateRating(width.current - x, false);
}
const newRating = Math.max(0, Math.min(Math.round(x / width.current * maxStars * 2 + 0.2) / 2, maxStars));
return enableHalfStar ? newRating : Math.ceil(newRating);
};
const handleChange = newRating => {
if (newRating !== rating) {
onChange(newRating);
}
};
return PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => true,
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderMove: e => {
if (enableSwiping) {
const newRating = calculateRating(e.nativeEvent.locationX);
handleChange(newRating);
}
},
onPanResponderStart: e => {
const newRating = calculateRating(e.nativeEvent.locationX);
onRatingStart === null || onRatingStart === void 0 ? void 0 : onRatingStart(newRating);
handleChange(newRating);
setInteracting(true);
},
onPanResponderEnd: e => {
const newRating = calculateRating(e.nativeEvent.locationX);
handleChange(newRating);
onRatingEnd === null || onRatingEnd === void 0 ? void 0 : onRatingEnd(newRating);
setTimeout(() => {
setInteracting(false);
}, animationConfig.delay || defaultAnimationConfig.delay);
},
onPanResponderTerminate: () => {
// called when user drags outside of the component
setTimeout(() => {
setInteracting(false);
}, animationConfig.delay || defaultAnimationConfig.delay);
}
});
}, [rating, maxStars, enableHalfStar, onChange, enableSwiping, onRatingStart, onRatingEnd, animationConfig.delay]);
return /*#__PURE__*/React.createElement(View, {
style: style
}, /*#__PURE__*/React.createElement(View, _extends({
style: styles.starRating
}, panResponder.panHandlers, {
onLayout: e => {
width.current = e.nativeEvent.layout.width;
},
testID: testID,
accessible: true,
accessibilityRole: "adjustable",
accessibilityLabel: accessibilityLabel.replace(/%value%/g, stagedRating.toString()),
accessibilityValue: {
min: 0,
max: enableHalfStar ? maxStars * 2 : maxStars,
now: enableHalfStar ? rating * 2 : rating // this has to be an integer
},
accessibilityActions: [{
name: 'increment',
label: accessabilityIncrementLabel
}, {
name: 'decrement',
label: accessabilityDecrementLabel
}, {
name: 'activate',
label: accessabilityActivateLabel
}],
onAccessibilityAction: event => {
const incrementor = enableHalfStar ? 0.5 : 1;
switch (event.nativeEvent.actionName) {
case 'increment':
if (stagedRating >= maxStars) {
AccessibilityInfo.announceForAccessibility(accessibilityAdjustmentLabel.replace(/%value%/g, `${maxStars}`));
} else {
AccessibilityInfo.announceForAccessibility(accessibilityAdjustmentLabel.replace(/%value%/g, `${stagedRating + incrementor}`));
setStagedRating(stagedRating + incrementor);
}
break;
case 'decrement':
if (stagedRating <= 0) {
AccessibilityInfo.announceForAccessibility(accessibilityAdjustmentLabel.replace(/%value%/g, `${0}`));
} else {
AccessibilityInfo.announceForAccessibility(accessibilityAdjustmentLabel.replace(/%value%/g, `${stagedRating - incrementor}`));
setStagedRating(stagedRating - incrementor);
}
break;
case 'activate':
onChange(stagedRating);
break;
}
}
}), getStars(rating, maxStars).map((starType, i) => {
return /*#__PURE__*/React.createElement(AnimatedIcon, {
key: i,
active: isInteracting && rating - i >= 0.5,
animationConfig: animationConfig,
style: starStyle
}, /*#__PURE__*/React.createElement(StarIconComponent, {
index: i,
type: starType,
size: starSize,
color: starType === 'empty' ? emptyColor : color
}));
})));
};
const AnimatedIcon = _ref2 => {
let {
active,
animationConfig,
children,
style
} = _ref2;
const {
scale = defaultAnimationConfig.scale,
easing = defaultAnimationConfig.easing,
duration = defaultAnimationConfig.duration
} = animationConfig;
const animatedSize = React.useRef(new Animated.Value(active ? scale : 1));
React.useEffect(() => {
const animation = Animated.timing(animatedSize.current, {
toValue: active ? scale : 1,
useNativeDriver: true,
easing,
duration
});
animation.start();
return animation.stop;
}, [active, scale, easing, duration]);
return /*#__PURE__*/React.createElement(Animated.View, {
pointerEvents: "none",
style: [styles.star, style, {
transform: [{
scale: animatedSize.current
}]
}]
}, children);
};
const styles = StyleSheet.create({
starRating: {
flexDirection: 'row',
alignSelf: 'flex-start'
},
star: {
marginHorizontal: 5
}
});
export default StarRating;
//# sourceMappingURL=StarRating.js.map