@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
252 lines (234 loc) • 6.68 kB
JavaScript
// Copyright 2022 @fto-consult/Boris Fouomene. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import React from '$react';
import {
Animated,
Easing,
StyleSheet,
View,
} from 'react-native';
import theme from "$theme";
import {defaultObj,isObj,defaultStr} from "$cutils";
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import useEventCallback from '$react/useEventCallback';
import PropTypes from "prop-types";
import Surface from '$ecomponents/Surface';
import Label from "$ecomponents/Label"
import Button from "$ecomponents/Button";
const DURATION_SHORT = 4000;
const DURATION_MEDIUM = 7000;
const DURATION_LONG = 10000;
const Snackbar = ({
visible,
action,
icon,
duration = DURATION_MEDIUM,
onDismiss,
children,
labelProps,
elevation = 2,
containerProps,
style,
testID,
contentProps,
...rest
}) => {
labelProps = defaultObj(labelProps);
containerProps = defaultObj(containerProps);
contentProps = defaultObj(contentProps);
labelProps = defaultObj(labelProps);
const { bottom, right, left } = useSafeAreaInsets();
testID = defaultStr(testID,"RN_SnackbarComponent")
const { current: opacity } = React.useRef(
new Animated.Value(0.0)
);
const hideTimeout = React.useRef(undefined);
const flattenStyle = StyleSheet.flatten(style) || {};
const [hidden, setHidden] = React.useState(!visible);
const scale = 1.0;
const handleOnVisible = useEventCallback(() => {
// show
if (hideTimeout.current) clearTimeout(hideTimeout.current);
setHidden(false);
Animated.timing(opacity, {
toValue: 1,
duration: 200 * scale,
easing: Easing.out(Easing.ease),
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) {
const isInfinity =
duration === Number.POSITIVE_INFINITY ||
duration === Number.NEGATIVE_INFINITY;
if (!isInfinity) {
hideTimeout.current = setTimeout(
onDismiss,
duration
);
}
}
});
});
const handleOnHidden = useEventCallback(() => {
// hide
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
Animated.timing(opacity, {
toValue: 0,
duration: 100 * scale,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) {
setHidden(true);
}
});
});
React.useEffect(() => {
return () => {
if (hideTimeout.current) clearTimeout(hideTimeout.current);
};
}, []);
React.useLayoutEffect(() => {
if (visible) {
handleOnVisible();
} else {
handleOnHidden();
}
}, [visible, handleOnVisible, handleOnHidden]);
const roundness = theme.roundness, colors = theme.colors;
action = React.isValidElement(action)? action : isObj(action) ? <Button {...action}/> : null;
if (hidden) {
return null;
}
const containerPaddings = {
paddingBottom: bottom,
paddingHorizontal: Math.max(left, right),
};
action = React.isValidElement(action)? action : null;
return (
<View
testID={testID+"_Container"}
{...containerProps}
pointerEvents="box-none"
style={[styles.wrapper, containerPaddings,action && theme.styles.pr1,containerProps.style]}
>
<Surface
pointerEvents="box-none"
role="polite"
elevation = {elevation}
{...rest}
testID={testID}
style={[
styles.container,
{
borderRadius: roundness,
opacity: opacity,
transform: [
{
scale: visible
? opacity.interpolate({
inputRange: [0, 1],
outputRange: [0.9, 1],
})
: 1,
},
],
},
style
]}
>
{<View testID={testID+"_Content"} {...contentProps} style={[styles.content,contentProps.style,action && theme.styles.pr1]}>
{typeof children =='string' || typeof children ==='string' ?
<Label {...labelProps} style={[{color:flattenStyle.color,backgroundColor:flattenStyle.backgroundColor},labelProps.style]}>
{children}
</Label> : React.isValidElement(children)? children : null}
</View>}
{action}
</Surface>
</View>
);
};
/**
* Show the Snackbar for a short duration.
*/
Snackbar.DURATION_SHORT = DURATION_SHORT;
/**
* Show the Snackbar for a medium duration.
*/
Snackbar.DURATION_MEDIUM = DURATION_MEDIUM;
/**
* Show the Snackbar for a long duration.
*/
Snackbar.DURATION_LONG = DURATION_LONG;
Snackbar.propTypes = {
/**
* Whether the Snackbar is currently visible.
*/
visible: PropTypes.bool,
/**
* Label and press callback for the action button. It should contain the following properties:
* - `label` - Label of the action button
* - `onPress` - Callback that is called when action button is pressed.
*/
action: PropTypes.oneOfType([
PropTypes.shape({
label: PropTypes.string,
}),
PropTypes.node,
PropTypes.element,
]),
duration : PropTypes.number,
/**
* Callback called when Snackbar is dismissed. The `visible` prop needs to be updated when this is called.
*/
onDismiss: PropTypes.func,
/**
* Text content of the Snackbar.
*/
children: PropTypes.any,
containerProps : PropTypes.object,
contentProps : PropTypes.object,
};
const styles = StyleSheet.create({
wrapper: {
position: 'absolute',
bottom: 0,
width: '100%',
},
container: {
flexDirection: 'row',
flexWrap : "wrap",
justifyContent: 'space-between',
margin: 0,
marginHorizontal : 10,
paddingRight : 10,
borderRadius: 4,
minHeight: 48,
},
content: {
marginHorizontal: 16,
marginVertical: 14,
flex: 1,
},
actionsContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
minHeight: 48,
},
button: {
marginRight: 8,
marginLeft: 4,
},
elevation: {
elevation: 6,
},
icon: {
width: 40,
height: 40,
margin: 0,
},
});
export default theme.withStyles(Snackbar,"SnackbarComponent");