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

403 lines (363 loc) • 10.7 kB
import _pt from "prop-types"; import _ from 'lodash'; import React, { PureComponent } from 'react'; import memoize from 'memoize-one'; import { Animated, Easing, StyleSheet } from 'react-native'; import { RectButton } from 'react-native-gesture-handler'; import { Constants, asBaseComponent } from "../../commons/new"; import { extractAccessibilityProps } from "../../commons/modifiers"; import { Colors } from "../../style"; import View from "../view"; import Swipeable from "./Swipeable"; import { LogService } from "../../services"; const DEFAULT_BG = Colors.primary; const DEFAULT_BOUNCINESS = 0; /** * @description: Drawer Component * @important: If your app works with RNN, your screen must be wrapped * with gestureHandlerRootHOC from 'react-native-gesture-handler'. see * @importantLink: https://kmagiera.github.io/react-native-gesture-handler/docs/getting-started.html#with-wix-react-native-navigation-https-githubcom-wix-react-native-navigation * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/Drawer/Drawer.gif?raw=true */ class Drawer extends PureComponent { static propTypes = { /** * The drawer animation bounciness */ bounciness: _pt.number, /** * The bottom layer's items to appear when opened from the right */ rightItems: _pt.arrayOf(_pt.shape({ width: _pt.number, background: _pt.string, text: _pt.string, icon: _pt.number, keepOpen: _pt.bool, testID: _pt.string, customElement: _pt.node })), /** * The bottom layer's item to appear when opened from the left (a single item) */ leftItem: _pt.shape({ width: _pt.number, background: _pt.string, text: _pt.string, icon: _pt.number, keepOpen: _pt.bool, testID: _pt.string, customElement: _pt.node }), /** * Set a different minimum width */ itemsMinWidth: _pt.number, /** * The color for the text and icon tint of the items */ itemsTintColor: _pt.string, /** * The items' icon size */ itemsIconSize: _pt.number, /** * Perform the animation in natively */ useNativeAnimations: _pt.bool, /** * Whether to allow a full left swipe */ fullSwipeLeft: _pt.bool, /** * Threshold for a left full swipe (0-1) */ fullLeftThreshold: _pt.number, /** * Whether to allow a full right swipe */ fullSwipeRight: _pt.bool, /** * Threshold for a right full swipe (0-1) */ fullRightThreshold: _pt.number, /** * Whether to disable the haptic */ disableHaptic: _pt.bool, /** * Custom value of any type to pass on to the component and receive back in the action callbacks */ customValue: _pt.any, /** * Used as testing identifier */ testID: _pt.string }; static displayName = 'Drawer'; static defaultProps = { itemsTintColor: Colors.white, itemsIconSize: 24 }; _swipeableRow = React.createRef(); animationOptions = { bounciness: this.props.bounciness || DEFAULT_BOUNCINESS }; leftActionX = new Animated.Value(0); constructor(props) { super(props); this.leftRender = Constants.isRTL ? props.rightItems && this.renderRightActions : props.leftItem && this.renderLeftActions; this.rightRender = Constants.isRTL ? props.leftItem && this.renderLeftActions : props.rightItems && this.renderRightActions; } getLeftActionsContainerStyle = memoize((leftItem, rightItems) => { return this.getActionsContainerStyle(Constants.isRTL ? rightItems : [leftItem]); }); getRightActionsContainerStyle = memoize((rightItems, leftItem) => { return this.getActionsContainerStyle(Constants.isRTL ? [leftItem] : rightItems); }); getActionsContainerStyle(items) { return { backgroundColor: _.get(_.first(items), 'background', DEFAULT_BG) }; } /** Actions */ closeDrawer = () => { this._swipeableRow.current?.close(); }; openLeft = () => { this._swipeableRow.current?.openLeft(); }; openLeftFull = () => { this._swipeableRow.current?.openLeftFull(); }; toggleLeft = () => { this._swipeableRow.current?.toggleLeft(); }; openRight = () => { this._swipeableRow.current?.openRight(); }; openRightFull = () => { this._swipeableRow.current?.openRightFull(); }; /** Events */ onActionPress = item => { if (!item.keepOpen) { this.closeDrawer(); } item.onPress?.(this.props); }; onSwipeableWillOpen = () => { this.props.onSwipeableWillOpen?.(this.props); }; onSwipeableWillClose = () => { this.props.onSwipeableWillClose?.(this.props); }; onToggleSwipeLeft = options => { if (this.props.onToggleSwipeLeft) { this.animateItem(options); } }; animateItem({ rowWidth, leftWidth, dragX, released, resetItemPosition }) { const toValue = resetItemPosition ? 0 : dragX ? dragX - leftWidth : rowWidth * 0.6 - leftWidth; Animated.timing(this.leftActionX, { toValue, easing: Easing.bezier(0.25, 1, 0.5, 1), duration: 200, delay: 100, useNativeDriver: true }).start(() => { if (released) { // reset Drawer this.animateItem({ released: false, resetItemPosition: true }); this.closeDrawer(); setTimeout(() => { this.props.onToggleSwipeLeft?.(this.props); }, 150); } }); } /** Accessability */ getAccessibilityActions(withOnPress = false) { const { rightItems, leftItem } = this.props; const actions = []; if (leftItem?.onPress && leftItem.text) { const action = { name: leftItem.text, label: leftItem.text }; if (withOnPress) { action.onPress = leftItem.onPress; } actions.push(action); } if (rightItems) { rightItems.forEach(item => { if (item.onPress && item.text) { const action = { name: item.text, label: item.text }; if (withOnPress) { action.onPress = item.onPress; } actions.push(action); } }); } return actions; } onAccessibilityAction = event => { const actions = this.getAccessibilityActions(true); const action = _.find(actions, o => { // return o.text === event.nativeEvent.action; return o.name === event.nativeEvent.actionName; }); action.onPress?.(); }; /** Renders */ // TODO: enable support for rendering more than one left item renderLeftActions = progress => { const { leftItem } = this.props; const leftItems = leftItem ? [leftItem] : undefined; return this.renderActions(leftItems, progress /* , dragX */ ); }; renderRightActions = progress => { const { rightItems } = this.props; return this.renderActions(rightItems, progress /* , dragX */ ); }; renderActions(items, progress) { if (items) { return (// @ts-ignore <View animated row style={{ transform: [{ translateX: this.leftActionX }] }}> {_.map(items, (item, index) => { return this.renderAction({ item, index: items.length - index - 1, progress, // dragX, itemsCount: items.length }); })} </View> ); } } renderAction = ({ item, index, progress, itemsCount }) => { const { itemsTintColor, itemsIconSize, itemsTextStyle, itemsMinWidth } = this.props; const inputRange = [index / itemsCount, (index + 1) / itemsCount]; const outputRange = [0.2, 1]; const scale = progress.interpolate({ inputRange, outputRange, extrapolate: 'clamp' }); const opacity = progress.interpolate({ inputRange, outputRange, extrapolate: 'clamp' }); return <RectButton key={index} testID={item.testID} style={[styles.action, item.style, { backgroundColor: item.background || DEFAULT_BG, width: item.width, minWidth: itemsMinWidth }]} onPress={() => this.onActionPress(item)}> {item.customElement} {!item.customElement && item.icon && <Animated.Image source={item.icon} style={[styles.actionIcon, { width: itemsIconSize, height: itemsIconSize, tintColor: itemsTintColor, opacity, transform: [{ scale }] }]} />} {!item.customElement && item.text && <Animated.Text style={[styles.actionText, { color: itemsTintColor, opacity, transform: [{ scale }] }, itemsTextStyle]} accessibilityElementsHidden importantForAccessibility="no-hide-descendants" accessible={false}> {item.text} </Animated.Text>} </RectButton>; }; render() { const { children, style, leftItem, rightItems, onToggleSwipeLeft, leftToggleHapticTrigger, ...others } = this.props; leftToggleHapticTrigger && LogService.deprecationWarn({ component: 'Drawer', oldProp: 'leftToggleHapticTrigger' }); return <Swipeable {...others} ref={this._swipeableRow} friction={1} containerStyle={style} animationOptions={this.animationOptions} renderLeftActions={this.leftRender} renderRightActions={this.rightRender} rightActionsContainerStyle={this.getRightActionsContainerStyle(rightItems, leftItem)} leftActionsContainerStyle={this.getLeftActionsContainerStyle(leftItem, rightItems)} onSwipeableWillOpen={this.onSwipeableWillOpen} onSwipeableWillClose={this.onSwipeableWillClose} onToggleSwipeLeft={onToggleSwipeLeft && this.onToggleSwipeLeft}> <View accessible accessibilityActions={this.getAccessibilityActions()} onAccessibilityAction={this.onAccessibilityAction} {...extractAccessibilityProps(this.props)}> {children} </View> </Swipeable>; } } export default asBaseComponent(Drawer); const styles = StyleSheet.create({ leftAction: { flex: 1, justifyContent: 'center', alignItems: /* Constants.isRTL ? 'flex-end' : */ 'flex-start', backgroundColor: '#388e3c' }, actionIcon: { width: 30, marginHorizontal: 10 }, actionText: { color: '#ffffff' }, action: { paddingHorizontal: 12, justifyContent: 'center', alignItems: 'center', backgroundColor: '#dd2c00' } });