Skip to content

Instantly share code, notes, and snippets.

@steida
Last active April 26, 2020 23:26
Show Gist options
  • Save steida/47c0500e3607d50a548ddcecf0bc409c to your computer and use it in GitHub Desktop.
Save steida/47c0500e3607d50a548ddcecf0bc409c to your computer and use it in GitHub Desktop.

Revisions

  1. steida revised this gist Apr 26, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Text.tsx
    Original file line number Diff line number Diff line change
    @@ -133,7 +133,7 @@ const styles = StyleSheet.create({
    });

    // Next.js needs ref for Intersection Observer based prefetching.
    // That's how we delegate it to React Native or Web Text component.
    // That's how we delegate it to React Native for Web Text component.
    const RNTextWithDOMRef = forwardRef(
    (props: RNTextProps & { children: ReactNode }, ref) => {
    return (
  2. steida created this gist Apr 26, 2020.
    224 changes: 224 additions & 0 deletions Text.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,224 @@
    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<typeof createStyles>;
    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 or Web Text component.
    const RNTextWithDOMRef = forwardRef(
    (props: RNTextProps & { children: ReactNode }, ref) => {
    return (
    <RNText
    {...props}
    // @ts-ignore RNfW prop.
    forwardedRef={ref}
    />
    );
    },
    );

    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 = (
    <RNTextWithDOMRef
    {...props}
    {...(href && {
    accessibilityRole: 'link',
    onMouseEnter,
    onMouseLeave,
    })}
    style={[
    variantStyle,
    colorStyle,
    props.style,
    hasHover && styles.linkHover,
    ]}
    >
    {children || '…'}
    </RNTextWithDOMRef>
    );

    // TODO: Handle external hrefs, use Platform.select({ web: { href, target: '_blank' } }).
    if (href)
    return (
    <Link {...{ href, as, replace, scroll, shallow, passHref: true }}>
    {text}
    </Link>
    );

    if (onPress || onPressIn || disabled) {
    return (
    <TouchableOpacity
    accessibilityRole="button"
    {...{ onPress, onPressIn, disabled }}
    {...(disabled && { style: { opacity: theme.opacity.disabled } })}
    >
    {text}
    </TouchableOpacity>
    );
    }

    return text;
    };