@momo-kits/foundation 0.80.8 → 0.81.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/Application/BottomSheet.tsx +212 -0
  2. package/Application/BottomTab/BottomTabBar.tsx +278 -0
  3. package/Application/BottomTab/index.tsx +143 -0
  4. package/Application/Components.tsx +384 -213
  5. package/Application/ModalScreen.tsx +14 -178
  6. package/Application/Navigation.ts +15 -19
  7. package/Application/NavigationContainer.tsx +66 -63
  8. package/Application/Navigator.ts +106 -53
  9. package/Application/StackScreen.tsx +123 -9
  10. package/Application/index.ts +19 -6
  11. package/Application/types.ts +83 -26
  12. package/Application/utils.tsx +62 -74
  13. package/Assets/icon.json +83 -5
  14. package/Assets/icon_bank.json +506 -0
  15. package/Assets/language.json +3 -1
  16. package/Badge/Badge.tsx +56 -0
  17. package/Badge/BadgeDot.tsx +11 -0
  18. package/Badge/BadgeRibbon.tsx +125 -0
  19. package/Badge/Shape.tsx +29 -0
  20. package/Badge/index.tsx +7 -0
  21. package/Badge/styles.ts +58 -0
  22. package/Badge/types.ts +63 -0
  23. package/Button/index.tsx +64 -28
  24. package/Button/styles.ts +19 -0
  25. package/CheckBox/index.tsx +37 -27
  26. package/CheckBox/types.ts +5 -3
  27. package/Consts/colors+spacing+radius.ts +18 -0
  28. package/Icon/index.tsx +14 -9
  29. package/Icon/types.ts +4 -3
  30. package/IconButton/index.tsx +24 -15
  31. package/Image/index.tsx +62 -47
  32. package/Image/styles.ts +1 -9
  33. package/Image/types.ts +5 -1
  34. package/Input/Input.tsx +104 -25
  35. package/Input/InputDropDown.tsx +115 -110
  36. package/Input/InputMoney.tsx +79 -18
  37. package/Input/InputOTP.tsx +190 -149
  38. package/Input/InputSearch.tsx +96 -44
  39. package/Input/InputTextArea.tsx +33 -17
  40. package/Input/common.tsx +26 -17
  41. package/Input/index.tsx +108 -41
  42. package/Input/styles.ts +5 -7
  43. package/Input/utils.ts +1 -1
  44. package/Layout/AnimatedInputSearch.tsx +44 -0
  45. package/Layout/Card.tsx +37 -13
  46. package/Layout/FloatingButton.tsx +168 -0
  47. package/Layout/GridSystem.tsx +2 -3
  48. package/Layout/Item.tsx +1 -2
  49. package/Layout/ItemList.tsx +55 -0
  50. package/Layout/ItemSectionList.tsx +39 -0
  51. package/Layout/Screen.tsx +404 -181
  52. package/Layout/Section.tsx +22 -22
  53. package/Layout/TextTyping.tsx +114 -0
  54. package/Layout/index.ts +12 -2
  55. package/Layout/styles.ts +46 -13
  56. package/Layout/types.ts +13 -5
  57. package/Layout/utils.ts +7 -12
  58. package/Loader/index.tsx +2 -0
  59. package/Pagination/PaginationDot.tsx +6 -1
  60. package/Pagination/PaginationNumber.tsx +7 -4
  61. package/Pagination/PaginationScroll.tsx +0 -1
  62. package/Pagination/PaginationWhiteDot.tsx +6 -1
  63. package/Pagination/styles.ts +3 -0
  64. package/Pagination/types.ts +2 -0
  65. package/Popup/PopupNotify.tsx +108 -40
  66. package/Popup/PopupPromotion.tsx +45 -10
  67. package/Popup/types.ts +13 -10
  68. package/Radio/index.tsx +35 -21
  69. package/Radio/types.ts +7 -5
  70. package/Skeleton/index.tsx +1 -1
  71. package/Switch/index.tsx +32 -19
  72. package/Switch/types.ts +5 -3
  73. package/Tag/index.tsx +2 -4
  74. package/Text/index.tsx +26 -6
  75. package/Text/styles.ts +38 -0
  76. package/Text/types.ts +25 -13
  77. package/Title/index.tsx +177 -64
  78. package/Title/styles.ts +48 -15
  79. package/Title/types.ts +16 -7
  80. package/code-scanner.js +125 -16
  81. package/index.ts +9 -0
  82. package/migrate_typo.js +56 -0
  83. package/package.json +8 -7
  84. package/publish.sh +21 -16
  85. package/tsconfig.json +1 -2
  86. package/verify.js +24 -6
  87. package/Application/BottomTab.tsx +0 -65
@@ -1,144 +1,112 @@
1
- import React, {useContext, useEffect, useState} from 'react';
1
+ import React, {
2
+ FC,
3
+ Fragment,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useState,
8
+ } from 'react';
2
9
  import {
3
- Alert,
4
10
  Animated,
11
+ BackHandler,
5
12
  DeviceEventEmitter,
6
- Platform,
7
13
  StatusBar,
8
14
  StyleSheet,
15
+ TouchableOpacity,
9
16
  View,
10
- TouchableOpacity as TouchableRN,
17
+ ViewStyle,
11
18
  } from 'react-native';
12
- import {TouchableOpacity as TouchableGesture} from 'react-native-gesture-handler';
13
- import {ApplicationContext} from './index';
19
+ import {ApplicationContext, MiniAppContext} from './index';
14
20
  import {Colors, Radius, Spacing, Styles} from '../Consts';
15
21
  import {Image} from '../Image';
16
22
  import {
23
+ HeaderAnimatedProps,
17
24
  HeaderBackgroundProps,
18
25
  HeaderBackProps,
19
- HeaderToolkitProps,
26
+ IconButton,
20
27
  NavigationButtonProps,
21
28
  TitleCustomProps,
22
29
  } from './types';
23
30
  import {scaleSize, Text} from '../Text';
24
31
  import {Icon} from '../Icon';
25
32
  import {PopupNotify} from '../Popup';
26
-
27
- const Touchable = Platform.OS === 'android' ? TouchableGesture : TouchableRN;
28
-
29
- const styles = StyleSheet.create({
30
- navigationButton: {
31
- height: 28,
32
- width: 28,
33
- borderRadius: 14,
34
- borderColor: '#00000066',
35
- justifyContent: 'center',
36
- alignItems: 'center',
37
- },
38
- headerBackground: {
39
- width: '100%',
40
- height: undefined,
41
- position: 'absolute',
42
- aspectRatio: 375 / 154,
43
- },
44
- headerTitleContainer: {
45
- alignItems: 'center',
46
- justifyContent: 'center',
47
- width: '100%',
48
- },
49
- circle: {width: 36, height: 36, borderRadius: 18},
50
- square: {width: 36, height: 36, borderRadius: Radius.XS},
51
- dotAvatar: {
52
- position: 'absolute',
53
- width: 12,
54
- height: 12,
55
- borderRadius: 6,
56
- bottom: 0,
57
- right: 0,
58
- borderWidth: 1,
59
- borderColor: Colors.black_01,
60
- },
61
- headerButton: {paddingHorizontal: 4},
62
- headerRightButton: {
63
- flexDirection: 'row',
64
- overflow: 'hidden',
65
- justifyContent: 'center',
66
- alignItems: 'center',
67
- paddingRight: Spacing.S,
68
- },
69
- toolkitContainer: {
70
- marginLeft: Spacing.S,
71
- padding: Spacing.XS,
72
- height: 28,
73
- borderRadius: 14,
74
- borderColor: '#00000066',
75
- justifyContent: 'center',
76
- alignItems: 'center',
77
- flexDirection: 'row',
78
- },
79
- divider: {
80
- width: 1,
81
- height: 12,
82
- marginHorizontal: 4,
83
- },
84
- headerLeft: {
85
- marginLeft: 12,
86
- },
87
- title: {
88
- fontSize: scaleSize(15),
89
- lineHeight: scaleSize(22),
90
- fontWeight: 'bold',
91
- },
92
- });
33
+ import {Badge, BadgeDot} from '../Badge';
93
34
 
94
35
  const NavigationButton: React.FC<NavigationButtonProps> = ({
95
36
  icon,
96
37
  tintColor,
97
38
  onPress,
98
- backgroundColor,
99
39
  useBorder = true,
100
- accessibilityLabel,
40
+ badgeType,
41
+ badgeValue,
42
+ accessibilityLabel = '',
101
43
  }) => {
102
44
  const {theme} = useContext(ApplicationContext);
103
- if (!backgroundColor && tintColor != Colors.black_01) {
104
- backgroundColor = theme.colors.background.surface;
45
+ let buttonStyle: ViewStyle = {backgroundColor: '#00000066'};
46
+ if (tintColor !== Colors.black_01) {
47
+ buttonStyle = {
48
+ backgroundColor: 'transparent',
49
+ borderColor: '#00000066',
50
+ borderWidth: useBorder ? 0.5 : 0,
51
+ };
105
52
  }
106
53
 
54
+ const isNumber = (label: any) => {
55
+ const numberRegex = /^\d+$/;
56
+ return numberRegex.test(String(label));
57
+ };
58
+
59
+ const renderBadge = useCallback(() => {
60
+ if (badgeType === 'dot') {
61
+ return <BadgeDot size="small" style={styles.badgeDot} />;
62
+ }
63
+
64
+ if (isNumber(badgeValue)) {
65
+ return <Badge label={badgeValue} style={styles.badge} />;
66
+ }
67
+ }, [badgeType, badgeValue]);
68
+
107
69
  return (
108
- <Touchable
109
- accessibilityLabel={`NavigationButton/${accessibilityLabel}`}
110
- style={[
111
- styles.navigationButton,
112
- {
113
- backgroundColor: backgroundColor ?? '#00000066',
114
- borderWidth: useBorder && backgroundColor ? 0.5 : 0,
115
- },
116
- ]}
117
- onPress={onPress}>
118
- <Icon
119
- source={icon}
120
- color={tintColor ?? theme.colors.text.default}
121
- size={20}
122
- />
123
- </Touchable>
70
+ <View style={{width: scaleSize(28)}}>
71
+ <TouchableOpacity
72
+ accessibilityLabel={accessibilityLabel}
73
+ style={[styles.navigationButton, buttonStyle]}
74
+ onPress={onPress}>
75
+ <Icon
76
+ source={icon}
77
+ color={tintColor ?? theme.colors.text.default}
78
+ size={20}
79
+ />
80
+ </TouchableOpacity>
81
+ {renderBadge()}
82
+ </View>
124
83
  );
125
84
  };
126
85
 
127
- const HeaderTitle: React.FC<any> = ({accessibilityLabel = '', ...props}) => {
86
+ const HeaderTitle: React.FC<any> = props => {
128
87
  const {theme} = useContext(ApplicationContext);
129
- const opacity = props.animatedValue?.interpolate({
88
+ const opacityDefault = props.animatedValue?.interpolate({
130
89
  inputRange: [0, 200],
131
90
  outputRange: [0, 1],
132
91
  extrapolate: 'clamp',
133
92
  });
93
+ const opacitySearch = props.animatedValue?.interpolate({
94
+ inputRange: [0, 100],
95
+ outputRange: [1, 0],
96
+ extrapolate: 'clamp',
97
+ });
134
98
 
135
99
  return (
136
100
  <Animated.Text
137
101
  {...props}
138
- accessibilityLabel={`HeaderTitle/${accessibilityLabel}${props.children}`}
102
+ accessibilityLabel={'title_navigation_header'}
139
103
  style={[
140
104
  styles.title,
141
- {fontFamily: `${theme.font}-Bold`, opacity, color: props.tintColor},
105
+ {
106
+ fontFamily: `${theme.font}-Bold`,
107
+ opacity: props.useSearchInput ? opacitySearch : opacityDefault,
108
+ color: props.tintColor,
109
+ },
142
110
  ]}
143
111
  />
144
112
  );
@@ -149,48 +117,76 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
149
117
  preventBack,
150
118
  onPressLeftHeader,
151
119
  useBorder,
152
- backgroundColor,
153
- accessibilityLabel = '',
154
120
  }) => {
121
+ const context = useContext<any>(MiniAppContext);
155
122
  const {navigator} = useContext(ApplicationContext);
156
- const goBack = () => {
157
- const canGoBack = navigator?.ref.current?.canGoBack();
158
- if (canGoBack) {
159
- if (preventBack) {
160
- navigator?.showModal({
161
- screen: () => (
162
- <PopupNotify
163
- {...preventBack}
164
- primary={{
165
- title: preventBack.primary.title,
166
- onPress: () => {
167
- preventBack?.primary.onPress();
168
- navigator?.ref.current?.goBack();
169
- },
170
- }}
171
- />
172
- ),
173
- });
123
+
124
+ useEffect(() => {
125
+ const backHandler = BackHandler.addEventListener(
126
+ 'hardwareBackPress',
127
+ goBackSafe
128
+ );
129
+
130
+ return () => backHandler.remove();
131
+ }, []);
132
+
133
+ const goBackSafe = () => {
134
+ const goBack = () => {
135
+ const canGoBack = navigator?.ref?.current?.canGoBack?.();
136
+ const currentRoute = navigator?.ref?.current?.getCurrentRoute?.();
137
+ const params = {
138
+ appId: context.appId,
139
+ code: context.code,
140
+ screen_name: currentRoute?.params?.screen?.name ?? currentRoute?.name,
141
+ trigger_id: '111154000000000000',
142
+ icon_name: 'back',
143
+ };
144
+ navigator?.maxApi?.showToastDebug?.({
145
+ appId: `auto - ${context.appId}`,
146
+ message: 'service_icon_clicked',
147
+ params,
148
+ });
149
+ navigator?.maxApi?.trackEvent?.('service_icon_clicked', params);
150
+ if (canGoBack) {
151
+ navigator?.ref?.current?.goBack?.();
152
+ } else if (navigator?.maxApi) {
153
+ navigator?.maxApi?.dismiss?.(navigator?.dismissData);
174
154
  } else {
175
- navigator?.ref.current?.goBack();
155
+ const currentTime = new Date().getTime();
156
+ const requestId = `navigationBackPress#${currentTime}`;
157
+ DeviceEventEmitter.emit('dismiss', {requestId});
176
158
  }
159
+ onPressLeftHeader?.();
160
+ };
161
+
162
+ if (preventBack) {
163
+ navigator?.showModal({
164
+ screen: () => (
165
+ <PopupNotify
166
+ {...preventBack}
167
+ primary={{
168
+ title: preventBack.primary.title,
169
+ onPress: () => {
170
+ preventBack?.primary.onPress();
171
+ goBack();
172
+ },
173
+ }}
174
+ />
175
+ ),
176
+ });
177
177
  } else {
178
- const currentTime = new Date().getTime();
179
- const requestId = `navigationBackPress#${currentTime}`;
180
- DeviceEventEmitter.emit('dismiss', {requestId});
178
+ goBack();
181
179
  }
182
- onPressLeftHeader?.();
180
+ return true;
183
181
  };
184
-
185
182
  return (
186
183
  <View style={styles.headerLeft}>
187
184
  <NavigationButton
185
+ accessibilityLabel="btn_navigation_back"
188
186
  icon="ic_back"
189
187
  tintColor={tintColor}
190
- onPress={goBack}
191
- accessibilityLabel={`${accessibilityLabel}Back`}
188
+ onPress={goBackSafe}
192
189
  useBorder={useBorder}
193
- backgroundColor={backgroundColor}
194
190
  />
195
191
  </View>
196
192
  );
@@ -198,17 +194,20 @@ const HeaderLeft: React.FC<HeaderBackProps> = ({
198
194
 
199
195
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
200
196
  image,
201
- backgroundColor,
202
197
  animatedValue,
203
- useBorder = false,
198
+ useSurface = false,
204
199
  }) => {
205
200
  const {theme} = useContext(ApplicationContext);
206
201
  let headerImage = theme.assets?.headerBackground;
202
+ let backgroundColor;
207
203
  if (image === null) {
208
204
  headerImage = undefined;
209
205
  }
206
+ if (useSurface) {
207
+ backgroundColor = theme.colors.background.surface;
208
+ }
210
209
  const opacity = animatedValue?.interpolate({
211
- inputRange: [0, 200],
210
+ inputRange: [0, 114],
212
211
  outputRange: [0, 1],
213
212
  extrapolate: 'clamp',
214
213
  });
@@ -218,15 +217,17 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
218
217
  style={[
219
218
  Styles.flex,
220
219
  {
221
- backgroundColor: backgroundColor ?? theme.colors.background.default,
220
+ backgroundColor,
222
221
  overflow: 'hidden',
223
- borderBottomWidth: useBorder ? 1 : 0,
222
+ borderBottomWidth: useSurface ? 1 : 0,
224
223
  borderColor: theme.colors.border.default,
225
224
  opacity,
226
225
  },
227
226
  ]}>
228
227
  <StatusBar
229
228
  barStyle={headerImage || theme.dark ? 'light-content' : 'dark-content'}
229
+ translucent={true}
230
+ backgroundColor={'transparent'}
230
231
  />
231
232
  {!!headerImage && (
232
233
  <Image
@@ -248,7 +249,7 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
248
249
  tintColor,
249
250
  dotColor,
250
251
  }) => {
251
- const avatarStyle = imageShape == 'circle' ? styles.circle : styles.square;
252
+ const avatarStyle = imageShape === 'circle' ? styles.circle : styles.square;
252
253
  const header = (
253
254
  <View style={Styles.row}>
254
255
  <View>
@@ -258,10 +259,13 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
258
259
  )}
259
260
  </View>
260
261
  <View style={[Styles.flex, {paddingHorizontal: 8}]}>
261
- <Text typography="title_xs" color={tintColor}>
262
+ <Text
263
+ accessibilityLabel={'title_navigation_header'}
264
+ typography="header_default_bold"
265
+ color={tintColor}>
262
266
  {title}
263
267
  </Text>
264
- <Text typography="description_s" color={tintColor}>
268
+ <Text typography="description_default_regular" color={tintColor}>
265
269
  {subTitle}
266
270
  </Text>
267
271
  </View>
@@ -271,142 +275,195 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
271
275
  };
272
276
 
273
277
  const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
274
- const validType = (item: any) => {
275
- if (__DEV__ && item.type !== NavigationButton) {
276
- Alert.alert(
277
- 'Rule',
278
- 'HeaderRightAction contains element type of NavigationButton, Please check again.',
278
+ const validateType = (child: React.ReactElement) => {
279
+ const valid = child?.type === NavigationButton || child?.type === Fragment;
280
+ if (__DEV__ && !valid) {
281
+ console.warn(
282
+ 'HeaderRightAction contains element type of NavigationButton, Please check again.'
279
283
  );
280
- return <View />;
281
284
  }
282
- return item;
285
+ return child;
283
286
  };
287
+
284
288
  const renderAction = () => {
285
289
  if (Array.isArray(children)) {
286
290
  return children.map((child, index) => {
287
291
  return (
288
- <View key={index} style={styles.headerButton}>
289
- {React.cloneElement(validType(child), {...restProps})}
292
+ <View
293
+ key={`HeaderRightAction ${index}`}
294
+ style={{
295
+ marginLeft: index !== 0 ? Spacing.S : 0,
296
+ }}>
297
+ {React.cloneElement(validateType(child), {...restProps})}
290
298
  </View>
291
299
  );
292
300
  });
293
301
  }
294
302
 
295
303
  return (
296
- <View style={styles.headerButton}>
297
- {React.cloneElement(validType(children), {...restProps})}
298
- </View>
304
+ <View>{React.cloneElement(validateType(children), {...restProps})}</View>
299
305
  );
300
306
  };
301
307
  return <View style={styles.headerRightButton}>{renderAction()}</View>;
302
308
  };
303
309
 
304
- const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
305
- const {theme, navigator} = useContext(ApplicationContext);
306
- const [toolConfig, setToolConfig] = useState<HeaderToolkitProps>();
307
- const {appCode, hidden, type, onAction} = toolConfig ?? {};
310
+ const HeaderRight: FC<any> = ({type, ...props}) => {
311
+ let buttons = props?.buttons || [];
312
+ if (!type) {
313
+ return <View />;
314
+ }
308
315
 
309
- let backgroundColor;
310
- if (tintColor != Colors.black_01) {
311
- backgroundColor = theme.colors.background.surface;
316
+ if (type === 'iconButton') {
317
+ if (buttons?.length > 3) {
318
+ buttons = buttons.slice(0, 3);
319
+ }
320
+ return (
321
+ <HeaderRightAction>
322
+ {buttons?.map?.(({icon, onPress}: IconButton) => {
323
+ return (
324
+ <NavigationButton
325
+ icon={icon}
326
+ onPress={onPress}
327
+ tintColor={props?.tintColor}
328
+ />
329
+ );
330
+ })}
331
+ </HeaderRightAction>
332
+ );
312
333
  }
313
334
 
314
- useEffect(() => {
315
- navigator?.getToolkit().then((toolConfig: any) => {
316
- setToolConfig(toolConfig);
317
- });
318
- }, []);
335
+ return <HeaderToolkitAction {...props} />;
336
+ };
337
+
338
+ const HeaderToolkitAction: React.FC<any> = ({
339
+ tintColor,
340
+ pinnedTool,
341
+ runtimeTools = [],
342
+ preventClose,
343
+ }) => {
344
+ const {navigator} = useContext(ApplicationContext);
345
+ const miniContext = useContext<any>(MiniAppContext);
346
+ const [toolConfig, setToolConfig] = useState<any>();
347
+ const {onAction} = toolConfig ?? {};
348
+
349
+ let backgroundColor;
350
+ if (tintColor !== Colors.black_01) {
351
+ backgroundColor = 'transparent';
352
+ }
319
353
 
320
354
  useEffect(() => {
321
- let addFavorite: any;
322
- let removeFavorite: any;
323
- if (appCode) {
324
- const addSchema = `observerToolkit_addFavorite_${appCode}`;
325
- const removeSchema = `observerToolkit_removeFavorite_${appCode}`;
326
- addFavorite = navigator?.requestApi(
327
- 'observer',
328
- [addSchema],
329
- (data: any) => {
330
- if (data?.status) {
331
- setToolConfig({
332
- ...(toolConfig as HeaderToolkitProps),
333
- hidden: true,
334
- });
335
- }
336
- },
337
- );
338
- addFavorite = navigator?.requestApi(
339
- 'observer',
340
- [removeSchema],
341
- (data: any) => {
342
- if (data?.status) {
343
- setToolConfig({
344
- ...(toolConfig as HeaderToolkitProps),
345
- hidden: false,
346
- });
347
- }
348
- },
349
- );
355
+ if (miniContext?.toolkitConfig?.hidden === false) {
356
+ if (navigator?.toolkitConfig) {
357
+ setToolConfig(navigator?.toolkitConfig);
358
+ } else {
359
+ getToolkitConfig();
360
+ }
350
361
  }
362
+ }, []);
351
363
 
352
- return () => {
353
- addFavorite?.();
354
- removeFavorite?.();
355
- };
356
- }, [appCode]);
357
-
358
- const onUtilityTool = () => {
359
- onAction?.('onUtilityTool');
360
- navigator?.requestApi(type, undefined, undefined);
364
+ const getToolkitConfig = () => {
365
+ navigator?.maxApi?.dispatchFunction?.(
366
+ 'getToolsConfig',
367
+ [undefined],
368
+ (config: any) => {
369
+ navigator.toolkitConfig = config;
370
+ setToolConfig(navigator?.toolkitConfig);
371
+ }
372
+ );
361
373
  };
362
374
 
363
375
  const onMore = () => {
364
376
  onAction?.('onMore');
365
- navigator?.requestApi('showToolkit', [toolConfig], undefined);
377
+ navigator?.maxApi?.dispatchFunction?.(
378
+ 'showTools',
379
+ {runtimeTools},
380
+ (res: {item: {action?: string; key: string}}) => {
381
+ const {item} = res;
382
+ navigator?.toolkitCallback?.(item);
383
+ getToolkitConfig();
384
+ }
385
+ );
366
386
  };
367
387
 
368
388
  const onClose = () => {
369
- onAction?.('onClose');
370
- navigator?.requestApi('dismiss', undefined, undefined);
389
+ if (preventClose) {
390
+ navigator?.showModal({
391
+ screen: () => (
392
+ <PopupNotify
393
+ {...preventClose}
394
+ primary={{
395
+ title: preventClose.primary.title,
396
+ onPress: () => {
397
+ preventClose?.primary.onPress();
398
+ navigator?.maxApi?.dispatchFunction?.(
399
+ 'dismiss',
400
+ undefined,
401
+ undefined
402
+ );
403
+ },
404
+ }}
405
+ />
406
+ ),
407
+ });
408
+ } else {
409
+ navigator?.maxApi?.dispatchFunction?.('dismiss', undefined, undefined);
410
+ }
371
411
  };
372
412
 
373
- const renderUtilityTool = () => {
374
- if (hidden === false) {
413
+ const renderPinnedTool = () => {
414
+ const pinKey = pinnedTool?.key || toolConfig?.pinnedTool?.key;
415
+ const pinTool = toolConfig?.tools?.find((i: any) => i.key === pinKey);
416
+ const {badgeValue, badgeType} = pinnedTool ?? {};
417
+
418
+ if (pinTool)
375
419
  return (
376
420
  <NavigationButton
377
- accessibilityLabel={'UtilityTool'}
378
- icon={type as string}
421
+ accessibilityLabel="btn_navigation_pin"
422
+ icon={pinTool.icon as string}
379
423
  tintColor={tintColor}
380
- onPress={onUtilityTool}
424
+ badgeValue={badgeValue}
425
+ badgeType={badgeType}
426
+ onPress={() => {
427
+ navigator?.maxApi?.dispatchFunction?.(
428
+ 'onToolAction',
429
+ [pinTool?.key],
430
+ () => {
431
+ getToolkitConfig();
432
+ }
433
+ );
434
+ navigator?.toolkitCallback?.(pinTool.key);
435
+ }}
381
436
  />
382
437
  );
383
- }
384
- return null;
385
438
  };
386
-
387
439
  if (toolConfig) {
388
440
  return (
389
441
  <View style={styles.headerRightButton}>
390
- {renderUtilityTool()}
442
+ {renderPinnedTool()}
391
443
  <View
392
444
  style={[
393
445
  styles.toolkitContainer,
394
446
  {
395
447
  backgroundColor: backgroundColor ?? '#00000066',
396
448
  borderWidth: backgroundColor ? 0.5 : 0,
449
+ marginLeft: renderPinnedTool() ? Spacing.S : 0,
397
450
  },
398
451
  ]}>
399
- <Touchable accessibilityLabel={'Toolkit/More'} onPress={onMore}>
452
+ <TouchableOpacity
453
+ accessibilityLabel={'btn_navigation_more'}
454
+ onPress={onMore}>
400
455
  <Icon color={tintColor} source="navigation_more_horiz" size={20} />
401
- </Touchable>
456
+ </TouchableOpacity>
402
457
  <View style={[styles.divider, {backgroundColor: tintColor}]} />
403
- <Touchable accessibilityLabel={`Toolkit/Close`} onPress={onClose}>
458
+ <TouchableOpacity
459
+ accessibilityLabel={'btn_navigation_close'}
460
+ onPress={onClose}>
404
461
  <Icon
405
462
  color={tintColor}
406
463
  source="16_navigation_close_circle"
407
464
  size={20}
408
465
  />
409
- </Touchable>
466
+ </TouchableOpacity>
410
467
  </View>
411
468
  </View>
412
469
  );
@@ -415,6 +472,118 @@ const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
415
472
  return <View />;
416
473
  };
417
474
 
475
+ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
476
+ animatedValue,
477
+ image,
478
+ children,
479
+ }) => {
480
+ const scale = animatedValue.interpolate({
481
+ inputRange: [-300, 0, 300],
482
+ outputRange: [4, 1, 1],
483
+ extrapolate: 'clamp',
484
+ });
485
+ const opacity = animatedValue.interpolate({
486
+ inputRange: [0, 150, 300],
487
+ outputRange: [1, 0.5, 0],
488
+ extrapolate: 'clamp',
489
+ });
490
+ const translateY = animatedValue.interpolate({
491
+ inputRange: [-300, 0],
492
+ outputRange: [-80, -2],
493
+ extrapolate: 'clamp',
494
+ });
495
+
496
+ return (
497
+ <Animated.View
498
+ style={{
499
+ height: 210,
500
+ opacity: opacity,
501
+ transform: [{scale}, {translateY}],
502
+ }}>
503
+ {children ?? (
504
+ <Image
505
+ source={{
506
+ uri: image,
507
+ }}
508
+ style={{width: '100%', height: '100%'}}
509
+ />
510
+ )}
511
+ </Animated.View>
512
+ );
513
+ };
514
+
515
+ const styles = StyleSheet.create({
516
+ navigationButton: {
517
+ height: 28,
518
+ width: 28,
519
+ borderRadius: 14,
520
+ justifyContent: 'center',
521
+ alignItems: 'center',
522
+ },
523
+ headerBackground: {
524
+ width: '100%',
525
+ height: undefined,
526
+ position: 'absolute',
527
+ aspectRatio: 375 / 154,
528
+ },
529
+ headerTitleContainer: {
530
+ alignItems: 'center',
531
+ justifyContent: 'center',
532
+ width: '100%',
533
+ },
534
+ circle: {width: 36, height: 36, borderRadius: 18},
535
+ square: {width: 36, height: 36, borderRadius: Radius.XS},
536
+ dotAvatar: {
537
+ position: 'absolute',
538
+ width: 12,
539
+ height: 12,
540
+ borderRadius: 6,
541
+ bottom: 0,
542
+ right: 0,
543
+ borderWidth: 1,
544
+ borderColor: Colors.black_01,
545
+ },
546
+ headerButton: {paddingHorizontal: 4},
547
+ headerRightButton: {
548
+ flexDirection: 'row',
549
+ justifyContent: 'center',
550
+ alignItems: 'center',
551
+ paddingRight: Spacing.M,
552
+ },
553
+ toolkitContainer: {
554
+ padding: Spacing.XS,
555
+ height: 28,
556
+ borderRadius: 14,
557
+ borderColor: '#00000066',
558
+ justifyContent: 'center',
559
+ alignItems: 'center',
560
+ flexDirection: 'row',
561
+ },
562
+ divider: {
563
+ width: 1,
564
+ height: 12,
565
+ marginHorizontal: 4,
566
+ },
567
+ headerLeft: {
568
+ marginLeft: 12,
569
+ },
570
+ title: {
571
+ fontSize: scaleSize(15),
572
+ lineHeight: scaleSize(22),
573
+ fontWeight: 'bold',
574
+ },
575
+ badge: {
576
+ position: 'absolute',
577
+ top: -Spacing.S,
578
+ right: -6,
579
+ },
580
+ badgeDot: {
581
+ position: 'absolute',
582
+ top: -Spacing.XS,
583
+ right: -Spacing.XS,
584
+ },
585
+ });
586
+
418
587
  export {
419
588
  NavigationButton,
420
589
  HeaderTitle,
@@ -423,4 +592,6 @@ export {
423
592
  HeaderRightAction,
424
593
  HeaderToolkitAction,
425
594
  HeaderCustom,
595
+ HeaderAnimated,
596
+ HeaderRight,
426
597
  };