@momo-kits/foundation 0.92.21 → 0.92.23

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,66 @@
1
+ import React, {FC, useEffect, useRef, useState} from 'react';
2
+ import {InputSearch, InputSearchProps} from '../Input';
3
+ import {Animated, Dimensions} from 'react-native';
4
+ import {Spacing} from '../Consts';
5
+ import {scaleSize} from '../Text';
6
+ import Navigation from '../Application/Navigation';
7
+
8
+ export interface AnimatedInputSearchProps extends InputSearchProps {
9
+ animatedValue: Animated.Value;
10
+ navigation?: Navigation;
11
+ }
12
+
13
+ const SCREEN_PADDING = 12;
14
+ const BACK_WIDTH = scaleSize(28);
15
+
16
+ const AnimatedInputSearch: FC<AnimatedInputSearchProps> = ({
17
+ animatedValue,
18
+ navigation,
19
+ ...props
20
+ }) => {
21
+ const {width: screenWidth} = Dimensions.get('window');
22
+ const widthAnimated = useRef(new Animated.Value(0));
23
+ const [rightSpace, setRightSpace] = useState(
24
+ navigation?.rightHeaderWidth ?? 0
25
+ );
26
+
27
+ useEffect(() => {
28
+ setTimeout(() => {
29
+ if (rightSpace != navigation?.rightHeaderWidth) {
30
+ setRightSpace(navigation?.rightHeaderWidth ?? 0);
31
+ }
32
+ }, 500);
33
+ const listener = animatedValue.addListener(({value}) => {
34
+ widthAnimated.current.setValue(value);
35
+ });
36
+ return () => {
37
+ animatedValue?.removeListener(listener);
38
+ };
39
+ }, []);
40
+
41
+ const translateX = widthAnimated.current.interpolate({
42
+ inputRange: [0, 100],
43
+ outputRange: [SCREEN_PADDING, BACK_WIDTH + SCREEN_PADDING * 2],
44
+ extrapolate: 'clamp',
45
+ });
46
+
47
+ return (
48
+ <Animated.View
49
+ style={{
50
+ transform: [{translateX}],
51
+ marginVertical: Spacing.S,
52
+ width: widthAnimated.current.interpolate({
53
+ inputRange: [0, 100],
54
+ outputRange: [
55
+ screenWidth - SCREEN_PADDING * 2,
56
+ screenWidth - SCREEN_PADDING * 3 - BACK_WIDTH - rightSpace,
57
+ ],
58
+ extrapolate: 'clamp',
59
+ }),
60
+ }}>
61
+ <InputSearch {...props} showButtonText={false} />
62
+ </Animated.View>
63
+ );
64
+ };
65
+
66
+ export default AnimatedInputSearch;
@@ -85,7 +85,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
85
85
 
86
86
  return () => {
87
87
  if (listener !== null) {
88
- animatedValue?.removeListener(listener);
88
+ animatedValue?.removeListener(listener!);
89
89
  }
90
90
  };
91
91
  }, [label, widthAnimated]);
package/Layout/Screen.tsx CHANGED
@@ -27,10 +27,12 @@ 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 AnimatedInputSearch from './AnimatedInputSearch';
34
+ import {InputSearchProps} from '../Input';
35
+ import {HeaderExtendBackground} from '../Application/Components';
34
36
 
35
37
  export interface ScreenProps extends ViewProps {
36
38
  /**
@@ -101,6 +103,11 @@ export interface ScreenProps extends ViewProps {
101
103
  * Optional. If `true`, the screen content is using grid layout by span.
102
104
  */
103
105
  useGridLayout?: boolean;
106
+
107
+ /**
108
+ * Optional. specs for input search.
109
+ */
110
+ inputSearchProps?: InputSearchProps;
104
111
  }
105
112
 
106
113
  const Screen = forwardRef(
@@ -120,32 +127,79 @@ const Screen = forwardRef(
120
127
  floatingButtonProps,
121
128
  useGridLayout = true,
122
129
  keyboardVerticalOffset,
130
+ inputSearchProps,
123
131
  }: ScreenProps,
124
- ref,
132
+ ref
125
133
  ) => {
126
- const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
127
134
  const {theme} = useContext(ApplicationContext);
128
135
  const insets = useSafeAreaInsets();
129
136
  const heightHeader = useHeaderHeight();
137
+ const animatedValue = useRef<Animated.Value>(new Animated.Value(0));
138
+ const scrollViewRef = useRef<any>(ref);
130
139
  const currentTint = useRef(Colors.black_01);
131
- const isTab = navigation?.instance?.getState?.()?.type == 'tab';
140
+ const isTab = navigation?.instance?.getState?.()?.type === 'tab';
132
141
 
133
- let styleAnimatedHeader: NavigationOptions;
134
142
  let handleScroll;
135
143
  let headerBackground: string | undefined = undefined;
136
144
  let Component: any = View;
137
145
  let keyboardOffset = heightHeader - 20;
138
- if (headerType == 'extended' || animatedHeader) {
146
+ if (headerType === 'extended' || animatedHeader) {
139
147
  keyboardOffset = -20;
140
148
  }
141
-
142
- if (headerType == 'extended') {
149
+ if (headerType === 'extended') {
143
150
  headerBackground = theme.assets?.headerBackground;
144
151
  }
145
152
 
146
153
  useEffect(() => {
154
+ let options: NavigationOptions;
155
+ /**
156
+ * handle for basic header type
157
+ */
158
+ switch (headerType) {
159
+ case 'none':
160
+ options = {
161
+ headerTransparent: true,
162
+ headerBackground: () => null,
163
+ headerShown: false,
164
+ };
165
+ break;
166
+
167
+ case 'surface':
168
+ options = {
169
+ headerTransparent: false,
170
+ headerShown: true,
171
+ headerTintColor: theme.colors.text.default,
172
+ headerBackground: (props: any) => (
173
+ <HeaderBackground {...props} image={null} useSurface={true} />
174
+ ),
175
+ headerTitle: (props: any) => <HeaderTitle {...props} />,
176
+ };
177
+ break;
178
+
179
+ case 'extended':
180
+ options = {
181
+ headerShown: true,
182
+ headerTransparent: true,
183
+ headerTintColor: Colors.black_01,
184
+ headerBackground: () => null,
185
+ headerTitle: (props: any) => <HeaderTitle {...props} />,
186
+ };
187
+ break;
188
+
189
+ default:
190
+ options = {
191
+ headerTransparent: false,
192
+ headerShown: true,
193
+ headerTintColor: Colors.black_01,
194
+ headerBackground: (props: any) => <HeaderBackground {...props} />,
195
+ };
196
+ }
197
+
198
+ /**
199
+ * override options for animated header
200
+ */
147
201
  if (animatedHeader) {
148
- styleAnimatedHeader = {
202
+ options = {
149
203
  headerTransparent: true,
150
204
  headerBackground: (props: any) => (
151
205
  <HeaderBackground
@@ -157,9 +211,9 @@ const Screen = forwardRef(
157
211
  <HeaderTitle {...props} animatedValue={animatedValue.current} />
158
212
  ),
159
213
  };
160
- if (animatedHeader.type == 'surface') {
161
- styleAnimatedHeader = {
162
- ...styleAnimatedHeader,
214
+ if (animatedHeader.type === 'surface') {
215
+ options = {
216
+ ...options,
163
217
  headerBackground: (props: any) => (
164
218
  <HeaderBackground
165
219
  {...props}
@@ -171,8 +225,8 @@ const Screen = forwardRef(
171
225
  };
172
226
  }
173
227
  if (animatedHeader.headerTitle) {
174
- styleAnimatedHeader = {
175
- ...styleAnimatedHeader,
228
+ options = {
229
+ ...options,
176
230
  headerTitle: (props: any) =>
177
231
  animatedHeader.headerTitle?.({
178
232
  ...props,
@@ -180,55 +234,10 @@ const Screen = forwardRef(
180
234
  }),
181
235
  };
182
236
  }
183
- navigation?.setOptions(styleAnimatedHeader);
184
237
  }
185
- }, [animatedHeader]);
186
238
 
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
-
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]);
239
+ navigation?.setOptions(options);
240
+ }, [headerType, animatedHeader]);
232
241
 
233
242
  /**
234
243
  * animated when use scroll && animated value
@@ -236,7 +245,7 @@ const Screen = forwardRef(
236
245
  if (scrollable) {
237
246
  Component = Animated.ScrollView;
238
247
  handleScroll = scrollViewProps?.onScroll;
239
- if (animatedHeader || floatingButtonProps?.icon) {
248
+ if (animatedHeader || floatingButtonProps?.icon || inputSearchProps) {
240
249
  handleScroll = Animated.event(
241
250
  [
242
251
  {
@@ -249,13 +258,13 @@ const Screen = forwardRef(
249
258
  useNativeDriver: true,
250
259
  listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
251
260
  scrollViewProps?.onScroll?.(e);
252
- if (animatedHeader?.type == 'surface') {
261
+ if (animatedHeader?.type === 'surface') {
253
262
  const offsetY = e.nativeEvent.contentOffset.y;
254
263
  let color = Colors.black_01;
255
264
  if (offsetY > 50) {
256
265
  color = theme.colors.text.default;
257
266
  }
258
- if (color != currentTint.current) {
267
+ if (color !== currentTint.current) {
259
268
  currentTint.current = color;
260
269
  navigation?.setOptions({
261
270
  headerTintColor: color,
@@ -263,31 +272,52 @@ const Screen = forwardRef(
263
272
  }
264
273
  }
265
274
  },
266
- },
275
+ }
267
276
  );
268
277
  }
269
278
  }
270
279
 
280
+ /**
281
+ * handle scroll end
282
+ * @param e
283
+ */
284
+ const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
285
+ const offsetY = e.nativeEvent.contentOffset.y;
286
+ if (inputSearchProps && offsetY < 100 && offsetY > 0) {
287
+ Animated.timing(animatedValue.current, {
288
+ toValue: 0,
289
+ useNativeDriver: true,
290
+ duration: 300,
291
+ }).start();
292
+ scrollViewRef.current?.scrollTo({y: 0, animated: true});
293
+ }
294
+ scrollViewProps?.onScrollEndDrag?.(e);
295
+ };
296
+
271
297
  /**
272
298
  * render top navigation banner
273
299
  */
274
- const renderHeader = () => {
300
+ const renderAnimatedHeader = () => {
275
301
  if (typeof animatedHeader?.component === 'function') {
276
- return animatedHeader?.component({
277
- animatedValue: animatedValue.current,
278
- });
302
+ return (
303
+ <View style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
304
+ {animatedHeader?.component({
305
+ animatedValue: animatedValue.current,
306
+ })}
307
+ </View>
308
+ );
279
309
  }
280
310
  };
281
311
 
282
312
  /**
283
- * build content for screen
313
+ * build content for screen for grid rule
284
314
  */
285
315
  const renderContent = (children: any): any => {
286
316
  const results = validateChildren(children, [Section, Card, undefined]);
287
317
  if (Array.isArray(results)) {
288
318
  return results.map((item, index) => {
289
319
  const space = item?.props?.useMargin === false ? 0 : Spacing.M;
290
- if (item?.type == Fragment) {
320
+ if (item?.type === Fragment) {
291
321
  return renderContent(item?.props?.children);
292
322
  }
293
323
  if (item) {
@@ -307,7 +337,7 @@ const Screen = forwardRef(
307
337
  } else {
308
338
  const item: any = children;
309
339
  const space = item?.props?.useMargin === false ? 0 : Spacing.M;
310
- if (item?.type == Fragment) {
340
+ if (item?.type === Fragment) {
311
341
  return renderContent(item?.props?.children);
312
342
  }
313
343
  return (
@@ -327,16 +357,21 @@ const Screen = forwardRef(
327
357
  return (
328
358
  <View style={[Styles.flex, {backgroundColor}]}>
329
359
  {!!headerBackground && (
330
- <>
331
- <Image
332
- source={{
333
- uri: headerBackground,
334
- }}
335
- style={styles.extendedHeader}
336
- />
337
- <View style={{height: heightHeader}} />
338
- </>
360
+ <HeaderExtendBackground
361
+ headerBackground={headerBackground}
362
+ heightHeader={heightHeader}
363
+ animatedValue={animatedValue.current}
364
+ />
365
+ )}
366
+
367
+ {inputSearchProps && (
368
+ <AnimatedInputSearch
369
+ animatedValue={animatedValue.current}
370
+ navigation={navigation}
371
+ {...inputSearchProps}
372
+ />
339
373
  )}
374
+
340
375
  <KeyboardAvoidingView
341
376
  style={Styles.flex}
342
377
  enabled={enableKeyboardAvoidingView}
@@ -349,14 +384,14 @@ const Screen = forwardRef(
349
384
 
350
385
  <Component
351
386
  {...scrollViewProps}
352
- ref={ref}
387
+ ref={scrollViewRef}
353
388
  showsVerticalScrollIndicator={false}
354
389
  onScroll={handleScroll}
390
+ onScrollEndDrag={handleScrollEnd}
391
+ scrollEventThrottle={16}
355
392
  style={Styles.flex}>
356
- <View
357
- style={[styles.screenBanner, {maxHeight: 210 + layoutOffset}]}>
358
- {renderHeader()}
359
- </View>
393
+ {renderAnimatedHeader()}
394
+
360
395
  {useGridLayout ? renderContent(children) : children}
361
396
  </Component>
362
397
 
@@ -387,7 +422,7 @@ const Screen = forwardRef(
387
422
  </KeyboardAvoidingView>
388
423
  </View>
389
424
  );
390
- },
425
+ }
391
426
  );
392
427
 
393
428
  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,8 @@
1
- export type HeaderType = 'default' | 'surface' | 'extended' | 'none';
1
+ import {Animated} from 'react-native';
2
+ import {InputSearchProps, InputSearchRef} from '../Input';
3
+ import {LegacyRef} from 'react';
4
+
5
+ export type HeaderType = 'default' | 'surface' | 'extended' | 'search' | 'none';
2
6
 
3
7
  export type GridContextProps = {
4
8
  /**
@@ -21,3 +25,18 @@ export type GridContextProps = {
21
25
  */
22
26
  getSizeSpan: (span: number) => number;
23
27
  };
28
+
29
+ export type AnimatedInputSearchProps = {
30
+ animatedValue: Animated.Value;
31
+ headerRightWidth: number;
32
+ heightHeader: number;
33
+ headerType: HeaderType;
34
+ inputSearchProps?: InputSearchProps;
35
+ inputRef?: LegacyRef<InputSearchRef>;
36
+ };
37
+
38
+ export type ExtendHeaderProps = {
39
+ headerBackground: string;
40
+ animatedValue: Animated.Value;
41
+ heightHeader: number;
42
+ };
package/Title/index.tsx CHANGED
@@ -1,13 +1,16 @@
1
1
  import React, {FC, useContext, useState} from 'react';
2
2
  import {Text as RNText, TouchableOpacity, View, ViewStyle} from 'react-native';
3
- import {Icon} from '../Icon';
4
- import {scaleSize, Text} from '../Text';
5
- import {ApplicationContext} from '../Application';
6
- import {Badge} from '../Badge';
7
- import {Colors} from '../Consts';
3
+ import {
4
+ Typography,
5
+ Colors,
6
+ Icon,
7
+ Badge,
8
+ ApplicationContext,
9
+ scaleSize,
10
+ Text,
11
+ } from '@momo-kits/foundation';
8
12
  import styles from './styles';
9
13
  import {TitleProps} from './types';
10
- import {Typography} from '../Text/types';
11
14
 
12
15
  const Title: FC<TitleProps> = ({
13
16
  title = 'Title',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.92.21",
3
+ "version": "0.92.23",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},