@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.
- package/dist/package.json +5 -3
- package/dist/src/ContentOverlay/ContentOverlay.js +107 -128
- package/dist/src/ContentOverlay/ContentOverlay.style.js +12 -8
- package/dist/src/ContentOverlay/UNSAFE_WrappedModalize.js +23 -0
- package/dist/src/ContentOverlay/index.js +0 -1
- package/dist/src/InputFieldWrapper/InputFieldWrapper.js +12 -2
- package/dist/src/InputText/InputText.js +2 -36
- package/dist/src/utils/meta/meta.json +0 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +5 -1
- package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +10 -11
- package/dist/types/src/ContentOverlay/UNSAFE_WrappedModalize.d.ts +3 -0
- package/dist/types/src/ContentOverlay/index.d.ts +0 -1
- package/dist/types/src/ContentOverlay/types.d.ts +12 -5
- package/dist/types/src/InputFieldWrapper/InputFieldWrapper.d.ts +5 -1
- package/jestSetup.js +0 -2
- package/package.json +5 -3
- package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
- package/src/ContentOverlay/ContentOverlay.test.tsx +79 -157
- package/src/ContentOverlay/ContentOverlay.tsx +210 -223
- package/src/ContentOverlay/UNSAFE_WrappedModalize.tsx +41 -0
- package/src/ContentOverlay/index.ts +0 -1
- package/src/ContentOverlay/types.ts +13 -5
- package/src/InputFieldWrapper/InputFieldWrapper.test.tsx +47 -0
- package/src/InputFieldWrapper/InputFieldWrapper.tsx +19 -1
- package/src/InputText/InputText.test.tsx +0 -122
- package/src/InputText/InputText.tsx +3 -52
- package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +20 -0
- package/src/utils/meta/meta.json +0 -1
- package/dist/src/ContentOverlay/ContentOverlayProvider.js +0 -5
- package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +0 -76
- package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +0 -25
- package/dist/types/src/ContentOverlay/ContentOverlayProvider.d.ts +0 -6
- package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +0 -32
- package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +0 -7
- package/src/ContentOverlay/ContentOverlay.stories.tsx +0 -59
- package/src/ContentOverlay/ContentOverlayProvider.tsx +0 -12
- package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +0 -276
- package/src/ContentOverlay/computeContentOverlayBehavior.ts +0 -119
- package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +0 -81
- 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
|
|
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": "^
|
|
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": "
|
|
99
|
+
"gitHead": "7ebeebd8f3798c54d5c1b3399f25bac554768e3d"
|
|
98
100
|
}
|
|
@@ -1,148 +1,121 @@
|
|
|
1
|
-
|
|
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 {
|
|
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 {
|
|
18
|
-
import {
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
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
|
|
45
|
-
|
|
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
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 =
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
13
|
+
overlay: {
|
|
16
14
|
backgroundColor: tokens["color-overlay"],
|
|
17
15
|
},
|
|
18
|
-
|
|
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(
|
|
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,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
|
|
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
|
}
|