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

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 (126) 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 +48 -49
  6. package/Application/BottomTab/BottomTabInset.tsx +8 -4
  7. package/Application/BottomTab/CurvedContainer.tsx +14 -12
  8. package/Application/BottomTab/CustomBottomTabItem.tsx +12 -13
  9. package/Application/BottomTab/DuoBarCompression.tsx +13 -19
  10. package/Application/BottomTab/DuoRailTabBar.tsx +20 -20
  11. package/Application/BottomTab/DuoTabBar.tsx +8 -9
  12. package/Application/BottomTab/TabBarIcon.tsx +15 -29
  13. package/Application/BottomTab/TabScreen.tsx +1 -5
  14. package/Application/BottomTab/index.tsx +33 -30
  15. package/Application/BottomTab/metrics.ts +22 -25
  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/ScaleSizeProvider.tsx +13 -6
  40. package/Application/StackScreen.tracking.ts +11 -26
  41. package/Application/StackScreen.tsx +8 -35
  42. package/Application/TooltipPortal.tsx +12 -5
  43. package/Application/WidgetContainer.tsx +10 -4
  44. package/Application/modalLayout.ts +13 -14
  45. package/Application/navigationOptions.tsx +21 -17
  46. package/Badge/Badge.tsx +5 -0
  47. package/Badge/BadgeDot.tsx +1 -0
  48. package/Badge/BadgeDotAnimation.tsx +15 -7
  49. package/Badge/BadgeRibbon.tsx +46 -38
  50. package/Badge/Shape.tsx +2 -0
  51. package/Badge/types.ts +2 -2
  52. package/Button/index.tsx +5 -16
  53. package/Button/utils.ts +9 -15
  54. package/CheckBox/index.tsx +4 -0
  55. package/Consts/theme.ts +10 -10
  56. package/Context/index.ts +5 -1
  57. package/Divider/DashDivider.tsx +1 -0
  58. package/Divider/index.tsx +4 -0
  59. package/FoundationList/index.tsx +4 -0
  60. package/Icon/index.tsx +6 -6
  61. package/Icon/types.ts +1 -1
  62. package/IconButton/index.tsx +4 -12
  63. package/Image/index.tsx +9 -7
  64. package/Input/Input.tsx +8 -9
  65. package/Input/InputDropDown.tsx +10 -12
  66. package/Input/InputMoney.tsx +12 -8
  67. package/Input/InputMoneyView.tsx +14 -7
  68. package/Input/InputOTP.tsx +35 -33
  69. package/Input/InputPhoneNumber.tsx +8 -9
  70. package/Input/InputSearch.tsx +4 -6
  71. package/Input/InputTextArea.tsx +4 -0
  72. package/Input/SearchTextTyping.tsx +4 -0
  73. package/Input/SystemTextInput.tsx +7 -2
  74. package/Input/TextTyping.tsx +4 -0
  75. package/Input/common.tsx +26 -12
  76. package/Input/index.tsx +17 -20
  77. package/Input/moneyUtils.ts +10 -4
  78. package/Input/styles.ts +0 -5
  79. package/Input/utils.ts +14 -12
  80. package/Layout/Card.tsx +7 -4
  81. package/Layout/FloatingButton.tsx +4 -0
  82. package/Layout/Item.tsx +6 -2
  83. package/Layout/ItemList.tsx +9 -7
  84. package/Layout/ItemSectionList.tsx +2 -1
  85. package/Layout/ReservedRegions.tsx +25 -25
  86. package/Layout/Screen.tsx +56 -58
  87. package/Layout/Section.tsx +8 -5
  88. package/Layout/TrackingScope.tsx +1 -0
  89. package/Layout/screenContent.tsx +4 -2
  90. package/Layout/screenFooter.tsx +28 -18
  91. package/Layout/screenHeader.tsx +34 -31
  92. package/Layout/screenQuickReturn.tsx +17 -20
  93. package/Layout/screenScroll.ts +4 -7
  94. package/Layout/screenTypes.ts +3 -2
  95. package/Layout/types.ts +2 -4
  96. package/Layout/utils.ts +31 -37
  97. package/Loader/DotLoader.tsx +4 -0
  98. package/Loader/Loader.tsx +4 -0
  99. package/Loader/ProgressBar.tsx +1 -0
  100. package/Loader/Spinner.tsx +4 -0
  101. package/Loader/utils.ts +11 -7
  102. package/Pagination/Dot.tsx +4 -0
  103. package/Pagination/PaginationDot.tsx +1 -0
  104. package/Pagination/PaginationNumber.tsx +5 -3
  105. package/Pagination/PaginationScroll.tsx +5 -0
  106. package/Pagination/PaginationWhiteDot.tsx +1 -0
  107. package/Pagination/index.tsx +4 -0
  108. package/Pagination/types.ts +2 -2
  109. package/Popup/PopupNotify.tsx +44 -48
  110. package/Popup/PopupPromotion.tsx +4 -7
  111. package/Popup/types.ts +4 -4
  112. package/Popup/utils.ts +4 -0
  113. package/Radio/index.tsx +4 -0
  114. package/Skeleton/index.tsx +8 -0
  115. package/Switch/index.tsx +1 -0
  116. package/Tag/index.tsx +5 -1
  117. package/Tag/types.ts +3 -3
  118. package/Text/index.tsx +14 -3
  119. package/Text/styles.ts +1 -1
  120. package/Text/types.ts +1 -6
  121. package/Text/utils.ts +20 -4
  122. package/Title/index.tsx +41 -32
  123. package/ViewBoundary/index.tsx +4 -2
  124. package/index.ts +0 -6
  125. package/package.json +1 -1
  126. package/Application/__tests__/modalLayout.test.ts +0 -128
@@ -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,
@@ -19,43 +19,43 @@ try {
19
19
  }
20
20
 
21
21
  /**
22
- * screen regions the system currently occludes, relative to the provider, which fills
23
- * the root view so they line up with window coordinates
22
+ * Screen regions the system currently occludes, relative to the provider, which fills
23
+ * the root view so they line up with window coordinates.
24
24
  */
25
25
  const ReservedRegionsContext = createContext<ReservedRegion[]>([]);
26
26
 
27
27
  /**
28
- * vertical seam splitting the display into left and right halves, e.g. iPhone Duo
29
- * half folded. `undefined` while the display is flat or the system reports none
28
+ * Vertical seam splitting the display into left and right halves, e.g. iPhone Duo
29
+ * half folded. `undefined` while the display is flat or the system reports none.
30
30
  */
31
31
  const HingeContext = createContext<ReservedRegion | undefined>(undefined);
32
32
 
33
+ /** Frame of the vertical hinge (see HingeContext), `undefined` while the display is flat. */
33
34
  const useHinge = () => useContext(HingeContext);
34
35
 
35
36
  /**
36
- * keeps the occlusion regions and the vertical division reported by the native provider
37
+ * Provides the occlusion regions and the vertical division (hinge) reported by the native
38
+ * provider.
37
39
  */
38
40
  const OcclusionRegions: React.FC<{
39
41
  useRegions: NativeReservedRegions['useReservedRegions'];
40
42
  children: ReactNode;
41
43
  }> = ({ useRegions, children }) => {
42
44
  const regions = useRegions();
43
- const occlusion = useMemo(
44
- () =>
45
- regions
46
- .filter(region => region.kind === 'occlusion')
47
- .map(region => region.frame),
48
- [regions],
49
- );
50
- const hinge = useMemo(
51
- () =>
52
- regions.find(
53
- region =>
54
- region.kind === 'division' &&
55
- region.frame.height > region.frame.width,
56
- )?.frame,
57
- [regions],
58
- );
45
+ const occlusion = useMemo(() => {
46
+ return regions
47
+ .filter(region => region.kind === 'occlusion')
48
+ .map(region => region.frame);
49
+ }, [regions]);
50
+ const hinge = useMemo(() => {
51
+ const division = regions.find(region => {
52
+ if (region.kind !== 'division') {
53
+ return false;
54
+ }
55
+ return region.frame.height > region.frame.width;
56
+ });
57
+ return division?.frame;
58
+ }, [regions]);
59
59
 
60
60
  return (
61
61
  <ReservedRegionsContext.Provider value={occlusion}>
@@ -65,8 +65,8 @@ const OcclusionRegions: React.FC<{
65
65
  };
66
66
 
67
67
  /**
68
- * tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
69
- * cluster growing for a call or a Live Activity
68
+ * Tracks the system occlusion regions (iOS 27.1+), e.g. the iPhone Duo status bar
69
+ * cluster growing for a call or a Live Activity.
70
70
  */
71
71
  const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
72
72
  children,
@@ -75,8 +75,8 @@ const ReservedRegionsProvider: React.FC<{ children: ReactNode }> = ({
75
75
  return <>{children}</>;
76
76
  }
77
77
 
78
- const { ReservedRegionsProvider: NativeProvider, useReservedRegions } =
79
- nativeLib;
78
+ const NativeProvider = nativeLib.ReservedRegionsProvider;
79
+ const { useReservedRegions } = nativeLib;
80
80
  return (
81
81
  <NativeProvider style={styles.container}>
82
82
  <OcclusionRegions useRegions={useReservedRegions}>
package/Layout/Screen.tsx CHANGED
@@ -37,6 +37,10 @@ import { useScreenQuickReturn } from './screenQuickReturn';
37
37
 
38
38
  export type { ScreenProps };
39
39
 
40
+ /**
41
+ * Screen container: sets the navigation header, lays children on the grid, and adds the
42
+ * scroll view, floating button and footer, with iPhone Duo side bar and tab bar insets.
43
+ */
40
44
  const Screen = forwardRef(
41
45
  (
42
46
  {
@@ -108,14 +112,12 @@ const Screen = forwardRef(
108
112
  const isIOS = Platform.OS === 'ios';
109
113
  const bottomInset = isIOS ? Math.min(insets.bottom, 21) : insets.bottom;
110
114
  const hasSideBar = !!layout.sideBar;
111
- const useDuoGlass =
112
- hasSideBar &&
113
- (headerType === 'default' || headerType === 'extended') &&
114
- !animatedHeader &&
115
- !inputSearchProps;
115
+ const isPlainHeader = headerType === 'default' || headerType === 'extended';
116
+ const hasCustomHeader = !!animatedHeader || !!inputSearchProps;
117
+ const useDuoGlass = hasSideBar && isPlainHeader && !hasCustomHeader;
116
118
 
117
- const useQuickReturn =
118
- hasSideBar && scrollable && (useDuoGlass || !!animatedHeader);
119
+ const hasOverlayHeader = useDuoGlass || !!animatedHeader;
120
+ const useQuickReturn = hasSideBar && scrollable && hasOverlayHeader;
119
121
  const { quickReturnOptions, snapQuickReturnHeader } = useScreenQuickReturn(
120
122
  sharedValue,
121
123
  useQuickReturn,
@@ -123,18 +125,10 @@ const Screen = forwardRef(
123
125
  );
124
126
 
125
127
  let keyboardOffset = heightHeader - bottomInset;
126
- if (
127
- headerType === 'extended' ||
128
- animatedHeader ||
129
- inputSearchProps ||
130
- useDuoGlass
131
- ) {
128
+ if (headerType === 'extended' || hasCustomHeader || useDuoGlass) {
132
129
  keyboardOffset = -bottomInset;
133
130
  }
134
131
 
135
- /**
136
- * inject params for screen tracking
137
- */
138
132
  screen?.onSetParams?.(trackingParams);
139
133
 
140
134
  const { setSearchHeader, currentTint } = useScreenHeader({
@@ -152,9 +146,7 @@ const Screen = forwardRef(
152
146
  layoutOffset,
153
147
  });
154
148
 
155
- /**
156
- * expose ref for screen
157
- */
149
+ // mutates the content node so callers keep its methods plus the header setters
158
150
  useImperativeHandle(ref, () => {
159
151
  return Object.assign(screenRef.current || {}, {
160
152
  setOptions: (params: NavigationOptions) => {
@@ -165,19 +157,18 @@ const Screen = forwardRef(
165
157
  });
166
158
  });
167
159
 
168
- const { handleScroll, handleScrollEnd, handleMomentumScrollEnd } =
169
- createScrollHandlers({
170
- scrollable,
171
- animatedValue: animatedValue.current,
172
- sharedValue,
173
- scrollViewProps,
174
- animatedHeader,
175
- inputSearchProps,
176
- currentTint,
177
- navigation,
178
- snapQuickReturnHeader,
179
- ref,
180
- });
160
+ const scrollHandlers = createScrollHandlers({
161
+ scrollable,
162
+ animatedValue: animatedValue.current,
163
+ sharedValue,
164
+ scrollViewProps,
165
+ animatedHeader,
166
+ inputSearchProps,
167
+ currentTint,
168
+ navigation,
169
+ snapQuickReturnHeader,
170
+ ref,
171
+ });
181
172
  const Component: any = scrollable ? Animated.ScrollView : View;
182
173
 
183
174
  const getHeight = () => {
@@ -207,6 +198,35 @@ const Screen = forwardRef(
207
198
  floatingButtonBottom = 12 + duoTabInset;
208
199
  }
209
200
 
201
+ const renderHeaderLayer = () => {
202
+ if (useDuoGlass) {
203
+ return (
204
+ <DuoScrollGradient
205
+ animatedValue={sharedValue}
206
+ gradientColor={gradientColor}
207
+ headerBackground={headerBackground}
208
+ heightHeader={heightHeader}
209
+ extended={headerType === 'extended'}
210
+ followScroll={useQuickReturn}
211
+ />
212
+ );
213
+ }
214
+ return (
215
+ <HeaderExtendHeader
216
+ headerType={headerType}
217
+ heightHeader={heightHeader}
218
+ headerRightWidth={headerRightWidth}
219
+ animatedValue={sharedValue}
220
+ inputSearchProps={inputSearchProps}
221
+ navigation={navigation}
222
+ inputSearchRef={inputSearchRef}
223
+ useShadowHeader={useShadowHeader}
224
+ gradientColor={gradientColor}
225
+ headerBackground={headerBackground}
226
+ />
227
+ );
228
+ };
229
+
210
230
  return (
211
231
  <View
212
232
  style={[
@@ -217,29 +237,7 @@ const Screen = forwardRef(
217
237
  },
218
238
  ]}
219
239
  >
220
- {useDuoGlass ? (
221
- <DuoScrollGradient
222
- animatedValue={sharedValue}
223
- gradientColor={gradientColor}
224
- headerBackground={headerBackground}
225
- heightHeader={heightHeader}
226
- extended={headerType === 'extended'}
227
- followScroll={useQuickReturn}
228
- />
229
- ) : (
230
- <HeaderExtendHeader
231
- headerType={headerType}
232
- heightHeader={heightHeader}
233
- headerRightWidth={headerRightWidth}
234
- animatedValue={sharedValue}
235
- inputSearchProps={inputSearchProps}
236
- navigation={navigation}
237
- inputSearchRef={inputSearchRef}
238
- useShadowHeader={useShadowHeader}
239
- gradientColor={gradientColor}
240
- headerBackground={headerBackground}
241
- />
242
- )}
240
+ {renderHeaderLayer()}
243
241
 
244
242
  <KeyboardAvoidingView
245
243
  style={[
@@ -259,9 +257,9 @@ const Screen = forwardRef(
259
257
  {...scrollViewProps}
260
258
  ref={screenRef}
261
259
  showsVerticalScrollIndicator={false}
262
- onScroll={handleScroll}
263
- onScrollEndDrag={handleScrollEnd}
264
- onMomentumScrollEnd={handleMomentumScrollEnd}
260
+ onScroll={scrollHandlers.handleScroll}
261
+ onScrollEndDrag={scrollHandlers.handleScrollEnd}
262
+ onMomentumScrollEnd={scrollHandlers.handleMomentumScrollEnd}
265
263
  scrollEventThrottle={16}
266
264
  style={[
267
265
  Styles.flex,
@@ -15,8 +15,8 @@ export interface SectionProps extends ViewProps {
15
15
  expanded?: boolean;
16
16
 
17
17
  /**
18
- * Optional. If `true`, margin will be applied to the Section component.
19
- * Defaults to `false` if not provided.
18
+ * Optional. If `true`, 12pt side margins are applied to the Section component.
19
+ * Defaults to `true`.
20
20
  */
21
21
  useMargin?: boolean;
22
22
 
@@ -26,6 +26,10 @@ export interface SectionProps extends ViewProps {
26
26
  backgroundImage?: string;
27
27
  }
28
28
 
29
+ /**
30
+ * Content-wide grid row (12 or 24 columns) with optional side margins; child Items size
31
+ * themselves from its GridContext.
32
+ */
29
33
  const Section: React.FC<SectionProps> = ({
30
34
  children,
31
35
  useMargin = true,
@@ -43,9 +47,8 @@ const Section: React.FC<SectionProps> = ({
43
47
  }
44
48
  const widthSection = contentWidth - margin * 2;
45
49
 
46
- const sizePerSpan =
47
- (widthSection - gutterSize * (numberOfColumns - 1)) / numberOfColumns -
48
- 1 / numberOfColumns;
50
+ const columnsWidth = widthSection - gutterSize * (numberOfColumns - 1);
51
+ const sizePerSpan = columnsWidth / numberOfColumns - 1 / numberOfColumns;
49
52
 
50
53
  const gridContext: GridContextProps = {
51
54
  numberOfColumns,
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { TrackingScopeContext } from '../Context';
3
3
 
4
+ /** Names a tracking scope; components inside prefix their component ids with it. */
4
5
  const TrackingScope = ({
5
6
  scopeName,
6
7
  children,
@@ -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, {