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

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 (63) hide show
  1. package/Application/BottomSheet.hooks.ts +27 -17
  2. package/Application/BottomSheet.tsx +35 -27
  3. package/Application/BottomTab/ActiveIndicator.tsx +5 -1
  4. package/Application/BottomTab/BottomTabBar.styles.ts +18 -1
  5. package/Application/BottomTab/BottomTabBar.tsx +14 -16
  6. package/Application/BottomTab/CurvedContainer.tsx +8 -2
  7. package/Application/BottomTab/CustomBottomTabItem.tsx +2 -2
  8. package/Application/BottomTab/DuoRailTabBar.tsx +5 -5
  9. package/Application/BottomTab/DuoTabBar.tsx +15 -3
  10. package/Application/BottomTab/TabScreen.tsx +11 -10
  11. package/Application/BottomTab/index.tsx +5 -5
  12. package/Application/BottomTab/metrics.ts +23 -3
  13. package/Application/BottomTab/usePressLift.ts +3 -3
  14. package/Application/Components/HeaderAnimated.tsx +13 -38
  15. package/Application/Components/HeaderBackground.tsx +3 -16
  16. package/Application/Components/HeaderExtendHeader.tsx +28 -41
  17. package/Application/Components/HeaderLeft.tsx +18 -4
  18. package/Application/Components/HeaderRight.styles.ts +13 -15
  19. package/Application/Components/HeaderTitle.shared.ts +11 -10
  20. package/Application/Components/HeaderToolkitAction.tsx +54 -21
  21. package/Application/Components/NavigationButton.tsx +6 -9
  22. package/Application/Components/SearchHeader.tsx +7 -20
  23. package/Application/Components/useToolkitActions.tsx +22 -21
  24. package/Application/ModalScreen.tsx +19 -15
  25. package/Application/StackScreen.tsx +13 -18
  26. package/Application/index.ts +1 -11
  27. package/Application/modalLayout.ts +1 -0
  28. package/Application/navigationOptions.tsx +15 -6
  29. package/Application/types.header.ts +2 -7
  30. package/Application/types.tabs.ts +0 -1
  31. package/Application/types.ts +0 -7
  32. package/Context/index.ts +3 -1
  33. package/Image/index.tsx +6 -6
  34. package/Input/ErrorView.tsx +59 -0
  35. package/Input/FloatingView.tsx +101 -0
  36. package/Input/Input.tsx +40 -37
  37. package/Input/InputDropDown.tsx +13 -10
  38. package/Input/InputMoney.tsx +17 -17
  39. package/Input/InputMoneyView.tsx +12 -12
  40. package/Input/InputOTP.tsx +46 -34
  41. package/Input/InputPhoneNumber.tsx +48 -43
  42. package/Input/InputSearch.tsx +26 -39
  43. package/Input/InputTextArea.tsx +29 -23
  44. package/Input/RenderTrailing.tsx +79 -0
  45. package/Input/constants.ts +8 -0
  46. package/Input/index.tsx +1 -7
  47. package/Input/utils.ts +126 -7
  48. package/Layout/FloatingButton.tsx +38 -24
  49. package/Layout/Screen.tsx +0 -3
  50. package/Layout/layoutMetrics.ts +43 -34
  51. package/Layout/screenHeader.tsx +52 -55
  52. package/Layout/screenTypes.ts +4 -13
  53. package/Pagination/PaginationScroll.tsx +5 -3
  54. package/Pagination/types.ts +3 -5
  55. package/Popup/PopupNotify.tsx +17 -14
  56. package/Popup/PopupPromotion.tsx +5 -4
  57. package/Popup/types.ts +2 -4
  58. package/Popup/utils.ts +7 -4
  59. package/Skeleton/index.tsx +15 -12
  60. package/package.json +1 -1
  61. package/Application/headerLayout.ts +0 -165
  62. package/Input/common.tsx +0 -257
  63. package/Input/moneyUtils.ts +0 -23
@@ -13,6 +13,8 @@ import { ApplicationContext, MiniAppContext } from '../Context';
13
13
 
14
14
  const INDICATOR_WIDTH = 24;
15
15
  const INDICATOR_CONTAINER_WIDTH = 72;
16
+ // one frame at 60 fps
17
+ const SCROLL_EVENT_THROTTLE = 16;
16
18
 
17
19
  /**
18
20
  * Horizontal scroll view with a small indicator that tracks the scroll offset.
@@ -31,7 +33,7 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
31
33
 
32
34
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
33
35
 
34
- const onScroll = useAnimatedScrollHandler({
36
+ const handleScroll = useAnimatedScrollHandler({
35
37
  onScroll: (event) => {
36
38
  left.value = event.contentOffset.x;
37
39
  },
@@ -54,8 +56,8 @@ const PaginationScroll: FC<ScrollIndicatorProps> = ({
54
56
  return (
55
57
  <Animated.ScrollView
56
58
  ref={scrollViewRef as any}
57
- onScroll={onScroll}
58
- scrollEventThrottle={16}
59
+ onScroll={handleScroll}
60
+ scrollEventThrottle={SCROLL_EVENT_THROTTLE}
59
61
  alwaysBounceHorizontal={false}
60
62
  showsHorizontalScrollIndicator={false}
61
63
  horizontal
@@ -3,11 +3,9 @@ import {Ref} from 'react';
3
3
 
4
4
  export type PaginationProps = {
5
5
  /**
6
- * Optional. Defines the visual style of the Pagination component.
7
- * - 'default': Represents the default style.
8
- * - 'black_white': Represents dots on a translucent dark background.
9
- * - 'number': Represents a style where pages are represented by numbers.
10
- * - 'scroll': Rendered as 'default'; use `PaginationScroll` instead.
6
+ * Optional. Visual style: 'default' dots, 'black_white' dots on a translucent
7
+ * dark background, 'number' page numbers. 'scroll' renders as 'default'; use
8
+ * `PaginationScroll` instead.
11
9
  */
12
10
  type?: 'default' | 'black_white' | 'number' | 'scroll';
13
11
 
@@ -12,6 +12,9 @@ import { ModalIconButton } from '../Application/ModalIconButton';
12
12
  import { splitHighlightText } from './utils';
13
13
 
14
14
  const MAX_ROW_TITLE_LENGTH = 12;
15
+ const DESCRIPTION_MAX_HEIGHT = 172;
16
+ const MAX_DESCRIPTION_LINES = 8.5;
17
+ const IMAGE_ASPECT_RATIO = 1.7777;
15
18
 
16
19
  /**
17
20
  * Notify popup body: optional image, title, description, error code and one or
@@ -32,10 +35,10 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
32
35
  }) => {
33
36
  const context = useContext<any>(MiniAppContext);
34
37
  const { theme, navigator, translate } = useContext(ApplicationContext);
35
- const [scrollContent, setScrollContent] = useState(false);
38
+ const [isDescriptionScrollable, setIsDescriptionScrollable] = useState(false);
36
39
  const { isFoldable, popup } = useModalLayout();
37
40
  const { width: popupWidth, maxHeight, radius } = popup;
38
- const scaledMaxHeight = useScaleSize(172);
41
+ const scaledMaxHeight = useScaleSize(DESCRIPTION_MAX_HEIGHT);
39
42
 
40
43
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
41
44
  const closeAction = useRef('touch_outside');
@@ -45,12 +48,12 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
45
48
  // foldables cap the popup height, the description takes what is left
46
49
  Description = ScrollView;
47
50
  descriptionStyle = styles.shrink;
48
- } else if (scrollContent) {
51
+ } else if (isDescriptionScrollable) {
49
52
  Description = ScrollView;
50
53
  descriptionStyle = { maxHeight: scaledMaxHeight };
51
54
  }
52
55
 
53
- const onAction = (callback?: () => void) => {
56
+ const handleAction = (callback?: () => void) => {
54
57
  if (typeof onRequestClose === 'function') {
55
58
  onRequestClose?.(callback);
56
59
  } else {
@@ -72,7 +75,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
72
75
  size="medium"
73
76
  onPress={() => {
74
77
  closeAction.current = 'button_action';
75
- onAction(secondary?.onPress);
78
+ handleAction(secondary?.onPress);
76
79
  }}
77
80
  accessibilityLabel={'btn_popup_cancel'}
78
81
  />
@@ -86,7 +89,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
86
89
  size="medium"
87
90
  onPress={() => {
88
91
  closeAction.current = 'button_action';
89
- onAction(primary.onPress);
92
+ handleAction(primary.onPress);
90
93
  }}
91
94
  accessibilityLabel={'btn_popup_allow'}
92
95
  />
@@ -103,7 +106,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
103
106
  size="medium"
104
107
  onPress={() => {
105
108
  closeAction.current = 'button_action';
106
- onAction(primary.onPress);
109
+ handleAction(primary.onPress);
107
110
  }}
108
111
  />
109
112
  {!!secondary?.title && (
@@ -116,7 +119,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
116
119
  size="medium"
117
120
  onPress={() => {
118
121
  closeAction.current = 'button_action';
119
- onAction(secondary?.onPress);
122
+ handleAction(secondary?.onPress);
120
123
  }}
121
124
  />
122
125
  </>
@@ -150,7 +153,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
150
153
  return text;
151
154
  }
152
155
  return segments.map((segment, index) => {
153
- if (segment.highlight) {
156
+ if (segment.isHighlighted) {
154
157
  return (
155
158
  <Text key={index} typography={'header_s_semibold'}>
156
159
  {segment.text}
@@ -172,7 +175,7 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
172
175
  style={styles.iconClose}
173
176
  onPress={() => {
174
177
  closeAction.current = 'icon_close';
175
- onAction(onIconClose);
178
+ handleAction(onIconClose);
176
179
  }}
177
180
  />
178
181
  );
@@ -189,9 +192,9 @@ const PopupNotify: React.FC<PopupNotifyProps> = ({
189
192
  accessibilityLabel={'description_popup_notify'}
190
193
  typography={'body_default_regular'}
191
194
  onTextLayout={({ nativeEvent: { lines } }) => {
192
- const enableScroll = lines.length > 8.5;
193
- if (enableScroll !== scrollContent) {
194
- setScrollContent(enableScroll);
195
+ const shouldScroll = lines.length > MAX_DESCRIPTION_LINES;
196
+ if (shouldScroll !== isDescriptionScrollable) {
197
+ setIsDescriptionScrollable(shouldScroll);
195
198
  }
196
199
  }}
197
200
  >
@@ -260,7 +263,7 @@ const styles = StyleSheet.create({
260
263
  // clips the image and content to the card radius
261
264
  card: { overflow: 'hidden' },
262
265
  image: {
263
- aspectRatio: 1.7777,
266
+ aspectRatio: IMAGE_ASPECT_RATIO,
264
267
  },
265
268
  shrink: { flexShrink: 1 },
266
269
  content: { padding: Spacing.XL },
@@ -9,6 +9,7 @@ import { useModalLayout } from '../Application/modalLayout';
9
9
 
10
10
  const ASPECT_RATIO = 0.72;
11
11
  const ICON_CLOSE_SIZE = 20;
12
+ const CLOSE_GLYPH_SIZE = 14;
12
13
  // close button row under the image: marginTop + vertical padding + icon
13
14
  const CLOSE_ROW_HEIGHT = Spacing.S * 3 + ICON_CLOSE_SIZE;
14
15
 
@@ -37,7 +38,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
37
38
 
38
39
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
39
40
 
40
- const onAction = (callback?: () => void) => {
41
+ const handleAction = (callback?: () => void) => {
41
42
  if (typeof onRequestClose === 'function') {
42
43
  onRequestClose?.(callback);
43
44
  } else {
@@ -53,7 +54,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
53
54
  accessibilityLabel={'ic_popup_close'}
54
55
  onPress={() => {
55
56
  closeAction.current = 'icon_close';
56
- onAction(onIconClose);
57
+ handleAction(onIconClose);
57
58
  }}
58
59
  style={styles.iconButton}
59
60
  >
@@ -69,7 +70,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
69
70
  <Icon
70
71
  source="navigation_close"
71
72
  color={theme.colors.background.surface}
72
- size={14}
73
+ size={CLOSE_GLYPH_SIZE}
73
74
  />
74
75
  </View>
75
76
  </TouchableOpacity>
@@ -82,7 +83,7 @@ const PopupPromotion: React.FC<PopupPromotionProps> = ({
82
83
  accessibilityLabel={'popup_promotion'}
83
84
  style={[{ width: imageWidth }, showBaseLineDebug && styles.debugBaseLine]}
84
85
  >
85
- <TouchableOpacity onPress={() => onAction(onActionClick)}>
86
+ <TouchableOpacity onPress={() => handleAction(onActionClick)}>
86
87
  <Image
87
88
  source={{
88
89
  uri: image,
package/Popup/types.ts CHANGED
@@ -43,10 +43,8 @@ export type PopupNotifyProps = {
43
43
  primary: PopupAction;
44
44
 
45
45
  /**
46
- * `buttonDirection`: Optional. Represents the direction in which the action buttons are laid out.
47
- * - 'row': Buttons are laid out horizontally.
48
- * - 'column': Buttons are laid out vertically.
49
- * - 'auto': The layout is automatically determined by title length.
46
+ * `buttonDirection`: Optional. Layout of the action buttons: 'row' horizontal,
47
+ * 'column' vertical, 'auto' column only when a title is too long for a row.
50
48
  */
51
49
  buttonDirection?: PopupActionDirection;
52
50
 
package/Popup/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- type HighlightSegment = { text: string; highlight: boolean };
1
+ type HighlightSegment = { text: string; isHighlighted: boolean };
2
2
 
3
3
  /**
4
4
  * True for cased-script letters, digits and `_`; caseless scripts (CJK, Thai)
@@ -46,12 +46,15 @@ const splitHighlightText = (
46
46
  continue;
47
47
  }
48
48
  if (start > lastIndex) {
49
- segments.push({ text: text.slice(lastIndex, start), highlight: false });
49
+ segments.push({
50
+ text: text.slice(lastIndex, start),
51
+ isHighlighted: false,
52
+ });
50
53
  }
51
- segments.push({ text: match[0], highlight: true });
54
+ segments.push({ text: match[0], isHighlighted: true });
52
55
  lastIndex = end;
53
56
  }
54
- segments.push({ text: text.slice(lastIndex), highlight: false });
57
+ segments.push({ text: text.slice(lastIndex), isHighlighted: false });
55
58
  return segments;
56
59
  };
57
60
 
@@ -21,6 +21,12 @@ import { Colors, Styles } from '../Consts';
21
21
  import styles from './styles';
22
22
  import { ScreenContext, SkeletonContext } from '../Context';
23
23
 
24
+ const PRIMARY_COLOR = Colors.black_05;
25
+ const HIGHLIGHT_COLOR1 = Colors.black_05;
26
+ const HIGHLIGHT_COLOR2 = Colors.black_03;
27
+ const SHIMMER_DURATION = 1000;
28
+ const LAYOUT_ANIMATION_DURATION = 500;
29
+
24
30
  /**
25
31
  * Shimmering placeholder that fills its parent. Reports loading to the screen
26
32
  * context while mounted.
@@ -28,9 +34,6 @@ import { ScreenContext, SkeletonContext } from '../Context';
28
34
  const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
29
35
  const screen = useContext<any>(ScreenContext);
30
36
  const [width, setWidth] = useState(0);
31
- const PRIMARY_COLOR = Colors.black_05;
32
- const HIGHLIGHT_COLOR1 = Colors.black_05;
33
- const HIGHLIGHT_COLOR2 = Colors.black_03;
34
37
  const progress = useSharedValue(0);
35
38
 
36
39
  const shimmerColors = [HIGHLIGHT_COLOR1, HIGHLIGHT_COLOR2, HIGHLIGHT_COLOR1];
@@ -39,7 +42,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
39
42
  progress.value = 0;
40
43
  progress.value = withRepeat(
41
44
  withTiming(1, {
42
- duration: 1000,
45
+ duration: SHIMMER_DURATION,
43
46
  easing: Easing.linear,
44
47
  }),
45
48
  -1,
@@ -57,7 +60,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
57
60
  return { transform: [{ translateX }] };
58
61
  });
59
62
 
60
- const onLayout = (newWidth: number) => {
63
+ const handleLayout = (newWidth: number) => {
61
64
  if (newWidth !== width) {
62
65
  setWidth(newWidth);
63
66
  }
@@ -66,7 +69,7 @@ const Skeleton: React.FC<SkeletonTypes> = ({ style }) => {
66
69
  return (
67
70
  <View style={[styles.container, style]}>
68
71
  <View
69
- onLayout={e => onLayout(e.nativeEvent.layout.width)}
72
+ onLayout={e => handleLayout(e.nativeEvent.layout.width)}
70
73
  style={[Styles.flex, { backgroundColor: PRIMARY_COLOR }]}
71
74
  >
72
75
  <Animated.View style={[styles.shimmer, shimmerStyle]}>
@@ -96,28 +99,28 @@ const SkeletonProvider: React.FC<{ loading: boolean; children: any }> = ({
96
99
  loading = true,
97
100
  children,
98
101
  }) => {
99
- const [previous, setPrevious] = useState(loading);
102
+ const [isLoadingShown, setIsLoadingShown] = useState(loading);
100
103
 
101
104
  useEffect(() => {
102
- if (previous !== loading) {
105
+ if (isLoadingShown !== loading) {
103
106
  if (Platform.OS === 'android') {
104
107
  if (UIManager.setLayoutAnimationEnabledExperimental) {
105
108
  UIManager.setLayoutAnimationEnabledExperimental(true);
106
109
  }
107
110
  }
108
111
  LayoutAnimation.configureNext({
109
- duration: 500,
112
+ duration: LAYOUT_ANIMATION_DURATION,
110
113
  create: {
111
114
  type: LayoutAnimation.Types.easeInEaseOut,
112
115
  property: LayoutAnimation.Properties.opacity,
113
116
  },
114
117
  });
115
- setPrevious(loading);
118
+ setIsLoadingShown(loading);
116
119
  }
117
- }, [loading, previous]);
120
+ }, [loading, isLoadingShown]);
118
121
 
119
122
  return (
120
- <SkeletonContext.Provider value={{ loading: previous }}>
123
+ <SkeletonContext.Provider value={{ loading: isLoadingShown }}>
121
124
  {children}
122
125
  </SkeletonContext.Provider>
123
126
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.169.9-beta.1",
3
+ "version": "0.169.9",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
@@ -1,165 +0,0 @@
1
- import { useMemo } from 'react';
2
- import { useWindowDimensions, type ViewStyle } from 'react-native';
3
- import type { StackNavigationOptions } from '@react-navigation/stack';
4
- import { Spacing } from '../Consts';
5
- import { getHeaderMargin } from '../Layout/utils';
6
- import { FOLDABLE_MIN_WIDTH } from './modalLayout';
7
- import { isIPhoneDuo } from './utils';
8
-
9
- // Top navigation spec, shared with the Compose / SwiftUI kits
10
- const HEADER_FRAME_MAX_WIDTH = 600;
11
-
12
- /**
13
- * Top navigation geometry on every device
14
- * - buttonSize / iconSize: round back and shortcut buttons, icons inside them and the pill
15
- * - pillWidth / pillHeight: help center | home capsule
16
- * - clusterWidth: shortcut, gap and pill on the right of the bar
17
- * - searchHeight: search field inside the header
18
- * - titleGap: space between back and the title, and the title and the right cluster
19
- * - sidePadding: bar edge to back and to the right cluster
20
- * - gradientHeight: header gradient, kept at the phone height on wide windows
21
- * - bannerWidth: animated banner image, kept at the phone width on wide windows
22
- */
23
- const HeaderBarMetrics = {
24
- buttonSize: 32,
25
- iconSize: 20,
26
- pillWidth: 72,
27
- pillHeight: 32,
28
- clusterWidth: 112,
29
- searchHeight: 32,
30
- titleGap: Spacing.S,
31
- sidePadding: Spacing.M,
32
- gradientHeight: 154,
33
- bannerWidth: 375,
34
- };
35
-
36
- /**
37
- * `grid` keeps the header row at the bar edges, `stretch` centers it in a 600pt
38
- * frame on wide windows. Background and status bar always span the window.
39
- */
40
- type HeaderContentLayout = 'grid' | 'stretch';
41
-
42
- type HeaderLayout = typeof HeaderBarMetrics & {
43
- /**
44
- * Window whose short side is at least 600 (Galaxy Z Fold / Pixel Fold inner
45
- * display, tablets), iPhone Duo excluded. Phones, landscape included, are not.
46
- */
47
- isWide: boolean;
48
- /**
49
- * Banner kept at `bannerWidth`, centered: wide windows plus the iPhone Duo
50
- * inner display, portrait and landscape.
51
- */
52
- isBannerCentered: boolean;
53
- /**
54
- * Window edge to back and to the right cluster: `sidePadding`, or centering the
55
- * row in the 600pt frame with `stretch` on a wide window.
56
- */
57
- frameInset: number;
58
- /**
59
- * Header row width between the two `frameInset`s.
60
- */
61
- frameWidth: number;
62
- };
63
-
64
- type HeaderLayoutInput = {
65
- width: number;
66
- height: number;
67
- isDuo: boolean;
68
- contentLayout?: HeaderContentLayout;
69
- };
70
-
71
- /**
72
- * Header geometry for a window, pure so it can be tested without a device.
73
- */
74
- const getHeaderLayout = ({
75
- width,
76
- height,
77
- isDuo,
78
- contentLayout = 'grid',
79
- }: HeaderLayoutInput): HeaderLayout => {
80
- const { sidePadding } = HeaderBarMetrics;
81
- const isLargeWindow = Math.min(width, height) >= FOLDABLE_MIN_WIDTH;
82
- const isWide = !isDuo && isLargeWindow;
83
- let frameInset = sidePadding;
84
- if (isWide && contentLayout === 'stretch') {
85
- const centered = (width - HEADER_FRAME_MAX_WIDTH) / 2;
86
- frameInset = Math.max(sidePadding, centered);
87
- }
88
- return {
89
- ...HeaderBarMetrics,
90
- isWide,
91
- isBannerCentered: isLargeWindow,
92
- frameInset,
93
- frameWidth: width - frameInset * 2,
94
- };
95
- };
96
-
97
- /**
98
- * Widest a title can be between back and the right cluster of the header row.
99
- */
100
- const getTitleBudget = (layout: HeaderLayout) => {
101
- const { buttonSize, titleGap, clusterWidth } = layout;
102
- return layout.frameWidth - buttonSize - titleGap * 2 - clusterWidth;
103
- };
104
-
105
- /**
106
- * Header geometry for the current window; follows fold, unfold and rotation.
107
- */
108
- const useHeaderLayout = (contentLayout?: HeaderContentLayout): HeaderLayout => {
109
- const { width, height } = useWindowDimensions();
110
- const isDuo = isIPhoneDuo();
111
- return useMemo(() => {
112
- return getHeaderLayout({ width, height, isDuo, contentLayout });
113
- }, [width, height, isDuo, contentLayout]);
114
- };
115
-
116
- const TITLE_CONTAINER_RESET: ViewStyle = {
117
- marginLeft: 0,
118
- marginEnd: 0,
119
- marginStart: 0,
120
- marginHorizontal: 0,
121
- marginVertical: 0,
122
- margin: 0,
123
- };
124
-
125
- /**
126
- * React Navigation container styles that keep back, title and the right cluster
127
- * in the `stretch` frame. `undefined` for `grid`, and on iPhone Duo, which keeps
128
- * its own header row.
129
- */
130
- const useHeaderFrameOptions = (
131
- contentLayout?: HeaderContentLayout,
132
- ): StackNavigationOptions | undefined => {
133
- const layout = useHeaderLayout(contentLayout);
134
- const { width } = useWindowDimensions();
135
- const isDuo = isIPhoneDuo();
136
- return useMemo(() => {
137
- if (contentLayout !== 'stretch' || isDuo) {
138
- return undefined;
139
- }
140
- // the header content row already sits this far in on large iOS windows
141
- const shift = layout.frameInset - layout.sidePadding;
142
- const containerShift = Math.max(0, shift - getHeaderMargin(width));
143
- return {
144
- headerLeftContainerStyle: { paddingLeft: containerShift },
145
- headerRightContainerStyle: { paddingRight: containerShift },
146
- headerTitleContainerStyle: {
147
- ...TITLE_CONTAINER_RESET,
148
- left: layout.titleGap,
149
- maxWidth: getTitleBudget(layout),
150
- },
151
- };
152
- }, [contentLayout, isDuo, layout, width]);
153
- };
154
-
155
- export type { HeaderContentLayout, HeaderLayout };
156
-
157
- export {
158
- HeaderBarMetrics,
159
- HEADER_FRAME_MAX_WIDTH,
160
- TITLE_CONTAINER_RESET,
161
- getHeaderLayout,
162
- getTitleBudget,
163
- useHeaderLayout,
164
- useHeaderFrameOptions,
165
- };