@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
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.95.4-improve-co-ca924fd.14+ca924fd5a",
3
+ "version": "0.95.4",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -44,8 +44,9 @@
44
44
  "deepmerge": "^4.2.2",
45
45
  "lodash": "^4.17.21",
46
46
  "react-hook-form": "^7.52.0",
47
- "react-intl": "^6 || ^7",
47
+ "react-intl": "^7.1.11",
48
48
  "react-native-keyboard-aware-scroll-view": "^0.9.5",
49
+ "react-native-modalize": "^2.0.13",
49
50
  "react-native-portalize": "^1.0.7",
50
51
  "react-native-toast-message": "^2.1.6",
51
52
  "react-native-uuid": "^1.4.9",
@@ -89,10 +90,11 @@
89
90
  "react-native-gesture-handler": ">=2.22.0",
90
91
  "react-native-keyboard-aware-scroll-view": "^0.9.5",
91
92
  "react-native-modal-datetime-picker": " >=13.0.0",
93
+ "react-native-modalize": "^2.0.13",
92
94
  "react-native-portalize": "^1.0.7",
93
95
  "react-native-reanimated": "^3.0.0",
94
96
  "react-native-safe-area-context": "^5.4.0",
95
97
  "react-native-svg": ">=12.0.0"
96
98
  },
97
- "gitHead": "ca924fd5a3a8d378218db75aa5b9233c46e7c256"
99
+ "gitHead": "7ebeebd8f3798c54d5c1b3399f25bac554768e3d"
98
100
  }
@@ -1,148 +1,121 @@
1
- var __rest = (this && this.__rest) || function (s, e) {
2
- var t = {};
3
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
- t[p] = s[p];
5
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
- t[p[i]] = s[p[i]];
9
- }
10
- return t;
11
- };
12
- import React, { useImperativeHandle, useMemo, useRef, useState } from "react";
13
- import { AccessibilityInfo, View, findNodeHandle, useWindowDimensions, } from "react-native";
1
+ import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState, } from "react";
14
2
  import { useSafeAreaInsets } from "react-native-safe-area-context";
15
- import { BottomSheetBackdrop, BottomSheetModal, BottomSheetScrollView, BottomSheetView, } from "@gorhom/bottom-sheet";
3
+ import { AccessibilityInfo, Platform, View, findNodeHandle, useWindowDimensions, } from "react-native";
4
+ import { Portal } from "react-native-portalize";
5
+ import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
16
6
  import { useStyles } from "./ContentOverlay.style";
17
- import { useBottomSheetModalBackHandler } from "./hooks/useBottomSheetModalBackHandler";
18
- import { computeContentOverlayBehavior } from "./computeContentOverlayBehavior";
7
+ import { useViewLayoutHeight } from "./hooks/useViewLayoutHeight";
8
+ import { UNSAFE_WrappedModalize } from "./UNSAFE_WrappedModalize";
19
9
  import { useIsScreenReaderEnabled } from "../hooks";
20
10
  import { IconButton } from "../IconButton";
21
11
  import { Heading } from "../Heading";
22
12
  import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
23
13
  import { useAtlantisTheme } from "../AtlantisThemeContext";
24
- const LARGE_SCREEN_BREAKPOINT = 640;
25
- function getModalBackgroundColor(variation, tokens) {
26
- switch (variation) {
27
- case "surface":
28
- return tokens["color-surface"];
29
- case "background":
30
- return tokens["color-surface--background"];
31
- }
32
- }
14
+ export const ContentOverlay = forwardRef(ContentOverlayPortal);
15
+ const ContentOverlayModal = forwardRef(ContentOverlayInternal);
33
16
  // eslint-disable-next-line max-statements
34
- export function ContentOverlay({ children, title, accessibilityLabel, fullScreen = false, showDismiss = false, isDraggable = true, adjustToContentHeight = false, keyboardShouldPersistTaps = false, scrollEnabled = false, modalBackgroundColor = "surface", onClose, onOpen, onBeforeExit, loading = false, ref, }) {
35
- const insets = useSafeAreaInsets();
36
- const { width: windowWidth } = useWindowDimensions();
37
- const bottomSheetModalRef = useRef(null);
38
- const previousIndexRef = useRef(-1);
39
- const [currentPosition, setCurrentPosition] = useState(-1);
40
- const styles = useStyles();
17
+ function ContentOverlayInternal({ children, title, accessibilityLabel, fullScreen = false, showDismiss = false, isDraggable = true, adjustToContentHeight = false, keyboardShouldPersistTaps = false, keyboardAvoidingBehavior, scrollEnabled = false, modalBackgroundColor = "surface", onClose, onOpen, onBeforeExit, loading = false, avoidKeyboardLikeIOS, }, ref) {
18
+ isDraggable = onBeforeExit ? false : isDraggable;
19
+ const isCloseableOnOverlayTap = onBeforeExit ? false : true;
41
20
  const { t } = useAtlantisI18n();
42
21
  const { tokens } = useAtlantisTheme();
22
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
23
+ const insets = useSafeAreaInsets();
24
+ const [position, setPosition] = useState("initial");
43
25
  const isScreenReaderEnabled = useIsScreenReaderEnabled();
44
- const behavior = computeContentOverlayBehavior({
45
- fullScreen,
46
- adjustToContentHeight,
47
- isDraggable,
48
- hasOnBeforeExit: onBeforeExit !== undefined,
49
- showDismiss,
50
- }, {
51
- isScreenReaderEnabled,
52
- position: currentPosition,
53
- });
54
- const effectiveIsDraggable = behavior.isDraggable;
55
- const shouldShowDismiss = behavior.showDismiss;
56
- const isCloseableOnOverlayTap = onBeforeExit === undefined;
57
- // Prevent the Overlay from being flush with the top of the screen, even if we are "100%" or "fullscreen"
58
- const topInset = insets.top || tokens["space-larger"];
26
+ const isFullScreenOrTopPosition = fullScreen || (!adjustToContentHeight && position === "top");
27
+ const shouldShowDismiss = showDismiss || isScreenReaderEnabled || isFullScreenOrTopPosition;
59
28
  const [showHeaderShadow, setShowHeaderShadow] = useState(false);
60
29
  const overlayHeader = useRef(null);
61
- const scrollViewRef = useRef(null);
62
- // enableDynamicSizing will add another snap point of the content height
63
- const snapPoints = useMemo(() => {
64
- // There is a bug with "restore" behavior after keyboard is dismissed.
65
- // https://github.com/gorhom/react-native-bottom-sheet/issues/2465
66
- // providing a 100% snap point "fixes" it for now, but there is an approved PR to fix it
67
- // that just needs to be merged and released: https://github.com/gorhom/react-native-bottom-sheet/pull/2511
68
- return ["100%"];
30
+ const internalRef = useRef(null);
31
+ const [modalizeMethods, setModalizeMethods] = useState();
32
+ const callbackInternalRef = useCallback((instance) => {
33
+ if (instance && !internalRef.current) {
34
+ internalRef.current = instance;
35
+ setModalizeMethods(instance);
36
+ }
69
37
  }, []);
38
+ const refMethods = useMemo(() => {
39
+ if (!(modalizeMethods === null || modalizeMethods === void 0 ? void 0 : modalizeMethods.open) || !(modalizeMethods === null || modalizeMethods === void 0 ? void 0 : modalizeMethods.close)) {
40
+ return {};
41
+ }
42
+ return {
43
+ open: modalizeMethods === null || modalizeMethods === void 0 ? void 0 : modalizeMethods.open,
44
+ close: modalizeMethods === null || modalizeMethods === void 0 ? void 0 : modalizeMethods.close,
45
+ };
46
+ }, [modalizeMethods]);
47
+ const { keyboardHeight } = useKeyboardVisibility();
48
+ useImperativeHandle(ref, () => refMethods, [refMethods]);
49
+ const { handleLayout: handleChildrenLayout, height: childrenHeight, heightKnown: childrenHeightKnown, } = useViewLayoutHeight();
50
+ const { handleLayout: handleHeaderLayout, height: headerHeight, heightKnown: headerHeightKnown, } = useViewLayoutHeight();
51
+ const snapPoint = useMemo(() => {
52
+ if (fullScreen || !isDraggable || adjustToContentHeight) {
53
+ return undefined;
54
+ }
55
+ const overlayHeight = headerHeight + childrenHeight;
56
+ if (overlayHeight >= windowHeight) {
57
+ return undefined;
58
+ }
59
+ return overlayHeight;
60
+ }, [
61
+ fullScreen,
62
+ isDraggable,
63
+ adjustToContentHeight,
64
+ headerHeight,
65
+ childrenHeight,
66
+ windowHeight,
67
+ ]);
68
+ const styles = useStyles();
69
+ const modalStyle = [
70
+ styles.modal,
71
+ windowWidth > 640 ? styles.modalForLargeScreens : undefined,
72
+ { backgroundColor: getModalBackgroundColor(modalBackgroundColor) },
73
+ keyboardHeight > 0 && { marginBottom: 0 },
74
+ ];
75
+ const renderedChildren = renderChildren();
76
+ const renderedHeader = renderHeader();
70
77
  const onCloseController = () => {
71
78
  var _a;
72
79
  if (!onBeforeExit) {
73
- (_a = bottomSheetModalRef.current) === null || _a === void 0 ? void 0 : _a.dismiss();
80
+ (_a = internalRef.current) === null || _a === void 0 ? void 0 : _a.close();
81
+ return true;
74
82
  }
75
83
  else {
76
84
  onBeforeExit();
85
+ return false;
77
86
  }
78
87
  };
79
- const { handleSheetPositionChange } = useBottomSheetModalBackHandler(onCloseController);
80
- useImperativeHandle(ref, () => ({
81
- open: () => {
82
- var _a;
83
- (_a = bottomSheetModalRef.current) === null || _a === void 0 ? void 0 : _a.present();
84
- },
85
- close: () => {
86
- var _a;
87
- (_a = bottomSheetModalRef.current) === null || _a === void 0 ? void 0 : _a.dismiss();
88
- },
89
- }));
90
- const handleChange = (index, position) => {
91
- const previousIndex = previousIndexRef.current;
92
- setCurrentPosition(position);
93
- handleSheetPositionChange(index);
94
- if (previousIndex === -1 && index >= 0) {
95
- // Transitioned from closed to open
96
- onOpen === null || onOpen === void 0 ? void 0 : onOpen();
97
- // Set accessibility focus on header when opened
98
- if (overlayHeader.current) {
99
- const reactTag = findNodeHandle(overlayHeader.current);
100
- if (reactTag) {
101
- AccessibilityInfo.setAccessibilityFocus(reactTag);
88
+ return (React.createElement(React.Fragment, null,
89
+ headerHeightKnown && childrenHeightKnown && (React.createElement(UNSAFE_WrappedModalize, { ref: callbackInternalRef, overlayStyle: styles.overlay, handleStyle: styles.handle, handlePosition: "inside", modalStyle: modalStyle, modalTopOffset: tokens["space-larger"], snapPoint: snapPoint, closeSnapPointStraightEnabled: false, withHandle: isDraggable, panGestureEnabled: isDraggable, adjustToContentHeight: adjustToContentHeight, disableScrollIfPossible: !adjustToContentHeight, onClose: onClose, onOpen: onOpen, keyboardAvoidingBehavior: keyboardAvoidingBehavior, avoidKeyboardLikeIOS: avoidKeyboardLikeIOS, childrenStyle: styles.childrenStyle, onBackButtonPress: onCloseController, closeOnOverlayTap: isCloseableOnOverlayTap, onOpened: () => {
90
+ if (overlayHeader.current) {
91
+ const reactTag = findNodeHandle(overlayHeader.current);
92
+ if (reactTag) {
93
+ AccessibilityInfo.setAccessibilityFocus(reactTag);
94
+ }
102
95
  }
103
- }
104
- }
105
- previousIndexRef.current = index;
106
- };
107
- const handleOnScroll = () => {
108
- var _a;
109
- const scrollTop = ((_a = scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollTop) || 0;
110
- setShowHeaderShadow(scrollTop > 0);
111
- };
112
- const sheetStyle = useMemo(() => windowWidth > LARGE_SCREEN_BREAKPOINT
113
- ? {
114
- width: LARGE_SCREEN_BREAKPOINT,
115
- marginLeft: (windowWidth - LARGE_SCREEN_BREAKPOINT) / 2,
116
- }
117
- : undefined, [windowWidth]);
118
- const backgroundStyle = [
119
- styles.background,
120
- { backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens) },
121
- ];
122
- const handleIndicatorStyles = [
123
- styles.handle,
124
- !effectiveIsDraggable && {
125
- opacity: 0,
126
- },
127
- ];
128
- const renderHeader = () => {
96
+ }, scrollViewProps: {
97
+ scrollEnabled,
98
+ showsVerticalScrollIndicator: false,
99
+ stickyHeaderIndices: Platform.OS === "android" ? [0] : undefined,
100
+ onScroll: handleOnScroll,
101
+ keyboardShouldPersistTaps: keyboardShouldPersistTaps
102
+ ? "handled"
103
+ : "never",
104
+ }, HeaderComponent: Platform.OS === "ios" ? renderedHeader : undefined, onPositionChange: setPosition },
105
+ Platform.OS === "android" ? renderedHeader : undefined,
106
+ renderedChildren)),
107
+ !childrenHeightKnown && (React.createElement(View, { style: [styles.hiddenContent, modalStyle] }, renderedChildren)),
108
+ !headerHeightKnown && (React.createElement(View, { style: [styles.hiddenContent, modalStyle] }, renderedHeader))));
109
+ function renderHeader() {
129
110
  const closeOverlayA11YLabel = t("ContentOverlay.close", {
130
111
  title: title,
131
112
  });
132
113
  const headerStyles = [
133
114
  styles.header,
134
- {
135
- // Background color is necessary for scrollable modals as the content flows behind the header.
136
- backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens),
137
- },
138
- ];
139
- const headerShadowStyles = [
140
115
  showHeaderShadow && styles.headerShadow,
141
- {
142
- backgroundColor: getModalBackgroundColor(modalBackgroundColor, tokens),
143
- },
116
+ { backgroundColor: getModalBackgroundColor(modalBackgroundColor) },
144
117
  ];
145
- return (React.createElement(View, { testID: "ATL-Overlay-Header" },
118
+ return (React.createElement(View, { onLayout: handleHeaderLayout, testID: "ATL-Overlay-Header" },
146
119
  React.createElement(View, { style: headerStyles },
147
120
  React.createElement(View, { style: [
148
121
  styles.title,
@@ -152,18 +125,24 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
152
125
  ] },
153
126
  React.createElement(Heading, { level: "subtitle", variation: loading ? "subdued" : "heading", align: "start" }, title)),
154
127
  shouldShowDismiss && (React.createElement(View, { style: styles.dismissButton, ref: overlayHeader, accessibilityLabel: accessibilityLabel || closeOverlayA11YLabel, accessible: true },
155
- React.createElement(IconButton, { name: "cross", customColor: loading ? tokens["color-disabled"] : tokens["color-heading"], onPress: () => onCloseController(), accessibilityLabel: closeOverlayA11YLabel, testID: "ATL-Overlay-CloseButton" })))),
156
- React.createElement(View, null,
157
- React.createElement(View, { style: headerShadowStyles }))));
158
- };
159
- return (React.createElement(BottomSheetModal, { ref: bottomSheetModalRef, onChange: handleChange, style: sheetStyle, backgroundStyle: backgroundStyle, handleStyle: styles.handleWrapper, handleIndicatorStyle: handleIndicatorStyles, backdropComponent: props => (React.createElement(Backdrop, Object.assign({}, props, { pressBehavior: isCloseableOnOverlayTap ? "close" : "none" }))), snapPoints: snapPoints, enablePanDownToClose: effectiveIsDraggable, enableContentPanningGesture: effectiveIsDraggable, enableHandlePanningGesture: effectiveIsDraggable, enableDynamicSizing: behavior.initialHeight === "contentHeight", keyboardBehavior: "interactive", keyboardBlurBehavior: "restore", topInset: topInset, onDismiss: () => onClose === null || onClose === void 0 ? void 0 : onClose() }, scrollEnabled ? (React.createElement(BottomSheetScrollView, { ref: scrollViewRef, contentContainerStyle: { paddingBottom: insets.bottom }, keyboardShouldPersistTaps: keyboardShouldPersistTaps ? "handled" : "never", showsVerticalScrollIndicator: false, onScroll: handleOnScroll, stickyHeaderIndices: [0] },
160
- renderHeader(),
161
- React.createElement(View, { testID: "ATL-Overlay-Children" }, children))) : (React.createElement(BottomSheetView, null,
162
- renderHeader(),
163
- React.createElement(View, { style: { paddingBottom: insets.bottom }, testID: "ATL-Overlay-Children" }, children)))));
128
+ React.createElement(IconButton, { name: "cross", customColor: loading ? tokens["color-disabled"] : tokens["color-heading"], onPress: () => onCloseController(), accessibilityLabel: closeOverlayA11YLabel, testID: "ATL-Overlay-CloseButton" }))))));
129
+ }
130
+ function renderChildren() {
131
+ return (React.createElement(View, { style: { paddingBottom: insets.bottom }, onLayout: handleChildrenLayout, testID: "ATL-Overlay-Children" }, children));
132
+ }
133
+ function handleOnScroll({ nativeEvent, }) {
134
+ setShowHeaderShadow(nativeEvent.contentOffset.y > 0);
135
+ }
136
+ function getModalBackgroundColor(variation) {
137
+ switch (variation) {
138
+ case "surface":
139
+ return tokens["color-surface"];
140
+ case "background":
141
+ return tokens["color-surface--background"];
142
+ }
143
+ }
164
144
  }
165
- function Backdrop(bottomSheetBackdropProps) {
166
- const styles = useStyles();
167
- const { pressBehavior } = bottomSheetBackdropProps, props = __rest(bottomSheetBackdropProps, ["pressBehavior"]);
168
- return (React.createElement(BottomSheetBackdrop, Object.assign({}, props, { appearsOnIndex: 0, disappearsOnIndex: -1, style: styles.backdrop, opacity: 1, pressBehavior: pressBehavior })));
145
+ function ContentOverlayPortal(modalProps, ref) {
146
+ return (React.createElement(Portal, null,
147
+ React.createElement(ContentOverlayModal, Object.assign({ ref: ref }, modalProps))));
169
148
  }
@@ -1,32 +1,36 @@
1
1
  import { buildThemedStyles } from "../AtlantisThemeContext";
2
2
  export const useStyles = buildThemedStyles(tokens => {
3
3
  const modalBorderRadius = tokens["radius-larger"];
4
+ const titleOffsetFromHandle = tokens["space-base"];
4
5
  return {
5
- handleWrapper: {
6
- paddingBottom: tokens["space-smallest"],
7
- paddingTop: tokens["space-small"],
8
- },
9
6
  handle: {
10
7
  width: tokens["space-largest"],
11
8
  height: tokens["space-smaller"] + tokens["space-smallest"],
12
9
  backgroundColor: tokens["color-border"],
10
+ top: tokens["space-small"],
13
11
  borderRadius: tokens["radius-circle"],
14
12
  },
15
- backdrop: {
13
+ overlay: {
16
14
  backgroundColor: tokens["color-overlay"],
17
15
  },
18
- background: {
16
+ modal: {
19
17
  borderTopLeftRadius: modalBorderRadius,
20
18
  borderTopRightRadius: modalBorderRadius,
21
19
  },
20
+ modalForLargeScreens: {
21
+ width: 640,
22
+ alignSelf: "center",
23
+ },
22
24
  header: {
23
25
  flexDirection: "row",
26
+ backgroundColor: tokens["color-surface"],
27
+ paddingTop: titleOffsetFromHandle,
24
28
  zIndex: tokens["elevation-base"],
25
- minHeight: tokens["space-extravagant"] - tokens["space-base"],
26
29
  borderTopLeftRadius: modalBorderRadius,
27
30
  borderTopRightRadius: modalBorderRadius,
31
+ minHeight: tokens["space-extravagant"],
28
32
  },
29
- headerShadow: Object.assign(Object.assign({}, tokens["shadow-base"]), { position: "absolute", top: -20, height: 20, width: "100%" }),
33
+ headerShadow: Object.assign({}, tokens["shadow-base"]),
30
34
  childrenStyle: {
31
35
  // We need to explicity lower the zIndex because otherwise, the modal content slides over the header shadow.
32
36
  zIndex: -1,
@@ -0,0 +1,23 @@
1
+ import React, { forwardRef, useImperativeHandle, useRef, useState, } from "react";
2
+ import { Modalize } from "react-native-modalize";
3
+ export const UNSAFE_WrappedModalize = forwardRef((props, ref) => {
4
+ const innerRef = useRef(null);
5
+ const [openRenderId, setOpenRenderId] = useState(0);
6
+ useImperativeHandle(ref, () => ({
7
+ open(dest) {
8
+ setOpenRenderId(id => id + 1);
9
+ // Open on a fresh tick for additional safety
10
+ requestAnimationFrame(() => {
11
+ var _a;
12
+ (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.open(dest);
13
+ });
14
+ },
15
+ close(dest) {
16
+ var _a;
17
+ (_a = innerRef.current) === null || _a === void 0 ? void 0 : _a.close(dest);
18
+ },
19
+ }), []);
20
+ // Use a unique key to force a remount, ensuring we get fresh gesture handler nodes within modalize
21
+ return (React.createElement(Modalize, Object.assign({ key: `modalize-${openRenderId}`, ref: innerRef }, props)));
22
+ });
23
+ UNSAFE_WrappedModalize.displayName = "UNSAFE_WrappedModalize";
@@ -1,2 +1 @@
1
1
  export { ContentOverlay } from "./ContentOverlay";
2
- export { ContentOverlayProvider } from "./ContentOverlayProvider";
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Text as RNText, View } from "react-native";
2
+ import { PixelRatio, Platform, Text as RNText, View } from "react-native";
3
3
  import { useStyles } from "./InputFieldWrapper.style";
4
4
  import { PrefixIcon, PrefixLabel } from "./components/Prefix/Prefix";
5
5
  import { SuffixIcon, SuffixLabel } from "./components/Suffix/Suffix";
@@ -11,7 +11,7 @@ import { Text } from "../Text";
11
11
  import { ActivityIndicator } from "../ActivityIndicator";
12
12
  export const INPUT_FIELD_WRAPPER_GLIMMERS_TEST_ID = "ATL-InputFieldWrapper-Glimmers";
13
13
  export const INPUT_FIELD_WRAPPER_SPINNER_TEST_ID = "ATL-InputFieldWrapper-Spinner";
14
- export function InputFieldWrapper({ invalid, disabled, placeholder, assistiveText, prefix, suffix, placeholderMode = "normal", hasValue = false, error, focused = false, children, onClear, showClearAction = false, styleOverride, toolbar, toolbarVisibility = "while-editing", loading = false, loadingType = "spinner", }) {
14
+ export function InputFieldWrapper({ invalid, disabled, placeholder, assistiveText, prefix, suffix, placeholderMode = "normal", hasValue = false, error, focused = false, children, onClear, showClearAction = false, styleOverride, toolbar, toolbarVisibility = "while-editing", loading = false, loadingType = "spinner", multiline = false, }) {
15
15
  fieldAffixRequiredPropsCheck([prefix, suffix]);
16
16
  const handleClear = onClear !== null && onClear !== void 0 ? onClear : noopClear;
17
17
  warnIfClearActionWithNoOnClear(onClear, showClearAction);
@@ -29,6 +29,9 @@ export function InputFieldWrapper({ invalid, disabled, placeholder, assistiveTex
29
29
  (Boolean(invalid) || error) && styles.inputInvalid,
30
30
  disabled && styles.disabled,
31
31
  styleOverride === null || styleOverride === void 0 ? void 0 : styleOverride.container,
32
+ shouldApplyScrollTrapWorkaround(multiline) && {
33
+ maxWidth: "90%",
34
+ },
32
35
  ] },
33
36
  React.createElement(View, { style: styles.field },
34
37
  (prefix === null || prefix === void 0 ? void 0 : prefix.icon) && (React.createElement(PrefixIcon, { disabled: disabled, focused: focused, inputInvalid: inputInvalid, icon: prefix.icon })),
@@ -63,6 +66,13 @@ export function InputFieldWrapper({ invalid, disabled, placeholder, assistiveTex
63
66
  isToolbarVisible && React.createElement(View, { style: styles.toolbar }, toolbar)),
64
67
  assistiveText && !error && !invalid && (React.createElement(Text, { level: "textSupporting", variation: disabled ? "disabled" : focused ? "interactive" : "subdued" }, assistiveText))));
65
68
  }
69
+ function shouldApplyScrollTrapWorkaround(isMultiline) {
70
+ const isCustomFontScale = PixelRatio.getFontScale() !== 1;
71
+ // On iOS, when the OS font scale is not default, it causes multiline inputs to become scroll-trapped
72
+ // which prevents the user from scrolling the parent form view. For now, we're working around this
73
+ // by limiting the width of the input field, thus providing a gap so the user can scroll more easily.
74
+ return isMultiline && Platform.OS === "ios" && isCustomFontScale;
75
+ }
66
76
  function getLabelVariation(error, invalid, disabled) {
67
77
  if (invalid || error) {
68
78
  return "error";
@@ -1,6 +1,5 @@
1
1
  import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
2
- import { Platform, TextInput, findNodeHandle } from "react-native";
3
- import { useBottomSheetInternal } from "@gorhom/bottom-sheet";
2
+ import { Platform, TextInput } from "react-native";
4
3
  import identity from "lodash/identity";
5
4
  import { useShowClear } from "@jobber/hooks";
6
5
  import { useStyles } from "./InputText.style";
@@ -33,10 +32,6 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
33
32
  hasValue,
34
33
  disabled,
35
34
  });
36
- // Bottom sheet keyboard handling - detect if we're inside a ContentOverlay
37
- const bottomSheetContext = useBottomSheetInternal(true);
38
- const animatedKeyboardState = bottomSheetContext === null || bottomSheetContext === void 0 ? void 0 : bottomSheetContext.animatedKeyboardState;
39
- const textInputNodesRef = bottomSheetContext === null || bottomSheetContext === void 0 ? void 0 : bottomSheetContext.textInputNodesRef;
40
35
  // Android doesn't have an accessibility label like iOS does. By adding
41
36
  // it as a placeholder it readds it like a label. However we don't want to
42
37
  // add a placeholder on iOS.
@@ -80,7 +75,7 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
80
75
  }
81
76
  const styles = useStyles();
82
77
  const commonInputStyles = useCommonInputStyles();
83
- return (React.createElement(InputFieldWrapper, { prefix: prefix, suffix: suffix, hasValue: hasValue, placeholderMode: placeholderMode, assistiveText: assistiveText, focused: focused, error: error, invalid: invalid, placeholder: placeholder, disabled: disabled, onClear: handleClear, showClearAction: showClear, styleOverride: styleOverride, toolbar: toolbar, toolbarVisibility: toolbarVisibility, loading: loading, loadingType: loadingType },
78
+ return (React.createElement(InputFieldWrapper, { prefix: prefix, suffix: suffix, hasValue: hasValue, placeholderMode: placeholderMode, assistiveText: assistiveText, focused: focused, error: error, invalid: invalid, placeholder: placeholder, disabled: disabled, onClear: handleClear, showClearAction: showClear, styleOverride: styleOverride, toolbar: toolbar, toolbarVisibility: toolbarVisibility, loading: loading, loadingType: loadingType, multiline: multiline },
84
79
  React.createElement(TextInput, Object.assign({ inputAccessoryViewID: inputAccessoryID || undefined, testID: testID, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, spellCheck: spellCheck, style: [
85
80
  commonInputStyles.input,
86
81
  styles.inputPaddingTop,
@@ -102,12 +97,10 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
102
97
  // This is tech debt related to an issue where keyboard aware scrollview doesn't work if `scrollEnabled` is true. However,
103
98
  // when `scrollEnabled` is false it causes an issue where super long text inputs will jump to the top when a new line is added to the bottom of the input.
104
99
  scrollEnabled: Platform.OS === "ios" && multiline, textContentType: textContentType, onChangeText: handleChangeText, onSubmitEditing: handleOnSubmitEditing, returnKeyType: returnKeyType, blurOnSubmit: shouldBlurOnSubmit, accessibilityLabel: accessibilityLabel || placeholder, accessibilityHint: accessibilityHint, accessibilityState: { busy: loading }, secureTextEntry: secureTextEntry }, androidA11yProps, { onFocus: event => {
105
- handleBottomSheetFocus(event);
106
100
  _name && setFocusedInput(_name);
107
101
  setFocused(true);
108
102
  onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);
109
103
  }, onBlur: event => {
110
- handleBottomSheetBlur(event);
111
104
  _name && setFocusedInput("");
112
105
  setFocused(false);
113
106
  onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
@@ -128,33 +121,6 @@ function InputTextInternal({ invalid, disabled, readonly = false, name, placehol
128
121
  const removedIOSCharValue = isIOS ? value.replace(/\uFFFC/g, "") : value;
129
122
  updateFormAndState(removedIOSCharValue);
130
123
  }
131
- function handleBottomSheetFocus(event) {
132
- if (!animatedKeyboardState || !textInputNodesRef || !(event === null || event === void 0 ? void 0 : event.nativeEvent)) {
133
- return;
134
- }
135
- animatedKeyboardState.set(state => (Object.assign(Object.assign({}, state), { target: event.nativeEvent.target })));
136
- }
137
- function handleBottomSheetBlur(event) {
138
- if (!animatedKeyboardState || !textInputNodesRef || !(event === null || event === void 0 ? void 0 : event.nativeEvent)) {
139
- return;
140
- }
141
- const keyboardState = animatedKeyboardState.get();
142
- const currentlyFocusedInput = TextInput.State.currentlyFocusedInput();
143
- const currentFocusedInput = currentlyFocusedInput !== null
144
- ? findNodeHandle(
145
- // @ts-expect-error - TextInput.State.currentlyFocusedInput() returns NativeMethods
146
- // which is not directly assignable to findNodeHandle's expected type,
147
- // but it works at runtime. This is a known type limitation in React Native.
148
- currentlyFocusedInput)
149
- : null;
150
- // Only remove the target if it belongs to the current component
151
- // and if the currently focused input is not in the targets set
152
- const shouldRemoveCurrentTarget = keyboardState.target === event.nativeEvent.target;
153
- const shouldIgnoreBlurEvent = currentFocusedInput && textInputNodesRef.current.has(currentFocusedInput);
154
- if (shouldRemoveCurrentTarget && !shouldIgnoreBlurEvent) {
155
- animatedKeyboardState.set(state => (Object.assign(Object.assign({}, state), { target: undefined })));
156
- }
157
- }
158
124
  function handleClear() {
159
125
  handleChangeText("");
160
126
  }
@@ -23,7 +23,6 @@
23
23
  "Chip",
24
24
  "Content",
25
25
  "ContentOverlay",
26
- "ContentOverlayProvider",
27
26
  "Disclosure",
28
27
  "Divider",
29
28
  "EmptyState",