@momo-kits/foundation 1.0.3 → 1.0.4

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/Button/index.tsx CHANGED
@@ -41,7 +41,6 @@ const Button: FC<ButtonProps> = ({
41
41
  ...rest
42
42
  }) => {
43
43
  const {theme} = useContext(ApplicationContext);
44
-
45
44
  const getSizeStyle = () => {
46
45
  const styleSheet: {[key: string]: any} = styles;
47
46
  return styleSheet[size ?? 'small'];
@@ -1,12 +1,10 @@
1
1
  import React, {FC, useContext} from 'react';
2
2
  import {TouchableOpacity, View} from 'react-native';
3
-
4
3
  import {CheckBoxProps} from './types';
5
- import {Text} from '@momo-kits/foundation';
6
4
  import styles from './styles';
7
5
  import Image from 'react-native-fast-image';
8
6
  import {ApplicationContext} from '../Navigation';
9
- import {Colors} from '../Consts';
7
+ import {Text} from '../Text';
10
8
 
11
9
  const IC_INDETERMINATED = 'https://img.mservice.com.vn/app/img/kits/minus.png';
12
10
  const IC_CHECKED_DEFAULT =
@@ -1,8 +1,8 @@
1
1
  import React, {useContext, useEffect, useMemo, useRef} from 'react';
2
2
  import {Animated, Platform, useWindowDimensions, View} from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
3
4
  import {ContentLoaderTypes} from './types';
4
5
  import {ApplicationContext} from '../Navigation';
5
- import LinearGradient from 'react-native-linear-gradient';
6
6
  import {Styles} from '../Consts';
7
7
  import styles from './styles';
8
8
  const ContentLoader: React.FC<ContentLoaderTypes> = ({style}) => {
package/Icon/icon.json CHANGED
@@ -3931,5 +3931,8 @@
3931
3931
  },
3932
3932
  "header_background": {
3933
3933
  "uri": "https://img.mservice.com.vn/app/img/kits/navigation-bar.png"
3934
+ },
3935
+ "ic_error": {
3936
+ "uri": "https://img.mservice.com.vn/app/img/kits/error_mess_icon.png"
3934
3937
  }
3935
3938
  }
package/Icon/index.tsx CHANGED
@@ -1,8 +1,10 @@
1
1
  import React, {useContext} from 'react';
2
- import {IconProps, Image, ApplicationContext} from '../index';
3
2
  import IconSources from './icon.json';
3
+ import {IconProps} from './types';
4
+ import {ApplicationContext} from '../Navigation';
5
+ import {Image} from '../Image';
4
6
 
5
- const Icon: React.FC<IconProps> = ({source, size, color}) => {
7
+ const Icon: React.FC<IconProps> = ({source, size, color, style = {}}) => {
6
8
  const {theme} = useContext(ApplicationContext);
7
9
 
8
10
  /**
@@ -10,7 +12,7 @@ const Icon: React.FC<IconProps> = ({source, size, color}) => {
10
12
  */
11
13
  const getIconSource = (): any => {
12
14
  if (source && !source.includes('http')) {
13
- let icon: {[key: string]: object} = IconSources;
15
+ let icon: {[key: string]: {uri: string}} = IconSources;
14
16
  return icon[source] ?? icon.ic_warning;
15
17
  }
16
18
  return {uri: source};
@@ -19,7 +21,7 @@ const Icon: React.FC<IconProps> = ({source, size, color}) => {
19
21
  return (
20
22
  <Image
21
23
  source={getIconSource()}
22
- style={{width: size, height: size}}
24
+ style={[style, {width: size, height: size}]}
23
25
  tintColor={color ?? theme.colors.text.default}
24
26
  />
25
27
  );
package/Icon/types.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import {ViewStyle} from 'react-native';
2
+
1
3
  export type IconProps = {
2
- source?: string;
4
+ source: string;
3
5
  size?: number;
4
6
  color?: string;
7
+ style?: ViewStyle;
5
8
  };
package/Image/index.tsx CHANGED
@@ -1,15 +1,18 @@
1
1
  import React, {useContext, useState} from 'react';
2
- import {StyleSheet, View} from 'react-native';
2
+ import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native';
3
3
  import FastImage from 'react-native-fast-image';
4
-
5
4
  import styles from './styles';
6
- import {ImageProps} from './types';
7
5
  import {ApplicationContext} from '../Navigation';
8
6
  import {ContentLoader} from '../ContentLoader';
9
7
  import {Icon} from '../Icon';
10
8
  import {Styles} from '../Consts';
9
+ import {FastImagePropsWithoutStyle} from './types';
10
+
11
+ export interface ImageProps extends FastImagePropsWithoutStyle {
12
+ style?: StyleProp<ViewStyle>;
13
+ }
11
14
 
12
- const Image: React.FC<ImageProps> = ({style, placeholder, source, ...rest}) => {
15
+ const Image: React.FC<ImageProps> = ({style, source, ...rest}) => {
13
16
  const {theme} = useContext(ApplicationContext);
14
17
  const [loading, setLoading] = useState(typeof source === 'object');
15
18
  const [fail, setFail] = useState(false);
@@ -20,7 +23,7 @@ const Image: React.FC<ImageProps> = ({style, placeholder, source, ...rest}) => {
20
23
  */
21
24
  const renderContent = () => {
22
25
  if (loading || fail) {
23
- let content = placeholder ?? (
26
+ let content = (
24
27
  <ContentLoader style={[StyleSheet.absoluteFill, styles.image]} />
25
28
  );
26
29
  if (fail) {
@@ -61,8 +64,4 @@ const Image: React.FC<ImageProps> = ({style, placeholder, source, ...rest}) => {
61
64
  );
62
65
  };
63
66
 
64
- Image.defaultProps = {
65
- style: {},
66
- };
67
-
68
67
  export {Image};
package/Image/types.ts CHANGED
@@ -1,5 +1,3 @@
1
1
  import {FastImageProps} from 'react-native-fast-image';
2
2
 
3
- export interface ImageProps extends FastImageProps {
4
- placeholder?: boolean;
5
- }
3
+ export type FastImagePropsWithoutStyle = Omit<FastImageProps, 'style'>;
package/Input/Input.tsx CHANGED
@@ -3,7 +3,6 @@ import {
3
3
  NativeSyntheticEvent,
4
4
  TextInput,
5
5
  TextInputFocusEventData,
6
- TextInputProps,
7
6
  TouchableOpacity,
8
7
  View,
9
8
  } from 'react-native';
@@ -13,12 +12,7 @@ import {Text} from '../Text';
13
12
  import {Image} from '../Image';
14
13
  import {getBorderColor, renderFloatingView} from './common';
15
14
  import {InputProps} from './index';
16
-
17
- const errorMessageIcon =
18
- 'https://img.mservice.com.vn/app/img/kits/error_mess_icon.png';
19
- const ic_clear =
20
- 'https://img.mservice.io/momo_app_v2/new_version/img/appx_icon/24_navigation_close_circle_full.png';
21
- const MAX_LENGTH = 100;
15
+ import {Icon} from '../Icon';
22
16
 
23
17
  const Input: FC<InputProps> = ({
24
18
  value,
@@ -34,11 +28,11 @@ const Input: FC<InputProps> = ({
34
28
  disabled,
35
29
  floatingIconColor,
36
30
  iconColor,
31
+ ...props
37
32
  }) => {
38
33
  const {theme} = useContext(ApplicationContext);
39
-
40
34
  const [focused, setFocused] = useState(false);
41
- const inputRef = useRef(null);
35
+ const inputRef = useRef<any>(null);
42
36
 
43
37
  const onClearText = () => {
44
38
  inputRef?.current?.clear();
@@ -91,6 +85,7 @@ const Input: FC<InputProps> = ({
91
85
  )}
92
86
  <View style={styles.inputView}>
93
87
  <TextInput
88
+ {...props}
94
89
  editable={!disabled}
95
90
  textAlignVertical="top"
96
91
  ref={inputRef}
@@ -112,10 +107,10 @@ const Input: FC<InputProps> = ({
112
107
  <View style={styles.iconView}>
113
108
  {focused && (
114
109
  <TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
115
- <Image
116
- tintColor={theme.colors.text.hint}
117
- source={{uri: ic_clear}}
118
- style={styles.iconClose}
110
+ <Icon
111
+ source="24_navigation_close_circle_full"
112
+ size={16}
113
+ color={theme.colors.text.hint}
119
114
  />
120
115
  </TouchableOpacity>
121
116
  )}
@@ -135,7 +130,13 @@ const Input: FC<InputProps> = ({
135
130
  if (errorMessage) {
136
131
  return (
137
132
  <View style={styles.errorView}>
138
- <Image style={styles.errorIcon} source={{uri: errorMessageIcon}} />
133
+ <View style={styles.errorIcon}>
134
+ <Icon
135
+ source="ic_error"
136
+ size={16}
137
+ color={theme.colors.error.primary}
138
+ />
139
+ </View>
139
140
  <Text color={theme.colors.error.primary} typography={'description_s'}>
140
141
  {errorMessage}
141
142
  </Text>
@@ -154,8 +155,6 @@ const Input: FC<InputProps> = ({
154
155
 
155
156
  Input.defaultProps = {
156
157
  size: 'large',
157
- maxLength: MAX_LENGTH,
158
- disabled: false,
159
158
  };
160
159
 
161
160
  export default Input;
@@ -3,21 +3,16 @@ import {
3
3
  NativeSyntheticEvent,
4
4
  TextInput,
5
5
  TextInputFocusEventData,
6
- TextInputProps,
7
6
  TouchableOpacity,
8
7
  View,
9
8
  } from 'react-native';
10
9
  import styles from './styles';
11
- import {Image} from '../Image';
12
10
  import {Text} from '../Text';
13
11
  import {ApplicationContext} from '../Navigation';
14
12
  import {getBorderColor, renderFloatingView} from './common';
15
13
  import {TextAreaProps} from './index';
14
+ import {Icon} from '../Icon';
16
15
 
17
- const errorMessageIcon =
18
- 'https://img.mservice.com.vn/app/img/kits/error_mess_icon.png';
19
- const ic_clear =
20
- 'https://img.mservice.io/momo_app_v2/new_version/img/appx_icon/24_navigation_close_circle_full.png';
21
16
  const DEFAULT_HEIGHT = 112;
22
17
  const MAX_LENGTH = 300;
23
18
 
@@ -122,10 +117,10 @@ const TextArea: FC<TextAreaProps> = props => {
122
117
  </View>
123
118
  {focused && (
124
119
  <TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
125
- <Image
126
- tintColor={theme.colors.text.hint}
127
- source={{uri: ic_clear}}
128
- style={styles.iconClose}
120
+ <Icon
121
+ source="24_navigation_close_circle_full"
122
+ size={16}
123
+ color={theme.colors.text.hint}
129
124
  />
130
125
  </TouchableOpacity>
131
126
  )}
@@ -139,7 +134,13 @@ const TextArea: FC<TextAreaProps> = props => {
139
134
  if (errorMessage) {
140
135
  return (
141
136
  <View style={styles.errorView}>
142
- <Image style={styles.errorIcon} source={{uri: errorMessageIcon}} />
137
+ <View style={styles.errorIcon}>
138
+ <Icon
139
+ source="ic_error"
140
+ size={16}
141
+ color={theme.colors.error.primary}
142
+ />
143
+ </View>
143
144
  <Text color={theme.colors.error.primary} typography={'description_s'}>
144
145
  {errorMessage}
145
146
  </Text>
package/Input/styles.ts CHANGED
@@ -29,7 +29,7 @@ export default StyleSheet.create({
29
29
  flexDirection: 'row',
30
30
  },
31
31
  floatingIcon: {width: 16, height: 16, marginLeft: Spacing.XS},
32
- errorIcon: {width: 16, height: 16, marginRight: Spacing.XS},
32
+ errorIcon: {marginRight: Spacing.XS},
33
33
  errorView: {
34
34
  flexDirection: 'row',
35
35
  alignItems: 'center',
@@ -38,10 +38,6 @@ export default StyleSheet.create({
38
38
  justifyContent: 'space-between',
39
39
  flex: 1,
40
40
  },
41
- iconClose: {
42
- width: 16,
43
- height: 16,
44
- },
45
41
  iconView: {
46
42
  flexDirection: 'row',
47
43
  alignItems: 'center',
@@ -1,36 +1,23 @@
1
1
  import {KeyboardAvoidingView, Platform, ScrollView, View} from 'react-native';
2
2
  import {useHeaderHeight} from '@react-navigation/stack';
3
3
  import {SafeAreaView} from 'react-native-safe-area-context';
4
- import React, {useContext, useLayoutEffect} from 'react';
4
+ import React from 'react';
5
5
  import {ScreenContainerProps} from '../Navigation/types';
6
- import {ApplicationContext} from '../Navigation';
7
6
  import {Spacing, Styles} from '../Consts';
8
7
  import {ScreenSection, validateChildren} from './index';
9
8
 
10
9
  const ScreenContainer: React.FC<ScreenContainerProps> = ({
11
10
  children,
12
- navigation,
13
- options,
14
11
  edges,
15
12
  enableKeyboardAvoidingView,
16
13
  scrollable,
17
14
  }) => {
18
15
  let Component: any = View;
19
- const {theme} = useContext(ApplicationContext);
20
16
  const headerHeight = useHeaderHeight();
21
17
  if (scrollable) {
22
18
  Component = ScrollView;
23
19
  }
24
20
 
25
- /**
26
- * handle set options for navigation if have props options
27
- */
28
- useLayoutEffect(() => {
29
- if (options) {
30
- navigation.setOptions(options);
31
- }
32
- }, [navigation, options]);
33
-
34
21
  /**
35
22
  * build content for screen
36
23
  */
@@ -54,14 +41,7 @@ const ScreenContainer: React.FC<ScreenContainerProps> = ({
54
41
  };
55
42
 
56
43
  return (
57
- <SafeAreaView
58
- style={[
59
- Styles.flex,
60
- {
61
- backgroundColor: theme.colors.background.default,
62
- },
63
- ]}
64
- edges={edges}>
44
+ <SafeAreaView style={Styles.flex} edges={edges}>
65
45
  <KeyboardAvoidingView
66
46
  style={Styles.flex}
67
47
  keyboardVerticalOffset={headerHeight}
@@ -1,10 +1,11 @@
1
1
  import React, {useContext} from 'react';
2
2
  import {StatusBar, StyleSheet, View} from 'react-native';
3
3
  import {ApplicationContext, NavigationButton} from './index';
4
- import {Styles} from '../Consts';
4
+ import {Colors, Styles} from '../Consts';
5
5
  import {Image} from '../Image';
6
6
  import {HeaderBackgroundProps, TitleCustomProps} from './types';
7
7
  import {useGridSystem} from '../Layout';
8
+ import {Text} from '../Text';
8
9
 
9
10
  const styles = StyleSheet.create({
10
11
  headerBackground: {
@@ -17,14 +18,21 @@ const styles = StyleSheet.create({
17
18
  alignItems: 'center',
18
19
  justifyContent: 'center',
19
20
  },
21
+ avatar: {width: 36, height: 36, borderRadius: 18},
22
+ dotAvatar: {
23
+ position: 'absolute',
24
+ width: 12,
25
+ height: 12,
26
+ borderRadius: 6,
27
+ bottom: 0,
28
+ right: 0,
29
+ borderWidth: 1,
30
+ borderColor: Colors.black_01,
31
+ },
20
32
  });
21
33
 
22
- const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
23
- surface = false,
24
- image,
25
- }) => {
34
+ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({image}) => {
26
35
  const {theme} = useContext(ApplicationContext);
27
- const allowImage = !surface && theme.assets?.headerBackground;
28
36
  return (
29
37
  <View
30
38
  style={[
@@ -32,14 +40,9 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
32
40
  {backgroundColor: theme.colors.background.surface, overflow: 'hidden'},
33
41
  ]}>
34
42
  <StatusBar
35
- barStyle={allowImage || theme.dark ? 'light-content' : 'dark-content'}
43
+ barStyle={image || theme.dark ? 'light-content' : 'dark-content'}
36
44
  />
37
- {allowImage && (
38
- <Image
39
- style={styles.headerBackground}
40
- source={{uri: image ?? theme.assets?.headerBackground}}
41
- />
42
- )}
45
+ {image && <Image style={styles.headerBackground} source={{uri: image}} />}
43
46
  </View>
44
47
  );
45
48
  };
@@ -49,12 +52,32 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
49
52
  subTitle,
50
53
  image,
51
54
  content,
55
+ tintColor,
56
+ dotColor,
52
57
  }) => {
53
58
  const {getSizeSpan} = useGridSystem();
54
59
 
60
+ const header = (
61
+ <View style={Styles.row}>
62
+ <View>
63
+ <Image source={{uri: image}} style={styles.avatar} />
64
+ {dotColor && (
65
+ <View style={[styles.dotAvatar, {backgroundColor: dotColor}]} />
66
+ )}
67
+ </View>
68
+ <View style={[Styles.flex, Styles.paddingHorizontal8]}>
69
+ <Text typography="title_xs" color={tintColor}>
70
+ {title}
71
+ </Text>
72
+ <Text typography="description_s" color={tintColor}>
73
+ {subTitle}
74
+ </Text>
75
+ </View>
76
+ </View>
77
+ );
55
78
  return (
56
79
  <View style={[styles.headerTitleContainer, {width: getSizeSpan(10)}]}>
57
- {content ?? <View />}
80
+ {content ?? header}
58
81
  </View>
59
82
  );
60
83
  };
@@ -26,11 +26,12 @@ const ModalScreen: React.FC<ModalParams> = ({navigation, route, ...rest}) => {
26
26
  backgroundColor,
27
27
  screen,
28
28
  barrierDismissible,
29
+ options,
29
30
  }: ModalParams = route.params;
30
31
  const Component = useRef(screen).current;
31
32
  const params = {
32
33
  ...route.params,
33
- navigation: new Navigation(navigation),
34
+ navigation: new Navigation(navigation, theme),
34
35
  };
35
36
  delete params.screen;
36
37
 
@@ -54,6 +55,7 @@ const ModalScreen: React.FC<ModalParams> = ({navigation, route, ...rest}) => {
54
55
  */
55
56
  if (isBottomSheet) {
56
57
  const bgColor = backgroundColor ?? theme.colors.background.surface;
58
+ const sheetTheme = {...theme, assets: undefined};
57
59
  return (
58
60
  <BottomSheet {...rest} onDismiss={onDismiss}>
59
61
  <View style={[styles.sheetContainer, {backgroundColor: bgColor}]}>
@@ -66,26 +68,22 @@ const ModalScreen: React.FC<ModalParams> = ({navigation, route, ...rest}) => {
66
68
  />
67
69
  </View>
68
70
  <NavigationContainer
69
- theme={{...theme, assets: undefined}}
70
- screen={props => <Component {...params} {...props} />}
71
- onDismiss={() => {
72
- navigation.pop();
73
- }}
74
- screenOptions={{
71
+ theme={sheetTheme}
72
+ screen={props => <Component {...props} {...params} />}
73
+ options={{
74
+ ...options,
75
+ hiddenBack: true,
76
+ headerTitleAlign: 'center',
75
77
  headerRight: (props: any) => (
76
78
  <HeaderRightAction>
77
79
  <NavigationButton
78
80
  icon="24_navigation_close"
79
81
  {...props}
80
- onPress={onDismiss}
82
+ onPress={() => navigation.pop()}
81
83
  />
82
84
  </HeaderRightAction>
83
85
  ),
84
- headerBackground: () => (
85
- <View style={[Styles.flex, {backgroundColor: bgColor}]} />
86
- ),
87
86
  }}
88
- hideBackFirst={true}
89
87
  />
90
88
  </View>
91
89
  </BottomSheet>
@@ -1,13 +1,15 @@
1
1
  import {NavigationProp} from '@react-navigation/native';
2
- import {NavigationOptions} from './types';
2
+ import {NavigationOptions, Theme} from './types';
3
3
  import {HeaderRightAction} from './index';
4
4
  import {getOptions} from './utils';
5
5
 
6
6
  class Navigation {
7
7
  instance: NavigationProp<any>;
8
+ theme: Theme;
8
9
 
9
- constructor(instance: any) {
10
+ constructor(instance: any, theme: Theme) {
10
11
  this.instance = instance;
12
+ this.theme = theme;
11
13
  }
12
14
 
13
15
  verifyParams = (params: NavigationOptions) => {
@@ -17,9 +19,9 @@ class Navigation {
17
19
  console.warn(
18
20
  'headerRight not return element type of HeaderRightAction, Please migrate to use HeaderRightAction of kits.',
19
21
  );
20
- return;
21
22
  }
22
23
  }
24
+ return params;
23
25
  };
24
26
 
25
27
  filterParams = (params: NavigationOptions) => {
@@ -27,9 +29,8 @@ class Navigation {
27
29
  };
28
30
 
29
31
  setOptions = (params: NavigationOptions) => {
30
- this.verifyParams(params);
31
- params = this.filterParams(params);
32
- params = getOptions(params);
32
+ params = this.verifyParams(params);
33
+ params = getOptions(params, this.theme);
33
34
  this.instance.setOptions(params);
34
35
  };
35
36
  }
@@ -1,4 +1,4 @@
1
- import React, {createContext, useRef} from 'react';
1
+ import React, {createContext, useEffect, useRef} from 'react';
2
2
  import {BottomSheetModalProvider} from '@gorhom/bottom-sheet';
3
3
  import {SafeAreaProvider} from 'react-native-safe-area-context';
4
4
  import {
@@ -11,7 +11,7 @@ import ModalScreen from './ModalScreen';
11
11
  import Navigator from './Navigator';
12
12
  import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
13
13
  import {GridSystem, useGridSystem} from '../Layout';
14
- import {defaultContext, defaultTheme} from '../Consts';
14
+ import {defaultContext} from '../Consts';
15
15
  import {NavigationContainerProps} from './types';
16
16
 
17
17
  const Stack = createStackNavigator();
@@ -20,23 +20,17 @@ const ApplicationContext = createContext(defaultContext);
20
20
  const NavigationContainer: React.FC<NavigationContainerProps> = ({
21
21
  screen,
22
22
  theme,
23
- onDismiss,
24
- screenOptions,
25
- hideBackFirst,
23
+ options,
26
24
  }) => {
27
25
  const grid = useGridSystem();
28
26
  const navigationRef = React.useRef<NavigationContainerRef>(null);
29
27
  const navigator = useRef(new Navigator(navigationRef));
30
- const themed: any = theme;
31
28
 
32
- const goBack = () => {
33
- const canGoBack = navigationRef?.current?.canGoBack();
34
- if (canGoBack) {
35
- navigationRef?.current?.goBack();
36
- } else {
37
- onDismiss?.();
29
+ useEffect(() => {
30
+ if (options) {
31
+ navigationRef.current?.setParams({options: options});
38
32
  }
39
- };
33
+ }, []);
40
34
 
41
35
  return (
42
36
  <SafeAreaProvider>
@@ -44,20 +38,31 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
44
38
  <ApplicationContext.Provider
45
39
  value={{theme, navigator: navigator.current}}>
46
40
  <ReactNavigationContainer
47
- theme={themed}
41
+ theme={{
42
+ ...theme,
43
+ dark: false,
44
+ colors: {
45
+ ...theme.colors,
46
+ background: theme.colors.background.default,
47
+ card: theme.colors.background.surface,
48
+ text: theme.colors.text.default,
49
+ border: theme.colors.border.default,
50
+ notification: theme.colors.error.primary,
51
+ },
52
+ }}
48
53
  ref={navigator.current?.ref}
49
54
  independent={true}>
50
55
  <Stack.Navigator initialRouteName="Stack" headerMode="screen">
51
56
  <Stack.Screen
52
57
  name="Stack"
53
58
  component={StackScreen}
54
- initialParams={{screen, hideBackFirst}}
55
- options={{...getStackOptions(theme, goBack), ...screenOptions}}
59
+ initialParams={{screen}}
60
+ options={getStackOptions(theme)}
56
61
  />
57
62
  <Stack.Screen
58
63
  name="Dialog"
59
64
  component={StackScreen}
60
- options={getDialogOptions(theme, goBack)}
65
+ options={getDialogOptions(theme)}
61
66
  initialParams={{screen}}
62
67
  />
63
68
  <Stack.Screen
@@ -1,23 +1,28 @@
1
- import React, {useLayoutEffect} from 'react';
1
+ import React, {useContext, useEffect} from 'react';
2
2
  import {ScreenParams} from './types';
3
3
  import Navigation from './Navigation';
4
+ import {ApplicationContext} from './NavigationContainer';
4
5
 
5
6
  const StackScreen: React.FC<any> = props => {
6
7
  const {route, navigation} = props;
7
- const {screen: Component, hideBackFirst}: ScreenParams = route.params;
8
+ const {screen: Component, options}: ScreenParams = route.params;
9
+ const {theme} = useContext(ApplicationContext);
8
10
 
9
11
  const params = {
10
12
  ...route.params,
11
- navigation: new Navigation(navigation),
13
+ navigation: new Navigation(navigation, theme),
12
14
  };
13
15
 
14
16
  delete params.screen;
15
17
 
16
- useLayoutEffect(() => {
17
- if (hideBackFirst && !navigation?.canGoBack()) {
18
- navigation?.setOptions({headerLeft: null});
18
+ /**
19
+ * handle set options for navigation if have props options
20
+ */
21
+ useEffect(() => {
22
+ if (options) {
23
+ params.navigation.setOptions(options);
19
24
  }
20
- }, []);
25
+ }, [navigation, options]);
21
26
 
22
27
  return <Component {...params} />;
23
28
  };
@@ -1,8 +1,6 @@
1
1
  import {ViewProps} from 'react-native';
2
2
  import React from 'react';
3
3
  import Navigator from './Navigator';
4
- import Navigation from './Navigation';
5
- import {StackNavigationOptions} from '@react-navigation/stack';
6
4
 
7
5
  export type Theme = {
8
6
  dark: boolean;
@@ -66,15 +64,11 @@ export type Context = {
66
64
 
67
65
  export type NavigationContainerProps = {
68
66
  screen: React.ElementType;
69
- screenOptions?: StackNavigationOptions;
70
- hideBackFirst?: boolean;
67
+ options?: NavigationOptions;
71
68
  theme: Theme;
72
- onDismiss?: () => void;
73
69
  };
74
70
 
75
71
  export interface ScreenContainerProps extends ViewProps {
76
- navigation: Navigation;
77
- options?: NavigationOptions;
78
72
  edges?: any[];
79
73
  enableKeyboardAvoidingView?: boolean;
80
74
  scrollable: boolean;
@@ -102,6 +96,7 @@ export type NavigationButtonProps = {
102
96
 
103
97
  export type NavigationOptions = {
104
98
  surface?: boolean;
99
+ hiddenBack?: boolean;
105
100
  title?: string;
106
101
  headerTitleAlign?: 'left' | 'center';
107
102
  customTitle?: TitleCustomProps;
@@ -109,7 +104,6 @@ export type NavigationOptions = {
109
104
  };
110
105
 
111
106
  export type HeaderBackgroundProps = {
112
- surface?: boolean;
113
107
  image?: string;
114
108
  };
115
109
 
@@ -117,5 +111,7 @@ export type TitleCustomProps = {
117
111
  title?: string;
118
112
  subTitle?: string;
119
113
  image?: string;
114
+ dotColor?: string;
115
+ tintColor?: string;
120
116
  content?: React.ReactNode;
121
117
  };
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, {useContext} from 'react';
2
2
  import {
3
3
  StackNavigationOptions,
4
4
  TransitionPresets,
@@ -7,9 +7,9 @@ import {HeaderBackground, HeaderCustom} from './Components';
7
7
  import {NavigationOptions, Theme} from './types';
8
8
  import {Colors} from '../Consts';
9
9
  import {Text} from '../Text';
10
- import {NavigationButton} from './index';
10
+ import {ApplicationContext, NavigationButton} from './index';
11
11
 
12
- const renderTitle = (props: any) => {
12
+ const HeaderTitle = (props: any) => {
13
13
  return (
14
14
  <Text
15
15
  {...props}
@@ -20,6 +20,17 @@ const renderTitle = (props: any) => {
20
20
  );
21
21
  };
22
22
 
23
+ const HeaderLeft = (props: any) => {
24
+ const {navigator} = useContext(ApplicationContext);
25
+ const goBack = () => {
26
+ const canGoBack = navigator?.ref.current?.canGoBack();
27
+ if (canGoBack) {
28
+ navigator?.ref.current?.goBack();
29
+ }
30
+ };
31
+ return <NavigationButton icon="ic_back" {...props} onPress={goBack} />;
32
+ };
33
+
23
34
  const getTintColor = (theme: Theme): any => {
24
35
  if (theme.assets?.headerBackground) {
25
36
  return {
@@ -31,32 +42,26 @@ const getTintColor = (theme: Theme): any => {
31
42
  };
32
43
  };
33
44
 
34
- const getStackOptions = (
35
- theme: Theme,
36
- goBack: () => void,
37
- ): StackNavigationOptions => {
45
+ const getStackOptions = (theme: Theme): StackNavigationOptions => {
38
46
  return {
39
47
  headerTitleAlign: 'center',
40
- headerTitle: renderTitle,
41
- headerBackground: HeaderBackground,
42
- headerLeft: (props: any) => (
43
- <NavigationButton icon="ic_back" {...props} onPress={goBack} />
48
+ headerTitle: HeaderTitle,
49
+ headerBackground: () => (
50
+ <HeaderBackground image={theme.assets?.headerBackground} />
44
51
  ),
52
+ headerLeft: (props: any) => <HeaderLeft {...props} />,
45
53
  ...getTintColor(theme),
46
54
  };
47
55
  };
48
56
 
49
- const getDialogOptions = (
50
- theme: Theme,
51
- goBack: () => void,
52
- ): StackNavigationOptions => {
57
+ const getDialogOptions = (theme: Theme): StackNavigationOptions => {
53
58
  return {
54
59
  headerTitleAlign: 'center',
55
- headerTitle: renderTitle,
56
- headerLeft: (props: any) => (
57
- <NavigationButton icon="ic_back" {...props} onPress={goBack} />
60
+ headerTitle: HeaderTitle,
61
+ headerLeft: (props: any) => <HeaderLeft {...props} />,
62
+ headerBackground: () => (
63
+ <HeaderBackground image={theme.assets?.headerBackground} />
58
64
  ),
59
- headerBackground: HeaderBackground,
60
65
  ...getTintColor(theme),
61
66
  ...TransitionPresets.ModalTransition,
62
67
  };
@@ -87,21 +92,51 @@ const getModalOptions = (): StackNavigationOptions => {
87
92
  };
88
93
  };
89
94
 
90
- const getOptions = (params: NavigationOptions) => {
95
+ const getOptions = (params: NavigationOptions, theme: Theme) => {
96
+ let backTheme = {};
91
97
  let surfaceTheme = {};
92
98
  let titleTheme = {};
93
- if (params.surface) {
99
+
100
+ if (params.hiddenBack == true) {
101
+ backTheme = {
102
+ headerLeft: null,
103
+ };
104
+ } else {
105
+ backTheme = {
106
+ headerLeft: (props: any) => <HeaderLeft {...props} />,
107
+ };
108
+ }
109
+
110
+ if (params.surface == true) {
94
111
  surfaceTheme = {
95
- headerBackground: () => <HeaderBackground surface={true} />,
96
- headerTintColor: undefined,
112
+ headerBackground: () => <HeaderBackground />,
113
+ ...getTintColor({
114
+ ...theme,
115
+ assets: {...theme.assets, headerBackground: undefined},
116
+ }),
117
+ };
118
+ } else {
119
+ surfaceTheme = {
120
+ headerBackground: () => (
121
+ <HeaderBackground image={theme.assets?.headerBackground} />
122
+ ),
123
+ ...getTintColor(theme),
97
124
  };
98
125
  }
126
+
99
127
  if (params.customTitle) {
100
128
  titleTheme = {
101
- headerTitle: () => <HeaderCustom {...params.customTitle} />,
129
+ headerTitle: (props: any) => {
130
+ return <HeaderCustom {...params.customTitle} {...props} />;
131
+ },
132
+ };
133
+ } else {
134
+ titleTheme = {
135
+ headerTitle: HeaderTitle,
102
136
  };
103
137
  }
104
- return {...params, ...surfaceTheme, ...titleTheme};
138
+
139
+ return {...params, ...backTheme, ...surfaceTheme, ...titleTheme};
105
140
  };
106
141
 
107
142
  export {getStackOptions, getDialogOptions, getModalOptions, getOptions};
@@ -1,13 +1,88 @@
1
1
  import React, {FC, useContext, useEffect, useState} from 'react';
2
- import {View} from 'react-native';
2
+ import {FlatList, TouchableOpacity, View} from 'react-native';
3
3
  import {CheckBox, Input, Radio, Text} from '@momo-kits/foundation';
4
4
  import {PlaygroundProps, PropValue} from './types';
5
5
  import styles from './styles';
6
- import {Spacing} from '../Consts';
6
+ import {Colors, Radius, Spacing} from '../Consts';
7
7
  import {ApplicationContext} from '../Navigation';
8
+ import {ScreenSection, SectionItem} from '../Layout';
9
+ import {Icon, IconSources} from '../Icon';
10
+ import {string} from 'prop-types';
8
11
 
9
- const Playground: FC<PlaygroundProps> = ({params}) => {
12
+ const ChooseOptionScreen = (
13
+ data: string[] = [],
14
+ optionType: 'color' | 'icon' | 'text' = 'text',
15
+ onPress: (value: string) => void,
16
+ ) => {
10
17
  const {theme} = useContext(ApplicationContext);
18
+
19
+ let mapData = data;
20
+ if (optionType === 'color') {
21
+ mapData = Object.values(Colors);
22
+ }
23
+ if (optionType === 'icon') {
24
+ mapData = Object.keys(IconSources);
25
+ }
26
+ const renderItemView = (item: string) => {
27
+ switch (optionType) {
28
+ case 'color': {
29
+ return (
30
+ <View
31
+ style={{
32
+ width: 36,
33
+ height: 36,
34
+ borderRadius: Radius.M,
35
+ backgroundColor: item,
36
+ marginRight: Spacing.S,
37
+ }}
38
+ />
39
+ );
40
+ }
41
+ case 'icon': {
42
+ return <Icon source={item} style={{marginRight: Spacing.S}} />;
43
+ }
44
+ }
45
+ };
46
+
47
+ const renderItem = ({item, index}) => {
48
+ const iconSource: {[key: string]: {uri: string}} = IconSources;
49
+ let key = item;
50
+ let chosenItem = item;
51
+ if (optionType === 'color') key = Object.keys(Colors)[index];
52
+ if (optionType === 'icon') chosenItem = iconSource[key].uri;
53
+ return (
54
+ <TouchableOpacity
55
+ key={`${item.toString()} ${index}`}
56
+ onPress={() => onPress(chosenItem)}
57
+ style={{
58
+ flexDirection: 'row',
59
+ alignItems: 'center',
60
+ marginBottom: Spacing.M,
61
+ borderBottomWidth: index !== mapData.length - 1 ? 1 : 0,
62
+ borderColor: theme.colors.border.default,
63
+ paddingBottom: Spacing.M,
64
+ }}>
65
+ {renderItemView(item)}
66
+ <Text typography={'description_default'}>{key}</Text>
67
+ </TouchableOpacity>
68
+ );
69
+ };
70
+ return (
71
+ <ScreenSection>
72
+ <SectionItem widthSpan={12}>
73
+ <FlatList
74
+ data={mapData}
75
+ renderItem={renderItem}
76
+ keyExtractor={(item, index) => `${item.toString()} ${index}`}
77
+ showsVerticalScrollIndicator={false}
78
+ />
79
+ </SectionItem>
80
+ </ScreenSection>
81
+ );
82
+ };
83
+
84
+ const Playground: FC<PlaygroundProps> = ({params}) => {
85
+ const {theme, navigator} = useContext(ApplicationContext);
11
86
  const {props} = params;
12
87
  const [compProps, setCompProps] = useState<{[key: string]: any}>({});
13
88
  useEffect(() => {
@@ -37,6 +112,19 @@ const Playground: FC<PlaygroundProps> = ({params}) => {
37
112
  setCompProps(newProps);
38
113
  };
39
114
 
115
+ const showOptions = (prop: any, key: string) => {
116
+ navigator?.showBottomSheet({
117
+ options: {
118
+ title: 'Choose option',
119
+ },
120
+ screen: () =>
121
+ ChooseOptionScreen(prop.data, prop.optionType, optionValue => {
122
+ onChangeOptions(optionValue, key);
123
+ navigator?.pop();
124
+ }),
125
+ });
126
+ };
127
+
40
128
  const generateView = (key: string, prop: PropValue) => {
41
129
  let PlayGroundView = <View />;
42
130
  switch (prop.type) {
@@ -50,6 +138,7 @@ const Playground: FC<PlaygroundProps> = ({params}) => {
50
138
  value={option}
51
139
  onChange={() => onChangeOptions(option, key)}
52
140
  groupValue={compProps?.[key]}
141
+ style={{marginBottom: Spacing.S}}
53
142
  />
54
143
  );
55
144
  })}
@@ -62,6 +151,7 @@ const Playground: FC<PlaygroundProps> = ({params}) => {
62
151
  <Input
63
152
  size={'small'}
64
153
  floatingValue={key}
154
+ value={compProps?.[key]}
65
155
  onChangeText={text => onChangeOptions(text, key)}
66
156
  />
67
157
  );
@@ -79,6 +169,29 @@ const Playground: FC<PlaygroundProps> = ({params}) => {
79
169
  onChange={value => onChangeOptions(value, key)}
80
170
  />
81
171
  );
172
+ break;
173
+ }
174
+ case 'options': {
175
+ PlayGroundView = (
176
+ <TouchableOpacity
177
+ style={{
178
+ width: '100%',
179
+ height: 48,
180
+ borderRadius: Radius.M,
181
+ borderColor: theme.colors.border.default,
182
+ borderWidth: 1,
183
+ justifyContent: 'center',
184
+ alignItems: 'center',
185
+ flexDirection: 'row',
186
+ backgroundColor: theme.colors.background.surface,
187
+ }}
188
+ onPress={() => showOptions(prop, key)}>
189
+ <Text typography={'description_default'}>{compProps?.[key]}</Text>
190
+ <View style={{position: 'absolute', right: Spacing.M}}>
191
+ <Icon source={'arrow_chevron_down_small'} size={24} />
192
+ </View>
193
+ </TouchableOpacity>
194
+ );
82
195
  }
83
196
  }
84
197
 
@@ -106,6 +219,7 @@ const Playground: FC<PlaygroundProps> = ({params}) => {
106
219
 
107
220
  const Component = params.component ?? <View />;
108
221
  const propsKeys = Object.keys(props);
222
+
109
223
  return (
110
224
  <View>
111
225
  <Text style={{marginBottom: Spacing.M}} typography={'title_s'}>
@@ -114,9 +228,12 @@ const Playground: FC<PlaygroundProps> = ({params}) => {
114
228
  <View
115
229
  style={[
116
230
  styles.propView,
117
- {backgroundColor: theme.colors.background.surface},
231
+ {
232
+ backgroundColor: theme.colors.background.surface,
233
+ justifyContent: 'center',
234
+ alignItems: 'center',
235
+ },
118
236
  ]}>
119
- <Text typography={'header_default'}>{params.displayName}</Text>
120
237
  <Component {...compProps} />
121
238
  </View>
122
239
  <Text style={{marginBottom: Spacing.M}} typography={'title_s'}>
@@ -3,13 +3,14 @@ import {ReactElement} from 'react';
3
3
  export type PropValue = {
4
4
  value: any;
5
5
  options?: string[];
6
- type: 'string' | 'enum' | 'bool';
6
+ type: 'string' | 'options' | 'enum' | 'bool';
7
7
  description?: string;
8
+ data?: {[key: string]: string}[];
9
+ optionType?: 'icon' | 'color';
8
10
  };
9
11
  export type PlaygroundProps = {
10
12
  params: {
11
13
  component: ReactElement;
12
- displayName: string;
13
14
  props: {[key: string]: PropValue};
14
15
  };
15
16
  };
@@ -6,11 +6,7 @@ import {Image} from '../Image';
6
6
  import {Button} from '../Button';
7
7
  import {Spacing} from '../Consts';
8
8
 
9
- const PopupPromotion: React.FC<PopupPromotionProps> = ({
10
- image,
11
- primary,
12
- onIconClose,
13
- }) => {
9
+ const PopupPromotion: React.FC<PopupPromotionProps> = ({image, primary}) => {
14
10
  const {theme, navigator} = useContext(ApplicationContext);
15
11
 
16
12
  /**
package/Popup/types.ts CHANGED
@@ -12,7 +12,6 @@ export type PopupNotifyProps = {
12
12
  export type PopupPromotionProps = {
13
13
  image: string;
14
14
  primary: PopupAction;
15
- onIconClose?: () => void;
16
15
  };
17
16
 
18
17
  type PopupAction = {
package/Radio/index.tsx CHANGED
@@ -13,6 +13,7 @@ const Radio: FC<RadioProps> = ({
13
13
  disabled,
14
14
  onChange,
15
15
  label,
16
+ style,
16
17
  }) => {
17
18
  const {theme} = useContext(ApplicationContext);
18
19
  const selected = value === groupValue;
@@ -26,15 +27,22 @@ const Radio: FC<RadioProps> = ({
26
27
 
27
28
  return (
28
29
  <TouchableOpacity
29
- onPress={() => onChange?.(value)}
30
+ onPress={onChange}
30
31
  disabled={disabled}
31
- style={{
32
- flexDirection: 'row',
33
- alignItems: 'center',
34
- marginRight: Spacing.S,
35
- }}>
36
- <View style={[styles.radio, {borderWidth, borderColor}]} />
37
- <Text typography={'description_default'}>{label}</Text>
32
+ style={[
33
+ style,
34
+ {
35
+ flexDirection: 'row',
36
+ alignItems: 'center',
37
+ },
38
+ ]}>
39
+ <View
40
+ style={[
41
+ styles.radio,
42
+ {borderWidth, borderColor, marginRight: !!label ? Spacing.XS : 0},
43
+ ]}
44
+ />
45
+ {!!label && <Text typography={'description_default'}>{label}</Text>}
38
46
  </TouchableOpacity>
39
47
  );
40
48
  };
package/Radio/styles.ts CHANGED
@@ -6,7 +6,6 @@ export default StyleSheet.create({
6
6
  height: 20,
7
7
  width: 20,
8
8
  borderRadius: Radius.M,
9
- marginRight: Spacing.S,
10
9
  },
11
10
  container: {flex: 1, backgroundColor: 'red'},
12
11
  });
package/Radio/types.ts CHANGED
@@ -1,7 +1,10 @@
1
+ import {ViewStyle} from 'react-native';
2
+
1
3
  export type RadioProps = {
2
4
  value: string;
3
5
  disabled?: boolean;
4
6
  label?: string;
5
- onChange: (newValue?: string) => void;
7
+ onChange: () => void;
6
8
  groupValue: string;
9
+ style?: ViewStyle;
7
10
  };
package/Switch/index.tsx CHANGED
@@ -7,7 +7,7 @@ import {Colors} from '../Consts';
7
7
 
8
8
  const Switch: FC<SwitchProps> = props => {
9
9
  const {theme} = useContext(ApplicationContext);
10
- const {value, onChange, disabled} = props;
10
+ const {value, onChange, disabled, style} = props;
11
11
  const circleBackgroundColor = value ? Colors.black_01 : Colors.black_03;
12
12
  const circleAlign = value ? 'flex-end' : 'flex-start';
13
13
 
@@ -20,7 +20,11 @@ const Switch: FC<SwitchProps> = props => {
20
20
  <TouchableOpacity
21
21
  disabled={disabled}
22
22
  onPress={() => onChange?.(!value)}
23
- style={[styles.container, {backgroundColor, alignItems: circleAlign}]}>
23
+ style={[
24
+ style,
25
+ styles.container,
26
+ {backgroundColor, alignItems: circleAlign},
27
+ ]}>
24
28
  <View style={[styles.circle, {backgroundColor: circleBackgroundColor}]}>
25
29
  <View style={[styles.circleSmall, {backgroundColor}]} />
26
30
  </View>
package/Switch/types.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import {ViewStyle} from 'react-native';
2
+
1
3
  export type SwitchProps = {
2
4
  value: boolean;
3
5
  onChange: (value: boolean) => void;
4
- disabled: boolean;
6
+ disabled?: boolean;
7
+ style?: ViewStyle;
5
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "dependencies": {
13
13
  "react-native-safe-area-context": "3.1.4",
14
- "@react-navigation/bottom-tabs": "5.11.15",
14
+ "@react-navigation/bottom-tabs": "git+https://gitlab.com/dung.huynh1/react-navigation-bottom-tabs.git#main",
15
15
  "@react-navigation/core": "5.16.1",
16
16
  "@react-navigation/native": "5.9.8",
17
17
  "@react-navigation/routers": "5.7.4",