import { useMemo, memo, useCallback, forwardRef, ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, } from 'react' import { Pressable, PressableProps, PressableStateCallbackType, StyleSheet, ViewStyle, TextStyle, StyleProp, View, Platform, } from 'react-native' import { ButtonSize, ButtonVariant, buttonSizeVariants, buttonVariants, getButtonShadowStyle, } from '../../config' import { generateStyledComponent } from '../../utils' import { Icon } from '../Icon' import { Loader } from '../Loader' import { Row } from '../Row' import { Text } from '../Text' import { useHover } from '../Touchables/useHover' import { StyledProps } from '../types' import { useTheme } from '@/hooks' import { IconNames } from '@/types' import { getColorValue } from '@/utils' export type ButtonProps = StyledProps & PressableProps & { disabled?: boolean leftElement?: JSX.Element leftIconName?: IconNames loaderElement?: JSX.Element loading?: boolean rightIconName?: IconNames size?: ButtonSize textStyle?: StyleProp title?: string variant?: ButtonVariant } const styles = StyleSheet.create({ baseButton: { alignItems: 'center', borderRadius: 4, flexDirection: 'row', justifyContent: 'center', }, baseText: { fontStyle: 'normal', fontWeight: '400', }, }) const RawButton = memo( forwardRef( ( { children, disabled, leftElement, leftIconName, loading, rightIconName, size = 'md', style, textStyle, title, variant = 'Primary', ...props }, ref ) => { const { colors } = useTheme() const { hoverProps, isHovered } = useHover() const { hoveredStyle, defaultStyle, disabledStyle } = useMemo( () => buttonVariants[variant], [variant] ) const pressedStyles = useMemo(() => { return getButtonShadowStyle({ variant }) }, [variant]) const hoveredStyles = useMemo( () => ({ backgroundColor: getColorValue({ color: hoveredStyle.backgroundColor, colors, }), borderColor: getColorValue({ color: hoveredStyle.borderColor!, colors, }), borderWidth: hoveredStyle.borderWidth, }), [colors, hoveredStyle.backgroundColor, hoveredStyle.borderColor, hoveredStyle.borderWidth] ) const hoverColorStyle = useMemo( () => ({ color: getColorValue({ color: hoveredStyle.color!, colors, }), }), [colors, hoveredStyle.color] ) const defaultStyles = useMemo( () => ({ backgroundColor: getColorValue({ color: defaultStyle.backgroundColor, colors, }), borderColor: getColorValue({ color: defaultStyle.borderColor!, colors, }), borderWidth: defaultStyle.borderWidth, }), [colors, defaultStyle.backgroundColor, defaultStyle.borderColor, defaultStyle.borderWidth] ) const defaultColorStyle = useMemo( () => ({ color: getColorValue({ color: defaultStyle.color!, colors, }), }), [colors, defaultStyle.color] ) const disabledStyles = useMemo( () => ({ backgroundColor: getColorValue({ color: disabledStyle.backgroundColor, colors, }), borderColor: getColorValue({ color: disabledStyle.borderColor!, colors, }), borderWidth: disabledStyle.borderWidth, }), [ colors, disabledStyle.backgroundColor, disabledStyle.borderColor, disabledStyle.borderWidth, ] ) const disabledColorStyle = useMemo( () => ({ color: getColorValue({ color: disabledStyle.color!, colors, }), }), [colors, disabledStyle.color] ) const buttonSizeVariant = buttonSizeVariants[size] const buttonSizeStyle = useMemo( () => ({ paddingHorizontal: buttonSizeVariant.paddingHorizontal, paddingVertical: buttonSizeVariant.paddingVertical, }), [buttonSizeVariant.paddingHorizontal, buttonSizeVariant.paddingVertical] ) const pressableStyleFunction = useCallback( ({ pressed }: PressableStateCallbackType): StyleProp => StyleSheet.flatten([ styles.baseButton, { ...Platform.select({ default: pressed || isHovered ? { ...hoveredStyles, ...pressedStyles } : defaultStyles, web: pressed ? { ...defaultStyles, ...pressedStyles } : isHovered ? hoveredStyles : defaultStyles, }), }, disabled && disabledStyles, loading && disabledStyles, buttonSizeStyle, typeof style === 'function' ? style({ pressed }) : style, ]), [ buttonSizeStyle, defaultStyles, disabled, disabledStyles, hoveredStyles, isHovered, loading, pressedStyles, style, ] ) const getIconColor = useCallback( ({ pressed }: PressableStateCallbackType): ColorNames | undefined => { if (disabled) return disabledStyle.color if (pressed || isHovered) return hoveredStyle.color return defaultStyle.color }, [defaultStyle.color, disabled, disabledStyle.color, hoveredStyle.color, isHovered] ) const pressableTextStyleFunction = useCallback( ({ pressed }: PressableStateCallbackType) => StyleSheet.flatten([ styles.baseText, { lineHeight: buttonSizeVariant.lineHeight }, pressed ? hoverColorStyle : defaultColorStyle, disabled && disabledColorStyle, textStyle, ]), [ buttonSizeVariant.lineHeight, hoverColorStyle, defaultColorStyle, disabled, disabledColorStyle, textStyle, ] ) const iconElement = useCallback( (props: PressableStateCallbackType, iconName?: IconNames) => { return iconName ? ( ) : null }, [buttonSizeVariant.iconSize, getIconColor] ) const childrenElement = useCallback( (props: PressableStateCallbackType) => { if (title) { return ( {title} ) } if (typeof children === 'string') { return ( {children} ) } return <>{children} }, [buttonSizeVariant.textVariant, children, pressableTextStyleFunction, title] ) return ( {loading ? ( ) : ( (props: PressableStateCallbackType) => ( {leftElement && leftElement} {leftIconName && iconElement(props, leftIconName)} {childrenElement(props)} {rightIconName && iconElement(props, rightIconName)} ) )} ) } ) ) type ButtonComposition = ForwardRefExoticComponent< PropsWithoutRef & RefAttributes > & { [key in ButtonVariant]: ForwardRefExoticComponent< PropsWithoutRef & RefAttributes > } const Button = generateStyledComponent( RawButton ) as ButtonComposition Button.displayName = 'Button' const generateButtonVariant = (variant: ButtonVariant) => forwardRef((props, ref) =>