import Link, { LinkProps } from 'next/link'; import React, { forwardRef, ReactNode, useCallback, useMemo, useState, } from 'react'; import { StyleSheet, Text as RNText, TextProps as RNTextProps, TouchableOpacity, TouchableOpacityProps, } from 'react-native'; import { useStyles } from '../hooks/useStyles'; import { useTheme } from '../hooks/useTheme'; import { Theme } from '../themes/theme'; const createStyles = (theme: Theme) => ({ h1: { ...theme.fontSize.big, color: theme.color.background, marginBottom: theme.spacing.medium, }, h2: { ...theme.fontSize.medium, color: theme.color.background, fontWeight: theme.fontWeight.medium, marginBottom: theme.spacing.medium, }, text: { ...theme.fontSize.medium, color: theme.color.background, }, p: { ...theme.fontSize.medium, color: theme.color.background, marginBottom: theme.spacing.medium, }, small: { ...theme.fontSize.small, color: theme.color.background, }, smallRed: { ...theme.fontSize.small, color: theme.color.red, }, smallBold: { ...theme.fontSize.small, color: theme.color.background, fontWeight: theme.fontWeight.medium, }, smallBoldRed: { ...theme.fontSize.small, color: theme.color.red, fontWeight: theme.fontWeight.medium, }, li: { ...theme.fontSize.medium, color: theme.color.background, marginBottom: theme.spacing.small, }, link: { ...theme.fontSize.medium, color: theme.color.blue, }, code: { ...theme.fontSize.small, fontFamily: theme.fontFamily.monospace, }, button: { ...theme.fontSize.medium, backgroundColor: theme.color.green, borderRadius: theme.spacing.smaller, color: theme.color.foreground, fontWeight: theme.fontWeight.medium, marginVertical: theme.spacing.smaller, paddingHorizontal: theme.spacing.small, paddingVertical: theme.spacing.smaller, textAlign: 'center', }, smallButton: { ...theme.fontSize.small, backgroundColor: theme.color.green, borderRadius: theme.spacing.smaller, color: theme.color.foreground, fontWeight: theme.fontWeight.medium, paddingHorizontal: theme.spacing.small, textAlign: 'center', }, }); /** * It's like Next LinkProps except href is optional and passHref with prefetch are * not allowed. The idea is simple. Because anchor is rendered via Text in RNfW, * we just added Next Link functionality into Text. * We had to copy-paste LinkProps, because props must be spread. */ interface NextLinkSomeProps { href?: LinkProps['href']; as?: LinkProps['as']; replace?: LinkProps['replace']; scroll?: LinkProps['scroll']; shallow?: LinkProps['shallow']; // Next.js auto-prefetches automatically based on viewport. The prefetch attribute is // no longer needed. More: https://err.sh/zeit/next.js/prefetch-true-deprecated // prefetch?: boolean; } // Only props I use. interface TouchableOpacitySomeProps { onPress?: TouchableOpacityProps['onPress']; onPressIn?: TouchableOpacityProps['onPressIn']; disabled?: TouchableOpacityProps['disabled']; } interface TextOwnProps { variant: keyof ReturnType; color?: keyof Theme['color']; children: ReactNode; } export type TextProps = RNTextProps & NextLinkSomeProps & TouchableOpacitySomeProps & TextOwnProps; const styles = StyleSheet.create({ linkHover: { textDecorationLine: 'underline', }, }); // Next.js needs ref for Intersection Observer based prefetching. // That's how we delegate it to React Native for Web Text component. const RNTextWithDOMRef = forwardRef( (props: RNTextProps & { children: ReactNode }, ref) => { return ( ); }, ); export const Text = ({ // TextNextLinkProps href, as, replace, scroll, shallow, // TouchableOpacitySomeProps onPress, onPressIn, disabled, // TextOwnProps variant, color, children, ...props }: TextProps) => { const variantStyle = useStyles(createStyles)[variant]; const theme = useTheme(); const colorStyle = useMemo( () => color && StyleSheet.create({ color: { color: theme.color[color] } }).color, [color, theme.color], ); const [hasHover, setHasHover] = useState(false); const onMouseEnter = useCallback(() => { setHasHover(true); }, []); const onMouseLeave = useCallback(() => { setHasHover(false); }, []); const text = ( {children || '…'} ); // TODO: Handle external hrefs, use Platform.select({ web: { href, target: '_blank' } }). if (href) return ( {text} ); if (onPress || onPressIn || disabled) { return ( {text} ); } return text; };