import * as React from "react"; import { Animated, ViewStyle, StyleSheet, StyleProp, TextStyle, View, } from "react-native"; import color from "color"; import ActivityIndicator from "../ActivityIndicator/ActivityIndicator"; import { SvgIcon } from "../Icon"; import Surface from "../Surface/Surface"; import Text from "../Text"; import TouchableRipple from "../TouchableRipple/TouchableRipple"; import { black, white } from "../theme/colors"; import { DefaultTheme, ThemeContext } from "styled-components"; type Props = React.ComponentProps & { /** * Mode of the button. You can change the mode to adjust the styling to give it desired emphasis. * - `text` - flat button without background or outline (low emphasis) * - `outlined` - button with an outline (medium emphasis) * - `contained` - button with a background color and elevation shadow (high emphasis) */ mode?: "text" | "outlined" | "contained"; /** * Whether the color is a dark color. A dark button will render light text and vice-versa. Only applicable for `contained` mode. */ dark?: boolean; /** * Use a compact look, useful for `text` buttons in a row. */ compact?: boolean; /** * Custom text color for flat button, or background color for contained button. */ color?: string; /** * Whether to show a loading indicator. */ loading?: boolean; /** * Icon to display for the `Button`. */ icon?: React.ReactElement; /** * Whether the button is disabled. A disabled button is greyed out and `onPress` is not called on touch. */ disabled?: boolean; /** * Label text of the button. */ children: React.ReactNode; /** * Make the label text uppercased. Note that this won't work if you pass React elements as children. */ uppercase?: boolean; /** * Accessibility label for the button. This is read by the screen reader when the user taps the button. */ accessibilityLabel?: string; /** * Function to execute on press. */ onPress?: () => void; /** * Function to execute on long press. */ onLongPress?: () => void; /** * Style of button's inner content. * Use this prop to apply custom height and width. */ contentStyle?: StyleProp; focusedStyle?: StyleProp; hoveredStyle?: StyleProp; pressedStyle?: StyleProp; style?: StyleProp; /** * Style for the button text. */ labelStyle?: StyleProp; /** * @optional */ theme?: DefaultTheme; /** * testID to be used on tests. */ testID?: string; }; /** * A button is component that the user can press to trigger an action. * *
*
* *
Text button
*
*
* *
Outlined button
*
*
* *
Contained button
*
*
* * ## Usage * ```js * import * as React from 'react'; * import Button from 'react-native-simple-elements/components/Button'; * * const MyComponent = () => ( * * ); * * export default MyComponent; * ``` */ const Button = ({ disabled, compact, mode = "text", dark, loading, icon, color: buttonColor, children, uppercase = true, accessibilityLabel, onPress, onLongPress, focusedStyle, hoveredStyle, pressedStyle, style, contentStyle, labelStyle, testID, accessible, ...rest }: Props) => { const theme = React.useContext(ThemeContext); const { current: elevation } = React.useRef( new Animated.Value(mode === "contained" ? 2 : 0) ); React.useEffect(() => { elevation.setValue(mode === "contained" ? 2 : 0); }, [mode, elevation]); const handlePressIn = () => { if (mode === "contained") { const { scale } = theme.animation; Animated.timing(elevation, { toValue: 8, duration: 200 * scale, useNativeDriver: true, }).start(); } }; const handlePressOut = () => { if (mode === "contained") { const { scale } = theme.animation; Animated.timing(elevation, { toValue: 2, duration: 150 * scale, useNativeDriver: true, }).start(); } }; const { colors, roundness } = theme; const font = theme.fonts.medium; let backgroundColor: string, borderColor: string, textColor: string, borderWidth: number; if (mode === "contained") { if (disabled) { backgroundColor = color(theme.dark ? white : black) .alpha(0.12) .rgb() .string(); } else if (buttonColor) { backgroundColor = buttonColor; } else { backgroundColor = colors.primary; } } else { backgroundColor = "transparent"; } if (mode === "outlined") { borderColor = color(theme.dark ? white : black) .alpha(0.29) .rgb() .string(); borderWidth = StyleSheet.hairlineWidth; } else { borderColor = "transparent"; borderWidth = 0; } if (disabled) { textColor = color(theme.dark ? white : black) .alpha(0.32) .rgb() .string(); } else if (mode === "contained") { let isDark; if (typeof dark === "boolean") { isDark = dark; } else { isDark = backgroundColor === "transparent" ? false : !color(backgroundColor).isLight(); } textColor = isDark ? white : black; } else if (buttonColor) { textColor = buttonColor; } else { textColor = colors.primary; } const rippleColor = color(textColor).alpha(0.32).rgb().string(); const buttonStyle = { backgroundColor, borderColor, borderWidth, borderRadius: roundness, }; const touchableStyle = { borderRadius: style ? ((StyleSheet.flatten(style) || {}) as ViewStyle).borderRadius || roundness : roundness, }; const { color: customLabelColor, fontSize: customLabelSize } = StyleSheet.flatten(labelStyle) || {}; const textStyle = { color: textColor, ...font }; const elevationRes = disabled || mode !== "contained" ? 0 : elevation; const iconStyle = StyleSheet.flatten(contentStyle)?.flexDirection === "row-reverse" ? styles.iconReverse : styles.icon; return ( {icon && loading !== true ? ( ) : null} {loading ? ( ) : null} {children} ); }; const styles = StyleSheet.create({ button: { minWidth: 64, borderStyle: "solid", }, compact: { minWidth: "auto", }, content: { flexDirection: "row", alignItems: "center", justifyContent: "center", }, icon: { marginLeft: 12, marginRight: -4, }, iconReverse: { marginRight: 12, marginLeft: -4, }, label: { textAlign: "center", letterSpacing: 1, marginVertical: 9, marginHorizontal: 16, }, compactLabel: { marginHorizontal: 8, }, uppercaseLabel: { textTransform: "uppercase", }, }); export default Button;