@momo-kits/foundation 0.92.26-beta.2 → 0.92.26-beta.21

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.
@@ -0,0 +1,113 @@
1
+ import React, {FC, memo, useEffect, useRef} from 'react';
2
+ import {Platform, StyleSheet, TextInput, TextStyle} from 'react-native';
3
+ import {Colors} from '../Consts';
4
+ import {scaleSize} from '../Text';
5
+
6
+ export interface TextTypingProps {
7
+ data: string[];
8
+ timeDelayEnd?: number;
9
+ timeDelayNextChar?: number;
10
+ inputStyle?: TextStyle;
11
+ }
12
+
13
+ const TextTyping: FC<TextTypingProps> = ({
14
+ data = [],
15
+ timeDelayEnd = 2000,
16
+ timeDelayNextChar = 20,
17
+ inputStyle = {},
18
+ }) => {
19
+ const textRef = useRef<TextInput>(null);
20
+
21
+ const currentIndex = useRef<number>(0);
22
+ const currentCharIndex = useRef<number>(0);
23
+ const currentText = useRef('');
24
+
25
+ useEffect(() => {
26
+ setTimeout(() => {
27
+ playAnimation();
28
+ }, 1000);
29
+ }, []);
30
+
31
+ const playAnimation = () => {
32
+ const handleResetList = () => {
33
+ currentIndex.current = 0;
34
+ handleAnimation();
35
+ };
36
+
37
+ const handleAnimation = () => {
38
+ const listChar = data?.[currentIndex.current]?.split('') || [];
39
+ listChar.length !== 0 && showChar(listChar?.[currentCharIndex.current]);
40
+ };
41
+
42
+ currentIndex.current >= data?.length
43
+ ? handleResetList()
44
+ : handleAnimation();
45
+ };
46
+
47
+ const showChar = (char?: string) => {
48
+ const handleResetString = () => {
49
+ setTimeout(() => {
50
+ reset();
51
+ }, timeDelayEnd);
52
+ };
53
+
54
+ const handleShowChar = () => {
55
+ setTimeout(() => {
56
+ textRef.current?.setNativeProps?.({
57
+ text: currentText.current + char,
58
+ });
59
+ currentText.current += char;
60
+ currentCharIndex.current++;
61
+ playAnimation();
62
+ }, timeDelayNextChar);
63
+ };
64
+
65
+ currentCharIndex.current >= (data?.[currentIndex.current]?.length || 0)
66
+ ? handleResetString()
67
+ : handleShowChar();
68
+ };
69
+
70
+ const reset = () => {
71
+ const handleNextString = () => {
72
+ currentIndex.current++;
73
+ playAnimation();
74
+ };
75
+
76
+ const handleReset = () => {
77
+ setTimeout(() => {
78
+ textRef.current?.setNativeProps?.({
79
+ text: currentText.current.slice(0, -1),
80
+ });
81
+ currentText.current = currentText.current.slice(0, -1);
82
+ currentCharIndex.current--;
83
+ reset();
84
+ }, timeDelayNextChar);
85
+ };
86
+
87
+ currentCharIndex.current <= 0 ? handleNextString() : handleReset();
88
+ };
89
+
90
+ return (
91
+ <TextInput
92
+ style={[styles.inputStyle, inputStyle]}
93
+ ref={textRef}
94
+ editable={false}
95
+ autoCorrect={false}
96
+ pointerEvents={'none'}
97
+ numberOfLines={1}
98
+ />
99
+ );
100
+ };
101
+
102
+ export default memo(TextTyping);
103
+
104
+ const styles = StyleSheet.create({
105
+ inputStyle: {
106
+ color: Colors.black_12,
107
+ fontSize: scaleSize(12),
108
+ height: Platform.select({
109
+ android: 60,
110
+ ios: 36,
111
+ }),
112
+ },
113
+ });
package/Input/index.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import {TextInputProps, ViewStyle} from 'react-native';
1
+ import {GestureResponderEvent, TextInputProps, ViewStyle} from 'react-native';
2
2
  import Input from './Input';
3
3
  import InputDropDown from './InputDropDown';
4
4
  import InputMoney from './InputMoney';
@@ -143,6 +143,17 @@ export interface InputSearchProps extends TextInputProps {
143
143
  * Optional. Represents the style of the InputSearch component.
144
144
  */
145
145
  params?: any;
146
+
147
+ /**
148
+ * Optional. Represents the data for typing animation.
149
+ */
150
+ typingData?: string[];
151
+
152
+ /**
153
+ * Optional. Represents the callback function to be called when the search button is pressed.
154
+ * @param e
155
+ */
156
+ onPress?: (e: GestureResponderEvent) => void;
146
157
  }
147
158
 
148
159
  export interface InputMoneyProps extends Omit<InputProps, 'placeholder'> {}
@@ -282,4 +293,11 @@ export interface InputDropDownProps extends InputProps {
282
293
  style?: ViewStyle | ViewStyle[];
283
294
  }
284
295
 
296
+ export type InputRef = {
297
+ clear: () => void;
298
+ focus: () => void | undefined;
299
+ blur: () => void | undefined;
300
+ setText: (text: string) => void;
301
+ };
302
+
285
303
  export {Input, InputDropDown, InputMoney, InputOTP, InputSearch, InputTextArea};
package/Input/styles.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {StyleSheet} from 'react-native';
2
- import {Radius, Spacing} from '../Consts';
1
+ import {Platform, StyleSheet} from 'react-native';
2
+ import {Colors, Radius, Spacing} from '../Consts';
3
3
  import {scaleSize} from '../Text';
4
4
 
5
5
  export default StyleSheet.create({
@@ -133,7 +133,14 @@ export default StyleSheet.create({
133
133
  paddingVertical: Spacing.S,
134
134
  fontWeight: 'bold',
135
135
  },
136
-
136
+ inputStyle: {
137
+ color: Colors.black_12,
138
+ fontSize: scaleSize(12),
139
+ height: Platform.select({
140
+ android: 60,
141
+ ios: 36,
142
+ }),
143
+ },
137
144
  //OTP
138
145
  otpWrapper: {
139
146
  width: '100%',
package/Layout/Screen.tsx CHANGED
@@ -27,10 +27,11 @@ import Navigation from '../Application/Navigation';
27
27
  import {AnimatedHeader, NavigationOptions} from '../Application/types';
28
28
  import {Colors, Spacing, Styles} from '../Consts';
29
29
  import {FloatingButton, FloatingButtonProps} from './FloatingButton';
30
- import {Image} from '../Image';
31
30
  import {Card, Section, validateChildren} from './index';
32
31
  import styles from './styles';
33
32
  import {HeaderType} from './types';
33
+ import {InputSearchProps} from '../Input';
34
+ import {HeaderExtendHeader} from '../Application/Components';
34
35
 
35
36
  export interface ScreenProps extends ViewProps {
36
37
  /**
@@ -101,6 +102,11 @@ export interface ScreenProps extends ViewProps {
101
102
  * Optional. If `true`, the screen content is using grid layout by span.
102
103
  */
103
104
  useGridLayout?: boolean;
105
+
106
+ /**
107
+ * Optional. specs for input search.
108
+ */
109
+ inputSearchProps?: InputSearchProps;
104
110
  }
105
111
 
106
112
  const Screen = forwardRef(
@@ -120,32 +126,132 @@ const Screen = forwardRef(
120
126
  floatingButtonProps,
121
127
  useGridLayout = true,
122
128
  keyboardVerticalOffset,
129
+ inputSearchProps,
123
130
  }: ScreenProps,
124
- ref,
131
+ ref
125
132
  ) => {
126
- const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
127
133
  const {theme} = useContext(ApplicationContext);
128
134
  const insets = useSafeAreaInsets();
129
135
  const heightHeader = useHeaderHeight();
136
+ const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
137
+ const scrollViewRef = useRef<any>(ref);
130
138
  const currentTint = useRef(Colors.black_01);
131
- const isTab = navigation?.instance?.getState?.()?.type == 'tab';
139
+ const isTab = navigation?.instance?.getState?.()?.type === 'tab';
132
140
 
133
- let styleAnimatedHeader: NavigationOptions;
134
141
  let handleScroll;
135
- let headerBackground: string | undefined = undefined;
136
142
  let Component: any = View;
137
143
  let keyboardOffset = heightHeader - 20;
138
- if (headerType == 'extended' || animatedHeader) {
144
+ if (headerType === 'extended' || animatedHeader) {
139
145
  keyboardOffset = -20;
140
146
  }
141
147
 
142
- if (headerType == 'extended') {
143
- headerBackground = theme.assets?.headerBackground;
144
- }
145
-
146
148
  useEffect(() => {
149
+ let options: NavigationOptions;
150
+ /**
151
+ * handle for basic header type
152
+ */
153
+ switch (headerType) {
154
+ case 'none':
155
+ options = {
156
+ headerTransparent: true,
157
+ headerBackground: () => null,
158
+ headerShown: false,
159
+ };
160
+ break;
161
+
162
+ case 'surface':
163
+ options = {
164
+ headerTransparent: false,
165
+ headerShown: true,
166
+ headerTintColor: theme.colors.text.default,
167
+ headerBackground: (props: any) => (
168
+ <HeaderBackground {...props} image={null} useSurface={true} />
169
+ ),
170
+ headerTitle: (props: any) => <HeaderTitle {...props} />,
171
+ };
172
+ if (inputSearchProps) {
173
+ options = {
174
+ headerShown: true,
175
+ headerTransparent: true,
176
+ headerTintColor: theme.colors.text.default,
177
+ headerBackground: () => null,
178
+ headerTitle: (props: any) => (
179
+ <HeaderTitle
180
+ {...props}
181
+ interpolate={{
182
+ inputRange: [0, 50],
183
+ outputRange: [1, 0],
184
+ extrapolate: 'clamp',
185
+ }}
186
+ animatedValue={animatedValue.current}
187
+ />
188
+ ),
189
+ };
190
+ }
191
+ break;
192
+
193
+ case 'extended':
194
+ options = {
195
+ headerShown: true,
196
+ headerTransparent: true,
197
+ headerTintColor: Colors.black_01,
198
+ headerBackground: () => null,
199
+ headerTitle: (props: any) => <HeaderTitle {...props} />,
200
+ };
201
+ if (inputSearchProps) {
202
+ options = {
203
+ headerShown: true,
204
+ headerTransparent: true,
205
+ headerTintColor: Colors.black_01,
206
+ headerBackground: () => null,
207
+ headerTitle: (props: any) => (
208
+ <HeaderTitle
209
+ {...props}
210
+ interpolate={{
211
+ inputRange: [0, 50],
212
+ outputRange: [1, 0],
213
+ extrapolate: 'clamp',
214
+ }}
215
+ animatedValue={animatedValue.current}
216
+ />
217
+ ),
218
+ };
219
+ }
220
+ break;
221
+
222
+ default:
223
+ options = {
224
+ headerTransparent: false,
225
+ headerShown: true,
226
+ headerTintColor: Colors.black_01,
227
+ headerBackground: (props: any) => <HeaderBackground {...props} />,
228
+ };
229
+ if (inputSearchProps) {
230
+ options = {
231
+ headerShown: true,
232
+ headerTransparent: true,
233
+ headerTintColor: Colors.black_01,
234
+ headerBackground: () => null,
235
+ headerTitle: (props: any) => (
236
+ <HeaderTitle
237
+ {...props}
238
+ interpolate={{
239
+ inputRange: [0, 50],
240
+ outputRange: [1, 0],
241
+ extrapolate: 'clamp',
242
+ }}
243
+ animatedValue={animatedValue.current}
244
+ />
245
+ ),
246
+ };
247
+ }
248
+ }
249
+
250
+ /**
251
+ * override options for animated header
252
+ */
147
253
  if (animatedHeader) {
148
- styleAnimatedHeader = {
254
+ options = {
149
255
  headerTransparent: true,
150
256
  headerBackground: (props: any) => (
151
257
  <HeaderBackground
@@ -157,9 +263,9 @@ const Screen = forwardRef(
157
263
  <HeaderTitle {...props} animatedValue={animatedValue.current} />
158
264
  ),
159
265
  };
160
- if (animatedHeader.type == 'surface') {
161
- styleAnimatedHeader = {
162
- ...styleAnimatedHeader,
266
+ if (animatedHeader.type === 'surface') {
267
+ options = {
268
+ ...options,
163
269
  headerBackground: (props: any) => (
164
270
  <HeaderBackground
165
271
  {...props}
@@ -171,8 +277,8 @@ const Screen = forwardRef(
171
277
  };
172
278
  }
173
279
  if (animatedHeader.headerTitle) {
174
- styleAnimatedHeader = {
175
- ...styleAnimatedHeader,
280
+ options = {
281
+ ...options,
176
282
  headerTitle: (props: any) =>
177
283
  animatedHeader.headerTitle?.({
178
284
  ...props,
@@ -180,55 +286,10 @@ const Screen = forwardRef(
180
286
  }),
181
287
  };
182
288
  }
183
- navigation?.setOptions(styleAnimatedHeader);
184
289
  }
185
- }, [animatedHeader]);
186
-
187
- useEffect(() => {
188
- if (!animatedHeader) {
189
- let headerOptions: NavigationOptions;
190
- switch (headerType) {
191
- case 'none':
192
- headerOptions = {
193
- headerTransparent: true,
194
- headerBackground: () => null,
195
- headerShown: false,
196
- };
197
- break;
198
290
 
199
- case 'surface':
200
- headerOptions = {
201
- headerTransparent: false,
202
- headerShown: true,
203
- headerTintColor: theme.colors.text.default,
204
- headerBackground: (props: any) => (
205
- <HeaderBackground {...props} image={null} useSurface={true} />
206
- ),
207
- headerTitle: (props: any) => <HeaderTitle {...props} />,
208
- };
209
- break;
210
-
211
- case 'extended':
212
- headerOptions = {
213
- headerShown: true,
214
- headerTransparent: true,
215
- headerTintColor: Colors.black_01,
216
- headerBackground: () => null,
217
- headerTitle: (props: any) => <HeaderTitle {...props} />,
218
- };
219
- break;
220
-
221
- default:
222
- headerOptions = {
223
- headerTransparent: false,
224
- headerShown: true,
225
- headerTintColor: Colors.black_01,
226
- headerBackground: (props: any) => <HeaderBackground {...props} />,
227
- };
228
- }
229
- navigation?.setOptions(headerOptions);
230
- }
231
- }, [headerType]);
291
+ navigation?.setOptions(options);
292
+ }, [headerType, animatedHeader]);
232
293
 
233
294
  /**
234
295
  * animated when use scroll && animated value
@@ -236,7 +297,7 @@ const Screen = forwardRef(
236
297
  if (scrollable) {
237
298
  Component = Animated.ScrollView;
238
299
  handleScroll = scrollViewProps?.onScroll;
239
- if (animatedHeader || floatingButtonProps?.icon) {
300
+ if (animatedHeader || floatingButtonProps?.icon || inputSearchProps) {
240
301
  handleScroll = Animated.event(
241
302
  [
242
303
  {
@@ -249,13 +310,13 @@ const Screen = forwardRef(
249
310
  useNativeDriver: true,
250
311
  listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
251
312
  scrollViewProps?.onScroll?.(e);
252
- if (animatedHeader?.type == 'surface') {
313
+ if (animatedHeader?.type === 'surface') {
253
314
  const offsetY = e.nativeEvent.contentOffset.y;
254
315
  let color = Colors.black_01;
255
316
  if (offsetY > 50) {
256
317
  color = theme.colors.text.default;
257
318
  }
258
- if (color != currentTint.current) {
319
+ if (color !== currentTint.current) {
259
320
  currentTint.current = color;
260
321
  navigation?.setOptions({
261
322
  headerTintColor: color,
@@ -263,31 +324,52 @@ const Screen = forwardRef(
263
324
  }
264
325
  }
265
326
  },
266
- },
327
+ }
267
328
  );
268
329
  }
269
330
  }
270
331
 
332
+ /**
333
+ * handle scroll end
334
+ * @param e
335
+ */
336
+ const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
337
+ const offsetY = e.nativeEvent.contentOffset.y;
338
+ if (inputSearchProps && offsetY < 100 && offsetY > 0) {
339
+ Animated.timing(animatedValue.current, {
340
+ toValue: 0,
341
+ useNativeDriver: true,
342
+ duration: 300,
343
+ }).start();
344
+ scrollViewRef.current?.scrollTo({y: 0, animated: true});
345
+ }
346
+ scrollViewProps?.onScrollEndDrag?.(e);
347
+ };
348
+
271
349
  /**
272
350
  * render top navigation banner
273
351
  */
274
- const renderHeader = () => {
352
+ const renderAnimatedHeader = () => {
275
353
  if (typeof animatedHeader?.component === 'function') {
276
- return animatedHeader?.component({
277
- animatedValue: animatedValue.current,
278
- });
354
+ return (
355
+ <View style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
356
+ {animatedHeader?.component({
357
+ animatedValue: animatedValue.current,
358
+ })}
359
+ </View>
360
+ );
279
361
  }
280
362
  };
281
363
 
282
364
  /**
283
- * build content for screen
365
+ * build content for screen for grid rule
284
366
  */
285
367
  const renderContent = (children: any): any => {
286
368
  const results = validateChildren(children, [Section, Card, undefined]);
287
369
  if (Array.isArray(results)) {
288
370
  return results.map((item, index) => {
289
371
  const space = item?.props?.useMargin === false ? 0 : Spacing.M;
290
- if (item?.type == Fragment) {
372
+ if (item?.type === Fragment) {
291
373
  return renderContent(item?.props?.children);
292
374
  }
293
375
  if (item) {
@@ -307,7 +389,7 @@ const Screen = forwardRef(
307
389
  } else {
308
390
  const item: any = children;
309
391
  const space = item?.props?.useMargin === false ? 0 : Spacing.M;
310
- if (item?.type == Fragment) {
392
+ if (item?.type === Fragment) {
311
393
  return renderContent(item?.props?.children);
312
394
  }
313
395
  return (
@@ -326,17 +408,14 @@ const Screen = forwardRef(
326
408
 
327
409
  return (
328
410
  <View style={[Styles.flex, {backgroundColor}]}>
329
- {!!headerBackground && (
330
- <>
331
- <Image
332
- source={{
333
- uri: headerBackground,
334
- }}
335
- style={styles.extendedHeader}
336
- />
337
- <View style={{height: heightHeader}} />
338
- </>
339
- )}
411
+ <HeaderExtendHeader
412
+ headerType={headerType}
413
+ heightHeader={heightHeader}
414
+ animatedValue={animatedValue.current}
415
+ inputSearchProps={inputSearchProps}
416
+ navigation={navigation}
417
+ />
418
+
340
419
  <KeyboardAvoidingView
341
420
  style={Styles.flex}
342
421
  enabled={enableKeyboardAvoidingView}
@@ -349,14 +428,14 @@ const Screen = forwardRef(
349
428
 
350
429
  <Component
351
430
  {...scrollViewProps}
352
- ref={ref}
431
+ ref={scrollViewRef}
353
432
  showsVerticalScrollIndicator={false}
354
433
  onScroll={handleScroll}
434
+ onScrollEndDrag={handleScrollEnd}
435
+ scrollEventThrottle={16}
355
436
  style={Styles.flex}>
356
- <View
357
- style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
358
- {renderHeader()}
359
- </View>
437
+ {renderAnimatedHeader()}
438
+
360
439
  {useGridLayout ? renderContent(children) : children}
361
440
  </Component>
362
441
 
@@ -387,7 +466,7 @@ const Screen = forwardRef(
387
466
  </KeyboardAvoidingView>
388
467
  </View>
389
468
  );
390
- },
469
+ }
391
470
  );
392
471
 
393
472
  export default Screen;
package/Layout/styles.ts CHANGED
@@ -2,12 +2,6 @@ import {Platform, StyleSheet} from 'react-native';
2
2
  import {Colors, Radius, Spacing} from '../Consts';
3
3
 
4
4
  export default StyleSheet.create({
5
- extendedHeader: {
6
- aspectRatio: 1.75,
7
- position: 'absolute',
8
- width: '100%',
9
- height: 210,
10
- },
11
5
  screenBanner: {
12
6
  width: '100%',
13
7
  },
package/Layout/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type HeaderType = 'default' | 'surface' | 'extended' | 'none';
1
+ export type HeaderType = 'default' | 'surface' | 'extended' | 'search' | 'none';
2
2
 
3
3
  export type GridContextProps = {
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.92.26-beta.2",
3
+ "version": "0.92.26-beta.21",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},