Skip to content

Commit bf231a4

Browse files
committed
feat(rating): add rating component
1 parent e290d4f commit bf231a4

10 files changed

Lines changed: 457 additions & 66 deletions

File tree

jest.config.snapshot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
'^.+\\.jsx?$': 'babel-jest',
88
'^.+\\.mdx?$': '<rootDir>/tests/mdxTransformer.js',
99
},
10+
transformIgnorePatterns: ['/node_modules/(?!react-native-svg).+\\.js$'],
1011
moduleNameMapper: {
1112
'^docz$': '<rootDir>/tests/doczMock.js',
1213
},

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"exenv": "1.2.2",
4343
"focus-trap": "5.0.0",
4444
"react-icons": "3.7.0",
45+
"react-native-svg": "9.4.0",
4546
"react-native-web": "0.11.2",
4647
"react-spring": "9.0.0-beta.1",
4748
"ts-essentials": "2.0.7"

src/components/Rating/Rating.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: Rating
3+
menu: Components
4+
---
5+
6+
import { Counter as CounterContainer } from 'react-powerplug';
7+
import { Playground, Props } from 'docz';
8+
import { Rating } from '.';
9+
10+
# Rating
11+
12+
### Usage
13+
14+
<Playground>
15+
<CounterContainer initial={3}>
16+
{({ count: rating, set }) => (
17+
<Rating
18+
value={rating}
19+
size="small"
20+
color="#67c6bb"
21+
onChange={value => set(value)}
22+
getStyles={(props, theme) => ({
23+
containerStyle: {},
24+
touchableStyle: {},
25+
starColor: '#67c6bb', // defined as color prop
26+
starSize: 40, // defined as size prop
27+
})}
28+
/>
29+
)}
30+
</CounterContainer>
31+
</Playground>
32+
33+
### Props
34+
35+
<Props of={Rating} />
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ViewStyle } from 'react-native';
2+
3+
import { ControlSize, TextColor, Theme } from '../../theme/ThemeInterface';
4+
import { getTextColor } from '../Typography/Text.styles';
5+
6+
export interface RatingStylesProps {
7+
size: ControlSize;
8+
color: TextColor;
9+
}
10+
11+
export type GetRatingStyles = (
12+
progressStylesProps: RatingStylesProps,
13+
theme: Theme,
14+
) => RatingStyles;
15+
16+
export interface RatingStyles {
17+
touchableStyle: ViewStyle;
18+
containerStyle: ViewStyle;
19+
starColor: string;
20+
starSize: number;
21+
}
22+
23+
export const getRatingStyles: GetRatingStyles = ({ size, color }, theme) => {
24+
const starSize = theme.controlHeights[size];
25+
const padding = theme.controlPaddings[size];
26+
27+
return {
28+
containerStyle: {
29+
flexDirection: 'row',
30+
},
31+
touchableStyle: {
32+
paddingRight: padding,
33+
},
34+
35+
starColor: getTextColor(theme.colors.text)(color),
36+
starSize,
37+
};
38+
};

src/components/Rating/Rating.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as React from 'react';
2+
import { TouchableOpacity, View } from 'react-native';
3+
import { DeepPartial } from 'ts-essentials';
4+
5+
import { ControlSize, TextColor, useTheme } from '../../theme';
6+
import { mergeStyles, ReplaceReturnType } from '../../utils/mergeStyles';
7+
import {
8+
GetRatingStyles,
9+
getRatingStyles,
10+
RatingStyles,
11+
} from './Rating.styles';
12+
import { Star } from './Star';
13+
14+
export interface RatingProps {
15+
value?: number;
16+
maxRating?: number;
17+
size?: ControlSize;
18+
getStyles?: ReplaceReturnType<GetRatingStyles, DeepPartial<RatingStyles>>;
19+
onChange?: (value: number) => void;
20+
color?: TextColor;
21+
}
22+
23+
export const Rating = (props: RatingProps) => {
24+
const {
25+
value = 0,
26+
maxRating = 5,
27+
size = 'medium',
28+
getStyles,
29+
onChange = () => undefined,
30+
color = 'primary',
31+
} = props;
32+
const theme = useTheme();
33+
34+
const { containerStyle, starColor, starSize, touchableStyle } = mergeStyles(
35+
getRatingStyles,
36+
getStyles,
37+
)({ size, color }, theme);
38+
39+
return (
40+
<View style={containerStyle}>
41+
{new Array(maxRating).fill(0).map((_, index) => {
42+
const currentValue = index + 1;
43+
const isWithinRating = currentValue <= value;
44+
const isLast = currentValue === maxRating;
45+
46+
return (
47+
<TouchableOpacity
48+
key={currentValue}
49+
style={{
50+
...touchableStyle,
51+
...(isLast ? { paddingRight: 0 } : {}),
52+
}}
53+
onPress={() => onChange(currentValue)}
54+
>
55+
<Star color={starColor} size={starSize} isFilled={isWithinRating} />
56+
</TouchableOpacity>
57+
);
58+
})}
59+
</View>
60+
);
61+
};

src/components/Rating/Star.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as React from 'react';
2+
import { Polygon, Svg } from 'react-native-svg';
3+
4+
import { useTheme } from '../../theme';
5+
6+
export interface StarProps {
7+
size?: number;
8+
isFilled?: boolean;
9+
color?: string;
10+
}
11+
12+
export const Star = (props: StarProps) => {
13+
const { size = 24, color, isFilled = false } = props;
14+
const theme = useTheme();
15+
16+
const coercedSize = String(size);
17+
18+
const finalColor = color || theme.fills.subtle.yellow.backgroundColor;
19+
20+
return (
21+
<Svg
22+
width={coercedSize}
23+
height={coercedSize}
24+
viewBox={`0 0 24 24`}
25+
fill={isFilled ? finalColor : 'none'}
26+
stroke={finalColor}
27+
strokeWidth={2}
28+
strokeLinecap="round"
29+
strokeLinejoin="round"
30+
>
31+
<Polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
32+
</Svg>
33+
);
34+
};

src/components/Rating/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './Rating';
2+
export * from './Star';

src/components/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
export * from './Alert';
22
export * from './Avatar';
33
export * from './Badge';
4+
export * from './Box';
45
export * from './Button';
56
export * from './Checkbox';
7+
export * from './Collapsible';
68
export * from './Counter';
79
export * from './Dialog';
810
export * from './Divider';
9-
export * from './Helpers';
1011
export * from './Form';
11-
export * from './Box';
12+
export * from './Grid';
13+
export * from './Helpers';
14+
export * from './Inputs';
1215
export * from './ListItem';
1316
export * from './Loading';
1417
export * from './Modal';
1518
export * from './Overlay';
1619
export * from './Pickers';
17-
export * from './Positioner';
1820
export * from './Popover';
21+
export * from './Positioner';
1922
export * from './ProgressBar';
23+
export * from './Rating';
2024
export * from './SelectList';
2125
export * from './Switch';
22-
export * from './Inputs';
2326
export * from './Tabs';
2427
export * from './Toast';
2528
export * from './Typography';

0 commit comments

Comments
 (0)