import { lightTheme, darkTheme, borderRadius, shadows, palette } from '@expo/styleguide-native'; import * as React from 'react'; import { Text as RNText, Animated, useColorScheme, Pressable as RNPressable, Easing, } from 'react-native'; import { create } from './create-primitive'; import { rounded, margin, padding, text } from './theme'; const AnimatedPressable = Animated.createAnimatedComponent(RNPressable); const Text = create(RNText, { base: { fontFamily: 'Inter-Regular', color: lightTheme.text.default, fontSize: 16, lineHeight: 22, }, props: { accessibilityRole: 'text', }, variants: { ...text, color: { primary: { color: lightTheme.button.primary.foreground }, secondary: { color: lightTheme.button.secondary.foreground }, tertiary: { color: lightTheme.button.tertiary.foreground }, ghost: { color: lightTheme.button.ghost.foreground }, transparent: { color: lightTheme.button.transparent.foreground }, default: { color: lightTheme.text.default }, }, }, selectors: { dark: { base: { color: darkTheme.text.default, }, color: { primary: { color: darkTheme.button.primary.foreground }, secondary: { color: darkTheme.button.secondary.foreground }, tertiary: { color: darkTheme.button.tertiary.foreground }, ghost: { color: darkTheme.button.ghost.foreground }, transparent: { color: darkTheme.button.transparent.foreground }, default: { color: darkTheme.text.default }, }, }, }, }); const Container = create(AnimatedPressable, { base: { overflow: 'hidden', borderRadius: borderRadius.medium, }, props: { accessibilityRole: 'button', android_disableSound: true, }, variants: { bg: { default: { backgroundColor: lightTheme.background.default }, primary: { backgroundColor: lightTheme.button.primary.background }, secondary: { backgroundColor: lightTheme.button.secondary.background }, tertiary: { backgroundColor: lightTheme.button.tertiary.background }, ghost: { backgroundColor: lightTheme.button.ghost.background }, transparent: { backgroundColor: lightTheme.button.transparent.background }, disabled: { backgroundColor: lightTheme.status.default }, }, border: { ghost: { borderColor: lightTheme.button.ghost.border, borderWidth: 1 }, }, shadow: { button: shadows.button, }, ...rounded, ...padding, ...margin, }, selectors: { dark: { bg: { default: { backgroundColor: darkTheme.background.default }, primary: { backgroundColor: darkTheme.button.primary.background }, secondary: { backgroundColor: darkTheme.button.secondary.background }, tertiary: { backgroundColor: darkTheme.button.tertiary.background }, ghost: { backgroundColor: darkTheme.button.ghost.background }, transparent: { backgroundColor: darkTheme.button.transparent.background }, disabled: { backgroundColor: darkTheme.status.default }, }, }, }, }); export const Button = { Container, FadeOnPressContainer, HighlightOnPressContainer, Text, }; type FadingPressableProps = { activeOpacity?: number; }; type NoOptionals = { [P in keyof T]-?: T[P]; }; type ContainerProps = React.ComponentProps; type ContainerBackgroundColors = NoOptionals['bg']; const lightHighlightColorMap: Record = { disabled: 'transparent', default: lightTheme.background.secondary, primary: lightTheme.background.tertiary, secondary: lightTheme.background.quaternary, tertiary: palette.light.gray[600], ghost: lightTheme.background.tertiary, transparent: lightTheme.background.secondary, }; const darkHighlightColorMap: Record = { disabled: 'transparent', default: darkTheme.background.secondary, primary: darkTheme.background.tertiary, secondary: darkTheme.background.quaternary, tertiary: palette.dark.gray[600], ghost: darkTheme.background.tertiary, transparent: darkTheme.background.secondary, }; const highlightColorMap = { dark: darkHighlightColorMap, light: lightHighlightColorMap, }; function FadeOnPressContainer({ activeOpacity = 0.2, ...props }: React.ComponentProps & FadingPressableProps) { const animatedValue = React.useRef(new Animated.Value(1)); const onPressIn = React.useCallback(() => { Animated.timing(animatedValue.current, { toValue: activeOpacity, duration: 150, easing: Easing.inOut(Easing.quad), useNativeDriver: true, }).start(); }, []); const onPressOut = React.useCallback(() => { Animated.timing(animatedValue.current, { toValue: 1, duration: 150, easing: Easing.inOut(Easing.quad), useNativeDriver: true, }).start(); }, []); return ( {props.children} ); } function HighlightOnPressContainer(props: React.ComponentProps) { const theme = useColorScheme(); const [isPressing, setIsPressing] = React.useState(false); const onPressIn = React.useCallback(() => { setIsPressing(true); }, []); const onPressOut = React.useCallback(() => { setIsPressing(false); }, []); let backgroundColor = 'transparent'; if (props.bg && isPressing && theme != null) { backgroundColor = highlightColorMap[theme][props.bg]; } const underlayStyle = { backgroundColor, }; return ( {props.children} ); }