@jobber/components-native 0.95.4-improve-co-ca924fd.14 → 0.95.4

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 (41) hide show
  1. package/dist/package.json +5 -3
  2. package/dist/src/ContentOverlay/ContentOverlay.js +107 -128
  3. package/dist/src/ContentOverlay/ContentOverlay.style.js +12 -8
  4. package/dist/src/ContentOverlay/UNSAFE_WrappedModalize.js +23 -0
  5. package/dist/src/ContentOverlay/index.js +0 -1
  6. package/dist/src/InputFieldWrapper/InputFieldWrapper.js +12 -2
  7. package/dist/src/InputText/InputText.js +2 -36
  8. package/dist/src/utils/meta/meta.json +0 -1
  9. package/dist/tsconfig.build.tsbuildinfo +1 -1
  10. package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +5 -1
  11. package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +10 -11
  12. package/dist/types/src/ContentOverlay/UNSAFE_WrappedModalize.d.ts +3 -0
  13. package/dist/types/src/ContentOverlay/index.d.ts +0 -1
  14. package/dist/types/src/ContentOverlay/types.d.ts +12 -5
  15. package/dist/types/src/InputFieldWrapper/InputFieldWrapper.d.ts +5 -1
  16. package/jestSetup.js +0 -2
  17. package/package.json +5 -3
  18. package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
  19. package/src/ContentOverlay/ContentOverlay.test.tsx +79 -157
  20. package/src/ContentOverlay/ContentOverlay.tsx +210 -223
  21. package/src/ContentOverlay/UNSAFE_WrappedModalize.tsx +41 -0
  22. package/src/ContentOverlay/index.ts +0 -1
  23. package/src/ContentOverlay/types.ts +13 -5
  24. package/src/InputFieldWrapper/InputFieldWrapper.test.tsx +47 -0
  25. package/src/InputFieldWrapper/InputFieldWrapper.tsx +19 -1
  26. package/src/InputText/InputText.test.tsx +0 -122
  27. package/src/InputText/InputText.tsx +3 -52
  28. package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +20 -0
  29. package/src/utils/meta/meta.json +0 -1
  30. package/dist/src/ContentOverlay/ContentOverlayProvider.js +0 -5
  31. package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +0 -76
  32. package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +0 -25
  33. package/dist/types/src/ContentOverlay/ContentOverlayProvider.d.ts +0 -6
  34. package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +0 -32
  35. package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +0 -7
  36. package/src/ContentOverlay/ContentOverlay.stories.tsx +0 -59
  37. package/src/ContentOverlay/ContentOverlayProvider.tsx +0 -12
  38. package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +0 -276
  39. package/src/ContentOverlay/computeContentOverlayBehavior.ts +0 -119
  40. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +0 -81
  41. package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.ts +0 -36
@@ -1,204 +1,228 @@
1
- import React, { useImperativeHandle, useMemo, useRef, useState } from "react";
1
+ import type { Ref } from "react";
2
+ import React, {
3
+ forwardRef,
4
+ useCallback,
5
+ useImperativeHandle,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import type { Modalize } from "react-native-modalize";
11
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
12
+ import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native";
2
13
  import {
3
14
  AccessibilityInfo,
15
+ Platform,
4
16
  View,
5
17
  findNodeHandle,
6
18
  useWindowDimensions,
7
19
  } from "react-native";
8
- import { useSafeAreaInsets } from "react-native-safe-area-context";
9
- import {
10
- BottomSheetBackdrop,
11
- BottomSheetModal,
12
- BottomSheetScrollView,
13
- BottomSheetView,
14
- } from "@gorhom/bottom-sheet";
15
- import type {
16
- BottomSheetBackdropProps,
17
- BottomSheetModal as BottomSheetModalType,
18
- BottomSheetScrollViewMethods,
19
- } from "@gorhom/bottom-sheet";
20
- import type { ContentOverlayProps, ModalBackgroundColor } from "./types";
20
+ import { Portal } from "react-native-portalize";
21
+ import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
21
22
  import { useStyles } from "./ContentOverlay.style";
22
- import { useBottomSheetModalBackHandler } from "./hooks/useBottomSheetModalBackHandler";
23
- import { computeContentOverlayBehavior } from "./computeContentOverlayBehavior";
23
+ import { useViewLayoutHeight } from "./hooks/useViewLayoutHeight";
24
+ import type {
25
+ ContentOverlayProps,
26
+ ContentOverlayRef,
27
+ ModalBackgroundColor,
28
+ } from "./types";
29
+ import { UNSAFE_WrappedModalize } from "./UNSAFE_WrappedModalize";
24
30
  import { useIsScreenReaderEnabled } from "../hooks";
25
31
  import { IconButton } from "../IconButton";
26
32
  import { Heading } from "../Heading";
27
33
  import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
28
34
  import { useAtlantisTheme } from "../AtlantisThemeContext";
29
35
 
30
- const LARGE_SCREEN_BREAKPOINT = 640;
31
-
32
- function getModalBackgroundColor(
33
- variation: ModalBackgroundColor,
34
- tokens: ReturnType<typeof useAtlantisTheme>["tokens"],
35
- ) {
36
- switch (variation) {
37
- case "surface":
38
- return tokens["color-surface"];
39
- case "background":
40
- return tokens["color-surface--background"];
41
- }
42
- }
36
+ export const ContentOverlay = forwardRef(ContentOverlayPortal);
37
+ const ContentOverlayModal = forwardRef(ContentOverlayInternal);
43
38
 
44
39
  // eslint-disable-next-line max-statements
45
- export function ContentOverlay({
46
- children,
47
- title,
48
- accessibilityLabel,
49
- fullScreen = false,
50
- showDismiss = false,
51
- isDraggable = true,
52
- adjustToContentHeight = false,
53
- keyboardShouldPersistTaps = false,
54
- scrollEnabled = false,
55
- modalBackgroundColor = "surface",
56
- onClose,
57
- onOpen,
58
- onBeforeExit,
59
- loading = false,
60
- ref,
61
- }: ContentOverlayProps) {
62
- const insets = useSafeAreaInsets();
63
- const { width: windowWidth } = useWindowDimensions();
64
- const bottomSheetModalRef = useRef<BottomSheetModalType>(null);
65
- const previousIndexRef = useRef(-1);
66
- const [currentPosition, setCurrentPosition] = useState<number>(-1);
67
-
68
- const styles = useStyles();
40
+ function ContentOverlayInternal(
41
+ {
42
+ children,
43
+ title,
44
+ accessibilityLabel,
45
+ fullScreen = false,
46
+ showDismiss = false,
47
+ isDraggable = true,
48
+ adjustToContentHeight = false,
49
+ keyboardShouldPersistTaps = false,
50
+ keyboardAvoidingBehavior,
51
+ scrollEnabled = false,
52
+ modalBackgroundColor = "surface",
53
+ onClose,
54
+ onOpen,
55
+ onBeforeExit,
56
+ loading = false,
57
+ avoidKeyboardLikeIOS,
58
+ }: ContentOverlayProps,
59
+ ref: Ref<ContentOverlayRef>,
60
+ ) {
61
+ isDraggable = onBeforeExit ? false : isDraggable;
62
+ const isCloseableOnOverlayTap = onBeforeExit ? false : true;
69
63
  const { t } = useAtlantisI18n();
70
64
  const { tokens } = useAtlantisTheme();
65
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
66
+ const insets = useSafeAreaInsets();
67
+ const [position, setPosition] = useState<"top" | "initial">("initial");
71
68
  const isScreenReaderEnabled = useIsScreenReaderEnabled();
69
+ const isFullScreenOrTopPosition =
70
+ fullScreen || (!adjustToContentHeight && position === "top");
71
+ const shouldShowDismiss =
72
+ showDismiss || isScreenReaderEnabled || isFullScreenOrTopPosition;
73
+ const [showHeaderShadow, setShowHeaderShadow] = useState<boolean>(false);
74
+ const overlayHeader = useRef<View>(null);
72
75
 
73
- const behavior = computeContentOverlayBehavior(
74
- {
75
- fullScreen,
76
- adjustToContentHeight,
77
- isDraggable,
78
- hasOnBeforeExit: onBeforeExit !== undefined,
79
- showDismiss,
80
- },
81
- {
82
- isScreenReaderEnabled,
83
- position: currentPosition,
84
- },
85
- );
76
+ const internalRef = useRef<Modalize>(null);
77
+ const [modalizeMethods, setModalizeMethods] = useState<ContentOverlayRef>();
78
+ const callbackInternalRef = useCallback((instance: Modalize) => {
79
+ if (instance && !internalRef.current) {
80
+ internalRef.current = instance;
81
+ setModalizeMethods(instance);
82
+ }
83
+ }, []);
86
84
 
87
- const effectiveIsDraggable = behavior.isDraggable;
88
- const shouldShowDismiss = behavior.showDismiss;
89
- const isCloseableOnOverlayTap = onBeforeExit === undefined;
85
+ const refMethods = useMemo(() => {
86
+ if (!modalizeMethods?.open || !modalizeMethods?.close) {
87
+ return {};
88
+ }
90
89
 
91
- // Prevent the Overlay from being flush with the top of the screen, even if we are "100%" or "fullscreen"
92
- const topInset = insets.top || tokens["space-larger"];
90
+ return {
91
+ open: modalizeMethods?.open,
92
+ close: modalizeMethods?.close,
93
+ };
94
+ }, [modalizeMethods]);
93
95
 
94
- const [showHeaderShadow, setShowHeaderShadow] = useState<boolean>(false);
95
- const overlayHeader = useRef<View>(null);
96
- const scrollViewRef = useRef<
97
- BottomSheetScrollViewMethods & { scrollTop?: number }
98
- >(null);
96
+ const { keyboardHeight } = useKeyboardVisibility();
97
+ useImperativeHandle(ref, () => refMethods, [refMethods]);
99
98
 
100
- // enableDynamicSizing will add another snap point of the content height
101
- const snapPoints = useMemo(() => {
102
- // There is a bug with "restore" behavior after keyboard is dismissed.
103
- // https://github.com/gorhom/react-native-bottom-sheet/issues/2465
104
- // providing a 100% snap point "fixes" it for now, but there is an approved PR to fix it
105
- // that just needs to be merged and released: https://github.com/gorhom/react-native-bottom-sheet/pull/2511
106
- return ["100%"];
107
- }, []);
99
+ const {
100
+ handleLayout: handleChildrenLayout,
101
+ height: childrenHeight,
102
+ heightKnown: childrenHeightKnown,
103
+ } = useViewLayoutHeight();
104
+ const {
105
+ handleLayout: handleHeaderLayout,
106
+ height: headerHeight,
107
+ heightKnown: headerHeightKnown,
108
+ } = useViewLayoutHeight();
108
109
 
109
- const onCloseController = () => {
110
- if (!onBeforeExit) {
111
- bottomSheetModalRef.current?.dismiss();
112
- } else {
113
- onBeforeExit();
110
+ const snapPoint = useMemo(() => {
111
+ if (fullScreen || !isDraggable || adjustToContentHeight) {
112
+ return undefined;
114
113
  }
115
- };
114
+ const overlayHeight = headerHeight + childrenHeight;
116
115
 
117
- const { handleSheetPositionChange } =
118
- useBottomSheetModalBackHandler(onCloseController);
116
+ if (overlayHeight >= windowHeight) {
117
+ return undefined;
118
+ }
119
119
 
120
- useImperativeHandle(ref, () => ({
121
- open: () => {
122
- bottomSheetModalRef.current?.present();
123
- },
124
- close: () => {
125
- bottomSheetModalRef.current?.dismiss();
126
- },
127
- }));
120
+ return overlayHeight;
121
+ }, [
122
+ fullScreen,
123
+ isDraggable,
124
+ adjustToContentHeight,
125
+ headerHeight,
126
+ childrenHeight,
127
+ windowHeight,
128
+ ]);
128
129
 
129
- const handleChange = (index: number, position: number) => {
130
- const previousIndex = previousIndexRef.current;
130
+ const styles = useStyles();
131
131
 
132
- setCurrentPosition(position);
133
- handleSheetPositionChange(index);
132
+ const modalStyle = [
133
+ styles.modal,
134
+ windowWidth > 640 ? styles.modalForLargeScreens : undefined,
135
+ { backgroundColor: getModalBackgroundColor(modalBackgroundColor) },
136
+ keyboardHeight > 0 && { marginBottom: 0 },
137
+ ];
134
138
 
135
- if (previousIndex === -1 && index >= 0) {
136
- // Transitioned from closed to open
137
- onOpen?.();
139
+ const renderedChildren = renderChildren();
140
+ const renderedHeader = renderHeader();
138
141
 
139
- // Set accessibility focus on header when opened
140
- if (overlayHeader.current) {
141
- const reactTag = findNodeHandle(overlayHeader.current);
142
+ const onCloseController = () => {
143
+ if (!onBeforeExit) {
144
+ internalRef.current?.close();
142
145
 
143
- if (reactTag) {
144
- AccessibilityInfo.setAccessibilityFocus(reactTag);
145
- }
146
- }
147
- }
146
+ return true;
147
+ } else {
148
+ onBeforeExit();
148
149
 
149
- previousIndexRef.current = index;
150
+ return false;
151
+ }
150
152
  };
151
153
 
152
- const handleOnScroll = () => {
153
- const scrollTop = scrollViewRef.current?.scrollTop || 0;
154
- setShowHeaderShadow(scrollTop > 0);
155
- };
154
+ return (
155
+ <>
156
+ {headerHeightKnown && childrenHeightKnown && (
157
+ <UNSAFE_WrappedModalize
158
+ ref={callbackInternalRef}
159
+ overlayStyle={styles.overlay}
160
+ handleStyle={styles.handle}
161
+ handlePosition="inside"
162
+ modalStyle={modalStyle}
163
+ modalTopOffset={tokens["space-larger"]}
164
+ snapPoint={snapPoint}
165
+ closeSnapPointStraightEnabled={false}
166
+ withHandle={isDraggable}
167
+ panGestureEnabled={isDraggable}
168
+ adjustToContentHeight={adjustToContentHeight}
169
+ disableScrollIfPossible={!adjustToContentHeight} // workaround for scroll not working on Android when content fills the screen with adjustToContentHeight
170
+ onClose={onClose}
171
+ onOpen={onOpen}
172
+ keyboardAvoidingBehavior={keyboardAvoidingBehavior}
173
+ avoidKeyboardLikeIOS={avoidKeyboardLikeIOS}
174
+ childrenStyle={styles.childrenStyle}
175
+ onBackButtonPress={onCloseController}
176
+ closeOnOverlayTap={isCloseableOnOverlayTap}
177
+ onOpened={() => {
178
+ if (overlayHeader.current) {
179
+ const reactTag = findNodeHandle(overlayHeader.current);
156
180
 
157
- const sheetStyle = useMemo(
158
- () =>
159
- windowWidth > LARGE_SCREEN_BREAKPOINT
160
- ? {
161
- width: LARGE_SCREEN_BREAKPOINT,
162
- marginLeft: (windowWidth - LARGE_SCREEN_BREAKPOINT) / 2,
163
- }
164
- : undefined,
165
- [windowWidth],
181
+ if (reactTag) {
182
+ AccessibilityInfo.setAccessibilityFocus(reactTag);
183
+ }
184
+ }
185
+ }}
186
+ scrollViewProps={{
187
+ scrollEnabled,
188
+ showsVerticalScrollIndicator: false,
189
+ stickyHeaderIndices: Platform.OS === "android" ? [0] : undefined,
190
+ onScroll: handleOnScroll,
191
+ keyboardShouldPersistTaps: keyboardShouldPersistTaps
192
+ ? "handled"
193
+ : "never",
194
+ }}
195
+ HeaderComponent={Platform.OS === "ios" ? renderedHeader : undefined}
196
+ onPositionChange={setPosition}
197
+ >
198
+ {Platform.OS === "android" ? renderedHeader : undefined}
199
+ {renderedChildren}
200
+ </UNSAFE_WrappedModalize>
201
+ )}
202
+ {!childrenHeightKnown && (
203
+ <View style={[styles.hiddenContent, modalStyle]}>
204
+ {renderedChildren}
205
+ </View>
206
+ )}
207
+ {!headerHeightKnown && (
208
+ <View style={[styles.hiddenContent, modalStyle]}>{renderedHeader}</View>
209
+ )}
210
+ </>
166
211
  );
167
212
 
168
- const backgroundStyle = [
169
- styles.background,
170
- { backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens) },
171
- ];
172
-
173
- const handleIndicatorStyles = [
174
- styles.handle,
175
- !effectiveIsDraggable && {
176
- opacity: 0,
177
- },
178
- ];
179
-
180
- const renderHeader = () => {
213
+ function renderHeader() {
181
214
  const closeOverlayA11YLabel = t("ContentOverlay.close", {
182
215
  title: title,
183
216
  });
184
217
 
185
218
  const headerStyles = [
186
219
  styles.header,
187
- {
188
- // Background color is necessary for scrollable modals as the content flows behind the header.
189
- backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens),
190
- },
191
- ];
192
-
193
- const headerShadowStyles = [
194
220
  showHeaderShadow && styles.headerShadow,
195
- {
196
- backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens),
197
- },
221
+ { backgroundColor: getModalBackgroundColor(modalBackgroundColor) },
198
222
  ];
199
223
 
200
224
  return (
201
- <View testID="ATL-Overlay-Header">
225
+ <View onLayout={handleHeaderLayout} testID="ATL-Overlay-Header">
202
226
  <View style={headerStyles}>
203
227
  <View
204
228
  style={[
@@ -236,82 +260,45 @@ export function ContentOverlay({
236
260
  </View>
237
261
  )}
238
262
  </View>
239
- <View>
240
- <View style={headerShadowStyles} />
241
- </View>
242
263
  </View>
243
264
  );
244
- };
265
+ }
245
266
 
246
- return (
247
- <BottomSheetModal
248
- ref={bottomSheetModalRef}
249
- onChange={handleChange}
250
- style={sheetStyle}
251
- backgroundStyle={backgroundStyle}
252
- handleStyle={styles.handleWrapper}
253
- handleIndicatorStyle={handleIndicatorStyles}
254
- backdropComponent={props => (
255
- <Backdrop
256
- {...props}
257
- pressBehavior={isCloseableOnOverlayTap ? "close" : "none"}
258
- />
259
- )}
260
- snapPoints={snapPoints}
261
- enablePanDownToClose={effectiveIsDraggable}
262
- enableContentPanningGesture={effectiveIsDraggable}
263
- enableHandlePanningGesture={effectiveIsDraggable}
264
- enableDynamicSizing={behavior.initialHeight === "contentHeight"}
265
- keyboardBehavior="interactive"
266
- keyboardBlurBehavior="restore"
267
- topInset={topInset}
268
- onDismiss={() => onClose?.()}
269
- >
270
- {scrollEnabled ? (
271
- <BottomSheetScrollView
272
- ref={scrollViewRef}
273
- contentContainerStyle={{ paddingBottom: insets.bottom }}
274
- keyboardShouldPersistTaps={
275
- keyboardShouldPersistTaps ? "handled" : "never"
276
- }
277
- showsVerticalScrollIndicator={false}
278
- onScroll={handleOnScroll}
279
- stickyHeaderIndices={[0]}
280
- >
281
- {renderHeader()}
282
- <View testID="ATL-Overlay-Children">{children}</View>
283
- </BottomSheetScrollView>
284
- ) : (
285
- <BottomSheetView>
286
- {renderHeader()}
287
- <View
288
- style={{ paddingBottom: insets.bottom }}
289
- testID="ATL-Overlay-Children"
290
- >
291
- {children}
292
- </View>
293
- </BottomSheetView>
294
- )}
295
- </BottomSheetModal>
296
- );
267
+ function renderChildren() {
268
+ return (
269
+ <View
270
+ style={{ paddingBottom: insets.bottom }}
271
+ onLayout={handleChildrenLayout}
272
+ testID="ATL-Overlay-Children"
273
+ >
274
+ {children}
275
+ </View>
276
+ );
277
+ }
278
+
279
+ function handleOnScroll({
280
+ nativeEvent,
281
+ }: NativeSyntheticEvent<NativeScrollEvent>) {
282
+ setShowHeaderShadow(nativeEvent.contentOffset.y > 0);
283
+ }
284
+
285
+ function getModalBackgroundColor(variation: ModalBackgroundColor) {
286
+ switch (variation) {
287
+ case "surface":
288
+ return tokens["color-surface"];
289
+ case "background":
290
+ return tokens["color-surface--background"];
291
+ }
292
+ }
297
293
  }
298
294
 
299
- function Backdrop(
300
- bottomSheetBackdropProps: BottomSheetBackdropProps & {
301
- pressBehavior: "none" | "close";
302
- },
295
+ function ContentOverlayPortal(
296
+ modalProps: ContentOverlayProps,
297
+ ref: Ref<ContentOverlayRef>,
303
298
  ) {
304
- const styles = useStyles();
305
- const { pressBehavior, ...props } = bottomSheetBackdropProps;
306
-
307
299
  return (
308
- <BottomSheetBackdrop
309
- {...props}
310
- appearsOnIndex={0}
311
- disappearsOnIndex={-1}
312
- style={styles.backdrop}
313
- opacity={1}
314
- pressBehavior={pressBehavior}
315
- />
300
+ <Portal>
301
+ <ContentOverlayModal ref={ref} {...modalProps} />
302
+ </Portal>
316
303
  );
317
304
  }
@@ -0,0 +1,41 @@
1
+ import React, {
2
+ forwardRef,
3
+ useImperativeHandle,
4
+ useRef,
5
+ useState,
6
+ } from "react";
7
+ import { Modalize } from "react-native-modalize";
8
+ import type { IHandles } from "react-native-modalize/lib/options";
9
+
10
+ type Props = React.ComponentProps<typeof Modalize>;
11
+
12
+ export const UNSAFE_WrappedModalize = forwardRef<IHandles | undefined, Props>(
13
+ (props, ref) => {
14
+ const innerRef = useRef<IHandles | null>(null);
15
+ const [openRenderId, setOpenRenderId] = useState(0);
16
+
17
+ useImperativeHandle(
18
+ ref,
19
+ () => ({
20
+ open(dest) {
21
+ setOpenRenderId(id => id + 1);
22
+ // Open on a fresh tick for additional safety
23
+ requestAnimationFrame(() => {
24
+ innerRef.current?.open(dest);
25
+ });
26
+ },
27
+ close(dest) {
28
+ innerRef.current?.close(dest);
29
+ },
30
+ }),
31
+ [],
32
+ );
33
+
34
+ // Use a unique key to force a remount, ensuring we get fresh gesture handler nodes within modalize
35
+ return (
36
+ <Modalize key={`modalize-${openRenderId}`} ref={innerRef} {...props} />
37
+ );
38
+ },
39
+ );
40
+
41
+ UNSAFE_WrappedModalize.displayName = "UNSAFE_WrappedModalize";
@@ -1,3 +1,2 @@
1
1
  export { ContentOverlay } from "./ContentOverlay";
2
- export { ContentOverlayProvider } from "./ContentOverlayProvider";
3
2
  export type { ContentOverlayRef, ModalBackgroundColor } from "./types";
@@ -1,4 +1,5 @@
1
- import type { ReactNode, Ref } from "react";
1
+ import type { ReactNode } from "react";
2
+ import type { Modalize } from "react-native-modalize";
2
3
 
3
4
  export interface ContentOverlayProps {
4
5
  /**
@@ -66,6 +67,12 @@ export interface ContentOverlayProps {
66
67
  */
67
68
  readonly onBeforeExit?: () => void;
68
69
 
70
+ /**
71
+ * Define the behavior of the keyboard when having inputs inside the modal.
72
+ * @default padding
73
+ */
74
+ readonly keyboardAvoidingBehavior?: "height" | "padding" | "position";
75
+
69
76
  /**
70
77
  * Boolean to show a disabled state
71
78
  * @default false
@@ -73,16 +80,17 @@ export interface ContentOverlayProps {
73
80
  readonly loading?: boolean;
74
81
 
75
82
  /**
76
- * Ref to the content overlay component.
83
+ * Define keyboard's Android behavior like iOS's one.
84
+ * @default Platform.select({ ios: true, android: false })
77
85
  */
78
- readonly ref?: Ref<ContentOverlayRef>;
86
+ readonly avoidKeyboardLikeIOS?: boolean;
79
87
  }
80
88
 
81
89
  export type ModalBackgroundColor = "surface" | "background";
82
90
 
83
91
  export type ContentOverlayRef =
84
92
  | {
85
- open?: () => void;
86
- close?: () => void;
93
+ open?: Modalize["open"];
94
+ close?: Modalize["close"];
87
95
  }
88
96
  | undefined;
@@ -358,4 +358,51 @@ describe("InputFieldWrapper", () => {
358
358
  ).toBeNull();
359
359
  });
360
360
  });
361
+
362
+ describe("multiline", () => {
363
+ it("applies maxWidth of 90% when multiline is true", () => {
364
+ const { getByTestId } = renderInputFieldWrapper({ multiline: true });
365
+ const container = getByTestId("ATL-InputFieldWrapper");
366
+
367
+ const flattenedStyle = container.props.style.reduce(
368
+ (style: ViewStyle, additionalStyles: ViewStyle) => ({
369
+ ...style,
370
+ ...additionalStyles,
371
+ }),
372
+ {},
373
+ );
374
+
375
+ expect(flattenedStyle.maxWidth).toEqual("90%");
376
+ });
377
+
378
+ it("does not apply maxWidth when multiline is false", () => {
379
+ const { getByTestId } = renderInputFieldWrapper({ multiline: false });
380
+ const container = getByTestId("ATL-InputFieldWrapper");
381
+
382
+ const flattenedStyle = container.props.style.reduce(
383
+ (style: ViewStyle, additionalStyles: ViewStyle) => ({
384
+ ...style,
385
+ ...additionalStyles,
386
+ }),
387
+ {},
388
+ );
389
+
390
+ expect(flattenedStyle.maxWidth).toBeUndefined();
391
+ });
392
+
393
+ it("does not apply maxWidth when multiline is not provided", () => {
394
+ const { getByTestId } = renderInputFieldWrapper({});
395
+ const container = getByTestId("ATL-InputFieldWrapper");
396
+
397
+ const flattenedStyle = container.props.style.reduce(
398
+ (style: ViewStyle, additionalStyles: ViewStyle) => ({
399
+ ...style,
400
+ ...additionalStyles,
401
+ }),
402
+ {},
403
+ );
404
+
405
+ expect(flattenedStyle.maxWidth).toBeUndefined();
406
+ });
407
+ });
361
408
  });