Skip to content

Commit 99b2b04

Browse files
authored
feat: add quarter and three-quarter star support (#83)
* feat: add quarter and three-quarter star support - Add StarQuarter (33% fill) and StarThreeQuarter (66% fill) components - Refactor all star components to use consistent LinearGradient approach - Add enableQuarterStar prop to StarRating and StarRatingDisplay - Update getStars utility function to handle quarter star logic - Maintain backward compatibility (enableQuarterStar defaults to false) This allows users to display more granular ratings with 0.25 step increments (0, 0.25, 0.5, 0.75, 1.0, etc.) * chore: resolve comments * chore: update docs with new prop
1 parent 028b43d commit 99b2b04

File tree

7 files changed

+243
-88
lines changed

7 files changed

+243
-88
lines changed

README.md

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,71 +7,70 @@ A customizable, animated star rating component for React Native. Compatible with
77
![Demo](https://github.com/benediktviebahn/react-native-star-rating-widget/raw/master/media/demo.gif)
88

99
## Installation
10+
1011
1. install react-native-star-rating-widget
11-
`npm install react-native-star-rating-widget --save` or `yarn add react-native-star-rating-widget`
12+
`npm install react-native-star-rating-widget --save` or `yarn add react-native-star-rating-widget`
1213
2. if not already installed, add [react-native-svg](https://github.com/react-native-community/react-native-svg)
1314

1415
## Usage
15-
This package exports an
16+
17+
This package exports an
1618

1719
### Interactive `StarRating` component
20+
1821
```js
1922
import StarRating from 'react-native-star-rating-widget';
2023

2124
const Example = () => {
2225
const [rating, setRating] = useState(0);
23-
return (
24-
<StarRating
25-
rating={rating}
26-
onChange={setRating}
27-
/>
28-
);
26+
return <StarRating rating={rating} onChange={setRating} />;
2927
};
3028
```
3129

3230
### Non-Interactive `StarRatingDisplay` component
31+
3332
```js
3433
import { StarRatingDisplay } from 'react-native-star-rating-widget';
3534

3635
const Example = () => {
37-
return (
38-
<StarRatingDisplay
39-
rating={4.5}
40-
/>
41-
);
36+
return <StarRatingDisplay rating={4.5} />;
4237
};
4338
```
4439

4540
See [example/src](example/src) for more examples.
4641

4742
## Props
43+
4844
### `StarRating` Props
49-
| Name | Type | Default | Description |
50-
| ----------------- | ----------------------- | ---------------- | ----------------------------------------------------- |
51-
| rating | number | **REQUIRED** | Rating Value. Should be between 0 and `maxStars` |
52-
| onChange | (rating: number) => void | **REQUIRED** | called when rating changes |
53-
| maxStars | number | 5 | number of stars |
54-
| starSize | number | 32 | star size |
55-
| color | string | "#fdd835" | star color |
56-
| emptyColor | string | same as `color` | empty star color |
57-
| style | object | undefined | optional style |
58-
| starStyle | object | undefined | optional star style |
59-
| enableHalfStar | boolean | true | enable or disable display of half stars |
60-
| enableSwiping | boolean | true | enable or disable swiping |
61-
| onRatingStart | (rating: number) => void | undefined | called when the interaction starts, before `onChange` |
62-
| onRatingEnd | (rating: number) => void | undefined | called when the interaction starts, after `onChange` |
63-
| animationConfig | see [AnimationConfig](#animationConfig) | see [AnimationConfig](#animationConfig) | animation configuration object |
64-
| StarIconComponent | (props: { index: number; size: number; color: string; type: "full" \| "half" \| "empty"; }) => JSX.Element | [StarIcon](https://github.com/bviebahn/react-native-star-rating-widget/blob/master/src/StarIcon.tsx) | Icon component |
65-
| accessibilityLabel | string | star rating. %value% stars. use custom actions to set rating. | The label used on the star component |
66-
| accessabilityIncrementLabel | string | increment | The label for the increment action |
67-
| accessabilityDecrementLabel | string | decrement | The label for the decrement action. |
68-
| accessabilityActivateLabel | string | activate (default) | The label for the activate action. |
69-
| accessibilityAdjustmentLabel | string | %value% stars | The label that is announced after adjustment action |
45+
46+
| Name | Type | Default | Description |
47+
| ---------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
48+
| rating | number | **REQUIRED** | Rating Value. Should be between 0 and `maxStars` |
49+
| onChange | (rating: number) => void | **REQUIRED** | called when rating changes |
50+
| maxStars | number | 5 | number of stars |
51+
| starSize | number | 32 | star size |
52+
| color | string | "#fdd835" | star color |
53+
| emptyColor | string | same as `color` | empty star color |
54+
| style | object | undefined | optional style |
55+
| starStyle | object | undefined | optional star style |
56+
| step | "full" \| "half" \| "quarter" | "half" | step size for rating - full stars, half stars, or quarter stars |
57+
| enableSwiping | boolean | true | enable or disable swiping |
58+
| onRatingStart | (rating: number) => void | undefined | called when the interaction starts, before `onChange` |
59+
| onRatingEnd | (rating: number) => void | undefined | called when the interaction starts, after `onChange` |
60+
| animationConfig | see [AnimationConfig](#animationConfig) | see [AnimationConfig](#animationConfig) | animation configuration object |
61+
| StarIconComponent | (props: { index: number; size: number; color: string; type: "full" \| "half" \| "empty"; }) => JSX.Element | [StarIcon](https://github.com/bviebahn/react-native-star-rating-widget/blob/master/src/StarIcon.tsx) | Icon component |
62+
| accessibilityLabel | string | star rating. %value% stars. use custom actions to set rating. | The label used on the star component |
63+
| accessabilityIncrementLabel | string | increment | The label for the increment action |
64+
| accessabilityDecrementLabel | string | decrement | The label for the decrement action. |
65+
| accessabilityActivateLabel | string | activate (default) | The label for the activate action. |
66+
| accessibilityAdjustmentLabel | string | %value% stars | The label that is announced after adjustment action |
7067

7168
### `StarRatingDisplay` Props
69+
7270
The `StarRatingDisplay` component accepts mostly the same props as `StarRating` except those that are interaction related props such as `onChange`, `enableSwiping`, `onRatingStart` etc.
7371

7472
### AnimationConfig
73+
7574
| Name | Type | Default | Description |
7675
| -------- | ------------------ | ----------------- | ------------------------------------------ |
7776
| scale | number | 1.2 | star animation scale value |

example/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import BasicExample from './BasicExample';
55
import CustomIconExample from './CustomIconExample';
66
import StarRatingDisplayExample from './StarRatingDisplayExample';
77
import ClearOnCurrentRatingTapExample from './ClearOnCurrentRatingTapExample';
8+
import StepPropExample from './StepPropExample';
89

910
export default function App() {
1011
return (
1112
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
13+
<StepPropExample />
1214
<BasicExample />
1315
<CustomIconExample />
1416
<StarRatingDisplayExample />

example/src/StepPropExample.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import { Text, StyleSheet, View } from 'react-native';
3+
import StarRating from 'react-native-star-rating-widget';
4+
import ExampleContainer from './ExampleContainer';
5+
6+
export default function StepPropExample() {
7+
const [fullRating, setFullRating] = React.useState(3);
8+
const [halfRating, setHalfRating] = React.useState(2.5);
9+
const [quarterRating, setQuarterRating] = React.useState(3.75);
10+
11+
return (
12+
<ExampleContainer title="Step Prop Examples">
13+
<View style={styles.section}>
14+
<Text style={styles.label}>Full Stars (step="full")</Text>
15+
<StarRating rating={fullRating} onChange={setFullRating} step="full" />
16+
<Text style={styles.rating}>Rating: {fullRating}</Text>
17+
</View>
18+
19+
<View style={styles.section}>
20+
<Text style={styles.label}>Half Stars (step="half" - default)</Text>
21+
<StarRating rating={halfRating} onChange={setHalfRating} step="half" />
22+
<Text style={styles.rating}>Rating: {halfRating}</Text>
23+
</View>
24+
25+
<View style={styles.section}>
26+
<Text style={styles.label}>Quarter Stars (step="quarter")</Text>
27+
<StarRating
28+
rating={quarterRating}
29+
onChange={setQuarterRating}
30+
step="quarter"
31+
/>
32+
<Text style={styles.rating}>Rating: {quarterRating}</Text>
33+
</View>
34+
</ExampleContainer>
35+
);
36+
}
37+
38+
const styles = StyleSheet.create({
39+
section: {
40+
alignItems: 'center',
41+
marginBottom: 16,
42+
width: '100%',
43+
},
44+
label: {
45+
fontSize: 14,
46+
color: '#666',
47+
marginBottom: 8,
48+
},
49+
rating: {
50+
fontSize: 12,
51+
color: '#999',
52+
marginTop: 4,
53+
},
54+
});

src/StarIcon.tsx

Lines changed: 103 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import React from 'react';
22
import { I18nManager, ViewStyle } from 'react-native';
3-
import Svg, { Path, Rect } from 'react-native-svg';
3+
import Svg, { Path, Defs, LinearGradient, Stop } from 'react-native-svg';
44

55
export type StarIconProps = {
66
index: number;
77
size: number;
88
color: string;
9-
type: 'full' | 'half' | 'empty';
9+
type: 'full' | 'half' | 'quarter' | 'three-quarter' | 'empty';
10+
};
11+
12+
const RTL_TRANSFORM: ViewStyle = {
13+
transform: [{ rotateY: '180deg' }],
1014
};
1115

1216
const StarBorder = ({ size, color }: Omit<StarIconProps, 'type'>) => (
@@ -18,40 +22,108 @@ const StarBorder = ({ size, color }: Omit<StarIconProps, 'type'>) => (
1822
</Svg>
1923
);
2024

21-
const StarFull = ({ size, color }: Omit<StarIconProps, 'type'>) => (
22-
<Svg height={size} viewBox="0 0 24 24" width={size}>
23-
<Path d="M0 0h24v24H0z" fill="none" />
24-
<Path d="M0 0h24v24H0z" fill="none" />
25-
<Path
26-
fill={color}
27-
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
28-
/>
29-
</Svg>
30-
);
25+
const StarFull = ({ size, color, index }: Omit<StarIconProps, 'type'>) => {
26+
const gradientId = `full-gradient-${index}`;
27+
return (
28+
<Svg height={size} viewBox="0 0 24 24" width={size}>
29+
<Defs>
30+
<LinearGradient id={gradientId} x1="0" y1="0" x2="1" y2="0">
31+
<Stop offset="100%" stopColor={color} stopOpacity="1" />
32+
<Stop offset="100%" stopColor={color} stopOpacity="0" />
33+
</LinearGradient>
34+
</Defs>
35+
<Path
36+
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
37+
fill={`url(#${gradientId})`}
38+
stroke={color}
39+
strokeWidth="1.5"
40+
/>
41+
</Svg>
42+
);
43+
};
3144

32-
const RTL_TRANSFORM: ViewStyle = {
33-
transform: [{ rotateY: '180deg' }],
45+
const StarQuarter = ({ size, color, index }: Omit<StarIconProps, 'type'>) => {
46+
const gradientId = `quarter-gradient-${index}`;
47+
return (
48+
<Svg height={size} viewBox="0 0 24 24" width={size}>
49+
<Defs>
50+
<LinearGradient id={gradientId} x1="0" y1="0" x2="1" y2="0">
51+
<Stop offset="33%" stopColor={color} stopOpacity="1" />
52+
<Stop offset="33%" stopColor={color} stopOpacity="0" />
53+
</LinearGradient>
54+
</Defs>
55+
<Path
56+
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
57+
fill={`url(#${gradientId})`}
58+
stroke={color}
59+
strokeWidth="1.5"
60+
/>
61+
</Svg>
62+
);
3463
};
3564

36-
const StarHalf = ({ size, color }: Omit<StarIconProps, 'type'>) => (
37-
<Svg
38-
height={size}
39-
viewBox="0 0 24 24"
40-
width={size}
41-
style={I18nManager.isRTL ? RTL_TRANSFORM : undefined}
42-
>
43-
<Rect fill="none" height="24" width="24" x="0" />
44-
<Path
45-
fill={color}
46-
d="M22,9.24l-7.19-0.62L12,2L9.19,8.63L2,9.24l5.46,4.73L5.82,21L12,17.27L18.18,21l-1.63-7.03L22,9.24z M12,15.4V6.1 l1.71,4.04l4.38,0.38l-3.32,2.88l1,4.28L12,15.4z"
47-
/>
48-
</Svg>
49-
);
65+
const StarThreeQuarter = ({ size, color, index }: Omit<StarIconProps, 'type'>) => {
66+
const gradientId = `three-quarter-gradient-${index}`;
67+
return (
68+
<Svg height={size} viewBox="0 0 24 24" width={size}>
69+
<Defs>
70+
<LinearGradient id={gradientId} x1="0" y1="0" x2="1" y2="0">
71+
<Stop offset="66%" stopColor={color} stopOpacity="1" />
72+
<Stop offset="66%" stopColor={color} stopOpacity="0" />
73+
</LinearGradient>
74+
</Defs>
75+
<Path
76+
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
77+
fill={`url(#${gradientId})`}
78+
stroke={color}
79+
strokeWidth="1.5"
80+
/>
81+
</Svg>
82+
);
83+
};
5084

51-
const StarIcon = ({ index, type, size, color }: StarIconProps) => {
52-
const Component =
53-
type === 'full' ? StarFull : type === 'half' ? StarHalf : StarBorder;
85+
const StarHalf = ({ size, color, index }: Omit<StarIconProps, 'type'>) => {
86+
const gradientId = `half-gradient-${index}`;
87+
return (
88+
<Svg
89+
height={size}
90+
viewBox="0 0 24 24"
91+
width={size}
92+
style={I18nManager.isRTL ? RTL_TRANSFORM : undefined}
93+
>
94+
<Defs>
95+
<LinearGradient id={gradientId} x1="0" y1="0" x2="1" y2="0">
96+
<Stop offset="50%" stopColor={color} stopOpacity="1" />
97+
<Stop offset="50%" stopColor={color} stopOpacity="0" />
98+
</LinearGradient>
99+
</Defs>
100+
<Path
101+
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
102+
fill={`url(#${gradientId})`}
103+
stroke={color}
104+
strokeWidth="1.5"
105+
/>
106+
</Svg>
107+
);
108+
};
54109

110+
const getStarComponent = (type: StarIconProps['type']) => {
111+
switch (type) {
112+
case 'full':
113+
return StarFull;
114+
case 'half':
115+
return StarHalf;
116+
case 'quarter':
117+
return StarQuarter;
118+
case 'three-quarter':
119+
return StarThreeQuarter;
120+
default:
121+
return StarBorder;
122+
}
123+
};
124+
125+
const StarIcon = ({ index, type, size, color }: StarIconProps) => {
126+
const Component = getStarComponent(type);
55127
return <Component index={index} size={size} color={color} />;
56128
};
57129

0 commit comments

Comments
 (0)