react-native-ui-lib
Version:
<p align="center"> <img src="https://user-images.githubusercontent.com/1780255/105469025-56759000-5ca0-11eb-993d-3568c1fd54f4.png" height="250px" style="display:block"/> </p> <p align="center">UI Toolset & Components Library for React Native</p> <p a
199 lines (181 loc) • 5.6 kB
JavaScript
import _pt from "prop-types";
import _ from 'lodash';
import React, { useRef, useCallback } from 'react';
import { StyleSheet } from 'react-native';
import Reanimated, { Easing, useAnimatedReaction, useAnimatedStyle, useSharedValue, withTiming, runOnJS } from 'react-native-reanimated';
import { Colors, BorderRadiuses, Spacings } from "../../style";
import { Constants, asBaseComponent } from "../../commons/new";
import View from "../view";
import Segment from "./segment";
import { useOrientation } from "../../hooks";
const BORDER_WIDTH = 1;
const TIMING_CONFIG = {
duration: 300,
// @ts-expect-error TODO: change this to bezierFn or to the new implementation
easing: Easing.bezier(0.33, 1, 0.68, 1)
};
/**
* @description: SegmentedControl component for toggling two values or more
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/SegmentedControlScreen.tsx
*/
const SegmentedControl = props => {
const {
onChangeIndex,
initialIndex = 0,
containerStyle,
style,
segments,
activeColor = Colors.primary,
borderRadius = BorderRadiuses.br100,
backgroundColor = Colors.grey80,
activeBackgroundColor = Colors.white,
inactiveColor = Colors.grey20,
outlineColor = activeColor,
outlineWidth = BORDER_WIDTH,
throttleTime = 0,
testID
} = props;
const animatedSelectedIndex = useSharedValue(initialIndex);
const segmentsStyle = useSharedValue([]);
const segmentedControlHeight = useSharedValue(0);
const segmentsCounter = useRef(0);
useOrientation({
onOrientationChange: () => {
segmentsCounter.current = 0;
segmentsStyle.value = [];
}
}); // eslint-disable-next-line react-hooks/exhaustive-deps
const changeIndex = useCallback(_.throttle(() => {
onChangeIndex?.(animatedSelectedIndex.value);
}, throttleTime, {
trailing: true,
leading: false
}), [throttleTime]);
useAnimatedReaction(() => {
return animatedSelectedIndex.value;
}, (selected, previous) => {
if (selected !== -1 && previous !== null && selected !== previous) {
onChangeIndex && runOnJS(changeIndex)();
}
}, []);
const onSegmentPress = useCallback(index => {
animatedSelectedIndex.value = index; // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onLayout = useCallback((index, event) => {
const {
x,
width,
height
} = event.nativeEvent.layout;
segmentsStyle.value[index] = {
x,
width
};
segmentedControlHeight.value = height - 2 * BORDER_WIDTH;
segmentsCounter.current++;
if (segmentsCounter.current === segments?.length) {
segmentsStyle.value = [...segmentsStyle.value];
}
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[initialIndex, segments?.length]);
const animatedStyle = useAnimatedStyle(() => {
if (segmentsStyle.value.length !== 0) {
const inset = withTiming(segmentsStyle.value[animatedSelectedIndex.value].x, TIMING_CONFIG);
const width = withTiming(segmentsStyle.value[animatedSelectedIndex.value].width - 2 * BORDER_WIDTH, TIMING_CONFIG);
const height = segmentedControlHeight.value;
return Constants.isRTL ? {
width,
right: inset,
height
} : {
width,
left: inset,
height
};
}
return {};
});
const renderSegments = () => _.map(segments, (_value, index) => {
return <Segment key={index} onLayout={onLayout} index={index} onPress={onSegmentPress} selectedIndex={animatedSelectedIndex} activeColor={activeColor} inactiveColor={inactiveColor} {...segments?.[index]} testID={testID} />;
});
return <View style={containerStyle} testID={testID}>
<View row center style={[styles.container, style, {
borderRadius,
backgroundColor
}]}>
<Reanimated.View style={[styles.selectedSegment, {
borderColor: outlineColor,
borderRadius,
backgroundColor: activeBackgroundColor,
borderWidth: outlineWidth
}, animatedStyle]} />
{renderSegments()}
</View>
</View>;
};
SegmentedControl.propTypes = {
/**
* Array on segments.
*/
segments: _pt.array,
/**
* The color of the active segment label.
*/
activeColor: _pt.string,
/**
* The color of the inactive segments (label).
*/
inactiveColor: _pt.string,
/**
* Callback for when index has change.
*/
onChangeIndex: _pt.func,
/**
* Initial index to be active.
*/
initialIndex: _pt.number,
/**
* The segmentedControl borderRadius
*/
borderRadius: _pt.number,
/**
* The background color of the inactive segments
*/
backgroundColor: _pt.string,
/**
* The background color of the active segment
*/
activeBackgroundColor: _pt.string,
/**
* The color of the active segment outline
*/
outlineColor: _pt.string,
/**
* The width of the active segment outline
*/
outlineWidth: _pt.number,
/**
* Should the icon be on right of the label
*/
iconOnRight: _pt.bool,
/**
* Trailing throttle time of changing index in ms.
*/
throttleTime: _pt.number,
testID: _pt.string
};
const styles = StyleSheet.create({
container: {
backgroundColor: Colors.grey80,
borderColor: Colors.grey60,
borderWidth: BORDER_WIDTH
},
selectedSegment: {
position: 'absolute'
},
segment: {
paddingHorizontal: Spacings.s3
}
});
SegmentedControl.displayName = 'SegmentedControl';
export default asBaseComponent(SegmentedControl);