@momo-kits/foundation 0.169.4 → 0.169.5

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 (125) hide show
  1. package/Application/BottomSheet.hooks.ts +8 -8
  2. package/Application/BottomSheet.parts.tsx +44 -34
  3. package/Application/BottomSheet.tsx +35 -26
  4. package/Application/BottomTab/ActiveIndicator.tsx +15 -14
  5. package/Application/BottomTab/BottomTabBar.tsx +43 -34
  6. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  7. package/Application/BottomTab/CurvedContainer.tsx +14 -12
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +10 -6
  9. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  10. package/Application/BottomTab/DuoRailTabBar.tsx +13 -13
  11. package/Application/BottomTab/DuoTabBar.tsx +8 -9
  12. package/Application/BottomTab/TabBarIcon.tsx +13 -8
  13. package/Application/BottomTab/TabScreen.tsx +1 -5
  14. package/Application/BottomTab/index.tsx +38 -30
  15. package/Application/BottomTab/metrics.ts +21 -20
  16. package/Application/BottomTab/tabItems.ts +1 -1
  17. package/Application/BottomTab/usePressLift.ts +2 -2
  18. package/Application/Components/BackgroundImageView.tsx +4 -0
  19. package/Application/Components/DuoGlassBackground.tsx +5 -4
  20. package/Application/Components/HeaderAnimated.tsx +4 -0
  21. package/Application/Components/HeaderBackground.tsx +4 -0
  22. package/Application/Components/HeaderExtendHeader.tsx +11 -10
  23. package/Application/Components/HeaderLeft.tsx +2 -1
  24. package/Application/Components/HeaderRight.tsx +2 -1
  25. package/Application/Components/HeaderTitle.shared.ts +11 -9
  26. package/Application/Components/HeaderTitle.tsx +4 -3
  27. package/Application/Components/HeaderToolkitAction.tsx +17 -20
  28. package/Application/Components/NavigationButton.tsx +2 -1
  29. package/Application/Components/SearchHeader.tsx +17 -10
  30. package/Application/Components/TitleJourney.tsx +2 -2
  31. package/Application/Components/TitleLocation.tsx +2 -2
  32. package/Application/Components/TitleUser.tsx +2 -2
  33. package/Application/Components/useToolkitActions.tsx +17 -32
  34. package/Application/Localize.ts +7 -0
  35. package/Application/ModalScreen.tsx +9 -3
  36. package/Application/Navigation.ts +14 -16
  37. package/Application/NavigationContainer.tsx +19 -16
  38. package/Application/Navigator.ts +20 -48
  39. package/Application/StackScreen.tracking.ts +11 -26
  40. package/Application/StackScreen.tsx +8 -35
  41. package/Application/TooltipPortal.tsx +12 -5
  42. package/Application/WidgetContainer.tsx +10 -4
  43. package/Application/modalLayout.ts +11 -12
  44. package/Application/navigationOptions.tsx +21 -17
  45. package/Badge/Badge.tsx +5 -0
  46. package/Badge/BadgeDot.tsx +1 -0
  47. package/Badge/BadgeDotAnimation.tsx +15 -7
  48. package/Badge/BadgeRibbon.tsx +46 -38
  49. package/Badge/Shape.tsx +2 -0
  50. package/Badge/types.ts +2 -2
  51. package/Button/index.tsx +5 -16
  52. package/Button/utils.ts +9 -15
  53. package/CheckBox/index.tsx +4 -0
  54. package/Consts/theme.ts +10 -10
  55. package/Context/index.ts +1 -0
  56. package/Divider/DashDivider.tsx +1 -0
  57. package/Divider/index.tsx +4 -0
  58. package/FoundationList/index.tsx +4 -0
  59. package/Icon/index.tsx +6 -6
  60. package/Icon/types.ts +1 -1
  61. package/IconButton/index.tsx +4 -12
  62. package/Image/index.tsx +9 -7
  63. package/Input/Input.tsx +8 -9
  64. package/Input/InputDropDown.tsx +10 -12
  65. package/Input/InputMoney.tsx +12 -8
  66. package/Input/InputMoneyView.tsx +14 -7
  67. package/Input/InputOTP.tsx +35 -33
  68. package/Input/InputPhoneNumber.tsx +8 -9
  69. package/Input/InputSearch.tsx +4 -6
  70. package/Input/InputTextArea.tsx +4 -0
  71. package/Input/SearchTextTyping.tsx +4 -0
  72. package/Input/SystemTextInput.tsx +7 -2
  73. package/Input/TextTyping.tsx +4 -0
  74. package/Input/common.tsx +26 -12
  75. package/Input/index.tsx +17 -20
  76. package/Input/moneyUtils.ts +10 -4
  77. package/Input/styles.ts +0 -5
  78. package/Input/utils.ts +14 -12
  79. package/Layout/Card.tsx +7 -4
  80. package/Layout/FloatingButton.tsx +4 -0
  81. package/Layout/Item.tsx +6 -2
  82. package/Layout/ItemList.tsx +9 -7
  83. package/Layout/ItemSectionList.tsx +2 -1
  84. package/Layout/ReservedRegions.tsx +25 -25
  85. package/Layout/Screen.tsx +56 -58
  86. package/Layout/Section.tsx +8 -5
  87. package/Layout/TrackingScope.tsx +1 -0
  88. package/Layout/screenContent.tsx +4 -2
  89. package/Layout/screenFooter.tsx +28 -18
  90. package/Layout/screenHeader.tsx +34 -31
  91. package/Layout/screenQuickReturn.tsx +17 -20
  92. package/Layout/screenScroll.ts +4 -7
  93. package/Layout/screenTypes.ts +3 -2
  94. package/Layout/types.ts +2 -4
  95. package/Layout/utils.ts +31 -37
  96. package/Loader/DotLoader.tsx +4 -0
  97. package/Loader/Loader.tsx +4 -0
  98. package/Loader/ProgressBar.tsx +1 -0
  99. package/Loader/Spinner.tsx +4 -0
  100. package/Loader/utils.ts +11 -7
  101. package/Pagination/Dot.tsx +4 -0
  102. package/Pagination/PaginationDot.tsx +1 -0
  103. package/Pagination/PaginationNumber.tsx +5 -3
  104. package/Pagination/PaginationScroll.tsx +5 -0
  105. package/Pagination/PaginationWhiteDot.tsx +1 -0
  106. package/Pagination/index.tsx +4 -0
  107. package/Pagination/types.ts +2 -2
  108. package/Popup/PopupNotify.tsx +39 -39
  109. package/Popup/PopupPromotion.tsx +4 -7
  110. package/Popup/types.ts +4 -4
  111. package/Popup/utils.ts +4 -0
  112. package/Radio/index.tsx +4 -0
  113. package/Skeleton/index.tsx +8 -0
  114. package/Switch/index.tsx +1 -0
  115. package/Tag/index.tsx +5 -1
  116. package/Tag/types.ts +3 -3
  117. package/Text/index.tsx +14 -3
  118. package/Text/styles.ts +1 -1
  119. package/Text/types.ts +1 -6
  120. package/Text/utils.ts +9 -0
  121. package/Title/index.tsx +41 -32
  122. package/ViewBoundary/index.tsx +4 -2
  123. package/index.ts +0 -6
  124. package/package.json +1 -1
  125. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -34,7 +34,8 @@ import { Image } from '../Image';
34
34
  import { Typography } from '../Text/types';
35
35
 
36
36
  /**
37
- * Input default component
37
+ * Vietnamese phone number input with a fixed flag and `+84` prefix, number pad
38
+ * by default, plus clear and trailing icons.
38
39
  */
39
40
  const InputPhoneNumber = forwardRef(
40
41
  (
@@ -146,9 +147,6 @@ const InputPhoneNumber = forwardRef(
146
147
  typography = 'header_m_bold';
147
148
  }
148
149
 
149
- /**
150
- * Render input view
151
- */
152
150
  const renderInputView = () => {
153
151
  const disabledColor = theme.colors.text.disable;
154
152
  const secure = secureTextEntry;
@@ -156,6 +154,11 @@ const InputPhoneNumber = forwardRef(
156
154
  let placeholderColor = theme.colors.text.hint;
157
155
  let iconTintColor = trailingColor ?? theme.colors.text.default;
158
156
  const borderWidth = focused ? 1.5 : 1;
157
+ let secureTextStyle = styles.secureTextLarge;
158
+
159
+ if (size === 'small') {
160
+ secureTextStyle = styles.secureTextSmall;
161
+ }
159
162
 
160
163
  if (disabled) {
161
164
  textColor = disabledColor;
@@ -214,11 +217,7 @@ const InputPhoneNumber = forwardRef(
214
217
  color: textColor,
215
218
  },
216
219
  inputTextStyles,
217
- secure &&
218
- haveValue &&
219
- (size === 'small'
220
- ? styles.secureTextSmall
221
- : styles.secureTextLarge),
220
+ secure && haveValue && secureTextStyle,
222
221
  ]}
223
222
  defaultValue={defaultValue}
224
223
  allowFontScaling={false}
@@ -26,6 +26,10 @@ import styles from './styles';
26
26
  import { Styles } from '../Consts';
27
27
  import SystemTextInput from './SystemTextInput';
28
28
 
29
+ /**
30
+ * Search field with search icon, clear icon, optional trailing icon and a
31
+ * cancel button; with `typingData` it shows an animated placeholder instead.
32
+ */
29
33
  const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
30
34
  {
31
35
  placeholder,
@@ -106,9 +110,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
106
110
  };
107
111
  });
108
112
 
109
- /**
110
- * Render the input view
111
- */
112
113
  const renderInputView = () => {
113
114
  if (typingData && typingData?.length > 0) {
114
115
  return (
@@ -149,9 +150,6 @@ const InputSearch: ForwardRefRenderFunction<InputRef, InputSearchProps> = (
149
150
  );
150
151
  };
151
152
 
152
- /**
153
- * Render trailing icon or text
154
- */
155
153
  const renderTrailing = () => {
156
154
  return (
157
155
  <View style={Styles.row}>
@@ -21,6 +21,10 @@ import styles from './styles';
21
21
  import SystemTextInput from './SystemTextInput';
22
22
  import { setAutomationID, useComponentId } from '../Application';
23
23
 
24
+ /**
25
+ * Multiline input with floating label, clear icon and a `length/maxLength`
26
+ * counter; the ref exposes clear, focus, blur and setText.
27
+ */
24
28
  const InputTextArea = forwardRef(
25
29
  (
26
30
  {
@@ -4,6 +4,10 @@ import { TextInput } from 'react-native';
4
4
  import { useScaleSize, exportFontFamily } from '../Text';
5
5
  import styles from './styles';
6
6
 
7
+ /**
8
+ * Animated search placeholder: types each string of `data` char by char, then
9
+ * erases it, looping forever. Writes via setNativeProps to avoid re-renders.
10
+ */
7
11
  export const TextTyping: FC<any> = ({
8
12
  data = [],
9
13
  timeDelayEnd = 2000,
@@ -11,11 +11,16 @@ interface SystemTextInputProps extends TextInputProps {
11
11
  ref?: React.RefObject<TextInput | null>;
12
12
  }
13
13
 
14
+ /**
15
+ * TextInput that still fires onPressIn/onPressOut when not editable on
16
+ * Android, by moving them to a wrapping touchable.
17
+ */
14
18
  const SystemTextInput: FC<SystemTextInputProps> = props => {
15
19
  const { editable, onPressIn, onPressOut, ref } = props;
16
20
 
17
- const needsAndroidWrapper =
18
- Platform.OS === 'android' && !editable && (onPressIn || onPressOut);
21
+ const isAndroidReadOnly = Platform.OS === 'android' && !editable;
22
+ const hasPressHandler = !!(onPressIn || onPressOut);
23
+ const needsAndroidWrapper = isAndroidReadOnly && hasPressHandler;
19
24
 
20
25
  const noPressHandlers = { onPressIn: undefined, onPressOut: undefined };
21
26
  const inputProps: TextInputProps = {
@@ -11,6 +11,10 @@ export interface TextTypingProps {
11
11
  inputStyle?: TextStyle;
12
12
  }
13
13
 
14
+ /**
15
+ * Typing placeholder: types each string of `data` char by char, then erases
16
+ * it, looping forever. Writes via setNativeProps to avoid re-renders.
17
+ */
14
18
  const TextTyping: FC<TextTypingProps> = ({
15
19
  data = [],
16
20
  timeDelayEnd = 2000,
package/Input/common.tsx CHANGED
@@ -35,6 +35,7 @@ type TrailingProps = {
35
35
 
36
36
  export const MAX_LENGTH = 300;
37
37
 
38
+ /** Border color by state; priority is disabled > error > focused > default. */
38
39
  export const getBorderColor = (
39
40
  theme: Theme,
40
41
  focused: boolean,
@@ -58,6 +59,10 @@ export const getBorderColor = (
58
59
  return { borderColor };
59
60
  };
60
61
 
62
+ /**
63
+ * Container style for `size`; multiline uses `height` as minHeight so the
64
+ * field can grow with its content.
65
+ */
61
66
  export const getSizeStyle = (
62
67
  size?: 'small' | 'large',
63
68
  multiline: boolean = false,
@@ -68,13 +73,14 @@ export const getSizeStyle = (
68
73
  scaleStyle = { height };
69
74
  }
70
75
 
71
- if (multiline)
76
+ if (multiline) {
72
77
  return [
73
78
  styles.multilineContainer,
74
79
  {
75
80
  minHeight: height,
76
81
  },
77
82
  ];
83
+ }
78
84
 
79
85
  if (size === 'small') {
80
86
  return [styles.smallContainer, scaleStyle];
@@ -82,6 +88,10 @@ export const getSizeStyle = (
82
88
  return [styles.container, scaleStyle];
83
89
  };
84
90
 
91
+ /**
92
+ * Error or hint row under an input (error wins); with neither, `errorSpacing`
93
+ * keeps an empty row so the layout doesn't jump when an error appears.
94
+ */
85
95
  export const ErrorView: FC<{
86
96
  errorMessage?: string;
87
97
  errorSpacing?: boolean;
@@ -95,21 +105,17 @@ export const ErrorView: FC<{
95
105
  const scaledIconSize = useScaleSize(16);
96
106
 
97
107
  if (errorMessage || hintText) {
108
+ const messageColor = errorMessage ? errorColor : hintColor;
109
+ const messageType = errorMessage ? 'error' : 'hint';
98
110
  return (
99
111
  <View style={styles.errorView}>
100
112
  <View style={styles.errorIcon}>
101
- <Icon
102
- color={errorMessage ? errorColor : hintColor}
103
- source="ic_error"
104
- size={scaledIconSize}
105
- />
113
+ <Icon color={messageColor} source="ic_error" size={scaledIconSize} />
106
114
  </View>
107
115
  <Text
108
- accessibilityLabel={`${componentId}|${
109
- errorMessage ? 'error' : 'hint'
110
- }-text`}
116
+ accessibilityLabel={`${componentId}|${messageType}-text`}
111
117
  style={Styles.flex}
112
- color={errorMessage ? errorColor : hintColor}
118
+ color={messageColor}
113
119
  typography={'description_default_regular'}
114
120
  >
115
121
  {errorMessage ?? hintTextDefault}
@@ -123,6 +129,10 @@ export const ErrorView: FC<{
123
129
  return <View />;
124
130
  };
125
131
 
132
+ /**
133
+ * Floating label on the input's top border with a required `*` and an
134
+ * optional pressable icon; renders nothing without `floatingValue`.
135
+ */
126
136
  export const FloatingView: FC<FloatingViewProps> = ({
127
137
  floatingValue,
128
138
  floatingIconColor,
@@ -191,6 +201,10 @@ export const FloatingView: FC<FloatingViewProps> = ({
191
201
  return null;
192
202
  };
193
203
 
204
+ /**
205
+ * Trailing slot: spinner while `loading`, an icon when `icon` is a known icon
206
+ * name or URL, otherwise `icon` as a text action (first 15 chars).
207
+ */
194
208
  export const RenderTrailing: FC<TrailingProps> = ({
195
209
  loading,
196
210
  color,
@@ -204,8 +218,8 @@ export const RenderTrailing: FC<TrailingProps> = ({
204
218
  }
205
219
 
206
220
  if (icon) {
207
- const isIcon =
208
- (IconSources as any)?.[icon as string]?.uri || icon?.includes('http');
221
+ const isIconName = !!(IconSources as any)?.[icon]?.uri;
222
+ const isIcon = isIconName || icon.includes('http');
209
223
  const renderIconTouchable = (content: ReactNode) => {
210
224
  return (
211
225
  <TouchableOpacity
package/Input/index.tsx CHANGED
@@ -36,7 +36,7 @@ export interface InputProps extends TextInputProps {
36
36
  errorMessage?: string;
37
37
 
38
38
  /**
39
- * Optional. Represents the name or key of the icon to be displayed in the Input component.
39
+ * Optional. Trailing icon name or URL; any other string is shown as a text action.
40
40
  */
41
41
  trailing?: string;
42
42
 
@@ -69,19 +69,18 @@ export interface InputProps extends TextInputProps {
69
69
  required?: boolean;
70
70
 
71
71
  /**
72
- * Optional. If `true`,
73
- * include spacing when Input not have error message.
72
+ * Optional. If `true`, keeps an empty error row when there is no error or hint.
74
73
  * Defaults to `false` if not provided.
75
74
  */
76
75
  errorSpacing?: boolean;
77
76
 
78
77
  /**
79
- * If true, the icon of input will use be show indicator loading.
78
+ * Optional. If `true`, shows a loading spinner in place of the trailing icon.
80
79
  */
81
80
  loading?: boolean;
82
81
 
83
82
  /**
84
- * Optional. Represents the leading icon in the Input component.
83
+ * Optional. Represents the leading icon in the Input component.
85
84
  */
86
85
  leadingIcon?: string;
87
86
 
@@ -111,12 +110,13 @@ export interface InputProps extends TextInputProps {
111
110
  hintText?: string;
112
111
 
113
112
  /**
114
- * Optional. Represents the style of the Input component.
113
+ * Optional. Callback function to be called when the floating icon is pressed.
115
114
  */
116
115
  onPressFloatingIcon?: () => void;
117
116
 
118
117
  /**
119
- * Optional. Represents the style of the Input component.
118
+ * Optional. If `true`, shows a clear icon while focused with a value.
119
+ * Defaults to `true` if not provided.
120
120
  */
121
121
  showClearIcon?: boolean;
122
122
  }
@@ -137,12 +137,12 @@ export interface InputSearchProps extends TextInputProps {
137
137
 
138
138
  /**
139
139
  * Optional. If `true`, the text on the search button will be displayed.
140
- * Defaults to `false` if not provided.
140
+ * Defaults to `true` if not provided.
141
141
  */
142
142
  showButtonText?: boolean;
143
143
 
144
144
  /**
145
- * Optional. Represents the value for the floating title in the InputSearch component.
145
+ * Optional. Callback function to be called when the search button is pressed.
146
146
  */
147
147
  onPressButtonText?: () => void;
148
148
 
@@ -180,7 +180,7 @@ export interface InputSearchProps extends TextInputProps {
180
180
  onPressTrailing?: () => void;
181
181
 
182
182
  /**
183
- * Optional. If `true`, the user won't be able to interact with the Input component.
183
+ * Optional. Background color of the search field. Defaults to the surface color.
184
184
  */
185
185
  backgroundColor?: string;
186
186
 
@@ -195,8 +195,7 @@ export interface InputSearchProps extends TextInputProps {
195
195
  typingData?: string[];
196
196
 
197
197
  /**
198
- * Optional. Represents the callback function to be called when the search button is pressed.
199
- * @param e
198
+ * Optional. Called when the field is pressed while showing `typingData`.
200
199
  */
201
200
  onPress?: (e: GestureResponderEvent) => void;
202
201
  }
@@ -259,7 +258,7 @@ export interface InputDropDownProps extends TouchableOpacityProps {
259
258
  placeholder?: string;
260
259
 
261
260
  /**
262
- * Optional. Represents the data for the dropdown.
261
+ * Optional. If `true`, the value wraps to multiple lines and the chevron aligns to the top.
263
262
  */
264
263
  multiline?: boolean;
265
264
 
@@ -303,19 +302,18 @@ export interface InputDropDownProps extends TouchableOpacityProps {
303
302
  required?: boolean;
304
303
 
305
304
  /**
306
- * Optional. If `true`,
307
- * include spacing when Input not have error message.
305
+ * Optional. If `true`, keeps an empty error row when there is no error or hint.
308
306
  * Defaults to `false` if not provided.
309
307
  */
310
308
  errorSpacing?: boolean;
311
309
 
312
310
  /**
313
- * If true, the icon of input will use be show indicator loading.
311
+ * Optional. If `true`, shows a loading spinner in place of the trailing icon.
314
312
  */
315
313
  loading?: boolean;
316
314
 
317
315
  /**
318
- * Optional. Represents the leading icon in the Input component.
316
+ * Optional. Represents the leading icon in the Input component.
319
317
  */
320
318
  leadingIcon?: string;
321
319
 
@@ -340,15 +338,14 @@ export interface InputDropDownProps extends TouchableOpacityProps {
340
338
  hintText?: string;
341
339
 
342
340
  /**
343
- * Optional. Represents the style of the Input component.
341
+ * Optional. Callback function to be called when the floating icon is pressed.
344
342
  */
345
343
  onPressFloatingIcon?: () => void;
346
344
  }
347
345
 
348
346
  export interface InputPhoneNumberProps extends InputProps {
349
347
  /**
350
- * Optional. Represents the country code to be displayed in the InputPhoneNumber component.
351
- * It should be in the format of a string, e.g., "+1" for the United States.
348
+ * Optional. Currently unused: the displayed prefix is always `+84`.
352
349
  */
353
350
  countryCode?: string;
354
351
  }
@@ -1,15 +1,21 @@
1
1
  export const moneyFormatter = new Intl.NumberFormat('vi-VN');
2
2
 
3
+ /**
4
+ * Locates the single edited span between `old` and `text`: common prefix
5
+ * length (`start`), common suffix length (`suffix`) and removed + inserted
6
+ * char count (`size`).
7
+ */
3
8
  export const findEdit = (old: string, text: string) => {
4
9
  let start = 0;
5
10
  while (start < text.length && old[start] === text[start]) {
6
11
  start += 1;
7
12
  }
13
+ const maxSuffix = Math.min(text.length, old.length) - start;
8
14
  let suffix = 0;
9
- while (
10
- suffix < Math.min(text.length, old.length) - start &&
11
- old[old.length - 1 - suffix] === text[text.length - 1 - suffix]
12
- ) {
15
+ while (suffix < maxSuffix) {
16
+ if (old[old.length - 1 - suffix] !== text[text.length - 1 - suffix]) {
17
+ break;
18
+ }
13
19
  suffix += 1;
14
20
  }
15
21
  const size = old.length + text.length - 2 * (start + suffix);
package/Input/styles.ts CHANGED
@@ -2,7 +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
- //input style
6
5
  input: {
7
6
  flex: 1,
8
7
  paddingLeft: Spacing.M,
@@ -65,7 +64,6 @@ export default StyleSheet.create({
65
64
  marginTop: Spacing.S,
66
65
  },
67
66
 
68
- //text area style
69
67
  textAreaView: {
70
68
  flex: 1,
71
69
  },
@@ -94,7 +92,6 @@ export default StyleSheet.create({
94
92
  marginBottom: Spacing.XS,
95
93
  },
96
94
 
97
- //searchInput
98
95
  searchInputWrapper: {
99
96
  flexDirection: 'row',
100
97
  borderRadius: Radius.XL,
@@ -136,7 +133,6 @@ export default StyleSheet.create({
136
133
  ios: 36,
137
134
  }),
138
135
  },
139
- //OTP
140
136
  otpWrapper: {
141
137
  width: '100%',
142
138
  marginTop: Spacing.M,
@@ -183,7 +179,6 @@ export default StyleSheet.create({
183
179
  opacity: 0.02,
184
180
  letterSpacing: Spacing.XXL,
185
181
  },
186
- //DropDown
187
182
  inputDropDownWrapper: {
188
183
  flexDirection: 'row',
189
184
  marginTop: Spacing.S,
package/Input/utils.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { Localize } from '../Application';
2
2
 
3
+ /**
4
+ * Formats a number Vietnamese-style (`.` thousands, `,` decimals) with
5
+ * `currency` appended; 0, NaN or a falsy value gives `0<currency>`.
6
+ */
3
7
  const formatNumberToMoney = (number: number, currency = '') => {
4
8
  if (!number || isNaN(number) || Number(number) === 0) {
5
9
  return `0${currency}`;
@@ -41,6 +45,10 @@ const formatNumberToMoney = (number: number, currency = '') => {
41
45
  return isNegative ? `-${result}${currency}` : `${result}${currency}`;
42
46
  };
43
47
 
48
+ /**
49
+ * Parses a formatted money string back to a number by stripping
50
+ * `currencyUnit`, separators, `đ` and spaces; unparsable text gives 0.
51
+ */
44
52
  const formatMoneyToNumber = (money: string, currencyUnit: string) => {
45
53
  if (money && money.length > 0) {
46
54
  const moneyString = money
@@ -59,26 +67,20 @@ const formatMoneyToNumber = (money: string, currencyUnit: string) => {
59
67
  return Number(money);
60
68
  };
61
69
 
62
- // --- PHONE VALIDATION ---
63
70
  function formatPhoneNumber(phone: string): string {
64
71
  return phone.replace(/\D/g, '');
65
72
  }
66
73
 
74
+ /**
75
+ * Keeps the digits of `phone` (prepending the `0` a 9-char local number lacks)
76
+ * and returns a localized error when empty or not 10-11 digits.
77
+ */
67
78
  function checkValidPhoneNumber(phone: string): {
68
79
  phoneFormatted: string;
69
80
  error?: string;
70
81
  } {
71
- let phoneNumber = phone;
72
-
73
- // if not starting with 0 and has 9 digits => prepend 0
74
- if (
75
- phoneNumber.length > 0 &&
76
- phoneNumber[0] !== '0' &&
77
- /^\d$/.test(phoneNumber[0]) &&
78
- phoneNumber.length === 9
79
- ) {
80
- phoneNumber = '0' + phoneNumber;
81
- }
82
+ const isMissingLeadingZero = phone.length === 9 && /^[1-9]/.test(phone);
83
+ const phoneNumber = isMissingLeadingZero ? `0${phone}` : phone;
82
84
 
83
85
  const phoneFormatted = formatPhoneNumber(phoneNumber);
84
86
  const localize = new Localize({ vi: {}, en: {} });
package/Layout/Card.tsx CHANGED
@@ -20,6 +20,10 @@ export interface CardProps extends ViewProps {
20
20
  useShadow?: boolean;
21
21
  }
22
22
 
23
+ /**
24
+ * Surface card across the content width minus 12pt margins, with its own grid for child
25
+ * Items; optional background image and light shadow.
26
+ */
23
27
  const Card: React.FC<CardProps> = ({
24
28
  children,
25
29
  style,
@@ -34,10 +38,9 @@ const Card: React.FC<CardProps> = ({
34
38
  const gutterSize = 8;
35
39
  const margin = 12;
36
40
  const widthSection = contentWidth - margin * 2;
37
- let sizePerSpan =
38
- (widthSection - margin * 2 - gutterSize * (numberOfColumns - 1)) /
39
- numberOfColumns -
40
- 1 / numberOfColumns;
41
+ const innerWidth = widthSection - margin * 2;
42
+ const columnsWidth = innerWidth - gutterSize * (numberOfColumns - 1);
43
+ const sizePerSpan = columnsWidth / numberOfColumns - 1 / numberOfColumns;
41
44
 
42
45
  const gridContext: GridContextProps = {
43
46
  numberOfColumns,
@@ -31,6 +31,10 @@ export interface FloatingButtonProps {
31
31
  renderComponent?: () => React.ReactNode;
32
32
  }
33
33
 
34
+ /**
35
+ * Floating action button above the screen bottom. With a label and a scroll value it
36
+ * shrinks to its icon while scrolling down and expands again on scroll up.
37
+ */
34
38
  export const FloatingButton: React.FC<FloatingButtonProps> = ({
35
39
  renderComponent,
36
40
  label = '',
package/Layout/Item.tsx CHANGED
@@ -7,16 +7,20 @@ import { SpanNumber } from './types';
7
7
 
8
8
  export interface ItemProps extends ViewProps {
9
9
  /**
10
- * Represents the width of the Card component in terms of span numbers (1-12).
10
+ * Width in grid columns (1-12). Defaults to the full row.
11
11
  */
12
12
  widthSpan?: SpanNumber;
13
13
 
14
14
  /**
15
- * Optional. Represents the height of the Card component in terms of span numbers (1-12).
15
+ * Optional. Height in grid spans (1-12). Unset keeps the content height.
16
16
  */
17
17
  heightSpan?: SpanNumber;
18
18
  }
19
19
 
20
+ /**
21
+ * Grid cell sized from the enclosing GridContext; outlined when the mini app turns on
22
+ * the baseline debug feature.
23
+ */
20
24
  const Item: React.FC<ItemProps> = ({
21
25
  widthSpan,
22
26
  heightSpan,
@@ -7,12 +7,16 @@ import { SpanNumber } from './types';
7
7
 
8
8
  export interface ItemListProps extends FlatListProps<any> {
9
9
  /**
10
- * Represents the width of the Card component in terms of span numbers (1-12).
10
+ * Width of each item in grid columns (1-12). Defaults to the full row.
11
11
  */
12
12
  widthSpan?: SpanNumber;
13
13
  data: any[];
14
14
  }
15
15
 
16
+ /**
17
+ * FlatList on the grid: `widthSpan` sets how many items share a row, and every item but
18
+ * the first of a row gets the gutter on its left.
19
+ */
16
20
  const ItemList = forwardRef(
17
21
  (
18
22
  {
@@ -30,6 +34,9 @@ const ItemList = forwardRef(
30
34
  const indexBegin = props?.data?.map(
31
35
  (_i: any, index: number) => index * numColumns,
32
36
  );
37
+ const defaultKeyExtractor = (item: any, index: number) => {
38
+ return (item?.id ?? item?.key ?? index).toString();
39
+ };
33
40
  const _renderItem = (item: any) => {
34
41
  if (indexBegin?.includes(item?.index)) {
35
42
  return <Item widthSpan={widthItem}>{renderItem?.(item)}</Item>;
@@ -46,12 +53,7 @@ const ItemList = forwardRef(
46
53
  ref={ref as React.RefObject<FlatList>}
47
54
  {...props}
48
55
  key={`ItemList-${numColumns}`}
49
- keyExtractor={
50
- // prefer caller's keyExtractor; fallback to common id/key/index
51
- (props as any)?.keyExtractor ??
52
- ((item: any, index: number) =>
53
- (item?.id ?? item?.key ?? index).toString())
54
- }
56
+ keyExtractor={(props as any)?.keyExtractor ?? defaultKeyExtractor}
55
57
  numColumns={numColumns}
56
58
  renderItem={_renderItem}
57
59
  horizontal={false}
@@ -7,11 +7,12 @@ import { SpanNumber } from './types';
7
7
 
8
8
  export interface ItemSectionListProps extends SectionListProps<any> {
9
9
  /**
10
- * Represents the width of the Card component in terms of span numbers (1-12).
10
+ * Width of each item in grid columns (1-12). Defaults to the full row.
11
11
  */
12
12
  widthSpan?: SpanNumber;
13
13
  }
14
14
 
15
+ /** SectionList whose items sit on the grid, `widthSpan` columns wide, a gutter apart. */
15
16
  const ItemSectionList: FC<ItemSectionListProps> = ({
16
17
  renderItem,
17
18
  widthSpan,