@momo-kits/foundation 0.80.9 → 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 +379 -209
  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 +38 -49
  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 +35 -23
  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 +69 -24
  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 -63
  78. package/Title/styles.ts +48 -15
  79. package/Title/types.ts +16 -47
  80. package/code-scanner.js +125 -16
  81. package/index.ts +9 -0
  82. package/migrate_typo.js +56 -0
  83. package/package.json +9 -8
  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, {Fragment, 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,
9
- TouchableOpacity as TouchableRN,
15
+ TouchableOpacity,
10
16
  View,
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>
@@ -272,11 +276,10 @@ const HeaderCustom: React.FC<TitleCustomProps> = ({
272
276
 
273
277
  const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
274
278
  const validateType = (child: React.ReactElement) => {
275
- const valid = child?.type === NavigationButton || child?.type == Fragment;
279
+ const valid = child?.type === NavigationButton || child?.type === Fragment;
276
280
  if (__DEV__ && !valid) {
277
- Alert.alert(
278
- 'Rule',
279
- 'HeaderRightAction contains element type of NavigationButton, Please check again.',
281
+ console.warn(
282
+ 'HeaderRightAction contains element type of NavigationButton, Please check again.'
280
283
  );
281
284
  }
282
285
  return child;
@@ -286,7 +289,11 @@ const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
286
289
  if (Array.isArray(children)) {
287
290
  return children.map((child, index) => {
288
291
  return (
289
- <View key={`HeaderRightAction ${index}`} style={styles.headerButton}>
292
+ <View
293
+ key={`HeaderRightAction ${index}`}
294
+ style={{
295
+ marginLeft: index !== 0 ? Spacing.S : 0,
296
+ }}>
290
297
  {React.cloneElement(validateType(child), {...restProps})}
291
298
  </View>
292
299
  );
@@ -294,120 +301,169 @@ const HeaderRightAction: React.FC<any> = ({children, ...restProps}) => {
294
301
  }
295
302
 
296
303
  return (
297
- <View style={styles.headerButton}>
298
- {React.cloneElement(validateType(children), {...restProps})}
299
- </View>
304
+ <View>{React.cloneElement(validateType(children), {...restProps})}</View>
300
305
  );
301
306
  };
302
307
  return <View style={styles.headerRightButton}>{renderAction()}</View>;
303
308
  };
304
309
 
305
- const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
306
- const {theme, navigator} = useContext(ApplicationContext);
307
- const [toolConfig, setToolConfig] = useState<HeaderToolkitProps>();
308
- 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
+ }
309
315
 
310
- let backgroundColor;
311
- if (tintColor != Colors.black_01) {
312
- 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
+ );
313
333
  }
314
334
 
315
- useEffect(() => {
316
- navigator?.getToolkit().then((toolConfig: any) => {
317
- setToolConfig(toolConfig);
318
- });
319
- }, []);
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
+ }
320
353
 
321
354
  useEffect(() => {
322
- let addFavorite: any;
323
- let removeFavorite: any;
324
- if (appCode) {
325
- const addSchema = `observerToolkit_addFavorite_${appCode}`;
326
- const removeSchema = `observerToolkit_removeFavorite_${appCode}`;
327
- addFavorite = navigator?.requestApi(
328
- 'observer',
329
- [addSchema],
330
- (data: any) => {
331
- if (data?.status) {
332
- setToolConfig({
333
- ...(toolConfig as HeaderToolkitProps),
334
- hidden: true,
335
- });
336
- }
337
- },
338
- );
339
- addFavorite = navigator?.requestApi(
340
- 'observer',
341
- [removeSchema],
342
- (data: any) => {
343
- if (data?.status) {
344
- setToolConfig({
345
- ...(toolConfig as HeaderToolkitProps),
346
- hidden: false,
347
- });
348
- }
349
- },
350
- );
355
+ if (miniContext?.toolkitConfig?.hidden === false) {
356
+ if (navigator?.toolkitConfig) {
357
+ setToolConfig(navigator?.toolkitConfig);
358
+ } else {
359
+ getToolkitConfig();
360
+ }
351
361
  }
362
+ }, []);
352
363
 
353
- return () => {
354
- addFavorite?.();
355
- removeFavorite?.();
356
- };
357
- }, [appCode]);
358
-
359
- const onUtilityTool = () => {
360
- onAction?.('onUtilityTool');
361
- 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
+ );
362
373
  };
363
374
 
364
375
  const onMore = () => {
365
376
  onAction?.('onMore');
366
- 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
+ );
367
386
  };
368
387
 
369
388
  const onClose = () => {
370
- onAction?.('onClose');
371
- 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
+ }
372
411
  };
373
412
 
374
- const renderUtilityTool = () => {
375
- 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)
376
419
  return (
377
420
  <NavigationButton
378
- accessibilityLabel={'UtilityTool'}
379
- icon={type as string}
421
+ accessibilityLabel="btn_navigation_pin"
422
+ icon={pinTool.icon as string}
380
423
  tintColor={tintColor}
381
- 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
+ }}
382
436
  />
383
437
  );
384
- }
385
- return null;
386
438
  };
387
-
388
439
  if (toolConfig) {
389
440
  return (
390
441
  <View style={styles.headerRightButton}>
391
- {renderUtilityTool()}
442
+ {renderPinnedTool()}
392
443
  <View
393
444
  style={[
394
445
  styles.toolkitContainer,
395
446
  {
396
447
  backgroundColor: backgroundColor ?? '#00000066',
397
448
  borderWidth: backgroundColor ? 0.5 : 0,
449
+ marginLeft: renderPinnedTool() ? Spacing.S : 0,
398
450
  },
399
451
  ]}>
400
- <Touchable accessibilityLabel={'Toolkit/More'} onPress={onMore}>
452
+ <TouchableOpacity
453
+ accessibilityLabel={'btn_navigation_more'}
454
+ onPress={onMore}>
401
455
  <Icon color={tintColor} source="navigation_more_horiz" size={20} />
402
- </Touchable>
456
+ </TouchableOpacity>
403
457
  <View style={[styles.divider, {backgroundColor: tintColor}]} />
404
- <Touchable accessibilityLabel={`Toolkit/Close`} onPress={onClose}>
458
+ <TouchableOpacity
459
+ accessibilityLabel={'btn_navigation_close'}
460
+ onPress={onClose}>
405
461
  <Icon
406
462
  color={tintColor}
407
463
  source="16_navigation_close_circle"
408
464
  size={20}
409
465
  />
410
- </Touchable>
466
+ </TouchableOpacity>
411
467
  </View>
412
468
  </View>
413
469
  );
@@ -416,6 +472,118 @@ const HeaderToolkitAction: React.FC<any> = ({tintColor}) => {
416
472
  return <View />;
417
473
  };
418
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
+
419
587
  export {
420
588
  NavigationButton,
421
589
  HeaderTitle,
@@ -424,4 +592,6 @@ export {
424
592
  HeaderRightAction,
425
593
  HeaderToolkitAction,
426
594
  HeaderCustom,
595
+ HeaderAnimated,
596
+ HeaderRight,
427
597
  };