UNPKG

react-native-ui-lib

Version:

[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://stand-with-ukraine.pp.ua)

228 lines • 6.99 kB
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, StyleSheet, findNodeHandle, AccessibilityInfo } from 'react-native'; import { Constants, asBaseComponent } from "../../commons/new"; import { useDidUpdate } from "../../hooks"; import { Colors, BorderRadiuses, Spacings, Typography, Shadows } from "../../style"; import View from "../../components/view"; import Text from "../../components/text"; import Icon from "../../components/icon"; import Button from "../../components/button"; import PanView from "../panView"; import { ToastProps, ToastPresets } from "./types"; import useToastTimer from "./helpers/useToastTimer"; import useToastPresets from "./helpers/useToastPresets"; import useToastAnimation from "./helpers/useToastAnimation"; const THRESHOLD = { x: Constants.screenWidth / 4, y: 10 }; const Toast = props => { const { visible, position = 'bottom', icon, iconColor, preset, zIndex = Constants.isAndroid ? 100 : undefined, elevation, style, containerStyle, message, messageStyle, messageProps, renderAttachment, centerMessage, showLoader, loaderElement, action, swipeable, backgroundColor, onDismiss, onAnimationEnd, children, testID } = props; const directions = useRef([props.position === 'bottom' ? PanView.directions.DOWN : PanView.directions.UP, PanView.directions.LEFT, PanView.directions.RIGHT]); const viewRef = useRef(); const [toastHeight, setToastHeight] = useState(); const { clearTimer, setTimer } = useToastTimer(props); const toastPreset = useToastPresets({ icon, iconColor, message, preset }); const playAccessibilityFeatures = () => { if (visible) { if (viewRef.current && (action || message)) { const reactTag = findNodeHandle(viewRef.current); AccessibilityInfo.setAccessibilityFocus(reactTag); } } }; const { isAnimating, toggleToast, opacityStyle, translateStyle } = useToastAnimation({ visible, position, onAnimationEnd, toastHeight, setTimer, playAccessibilityFeatures }); useEffect(() => { if (visible) { toggleToast(visible, { delay: 100 }); } return () => clearTimer(); }, []); useDidUpdate(() => { if (!visible) { clearTimer(); } toggleToast(visible); }, [visible]); const handleDismiss = useCallback(() => { clearTimer(); onDismiss?.(); }, [onDismiss]); const positionStyle = useMemo(() => { return { position: 'absolute', left: 0, right: 0, [position]: 0 }; }, [position]); const toastStyle = useMemo(() => { return [opacityStyle, containerStyle]; }, [opacityStyle, containerStyle]); const toastContainerStyle = useMemo(() => { return [positionStyle, translateStyle, { zIndex, elevation }]; }, [positionStyle, translateStyle, zIndex, elevation]); const onLayout = useCallback(event => { const height = event.nativeEvent.layout.height; if (height !== toastHeight) { setToastHeight(height); } }, [toastHeight]); const renderRightElement = () => { // NOTE: order does matter if (showLoader) { return loaderElement ?? <ActivityIndicator size={'small'} testID={`${testID}-loader`} color={Colors.rgba(Colors.$backgroundNeutralHeavy, 0.6)} style={styles.loader} />; } if (action) { return <Button link style={styles.action} color={Colors.$backgroundNeutralHeavy} activeBackgroundColor={Colors.$backgroundNeutral} {...action} labelStyle={Typography.bodySmallBold} accessibilityRole={'button'} testID={`${testID}-action`} />; } }; const renderMessage = () => { const textAlign = centerMessage ? 'center' : 'left'; return <View accessible={Constants.isIOS} style={styles.messageContainer}> <Text testID={`${testID}-message`} ref={viewRef} style={[styles.message, { textAlign }, messageStyle]} accessibilityLabel={toastPreset.accessibilityMessage} {...messageProps}> {message} </Text> </View>; }; const renderIcon = () => { if (toastPreset.icon) { return <Icon source={toastPreset.icon} resizeMode={'contain'} style={styles.icon} tintColor={toastPreset.iconColor} />; } }; const renderToastContent = () => { return <PanView directions={swipeable ? directions.current : []} dismissible animateToOrigin directionLock onDismiss={handleDismiss} threshold={THRESHOLD}> {children ?? <View style={[styles.toastContent, style, backgroundColor ? { backgroundColor } : undefined]}> {renderIcon()} {renderMessage()} {renderRightElement()} </View>} </PanView>; }; const renderAttachmentContent = () => { if (renderAttachment) { return <View pointerEvents={'box-none'}>{renderAttachment()}</View>; } }; const _renderAttachment = (positionStyle, zIndex) => { return <View style={[positionStyle, { zIndex }]} pointerEvents={'box-none'}> {renderAttachmentContent()} </View>; }; const renderToast = () => { const isTop = position === 'top'; return <> {!isTop && !!toastHeight && renderAttachmentContent()} <View animated useSafeArea style={toastStyle} onLayout={onLayout} pointerEvents={visible ? 'box-none' : 'none'}> {renderToastContent()} </View> {isTop && !!toastHeight && renderAttachmentContent()} </>; }; if (!visible && !isAnimating) { return renderAttachment ? _renderAttachment(positionStyle, zIndex) : null; } return <View key="toast" animated testID={testID} style={toastContainerStyle} pointerEvents={'box-none'}> {renderToast()} </View>; }; const styles = StyleSheet.create({ toastContent: { backgroundColor: Colors.$backgroundElevatedLight, minHeight: 48, flexDirection: 'row', alignSelf: 'center', alignItems: 'center', borderRadius: BorderRadiuses.br40, ...Shadows.sh20.bottom, marginHorizontal: Spacings.s5, marginVertical: Spacings.s3, paddingLeft: Spacings.s3 }, messageContainer: { flex: Constants.isTablet ? undefined : 1, paddingVertical: Spacings.s3, justifyContent: 'center' }, message: { ...Typography.bodySmall, color: Colors.$textDefault, marginLeft: Spacings.s2, marginRight: Spacings.s5 }, icon: { width: 24, height: 24, marginRight: Spacings.s1 }, loader: { marginRight: Spacings.s3 }, action: { borderLeftColor: Colors.$outlineDisabled, borderLeftWidth: 1, borderTopRightRadius: BorderRadiuses.br40, borderBottomRightRadius: BorderRadiuses.br40, paddingHorizontal: Spacings.s3, height: '100%' } }); Toast.presets = ToastPresets; Toast.displayName = 'Incubator.Toast'; export { ToastProps, ToastPresets }; export default asBaseComponent(Toast);