@momo-kits/foundation 1.0.12 → 1.0.14

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
@@ -10,7 +10,6 @@ import {Text} from '../Text';
10
10
  import {Typography} from '../Text/types';
11
11
  import {Colors} from '../Consts';
12
12
  import styles from './styles';
13
- import {Image} from '../Image';
14
13
  import {Icon} from '../Icon';
15
14
 
16
15
  export interface ButtonProps extends TouchableOpacityProps {
@@ -224,6 +223,7 @@ const Button: FC<ButtonProps> = ({
224
223
  return (
225
224
  <TouchableOpacity
226
225
  {...rest}
226
+ accessibilityLabel={`Button/${title}`}
227
227
  activeOpacity={activeOpacity}
228
228
  onPress={onPressButton}
229
229
  style={buttonStyle}>
@@ -35,6 +35,7 @@ const CheckBox: FC<CheckBoxProps> = props => {
35
35
  };
36
36
  return (
37
37
  <TouchableOpacity
38
+ accessibilityLabel={`CheckBox/${label}`}
38
39
  activeOpacity={0.8}
39
40
  onPress={onChangeValue}
40
41
  disabled={disabled}
@@ -48,7 +49,7 @@ const CheckBox: FC<CheckBoxProps> = props => {
48
49
  />
49
50
  )}
50
51
  </View>
51
- <Text typography={'description_default'}>{label}</Text>
52
+ {!!label && <Text typography={'description_default'}>{label}</Text>}
52
53
  </TouchableOpacity>
53
54
  );
54
55
  };
package/Icon/index.tsx CHANGED
@@ -20,6 +20,8 @@ const Icon: React.FC<IconProps> = ({source, size, color, style = {}}) => {
20
20
 
21
21
  return (
22
22
  <Image
23
+ accessibilityLabel={`Icon/${getIconSource()}`}
24
+ loading={false}
23
25
  source={getIconSource()}
24
26
  style={[style, {width: size, height: size}]}
25
27
  tintColor={color ?? theme.colors.text.default}
@@ -111,6 +111,7 @@ const IconButton: React.FC<IconButtonProps> = ({
111
111
  return (
112
112
  <TouchableOpacity
113
113
  {...rest}
114
+ accessibilityLabel={`IconButton/${icon}`}
114
115
  activeOpacity={activeOpacity}
115
116
  onPress={onPressButton}
116
117
  style={buttonStyle}>
package/Image/index.tsx CHANGED
@@ -10,11 +10,18 @@ import {FastImagePropsWithoutStyle} from './types';
10
10
 
11
11
  export interface ImageProps extends FastImagePropsWithoutStyle {
12
12
  style?: StyleProp<ViewStyle>;
13
+ loading?: boolean;
14
+ accessibilityLabel?: string;
13
15
  }
14
16
 
15
- const Image: React.FC<ImageProps> = ({style, source, ...rest}) => {
17
+ const Image: React.FC<ImageProps> = ({
18
+ style,
19
+ source,
20
+ accessibilityLabel,
21
+ ...rest
22
+ }) => {
16
23
  const {theme} = useContext(ApplicationContext);
17
- const [loading, setLoading] = useState(typeof source === 'object');
24
+ const [loading, setLoading] = useState(rest.loading);
18
25
  const [fail, setFail] = useState(false);
19
26
 
20
27
  /**
@@ -47,14 +54,16 @@ const Image: React.FC<ImageProps> = ({style, source, ...rest}) => {
47
54
  };
48
55
 
49
56
  return (
50
- <View style={[styles.container, style]}>
57
+ <View
58
+ style={[styles.container, style]}
59
+ accessibilityLabel={`Image/${accessibilityLabel ?? source}`}>
51
60
  <FastImage
52
61
  {...rest}
53
62
  source={source}
54
63
  style={styles.image}
55
64
  onLoad={() => {
56
65
  setFail(false);
57
- setLoading(true);
66
+ setLoading(rest.loading);
58
67
  }}
59
68
  onLoadEnd={() => setLoading(false)}
60
69
  onError={() => setFail(true)}
@@ -64,4 +73,8 @@ const Image: React.FC<ImageProps> = ({style, source, ...rest}) => {
64
73
  );
65
74
  };
66
75
 
76
+ Image.defaultProps = {
77
+ loading: true,
78
+ };
79
+
67
80
  export {Image};
package/Input/Input.tsx CHANGED
@@ -8,7 +8,6 @@ import {
8
8
  } from 'react-native';
9
9
  import {ApplicationContext} from '../Navigation';
10
10
  import styles from './styles';
11
- import {Image} from '../Image';
12
11
  import {ErrorView, FloatingView, getBorderColor, getSizeStyle} from './common';
13
12
  import {InputProps} from './index';
14
13
  import {Icon} from '../Icon';
@@ -112,16 +111,14 @@ const Input: FC<InputProps> = ({
112
111
  />
113
112
  </TouchableOpacity>
114
113
  )}
115
- {icon && (
116
- <Icon icon={iconTintColor} source={icon} style={styles.icon} />
117
- )}
114
+ {icon && <Icon color={iconTintColor} source={icon} size={24} />}
118
115
  </View>
119
116
  </View>
120
117
  );
121
118
  };
122
119
 
123
120
  return (
124
- <View style={styles.wrapper}>
121
+ <View style={styles.wrapper} accessibilityLabel={`Input`}>
125
122
  {renderInputView()}
126
123
  <ErrorView errorMessage={errorMessage} />
127
124
  </View>
@@ -123,7 +123,7 @@ const InputMoney: FC<InputMoneyProps> = ({
123
123
  };
124
124
 
125
125
  return (
126
- <View style={styles.wrapper}>
126
+ <View style={styles.wrapper} accessibilityLabel={`InputMoney`}>
127
127
  {renderInputView()}
128
128
  <ErrorView errorMessage={errorMessage} />
129
129
  </View>
@@ -7,16 +7,12 @@ import {
7
7
  View,
8
8
  } from 'react-native';
9
9
  import {InputSearchProps} from './index';
10
- import {getBorderColor} from './common';
11
10
  import {ApplicationContext} from '../Navigation';
12
11
  import styles from './styles';
13
12
  import {Icon} from '../Icon';
14
- import {Image} from '../Image';
15
13
  import {Text} from '../Text';
16
14
 
17
15
  const InputSearch: FC<InputSearchProps> = ({
18
- errorMessage,
19
- disabled,
20
16
  placeholder,
21
17
  onFocus,
22
18
  onBlur,
@@ -27,6 +23,7 @@ const InputSearch: FC<InputSearchProps> = ({
27
23
  buttonText = 'Hủy',
28
24
  showButtonText = true,
29
25
  showIcon = true,
26
+ showBorder = true,
30
27
  style,
31
28
  ...props
32
29
  }) => {
@@ -54,20 +51,25 @@ const InputSearch: FC<InputSearchProps> = ({
54
51
  onChangeText?.(text);
55
52
  };
56
53
 
54
+ const getBorderColor = () => {
55
+ let borderColor = showBorder
56
+ ? theme.colors.border.default
57
+ : theme.colors.background.surface;
58
+
59
+ if (focused) {
60
+ borderColor = theme.colors.primary;
61
+ }
62
+
63
+ return {borderColor};
64
+ };
65
+
57
66
  const renderInputView = () => {
58
- const disabledColor = theme.colors.text.disable;
59
67
  let textColor = theme.colors.text.default;
60
68
  let placeholderColor = theme.colors.text.hint;
61
69
 
62
- if (disabled) {
63
- textColor = disabledColor;
64
- placeholderColor = disabledColor;
65
- iconTintColor = disabledColor;
66
- }
67
70
  return (
68
71
  <TextInput
69
72
  {...props}
70
- editable={!disabled}
71
73
  textAlignVertical="top"
72
74
  ref={inputRef}
73
75
  style={[
@@ -112,19 +114,27 @@ const InputSearch: FC<InputSearchProps> = ({
112
114
  },
113
115
  ]}
114
116
  />
115
- <Icon color={iconTintColor} source={icon} />
117
+ <Icon
118
+ color={iconTintColor}
119
+ source={icon}
120
+ style={styles.iconSearchInput}
121
+ />
116
122
  </View>
117
123
  )}
118
124
  </View>
119
125
  );
120
126
  };
121
127
  return (
122
- <View style={[style, styles.searchInputContainer]}>
128
+ <View
129
+ style={[style, styles.searchInputContainer]}
130
+ accessibilityLabel={`InputSearch`}>
123
131
  <View
124
132
  style={[
125
- getBorderColor(focused, errorMessage, disabled),
133
+ getBorderColor(),
126
134
  styles.searchInputWrapper,
127
- {backgroundColor: theme.colors.background.surface},
135
+ {
136
+ backgroundColor: theme.colors.background.surface,
137
+ },
128
138
  ]}>
129
139
  <Icon
130
140
  source={'navigation_search'}
@@ -138,7 +138,7 @@ const InputTextArea: FC<InputTextAreaProps> = props => {
138
138
  };
139
139
 
140
140
  return (
141
- <View style={styles.wrapper}>
141
+ <View style={styles.wrapper} accessibilityLabel={`InputTextArea`}>
142
142
  {renderInputView()}
143
143
  <ErrorView errorMessage={errorMessage} />
144
144
  </View>
package/Input/index.tsx CHANGED
@@ -20,17 +20,21 @@ type InputPropsWithoutSizeAndIcon = Omit<
20
20
  InputProps,
21
21
  'size' | 'icon' | 'iconColor'
22
22
  >;
23
- type InputPropsWithoutRequiredAndSize = Omit<InputProps, 'required' | 'size'>;
23
+ type InputPropsOmitForSearch = Omit<
24
+ InputProps,
25
+ 'required' | 'size' | 'errorMessage' | 'disabled'
26
+ >;
24
27
  type InputPropsWithoutPlaceholder = Omit<InputProps, 'placeholder'>;
25
28
 
26
29
  export interface InputTextAreaProps extends InputPropsWithoutSizeAndIcon {
27
30
  height?: number;
28
31
  }
29
32
 
30
- export interface InputSearchProps extends InputPropsWithoutRequiredAndSize {
33
+ export interface InputSearchProps extends InputPropsOmitForSearch {
31
34
  buttonText?: string;
32
35
  showButtonText?: boolean;
33
36
  showIcon?: boolean;
37
+ showBorder?: boolean;
34
38
  }
35
39
 
36
40
  export interface InputMoneyProps extends InputPropsWithoutPlaceholder {}
package/Input/styles.ts CHANGED
@@ -104,8 +104,6 @@ export default StyleSheet.create({
104
104
  alignItems: 'center',
105
105
  },
106
106
  iconSearchInput: {
107
- width: 24,
108
- height: 24,
109
107
  marginLeft: Spacing.S,
110
108
  },
111
109
  searchInputContainer: {flexDirection: 'row', alignItems: 'center'},
@@ -0,0 +1,87 @@
1
+ import React, {useContext} from 'react';
2
+ import {View} from 'react-native';
3
+
4
+ import {CardProps, GridContextProps} from './types';
5
+ import {GridContext} from './index';
6
+
7
+ const Card: React.FC<CardProps> = ({
8
+ widthSpan = 12,
9
+ heightSpan,
10
+ usePadding,
11
+ children,
12
+ style,
13
+ }) => {
14
+ const grid = useContext(GridContext);
15
+
16
+ /**
17
+ * render overlay view only dev mode
18
+ */
19
+ const renderOverlay = () => {
20
+ return (
21
+ <View
22
+ pointerEvents={'none'}
23
+ style={{
24
+ position: 'absolute',
25
+ top: 0,
26
+ bottom: 0,
27
+ left: 0,
28
+ right: 0,
29
+ borderColor: 'red',
30
+ borderWidth: 1,
31
+ }}
32
+ />
33
+ );
34
+ };
35
+
36
+ const styles: any = style ?? {};
37
+
38
+ const numberOfColumns = 12;
39
+ const gutterSize = 8;
40
+ let margin = 0;
41
+ if (usePadding) {
42
+ margin = 12;
43
+ }
44
+ const widthSection = grid.getSizeSpan(widthSpan) - margin * 2;
45
+ const totalGutterSize = gutterSize * (numberOfColumns - 1);
46
+ const sizePerSpan = (widthSection - totalGutterSize) / numberOfColumns;
47
+ const gridContext: GridContextProps = {
48
+ numberOfColumns,
49
+ gutterSize,
50
+ margin,
51
+ sizePerSpan,
52
+ getSizeSpan: span => {
53
+ return span * sizePerSpan + (span - 1) * gutterSize;
54
+ },
55
+ };
56
+
57
+ return (
58
+ <View
59
+ accessibilityLabel={`Card`}
60
+ style={{
61
+ ...styles,
62
+ width: grid.getSizeSpan(widthSpan),
63
+ height: heightSpan ? grid.getSizeSpan(heightSpan) : undefined,
64
+ paddingHorizontal: margin,
65
+ overflow: 'hidden',
66
+ margin: undefined,
67
+ marginTop: undefined,
68
+ marginBottom: undefined,
69
+ marginLeft: undefined,
70
+ marginRight: undefined,
71
+ }}>
72
+ <GridContext.Provider value={gridContext}>
73
+ {children}
74
+ </GridContext.Provider>
75
+ {__DEV__ && renderOverlay()}
76
+ </View>
77
+ );
78
+ };
79
+
80
+ Card.defaultProps = {
81
+ widthSpan: 12,
82
+ usePadding: false,
83
+ };
84
+
85
+ Card.displayName = 'Card';
86
+
87
+ export default Card;
@@ -1,13 +1,28 @@
1
1
  import React, {useContext} from 'react';
2
2
  import {useSafeAreaInsets} from 'react-native-safe-area-context';
3
- import {StyleSheet, View} from 'react-native';
3
+ import {Dimensions, StyleSheet, View} from 'react-native';
4
4
  import {ApplicationContext} from '../Navigation';
5
- import {useGridSystem} from './index';
6
5
  import {Colors, Spacing} from '../Consts';
6
+ import {GridContextProps} from './types';
7
7
 
8
8
  const GridSystem: React.FC = () => {
9
9
  const {theme} = useContext(ApplicationContext);
10
- const grid = useGridSystem();
10
+ const numberOfColumns = 12;
11
+ const gutterSize = 12;
12
+ const margin = 12;
13
+ const widthSection = Dimensions.get('window').width - margin * 2;
14
+ const totalGutterSize = gutterSize * (numberOfColumns - 1);
15
+ const sizePerSpan = (widthSection - totalGutterSize) / numberOfColumns;
16
+
17
+ const grid: GridContextProps = {
18
+ numberOfColumns,
19
+ gutterSize,
20
+ margin,
21
+ sizePerSpan,
22
+ getSizeSpan: span => {
23
+ return span * sizePerSpan + (span - 1) * gutterSize;
24
+ },
25
+ };
11
26
  const insets = useSafeAreaInsets();
12
27
 
13
28
  const list = [];
@@ -24,7 +39,7 @@ const GridSystem: React.FC = () => {
24
39
  insets,
25
40
  {
26
41
  borderColor: theme.colors.error.primary,
27
- marginHorizontal: grid.screenPadding,
42
+ marginHorizontal: grid.margin,
28
43
  },
29
44
  ]}>
30
45
  {list.map((i, index) => {
@@ -1,36 +1,60 @@
1
- import {KeyboardAvoidingView, Platform, ScrollView, View} from 'react-native';
1
+ import {Animated, KeyboardAvoidingView, Platform, View} from 'react-native';
2
2
  import {useHeaderHeight} from '@react-navigation/stack';
3
3
  import {SafeAreaView} from 'react-native-safe-area-context';
4
4
  import React from 'react';
5
5
  import {ScreenContainerProps} from '../Navigation/types';
6
6
  import {Spacing, Styles} from '../Consts';
7
- import {ScreenSection, validateChildren} from './index';
7
+ import {Section, validateChildren} from './index';
8
8
 
9
9
  const ScreenContainer: React.FC<ScreenContainerProps> = ({
10
10
  children,
11
11
  edges,
12
12
  enableKeyboardAvoidingView,
13
13
  scrollable,
14
+ scrollY,
15
+ headerBackground,
14
16
  scrollViewProps,
17
+ footerComponent: Footer,
15
18
  }) => {
19
+ let marginHeader = 0;
20
+ let handleScroll;
21
+ let imageAnimatedStyle = {};
16
22
  let Component: any = View;
17
23
  const headerHeight = useHeaderHeight();
18
24
  if (scrollable) {
19
- Component = ScrollView;
25
+ Component = Animated.ScrollView;
26
+ }
27
+ if (scrollY) {
28
+ marginHeader = useHeaderHeight();
29
+ imageAnimatedStyle = {
30
+ transform: [
31
+ {
32
+ scale: scrollY.interpolate?.({
33
+ inputRange: [-300, -110, 100],
34
+ outputRange: [1.75, 1, 1],
35
+ extrapolate: 'clamp',
36
+ }),
37
+ },
38
+ ],
39
+ };
40
+ handleScroll = Animated.event(
41
+ [{nativeEvent: {contentOffset: {y: scrollY}}}],
42
+ {useNativeDriver: true},
43
+ );
20
44
  }
21
45
 
22
46
  /**
23
47
  * build content for screen
24
48
  */
25
49
  const renderContent = () => {
26
- const results = validateChildren(children, ScreenSection);
50
+ const results = validateChildren(children, Section);
27
51
  if (Array.isArray(results)) {
28
52
  return results.map((item, index) => {
29
53
  return (
30
54
  <View
31
- key={`ScreenSection${index}`}
55
+ key={`Section${index}`}
32
56
  style={{
33
- paddingTop: index != 0 ? 12 : Spacing.S,
57
+ marginTop: item.props?.useMargin ? Spacing.M : 0,
34
58
  flex: item.props?.expanded ? 1 : undefined,
35
59
  }}>
36
60
  {item}
@@ -42,7 +66,28 @@ const ScreenContainer: React.FC<ScreenContainerProps> = ({
42
66
  };
43
67
 
44
68
  return (
45
- <SafeAreaView style={Styles.flex} edges={edges}>
69
+ <SafeAreaView
70
+ style={Styles.flex}
71
+ edges={edges}
72
+ accessibilityLabel={`ScreenContainer`}>
73
+ <View
74
+ style={{
75
+ position: 'absolute',
76
+ height: 300,
77
+ width: '100%',
78
+ }}>
79
+ {headerBackground && (
80
+ <Animated.Image
81
+ source={{
82
+ uri: headerBackground,
83
+ }}
84
+ style={{
85
+ aspectRatio: 1.3,
86
+ ...imageAnimatedStyle,
87
+ }}
88
+ />
89
+ )}
90
+ </View>
46
91
  <KeyboardAvoidingView
47
92
  style={Styles.flex}
48
93
  keyboardVerticalOffset={headerHeight}
@@ -51,9 +96,13 @@ const ScreenContainer: React.FC<ScreenContainerProps> = ({
51
96
  ios: 'padding',
52
97
  android: undefined,
53
98
  })}>
54
- <Component {...scrollViewProps} style={Styles.flex}>
99
+ <Component
100
+ {...scrollViewProps}
101
+ onScroll={handleScroll}
102
+ style={[Styles.flex, {marginTop: marginHeader}]}>
55
103
  {renderContent()}
56
104
  </Component>
105
+ {Footer && <Section>{Footer}</Section>}
57
106
  </KeyboardAvoidingView>
58
107
  </SafeAreaView>
59
108
  );
@@ -1,11 +1,31 @@
1
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';
2
+ import {Dimensions, View} from 'react-native';
3
+ import {validateChildren} from './utils';
4
+ import Card from './Card';
5
+ import {GridContextProps, SectionProps} from './types';
6
+ import {GridContext} from './index';
6
7
 
7
- const ScreenSection: React.FC<ScreenSectionProps> = ({children}) => {
8
- const grid = useGridSystem();
8
+ const Section: React.FC<SectionProps> = ({children, useMargin = true}) => {
9
+ const numberOfColumns = 12;
10
+ const gutterSize = 12;
11
+ let margin = 0;
12
+ if (useMargin) {
13
+ margin = 12;
14
+ }
15
+ let widthSection = Dimensions.get('window').width;
16
+ if (useMargin) widthSection = widthSection - margin * 2;
17
+ const totalGutterSize = gutterSize * (numberOfColumns - 1);
18
+ const sizePerSpan = (widthSection - totalGutterSize) / numberOfColumns;
19
+
20
+ const gridContext: GridContextProps = {
21
+ numberOfColumns,
22
+ gutterSize,
23
+ margin,
24
+ sizePerSpan,
25
+ getSizeSpan: span => {
26
+ return span * sizePerSpan + (span - 1) * gutterSize;
27
+ },
28
+ };
9
29
 
10
30
  /**
11
31
  * render overlay only dev mode
@@ -40,17 +60,17 @@ const ScreenSection: React.FC<ScreenSectionProps> = ({children}) => {
40
60
  ) => {
41
61
  return (
42
62
  <View
43
- key={`SectionItem${cursor}`}
63
+ key={`Card${cursor}`}
44
64
  style={{
45
65
  flexDirection: 'row',
46
- marginBottom: endRow ? 0 : grid.gutterSize,
66
+ marginBottom: endRow ? 0 : gridContext.gutterSize,
47
67
  }}>
48
68
  {rows.map((i: React.ReactElement, index: number) => {
49
69
  return (
50
- <View
51
- key={`SectionItem${cursor}${index}`}
52
- style={{flexDirection: 'row'}}>
53
- {index != 0 && <View style={{width: grid.gutterSize}}></View>}
70
+ <View key={`Card${cursor}${index}`} style={{flexDirection: 'row'}}>
71
+ {index != 0 && (
72
+ <View style={{width: gridContext.gutterSize}}></View>
73
+ )}
54
74
  {i}
55
75
  </View>
56
76
  );
@@ -79,7 +99,7 @@ const ScreenSection: React.FC<ScreenSectionProps> = ({children}) => {
79
99
  return previousValue + currentValue;
80
100
  });
81
101
 
82
- if (totalSpan <= grid.numberOfColumns) {
102
+ if (totalSpan <= gridContext.numberOfColumns) {
83
103
  rows.push(item);
84
104
  } else {
85
105
  cursor += 1;
@@ -99,19 +119,25 @@ const ScreenSection: React.FC<ScreenSectionProps> = ({children}) => {
99
119
  };
100
120
 
101
121
  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>
122
+ <GridContext.Provider value={gridContext}>
123
+ <View
124
+ accessibilityLabel={`Section`}
125
+ style={{
126
+ width: gridContext.getSizeSpan(12),
127
+ marginHorizontal: gridContext.margin,
128
+ flexDirection: 'row',
129
+ flexWrap: 'wrap',
130
+ }}>
131
+ {renderView(validateChildren(children, Card))}
132
+ {__DEV__ && renderOverlay()}
133
+ </View>
134
+ </GridContext.Provider>
112
135
  );
113
136
  };
114
137
 
115
- ScreenSection.displayName = 'ScreenSection';
138
+ Section.displayName = 'Section';
116
139
 
117
- export default ScreenSection;
140
+ Section.defaultProps = {
141
+ useMargin: true,
142
+ };
143
+ export default Section;
package/Layout/index.ts CHANGED
@@ -1,12 +1,16 @@
1
- import SectionItem from './SectionItem';
2
- import ScreenSection from './ScreenSection';
1
+ import Card from './Card';
2
+ import Section from './Section';
3
3
  import GridSystem from './GridSystem';
4
- import {useGridSystem, validateChildren} from './utils';
4
+ import {validateChildren} from './utils';
5
+ import {createContext} from 'react';
6
+ import {GridContextProps} from './types';
5
7
 
6
- export {
7
- SectionItem,
8
- ScreenSection,
9
- GridSystem,
10
- useGridSystem,
11
- validateChildren,
12
- };
8
+ const GridContext = createContext<GridContextProps>({
9
+ numberOfColumns: 12,
10
+ gutterSize: 12,
11
+ margin: 12,
12
+ sizePerSpan: 0,
13
+ getSizeSpan: span => 0,
14
+ });
15
+
16
+ export {GridContext, Card, Section, GridSystem, validateChildren};
package/Layout/types.ts CHANGED
@@ -2,11 +2,20 @@ import {ViewProps} from 'react-native';
2
2
 
3
3
  type SpanNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
4
4
 
5
- export interface ScreenSectionProps extends ViewProps {
5
+ export type GridContextProps = {
6
+ numberOfColumns: number;
7
+ gutterSize: number;
8
+ margin?: number;
9
+ sizePerSpan: number;
10
+ getSizeSpan: (span: number) => number;
11
+ };
12
+ export interface SectionProps extends ViewProps {
6
13
  expanded?: boolean;
14
+ useMargin?: boolean;
7
15
  }
8
16
 
9
- export interface SectionItemProps extends ViewProps {
17
+ export interface CardProps extends ViewProps {
10
18
  widthSpan: SpanNumber;
11
19
  heightSpan?: SpanNumber;
20
+ usePadding?: boolean;
12
21
  }
package/Layout/utils.ts CHANGED
@@ -1,38 +1,5 @@
1
- import {Alert, Dimensions} from 'react-native';
1
+ import {Alert} from 'react-native';
2
2
  import React, {ReactElement} from 'react';
3
- import {GutterSize, NumberOfColumns, ScreenPadding} from '../Consts';
4
-
5
- /**
6
- * hook component for use grid system
7
- * @param useMargin
8
- */
9
- const useGridSystem = (useMargin: boolean = true) => {
10
- const isDevMode = false;
11
- const gutterSize = GutterSize;
12
- const screenPadding = useMargin ? ScreenPadding : 0;
13
- const numberOfColumns = NumberOfColumns;
14
- const widthDevice = Dimensions.get('window').width;
15
- const parentWidth = widthDevice - screenPadding * 2;
16
- const totalGutterSize = gutterSize * (numberOfColumns - 1);
17
- const sizePerSpan = (parentWidth - totalGutterSize) / numberOfColumns;
18
-
19
- /**
20
- * get size of element with number span
21
- * @param span
22
- */
23
- const getSizeSpan = (span: number = numberOfColumns): number => {
24
- return span * sizePerSpan + (span - 1) * gutterSize;
25
- };
26
-
27
- return {
28
- isDevMode,
29
- gutterSize,
30
- screenPadding,
31
- numberOfColumns,
32
- sizePerSpan,
33
- getSizeSpan,
34
- };
35
- };
36
3
 
37
4
  /**
38
5
  * validate children type
@@ -80,4 +47,4 @@ const validateChildren = (
80
47
  return results;
81
48
  };
82
49
 
83
- export {useGridSystem, validateChildren};
50
+ export {validateChildren};
@@ -10,11 +10,10 @@ const BottomTab: FC<BottomTabProps> = ({tabs}) => {
10
10
  <Tab.Navigator>
11
11
  {tabs.map((item, index) => {
12
12
  return (
13
- // @ts-ignore
14
13
  <Tab.Screen
15
14
  key={`${item.label}-${index}`}
16
15
  name={item.label}
17
- component={item.component}
16
+ component={item.screen}
18
17
  options={{
19
18
  tabBarLabel: item.label,
20
19
  tabBarBadge: item?.badgeLabel,
@@ -1,5 +1,6 @@
1
1
  import React, {useContext} from 'react';
2
2
  import {
3
+ Animated,
3
4
  DeviceEventEmitter,
4
5
  StatusBar,
5
6
  StyleSheet,
@@ -10,7 +11,6 @@ import {ApplicationContext, NavigationButton} from './index';
10
11
  import {Colors, Spacing, Styles} from '../Consts';
11
12
  import {Image} from '../Image';
12
13
  import {HeaderBackgroundProps, TitleCustomProps} from './types';
13
- import {useGridSystem} from '../Layout';
14
14
  import {Text} from '../Text';
15
15
  import {Icon} from '../Icon';
16
16
 
@@ -24,6 +24,7 @@ const styles = StyleSheet.create({
24
24
  headerTitleContainer: {
25
25
  alignItems: 'center',
26
26
  justifyContent: 'center',
27
+ width: '100%',
27
28
  },
28
29
  avatar: {width: 36, height: 36, borderRadius: 18},
29
30
  dotAvatar: {
@@ -47,7 +48,6 @@ const styles = StyleSheet.create({
47
48
  marginLeft: Spacing.S,
48
49
  padding: Spacing.XS,
49
50
  height: 28,
50
- borderWidth: 0.5,
51
51
  borderRadius: 14,
52
52
  borderColor: '#00000066',
53
53
  justifyContent: 'center',
@@ -62,15 +62,20 @@ const styles = StyleSheet.create({
62
62
  headerLeft: {
63
63
  marginLeft: 12,
64
64
  },
65
+ title: {fontSize: 15, lineHeight: 22, fontWeight: '600'},
65
66
  });
66
67
 
67
- const HeaderTitle = (props: any) => {
68
+ const HeaderTitle: React.FC<any> = props => {
69
+ const opacity = props.animatedValue?.interpolate({
70
+ inputRange: [0, 200],
71
+ outputRange: [0, 1],
72
+ extrapolate: 'clamp',
73
+ });
68
74
  return (
69
- <Text
75
+ <Animated.Text
70
76
  {...props}
71
- typography="header_default"
72
- weight="bold"
73
- color={props.tintColor}
77
+ accessibilityLabel={`HeaderTitle/${props.children}`}
78
+ style={[styles.title, {opacity, color: props.tintColor}]}
74
79
  />
75
80
  );
76
81
  };
@@ -90,7 +95,12 @@ const HeaderLeft: React.FC<any> = ({tintColor}) => {
90
95
 
91
96
  return (
92
97
  <View style={styles.headerLeft}>
93
- <NavigationButton icon="ic_back" tintColor={tintColor} onPress={goBack} />
98
+ <NavigationButton
99
+ icon="ic_back"
100
+ tintColor={tintColor}
101
+ onPress={goBack}
102
+ accessibilityLabel={'Back'}
103
+ />
94
104
  </View>
95
105
  );
96
106
  };
@@ -98,19 +108,31 @@ const HeaderLeft: React.FC<any> = ({tintColor}) => {
98
108
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
99
109
  image,
100
110
  backgroundColor,
111
+ animatedValue,
101
112
  }) => {
102
113
  const {theme} = useContext(ApplicationContext);
103
114
  let headerImage = theme.assets?.headerBackground;
115
+ let borderBottomWidth = 0;
104
116
  if (image === null) {
117
+ borderBottomWidth = 1;
105
118
  headerImage = undefined;
106
119
  }
120
+ const opacity = animatedValue?.interpolate({
121
+ inputRange: [0, 200],
122
+ outputRange: [0, 1],
123
+ extrapolate: 'clamp',
124
+ });
125
+
107
126
  return (
108
- <View
127
+ <Animated.View
109
128
  style={[
110
129
  Styles.flex,
111
130
  {
112
131
  backgroundColor: backgroundColor ?? theme.colors.background.default,
113
132
  overflow: 'hidden',
133
+ borderBottomWidth: borderBottomWidth,
134
+ borderColor: theme.colors.border.default,
135
+ opacity,
114
136
  },
115
137
  ]}>
116
138
  <StatusBar
@@ -119,7 +141,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
119
141
  {headerImage && (
120
142
  <Image style={styles.headerBackground} source={{uri: headerImage}} />
121
143
  )}
122
- </View>
144
+ </Animated.View>
123
145
  );
124
146
  };
125
147
 
@@ -131,8 +153,6 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
131
153
  tintColor,
132
154
  dotColor,
133
155
  }) => {
134
- const {getSizeSpan} = useGridSystem();
135
-
136
156
  const header = (
137
157
  <View style={Styles.row}>
138
158
  <View>
@@ -151,11 +171,7 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
151
171
  </View>
152
172
  </View>
153
173
  );
154
- return (
155
- <View style={[styles.headerTitleContainer, {width: getSizeSpan(10)}]}>
156
- {content ?? header}
157
- </View>
158
- );
174
+ return <View style={styles.headerTitleContainer}>{content ?? header}</View>;
159
175
  };
160
176
 
161
177
  const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
@@ -193,21 +209,19 @@ const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
193
209
 
194
210
  return (
195
211
  <View style={styles.headerRightButton}>
196
- <NavigationButton
197
- icon="addFavorite"
198
- tintColor={tintColor}
199
- onPress={() => {}}
200
- />
201
212
  <View
202
213
  style={[
203
214
  styles.toolkitContainer,
204
- {backgroundColor: backgroundColor ?? '#00000066'},
215
+ {
216
+ backgroundColor: backgroundColor ?? '#00000066',
217
+ borderWidth: backgroundColor ? 0.5 : 0,
218
+ },
205
219
  ]}>
206
- <TouchableOpacity>
220
+ <TouchableOpacity accessibilityLabel={'Toolkit/More'}>
207
221
  <Icon color={tintColor} source="navigation_more_horiz" size={20} />
208
222
  </TouchableOpacity>
209
223
  <View style={[styles.divider, {backgroundColor: tintColor}]} />
210
- <TouchableOpacity>
224
+ <TouchableOpacity accessibilityLabel={'Toolkit/Close'}>
211
225
  <Icon
212
226
  color={tintColor}
213
227
  source="16_navigation_close_circle"
@@ -63,7 +63,7 @@ const Modal: React.FC<ModalParams> = ({navigation, route}) => {
63
63
  style={styles.modalSpaceVertical}
64
64
  onPress={() => onDismiss()}
65
65
  />
66
- <View style={styles.modalContent}>
66
+ <View style={styles.modalContent} accessibilityLabel={'Modal'}>
67
67
  <Component {...params} />
68
68
  </View>
69
69
  <Pressable
@@ -128,7 +128,9 @@ const BottomSheet: React.FC<BottomSheetParams> = ({navigation, route}) => {
128
128
  }}
129
129
  handleComponent={null}
130
130
  backdropComponent={backdropComponent}>
131
- <View style={{paddingBottom: bottom}}>
131
+ <View
132
+ style={{paddingBottom: bottom}}
133
+ accessibilityLabel={'BottomSheet'}>
132
134
  <View style={[styles.sheetContainer, {backgroundColor}]}>
133
135
  <View style={styles.indicatorContainer}>
134
136
  <View
@@ -11,6 +11,7 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
11
11
  onPress,
12
12
  backgroundColor,
13
13
  useBorder = true,
14
+ accessibilityLabel,
14
15
  }) => {
15
16
  const {theme} = useContext(ApplicationContext);
16
17
  if (!backgroundColor && tintColor != Colors.black_01) {
@@ -19,11 +20,12 @@ const NavigationButton: React.FC<NavigationButtonProps> = ({
19
20
 
20
21
  return (
21
22
  <TouchableOpacity
23
+ accessibilityLabel={`NavigationButton/${accessibilityLabel}`}
22
24
  style={[
23
25
  styles.container,
24
26
  {
25
27
  backgroundColor: backgroundColor ?? '#00000066',
26
- borderWidth: useBorder ? 0.5 : 0,
28
+ borderWidth: useBorder && backgroundColor ? 0.5 : 0,
27
29
  },
28
30
  ]}
29
31
  onPress={onPress}>
@@ -10,7 +10,7 @@ import StackScreen from './StackScreen';
10
10
  import ModalScreen from './ModalScreen';
11
11
  import Navigator from './Navigator';
12
12
  import {getDialogOptions, getModalOptions, getStackOptions} from './utils';
13
- import {GridSystem, useGridSystem} from '../Layout';
13
+ import {GridSystem} from '../Layout';
14
14
  import {defaultContext} from '../Consts';
15
15
  import {NavigationContainerProps} from './types';
16
16
 
@@ -22,7 +22,6 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
22
22
  theme,
23
23
  options,
24
24
  }) => {
25
- const grid = useGridSystem();
26
25
  const navigationRef = React.useRef<NavigationContainerRef>(null);
27
26
  const navigator = useRef(new Navigator(navigationRef));
28
27
 
@@ -75,7 +74,7 @@ const NavigationContainer: React.FC<NavigationContainerProps> = ({
75
74
  </ReactNavigationContainer>
76
75
  </ApplicationContext.Provider>
77
76
  </BottomSheetModalProvider>
78
- {grid.isDevMode && <GridSystem />}
77
+ {__DEV__ && <GridSystem />}
79
78
  </SafeAreaProvider>
80
79
  );
81
80
  };
@@ -22,7 +22,7 @@ const StackScreen: React.FC<any> = props => {
22
22
  if (options) {
23
23
  params.navigation.setOptions(options);
24
24
  }
25
- }, [navigation, options]);
25
+ }, [options]);
26
26
 
27
27
  return <Component {...params} />;
28
28
  };
@@ -1,5 +1,5 @@
1
- import {ScrollViewProps, ViewProps} from 'react-native';
2
- import React, {ComponentType, ReactElement} from 'react';
1
+ import {Animated, ScrollViewProps, ViewProps} from 'react-native';
2
+ import React from 'react';
3
3
  import Navigator from './Navigator';
4
4
 
5
5
  export type Theme = {
@@ -71,8 +71,11 @@ export type NavigationContainerProps = {
71
71
  export interface ScreenContainerProps extends ViewProps {
72
72
  edges?: any[];
73
73
  enableKeyboardAvoidingView?: boolean;
74
- scrollable: boolean;
74
+ headerBackground?: string;
75
+ scrollable?: boolean;
76
+ scrollY?: Animated.Value;
75
77
  scrollViewProps?: ScrollViewProps;
78
+ footerComponent?: React.ElementType;
76
79
  }
77
80
 
78
81
  export type ScreenParams = {
@@ -99,6 +102,7 @@ export type NavigationButtonProps = {
99
102
  backgroundColor?: string;
100
103
  useBorder?: boolean;
101
104
  onPress: () => void;
105
+ accessibilityLabel?: string;
102
106
  };
103
107
 
104
108
  export type NavigationOptions = {
@@ -107,11 +111,13 @@ export type NavigationOptions = {
107
111
  headerTitleAlign?: 'left' | 'center';
108
112
  customTitle?: TitleCustomProps;
109
113
  headerRight?: (props?: any) => React.ReactElement;
114
+ animatedValue?: Animated.Value;
110
115
  };
111
116
 
112
117
  export type HeaderBackgroundProps = {
113
118
  image?: string | null;
114
119
  backgroundColor?: string | null;
120
+ animatedValue?: Animated.Value;
115
121
  };
116
122
 
117
123
  export type TitleCustomProps = {
@@ -128,7 +134,7 @@ export type BottomTabItemProps = {
128
134
  icon: string;
129
135
  showDot?: boolean;
130
136
  badgeLabel?: string;
131
- component: ComponentType<any> | undefined;
137
+ screen: React.ComponentType<any>;
132
138
  };
133
139
 
134
140
  export type BottomTabProps = {
@@ -73,43 +73,58 @@ const getModalOptions = (): StackNavigationOptions => {
73
73
  };
74
74
 
75
75
  const getOptions = (params: NavigationOptions, theme: Theme) => {
76
- let surfaceTheme: {};
77
- let titleTheme: {};
76
+ let backgroundProps = {};
77
+ let titleProps = {};
78
+ let options = {};
78
79
 
79
80
  if (params.surface == true) {
80
- surfaceTheme = {
81
- headerBackground: () => (
82
- <HeaderBackground
83
- image={null}
84
- backgroundColor={theme.colors.background.surface}
85
- />
86
- ),
81
+ options = {
82
+ ...options,
87
83
  ...getTintColor({
88
84
  ...theme,
89
85
  assets: {...theme.assets, headerBackground: undefined},
90
86
  }),
91
87
  };
92
- } else {
93
- surfaceTheme = {
94
- headerBackground: HeaderBackground,
95
- ...getTintColor(theme),
88
+ backgroundProps = {
89
+ ...backgroundProps,
90
+ image: null,
91
+ backgroundColor: theme.colors.background.surface,
96
92
  };
93
+ } else {
94
+ backgroundProps = {};
95
+ options = {...options, ...getTintColor(theme)};
96
+ }
97
+
98
+ if (params.animatedValue) {
99
+ backgroundProps = {...backgroundProps, animatedValue: params.animatedValue};
100
+ titleProps = {...titleProps, animatedValue: params.animatedValue};
101
+ options = {...options, headerTransparent: true};
102
+ } else {
103
+ options = {...options, headerTransparent: false};
97
104
  }
98
105
 
99
106
  if (params.customTitle) {
100
- titleTheme = {
107
+ options = {
108
+ ...options,
101
109
  headerTitleAlign: 'left',
102
110
  headerTitle: (props: any) => {
103
111
  return <HeaderCustom {...params.customTitle} {...props} />;
104
112
  },
105
113
  };
106
114
  } else {
107
- titleTheme = {
108
- headerTitle: HeaderTitle,
115
+ options = {
116
+ ...options,
117
+ headerTitle: (props: any) => {
118
+ return <HeaderTitle {...titleProps} {...props} />;
119
+ },
109
120
  };
110
121
  }
111
122
 
112
- return {...params, ...surfaceTheme, ...titleTheme};
123
+ return {
124
+ ...params,
125
+ ...options,
126
+ headerBackground: () => <HeaderBackground {...backgroundProps} />,
127
+ };
113
128
  };
114
129
 
115
130
  export {getStackOptions, getDialogOptions, getModalOptions, getOptions};
@@ -132,6 +132,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
132
132
 
133
133
  return (
134
134
  <View
135
+ accessibilityLabel={'PopupNotify'}
135
136
  style={[
136
137
  styles.container,
137
138
  {backgroundColor: theme.colors.background.surface},
@@ -3,7 +3,7 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native';
3
3
  import {PopupPromotionProps} from './types';
4
4
  import {ApplicationContext} from '../Navigation';
5
5
  import {Image} from '../Image';
6
- import {Radius} from '../Consts';
6
+ import {Radius, Spacing} from '../Consts';
7
7
  import {Icon} from '../Icon';
8
8
 
9
9
  const PopupPromotion: React.FC<PopupPromotionProps> = ({image, onClose}) => {
@@ -38,7 +38,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({image, onClose}) => {
38
38
  };
39
39
 
40
40
  return (
41
- <>
41
+ <View accessibilityLabel={'PopupPromotion'}>
42
42
  <Image
43
43
  style={styles.container}
44
44
  source={{
@@ -46,7 +46,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({image, onClose}) => {
46
46
  }}
47
47
  />
48
48
  {buildCloseIcon()}
49
- </>
49
+ </View>
50
50
  );
51
51
  };
52
52
 
@@ -55,11 +55,10 @@ const styles = StyleSheet.create({
55
55
  aspectRatio: 0.72,
56
56
  },
57
57
  iconCloseContainer: {
58
- position: 'absolute',
59
58
  width: '100%',
60
59
  flexDirection: 'row',
61
60
  justifyContent: 'center',
62
- bottom: -36,
61
+ marginTop: Spacing.L,
63
62
  },
64
63
  iconClose: {
65
64
  width: 20,
package/Radio/index.tsx CHANGED
@@ -39,6 +39,7 @@ const Radio: FC<RadioProps> = ({
39
39
 
40
40
  return (
41
41
  <TouchableOpacity
42
+ accessibilityLabel={`Radio/${label}`}
42
43
  onPress={onChange}
43
44
  disabled={disabled}
44
45
  style={[
@@ -42,7 +42,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({style}) => {
42
42
  }
43
43
  };
44
44
  return (
45
- <View style={[styles.container, style]}>
45
+ <View style={[styles.container, style]} accessibilityLabel={'Skeleton'}>
46
46
  <View
47
47
  onLayout={e => onLayout(e.nativeEvent.layout.width)}
48
48
  style={[Styles.flex, {backgroundColor: PRIMARY_COLOR}]}>
package/Switch/index.tsx CHANGED
@@ -18,6 +18,7 @@ const Switch: FC<SwitchProps> = props => {
18
18
 
19
19
  return (
20
20
  <TouchableOpacity
21
+ accessibilityLabel={'Switch'}
21
22
  disabled={disabled}
22
23
  onPress={() => onChange?.(!value)}
23
24
  style={[
package/Text/index.tsx CHANGED
@@ -3,6 +3,7 @@ import {Text as RNText, TextProps as RNTextProps} from 'react-native';
3
3
  import styles from './styles';
4
4
  import {Typography, TypographyWeight} from './types';
5
5
  import {ApplicationContext} from '../Navigation';
6
+ import {GridContext} from '../Layout';
6
7
 
7
8
  const SFProText: TypographyWeight = {
8
9
  100: 'Thin',
@@ -40,6 +41,7 @@ const FontStyle: {[key: string]: string} = {
40
41
  export interface TextProps extends RNTextProps {
41
42
  typography: Typography;
42
43
  color?: string;
44
+ widthSpan?: number;
43
45
  }
44
46
 
45
47
  const Text: React.FC<TextProps> = ({
@@ -47,8 +49,10 @@ const Text: React.FC<TextProps> = ({
47
49
  color,
48
50
  children,
49
51
  style,
52
+ widthSpan,
50
53
  ...rest
51
54
  }) => {
55
+ const grid = useContext(GridContext);
52
56
  const {theme} = useContext(ApplicationContext);
53
57
 
54
58
  const getTypoStyle = (typo: Typography) => {
@@ -83,11 +87,17 @@ const Text: React.FC<TextProps> = ({
83
87
  };
84
88
 
85
89
  const textStyle = getTypoStyle(typography);
86
-
90
+ const spanStyle = widthSpan ? {width: grid.getSizeSpan(widthSpan)} : {};
87
91
  return (
88
92
  <RNText
89
93
  {...rest}
90
- style={[style, textStyle, {color: color ?? theme.colors.text.default}]}>
94
+ accessibilityLabel={`Text/${children}`}
95
+ style={[
96
+ style,
97
+ textStyle,
98
+ spanStyle,
99
+ {color: color ?? theme.colors.text.default},
100
+ ]}>
91
101
  {children ?? ''}
92
102
  </RNText>
93
103
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -1,63 +0,0 @@
1
- import React, {useContext} from 'react';
2
- import {View} from 'react-native';
3
- import {useGridSystem} from './index';
4
- import {SectionItemProps} from './types';
5
- import {ApplicationContext} from '../Navigation';
6
-
7
- const SectionItem: React.FC<SectionItemProps> = ({
8
- widthSpan = 12,
9
- heightSpan,
10
- children,
11
- style,
12
- }) => {
13
- const {theme} = useContext(ApplicationContext);
14
- const grid = useGridSystem();
15
-
16
- /**
17
- * render overlay view only dev mode
18
- */
19
- const renderOverlay = () => {
20
- return (
21
- <View
22
- pointerEvents={'none'}
23
- style={{
24
- position: 'absolute',
25
- top: 0,
26
- bottom: 0,
27
- left: 0,
28
- right: 0,
29
- borderColor: 'red',
30
- borderWidth: 1,
31
- }}
32
- />
33
- );
34
- };
35
-
36
- const styles: any = style ?? {};
37
-
38
- return (
39
- <View
40
- style={{
41
- ...styles,
42
- width: grid.getSizeSpan(widthSpan),
43
- height: heightSpan ? grid.getSizeSpan(heightSpan) : undefined,
44
- overflow: 'hidden',
45
- margin: undefined,
46
- marginTop: undefined,
47
- marginBottom: undefined,
48
- marginLeft: undefined,
49
- marginRight: undefined,
50
- }}>
51
- {children}
52
- {grid.isDevMode && renderOverlay()}
53
- </View>
54
- );
55
- };
56
-
57
- SectionItem.defaultProps = {
58
- widthSpan: 12,
59
- };
60
-
61
- SectionItem.displayName = 'SectionItem';
62
-
63
- export default SectionItem;