@momo-kits/foundation 0.80.9 → 0.81.2

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/Layout/Screen.tsx CHANGED
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  Animated,
3
+ Image,
3
4
  KeyboardAvoidingView,
5
+ NativeScrollEvent,
6
+ NativeSyntheticEvent,
4
7
  Platform,
5
8
  ScrollViewProps,
6
9
  View,
@@ -8,13 +11,26 @@ import {
8
11
  } from 'react-native';
9
12
  import {useHeaderHeight} from '@react-navigation/stack';
10
13
  import {useSafeAreaInsets} from 'react-native-safe-area-context';
11
- import React, {ReactNode, useContext, useEffect, useRef} from 'react';
12
- import {Spacing, Styles} from '../Consts';
14
+ import React, {
15
+ forwardRef,
16
+ ReactNode,
17
+ useContext,
18
+ useEffect,
19
+ useRef,
20
+ } from 'react';
21
+ import {Colors, Spacing, Styles} from '../Consts';
13
22
  import {Card, Section, validateChildren} from './index';
14
- import {ApplicationContext, HeaderTitle} from '../Application';
23
+ import {
24
+ ApplicationContext,
25
+ HeaderBackground,
26
+ HeaderTitle,
27
+ } from '../Application';
15
28
  import Navigation from '../Application/Navigation';
29
+ import styles from './styles';
30
+ import {AnimatedHeader, NavigationOptions} from '../Application/types';
16
31
 
17
32
  export interface ScreenProps extends ViewProps {
33
+ children: React.ReactNode;
18
34
  /**
19
35
  * Optional. Navigation object for handling navigation actions.
20
36
  */
@@ -25,25 +41,20 @@ export interface ScreenProps extends ViewProps {
25
41
  */
26
42
  enableKeyboardAvoidingView?: boolean;
27
43
 
28
- /**
29
- * Optional. Background color of the header.
30
- */
31
- headerBackground?: string;
32
-
33
44
  /**
34
45
  * Optional. If `true`, the screen content is scrollable.
35
46
  */
36
47
  scrollable?: boolean;
37
48
 
38
49
  /**
39
- * Optional. If `true`, use an animated header for the screen.
50
+ * Optional. If `true`, use an extended header for the screen.
40
51
  */
41
- useAnimatedHeader?: boolean;
52
+ useExtendedHeader?: boolean;
42
53
 
43
54
  /**
44
- * Optional. If `true`, use an extended header for the screen.
55
+ * Optional. Props for the animated component or bool used top navigation animated.
45
56
  */
46
- useExtendedHeader?: boolean;
57
+ animatedHeader?: AnimatedHeader;
47
58
 
48
59
  /**
49
60
  * Optional. Props for the underlying ScrollView component when `scrollable` is `true`.
@@ -51,206 +62,242 @@ export interface ScreenProps extends ViewProps {
51
62
  scrollViewProps?: ScrollViewProps;
52
63
 
53
64
  /**
54
- * Optional. ReactNode representing the footer component of the screen.
65
+ * Optional. ReactNode representing the header component of the screen.
55
66
  */
56
- footerComponent?: ReactNode;
67
+ headerComponent?: ReactNode;
57
68
 
58
69
  /**
59
- * Optional. Margin for the header component.
70
+ * Optional. ReactNode representing the footer component of the screen.
60
71
  */
61
- marginHeader?: number;
72
+ footerComponent?: ReactNode;
62
73
 
63
74
  /**
64
75
  * Optional. Background color of the screen.
65
76
  */
66
77
  backgroundColor?: string;
67
- }
68
-
69
- const Screen: React.FC<ScreenProps> = ({
70
- children,
71
- enableKeyboardAvoidingView = false,
72
- scrollable = false,
73
- navigation,
74
- marginHeader,
75
- useAnimatedHeader,
76
- useExtendedHeader,
77
- headerBackground,
78
- scrollViewProps,
79
- footerComponent: Footer,
80
- backgroundColor,
81
- }) => {
82
- const animatedValue = useRef<Animated.Value | undefined>(undefined);
83
- const {theme} = useContext(ApplicationContext);
84
- const insets = useSafeAreaInsets();
85
- const headerTransparent = useAnimatedHeader || useExtendedHeader;
86
-
87
- let handleScroll;
88
- let imageAnimatedStyle = {};
89
- let opacity;
90
- let Component: any = View;
91
- animatedValue.current = undefined;
92
78
 
93
79
  /**
94
- * automatic margin content if use header transparent
80
+ * Optional. layout card offset overlap header banner.
95
81
  */
96
- if (headerTransparent) {
97
- animatedValue.current = new Animated.Value(0);
98
- marginHeader = marginHeader ?? useHeaderHeight();
82
+ layoutOffset?: -8 | -24 | -56;
83
+ }
84
+
85
+ const Screen = forwardRef(
86
+ (
87
+ {
88
+ children,
89
+ enableKeyboardAvoidingView = false,
90
+ scrollable = false,
91
+ navigation,
92
+ useExtendedHeader,
93
+ animatedHeader,
94
+ scrollViewProps,
95
+ headerComponent: Header,
96
+ footerComponent: Footer,
97
+ layoutOffset = -56,
98
+ backgroundColor,
99
+ }: ScreenProps,
100
+ ref,
101
+ ) => {
102
+ const animatedValue = useRef<Animated.Value | undefined>(undefined);
103
+ const {theme} = useContext(ApplicationContext);
104
+ const insets = useSafeAreaInsets();
105
+ const heightHeader = useHeaderHeight();
106
+ const currentTint = useRef(Colors.black_01);
107
+ let styleAnimatedHeader: NavigationOptions;
108
+ let handleScroll;
109
+ let headerBackground: string | undefined = undefined;
110
+ let Component: any = View;
111
+ animatedValue.current = undefined;
112
+
99
113
  if (useExtendedHeader) {
100
114
  headerBackground = theme.assets?.headerBackground;
101
115
  }
102
- }
103
116
 
104
- useEffect(() => {
105
- if (useAnimatedHeader != undefined || useExtendedHeader != undefined) {
106
- let title = {};
117
+ useEffect(() => {
107
118
  if (useExtendedHeader) {
108
- title = {headerTitle: HeaderTitle};
119
+ navigation?.setOptions({
120
+ headerTransparent: true,
121
+ headerBackground: () => null,
122
+ });
109
123
  }
110
- navigation?.setOptions({
111
- animatedValue: animatedValue.current,
112
- headerTransparent: headerTransparent,
113
- ...title,
114
- });
115
- }
116
- }, [useExtendedHeader, useAnimatedHeader]);
124
+ }, [useExtendedHeader]);
117
125
 
118
- /**
119
- * animated for header stack when used bottom tab
120
- */
121
- useEffect(() => {
122
- const current: any = navigation;
123
- if (headerTransparent) {
124
- return current?.addListener?.('focus', () => {
125
- const stackNavigation = new Navigation(current?.getParent(), theme);
126
- stackNavigation?.setOptions({
127
- animatedValue: animatedValue.current,
128
- });
129
- });
130
- }
131
- }, [useAnimatedHeader, useExtendedHeader, navigation]);
126
+ useEffect(() => {
127
+ if (animatedHeader) {
128
+ styleAnimatedHeader = {
129
+ headerTransparent: true,
130
+ headerBackground: (props: any) => (
131
+ <HeaderBackground
132
+ {...props}
133
+ animatedValue={animatedValue.current}
134
+ />
135
+ ),
136
+ headerTitle: (props: any) => (
137
+ <HeaderTitle {...props} animatedValue={animatedValue.current} />
138
+ ),
139
+ };
140
+ if (animatedHeader.type == 'surface') {
141
+ styleAnimatedHeader = {
142
+ headerTransparent: true,
143
+ headerBackground: (props: any) => (
144
+ <HeaderBackground
145
+ {...props}
146
+ image={null}
147
+ backgroundColor={theme.colors.background.surface}
148
+ useBorder={true}
149
+ animatedValue={animatedValue.current}
150
+ />
151
+ ),
152
+ };
153
+ }
154
+ navigation?.setOptions(styleAnimatedHeader);
155
+ const current: any = navigation;
156
+ const parent = current?.getParent?.();
157
+ if (parent) {
158
+ return current?.addListener?.('focus', () => {
159
+ const stackNavigation = new Navigation(parent, theme);
160
+ stackNavigation?.setOptions(styleAnimatedHeader);
161
+ });
162
+ }
163
+ }
164
+ }, [animatedHeader]);
132
165
 
133
- /**
134
- * animated when use scroll && animated value
135
- */
136
- if (scrollable) {
137
- Component = Animated.ScrollView;
138
- if (useAnimatedHeader) {
139
- opacity = animatedValue.current?.interpolate({
140
- inputRange: [0, 100],
141
- outputRange: [1, 0],
142
- extrapolate: 'clamp',
143
- });
144
- imageAnimatedStyle = {
145
- transform: [
146
- {
147
- scale: animatedValue.current?.interpolate?.({
148
- inputRange: [-300, 0, 1],
149
- outputRange: [1.75, 1, 1],
150
- extrapolate: 'clamp',
151
- }),
152
- },
153
- ],
154
- };
155
- handleScroll = Animated.event(
156
- [
166
+ /**
167
+ * animated when use scroll && animated value
168
+ */
169
+ if (scrollable) {
170
+ Component = Animated.ScrollView;
171
+ handleScroll = scrollViewProps?.onScroll;
172
+ if (animatedHeader) {
173
+ animatedValue.current = new Animated.Value(0);
174
+ handleScroll = Animated.event(
175
+ [
176
+ {
177
+ nativeEvent: {
178
+ contentOffset: {y: animatedValue.current as Animated.Value},
179
+ },
180
+ },
181
+ ],
157
182
  {
158
- nativeEvent: {
159
- contentOffset: {y: animatedValue.current as Animated.Value},
183
+ useNativeDriver: true,
184
+ listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
185
+ if (animatedHeader.type == 'surface') {
186
+ const offsetY = e.nativeEvent.contentOffset.y;
187
+ let color = Colors.black_01;
188
+ if (offsetY > 50) {
189
+ color = theme.colors.text.default;
190
+ }
191
+ if (color != currentTint.current) {
192
+ currentTint.current = color;
193
+ navigation?.setOptions({
194
+ headerTintColor: color,
195
+ });
196
+ }
197
+ }
198
+ scrollViewProps?.onScroll?.(e);
160
199
  },
161
200
  },
162
- ],
163
- {useNativeDriver: true},
164
- );
201
+ );
202
+ }
165
203
  }
166
- }
167
204
 
168
- /**
169
- * build content for screen
170
- */
171
- const renderContent = () => {
172
- const results = validateChildren(children, [Section, Card]);
173
- if (Array.isArray(results)) {
174
- return results.map((item, index) => {
175
- const space = item.props?.useMargin === false ? 0 : Spacing.M;
205
+ /**
206
+ * render top navigation banner
207
+ */
208
+ const renderHeader = () => {
209
+ if (typeof animatedHeader?.component === 'function') {
210
+ return animatedHeader?.component({
211
+ animatedValue: animatedValue.current,
212
+ });
213
+ }
214
+ };
215
+
216
+ /**
217
+ * build content for screen
218
+ */
219
+ const renderContent = () => {
220
+ const results = validateChildren(children, [Section, Card]);
221
+ if (Array.isArray(results)) {
222
+ return results.map((item, index) => {
223
+ const space = item.props?.useMargin === false ? 0 : Spacing.M;
224
+ return (
225
+ <View
226
+ key={`ScreenItem${index}`}
227
+ style={{
228
+ marginTop: space,
229
+ flex: item.props?.expanded ? 1 : undefined,
230
+ }}>
231
+ {item}
232
+ </View>
233
+ );
234
+ });
235
+ } else {
236
+ const item: any = children;
237
+ const space = item?.props?.useMargin === false ? 0 : Spacing.M;
176
238
  return (
177
239
  <View
178
- key={`ScreenItem${index}`}
240
+ key={`ScreenItem`}
179
241
  style={{
180
242
  marginTop: space,
181
- flex: item.props?.expanded ? 1 : undefined,
243
+ flex: item?.props?.expanded ? 1 : undefined,
182
244
  }}>
183
- {item}
245
+ {children}
184
246
  </View>
185
247
  );
186
- });
187
- } else {
188
- const item: any = children;
189
- const space = item?.props?.useMargin === false ? 0 : Spacing.M;
190
- return (
191
- <View
192
- key={`ScreenItem`}
193
- style={{
194
- marginTop: space,
195
- flex: item?.props?.expanded ? 1 : undefined,
196
- }}>
197
- {children}
198
- </View>
199
- );
200
- }
201
- };
248
+ }
249
+ };
202
250
 
203
- return (
204
- <View
205
- style={[Styles.flex, {backgroundColor}]}
206
- accessibilityLabel={`ScreenContainer`}>
251
+ return (
207
252
  <View
208
- style={{
209
- position: 'absolute',
210
- maxHeight: 215,
211
- width: '100%',
212
- overflow: 'hidden',
213
- }}>
253
+ style={[Styles.flex, {backgroundColor}]}
254
+ accessibilityLabel={`ScreenContainer`}>
214
255
  {!!headerBackground && (
215
- <Animated.Image
216
- source={{
217
- uri: headerBackground,
218
- }}
219
- style={{
220
- opacity: opacity ?? 1,
221
- aspectRatio: 1.75,
222
- ...imageAnimatedStyle,
223
- }}
224
- />
256
+ <>
257
+ <Image
258
+ source={{
259
+ uri: headerBackground,
260
+ }}
261
+ style={styles.extendedHeader}
262
+ />
263
+ <View style={{height: heightHeader}} />
264
+ </>
225
265
  )}
266
+ <KeyboardAvoidingView
267
+ style={Styles.flex}
268
+ enabled={enableKeyboardAvoidingView}
269
+ behavior={Platform.select({
270
+ ios: 'padding',
271
+ android: undefined,
272
+ })}>
273
+ {Header}
274
+ <Component
275
+ {...scrollViewProps}
276
+ ref={ref}
277
+ heightHeader={heightHeader}
278
+ showsVerticalScrollIndicator={false}
279
+ onScroll={handleScroll}
280
+ style={Styles.flex}>
281
+ <View
282
+ style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
283
+ {renderHeader()}
284
+ </View>
285
+ {renderContent()}
286
+ </Component>
287
+ {Footer && (
288
+ <View
289
+ style={{
290
+ paddingTop: Spacing.S,
291
+ paddingBottom: Math.min(insets.bottom, 20) + Spacing.S,
292
+ backgroundColor: theme.colors.background.surface,
293
+ }}>
294
+ <Section>{Footer}</Section>
295
+ </View>
296
+ )}
297
+ </KeyboardAvoidingView>
226
298
  </View>
227
- <KeyboardAvoidingView
228
- style={Styles.flex}
229
- enabled={enableKeyboardAvoidingView}
230
- behavior={Platform.select({
231
- ios: 'padding',
232
- android: undefined,
233
- })}>
234
- <Component
235
- {...scrollViewProps}
236
- showsVerticalScrollIndicator={false}
237
- onScroll={handleScroll}
238
- style={[Styles.flex, {marginTop: marginHeader}]}>
239
- {renderContent()}
240
- </Component>
241
- {Footer && (
242
- <View
243
- style={{
244
- paddingTop: Spacing.S,
245
- paddingBottom: Math.min(insets.bottom, 20) + Spacing.S,
246
- backgroundColor: theme.colors.background.surface,
247
- }}>
248
- <Section>{Footer}</Section>
249
- </View>
250
- )}
251
- </KeyboardAvoidingView>
252
- </View>
253
- );
254
- };
299
+ );
300
+ },
301
+ );
255
302
 
256
303
  export default Screen;
@@ -7,6 +7,7 @@ import Item from './Item';
7
7
  import {ApplicationContext} from '../Application';
8
8
  import styles from './styles';
9
9
  import {Image} from '../Image';
10
+ import ItemList from './ItemList';
10
11
 
11
12
  export interface SectionProps extends ViewProps {
12
13
  /**
@@ -168,7 +169,7 @@ const Section: React.FC<SectionProps> = ({
168
169
  style={styles.imageBackground}
169
170
  />
170
171
  )}
171
- {renderView(validateChildren(children, [Item]))}
172
+ {renderView(validateChildren(children, [Item, ItemList]))}
172
173
  {showGrid && renderOverlay()}
173
174
  </View>
174
175
  </GridContext.Provider>
package/Layout/index.ts CHANGED
@@ -5,6 +5,7 @@ import {validateChildren} from './utils';
5
5
  import {createContext} from 'react';
6
6
  import {GridContextProps} from './types';
7
7
  import Item from './Item';
8
+ import ItemList from './ItemList';
8
9
 
9
10
  const GridContext = createContext<GridContextProps>({
10
11
  numberOfColumns: 12,
@@ -14,4 +15,12 @@ const GridContext = createContext<GridContextProps>({
14
15
  getSizeSpan: () => 0,
15
16
  });
16
17
 
17
- export {GridContext, Card, Section, GridSystem, validateChildren, Item};
18
+ export {
19
+ GridContext,
20
+ Card,
21
+ Section,
22
+ GridSystem,
23
+ validateChildren,
24
+ Item,
25
+ ItemList,
26
+ };
package/Layout/styles.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import {StyleSheet} from 'react-native';
2
+ import {Radius, Spacing} from '../Consts';
2
3
 
3
4
  export default StyleSheet.create({
5
+ extendedHeader: {
6
+ aspectRatio: 1.75,
7
+ position: 'absolute',
8
+ width: '100%',
9
+ height: 210,
10
+ },
11
+ screenBanner: {
12
+ width: '100%',
13
+ },
4
14
  boxGrid: {
5
15
  position: 'absolute',
6
16
  top: 0,
@@ -34,6 +44,7 @@ export default StyleSheet.create({
34
44
  card: {
35
45
  flexDirection: 'row',
36
46
  flexWrap: 'wrap',
47
+ borderRadius: Radius.M,
37
48
  paddingTop: undefined,
38
49
  paddingBottom: undefined,
39
50
  paddingLeft: undefined,
@@ -64,5 +75,6 @@ export default StyleSheet.create({
64
75
  bottom: 0,
65
76
  left: 0,
66
77
  right: 0,
78
+ borderRadius: Spacing.M,
67
79
  },
68
80
  });
package/Layout/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import {FlatListProps} from 'react-native';
2
+
1
3
  export type GridContextProps = {
2
4
  /**
3
5
  * Represents the number of columns in the grid.
package/Layout/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Alert, Dimensions} from 'react-native';
1
+ import {Dimensions} from 'react-native';
2
2
  import React, {Fragment, ReactElement, useEffect, useState} from 'react';
3
3
 
4
4
  /**
@@ -10,23 +10,18 @@ const validateChildren = (
10
10
  children: React.ReactNode | React.ReactNode[],
11
11
  childrenTypes: Array<React.FC>,
12
12
  ) => {
13
- if (!__DEV__) return children;
14
-
15
13
  const validateType = (child: React.ReactElement) => {
16
14
  const valid = childrenTypes.some(
17
15
  childrenType => child?.type === childrenType || child?.type == Fragment,
18
16
  );
19
17
  if (!valid) {
20
- Alert.alert(
21
- 'Component Kits',
18
+ console.warn(
22
19
  `Wrong children type for this component, expected are ${childrenTypes
23
20
  .map(type => type.displayName)
24
21
  .join(', ')}.`,
25
22
  );
26
23
  }
27
- return valid;
28
24
  };
29
-
30
25
  if (Array.isArray(children)) {
31
26
  const flattenChildren: React.ReactElement[] = children.flat();
32
27
  for (const child of flattenChildren) {
@@ -9,6 +9,7 @@ import {ApplicationContext} from '../Application';
9
9
  const PaginationDot: FC<ChildPaginationProps> = ({
10
10
  dataLength = 2,
11
11
  activeIndex = 0,
12
+ style,
12
13
  }) => {
13
14
  const {theme} = useContext(ApplicationContext);
14
15
  const renderDots = () => {
@@ -27,7 +28,11 @@ const PaginationDot: FC<ChildPaginationProps> = ({
27
28
  }
28
29
  return dots;
29
30
  };
30
- return <View style={styles.paginationPinkContainer}>{renderDots()}</View>;
31
+ return (
32
+ <View style={[style, styles.row]}>
33
+ <View style={styles.paginationPinkContainer}>{renderDots()}</View>
34
+ </View>
35
+ );
31
36
  };
32
37
 
33
38
  export default PaginationDot;
@@ -8,12 +8,15 @@ import {Colors} from '../Consts';
8
8
  const PaginationNumber: FC<ChildPaginationProps> = ({
9
9
  activeIndex = 0,
10
10
  dataLength = 2,
11
+ style,
11
12
  }) => {
12
13
  return (
13
- <View style={[styles.paginationNumberContainer]}>
14
- <Text color={Colors.black_01} typography={'label_default'}>{`${
15
- activeIndex + 1
16
- }/${dataLength}`}</Text>
14
+ <View style={[style, styles.row]}>
15
+ <View style={styles.paginationNumberContainer}>
16
+ <Text color={Colors.black_01} typography={'label_default'}>{`${
17
+ activeIndex + 1
18
+ }/${dataLength}`}</Text>
19
+ </View>
17
20
  </View>
18
21
  );
19
22
  };
@@ -8,6 +8,7 @@ import {Colors, Spacing} from '../Consts';
8
8
  const PaginationWhiteDot: FC<ChildPaginationProps> = ({
9
9
  dataLength = 2,
10
10
  activeIndex = 0,
11
+ style,
11
12
  }) => {
12
13
  const renderDots = () => {
13
14
  const dots: React.ReactElement[] = [];
@@ -24,7 +25,11 @@ const PaginationWhiteDot: FC<ChildPaginationProps> = ({
24
25
  }
25
26
  return dots;
26
27
  };
27
- return <View style={[styles.paginationWhiteContainer]}>{renderDots()}</View>;
28
+ return (
29
+ <View style={[style, styles.row]}>
30
+ <View style={styles.paginationWhiteContainer}>{renderDots()}</View>
31
+ </View>
32
+ );
28
33
  };
29
34
 
30
35
  export default PaginationWhiteDot;
@@ -25,6 +25,8 @@ export default StyleSheet.create({
25
25
  paddingHorizontal: Spacing.S,
26
26
  borderRadius: Radius.L,
27
27
  backgroundColor: Colors.black_20 + '33',
28
+ alignItems: 'center',
29
+ justifyContent: 'center',
28
30
  },
29
31
  scrollContainer: {
30
32
  alignItems: 'center',
@@ -44,4 +46,5 @@ export default StyleSheet.create({
44
46
  height: 4,
45
47
  borderRadius: Radius.XS,
46
48
  },
49
+ row: {flexDirection: 'row'},
47
50
  });
@@ -20,6 +20,8 @@ export type PaginationProps = {
20
20
  * Represents the total number of items (pages) in the Pagination component.
21
21
  */
22
22
  dataLength: number;
23
+
24
+ style?: ViewStyle | ViewStyle[];
23
25
  };
24
26
 
25
27
  export type DotProps = {