@momo-kits/foundation 1.0.0 → 1.0.3

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.
Files changed (68) hide show
  1. package/Button/index.tsx +118 -171
  2. package/CheckBox/index.tsx +63 -0
  3. package/CheckBox/styles.ts +14 -0
  4. package/CheckBox/types.ts +10 -0
  5. package/Consts/colors+spacing+radius.ts +6 -4
  6. package/Consts/index.ts +4 -73
  7. package/Consts/styles.ts +1 -1
  8. package/Consts/theme.ts +121 -0
  9. package/ContentLoader/index.tsx +9 -13
  10. package/Icon/index.tsx +14 -11
  11. package/Icon/types.ts +1 -4
  12. package/IconButton/index.tsx +67 -67
  13. package/IconButton/styles.ts +19 -0
  14. package/Image/index.tsx +22 -21
  15. package/Image/types.ts +0 -1
  16. package/Input/Input.tsx +161 -0
  17. package/Input/TextArea.tsx +162 -0
  18. package/Input/common.tsx +70 -0
  19. package/Input/index.tsx +26 -0
  20. package/Input/styles.ts +92 -0
  21. package/Layout/GridSystem.tsx +109 -0
  22. package/Layout/ScreenContainer.tsx +84 -0
  23. package/Layout/ScreenSection.tsx +117 -0
  24. package/Layout/SectionItem.tsx +63 -0
  25. package/Layout/index.ts +11 -5
  26. package/Layout/types.ts +6 -33
  27. package/Layout/utils.ts +69 -23
  28. package/Navigation/Components.tsx +42 -9
  29. package/Navigation/ModalScreen.tsx +65 -40
  30. package/Navigation/Navigation.ts +5 -2
  31. package/Navigation/NavigationButton.tsx +10 -5
  32. package/Navigation/NavigationContainer.tsx +17 -13
  33. package/Navigation/StackScreen.tsx +8 -2
  34. package/Navigation/index.ts +5 -3
  35. package/Navigation/types.ts +72 -38
  36. package/Navigation/utils.tsx +37 -14
  37. package/Playground/index.tsx +132 -0
  38. package/Playground/styles.ts +16 -0
  39. package/Playground/types.ts +15 -0
  40. package/Popup/PopupNotify.tsx +210 -0
  41. package/Popup/PopupPromotion.tsx +66 -0
  42. package/Popup/index.tsx +4 -0
  43. package/Popup/types.ts +23 -0
  44. package/Radio/index.tsx +42 -0
  45. package/Radio/styles.ts +12 -0
  46. package/Radio/types.ts +7 -0
  47. package/Switch/index.tsx +35 -0
  48. package/Switch/styles.ts +23 -0
  49. package/Switch/types.ts +5 -0
  50. package/Text/index.tsx +36 -118
  51. package/Text/styles.ts +24 -23
  52. package/Text/types.ts +5 -12
  53. package/index.ts +21 -4
  54. package/package.json +3 -4
  55. package/Button/types.ts +0 -18
  56. package/CheckBox/index.js +0 -74
  57. package/CheckBox/styles.js +0 -3
  58. package/IconButton/types.ts +0 -17
  59. package/Layout/Row.tsx +0 -42
  60. package/Layout/Screen.tsx +0 -68
  61. package/Layout/Section.tsx +0 -30
  62. package/Layout/View.tsx +0 -84
  63. package/Layout/styles.ts +0 -24
  64. package/Navigation/ScreenContainer.tsx +0 -38
  65. package/SizedBox/index.js +0 -23
  66. package/SizedBox/styles.js +0 -7
  67. package/TextInput/index.js +0 -225
  68. package/TextInput/styles.js +0 -55
@@ -0,0 +1,162 @@
1
+ import React, {FC, useContext, useRef, useState} from 'react';
2
+ import {
3
+ NativeSyntheticEvent,
4
+ TextInput,
5
+ TextInputFocusEventData,
6
+ TextInputProps,
7
+ TouchableOpacity,
8
+ View,
9
+ } from 'react-native';
10
+ import styles from './styles';
11
+ import {Image} from '../Image';
12
+ import {Text} from '../Text';
13
+ import {ApplicationContext} from '../Navigation';
14
+ import {getBorderColor, renderFloatingView} from './common';
15
+ import {TextAreaProps} 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 DEFAULT_HEIGHT = 112;
22
+ const MAX_LENGTH = 300;
23
+
24
+ const TextArea: FC<TextAreaProps> = props => {
25
+ const {theme} = useContext(ApplicationContext);
26
+ const {
27
+ errorMessage,
28
+ onChangeText,
29
+ floatingIconColor,
30
+ floatingIcon,
31
+ floatingValue,
32
+ onFocus,
33
+ onBlur,
34
+ disabled,
35
+ value,
36
+ defaultValue,
37
+ height,
38
+ placeholder,
39
+ maxLength,
40
+ } = props;
41
+
42
+ const [focused, setFocused] = useState(false);
43
+ const [inputValue, setInputValue] = useState(defaultValue ?? '');
44
+ const inputRef = useRef<any>(null);
45
+
46
+ const onClearText = () => {
47
+ inputRef?.current?.clear();
48
+ };
49
+
50
+ const _onChangeText = (text: string) => {
51
+ onChangeText?.(text);
52
+ setInputValue(text);
53
+ };
54
+
55
+ const _onFocus = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
56
+ setFocused(true);
57
+ onFocus?.(e);
58
+ };
59
+
60
+ const _onBlur = (e: NativeSyntheticEvent<TextInputFocusEventData>) => {
61
+ setFocused(false);
62
+ onBlur?.(e);
63
+ };
64
+
65
+ const renderCountingView = () => {
66
+ return (
67
+ <View style={styles.countingView}>
68
+ <Text color={theme.colors.text.hint} typography={'description_xs'}>
69
+ {`${inputValue.length}/${maxLength}`}
70
+ </Text>
71
+ </View>
72
+ );
73
+ };
74
+ const renderInputView = () => {
75
+ const disabledColor = theme.colors.text.disable;
76
+ let textColor = theme.colors.text.default;
77
+ let placeholderColor = theme.colors.text.hint;
78
+
79
+ if (disabled) {
80
+ textColor = disabledColor;
81
+ placeholderColor = disabledColor;
82
+ }
83
+
84
+ return (
85
+ <View
86
+ style={[
87
+ getBorderColor(focused, errorMessage, disabled),
88
+ styles.textAreaWrapper,
89
+ {
90
+ height: height || DEFAULT_HEIGHT,
91
+ backgroundColor: theme.colors.background.surface,
92
+ },
93
+ ]}>
94
+ {renderFloatingView(
95
+ floatingValue,
96
+ floatingIconColor,
97
+ disabled,
98
+ floatingIcon,
99
+ )}
100
+ <View style={styles.rowArea}>
101
+ <View style={styles.textAreaView}>
102
+ <TextInput
103
+ ref={inputRef}
104
+ editable={!disabled}
105
+ textAlignVertical="top"
106
+ style={[
107
+ styles.textArea,
108
+ {
109
+ color: textColor,
110
+ },
111
+ ]}
112
+ maxLength={maxLength}
113
+ multiline={true}
114
+ value={value}
115
+ onChangeText={_onChangeText}
116
+ onFocus={_onFocus}
117
+ onBlur={_onBlur}
118
+ placeholder={placeholder}
119
+ selectionColor={theme.colors.primary}
120
+ placeholderTextColor={placeholderColor}
121
+ />
122
+ </View>
123
+ {focused && (
124
+ <TouchableOpacity style={styles.iconWrapper} onPress={onClearText}>
125
+ <Image
126
+ tintColor={theme.colors.text.hint}
127
+ source={{uri: ic_clear}}
128
+ style={styles.iconClose}
129
+ />
130
+ </TouchableOpacity>
131
+ )}
132
+ </View>
133
+ {renderCountingView()}
134
+ </View>
135
+ );
136
+ };
137
+
138
+ const renderErrorView = () => {
139
+ if (errorMessage) {
140
+ return (
141
+ <View style={styles.errorView}>
142
+ <Image style={styles.errorIcon} source={{uri: errorMessageIcon}} />
143
+ <Text color={theme.colors.error.primary} typography={'description_s'}>
144
+ {errorMessage}
145
+ </Text>
146
+ </View>
147
+ );
148
+ }
149
+ };
150
+ return (
151
+ <View style={styles.wrapper}>
152
+ {renderInputView()}
153
+ {renderErrorView()}
154
+ </View>
155
+ );
156
+ };
157
+
158
+ TextArea.defaultProps = {
159
+ maxLength: MAX_LENGTH,
160
+ };
161
+
162
+ export default TextArea;
@@ -0,0 +1,70 @@
1
+ import {View} from 'react-native';
2
+ import styles from './styles';
3
+ import {Text} from '../Text';
4
+ import {Image} from '../Image';
5
+ import React, {useContext} from 'react';
6
+ import {ApplicationContext} from '../Navigation';
7
+
8
+ export const getBorderColor = (
9
+ focused: boolean,
10
+ errorMessage?: string,
11
+ disabled?: boolean,
12
+ ) => {
13
+ const {theme} = useContext(ApplicationContext);
14
+
15
+ let borderColor = theme.colors.border.default;
16
+
17
+ if (focused) {
18
+ borderColor = theme.colors.primary;
19
+ }
20
+
21
+ if (!!errorMessage) {
22
+ borderColor = theme.colors.error.primary;
23
+ }
24
+
25
+ if (disabled) {
26
+ borderColor = theme.colors.border.disable;
27
+ }
28
+
29
+ return {borderColor};
30
+ };
31
+
32
+ export const renderFloatingView = (
33
+ floatingValue?: string,
34
+ floatingIconColor?: string,
35
+ disabled?: boolean,
36
+ floatingIcon?: string,
37
+ ) => {
38
+ const {theme} = useContext(ApplicationContext);
39
+
40
+ if (floatingValue) {
41
+ let floatingTextColor = theme.colors.text.hint;
42
+ let floatingIconTintColor = floatingIconColor;
43
+ if (disabled) {
44
+ floatingTextColor = theme.colors.text.disable;
45
+ floatingIconTintColor = theme.colors.text.disable;
46
+ }
47
+
48
+ return (
49
+ <View
50
+ style={[
51
+ styles.floatingView,
52
+ {
53
+ backgroundColor: theme.colors.background.surface,
54
+ zIndex: 10,
55
+ },
56
+ ]}>
57
+ <Text color={floatingTextColor} typography={'label_s'}>
58
+ {floatingValue}
59
+ </Text>
60
+ {floatingIcon && (
61
+ <Image
62
+ tintColor={floatingIconTintColor}
63
+ source={{uri: floatingIcon}}
64
+ style={styles.floatingIcon}
65
+ />
66
+ )}
67
+ </View>
68
+ );
69
+ }
70
+ };
@@ -0,0 +1,26 @@
1
+ import Input from './Input';
2
+ import TextArea from './TextArea';
3
+ import {TextInputProps} from 'react-native';
4
+
5
+ export interface InputProps extends TextInputProps {
6
+ size?: 'small' | 'large';
7
+ floatingValue?: string;
8
+ floatingIcon?: string;
9
+ errorMessage?: string;
10
+ icon?: string;
11
+ disabled?: boolean;
12
+ floatingIconColor?: string;
13
+ iconColor?: string;
14
+ }
15
+
16
+ export interface TextAreaProps extends TextInputProps {
17
+ errorMessage: string;
18
+ floatingValue: string;
19
+ floatingIcon: string;
20
+ disabled: boolean;
21
+ floatingIconColor: string;
22
+ placeholder: string;
23
+ height: number;
24
+ }
25
+
26
+ export {Input, TextArea};
@@ -0,0 +1,92 @@
1
+ import {StyleSheet} from 'react-native';
2
+ import {Radius, Spacing} from '../Consts';
3
+
4
+ export default StyleSheet.create({
5
+ //input style
6
+ input: {width: '100%', paddingLeft: Spacing.M, height: '100%'},
7
+ wrapper: {
8
+ marginVertical: Spacing.M,
9
+ width: '100%',
10
+ },
11
+ container: {
12
+ borderWidth: 1,
13
+ borderRadius: Radius.S,
14
+ marginBottom: Spacing.XS,
15
+ height: 56,
16
+ paddingVertical: 8,
17
+ },
18
+ smallContainer: {
19
+ borderRadius: Radius.S,
20
+ borderWidth: 1,
21
+ marginBottom: Spacing.XS,
22
+ height: 48,
23
+ },
24
+ floatingView: {
25
+ position: 'absolute',
26
+ top: -10,
27
+ left: Spacing.S,
28
+ paddingHorizontal: Spacing.S,
29
+ flexDirection: 'row',
30
+ },
31
+ floatingIcon: {width: 16, height: 16, marginLeft: Spacing.XS},
32
+ errorIcon: {width: 16, height: 16, marginRight: Spacing.XS},
33
+ errorView: {
34
+ flexDirection: 'row',
35
+ alignItems: 'center',
36
+ },
37
+ inputView: {
38
+ justifyContent: 'space-between',
39
+ flex: 1,
40
+ },
41
+ iconClose: {
42
+ width: 16,
43
+ height: 16,
44
+ },
45
+ iconView: {
46
+ flexDirection: 'row',
47
+ alignItems: 'center',
48
+ marginLeft: Spacing.XS,
49
+ marginRight: Spacing.M,
50
+ },
51
+ icon: {
52
+ width: 24,
53
+ height: 24,
54
+ },
55
+ iconWrapper: {
56
+ width: 24,
57
+ height: 24,
58
+ justifyContent: 'center',
59
+ alignItems: 'center',
60
+ },
61
+ inputWrapper: {
62
+ flexDirection: 'row',
63
+ },
64
+
65
+ //text area style
66
+ textAreaView: {
67
+ flex: 1,
68
+ },
69
+ textArea: {
70
+ width: '100%',
71
+ flex: 1,
72
+ paddingRight: Spacing.XS,
73
+ },
74
+ textAreaWrapper: {
75
+ borderWidth: 1,
76
+ borderRadius: Radius.S,
77
+ paddingVertical: Spacing.S,
78
+ paddingLeft: Spacing.M,
79
+ paddingRight: Spacing.S,
80
+ },
81
+ countingView: {
82
+ width: '100%',
83
+ alignItems: 'flex-end',
84
+ paddingRight: Spacing.XS,
85
+ },
86
+ rowArea: {
87
+ flexDirection: 'row',
88
+ width: '100%',
89
+ flex: 1,
90
+ marginBottom: Spacing.XS,
91
+ },
92
+ });
@@ -0,0 +1,109 @@
1
+ import React, {useContext} from 'react';
2
+ import {useSafeAreaInsets} from 'react-native-safe-area-context';
3
+ import {StyleSheet, View} from 'react-native';
4
+ import {ApplicationContext} from '../Navigation';
5
+ import {useGridSystem} from './index';
6
+ import {Colors, Spacing} from '../Consts';
7
+
8
+ const GridSystem: React.FC = () => {
9
+ const {theme} = useContext(ApplicationContext);
10
+ const grid = useGridSystem();
11
+ const insets = useSafeAreaInsets();
12
+
13
+ const list = [];
14
+ for (let i = 1; i <= grid.numberOfColumns; i++) {
15
+ list.push(i);
16
+ }
17
+
18
+ return (
19
+ <>
20
+ <View
21
+ pointerEvents="none"
22
+ style={[
23
+ styles.gridContainer,
24
+ insets,
25
+ {
26
+ borderColor: theme.colors.error.primary,
27
+ marginHorizontal: grid.screenPadding,
28
+ },
29
+ ]}>
30
+ {list.map((i, index) => {
31
+ return (
32
+ <View style={{flexDirection: 'row'}} key={index.toString()}>
33
+ {index != -0 && <View style={{width: grid.gutterSize}} />}
34
+ <View
35
+ key={index.toString()}
36
+ pointerEvents="none"
37
+ style={[
38
+ styles.gridColumn,
39
+ {
40
+ width: grid.sizePerSpan,
41
+ },
42
+ ]}
43
+ />
44
+ </View>
45
+ );
46
+ })}
47
+ </View>
48
+ <View pointerEvents="none" style={styles.dangerLeftContainer} />
49
+ <View pointerEvents="none" style={styles.dangerRightContainer} />
50
+ <View
51
+ pointerEvents="none"
52
+ style={[styles.dangerTopContainer, {height: insets.top}]}
53
+ />
54
+ <View
55
+ pointerEvents="none"
56
+ style={[styles.dangerBottomContainer, {height: insets.bottom}]}
57
+ />
58
+ </>
59
+ );
60
+ };
61
+
62
+ const styles = StyleSheet.create({
63
+ gridContainer: {
64
+ position: 'absolute',
65
+ top: 0,
66
+ bottom: 0,
67
+ left: 0,
68
+ right: 0,
69
+ flexDirection: 'row',
70
+ borderWidth: 1,
71
+ },
72
+ gridColumn: {
73
+ height: '100%',
74
+ opacity: 0.2,
75
+ backgroundColor: Colors.blue_07,
76
+ },
77
+ dangerTopContainer: {
78
+ opacity: 0.2,
79
+ backgroundColor: Colors.red_03,
80
+ width: '100%',
81
+ top: 0,
82
+ position: 'absolute',
83
+ },
84
+ dangerBottomContainer: {
85
+ opacity: 0.2,
86
+ backgroundColor: Colors.red_03,
87
+ width: '100%',
88
+ bottom: 0,
89
+ position: 'absolute',
90
+ },
91
+ dangerLeftContainer: {
92
+ opacity: 0.2,
93
+ backgroundColor: Colors.red_03,
94
+ width: Spacing.M,
95
+ height: '100%',
96
+ left: 0,
97
+ position: 'absolute',
98
+ },
99
+ dangerRightContainer: {
100
+ opacity: 0.2,
101
+ backgroundColor: Colors.red_03,
102
+ width: Spacing.M,
103
+ height: '100%',
104
+ right: 0,
105
+ position: 'absolute',
106
+ },
107
+ });
108
+
109
+ export default GridSystem;
@@ -0,0 +1,84 @@
1
+ import {KeyboardAvoidingView, Platform, ScrollView, View} from 'react-native';
2
+ import {useHeaderHeight} from '@react-navigation/stack';
3
+ import {SafeAreaView} from 'react-native-safe-area-context';
4
+ import React, {useContext, useLayoutEffect} from 'react';
5
+ import {ScreenContainerProps} from '../Navigation/types';
6
+ import {ApplicationContext} from '../Navigation';
7
+ import {Spacing, Styles} from '../Consts';
8
+ import {ScreenSection, validateChildren} from './index';
9
+
10
+ const ScreenContainer: React.FC<ScreenContainerProps> = ({
11
+ children,
12
+ navigation,
13
+ options,
14
+ edges,
15
+ enableKeyboardAvoidingView,
16
+ scrollable,
17
+ }) => {
18
+ let Component: any = View;
19
+ const {theme} = useContext(ApplicationContext);
20
+ const headerHeight = useHeaderHeight();
21
+ if (scrollable) {
22
+ Component = ScrollView;
23
+ }
24
+
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
+ /**
35
+ * build content for screen
36
+ */
37
+ const renderContent = () => {
38
+ const results = validateChildren(children, ScreenSection);
39
+ if (Array.isArray(results)) {
40
+ return results.map((item, index) => {
41
+ return (
42
+ <View
43
+ key={`ScreenSection${index}`}
44
+ style={{
45
+ paddingTop: index != 0 ? 12 : Spacing.S,
46
+ flex: item.props?.expanded ? 1 : undefined,
47
+ }}>
48
+ {item}
49
+ </View>
50
+ );
51
+ });
52
+ }
53
+ return results;
54
+ };
55
+
56
+ return (
57
+ <SafeAreaView
58
+ style={[
59
+ Styles.flex,
60
+ {
61
+ backgroundColor: theme.colors.background.default,
62
+ },
63
+ ]}
64
+ edges={edges}>
65
+ <KeyboardAvoidingView
66
+ style={Styles.flex}
67
+ keyboardVerticalOffset={headerHeight}
68
+ enabled={enableKeyboardAvoidingView}
69
+ behavior={Platform.select({
70
+ ios: 'padding',
71
+ android: undefined,
72
+ })}>
73
+ <Component style={Styles.flex}>{renderContent()}</Component>
74
+ </KeyboardAvoidingView>
75
+ </SafeAreaView>
76
+ );
77
+ };
78
+
79
+ ScreenContainer.defaultProps = {
80
+ edges: ['bottom', 'left', 'right'],
81
+ enableKeyboardAvoidingView: false,
82
+ scrollable: false,
83
+ };
84
+ export default ScreenContainer;
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import {View} from 'react-native';
3
+ import {useGridSystem, validateChildren} from './utils';
4
+ import SectionItem from './SectionItem';
5
+ import {ScreenSectionProps} from './types';
6
+
7
+ const ScreenSection: React.FC<ScreenSectionProps> = ({children}) => {
8
+ const grid = useGridSystem();
9
+
10
+ /**
11
+ * render overlay only dev mode
12
+ */
13
+ const renderOverlay = () => {
14
+ return (
15
+ <View
16
+ pointerEvents={'none'}
17
+ style={{
18
+ position: 'absolute',
19
+ top: 0,
20
+ bottom: 0,
21
+ left: 0,
22
+ right: 0,
23
+ borderColor: 'red',
24
+ borderWidth: 1,
25
+ }}
26
+ />
27
+ );
28
+ };
29
+
30
+ /**
31
+ * build a row view in side session
32
+ * @param rows
33
+ * @param cursor
34
+ * @param endRow
35
+ */
36
+ const addRowView = (
37
+ rows: any[],
38
+ cursor: number = 0,
39
+ endRow: boolean = false,
40
+ ) => {
41
+ return (
42
+ <View
43
+ key={`SectionItem${cursor}`}
44
+ style={{
45
+ flexDirection: 'row',
46
+ marginBottom: endRow ? 0 : grid.gutterSize,
47
+ }}>
48
+ {rows.map((i: React.ReactElement, index: number) => {
49
+ return (
50
+ <View
51
+ key={`SectionItem${cursor}${index}`}
52
+ style={{flexDirection: 'row'}}>
53
+ {index != 0 && <View style={{width: grid.gutterSize}}></View>}
54
+ {i}
55
+ </View>
56
+ );
57
+ })}
58
+ </View>
59
+ );
60
+ };
61
+
62
+ /**
63
+ * render children content for session
64
+ * @param children
65
+ */
66
+ const renderView = (
67
+ children: any,
68
+ ): React.ReactElement | React.ReactElement[] => {
69
+ const element: React.ReactElement | React.ReactElement[] = [];
70
+ if (Array.isArray(children)) {
71
+ let cursor = 0;
72
+ let rows: any[] = [];
73
+ for (let i = 0; i < children.length; ++i) {
74
+ const item = children[i];
75
+ const totalSpan = rows
76
+ .concat([item])
77
+ .map(i => i.props?.widthSpan)
78
+ .reduce((previousValue, currentValue) => {
79
+ return previousValue + currentValue;
80
+ });
81
+
82
+ if (totalSpan <= grid.numberOfColumns) {
83
+ rows.push(item);
84
+ } else {
85
+ cursor += 1;
86
+ element.push(addRowView(rows, cursor));
87
+ rows = [item];
88
+ }
89
+
90
+ if (i == children.length - 1) {
91
+ cursor += 1;
92
+ element.push(addRowView(rows, cursor, true));
93
+ }
94
+ }
95
+ } else {
96
+ element.push(children);
97
+ }
98
+ return element;
99
+ };
100
+
101
+ return (
102
+ <View
103
+ style={{
104
+ width: grid.getSizeSpan(),
105
+ marginHorizontal: grid.screenPadding,
106
+ flexDirection: 'row',
107
+ flexWrap: 'wrap',
108
+ }}>
109
+ {renderView(validateChildren(children, SectionItem))}
110
+ {grid.isDevMode && renderOverlay()}
111
+ </View>
112
+ );
113
+ };
114
+
115
+ ScreenSection.displayName = 'ScreenSection';
116
+
117
+ export default ScreenSection;