@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
package/Layout/utils.ts CHANGED
@@ -15,9 +15,8 @@ import {
15
15
  import { ReservedRegion, ReservedRegionsContext } from './ReservedRegions';
16
16
 
17
17
  /**
18
- * validate children type
19
- * @param children
20
- * @param childrenTypes
18
+ * Warns when a child is not one of `childrenTypes` (Fragments pass) and returns the
19
+ * children, flattened to the valid elements when given an array.
21
20
  */
22
21
  const validateChildren = (
23
22
  children: React.ReactNode | React.ReactNode[],
@@ -54,20 +53,20 @@ const validateChildren = (
54
53
  const NO_INSETS: EdgeInsets = { top: 0, right: 0, bottom: 0, left: 0 };
55
54
 
56
55
  /**
57
- * how far system regions reach into a side strip from its top and from its bottom end
58
- * (e.g. the camera, which sits at either end depending on the rotation), and the center
59
- * y of the region reaching furthest from the top
56
+ * How far system regions (e.g. the camera, at either end depending on the rotation) reach
57
+ * into a side strip from its top and bottom ends, plus the center y of the region reaching
58
+ * furthest from the top.
60
59
  */
61
60
  const getStripOcclusion = (
62
61
  regions: ReservedRegion[],
63
62
  stripLeft: number,
64
63
  stripRight: number,
65
64
  windowHeight: number,
66
- ) =>
67
- regions.reduce(
65
+ ) => {
66
+ return regions.reduce(
68
67
  (occlusion, region) => {
69
- const overlapsStrip =
70
- region.x < stripRight && region.x + region.width > stripLeft;
68
+ const regionRight = region.x + region.width;
69
+ const overlapsStrip = region.x < stripRight && regionRight > stripLeft;
71
70
  if (!overlapsStrip) {
72
71
  return occlusion;
73
72
  }
@@ -84,10 +83,11 @@ const getStripOcclusion = (
84
83
  },
85
84
  { top: 0, topCenter: 0, bottom: 0 },
86
85
  );
86
+ };
87
87
 
88
88
  /**
89
- * how far the clock and battery clusters of a status bar on the top edge reach in from
90
- * each side. `undefined` without reserved regions there
89
+ * How far the clock and battery clusters of a status bar on the top edge reach in from
90
+ * each side. `undefined` without reserved regions there.
91
91
  */
92
92
  const getStatusBarClusters = (
93
93
  regions: ReservedRegion[],
@@ -95,12 +95,11 @@ const getStatusBarClusters = (
95
95
  topInset: number,
96
96
  ) => {
97
97
  const midX = windowWidth / 2;
98
- const clusters = regions.filter(
99
- region =>
100
- region.y < topInset &&
101
- region.y + region.height <= topInset &&
102
- (region.x + region.width <= midX || region.x >= midX),
103
- );
98
+ const clusters = regions.filter(region => {
99
+ const isInTopInset = region.y + region.height <= topInset;
100
+ const isOnOneSide = region.x + region.width <= midX || region.x >= midX;
101
+ return region.y < topInset && isInTopInset && isOnOneSide;
102
+ });
104
103
  if (!clusters.length) {
105
104
  return undefined;
106
105
  }
@@ -118,8 +117,10 @@ const getStatusBarClusters = (
118
117
  );
119
118
  };
120
119
 
121
- const isIPhone = () =>
122
- Platform.OS === 'ios' && Platform.constants.interfaceIdiom === 'phone';
120
+ /** iOS phone idiom: iPad keeps the plain header and is never treated as iPhone Duo. */
121
+ const isIPhone = () => {
122
+ return Platform.OS === 'ios' && Platform.constants.interfaceIdiom === 'phone';
123
+ };
123
124
 
124
125
  /**
125
126
  * Header height and the room it keeps above the bar row for the status bar. On iPhone the
@@ -141,7 +142,7 @@ const getHeaderLayout = (topInset: number) => {
141
142
 
142
143
  /**
143
144
  * Header whose bar row is centered on a window y, at least iPhoneMinHeight tall and
144
- * reaching 8pt below a side bar control centered there
145
+ * reaching 8pt below a side bar control centered there.
145
146
  */
146
147
  const getCenteredHeaderLayout = (centerY: number) => {
147
148
  const headerHeight = Math.max(
@@ -221,8 +222,7 @@ const useScreenLayout = (): ScreenLayout => {
221
222
  if (sideInset >= DuoMetrics.controlSize + Spacing.M) {
222
223
  const edge = insets.right >= insets.left ? 'right' : 'left';
223
224
  const stripLeft = edge === 'right' ? width - sideInset : 0;
224
- // keep controls clear of the camera and of the status bar cluster, which grows for
225
- // calls and Live Activities. Region frames already include the system's margin
225
+ // clear the camera and status bar cluster; region frames include the system margin
226
226
  const occlusion = getStripOcclusion(
227
227
  reservedRegions,
228
228
  stripLeft,
@@ -242,8 +242,7 @@ const useScreenLayout = (): ScreenLayout => {
242
242
  } else if (height >= DuoMetrics.innerMinHeight) {
243
243
  top = DuoMetrics.firstItemTopLandscape;
244
244
  } else {
245
- // outer display in landscape: no status bar in the strip, the title row centers
246
- // on the camera when it sits at the top end, otherwise on the back button
245
+ // outer landscape: title row centers on a camera at the top end, else on back
247
246
  top = DuoMetrics.edgeInset;
248
247
  const cameraAtTop = occlusion.top > top;
249
248
  const backCenterY = top + DuoMetrics.controlSize / 2;
@@ -285,7 +284,7 @@ const useScreenLayout = (): ScreenLayout => {
285
284
 
286
285
  /**
287
286
  * Header row options for react-navigation: its height, the room for the status bar, and
288
- * on iPhone Duo inner portrait the buttons kept clear of the clock and battery clusters
287
+ * on iPhone Duo inner portrait the buttons kept clear of the clock and battery clusters.
289
288
  */
290
289
  const getHeaderRowOptions = ({
291
290
  headerHeight,
@@ -305,9 +304,7 @@ const getHeaderRowOptions = ({
305
304
  };
306
305
  };
307
306
 
308
- /**
309
- * Side margin @react-navigation/elements Header adds to its content row
310
- */
307
+ /** Side margin @react-navigation/elements Header adds to its content row. */
311
308
  const getHeaderMargin = (width: number) => {
312
309
  if (Platform.OS === 'ios' && width >= HeaderMetrics.largeWidth) {
313
310
  return HeaderMetrics.largeMargin;
@@ -321,11 +318,9 @@ const InnerEdgeStyles: Record<SideBarEdge, ViewStyle> = {
321
318
  };
322
319
 
323
320
  /**
324
- * Absolute style that floats a header item into the side bar, relative to the header
325
- * container (left or right) rendering it, 12pt from the strip's inner edge.
321
+ * Absolute style floating a header item into the side bar, 12pt from the strip's inner
322
+ * edge, relative to the `container` rendering it; `slot` 0 is back, 1 the toolkit below.
326
323
  * `undefined` while there is no side bar.
327
- * @param container header container rendering the item
328
- * @param slot position in the side bar, 0 for back, 1 for the toolkit below it
329
324
  */
330
325
  const useSideBarItemStyle = (
331
326
  container: 'left' | 'right',
@@ -338,10 +333,8 @@ const useSideBarItemStyle = (
338
333
 
339
334
  const margin = getHeaderMargin(width);
340
335
  const columnLeft = sideBar.edge === 'left' ? 0 : width - sideBar.width;
341
- const top =
342
- sideBar.top -
343
- headerStatusBarHeight +
344
- slot * (DuoMetrics.controlSize + Spacing.M);
336
+ const slotOffset = slot * (DuoMetrics.controlSize + Spacing.M);
337
+ const top = sideBar.top - headerStatusBarHeight + slotOffset;
345
338
  const innerEdgeStyle = InnerEdgeStyles[sideBar.edge];
346
339
 
347
340
  if (container === 'left') {
@@ -364,6 +357,7 @@ const useSideBarItemStyle = (
364
357
  };
365
358
  };
366
359
 
360
+ /** Column count of the content grid: 24 on content wider than 600pt, otherwise 12. */
367
361
  const useGridSystem = () => {
368
362
  const { contentWidth } = useScreenLayout();
369
363
  const numberOfColumns = contentWidth > 600 ? 24 : 12;
@@ -6,6 +6,10 @@ import { ApplicationContext } from '../Context';
6
6
  import { hexToRGBA } from './utils';
7
7
  import { LoaderProps } from './types';
8
8
 
9
+ /**
10
+ * Three-dot Lottie loader recolored to `color` (theme primary by default).
11
+ * Recoloring mutates the shared animation JSON, so `Loader` remounts on change.
12
+ */
9
13
  const DotLoader: FC<LoaderProps> = ({ color, style }) => {
10
14
  const { theme } = useContext(ApplicationContext);
11
15
 
package/Loader/Loader.tsx CHANGED
@@ -3,6 +3,10 @@ import {LoaderProps} from './types';
3
3
  import DotLoader from './DotLoader';
4
4
  import Spinner from './Spinner';
5
5
 
6
+ /**
7
+ * Dot (default) or spinner loader. Keyed by `color` so a color change remounts
8
+ * the Lottie view with the recolored source.
9
+ */
6
10
  const Loader: FC<LoaderProps> = ({type = 'dot', ...props}) => {
7
11
  if (type === 'spinner') {
8
12
  return <Spinner {...props} key={props.color} />;
@@ -9,6 +9,7 @@ import styles from './styles';
9
9
  import { ProgressBarProps } from './types';
10
10
  import { ApplicationContext } from '../Context';
11
11
 
12
+ /** Full-width bar that animates its fill to `percent`, clamped to 0–100. */
12
13
  const ProgressBar: FC<ProgressBarProps> = ({ percent = 0, style }) => {
13
14
  const { theme } = useContext(ApplicationContext);
14
15
  const animation = useSharedValue(0);
@@ -6,6 +6,10 @@ import { hexToRGBA } from './utils';
6
6
  import { ApplicationContext } from '../Context';
7
7
  import { LoaderProps } from './types';
8
8
 
9
+ /**
10
+ * Lottie spinner recolored to `color` (theme primary by default).
11
+ * Recoloring mutates the shared animation JSON, so `Loader` remounts on change.
12
+ */
9
13
  const Spinner: FC<LoaderProps> = ({ color, style }) => {
10
14
  const { theme } = useContext(ApplicationContext);
11
15
 
package/Loader/utils.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Converts `#rgb`/`#rrggbb` (the `#` is optional) to Lottie's `[r, g, b]` in
3
+ * 0–1, rounded to 3 decimals. Throws on an invalid hex.
4
+ */
1
5
  export const hexToRGBA = (hex: string) => {
2
6
  const normalizedHex = hex.startsWith('#') ? hex.slice(1) : hex;
3
7
 
@@ -5,13 +9,13 @@ export const hexToRGBA = (hex: string) => {
5
9
  throw new Error('Invalid HEX color.');
6
10
  }
7
11
 
8
- const expandedHex =
9
- normalizedHex.length === 3
10
- ? normalizedHex
11
- .split('')
12
- .map(val => val + val)
13
- .join('')
14
- : normalizedHex;
12
+ let expandedHex = normalizedHex;
13
+ if (normalizedHex.length === 3) {
14
+ expandedHex = normalizedHex
15
+ .split('')
16
+ .map(val => val + val)
17
+ .join('');
18
+ }
15
19
 
16
20
  const hexMatch = expandedHex.match(/.{2}/g);
17
21
 
@@ -4,6 +4,10 @@ import styles from './styles';
4
4
  import { DotProps } from './types';
5
5
  import { ApplicationContext } from '../Context';
6
6
 
7
+ /**
8
+ * One pagination dot: a wide bar in the caller's color when active, otherwise
9
+ * a small dot in the theme pressed color.
10
+ */
7
11
  const Dot: FC<DotProps> = ({ active, style }) => {
8
12
  const { theme } = useContext(ApplicationContext);
9
13
  const pressedColor = theme.colors.background.pressed;
@@ -6,6 +6,7 @@ import styles from './styles';
6
6
  import { Spacing } from '../Consts';
7
7
  import { ApplicationContext, MiniAppContext } from '../Context';
8
8
 
9
+ /** Row of primary-color dots with the `activeIndex` dot widened. */
9
10
  const PaginationDot: FC<ChildPaginationProps> = ({
10
11
  dataLength = 2,
11
12
  activeIndex = 0,
@@ -6,6 +6,7 @@ import { Text } from '../Text';
6
6
  import { Colors } from '../Consts';
7
7
  import { MiniAppContext } from '../Context';
8
8
 
9
+ /** `n/total` counter on a translucent dark pill. */
9
10
  const PaginationNumber: FC<ChildPaginationProps> = ({
10
11
  activeIndex = 0,
11
12
  dataLength = 2,
@@ -14,15 +15,16 @@ const PaginationNumber: FC<ChildPaginationProps> = ({
14
15
  const context = useContext<any>(MiniAppContext);
15
16
 
16
17
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
18
+ const pageLabel = `${activeIndex + 1}/${dataLength}`;
17
19
 
18
20
  return (
19
21
  <View
20
22
  style={[style, styles.row, showBaseLineDebug && styles.debugBaseLine]}
21
23
  >
22
24
  <View style={styles.paginationNumberContainer}>
23
- <Text color={Colors.black_01} typography={'label_default_medium'}>{`${
24
- activeIndex + 1
25
- }/${dataLength}`}</Text>
25
+ <Text color={Colors.black_01} typography={'label_default_medium'}>
26
+ {pageLabel}
27
+ </Text>
26
28
  </View>
27
29
  </View>
28
30
  );
@@ -13,6 +13,11 @@ import { ApplicationContext, MiniAppContext } from '../Context';
13
13
 
14
14
  const INDICATOR_WIDTH = 24;
15
15
  const INDICATOR_CONTAINER_WIDTH = 72;
16
+
17
+ /**
18
+ * Horizontal scroll view with a small indicator that tracks the scroll offset.
19
+ * The indicator shows only when the content is wider than the view.
20
+ */
16
21
  const PaginationScroll: FC<ScrollIndicatorProps> = ({
17
22
  style,
18
23
  children,
@@ -6,6 +6,7 @@ import Dot from './Dot';
6
6
  import { Colors, Spacing } from '../Consts';
7
7
  import { MiniAppContext } from '../Context';
8
8
 
9
+ /** Pagination dots on a translucent dark pill, for use over images. */
9
10
  const PaginationWhiteDot: FC<ChildPaginationProps> = ({
10
11
  dataLength = 2,
11
12
  activeIndex = 0,
@@ -5,6 +5,10 @@ import PaginationDot from './PaginationDot';
5
5
  import PaginationNumber from './PaginationNumber';
6
6
  import PaginationScroll from './PaginationScroll';
7
7
 
8
+ /**
9
+ * Page indicator: `type` picks plain dots, dots on a dark pill or an `n/total`
10
+ * counter. `PaginationScroll` is the separate scroll-indicator variant.
11
+ */
8
12
  const Pagination: FC<PaginationProps> = props => {
9
13
  const {type} = props;
10
14
 
@@ -5,9 +5,9 @@ export type PaginationProps = {
5
5
  /**
6
6
  * Optional. Defines the visual style of the Pagination component.
7
7
  * - 'default': Represents the default style.
8
- * - 'black_white': Represents a pagination that have black background.
8
+ * - 'black_white': Represents dots on a translucent dark background.
9
9
  * - 'number': Represents a style where pages are represented by numbers.
10
- * - 'scroll': Represents a scrollable style.
10
+ * - 'scroll': Rendered as 'default'; use `PaginationScroll` instead.
11
11
  */
12
12
  type?: 'default' | 'black_white' | 'number' | 'scroll';
13
13
 
@@ -11,6 +11,13 @@ import { useModalLayout } from '../Application/modalLayout';
11
11
  import { ModalIconButton } from '../Application/ModalIconButton';
12
12
  import { splitHighlightText } from './utils';
13
13
 
14
+ const MAX_ROW_TITLE_LENGTH = 12;
15
+
16
+ /**
17
+ * Notify popup body: optional image, title, description, error code and one or
18
+ * two actions. Actions close via `onRequestClose` if given, else pop the modal
19
+ * and then run the callback.
20
+ */
14
21
  const PopupNotify: React.FC<PopupNotifyProps> = ({
15
22
  image,
16
23
  title = 'Title',
@@ -43,10 +50,6 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
43
50
  descriptionStyle = { maxHeight: scaledMaxHeight };
44
51
  }
45
52
 
46
- /**
47
- * on action popup
48
- * @param callback
49
- */
50
53
  const onAction = (callback?: () => void) => {
51
54
  if (typeof onRequestClose === 'function') {
52
55
  onRequestClose?.(callback);
@@ -56,9 +59,6 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
56
59
  }
57
60
  };
58
61
 
59
- /**
60
- * build main action of popup
61
- */
62
62
  const buildAction = () => {
63
63
  const renderRow = () => {
64
64
  return (
@@ -126,10 +126,13 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
126
126
  };
127
127
 
128
128
  if (buttonDirection === 'auto') {
129
- if (
130
- secondary?.title &&
131
- (secondary.title.length > 12 || primary.title.length > 12)
132
- ) {
129
+ const secondaryTitle = secondary?.title;
130
+ if (!secondaryTitle) {
131
+ return renderRow();
132
+ }
133
+ const isLongSecondary = secondaryTitle.length > MAX_ROW_TITLE_LENGTH;
134
+ const isLongPrimary = primary.title.length > MAX_ROW_TITLE_LENGTH;
135
+ if (isLongSecondary || isLongPrimary) {
133
136
  return renderColumn();
134
137
  }
135
138
  return renderRow();
@@ -140,9 +143,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
140
143
  return renderColumn();
141
144
  };
142
145
 
143
- /**
144
- * build description text, highlighted keywords keep size/lineHeight and use weight 600
145
- */
146
+ /** Highlighted keywords use weight 600 but keep the body size and line height. */
146
147
  const renderDescription = (text: string) => {
147
148
  const segments = splitHighlightText(text, highlightText);
148
149
  if (!segments) {
@@ -160,9 +161,6 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
160
161
  });
161
162
  };
162
163
 
163
- /**
164
- * build close action
165
- */
166
164
  const buildCloseIcon = () => {
167
165
  if (typeof onIconClose !== 'function') {
168
166
  return null;
@@ -180,6 +178,28 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
180
178
  );
181
179
  };
182
180
 
181
+ const hasCloseIcon = typeof onIconClose === 'function';
182
+ // keeps the title clear of the close button on a text only popup
183
+ const titleStyle = !image && hasCloseIcon && styles.titleClearClose;
184
+
185
+ let descriptionContent: React.ReactNode = description;
186
+ if (typeof description === 'string') {
187
+ descriptionContent = (
188
+ <Text
189
+ accessibilityLabel={'description_popup_notify'}
190
+ typography={'body_default_regular'}
191
+ onTextLayout={({ nativeEvent: { lines } }) => {
192
+ const enableScroll = lines.length > 8.5;
193
+ if (enableScroll !== scrollContent) {
194
+ setScrollContent(enableScroll);
195
+ }
196
+ }}
197
+ >
198
+ {renderDescription(description)}
199
+ </Text>
200
+ );
201
+ }
202
+
183
203
  const body = (
184
204
  <>
185
205
  {!!image && (
@@ -190,32 +210,12 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
190
210
  typography={'header_default_bold'}
191
211
  numberOfLines={2}
192
212
  accessibilityLabel={'title_popup_notify'}
193
- // keeps the title clear of the close button on a text only popup
194
- style={
195
- !image &&
196
- typeof onIconClose === 'function' &&
197
- styles.titleClearClose
198
- }
213
+ style={titleStyle}
199
214
  >
200
215
  {title}
201
216
  </Text>
202
217
  <Description style={[styles.description, descriptionStyle]}>
203
- {typeof description === 'string' ? (
204
- <Text
205
- accessibilityLabel={'description_popup_notify'}
206
- typography={'body_default_regular'}
207
- onTextLayout={({ nativeEvent: { lines } }) => {
208
- const enableScroll = lines.length > 8.5;
209
- if (enableScroll !== scrollContent) {
210
- setScrollContent(enableScroll);
211
- }
212
- }}
213
- >
214
- {renderDescription(description)}
215
- </Text>
216
- ) : (
217
- description
218
- )}
218
+ {descriptionContent}
219
219
  </Description>
220
220
  {!!error && (
221
221
  <View style={styles.information}>
@@ -12,6 +12,10 @@ const ICON_CLOSE_SIZE = 20;
12
12
  // close button row under the image: marginTop + vertical padding + icon
13
13
  const CLOSE_ROW_HEIGHT = Spacing.S * 3 + ICON_CLOSE_SIZE;
14
14
 
15
+ /**
16
+ * Promotion popup body: a tappable portrait image with a close button under it.
17
+ * Both close the popup first, then call `onActionClick` / `onIconClose`.
18
+ */
15
19
  const PopupPromotion: React.FC<PopupPromotionProps> = ({
16
20
  image,
17
21
  onIconClose,
@@ -33,10 +37,6 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
33
37
 
34
38
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
35
39
 
36
- /**
37
- * on action popup
38
- * @param callback
39
- */
40
40
  const onAction = (callback?: () => void) => {
41
41
  if (typeof onRequestClose === 'function') {
42
42
  onRequestClose?.(callback);
@@ -46,9 +46,6 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
46
46
  }
47
47
  };
48
48
 
49
- /**
50
- * build close action
51
- */
52
49
  const buildCloseIcon = () => {
53
50
  return (
54
51
  <View style={styles.iconCloseContainer}>
package/Popup/types.ts CHANGED
@@ -56,7 +56,7 @@ export type PopupNotifyProps = {
56
56
  onIconClose?: () => void;
57
57
 
58
58
  /**
59
- * onRequestClose on request close for modal
59
+ * `onRequestClose`: Optional. Closes the modal, then runs `callback`. ModalScreen injects it; without it the popup uses `navigator.pop()`.
60
60
  */
61
61
  onRequestClose?: (callback?: () => void) => void;
62
62
  };
@@ -73,17 +73,17 @@ export type PopupPromotionProps = {
73
73
  image: string;
74
74
 
75
75
  /**
76
- * `onClose`: Optional. A callback function that is called when the PopupPromotion component is closed.
76
+ * `onIconClose`: Optional. A callback function that is called after the popup closes from the close icon.
77
77
  */
78
78
  onIconClose?: () => void;
79
79
 
80
80
  /**
81
- * `onActionClick`: Optional. A callback function that is called when the PopupPromotion component is closed.
81
+ * `onActionClick`: Optional. A callback function that is called after the popup closes from an image tap.
82
82
  */
83
83
  onActionClick?: () => void;
84
84
 
85
85
  /**
86
- * onRequestClose on request close for modal
86
+ * `onRequestClose`: Optional. Closes the modal, then runs `callback`. ModalScreen injects it; without it the popup uses `navigator.pop()`.
87
87
  */
88
88
  onRequestClose?: (callback?: () => void) => void;
89
89
  };
package/Popup/utils.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  type HighlightSegment = { text: string; highlight: boolean };
2
2
 
3
+ /**
4
+ * True for cased-script letters, digits and `_`; caseless scripts (CJK, Thai)
5
+ * count as word breaks.
6
+ */
3
7
  const isWordChar = (char?: string) => {
4
8
  if (!char) {
5
9
  return false;
package/Radio/index.tsx CHANGED
@@ -11,6 +11,10 @@ import {
11
11
  MiniAppContext,
12
12
  } from '../Context';
13
13
 
14
+ /**
15
+ * Radio button, selected when `value === groupValue`. `onChange` is called on
16
+ * press without arguments; the parent updates `groupValue`.
17
+ */
14
18
  const Radio: FC<RadioProps> = ({
15
19
  value,
16
20
  groupValue,
@@ -21,6 +21,10 @@ import { Colors, Styles } from '../Consts';
21
21
  import styles from './styles';
22
22
  import { ScreenContext, SkeletonContext } from '../Context';
23
23
 
24
+ /**
25
+ * Shimmering placeholder that fills its parent. Reports loading to the screen
26
+ * context while mounted.
27
+ */
24
28
  const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
25
29
  const screen = useContext<any>(ScreenContext);
26
30
  const [width, setWidth] = useState(0);
@@ -84,6 +88,10 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
84
88
  );
85
89
  };
86
90
 
91
+ /**
92
+ * Shares `loading` with descendants (e.g. `Image` shows a skeleton) and animates
93
+ * the layout change when it flips.
94
+ */
87
95
  const SkeletonProvider: React.FC<{ loading: boolean; children: any }> = ({
88
96
  loading = true,
89
97
  children,
package/Switch/index.tsx CHANGED
@@ -6,6 +6,7 @@ import { Colors } from '../Consts';
6
6
  import { useComponentId } from '../Application';
7
7
  import { ComponentContext, MiniAppContext } from '../Context';
8
8
 
9
+ /** On/off toggle; `onChange` receives the negated `value`. */
9
10
  const Switch: FC<SwitchProps> = ({
10
11
  value = false,
11
12
  onChange,
package/Tag/index.tsx CHANGED
@@ -25,6 +25,10 @@ const textColor = {
25
25
  grey: Colors.black_12,
26
26
  };
27
27
 
28
+ /**
29
+ * Small status label with an optional leading icon. `customColor` replaces the
30
+ * palette color and turns the text white.
31
+ */
28
32
  const Tag: FC<TagProps> = ({
29
33
  label = 'Label',
30
34
  icon,
@@ -72,7 +76,7 @@ const Tag: FC<TagProps> = ({
72
76
  `${componentName}/${label}`,
73
77
  accessibilityLabel,
74
78
  );
75
- //Check if custom color is [color]_03 (only check in dev mode)
79
+ // Dev builds only accept `Colors.*_03` as customColor; release accepts any.
76
80
  const validateCustomColor = (hex: string) => {
77
81
  if (__DEV__) {
78
82
  const colorCore: { [key: string]: string } = Colors;
package/Tag/types.ts CHANGED
@@ -8,7 +8,7 @@ export type TagProps = {
8
8
 
9
9
  /**
10
10
  * `size`: Optional. Defines the size of the Tag component.
11
- * Defaults to 'medium' if not provided.
11
+ * Defaults to 'large' if not provided.
12
12
  */
13
13
  size?: 'medium' | 'large';
14
14
 
@@ -29,8 +29,8 @@ export type TagProps = {
29
29
  style?: ViewStyle;
30
30
 
31
31
  /**
32
- * `customColor`: Optional. Represents a custom color string (e.g., hex, rgb, rgba, etc.)
33
- * that can be applied to the Tag component.
32
+ * `customColor`: Optional. Custom background color; the label turns white.
33
+ * Dev builds ignore values other than the `Colors.*_03` tokens.
34
34
  */
35
35
  customColor?: string;
36
36