140 lines
3.5 KiB
JavaScript
140 lines
3.5 KiB
JavaScript
import React, { useMemo } from "react";
|
|
import { Animated, StyleSheet, useWindowDimensions, View } from "react-native";
|
|
import { Palette } from "../../styles";
|
|
|
|
const ORIENTATION = {
|
|
HORIZONTAL: "horizontal",
|
|
VERTICAL: "vertical",
|
|
};
|
|
|
|
const AnimatedPaginationDot = ({
|
|
data = [],
|
|
scrollValue,
|
|
itemDimension,
|
|
containerStyle,
|
|
dotStyle,
|
|
orientation = ORIENTATION.HORIZONTAL,
|
|
expandingDotSize = 20,
|
|
inactiveDotOpacity = 0.4,
|
|
inactiveDotColor = "rgba(255,255,255,0.4)",
|
|
activeDotColor = Palette.white,
|
|
baseDotSize = 10,
|
|
}) => {
|
|
const animatedValue = useMemo(
|
|
() => scrollValue || new Animated.Value(0),
|
|
[scrollValue]
|
|
);
|
|
|
|
const { width, height } = useWindowDimensions();
|
|
const distance = useMemo(() => {
|
|
if (typeof itemDimension === "number" && itemDimension > 0) {
|
|
return itemDimension;
|
|
}
|
|
if (orientation === ORIENTATION.VERTICAL) {
|
|
return Math.max(height, 1);
|
|
}
|
|
return Math.max(width, 1);
|
|
}, [height, itemDimension, orientation, width]);
|
|
|
|
const resolvedDotStyle = useMemo(
|
|
() => StyleSheet.flatten(dotStyle) || {},
|
|
[dotStyle]
|
|
);
|
|
const defaultSize = Math.max(baseDotSize, 1);
|
|
const baseWidth =
|
|
typeof resolvedDotStyle?.width === "number"
|
|
? resolvedDotStyle.width
|
|
: defaultSize;
|
|
const baseHeight =
|
|
typeof resolvedDotStyle?.height === "number"
|
|
? resolvedDotStyle.height
|
|
: defaultSize;
|
|
|
|
const containerOrientationStyle =
|
|
orientation === ORIENTATION.VERTICAL
|
|
? styles.containerVertical
|
|
: styles.containerHorizontal;
|
|
|
|
return (
|
|
<View
|
|
pointerEvents="none"
|
|
style={[styles.containerBase, containerOrientationStyle, containerStyle]}
|
|
>
|
|
{data.map((_, index) => {
|
|
const inputRange = [
|
|
(index - 1) * distance,
|
|
index * distance,
|
|
(index + 1) * distance,
|
|
];
|
|
|
|
const staticSizeStyle = {
|
|
width: baseWidth,
|
|
height: baseHeight,
|
|
};
|
|
|
|
const color = animatedValue.interpolate({
|
|
inputRange,
|
|
outputRange: [inactiveDotColor, activeDotColor, inactiveDotColor],
|
|
extrapolate: "clamp",
|
|
});
|
|
|
|
const opacity = animatedValue.interpolate({
|
|
inputRange,
|
|
outputRange: [inactiveDotOpacity, 1, inactiveDotOpacity],
|
|
extrapolate: "clamp",
|
|
});
|
|
|
|
const primarySize = animatedValue.interpolate({
|
|
inputRange,
|
|
outputRange: [
|
|
orientation === ORIENTATION.VERTICAL ? baseHeight : baseWidth,
|
|
expandingDotSize,
|
|
orientation === ORIENTATION.VERTICAL ? baseHeight : baseWidth,
|
|
],
|
|
extrapolate: "clamp",
|
|
});
|
|
|
|
const animatedSizeStyle =
|
|
orientation === ORIENTATION.VERTICAL
|
|
? { height: primarySize }
|
|
: { width: primarySize };
|
|
|
|
return (
|
|
<Animated.View
|
|
key={`pagination-dot-${index}`}
|
|
style={[
|
|
styles.dotBase,
|
|
staticSizeStyle,
|
|
dotStyle,
|
|
animatedSizeStyle,
|
|
{ backgroundColor: color, opacity },
|
|
]}
|
|
/>
|
|
);
|
|
})}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
AnimatedPaginationDot.orientation = ORIENTATION;
|
|
|
|
export default AnimatedPaginationDot;
|
|
|
|
const styles = StyleSheet.create({
|
|
containerBase: {
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
},
|
|
containerHorizontal: {
|
|
flexDirection: "row",
|
|
},
|
|
containerVertical: {
|
|
flexDirection: "column",
|
|
},
|
|
dotBase: {
|
|
borderRadius: 999,
|
|
marginHorizontal: 4,
|
|
marginVertical: 4,
|
|
},
|
|
});
|