UNPKG

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
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);