@fto-consult/expo-ui 2.8.0 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "2.8.0",
3
+ "version": "2.9.1",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "main": "index.js",
6
6
  "resolutions": {
@@ -67,7 +67,7 @@
67
67
  "@expo/metro-config": "^0.4.0",
68
68
  "@expo/vector-icons": "^13.0.0",
69
69
  "@expo/webpack-config": "^0.17.2",
70
- "@fto-consult/common": "^1.14.15",
70
+ "@fto-consult/common": "^1.14.16",
71
71
  "@gorhom/portal": "^1.0.14",
72
72
  "@react-native-async-storage/async-storage": "~1.17.3",
73
73
  "@react-native-community/datetimepicker": "6.5.2",
@@ -11,7 +11,7 @@ const AppexChartComponent = React.forwardRef(({chartContext,options,...props},re
11
11
  if (chartContext.current && typeof chartContext.current.destroy === 'function') {
12
12
  setTimeout(()=>{
13
13
  try {chartContext.current.destroy();} catch{}
14
- },500);
14
+ },1000);
15
15
  }
16
16
  }
17
17
  },[]);
@@ -1,6 +1,6 @@
1
1
  import React from '$react'
2
2
  import PropTypes from 'prop-types'
3
- import {defaultStr} from "$utils";
3
+ import {defaultStr,defaultObj,uniqid} from "$utils";
4
4
  import {extend} from "./utils"
5
5
  import stableHash from 'stable-hash';
6
6
  import Chart from "./appexChart";
@@ -14,30 +14,34 @@ import Chart from "./appexChart";
14
14
  * options {number} - les options supplémentaires au chart
15
15
  *
16
16
  */
17
- const ChartComponent = React.forwardRef(({type, height, width, series, options,testID,webViewProps, ...props },ref)=>{
17
+ const ChartComponent = React.forwardRef(({type, height,chartId, width, series, options,testID,webViewProps, ...props },ref)=>{
18
18
  const chartContext = React.useRef(null);
19
+ options = defaultObj(options);
20
+ const chartIdRef = React.useRef(defaultStr(chartId,options.chart?.id,uniqid("chart-id")));
19
21
  const config = extend(options,{
20
22
  chart: {
21
23
  type,
22
24
  height,
23
- width
25
+ width,
26
+ id : chartIdRef.current
24
27
  },
25
28
  series,
26
29
  });
30
+ config.chart.id = chartIdRef.current;
27
31
  testID = defaultStr(testID,"RN_ChartComponent");
28
32
  const prevWidth = React.usePrevious(width), prevHeight = React.usePrevious(height);
29
- const prevOptions = React.usePrevious(options,stableHash);
33
+ const prevOptions = React.usePrevious(options,JSON.stringify);
30
34
  const prevSeries = React.usePrevious(series,JSON.stringify);
31
35
  React.useEffect(()=>{
32
36
  if(chartContext.current && chartContext.current.updateOptions){
33
- if((JSON.stringify(prevSeries) == JSON.stringify(series)) || width != prevWidth || height != prevHeight){
37
+ if((prevSeries == series) || width != prevWidth || height != prevHeight){
34
38
  chartContext.current.updateOptions(config);
35
- } else if(JSON.stringify(prevOptions) != JSON.stringify(options)){
39
+ } else if(prevOptions != options){
36
40
  chartContext.current.updateSeries(series);
37
41
  }
38
42
  }
39
43
  },[stableHash({type,options,series,width,height})])
40
- return <Chart {...props} options={config} chartContext={chartContext} testID={testID} ref={ref}/>
44
+ return <Chart {...props} options={config} chartId={chartIdRef.current} chartContext={chartContext} testID={testID} ref={ref}/>
41
45
  });
42
46
 
43
47
 
@@ -1458,7 +1458,7 @@ export default class CommonDatagridComponent extends AppComponent {
1458
1458
  onPress = {sortMe}
1459
1459
  primary
1460
1460
  />: null}
1461
- <Label primary={isColumnSorted}>{ret}</Label>
1461
+ <Label textBold style={[{fontSize:13}]} primary={isColumnSorted}>{ret}</Label>
1462
1462
  </>
1463
1463
  </TouchableRipple>
1464
1464
  }
@@ -3,6 +3,7 @@ import {isObj,defaultStr} from "$utils";
3
3
  import View from "$ecomponents/View";
4
4
  import Portal from "$ecomponents/Portal";
5
5
  import theme,{Colors} from "$theme";
6
+ import Dimensions,{isMobileMedia,isTabletMedia,isDesktopMedia} from "$dimensions";
6
7
  import {
7
8
  StyleSheet,
8
9
  TouchableOpacity,
@@ -598,7 +599,7 @@ export default class DropdownAlert extends Component {
598
599
  wrapperStyle,
599
600
  tapToCloseEnabled,
600
601
  accessibilityLabel,
601
- testID,
602
+ testID:customTestId,
602
603
  accessible,
603
604
  contentContainerStyle,
604
605
  defaultTextContainer,
@@ -608,6 +609,7 @@ export default class DropdownAlert extends Component {
608
609
  showCancel,
609
610
  imageStyle,
610
611
  } = this.props;
612
+ const testID = defaultStr(customTestId,"RN_DropdownAlert");
611
613
  const {animationValue, bottomValue, height} = this.state;
612
614
  const {type, payload} = this.alertData;
613
615
  let style = this.getStyleForType(type);
@@ -641,14 +643,6 @@ export default class DropdownAlert extends Component {
641
643
  right: 0,
642
644
  elevation: elevation,
643
645
  };
644
- /*wrapperAnimStyle.zIndex = 1000100;
645
- if (zIndex != null) {
646
- wrapperAnimStyle.zIndex = zIndex;
647
- }*/
648
- let ContentView = View//SafeAreaView;
649
- if (IS_IOS_BELOW_11 || IS_ANDROID) {
650
- ContentView = View;
651
- }
652
646
  const activeOpacity = !tapToCloseEnabled || showCancel ? 1 : 0.95;
653
647
  const onPress = !tapToCloseEnabled
654
648
  ? null
@@ -658,28 +652,45 @@ export default class DropdownAlert extends Component {
658
652
  <Animated.View
659
653
  ref={(ref) => (this.mainView = ref)}
660
654
  {...this.panResponder.panHandlers}
655
+ testID={testID+"_AnimatedView"}
661
656
  style={[wrapperAnimStyle, wrapperStyle]}>
662
657
  <TouchableOpacity
663
- activeOpacity={activeOpacity}
664
- onPress={onPress}
665
- disabled={!tapToCloseEnabled}
666
- onLayout={(event) => this._onLayoutEvent(event)}
667
- testID={testID}
668
- accessibilityLabel={accessibilityLabel}
669
- accessible={accessible}>
670
- <View style={style}>
671
- <ContentView style={StyleSheet.flatten(contentContainerStyle)}>
672
- {this._renderImage(imageSrc, imageStyle)}
673
- <View style={StyleSheet.flatten(defaultTextContainer)}>
674
- {this._renderTitle()}
675
- {this._renderMessage()}
658
+ activeOpacity={activeOpacity}
659
+ onPress={onPress}
660
+ disabled={!tapToCloseEnabled}
661
+ onLayout={(event) => this._onLayoutEvent(event)}
662
+ testID={testID}
663
+ accessibilityLabel={accessibilityLabel}
664
+ accessible={accessible}>
665
+ <View style={style} testID={testID+"_ContentContainer"}>
666
+ <View testID={testID+"_ContentWrapper"} style={[contentContainerStyle,getContainerStyle().style]}
667
+ mediaQueryUpdateNativeProps={(args)=>{
668
+ return getContainerStyle(args);
669
+ }}
670
+ >
671
+ {this._renderImage(imageSrc, imageStyle)}
672
+ <View testID={testID+"_Content"} style={defaultTextContainer}>
673
+ {this._renderTitle()}
674
+ {this._renderMessage()}
675
+ </View>
676
+ {this._renderCancel(showCancel)}
676
677
  </View>
677
- {this._renderCancel(showCancel)}
678
- </ContentView>
679
678
  </View>
680
679
  </TouchableOpacity>
681
680
  </Animated.View>
682
681
  </Portal>
683
682
  );
684
683
  }
684
+ }
685
+
686
+ const getContainerStyle = (args)=>{
687
+ if(!isObj(args)){
688
+ args = {isMobile : isMobileMedia(),isTablet : isTabletMedia()};
689
+ }
690
+ const {width} = Dimensions.get("window");
691
+ return {
692
+ style : {
693
+ maxWidth : args.isMobile ? (90*width)/100 : args.isTablet? Math.max((70*width/100),350) : 500
694
+ }
695
+ }
685
696
  }
@@ -9,8 +9,122 @@ import Group from "./GroupComponent";
9
9
  import Portal from "$ecomponents/Portal";
10
10
  import {isAllowedFromStr} from "$cauth/perms";
11
11
 
12
+ const activeRef = {current:null};
13
+
14
+ export const isValid = (context)=>{
15
+ if(!isObj(context) || !isNonNullString(context.fabId) || typeof context.show !=="function" || context.hide !="function") return false;
16
+ return true;
17
+ }
18
+ /*** retourne le fab d'ont l'id est passé en paramètre */
19
+ export const getFab = (fabId)=>{
20
+ if(!isNonNullString(fabId)) return null;
21
+ return isValid(allFabs[fabId])? allFabs[fabId] : null;
22
+ }
23
+ const allFabs = {};
24
+ ///les ids des fabs
25
+ const fabIdRefs = {current:[]};
26
+
27
+ //ajoute l'id du fab dans la lite des fabActif
28
+ export const activateFabId = (fabId)=>{
29
+ if(!isNonNullString(fabId)) return fabIdRefs.current;
30
+ const nIds = desactivateFabId(fabId);
31
+ ///le fab active devient en top des id
32
+ nIds.push(fabId);
33
+ fabIdRefs.current = nIds;
34
+ }
35
+
36
+ export const desactivateFabId = (fabId)=>{
37
+ if(!isNonNullString(fabId)) return fabIdRefs.current;
38
+ const nIds = [];
39
+ for(let i in fabIdRefs.current){
40
+ if(fabIdRefs.current[i] != fabId){
41
+ nIds.push(fabIdRefs.current[i]);
42
+ }
43
+ }
44
+ fabIdRefs.current = nIds;
45
+ return nIds;
46
+ }
47
+
48
+ export const isActive = (fabId)=>{
49
+ return isNonNullString(fabId) && fabIdRefs.current[fabIdRefs.current.length-1] == fabId && isValid(getFab(fabId));
50
+ }
51
+
52
+ export const MANAGER = {
53
+ get active (){
54
+ return activeRef.current;
55
+ },
56
+ ///la liste des fabs
57
+ get all (){
58
+ return allFabs;
59
+ },
60
+ ///la liste des fabs Id
61
+ get fabIds (){
62
+ return fabIdRefs.current;
63
+ },
64
+ set active(active){
65
+ active = isValid(active)? active : null;
66
+ if(active){
67
+ ///on désactive l'ancien fab qui était actif
68
+ if(isValid(activeRef.current)){
69
+ activeRef.current.hide();
70
+ }
71
+ activateFabId(active.fabId);
72
+ activeRef.current = active;
73
+ } else {
74
+ //l'ancien fab devient active
75
+ let length = fabIdRefs.current.length-1;
76
+ let prevActive = null,prevFabId = null;
77
+ ///ça veut dire que l'ancien fab active a été démonté
78
+ if(isValid(activeRef.current)){
79
+ prevFabId = activeRef.current.fabId
80
+ }
81
+ while(length >=0 && !isValid(prevActive)){
82
+ const fId = fabIdRefs.current[length];
83
+ if(isNonNullString(fId) && fId !== prevFabId){
84
+ prevActive = allFabs[fId];
85
+ if(isValid(prevActive)){
86
+ break;
87
+ }
88
+ }
89
+ length --;
90
+ }
91
+ if(!prevActive){
92
+ fabIdRefs.current = [];
93
+ Object.map(allFabs,(f,i)=>{
94
+ delete allFabs[i];
95
+ })
96
+ }
97
+ if(isValid(prevActive)){
98
+ prevActive.show();
99
+ activateFabId(prevActive.fabId);
100
+ }
101
+ activeRef.current = prevActive;
102
+ }
103
+ },
104
+ get hasActive(){
105
+ return isValid(activeRef.current);
106
+ },
107
+ get get (){
108
+ return getFab;
109
+ }
110
+ };
111
+
112
+ export const activate = (args)=>{
113
+ const {context,fabId} = args;
114
+ if(!isNonNullString(fabId)|| !isObj(context)) return false;
115
+ let hasFound = false;
116
+ for(let i in allFabs){
117
+ if(allFabs[i]?.fabId == fabId){
118
+ hasFound = true;
119
+ break;
120
+ }
121
+ }
122
+ }
12
123
  const FabGroupComponent = React.forwardRef((props,innerRef)=>{
13
- let {openedIcon,screenName,display:customDisplay,primary,actionMutator,secondary,onOpen,prepareActions,fabStyle,open:customOpen,onClose,onStateChange:customOnStateChange,closedIcon,color,actions:customActions,children,...customRest} = props;
124
+ let {openedIcon,screenName,fabId,display:customDisplay,onMount,onUnmount,primary,actionMutator,secondary,onOpen,prepareActions,fabStyle,open:customOpen,onClose,onStateChange:customOnStateChange,closedIcon,color,actions:customActions,children,...customRest} = props;
125
+ const fabIdRef = React.useRef(defaultStr(fabId,uniqid("fab-id-ref")));
126
+ fabId = fabIdRef.current;
127
+ const isMountedRef = React.useRef(false);
14
128
  const [state, setState] = React.useStateIfMounted({
15
129
  open: typeof customOpen =='boolean'? customOpen : false,
16
130
  display : typeof customDisplay ==='boolean'? customDisplay : true,
@@ -27,6 +141,8 @@ const FabGroupComponent = React.forwardRef((props,innerRef)=>{
27
141
  const context = {
28
142
  open:x=>setState({...state,open:true}),
29
143
  close : x=> {setState({...state,open:false})},
144
+ fabId,
145
+ id : fabId,
30
146
  hide : x=> {
31
147
  setState({...state,display:false})
32
148
  },
@@ -35,9 +151,11 @@ const FabGroupComponent = React.forwardRef((props,innerRef)=>{
35
151
  },
36
152
  isHidden : x => !state.display,
37
153
  isShown : x => state.display,
154
+ isVisible : x=> state.display,
38
155
  isClosed : x => !state.open,
39
156
  isOpened : x => state.open,
40
157
  }
158
+ allFabs[fabId] = context;
41
159
  const {open,display} = state;
42
160
  openedIcon = defaultStr(openedIcon,"close");
43
161
  closedIcon = defaultStr(closedIcon,MENU_ICON);
@@ -77,11 +195,24 @@ const FabGroupComponent = React.forwardRef((props,innerRef)=>{
77
195
  },[customActions,prepareActions,open])();
78
196
 
79
197
  React.useEffect(()=>{
198
+ onMount && onMount(context);
80
199
  React.setRef(innerRef,context);
200
+ isMountedRef.current = true;
81
201
  return ()=>{
202
+ onUnmount && onUnmount({fabId});
203
+ delete allFabs[fabId];
204
+ desactivateFabId(fabId);
205
+ isMountedRef.current = false;
82
206
  React.setRef(innerRef,null);
83
207
  }
84
208
  },[])
209
+ React.useEffect(()=>{
210
+ if(display){
211
+ MANAGER.active = context;
212
+ } else {
213
+ MANAGER.active = null;
214
+ }
215
+ },[display]);
85
216
  return <Portal>
86
217
  <Group
87
218
  {...rest}
@@ -112,6 +243,9 @@ const actionType = PropTypes.shape({
112
243
  });
113
244
  FabGroupComponent.propTypes = {
114
245
  ...defaultObj(FAB.Group.propTypes),
246
+ fabId : PropTypes.string,//l'id du fab
247
+ onMount : PropTypes.func, ///lorsque le composant est monté
248
+ onUnmount : PropTypes.func, //lorsque le composant est démonté
115
249
  actionMutator : PropTypes.func,
116
250
  prepareActions : PropTypes.bool, //si un retraitement sera effectué sur les actions du FAB
117
251
  onOpen : PropTypes.func,
@@ -1,10 +1,11 @@
1
1
  import { Pressable } from "react-native";
2
2
  import { navigate } from "$cnavigation";
3
3
  import PropTypes from "prop-types";
4
- import {defaultStr} from "$utils";
4
+ import {defaultStr,defaultNumber} from "$utils";
5
5
  import React from "$react";
6
+
6
7
  const LinkComponent= React.forwardRef((props,ref)=>{
7
- let {Component,navigation,children,params,stopEventPropagation,source,onPress,routeParams,routeName,routeSource,routeType, ...rest} = props;
8
+ let {Component,navigation,children,params,stopEventPropagation,timeout,delay,source,onPress,routeParams,routeName,routeSource,routeType, ...rest} = props;
8
9
  const onRoutePress = (e)=>{
9
10
  if(stopEventPropagation !== false){
10
11
  React.stopEventPropagation(e);
@@ -12,7 +13,9 @@ const LinkComponent= React.forwardRef((props,ref)=>{
12
13
  if(onPress && onPress(e) === false){
13
14
  return;
14
15
  }
15
- navigate({routeName,previousRoute:undefined,routeParams,params,type:routeType,source:defaultStr(routeSource,source)},navigation);
16
+ setTimeout(()=>{
17
+ navigate({routeName,previousRoute:undefined,routeParams,params,type:routeType,source:defaultStr(routeSource,source)},navigation);
18
+ },defaultNumber(timeout,delay))
16
19
  };
17
20
  if(typeof children =='function'){
18
21
  return children ({...rest,onPress:onRoutePress},ref);
@@ -26,6 +29,8 @@ const LinkComponent= React.forwardRef((props,ref)=>{
26
29
  LinkComponent.propTypes = {
27
30
  stopEventPropagation : PropTypes.bool, //si la propagation d'évènement sera effective une fois qu'on ait cliqué sur le lien
28
31
  onPress : PropTypes.func,
32
+ timeout : PropTypes.number,
33
+ delay : PropTypes.number,//le delay d'attente lorsqu'on clique sur l'élément avant de faire la navigation
29
34
  routeName : PropTypes.string.isRequired,
30
35
  routeParams : PropTypes.object, //les paramètres à passer à la route
31
36
  routeType : PropTypes.string, //le type de route : stack ou drawer
@@ -213,7 +213,7 @@ const TableComponent = React.forwardRef(({containerProps,renderEmpty,isRowSelect
213
213
  React.setRef(tableRef,context);
214
214
  const cStyle = {width:listWidth}
215
215
  const absoluteScrollViewRef = React.useRef(null);
216
- return <View testID= {testID+"_Container"} {...containerProps} style={[styles.container,{alignItems:'stretch'},containerProps.style,theme.styles.pl0,theme.styles.pr0]}>
216
+ return <View testID= {testID+"_Container"} {...containerProps} style={[styles.container,{alignItems:'stretch'},containerProps.style]}>
217
217
  <RNView style={[cStyle]} testID={testID+"_Headers_ScrollViewContainer"}>
218
218
  <ScrollView
219
219
  testID={testID+"_HeaderScrollView"}
@@ -352,7 +352,8 @@ const styles = StyleSheet.create({
352
352
  width : '100%',
353
353
  minHeight : 300,
354
354
  paddingBottom : 50,
355
- paddingHorizontal : 5,
355
+ paddingLeft : 10,
356
+ paddingRight : 10,
356
357
  flex : 1,
357
358
  position : 'relative',
358
359
  },
@@ -16,6 +16,7 @@ const FABContainer = React.forwardRef((props,ref)=>{
16
16
  ref = ref || createFabRef(screenName);
17
17
  React.useEffect(()=>{
18
18
  const onFocusFab = ({sanitizedName})=>{
19
+ console.log(sanitizedName," is dddddd focused")
19
20
  const isFocused = sanitizedName === sScreenName;
20
21
  if(isFocused && ref.current && ref.current.show){
21
22
  ref.current.show();
@@ -107,8 +107,8 @@ const UserProfileAvatarComponent = React.forwardRef(({drawerRef,...props},ref)=>
107
107
  }}
108
108
  >
109
109
  <View style={styles.labelContainer}>
110
- <Label splitText style={{color:theme.colors.primaryOnSurface}}>{pseudo}</Label>
111
- <Label splitText style={{fontSize:12,color:theme.colors.secondaryOnSurface,marginTop:6}}>
110
+ <Label splitText numberOfLines={1} style={{color:theme.colors.primaryOnSurface,fontSize:14}}>{pseudo}</Label>
111
+ <Label splitText numberOfLines={1} style={{fontSize:12,color:theme.colors.secondaryOnSurface,marginTop:6}}>
112
112
  {label}
113
113
  </Label>
114
114
  {deviceName && <Label.withRef textBold splitText title={"Identifiant unique de l'application, installé sur cet appareil"} ref={deviceNameRef} secondary style={{fontSize:10}}>
@@ -1,4 +0,0 @@
1
- // Copyright 2022 @fto-consult/Boris Fouomene. All rights reserved.
2
- // Use of this source code is governed by a BSD-style
3
- // license that can be found in the LICENSE file.
4
-