@momo-kits/foundation 0.169.6-beta.1 → 0.169.6

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 (128) 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.styles.ts +1 -18
  6. package/Application/BottomTab/BottomTabBar.tsx +58 -57
  7. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  8. package/Application/BottomTab/CurvedContainer.tsx +16 -20
  9. package/Application/BottomTab/CustomBottomTabItem.tsx +14 -15
  10. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  11. package/Application/BottomTab/DuoRailTabBar.tsx +20 -20
  12. package/Application/BottomTab/DuoTabBar.tsx +11 -24
  13. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  14. package/Application/BottomTab/TabScreen.tsx +1 -5
  15. package/Application/BottomTab/index.tsx +33 -30
  16. package/Application/BottomTab/metrics.ts +25 -48
  17. package/Application/BottomTab/tabItems.ts +1 -1
  18. package/Application/BottomTab/usePressLift.ts +2 -2
  19. package/Application/Components/BackgroundImageView.tsx +4 -0
  20. package/Application/Components/DuoGlassBackground.tsx +5 -4
  21. package/Application/Components/HeaderAnimated.tsx +4 -0
  22. package/Application/Components/HeaderBackground.tsx +4 -0
  23. package/Application/Components/HeaderExtendHeader.tsx +11 -10
  24. package/Application/Components/HeaderLeft.tsx +2 -1
  25. package/Application/Components/HeaderRight.tsx +2 -1
  26. package/Application/Components/HeaderTitle.shared.ts +11 -9
  27. package/Application/Components/HeaderTitle.tsx +4 -3
  28. package/Application/Components/HeaderToolkitAction.tsx +17 -20
  29. package/Application/Components/NavigationButton.tsx +2 -1
  30. package/Application/Components/SearchHeader.tsx +17 -10
  31. package/Application/Components/TitleJourney.tsx +2 -2
  32. package/Application/Components/TitleLocation.tsx +2 -2
  33. package/Application/Components/TitleUser.tsx +2 -2
  34. package/Application/Components/useToolkitActions.tsx +17 -32
  35. package/Application/Localize.ts +7 -0
  36. package/Application/ModalScreen.tsx +9 -3
  37. package/Application/Navigation.ts +14 -16
  38. package/Application/NavigationContainer.tsx +19 -16
  39. package/Application/Navigator.ts +20 -48
  40. package/Application/ScaleSizeProvider.tsx +13 -6
  41. package/Application/StackScreen.tracking.ts +11 -26
  42. package/Application/StackScreen.tsx +8 -35
  43. package/Application/TooltipPortal.tsx +12 -5
  44. package/Application/WidgetContainer.tsx +10 -4
  45. package/Application/modalLayout.ts +13 -14
  46. package/Application/navigationOptions.tsx +21 -17
  47. package/Application/types.tabs.ts +1 -0
  48. package/Badge/Badge.tsx +5 -0
  49. package/Badge/BadgeDot.tsx +1 -0
  50. package/Badge/BadgeDotAnimation.tsx +15 -7
  51. package/Badge/BadgeRibbon.tsx +46 -38
  52. package/Badge/Shape.tsx +2 -0
  53. package/Badge/types.ts +2 -2
  54. package/Button/index.tsx +5 -16
  55. package/Button/utils.ts +9 -15
  56. package/CheckBox/index.tsx +4 -0
  57. package/Consts/theme.ts +10 -10
  58. package/Context/index.ts +5 -1
  59. package/Divider/DashDivider.tsx +1 -0
  60. package/Divider/index.tsx +4 -0
  61. package/FoundationList/index.tsx +4 -0
  62. package/Icon/index.tsx +6 -6
  63. package/Icon/types.ts +1 -1
  64. package/IconButton/index.tsx +4 -12
  65. package/Image/index.tsx +9 -7
  66. package/Input/Input.tsx +8 -9
  67. package/Input/InputDropDown.tsx +10 -12
  68. package/Input/InputMoney.tsx +12 -8
  69. package/Input/InputMoneyView.tsx +14 -7
  70. package/Input/InputOTP.tsx +35 -33
  71. package/Input/InputPhoneNumber.tsx +8 -9
  72. package/Input/InputSearch.tsx +4 -6
  73. package/Input/InputTextArea.tsx +4 -0
  74. package/Input/SearchTextTyping.tsx +4 -0
  75. package/Input/SystemTextInput.tsx +7 -2
  76. package/Input/TextTyping.tsx +4 -0
  77. package/Input/common.tsx +26 -12
  78. package/Input/index.tsx +17 -20
  79. package/Input/moneyUtils.ts +10 -4
  80. package/Input/styles.ts +0 -5
  81. package/Input/utils.ts +14 -12
  82. package/Layout/Card.tsx +7 -4
  83. package/Layout/FloatingButton.tsx +4 -0
  84. package/Layout/Item.tsx +6 -2
  85. package/Layout/ItemList.tsx +9 -7
  86. package/Layout/ItemSectionList.tsx +2 -1
  87. package/Layout/ReservedRegions.tsx +25 -25
  88. package/Layout/Screen.tsx +56 -58
  89. package/Layout/Section.tsx +8 -5
  90. package/Layout/TrackingScope.tsx +1 -0
  91. package/Layout/screenContent.tsx +4 -2
  92. package/Layout/screenFooter.tsx +28 -18
  93. package/Layout/screenHeader.tsx +34 -31
  94. package/Layout/screenQuickReturn.tsx +17 -20
  95. package/Layout/screenScroll.ts +4 -7
  96. package/Layout/screenTypes.ts +3 -2
  97. package/Layout/types.ts +2 -4
  98. package/Layout/utils.ts +31 -37
  99. package/Loader/DotLoader.tsx +4 -0
  100. package/Loader/Loader.tsx +4 -0
  101. package/Loader/ProgressBar.tsx +1 -0
  102. package/Loader/Spinner.tsx +4 -0
  103. package/Loader/utils.ts +11 -7
  104. package/Pagination/Dot.tsx +4 -0
  105. package/Pagination/PaginationDot.tsx +1 -0
  106. package/Pagination/PaginationNumber.tsx +5 -3
  107. package/Pagination/PaginationScroll.tsx +5 -0
  108. package/Pagination/PaginationWhiteDot.tsx +1 -0
  109. package/Pagination/index.tsx +4 -0
  110. package/Pagination/types.ts +2 -2
  111. package/Popup/PopupNotify.tsx +44 -48
  112. package/Popup/PopupPromotion.tsx +4 -7
  113. package/Popup/types.ts +4 -4
  114. package/Popup/utils.ts +4 -0
  115. package/Radio/index.tsx +4 -0
  116. package/Skeleton/index.tsx +8 -0
  117. package/Switch/index.tsx +1 -0
  118. package/Tag/index.tsx +5 -1
  119. package/Tag/types.ts +3 -3
  120. package/Text/index.tsx +14 -3
  121. package/Text/styles.ts +1 -1
  122. package/Text/types.ts +1 -6
  123. package/Text/utils.ts +20 -4
  124. package/Title/index.tsx +41 -32
  125. package/ViewBoundary/index.tsx +4 -2
  126. package/index.ts +0 -6
  127. package/package.json +1 -1
  128. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -9,7 +9,8 @@ import { AnimatedHeader } from '../Application/types';
9
9
  type ContentInset = { insetLeft: number; insetRight: number };
10
10
 
11
11
  /**
12
- * build content for screen for grid rule
12
+ * Wraps each screen child (Fragments flattened) in a view with the screen's top spacing
13
+ * and side insets; a child with `useMargin: false` gets no top spacing.
13
14
  */
14
15
  export const renderContent = (content: any, layout: ContentInset): any => {
15
16
  const results = validateChildren(content, [undefined]);
@@ -64,7 +65,8 @@ export const renderContent = (content: any, layout: ContentInset): any => {
64
65
  };
65
66
 
66
67
  /**
67
- * render top navigation banner
68
+ * Animated banner from `animatedHeader.component`, clipped to the banner height plus
69
+ * `layoutOffset`. Nothing without a component.
68
70
  */
69
71
  export const renderAnimatedHeader = (
70
72
  animatedHeader: AnimatedHeader | undefined,
@@ -16,6 +16,10 @@ type ScreenFooterProps = {
16
16
  surfaceColor: string;
17
17
  };
18
18
 
19
+ /**
20
+ * Screen footer on the surface color with a top shadow, padded above the home indicator,
21
+ * or above the floating tab bar on a tab screen.
22
+ */
19
23
  export const ScreenFooter = ({
20
24
  footer,
21
25
  marginFooter,
@@ -26,28 +30,34 @@ export const ScreenFooter = ({
26
30
  duoTabInset,
27
31
  bottomInset,
28
32
  surfaceColor,
29
- }: ScreenFooterProps) => (
30
- <View
31
- pointerEvents={'box-none'}
32
- style={[
33
- hasContentInset && styles.footerClip,
34
- {
35
- marginLeft: insetLeft,
36
- marginRight: insetRight,
37
- },
38
- ]}
39
- >
33
+ }: ScreenFooterProps) => {
34
+ let bottomSpace = bottomInset;
35
+ if (isTab && duoTabInset) {
36
+ bottomSpace = duoTabInset;
37
+ }
38
+
39
+ return (
40
40
  <View
41
+ pointerEvents={'box-none'}
41
42
  style={[
42
- styles.shadow,
43
+ hasContentInset && styles.footerClip,
43
44
  {
44
- paddingBottom:
45
- (isTab && duoTabInset ? duoTabInset : bottomInset) + Spacing.S,
46
- backgroundColor: surfaceColor,
45
+ marginLeft: insetLeft,
46
+ marginRight: insetRight,
47
47
  },
48
48
  ]}
49
49
  >
50
- <Section useMargin={marginFooter}>{footer}</Section>
50
+ <View
51
+ style={[
52
+ styles.shadow,
53
+ {
54
+ paddingBottom: bottomSpace + Spacing.S,
55
+ backgroundColor: surfaceColor,
56
+ },
57
+ ]}
58
+ >
59
+ <Section useMargin={marginFooter}>{footer}</Section>
60
+ </View>
51
61
  </View>
52
- </View>
53
- );
62
+ );
63
+ };
@@ -31,6 +31,10 @@ type ScreenHeaderParams = {
31
31
  layoutOffset: number;
32
32
  };
33
33
 
34
+ /**
35
+ * Applies the Screen's navigation header (plain, extended, animated banner or search) and
36
+ * re-applies it when those props change. Returns the search header setter and the tint.
37
+ */
34
38
  export const useScreenHeader = ({
35
39
  navigation,
36
40
  layout,
@@ -48,10 +52,7 @@ export const useScreenHeader = ({
48
52
  const currentTint = useRef<string | undefined>(undefined);
49
53
  const appliedHeaderMode = useRef<string | undefined>(undefined);
50
54
 
51
- /**
52
- * export options for screen
53
- * @param type
54
- */
55
+ // keepTitle leaves a title the screen set itself when the same type is re-applied
55
56
  const setHeaderType = useCallback(
56
57
  (type: HeaderType, keepTitle = false) => {
57
58
  let options: StackNavigationOptions;
@@ -69,10 +70,12 @@ export const useScreenHeader = ({
69
70
  ...quickReturnOptions,
70
71
  headerShown: true,
71
72
  headerTransparent: true,
72
- headerBackground: () =>
73
- hasSideBar ? (
74
- <DuoGlassBackground animatedValue={sharedValue} />
75
- ) : null,
73
+ headerBackground: () => {
74
+ if (!hasSideBar) {
75
+ return null;
76
+ }
77
+ return <DuoGlassBackground animatedValue={sharedValue} />;
78
+ },
76
79
  headerTitle: (props: any) => <HeaderTitle {...props} />,
77
80
  };
78
81
  if (inputSearchProps) {
@@ -160,10 +163,7 @@ export const useScreenHeader = ({
160
163
  ],
161
164
  );
162
165
 
163
- /**
164
- * export animatedHeader options for screen
165
- * @param nextAnimatedHeader
166
- */
166
+ // the first tint the animated header sets is kept for later scroll updates
167
167
  const setAnimatedHeader = useCallback(
168
168
  (nextAnimatedHeader: AnimatedHeader) => {
169
169
  let options: StackNavigationOptions;
@@ -175,13 +175,16 @@ export const useScreenHeader = ({
175
175
  ...quickReturnOptions,
176
176
  headerTintColor: currentTint.current ?? Colors.black_17,
177
177
  headerTransparent: true,
178
- headerBackground: (props: any) =>
179
- hasSideBar ? (
180
- <DuoGlassBackground
181
- animatedValue={sharedValue}
182
- bannerBottom={ANIMATED_BANNER_HEIGHT + layoutOffset}
183
- />
184
- ) : (
178
+ headerBackground: (props: any) => {
179
+ if (hasSideBar) {
180
+ return (
181
+ <DuoGlassBackground
182
+ animatedValue={sharedValue}
183
+ bannerBottom={ANIMATED_BANNER_HEIGHT + layoutOffset}
184
+ />
185
+ );
186
+ }
187
+ return (
185
188
  <HeaderBackground
186
189
  {...props}
187
190
  animatedValue={sharedValue}
@@ -190,7 +193,8 @@ export const useScreenHeader = ({
190
193
  headerBackground={headerBackground}
191
194
  gradientColor={gradientColor}
192
195
  />
193
- ),
196
+ );
197
+ },
194
198
  headerTitle: (props: any) => {
195
199
  return <HeaderTitle {...props} />;
196
200
  },
@@ -217,9 +221,6 @@ export const useScreenHeader = ({
217
221
  ],
218
222
  );
219
223
 
220
- /**
221
- * export animatedHeader options for screen
222
- */
223
224
  const setAnimatedSearch = useCallback(() => {
224
225
  const options: StackNavigationOptions = {
225
226
  headerShown: true,
@@ -240,9 +241,7 @@ export const useScreenHeader = ({
240
241
  navigation?.instance?.setOptions({ header: undefined, ...options });
241
242
  }, [navigation?.instance, sharedValue]);
242
243
 
243
- /**
244
- * export search header
245
- */
244
+ // the search field spans the content, clear of the side bar and status bar clusters
246
245
  const setSearchHeader = (params: SearchHeaderProps) => {
247
246
  let searchWidth = layout.width - 24;
248
247
  let searchLeft = Spacing.M;
@@ -250,9 +249,9 @@ export const useScreenHeader = ({
250
249
  searchWidth = layout.contentWidth - Spacing.M * 2;
251
250
  searchLeft = Spacing.M - getHeaderMargin(layout.width);
252
251
  }
253
- if (layout.statusBarClearance) {
254
- searchWidth -=
255
- layout.statusBarClearance.left + layout.statusBarClearance.right;
252
+ const clearance = layout.statusBarClearance;
253
+ if (clearance) {
254
+ searchWidth -= clearance.left + clearance.right;
256
255
  }
257
256
  const options: StackNavigationOptions = {
258
257
  headerRight: undefined,
@@ -267,8 +266,12 @@ export const useScreenHeader = ({
267
266
  margin: 0,
268
267
  left: searchLeft,
269
268
  },
270
- headerLeft: (props: any) =>
271
- params?.hiddenBack ? null : <HeaderLeft {...props} floating />,
269
+ headerLeft: (props: any) => {
270
+ if (params?.hiddenBack) {
271
+ return null;
272
+ }
273
+ return <HeaderLeft {...props} floating />;
274
+ },
272
275
  headerTitle: () => (
273
276
  <SearchHeader {...params} animatedValue={sharedValue} />
274
277
  ),
@@ -8,6 +8,10 @@ import {
8
8
  } from 'react-native-reanimated';
9
9
  import { DuoQuickReturnHeader } from '../Application/Components/DuoQuickReturnHeader';
10
10
 
11
+ /**
12
+ * iPhone Duo quick return header: it slides out while the content scrolls down and back
13
+ * on scroll up. Returns the header options and a snap to call when scrolling ends.
14
+ */
11
15
  export const useScreenQuickReturn = (
12
16
  sharedValue: SharedValue<number>,
13
17
  useQuickReturn: boolean,
@@ -33,29 +37,22 @@ export const useScreenQuickReturn = (
33
37
  [useQuickReturn, heightHeader],
34
38
  );
35
39
 
36
- const quickReturnOptions: StackNavigationOptions = useMemo(
37
- () =>
38
- useQuickReturn
39
- ? {
40
- header: (props: any) => (
41
- <DuoQuickReturnHeader {...props} offset={headerHide} />
42
- ),
43
- }
44
- : { header: undefined },
45
- [useQuickReturn, headerHide],
46
- );
40
+ const quickReturnOptions: StackNavigationOptions = useMemo(() => {
41
+ if (!useQuickReturn) {
42
+ return { header: undefined };
43
+ }
44
+ return {
45
+ header: (props: any) => (
46
+ <DuoQuickReturnHeader {...props} offset={headerHide} />
47
+ ),
48
+ };
49
+ }, [useQuickReturn, headerHide]);
47
50
 
48
- /**
49
- * a half-shown quick return header settles fully in or out
50
- */
51
+ // a half-shown quick return header settles fully in or out
51
52
  const snapQuickReturnHeader = (offsetY: number) => {
52
53
  const hide = headerHide.value;
53
- if (
54
- !useQuickReturn ||
55
- offsetY < heightHeader ||
56
- hide <= 0 ||
57
- hide >= heightHeader
58
- ) {
54
+ const isSettled = hide <= 0 || hide >= heightHeader;
55
+ if (!useQuickReturn || offsetY < heightHeader || isSettled) {
59
56
  return;
60
57
  }
61
58
  headerHide.value = withTiming(hide > heightHeader / 2 ? heightHeader : 0, {
@@ -25,6 +25,10 @@ type ScrollHandlerParams = {
25
25
  ref: any;
26
26
  };
27
27
 
28
+ /**
29
+ * Scroll handlers for Screen: feed the header animated values, retint an animated header
30
+ * past 50pt, snap the quick return header and reset a search screen scrolled under 100pt.
31
+ */
28
32
  export const createScrollHandlers = ({
29
33
  scrollable,
30
34
  animatedValue,
@@ -39,9 +43,6 @@ export const createScrollHandlers = ({
39
43
  }: ScrollHandlerParams) => {
40
44
  let handleScroll;
41
45
 
42
- /**
43
- * animated when use scroll && animated value
44
- */
45
46
  if (scrollable) {
46
47
  handleScroll = Animated.event(
47
48
  [
@@ -80,10 +81,6 @@ export const createScrollHandlers = ({
80
81
  );
81
82
  }
82
83
 
83
- /**
84
- * handle scroll end
85
- * @param e
86
- */
87
84
  const handleMomentumScrollEnd = (
88
85
  e: NativeSyntheticEvent<NativeScrollEvent>,
89
86
  ) => {
@@ -38,7 +38,7 @@ export interface ScreenProps extends ViewProps {
38
38
  headerType?: HeaderType;
39
39
 
40
40
  /**
41
- * Optional. Props for the animated component or bool used top navigation animated.
41
+ * Optional. Animated banner header: its component, tint and title.
42
42
  */
43
43
  animatedHeader?: AnimatedHeader;
44
44
 
@@ -71,7 +71,8 @@ export interface ScreenProps extends ViewProps {
71
71
  backgroundColor?: string;
72
72
 
73
73
  /**
74
- * Optional. layout card offset overlap header banner.
74
+ * Optional. Shift of the content against the animated header banner bottom; negative
75
+ * values overlap the banner.
75
76
  */
76
77
  layoutOffset?: -8 | -24 | -56 | number;
77
78
 
package/Layout/types.ts CHANGED
@@ -4,14 +4,12 @@ export type HeaderType = 'default' | 'extended' | 'none';
4
4
 
5
5
  export type ScreenRef = {
6
6
  /**
7
- * Set options for screen
8
- * @param params
7
+ * Applies navigation options to the screen's header.
9
8
  */
10
9
  setOptions: (params: NavigationOptions) => void;
11
10
 
12
11
  /**
13
- * Show search header
14
- * @param params
12
+ * Replaces the header title with a search field.
15
13
  */
16
14
  setSearchHeader: (params: SearchHeaderProps) => void;
17
15
  };
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