@momo-kits/foundation 0.152.4-beta.3 → 0.152.4-beta.5

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.
@@ -1,6 +1,5 @@
1
1
  import React, { useCallback, useContext, useEffect, useRef } from 'react';
2
2
  import {
3
- BackHandler,
4
3
  Dimensions,
5
4
  KeyboardAvoidingView,
6
5
  Modal,
@@ -151,17 +150,6 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
151
150
  [onDismiss],
152
151
  );
153
152
 
154
- useEffect(() => {
155
- const backHandler = BackHandler.addEventListener(
156
- 'hardwareBackPress',
157
- () => {
158
- onDismiss();
159
- return true;
160
- },
161
- );
162
- return () => backHandler.remove();
163
- }, [barrierDismissible, onDismiss]);
164
-
165
153
  /**
166
154
  * render header
167
155
  */
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useContext, useEffect, useRef } from 'react';
1
+ import React, { useContext, useEffect, useRef } from 'react';
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -8,7 +8,6 @@ import {
8
8
  StyleSheet,
9
9
  View,
10
10
  Modal as ModalRN,
11
- BackHandler,
12
11
  } from 'react-native';
13
12
 
14
13
  import { ApplicationContext, MiniAppContext } from '../Context';
@@ -80,7 +79,7 @@ const Modal: React.FC<ModalParams> = props => {
80
79
  };
81
80
  }, [opacity, props.route.params, scale]);
82
81
 
83
- const onDismiss = useCallback((callback = () => {}, preventClose = false) => {
82
+ const onDismiss = (callback = () => {}, preventClose = false) => {
84
83
  if (preventClose) {
85
84
  return;
86
85
  }
@@ -100,18 +99,7 @@ const Modal: React.FC<ModalParams> = props => {
100
99
  navigator?.pop();
101
100
  runOnJS(callback)();
102
101
  });
103
- }, [navigator, opacity, scale]);
104
-
105
- useEffect(() => {
106
- const backHandler = BackHandler.addEventListener(
107
- 'hardwareBackPress',
108
- () => {
109
- onDismiss(undefined, barrierDismissible);
110
- return true;
111
- },
112
- );
113
- return () => backHandler.remove();
114
- }, [barrierDismissible, onDismiss]);
102
+ };
115
103
 
116
104
  return (
117
105
  <Container
@@ -1,5 +1,11 @@
1
- import {CommonActions, StackActions} from '@react-navigation/native';
2
- import {BottomSheetParams, ModalParams, ScreenParams} from './types';
1
+ import { CommonActions, StackActions } from '@react-navigation/native';
2
+ import {
3
+ BottomSheetParams,
4
+ ModalParams,
5
+ ScreenParams,
6
+ SnackBar,
7
+ SnackBarHandler,
8
+ } from './types';
3
9
 
4
10
  class Navigator {
5
11
  ref?: any;
@@ -7,6 +13,7 @@ class Navigator {
7
13
  isWidget?: any;
8
14
  maxApi?: any;
9
15
  dismissData?: any;
16
+ snackBarHost?: SnackBarHandler;
10
17
 
11
18
  constructor(navigation: any, isReady: any, isWidget = false) {
12
19
  this.ref = navigation;
@@ -160,12 +167,35 @@ class Navigator {
160
167
  */
161
168
  setCurrentContext = (context: {
162
169
  code?: string;
163
- name?: {vi: string; en: string};
164
- description?: {vi: string; en: string};
170
+ name?: { vi: string; en: string };
171
+ description?: { vi: string; en: string };
165
172
  icon?: string;
166
173
  }) => {
167
174
  console.log(context);
168
175
  };
176
+
177
+ /**
178
+ * register host to render snackbar inside current screen
179
+ * @param handler
180
+ */
181
+ setSnackBarHost = (handler?: SnackBarHandler) => {
182
+ this.snackBarHost = handler;
183
+ };
184
+
185
+ /**
186
+ * show snackbar via current host
187
+ * @param params
188
+ */
189
+ showSnackBar = (params: SnackBar) => {
190
+ this.snackBarHost?.show?.(params);
191
+ };
192
+
193
+ /**
194
+ * hide snackbar via current host
195
+ */
196
+ hideSnackBar = () => {
197
+ this.snackBarHost?.hide?.();
198
+ };
169
199
  }
170
200
 
171
201
  export default Navigator;
@@ -15,6 +15,25 @@ export type NavigationProps = {
15
15
  setOptions: (params: NavigationOptions) => void;
16
16
  };
17
17
 
18
+ export type SnackBarCustom = {
19
+ type: 'custom';
20
+ content: () => React.ReactNode;
21
+ heightContent?: number;
22
+ duration?: number;
23
+ };
24
+
25
+ export type SnackBarToast = {
26
+ type: 'toast';
27
+ duration?: number;
28
+ };
29
+
30
+ export type SnackBar = SnackBarCustom | SnackBarToast;
31
+
32
+ export type SnackBarHandler = {
33
+ show: (params: SnackBar) => void;
34
+ hide: () => void;
35
+ };
36
+
18
37
  export type NavigatorProps = {
19
38
  ref?: any;
20
39
  isReady?: any;
@@ -40,6 +59,9 @@ export type NavigatorProps = {
40
59
  description?: { vi: string; en: string };
41
60
  icon?: string;
42
61
  }) => void;
62
+ showSnackBar: (params: SnackBar) => void;
63
+ hideSnackBar: () => void;
64
+ setSnackBarHost?: (handler?: SnackBarHandler) => void;
43
65
  };
44
66
 
45
67
  export type LocalizeProps = {
package/Layout/Screen.tsx CHANGED
@@ -49,6 +49,10 @@ import {
49
49
  HeaderTitle,
50
50
  SearchHeader,
51
51
  } from '../Application';
52
+ import {
53
+ SnackBarContext,
54
+ useScreenSnackBar,
55
+ } from './SnackBar';
52
56
 
53
57
  export interface ScreenProps extends ViewProps {
54
58
  /**
@@ -191,7 +195,7 @@ const Screen = forwardRef(
191
195
  ) => {
192
196
  const screenRef = useRef<View | ScrollView>(null);
193
197
  const { width: widthDevice } = useWindowDimensions();
194
- const { theme } = useContext(ApplicationContext);
198
+ const { theme, navigator } = useContext(ApplicationContext);
195
199
  const screen: any = useContext(ScreenContext);
196
200
  const insets = useSafeAreaInsets();
197
201
  const heightHeader = useHeaderHeight();
@@ -200,6 +204,18 @@ const Screen = forwardRef(
200
204
  );
201
205
  const currentTint = useRef<string | undefined>(undefined);
202
206
  const isTab = navigation?.instance?.getState?.()?.type === 'tab';
207
+ const {
208
+ snackBarContextValue,
209
+ snackBarConfig,
210
+ snackBarBottomOffset,
211
+ snackBarTranslateY,
212
+ handleFooterLayout,
213
+ handleSnackBarLayout,
214
+ } = useScreenSnackBar({
215
+ Footer,
216
+ bottomInset: insets.bottom,
217
+ navigator,
218
+ });
203
219
 
204
220
  let handleScroll;
205
221
  let Component: any = View;
@@ -564,79 +580,102 @@ const Screen = forwardRef(
564
580
  };
565
581
 
566
582
  return (
567
- <View
568
- style={[
569
- Styles.flex,
570
- {
571
- backgroundColor: backgroundColor ?? theme.colors.background.default,
572
- },
573
- ]}
574
- >
575
- <HeaderExtendHeader
576
- headerType={headerType}
577
- heightHeader={heightHeader}
578
- headerRightWidth={headerRightWidth}
579
- animatedValue={animatedValue.current}
580
- inputSearchProps={inputSearchProps}
581
- navigation={navigation}
582
- inputSearchRef={inputSearchRef}
583
- useShadowHeader={useShadowHeader}
584
- gradientColor={gradientColor}
585
- headerBackground={headerBackground}
586
- />
587
-
588
- <KeyboardAvoidingView
589
- style={Styles.flex}
590
- enabled={enableKeyboardAvoidingView}
591
- keyboardVerticalOffset={keyboardVerticalOffset ?? keyboardOffset}
592
- behavior={Platform.select({
593
- ios: 'padding',
594
- android: undefined,
595
- })}
583
+ <SnackBarContext.Provider value={snackBarContextValue}>
584
+ <View
585
+ style={[
586
+ Styles.flex,
587
+ {
588
+ backgroundColor: backgroundColor ?? theme.colors.background.default,
589
+ },
590
+ ]}
596
591
  >
597
- {Header}
598
-
599
- <Component
600
- {...scrollViewProps}
601
- ref={screenRef}
602
- showsVerticalScrollIndicator={false}
603
- onScroll={handleScroll}
604
- onScrollEndDrag={handleScrollEnd}
605
- scrollEventThrottle={16}
592
+ <HeaderExtendHeader
593
+ headerType={headerType}
594
+ heightHeader={heightHeader}
595
+ headerRightWidth={headerRightWidth}
596
+ animatedValue={animatedValue.current}
597
+ inputSearchProps={inputSearchProps}
598
+ navigation={navigation}
599
+ inputSearchRef={inputSearchRef}
600
+ useShadowHeader={useShadowHeader}
601
+ gradientColor={gradientColor}
602
+ headerBackground={headerBackground}
603
+ />
604
+
605
+ <KeyboardAvoidingView
606
606
  style={Styles.flex}
607
+ enabled={enableKeyboardAvoidingView}
608
+ keyboardVerticalOffset={keyboardVerticalOffset ?? keyboardOffset}
609
+ behavior={Platform.select({
610
+ ios: 'padding',
611
+ android: undefined,
612
+ })}
607
613
  >
608
- {renderAnimatedHeader()}
609
-
610
- {useGridLayout ? renderContent(children) : children}
611
- </Component>
612
-
613
- {floatingButtonProps && (
614
- <View>
615
- <FloatingButton
616
- {...floatingButtonProps}
617
- animatedValue={animatedValue.current}
618
- bottom={
619
- Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
620
- }
621
- />
622
- </View>
623
- )}
624
-
625
- {Footer && (
626
- <View
627
- style={[
628
- styles.shadow,
629
- {
630
- paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
631
- backgroundColor: theme.colors.background.surface,
632
- },
633
- ]}
614
+ {Header}
615
+
616
+ <Component
617
+ {...scrollViewProps}
618
+ ref={screenRef}
619
+ showsVerticalScrollIndicator={false}
620
+ onScroll={handleScroll}
621
+ onScrollEndDrag={handleScrollEnd}
622
+ scrollEventThrottle={16}
623
+ style={Styles.flex}
634
624
  >
635
- <Section>{Footer}</Section>
636
- </View>
637
- )}
638
- </KeyboardAvoidingView>
639
- </View>
625
+ {renderAnimatedHeader()}
626
+
627
+ {useGridLayout ? renderContent(children) : children}
628
+ </Component>
629
+
630
+ {floatingButtonProps && (
631
+ <View>
632
+ <FloatingButton
633
+ {...floatingButtonProps}
634
+ animatedValue={animatedValue.current}
635
+ bottom={
636
+ Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
637
+ }
638
+ />
639
+ </View>
640
+ )}
641
+
642
+ {snackBarConfig?.type === 'custom' && (
643
+ <Animated.View
644
+ accessibilityLiveRegion="polite"
645
+ style={{
646
+ position: 'absolute',
647
+ width: '100%',
648
+ bottom: snackBarBottomOffset,
649
+ transform: [{ translateY: snackBarTranslateY }],
650
+ }}
651
+ >
652
+ <View
653
+ accessible
654
+ accessibilityRole="summary"
655
+ onLayout={handleSnackBarLayout}
656
+ >
657
+ {snackBarConfig.content?.()}
658
+ </View>
659
+ </Animated.View>
660
+ )}
661
+
662
+ {Footer && (
663
+ <View
664
+ onLayout={handleFooterLayout}
665
+ style={[
666
+ styles.shadow,
667
+ {
668
+ paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
669
+ backgroundColor: theme.colors.background.surface,
670
+ },
671
+ ]}
672
+ >
673
+ <Section>{Footer}</Section>
674
+ </View>
675
+ )}
676
+ </KeyboardAvoidingView>
677
+ </View>
678
+ </SnackBarContext.Provider>
640
679
  );
641
680
  },
642
681
  );
@@ -0,0 +1,163 @@
1
+ import {
2
+ createContext,
3
+ ReactNode,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import { Animated, LayoutChangeEvent } from 'react-native';
11
+ import { Spacing } from '../Consts';
12
+ import { NavigatorProps, SnackBar } from '../Application/types';
13
+
14
+ type SnackBarContextValue = {
15
+ showSnackBar: (params: SnackBar) => void;
16
+ hideSnackBar: () => void;
17
+ isVisible: boolean;
18
+ };
19
+
20
+ export const SnackBarContext = createContext<SnackBarContextValue>({
21
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
22
+ showSnackBar: () => { },
23
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
24
+ hideSnackBar: () => { },
25
+ isVisible: false,
26
+ });
27
+
28
+ type UseScreenSnackBarParams = {
29
+ /**
30
+ * Footer component passed to Screen.
31
+ * Used only to compute bottom offset when it exists.
32
+ */
33
+ Footer?: ReactNode;
34
+ /**
35
+ * Bottom safe-area inset of the device.
36
+ */
37
+ bottomInset: number;
38
+ /**
39
+ * Global navigator instance to register snack bar host on.
40
+ */
41
+ navigator?: NavigatorProps;
42
+ };
43
+
44
+ export const useScreenSnackBar = ({
45
+ Footer,
46
+ bottomInset,
47
+ navigator,
48
+ }: UseScreenSnackBarParams) => {
49
+ const snackBarAnimation = useRef(new Animated.Value(0));
50
+ const [snackBarConfig, setSnackBarConfig] = useState<SnackBar | null>(null);
51
+ const [snackBarHeight, setSnackBarHeight] = useState(0);
52
+ const [footerHeight, setFooterHeight] = useState(0);
53
+ const footerHeightRef = useRef(0);
54
+
55
+ const animateSnackBar = useCallback(
56
+ (toValue: number, duration: number, onEnd?: () => void) => {
57
+ snackBarAnimation.current.stopAnimation();
58
+ Animated.timing(snackBarAnimation.current, {
59
+ toValue,
60
+ duration,
61
+ useNativeDriver: true,
62
+ }).start(({ finished }) => {
63
+ if (finished) {
64
+ onEnd?.();
65
+ }
66
+ });
67
+ },
68
+ [],
69
+ );
70
+
71
+ const hideSnackBar = useCallback(() => {
72
+ animateSnackBar(0, 200, () => {
73
+ setSnackBarConfig(null);
74
+ });
75
+ }, [animateSnackBar]);
76
+
77
+ const showSnackBar = useCallback(
78
+ (config: SnackBar) => {
79
+ setSnackBarConfig(config);
80
+ animateSnackBar(1, 350);
81
+ },
82
+ [animateSnackBar],
83
+ );
84
+
85
+ const navigatorRef = useRef(navigator);
86
+
87
+ useEffect(() => {
88
+ navigatorRef.current = navigator;
89
+ }, [navigator]);
90
+
91
+ useEffect(() => {
92
+ if (!snackBarConfig?.duration) return;
93
+
94
+ const timer = setTimeout(() => {
95
+ navigatorRef.current?.hideSnackBar();
96
+ }, snackBarConfig.duration);
97
+
98
+ return () => clearTimeout(timer);
99
+ }, [snackBarConfig?.duration]);
100
+
101
+ useEffect(() => {
102
+ navigator?.setSnackBarHost?.({
103
+ show: showSnackBar,
104
+ hide: hideSnackBar,
105
+ });
106
+
107
+ return () => {
108
+ navigator?.setSnackBarHost?.(undefined);
109
+ };
110
+ }, [navigator, showSnackBar, hideSnackBar]);
111
+
112
+ const snackBarContextValue = useMemo(
113
+ () => ({
114
+ showSnackBar,
115
+ hideSnackBar,
116
+ isVisible: !!snackBarConfig,
117
+ }),
118
+ [showSnackBar, hideSnackBar, snackBarConfig],
119
+ );
120
+
121
+ const handleFooterLayout = useCallback((event: LayoutChangeEvent) => {
122
+ const { height } = event.nativeEvent.layout;
123
+ if (height !== footerHeightRef.current) {
124
+ footerHeightRef.current = height;
125
+ setFooterHeight(height);
126
+ }
127
+ }, []);
128
+
129
+ const handleSnackBarLayout = useCallback(
130
+ (event: LayoutChangeEvent) => {
131
+ const { height } = event.nativeEvent.layout;
132
+ if (height !== snackBarHeight) {
133
+ setSnackBarHeight(height);
134
+ }
135
+ },
136
+ [snackBarHeight],
137
+ );
138
+
139
+ const snackBarBottomOffset =
140
+ Math.min(bottomInset, 21) + Spacing.S + (Footer ? footerHeight : 0);
141
+
142
+ const snackBarCustomHeight =
143
+ snackBarConfig?.type === 'custom' ? snackBarConfig.heightContent : 0;
144
+ const snackBarHiddenOffset =
145
+ (snackBarHeight || snackBarCustomHeight || 0) + Spacing.XL;
146
+
147
+ const snackBarTranslateY = snackBarAnimation.current.interpolate({
148
+ inputRange: [0, 1],
149
+ outputRange: [snackBarHiddenOffset, 0],
150
+ });
151
+
152
+ return {
153
+ snackBarContextValue,
154
+ snackBarConfig,
155
+ snackBarBottomOffset,
156
+ snackBarTranslateY,
157
+ handleFooterLayout,
158
+ handleSnackBarLayout,
159
+ };
160
+ };
161
+
162
+
163
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.152.4-beta.3",
3
+ "version": "0.152.4-beta.5",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},