import React, { forwardRef, useState } from 'react' import { TypeGuards } from '@codeleap/types' import { Animated, Platform, Text as NativeText } from 'react-native' import { TouchableFeedbackConfig, usePressableFeedback } from '../../utils' import { TextProps } from './types' import { AnyRecord, IJSX, StyledComponentProps, StyledComponentWithProps } from '@codeleap/styles' import { MobileStyleRegistry } from '../../Registry' import { useStylesFor } from '../../hooks' export * from './styles' export * from './types' export const Text = forwardRef((textProps, ref) => { const { text, children, onPress, style, debounce, pressDisabled, animated, animatedStyle, ...props } = { ...Text.defaultProps, ...textProps, } // On iOS, RN Text renders a highlight on tap by default; suppressHighlighting must be true to // opt into the custom pressFeedback color animation. Android always uses the polyfill because // it has no native text-press highlight at all. const pressPolyfillEnabled = Platform.select({ ios: props.suppressHighlighting, android: true, }) && !!onPress && !pressDisabled const [pressed, setPressed] = useState(false) const pressedRef = React.useRef(false) const _onPress: TextProps['onPress'] = (e) => { if (!onPress) return if (TypeGuards.isNumber(debounce)) { if (pressedRef.current) { return } pressedRef.current = true onPress?.(e) setTimeout(() => { pressedRef.current = false }, debounce) } else { onPress?.(e) } } const handlePress = (pressed) => { if (!pressPolyfillEnabled) return return () => { if (onPress) { setPressed(pressed) } } } const styles = useStylesFor(Text.styleRegistryName, style) if (!!text && !TypeGuards.isString(text)) return <>{text} const Component = animated ? Animated.Text : NativeText const { getFeedbackStyle } = usePressableFeedback(styles, { disabled: !pressPolyfillEnabled, feedbackConfig: styles?.pressFeedback as TouchableFeedbackConfig, hightlightPropertyIn: 'color', hightlightPropertyOut: 'backgroundColor', }) const feedbackStyle = pressPolyfillEnabled ? getFeedbackStyle(pressed) : undefined const pressProps = !!onPress ? { onPress: pressDisabled ? null : _onPress, } : {} const disabled = !!onPress && pressDisabled return ( {text} {children} ) }) as StyledComponentWithProps Text.styleRegistryName = 'Text' Text.elements = ['text', 'pressFeedback'] Text.rootElement = 'text' Text.withVariantTypes = (styles: S) => { return Text as (props: StyledComponentProps) => IJSX } /** Default debounce is 1000 ms — longer than Touchable's 500 ms — because double-tap on a * pressable Text is harder to perceive and native tap coalescing on Text is unreliable. */ Text.defaultProps = { debounce: 1000, } as Partial MobileStyleRegistry.registerComponent(Text) export const AnimatedText = Animated.createAnimatedComponent(Text)