import React, { useMemo, useRef } from 'react'; import { ActivityIndicator, Animated, Pressable, PressableProps, Text, View, } from 'react-native'; import { getStyles, styles } from './styles'; import useAutoWidth from './useAutoWidth'; import useButtonTextTransition from './useButtonTextTransition'; import usePressProgressController from './usePressProgressController'; import { ANIMATED_TIMING_LOADING, DEFAULT_ACTIVITY_COLOR, DEFAULT_ACTIVE_OPACITY, DEFAULT_BACKGROUND_ACTIVE, DEFAULT_BACKGROUND_COLOR, DEFAULT_BACKGROUND_DARKER, DEFAULT_BACKGROUND_SHADOW, DEFAULT_BORDER_RADIUS, DEFAULT_BORDER_WIDTH, DEFAULT_DEBOUNCED_PRESS_TIME, DEFAULT_HEIGHT, DEFAULT_HORIZONTAL_PADDING, DEFAULT_LINE_HEIGHT, DEFAULT_RAISE_LEVEL, DEFAULT_TEXT_COLOR, DEFAULT_TEXT_SIZE, DEFAULT_WIDTH, } from './constants'; import Placeholder from './Placeholder'; import type { AwesomeButtonProps } from './types'; /** * @deprecated Use AwesomeButtonProps instead. */ export type ButtonTypes = AwesomeButtonProps; const getMergedAccessibilityState = ( accessibilityState: PressableProps['accessibilityState'], { busy, disabled, }: { busy: boolean; disabled: boolean; } ) => { const nextState = { ...accessibilityState, }; if (disabled || nextState.disabled !== undefined) { nextState.disabled = Boolean(disabled || nextState.disabled); } if (busy || nextState.busy !== undefined) { nextState.busy = Boolean(busy || nextState.busy); } return Object.keys(nextState).length > 0 ? nextState : undefined; }; const AwesomeButton = ({ activityColor = DEFAULT_ACTIVITY_COLOR, activeOpacity = DEFAULT_ACTIVE_OPACITY, animatedPlaceholder = true, backgroundActive = DEFAULT_BACKGROUND_ACTIVE, backgroundColor = DEFAULT_BACKGROUND_COLOR, backgroundDarker = DEFAULT_BACKGROUND_DARKER, backgroundPlaceholder = DEFAULT_BACKGROUND_SHADOW, backgroundProgress = DEFAULT_BACKGROUND_SHADOW, backgroundShadow = DEFAULT_BACKGROUND_SHADOW, borderColor, borderRadius = DEFAULT_BORDER_RADIUS, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadius, borderWidth = DEFAULT_BORDER_WIDTH, children = null, before = null, after = null, disabled = false, height = DEFAULT_HEIGHT, hitSlop, debouncedPressTime = DEFAULT_DEBOUNCED_PRESS_TIME, paddingHorizontal = DEFAULT_HORIZONTAL_PADDING, onPress = () => undefined, onPressIn = () => undefined, onPressedIn = () => undefined, onPressOut = () => undefined, onPressedOut = () => undefined, onProgressStart = () => undefined, onProgressEnd = () => undefined, onLongPress, dangerouslySetPressableProps = {}, progress = false, paddingBottom = 0, paddingTop = 0, progressLoadingTime = ANIMATED_TIMING_LOADING, raiseLevel = DEFAULT_RAISE_LEVEL, springRelease = true, stretch = false, style, textColor = DEFAULT_TEXT_COLOR, textLineHeight = DEFAULT_LINE_HEIGHT, textSize = DEFAULT_TEXT_SIZE, textTransition = false, textFontFamily, width: rawWidth = DEFAULT_WIDTH, extra = null, }: AwesomeButtonProps) => { const width = rawWidth === 'auto' ? null : rawWidth; const loadingOpacity = useRef(new Animated.Value(1)).current; const textOpacity = useRef(new Animated.Value(1)).current; const activityOpacity = useRef(new Animated.Value(0)).current; const animatedActive = useRef(new Animated.Value(0)).current; const animatedValue = useRef(new Animated.Value(0)).current; const animatedLoading = useRef(new Animated.Value(0)).current; const animatedOpacity = useRef( new Animated.Value(width === null && stretch !== true ? 0 : 1) ).current; const { displayedText } = useButtonTextTransition({ children, textTransition, }); const { measuredWidth, onTextLayout, stateWidth } = useAutoWidth({ animatedOpacity, stretch, width, }); const { activity, handlePress, handlePressIn, handlePressOut } = usePressProgressController({ activeOpacity, animatedActive, animatedLoading, animatedOpacity, animatedValue, activityOpacity, disabled, hasChildren: Boolean(children), loadingOpacity, onPress, onPressIn, onPressOut, onPressedIn, onPressedOut, onProgressEnd, onProgressStart, progress, progressLoadingTime, springRelease, textOpacity, debouncedPressTime, }); const { accessibilityRole: dangerousAccessibilityRole, accessibilityState: dangerousAccessibilityState, children: _ignoredDangerousChildren, hitSlop: dangerousHitSlop, onLongPress: _ignoredDangerousOnLongPress, onPress: _ignoredDangerousOnPress, onPressIn: _ignoredDangerousOnPressIn, onPressOut: _ignoredDangerousOnPressOut, ...safePressableProps } = dangerouslySetPressableProps as PressableProps & { children?: React.ReactNode; }; const dynamicStyles = useMemo( () => getStyles({ backgroundActive, backgroundColor, backgroundDarker, backgroundPlaceholder, backgroundProgress, backgroundShadow, borderColor, borderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadius, borderWidth, height, paddingBottom, paddingHorizontal, paddingTop, raiseLevel, stateWidth, stretch, textColor, textFontFamily, textLineHeight, textSize, width, }), [ backgroundActive, backgroundColor, backgroundDarker, backgroundPlaceholder, backgroundProgress, backgroundShadow, borderColor, borderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadius, borderWidth, height, paddingBottom, paddingHorizontal, paddingTop, raiseLevel, stateWidth, stretch, textColor, textFontFamily, textLineHeight, textSize, width, ] ); const animatedValues = useMemo(() => { const offsetWidth = measuredWidth ? measuredWidth * -1 : 0; return { animatedActivity: { opacity: activityOpacity, transform: [ { scale: activityOpacity, }, ], }, animatedActive: { opacity: animatedActive, }, animatedContainer: { opacity: animatedOpacity, }, animatedContent: { transform: [ { translateY: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, raiseLevel], }), }, ], }, animatedProgress: { opacity: loadingOpacity, transform: [ { translateX: animatedLoading.interpolate({ inputRange: [0, 1], outputRange: [offsetWidth, 0], }), }, ], }, animatedShadow: { transform: [ { translateY: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, -raiseLevel / 2], }), }, ], }, }; }, [ activityOpacity, animatedActive, animatedLoading, animatedOpacity, animatedValue, loadingOpacity, measuredWidth, raiseLevel, ]); const renderActivity = useMemo(() => { if (activity === false) { return null; } return ( <> ); }, [ activity, activityColor, animatedValues.animatedActivity, animatedValues.animatedProgress, dynamicStyles.progress, ]); const animatedStyles = useMemo( () => ({ opacity: textOpacity, transform: [ { scale: textOpacity, }, ], }), [textOpacity] ); const renderContent = useMemo(() => { if (!children) { return ( ); } const content = typeof children === 'string' ? ( {displayedText ?? children} ) : ( children ); return ( {before} {content} {after} ); }, [ after, animatedPlaceholder, animatedStyles, before, children, displayedText, dynamicStyles.container__placeholder, dynamicStyles.container__text, dynamicStyles.container__view, ]); const pressableHitSlop = hitSlop ?? dangerousHitSlop; const accessibilityRole = dangerousAccessibilityRole ?? 'button'; const accessibilityState = useMemo( () => getMergedAccessibilityState(dangerousAccessibilityState, { busy: activity, disabled, }), [activity, dangerousAccessibilityState, disabled] ); return ( {extra} {renderContent} {renderActivity} ); }; export default AwesomeButton;