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.
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 for 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;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment