import * as React from "react"; import { View, ViewStyle, StyleSheet, StyleProp, Animated } from "react-native"; import Surface from "../Surface"; import Text from "../Text"; import Button from "../Button"; import { SvgIcon } from "../Icon"; import type { $RemoveChildren } from "../types"; import shadow from "../theme/shadow"; import { DefaultTheme, ThemeContext } from "styled-components"; const ELEVATION = 1; const DEFAULT_MAX_WIDTH = 960; type Props = $RemoveChildren & { /** * Whether banner is currently visible. */ visible: boolean; /** * Content that will be displayed inside banner. */ children: string; /** * Icon to display for the `Banner`. Can be an image. */ icon?: React.ReactElement; /** * Action items to shown in the banner. * An action item should contain the following properties: * * - `label`: label of the action button (required) * - `onPress`: callback that is called when button is pressed (required) * * To customize button you can pass other props that button component takes. */ actions: Array<{ label: string; onPress: () => void; }>; /** * Style of banner's inner content. * Use this prop to apply custom width for wide layouts. */ contentStyle?: StyleProp; style?: StyleProp; ref?: React.RefObject; /** * @optional */ theme?: DefaultTheme; }; type NativeEvent = { nativeEvent: { layout: { x: number; y: number; width: number; height: number; }; }; }; /** * Banner displays a prominent message and related actions. * *
* *
* * ## Usage * ```js * import * as React from 'react'; * import { Image } from 'react-native'; * import Banner from 'react-native-simple-elements/components/Banner'; * * const MyComponent = () => { * const [visible, setVisible] = React.useState(true); * * return ( * setVisible(false), * }, * { * label: 'Learn more', * onPress: () => setVisible(false), * }, * ]} * icon={({size}) => ( * * )}> * There was a problem processing a transaction on your credit card. * * ); * }; * * export default MyComponent; * ``` */ const Banner = ({ visible, icon, children, actions, contentStyle, style, ...rest }: Props) => { const theme = React.useContext(ThemeContext); const { current: position } = React.useRef( new Animated.Value(visible ? 1 : 0) ); const [layout, setLayout] = React.useState<{ height: number; measured: boolean; }>({ height: 0, measured: false, }); const { scale } = theme.animation; React.useEffect(() => { if (visible) { // show Animated.timing(position, { duration: 250 * scale, toValue: 1, useNativeDriver: false, }).start(); } else { // hide Animated.timing(position, { duration: 200 * scale, toValue: 0, useNativeDriver: false, }).start(); } }, [visible, position, scale]); const handleLayout = ({ nativeEvent }: NativeEvent) => { const { height } = nativeEvent.layout; setLayout({ height, measured: true }); }; // The banner animation has 2 parts: // 1. Blank spacer element which animates its height to move the content // 2. Actual banner which animates its translateY // In initial render, we position everything normally and measure the height of the banner // Once we have the height, we apply the height to the spacer and switch the banner to position: absolute // We need this because we need to move the content below as if banner's height was being animated // However we can't animated banner's height directly as it'll also resize the content inside const height = Animated.multiply(position, layout.height); const translateY = Animated.multiply( Animated.add(position, -1), layout.height ); return ( {icon ? ( ) : null} {children} {actions.map(({ label, ...others }, i) => ( ))} ); }; const styles = StyleSheet.create({ container: { elevation: ELEVATION, }, wrapper: { overflow: "hidden", alignSelf: "center", width: "100%", maxWidth: DEFAULT_MAX_WIDTH, }, absolute: { position: "absolute", top: 0, width: "100%", }, content: { flexDirection: "row", justifyContent: "flex-start", marginHorizontal: 8, marginTop: 16, marginBottom: 0, }, icon: { margin: 8, }, message: { flex: 1, margin: 8, }, actions: { flexDirection: "row", justifyContent: "flex-end", margin: 4, }, button: { margin: 4, }, }); export default Banner;