Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export { default as Portal } from './portal/index'
export { default as Progress } from './progress/index'
export { default as Provider } from './provider/index'
export { default as Radio } from './radio/index'
export { default as Rate } from './rate/index'
export { default as Result } from './result/index'
export { default as SearchBar } from './search-bar/index'
export { default as Slider } from './slider/index'
Expand Down
51 changes: 51 additions & 0 deletions components/rate/AnimatedIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react'
import { Animated } from 'react-native'

import { AnimatedIconProps } from './PropsType'
import { defaultAnimationConfig } from './constants'

const AnimatedIcon: React.FC<AnimatedIconProps> = ({
active,
config,
children,
style,
}) => {
const {
scale = defaultAnimationConfig.scale,
easing = defaultAnimationConfig.easing,
duration = defaultAnimationConfig.duration,
} = config

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 (
<Animated.View
pointerEvents="none"
style={[
style,
{
transform: [
{
scale: animatedSize.current,
},
],
},
]}>
{children}
</Animated.View>
)
}

export default AnimatedIcon
57 changes: 57 additions & 0 deletions components/rate/PropsType.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
FillGlyphMapType,
OutlineGlyphMapType,
} from '@ant-design/icons-react-native'
import React from 'react'
import { StyleProp, ViewStyle } from 'react-native'

type IconName = FillGlyphMapType & OutlineGlyphMapType

export interface RateProps {
value?: number
defaultValue?: number
count?: number
readOnly?: boolean
allowClear?: boolean
allowHalf?: boolean
allowSwiping?: boolean
style?: ViewStyle
color?: string
emptyColor?: string
iconName?: IconName
iconType?: 'fill' | 'outline'
iconSize?: number
iconStyle?: ViewStyle
animationConfig?: boolean | AnimationConfig
onChange?: (value: number) => void
onRatingStart?: (value: number) => void
onRatingEnd?: (value: number) => void
}

export type RateIconProps = {
size: number
name: IconName
color?: string
emptyColor?: string
type: 'full' | 'half' | 'empty'
isFill?: boolean
}

export type AnimationConfig = {
easing?: (value: number) => number
duration?: number
delay?: number
scale?: number
}

export type AnimationOptions = {
need: boolean
config: Required<AnimationConfig>
}

export type AnimatedIconProps = {
active: boolean
children: React.ReactElement
config: AnimationConfig
style?: StyleProp<ViewStyle>
}
114 changes: 114 additions & 0 deletions components/rate/RateIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
IconFill,
IconFillProps,
IconOutline,
IconOutlineProps,
} from '@ant-design/icons-react-native'
import React from 'react'
import { I18nManager, StyleSheet, View, ViewStyle } from 'react-native'
import { RateIconProps } from './PropsType'

const Icon = ({
size,
name,
color,
isFill,
}: Omit<RateIconProps, 'type' | 'emptyColor'>) => {
const IconComponent: React.ComponentType<IconFillProps | IconOutlineProps> =
isFill ? IconFill : IconOutline
return <IconComponent size={size} color={color} name={name} />
}

const EmptyIcon = ({
size,
name = 'star',
emptyColor,
isFill,
}: Omit<RateIconProps, 'type' | 'color'>) => (
<Icon name={name} size={size} color={emptyColor} isFill={isFill} />
)

const FullIcon = ({
size,
color,
name = 'star',
isFill,
}: Omit<RateIconProps, 'type' | 'emptyColor'>) => (
<Icon name={name} size={size} color={color} isFill={isFill} />
)

const RTL_TRANSFORM: ViewStyle = {
transform: [{ rotateY: '180deg' }],
}

const HalfIcon = ({
size,
name = 'star',
color,
emptyColor,
isFill,
}: Omit<RateIconProps, 'type'>) => (
<View
style={[
styles.container,
{ width: size, height: size },
I18nManager.isRTL ? RTL_TRANSFORM : undefined,
]}>
<View
style={[
styles.half,
{
width: size / 2,
height: size,
},
]}>
<Icon name={name} size={size} color={color} isFill={isFill} />
</View>

<View
style={[
styles.half,
{
width: size / 2,
height: size,
},
RTL_TRANSFORM,
]}>
<Icon name={name} size={size} color={emptyColor} isFill={isFill} />
</View>
</View>
)

const RateIcon = ({
type,
name = 'star',
size,
color,
emptyColor,
isFill,
}: RateIconProps) => {
const Component =
type === 'full' ? FullIcon : type === 'half' ? HalfIcon : EmptyIcon
return (
<Component
name={name}
size={size}
color={color}
emptyColor={emptyColor}
isFill={isFill}
/>
)
}

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
position: 'relative',
},
half: {
overflow: 'hidden',
position: 'relative',
},
})

export default RateIcon
Loading