@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
396 lines (381 loc) • 18.5 kB
JavaScript
import {defaultStr,isObj} from "$cutils";
import {Dialog} from "react-native-paper";
import {isNonNullString,defaultVal,defaultNumber,defaultObj,defaultBool } from "$cutils";
import {StyleSheet,ScrollView} from "react-native";
import View from "$ecomponents/View";
import Modal from "$ecomponents/Modal";
import PropTypes from "prop-types";
import React from "$react";
import theme,{Colors} from "$theme";
import AppBarDialog from "./AppBarDialog";
import DialogActions from "./DialogActions";
import DialogTitle from './DialogTitle';
import {MAX_WIDTH,SCREEN_INDENT,MIN_HEIGHT} from "./utils";
import {isMobileOrTabletMedia,isMobileMedia} from "$cplatform/dimensions";
import Platform,{isMobileNative} from "$cplatform";
import Icon,{BACK_ICON} from "$ecomponents/Icon";
import {ACTION_ICON_SIZE,getThemeColors} from "$ecomponents/AppBar";
import DialogFooter from "./DialogFooter";
import { Dimensions } from "react-native";
import Surface from "$ecomponents/Surface";
import DialogContent from "./DialogContent";
export const FOOTER_HEIGHT = 50;
export const HEADER_HEIGHT = 50;
export const CONTENT_MARGIN_TOP = 24;
export const measureInWindow = (ref)=>{
if(!ref||!ref.current || !ref.current.measureInWindow) return Promise.resolve({width:0,height:0,top:0,left:0});
return new Promise((resolve,reject)=>{
ref.current.measureInWindow((x, y, width, height)=>{
resolve({left:x,top:y,width,height});
})
})
}
const DialogComponent = React.forwardRef((props,ref)=>{
let {content,id,responsive,dismissable,onBackActionPress,propsMutator,children,
controlledProps,
cancelButton,///ça permet de spécifier si on ajoutera dynamiquement un bouton cancel, pour le rendu mobile du composant Dialog
onCancelButtonPress,no,yes,
onDismiss:customOnDismiss,backAction,onShow,backActionProps,
fullScreen:customFullScreen,appBarProps,contentProps,actionsProps,
title,subtitle,onMount,onUnmount,
titleProps,
visible,
scrollViewProps,withScrollView,
footerProps,footer,actionMutator,overlayProps,
ModalComponent,
isProvider,
isAlert,
onAlertRequestClose,
isFormData,
isPreloader,
borderRadius,
onCancel,
...modalProps
} = props;
withScrollView = typeof withScrollView =='boolean'? withScrollView : false;
ModalComponent = React.isComponent(ModalComponent)? ModalComponent : Modal;
modalProps = Object.assign({},modalProps);
overlayProps = defaultObj(overlayProps);
contentProps = defaultObj(contentProps);
scrollViewProps = defaultObj(scrollViewProps);
title = defaultVal(modalProps.title,title);
actionsProps= defaultObj(actionsProps);
footerProps = Object.assign({},footerProps);
const [context] = React.useState({});
content = content || children;
let fullScreen = typeof customFullScreen =='boolean'? customFullScreen : undefined;
let cFulllScreen = undefined;
const isResponsive = typeof responsive === "boolean" ? responsive : true;
if(isResponsive){
cFulllScreen = isMobileOrTabletMedia();
}
if(typeof fullScreen !=='boolean'){
fullScreen = cFulllScreen;
}
const isFullScreenDialog = context.isFullScreen = x => isAlert ? false : typeof customFullScreen =='boolean'? customFullScreen : typeof responsive ==='boolean' ? (responsive ? isMobileOrTabletMedia():false) : isMobileOrTabletMedia();
fullScreen = isAlert ? false : defaultBool(fullScreen,responsive !== false ? isMobileOrTabletMedia() : false);
const dimensions = Dimensions.get("window");
if(typeof propsMutator ==='function'){
modalProps.fullScreen = fullScreen;
propsMutator(modalProps);
}
let actions = modalProps.actions;
controlledProps = defaultObj(controlledProps);
appBarProps = defaultObj(appBarProps);
subtitle = subtitle !== false ? defaultVal(appBarProps.subtitle,modalProps.subtitle,subtitle) : null;
backActionProps = Object.assign({},backActionProps);
backActionProps.color = Colors.isValid(backActionProps.color)? backActionProps.color : getThemeColors().onPrimary;
cancelButton = cancelButton === false || modalProps.cancelButton === false ? null : isObj(cancelButton)? {...cancelButton} : {};
if(isNonNullString(no)){
no = {label:no};
}
if(isObj(no)){
cancelButton = cancelButton ? {...no,...cancelButton} : no;
backActionProps = {...no,...backActionProps};
}
context.isVisible = context.isOpen = x => visible ? true : false;
context.isClosed = x=> !visible ? true : false;
const isDimissable = defaultBool(dismissable,false);
const handleBack = (args,force)=>{
args = {...React.getOnPressArgs(args),isProvider,isFullScreen:isFullScreenDialog(),isPreloader,context,props};
if(typeof onBackActionPress =='function' && onBackActionPress(args) === false) return true;
if(typeof backActionProps.onPress =='function' && backActionProps.onPress(args)=== false) return true;
if(onCancelButtonPress && onCancelButtonPress(args) === false) return true;
else if(typeof onCancel =='function' && onCancel(args) === false) return true;
else if(isObj(cancelButton) && cancelButton.onPress && cancelButton.onPress(args) === false) return true;
if(force === false && !isDimissable) return true;
if(isAlert && typeof onAlertRequestClose =='function'){
return onAlertRequestClose(args);
}
if(typeof customOnDismiss =='function'){
const r = customOnDismiss(args);
return typeof r =='boolean'? r : true;
}
return true;
}
if(backAction === false){
backAction = null;
} else {
backAction = <Icon
icon = {BACK_ICON}
size = {ACTION_ICON_SIZE}
{...backActionProps}
onPress = {(e)=>{
handleBack(e,true);
}}
/>
}
const appBarRef = context.appBarRef = React.useRef(null);
const titleRef = context.titleRef = React.useRef(null);
const contentRef = context.contentRef = React.useRef(null);
const footerRef = context.footerRef = React.useRef(null);
const overlayRef = context.overlayRef = React.useRef(null);
const footerContentRef = context.footerContentRef = React.useRef(null);
const scrollViewRef = React.useRef(null);
const modalRef = context.modalRef = React.useRef(null);
const getMaxWidth = ()=>{
const {width} = Dimensions.get("window");
return Math.min(MAX_WIDTH,80*width/100);
}
const getMaxHeight = ()=>{
const {height} = Dimensions.get("window");
return Math.max((height>600?(50):70)*height/100,MIN_HEIGHT);
}
const onModalShown = (a)=>{
if(onShow){
onShow(a);
}
}
React.useEffect(()=>{
return ()=>{
React.setRef(ref,null);
}
},[]);
React.setRef(ref,context);
actionsProps = {...modalProps,context,dialogRef:context,...controlledProps}
const contentContainerProps = Object.assign({},modalProps.contentContainerProps);
const testID = defaultStr(modalProps.testID,"RN_DialogComponent");
const maxHeight = getMaxHeight(),maxWidth = getMaxWidth();
const backgroundColor = theme.surfaceBackgroundColor;
borderRadius = fullScreen || !(isMobileNative() || isMobileMedia()) || isPreloader ? 0 : typeof borderRadius =='number'? borderRadius : 5*theme.roundness;
const fullScreenStyle = fullScreen ? {width:dimensions.width,minHeight:MIN_HEIGHT,/*height:dimensions.height*/height:"100%",marginHorizontal:0,paddingHorizontal:0}: {
maxWidth,
maxHeight,
borderRadius,
paddingLeft : borderRadius,
paddingRight : borderRadius,
paddingVertical : borderRadius?10:0,
};
const getRRProps = (containerProps,setDimensions)=>{
if(!isPreloader){
const {mediaQueryUpdateStyle} = containerProps;
containerProps.mediaQueryUpdateStyle = (...rest)=>{
const r = typeof mediaQueryUpdateStyle =="function"? mediaQueryUpdateStyle(...rest) : undefined;
const {width,height} = rest[0];
let rW = setDimensions !== false ? {width,height} : {};
if(setDimensions !== false && isFullScreenDialog()){
rW = {width:'100%',height:'100%',minHeight:height,maxWidth:"100%",maxHeight:"100%"};
}
return isFullScreenDialog()? [r,rW] : [r,setDimensions !== false && {maxHeight:getMaxHeight(),maxWidth:getMaxWidth()}];
}
}
return containerProps;
}
const alertContentStyle = isAlert ? {paddingHorizontal:15} : null;
content = <View ref={contentRef} testID = {testID+"_Content11"} {...getRRProps(contentProps,false)} style={[fullScreen? {flex:1}:{maxWidth,maxHeight:maxHeight-Math.min(SCREEN_INDENT*2+50,100)},isPreloader && {paddingHorizontal:10},{backgroundColor},alertContentStyle,contentProps.style]}>
{content}
</View>
if(withScrollView){
content = <ScrollView centerContent
contentContainerStyle={{ flexGrow: 0, justifyContent: 'flex-start' }}
ref={scrollViewRef} testID={testID+"_ScrollViewContent"} {...scrollViewProps}>
{content}
</ScrollView>
}
return <ModalComponent
onDismiss={(e)=>{
return handleBack(e,false);
}}
{...modalProps}
isPreloader = {isPreloader}
dismissable = {isDimissable}
onShow = {onModalShown}
visible={visible}
style = {[styles.modal,modalProps.style]}
ref={modalRef}
testID = {testID}
contentContainerProps = {contentContainerProps}
>
<DialogContent isFullScreen={isFullScreenDialog} isPreloader={isPreloader}>
<Surface
testID = {testID+"_Overlay"}
ref={overlayRef}
{...getRRProps(overlayProps)}
style={[styles.overlay,isAlert && styles.overlayAlert,{backgroundColor},overlayProps.style,fullScreenStyle]}
>
{(!isAlert && (actions || title || subtitle)) ? <AppBarDialog
actionsProps = {actionsProps}
testID = {testID+"_AppBar"}
{...appBarProps}
actions = {actions}
actionMutator = {actionMutator}
ref = {appBarRef}
responsive = {isResponsive}
isFullScreen = {isFullScreenDialog}
fullScreen = {customFullScreen}
backAction = {backAction}
backActionProps = {{...backActionProps,onPress:(a)=>{
handleBack(a,true);
}}}
title = {title}
subtitle = {subtitle}
titleProps = {titleProps}
/>:null}
<DialogTitle
testID = {testID+"_Title"}
{...titleProps}
ref = {titleRef}
title = {title}
responsive = {isResponsive}
isFullScreen = {isFullScreenDialog}
fullScreen = {customFullScreen}
/>
{content}
{actions ? <DialogActions
testID = {testID+"_Footer"}
{...footerProps}
ref = {footerRef}
isAlert = {isAlert}
onAlertRequestClose = {onAlertRequestClose}
actionsProps = {actionsProps}
responsive = {isResponsive}
isFullScreen = {isFullScreenDialog}
fullScreen = {customFullScreen}
actions = {actions}
style = {[{backgroundColor},footerProps.style]}
actionMutator = {actionMutator}
cancelButton = {isAlert || !cancelButton || isPreloader ? null : {
icon : 'cancel',
mode : 'contained',
error : true,
canSave : false,
...cancelButton,
text : undefined,
label : defaultVal(cancelButton.label,cancelButton.text,"Annuler"),
onPress : (a1,a2)=>{
return handleBack(a1,true);
}
}}
menuProps = {defaultObj(appBarProps.menuProps)}
/> : null}
{(!isAlert && footer) ? <DialogFooter
{...footerProps}
style = {[{backgroundColor},footerProps.style]}
testID = {testID+"_FullPageFooter"}
ref = {footerContentRef}
responsive = {isResponsive}
isFullScreen = {isFullScreenDialog}
fullScreen = {customFullScreen}
children = {footer}
/> : null}
</Surface>
</DialogContent>
</ModalComponent>
});
export default DialogComponent;
DialogComponent.propTypes= {
...Modal.propTypes,
isAlert : PropTypes.bool,//si c'est le rendu alert, pour le rendu de l'alerte
ModalComponent : PropTypes.element,
withScrollView : PropTypes.bool,//si le modal utilisera un scrollView
contentProps : PropTypes.object,///les props du contenu à rendre à la boîte de dialogue
modalProps : PropTypes.object,//les props à paser au Modal react-native,
responsive : PropTypes.bool,//si le contenu est responsive
actions : PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
]),
propsMutator : PropTypes.func, ///la fonction permettant de muter les props de la boîte de de modalue en cours d'être rendu
children : PropTypes.node,
visible : PropTypes.bool,
fullScreen : PropTypes.bool,
onBackActionPress : PropTypes.func,//lorsque l'on clique sur le bouton backAction
withScrollView : PropTypes.bool,
title : PropTypes.any,
subtitle : PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
PropTypes.node,
]), //le subtitle de l'appBar lorsque le modal est fullScreen
appBarProps : PropTypes.object,//les props de l'appBar lorsque le modal est fullScreen
backAction : PropTypes.any,//si le back action sera autorisé
backActionProps : PropTypes.object, //les props du bouton backAction
footer : PropTypes.node, ///le contenu du footer lorsque la boîte de dialogue est en plein écran
footerProps : PropTypes.object, //les props du footer à appliquer aux actions de la bôite de dialogue en mode modal et au footer en mode plein écran
}
export const DialogControlledComponent = React.forwardRef(({visible:dVisible,...props},ref)=>{
const [visible,setVisible] = React.useState(defaultBool(dVisible,false));
const {onDismiss,onClose,onVisibilityChanged,...rest} = props;
React.useEffect(()=>{
if(typeof dVisible =='boolean' && dVisible !== visible){
setVisible(dVisible);
}
},[dVisible])
const close = x=> setVisible(false);
const open = x => setVisible(true);
const isOpen=x=>visible,isClosed = x=>!visible;
const ctx = {open,close,closeDialog:close,isOpen,isClosed,isDialogOpen:isOpen,isDialogClosed:isClosed,openDialog:open,setVisible};
return <DialogComponent ref={(el)=>{
if(el){
for(let i in ctx){
el[i] = ctx[i];
}
}
React.setRef(ref,el);
}}
controlledProps = {{controlledContext:ctx}}
{...rest}
visible={visible}
onDismiss={(args)=>{
if(typeof onDismiss ==='function' && onDismiss({...defaultObj(args),visible,open,close,setVisible}) ===false) return;
setVisible(false);
}}
/>
})
DialogControlledComponent.propTypes = {
...DialogComponent.propTypes,
dismissable : PropTypes.bool, //si la boîte de modalue peut être fermée en cliquant sur le bouton backAction en environnement android
onClose : PropTypes.func,
onVisibilityChanged : PropTypes.func,
}
DialogComponent.Controlled = DialogControlledComponent;
DialogComponent.displayName = "AppDialogComponent"
export const isDialog = Dialog.isDialog = (Component)=> typeof Component ==="function" && Component.displayName == DialogComponent.displayName;
const styles = StyleSheet.create({
modal : {
alignSelf : 'center',
marginVertical : 0,
marginHorizontal : 0,
paddingHorizontal:0,
paddingVertical : 0,
},
overlayAlert : {
paddingVertical : 5,
},
overlay: {
paddingHorizontal:0,
paddingVertical : 0,
marginHorizontal : 0,
justifyContent: 'flex-start',
flexGrow : 0,
marginVertical : 0,
//maxWidth : "100%",
...Platform.select({
default: {
shadowColor: 'rgba(0, 0, 0, .3)',
shadowOffset: { width: 0, height: 1 },
shadowRadius: 4,
},
}),
},
})