Last active
April 26, 2020 23:26
-
-
Save steida/47c0500e3607d50a548ddcecf0bc409c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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