@jobber/components-native 0.37.0 → 0.38.0
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/src/ContentOverlay/ContentOverlay.js +144 -0
- package/dist/src/ContentOverlay/ContentOverlay.style.js +56 -0
- package/dist/src/ContentOverlay/hooks/useKeyboardVisibility.js +21 -0
- package/dist/src/ContentOverlay/hooks/useViewLayoutHeight.js +10 -0
- package/dist/src/ContentOverlay/index.js +1 -0
- package/dist/src/ContentOverlay/messages.js +8 -0
- package/dist/src/ContentOverlay/types.js +1 -0
- package/dist/src/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +6 -0
- package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +60 -0
- package/dist/types/src/ContentOverlay/hooks/useKeyboardVisibility.d.ts +6 -0
- package/dist/types/src/ContentOverlay/hooks/useViewLayoutHeight.d.ts +6 -0
- package/dist/types/src/ContentOverlay/index.d.ts +2 -0
- package/dist/types/src/ContentOverlay/messages.d.ts +7 -0
- package/dist/types/src/ContentOverlay/types.d.ts +87 -0
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/ContentOverlay/ContentOverlay.style.ts +70 -0
- package/src/ContentOverlay/ContentOverlay.test.tsx +371 -0
- package/src/ContentOverlay/ContentOverlay.tsx +295 -0
- package/src/ContentOverlay/hooks/useKeyboardVisibility.test.ts +42 -0
- package/src/ContentOverlay/hooks/useKeyboardVisibility.ts +36 -0
- package/src/ContentOverlay/hooks/useViewLayoutHeight.test.ts +56 -0
- package/src/ContentOverlay/hooks/useViewLayoutHeight.ts +18 -0
- package/src/ContentOverlay/index.ts +2 -0
- package/src/ContentOverlay/messages.ts +9 -0
- package/src/ContentOverlay/types.ts +96 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
Ref,
|
|
3
|
+
forwardRef,
|
|
4
|
+
useCallback,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { Modalize } from "react-native-modalize";
|
|
11
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
|
+
import {
|
|
13
|
+
AccessibilityInfo,
|
|
14
|
+
NativeScrollEvent,
|
|
15
|
+
NativeSyntheticEvent,
|
|
16
|
+
Platform,
|
|
17
|
+
View,
|
|
18
|
+
findNodeHandle,
|
|
19
|
+
useWindowDimensions,
|
|
20
|
+
} from "react-native";
|
|
21
|
+
import { Portal } from "react-native-portalize";
|
|
22
|
+
import { useIntl } from "react-intl";
|
|
23
|
+
import { useKeyboardVisibility } from "./hooks/useKeyboardVisibility";
|
|
24
|
+
import { styles } from "./ContentOverlay.style";
|
|
25
|
+
import { messages } from "./messages";
|
|
26
|
+
import { useViewLayoutHeight } from "./hooks/useViewLayoutHeight";
|
|
27
|
+
import {
|
|
28
|
+
ContentOverlayProps,
|
|
29
|
+
ContentOverlayRef,
|
|
30
|
+
ModalBackgroundColor,
|
|
31
|
+
} from "./types";
|
|
32
|
+
import { useIsScreenReaderEnabled } from "../hooks";
|
|
33
|
+
import { IconButton } from "../IconButton";
|
|
34
|
+
import { tokens } from "../utils/design";
|
|
35
|
+
import { Heading } from "../Heading";
|
|
36
|
+
|
|
37
|
+
export const ContentOverlay = forwardRef(ContentOverlayPortal);
|
|
38
|
+
const ContentOverlayModal = forwardRef(ContentOverlayInternal);
|
|
39
|
+
|
|
40
|
+
// eslint-disable-next-line max-statements
|
|
41
|
+
function ContentOverlayInternal(
|
|
42
|
+
{
|
|
43
|
+
children,
|
|
44
|
+
title,
|
|
45
|
+
accessibilityLabel,
|
|
46
|
+
fullScreen = false,
|
|
47
|
+
showDismiss = false,
|
|
48
|
+
isDraggable = true,
|
|
49
|
+
adjustToContentHeight = false,
|
|
50
|
+
keyboardShouldPersistTaps = false,
|
|
51
|
+
keyboardAvoidingBehavior,
|
|
52
|
+
scrollEnabled = false,
|
|
53
|
+
modalBackgroundColor = "surface",
|
|
54
|
+
onClose,
|
|
55
|
+
onOpen,
|
|
56
|
+
onBeforeExit,
|
|
57
|
+
loading = false,
|
|
58
|
+
avoidKeyboardLikeIOS,
|
|
59
|
+
}: ContentOverlayProps,
|
|
60
|
+
ref: Ref<ContentOverlayRef>,
|
|
61
|
+
): JSX.Element {
|
|
62
|
+
isDraggable = onBeforeExit ? false : isDraggable;
|
|
63
|
+
const isCloseableOnOverlayTap = onBeforeExit ? false : true;
|
|
64
|
+
const { formatMessage } = useIntl();
|
|
65
|
+
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
66
|
+
const insets = useSafeAreaInsets();
|
|
67
|
+
const [position, setPosition] = useState<"top" | "initial">("initial");
|
|
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>();
|
|
75
|
+
|
|
76
|
+
const internalRef = useRef<Modalize>();
|
|
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
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const refMethods = useMemo(() => {
|
|
86
|
+
if (!modalizeMethods?.open || !modalizeMethods?.close) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
open: modalizeMethods?.open,
|
|
91
|
+
close: modalizeMethods?.close,
|
|
92
|
+
};
|
|
93
|
+
}, [modalizeMethods]);
|
|
94
|
+
|
|
95
|
+
const { keyboardHeight } = useKeyboardVisibility();
|
|
96
|
+
useImperativeHandle(ref, () => refMethods, [refMethods]);
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
handleLayout: handleChildrenLayout,
|
|
100
|
+
height: childrenHeight,
|
|
101
|
+
heightKnown: childrenHeightKnown,
|
|
102
|
+
} = useViewLayoutHeight();
|
|
103
|
+
const {
|
|
104
|
+
handleLayout: handleHeaderLayout,
|
|
105
|
+
height: headerHeight,
|
|
106
|
+
heightKnown: headerHeightKnown,
|
|
107
|
+
} = useViewLayoutHeight();
|
|
108
|
+
|
|
109
|
+
const snapPoint = useMemo(() => {
|
|
110
|
+
if (fullScreen || !isDraggable || adjustToContentHeight) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const overlayHeight = headerHeight + childrenHeight;
|
|
114
|
+
if (overlayHeight >= windowHeight) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return overlayHeight;
|
|
118
|
+
}, [
|
|
119
|
+
fullScreen,
|
|
120
|
+
isDraggable,
|
|
121
|
+
adjustToContentHeight,
|
|
122
|
+
headerHeight,
|
|
123
|
+
childrenHeight,
|
|
124
|
+
windowHeight,
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const modalStyle = [
|
|
128
|
+
styles.modal,
|
|
129
|
+
windowWidth > 640 ? styles.modalForLargeScreens : undefined,
|
|
130
|
+
{ backgroundColor: getModalBackgroundColor(modalBackgroundColor) },
|
|
131
|
+
keyboardHeight > 0 && { marginBottom: 0 },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const renderedChildren = renderChildren();
|
|
135
|
+
const renderedHeader = renderHeader();
|
|
136
|
+
|
|
137
|
+
const onCloseController = () => {
|
|
138
|
+
if (!onBeforeExit) {
|
|
139
|
+
internalRef.current?.close();
|
|
140
|
+
return true;
|
|
141
|
+
} else {
|
|
142
|
+
onBeforeExit();
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<>
|
|
149
|
+
{headerHeightKnown && childrenHeightKnown && (
|
|
150
|
+
<Modalize
|
|
151
|
+
ref={callbackInternalRef}
|
|
152
|
+
overlayStyle={styles.overlay}
|
|
153
|
+
handleStyle={styles.handle}
|
|
154
|
+
handlePosition="inside"
|
|
155
|
+
modalStyle={modalStyle}
|
|
156
|
+
modalTopOffset={tokens["space-larger"]}
|
|
157
|
+
snapPoint={snapPoint}
|
|
158
|
+
closeSnapPointStraightEnabled={false}
|
|
159
|
+
withHandle={isDraggable}
|
|
160
|
+
panGestureEnabled={isDraggable}
|
|
161
|
+
adjustToContentHeight={adjustToContentHeight}
|
|
162
|
+
disableScrollIfPossible={!adjustToContentHeight} // workaround for scroll not working on Android when content fills the screen with adjustToContentHeight
|
|
163
|
+
onClose={onClose}
|
|
164
|
+
onOpen={onOpen}
|
|
165
|
+
keyboardAvoidingBehavior={keyboardAvoidingBehavior}
|
|
166
|
+
avoidKeyboardLikeIOS={avoidKeyboardLikeIOS}
|
|
167
|
+
childrenStyle={styles.childrenStyle}
|
|
168
|
+
onBackButtonPress={onCloseController}
|
|
169
|
+
closeOnOverlayTap={isCloseableOnOverlayTap}
|
|
170
|
+
onOpened={() => {
|
|
171
|
+
if (overlayHeader.current) {
|
|
172
|
+
const reactTag = findNodeHandle(overlayHeader.current);
|
|
173
|
+
if (reactTag) {
|
|
174
|
+
AccessibilityInfo.setAccessibilityFocus(reactTag);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}}
|
|
178
|
+
scrollViewProps={{
|
|
179
|
+
scrollEnabled,
|
|
180
|
+
showsVerticalScrollIndicator: false,
|
|
181
|
+
stickyHeaderIndices: Platform.OS === "android" ? [0] : undefined,
|
|
182
|
+
onScroll: handleOnScroll,
|
|
183
|
+
keyboardShouldPersistTaps: keyboardShouldPersistTaps
|
|
184
|
+
? "handled"
|
|
185
|
+
: "never",
|
|
186
|
+
}}
|
|
187
|
+
HeaderComponent={Platform.OS === "ios" ? renderedHeader : undefined}
|
|
188
|
+
onPositionChange={setPosition}
|
|
189
|
+
>
|
|
190
|
+
{Platform.OS === "android" ? renderedHeader : undefined}
|
|
191
|
+
{renderedChildren}
|
|
192
|
+
</Modalize>
|
|
193
|
+
)}
|
|
194
|
+
{!childrenHeightKnown && (
|
|
195
|
+
<View style={[styles.hiddenContent, modalStyle]}>
|
|
196
|
+
{renderedChildren}
|
|
197
|
+
</View>
|
|
198
|
+
)}
|
|
199
|
+
{!headerHeightKnown && (
|
|
200
|
+
<View style={[styles.hiddenContent, modalStyle]}>{renderedHeader}</View>
|
|
201
|
+
)}
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
function renderHeader() {
|
|
206
|
+
const closeOverlayA11YLabel = formatMessage(
|
|
207
|
+
messages.closeOverlayA11YLabel,
|
|
208
|
+
{
|
|
209
|
+
title: title,
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const headerStyles = [
|
|
214
|
+
styles.header,
|
|
215
|
+
showHeaderShadow && styles.headerShadow,
|
|
216
|
+
{ backgroundColor: getModalBackgroundColor(modalBackgroundColor) },
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<View onLayout={handleHeaderLayout} testID="ATL-Overlay-Header">
|
|
221
|
+
<View style={headerStyles}>
|
|
222
|
+
<View
|
|
223
|
+
style={
|
|
224
|
+
showDismiss ? styles.titleWithDismiss : styles.titleWithoutDimiss
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
<Heading
|
|
228
|
+
level="subtitle"
|
|
229
|
+
variation={loading ? "subdued" : "heading"}
|
|
230
|
+
>
|
|
231
|
+
{title}
|
|
232
|
+
</Heading>
|
|
233
|
+
</View>
|
|
234
|
+
{shouldShowDismiss && (
|
|
235
|
+
<View
|
|
236
|
+
style={styles.dismissButton}
|
|
237
|
+
// @ts-expect-error tsc-ci
|
|
238
|
+
ref={overlayHeader}
|
|
239
|
+
accessibilityLabel={accessibilityLabel || closeOverlayA11YLabel}
|
|
240
|
+
accessible={true}
|
|
241
|
+
>
|
|
242
|
+
<IconButton
|
|
243
|
+
name="cross"
|
|
244
|
+
customColor={
|
|
245
|
+
loading ? tokens["color-disabled"] : tokens["color-heading"]
|
|
246
|
+
}
|
|
247
|
+
onPress={() => onCloseController()}
|
|
248
|
+
accessibilityLabel={closeOverlayA11YLabel}
|
|
249
|
+
testID="ATL-Overlay-CloseButton"
|
|
250
|
+
/>
|
|
251
|
+
</View>
|
|
252
|
+
)}
|
|
253
|
+
</View>
|
|
254
|
+
</View>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function renderChildren() {
|
|
259
|
+
return (
|
|
260
|
+
<View
|
|
261
|
+
style={{ paddingBottom: insets.bottom }}
|
|
262
|
+
onLayout={handleChildrenLayout}
|
|
263
|
+
testID="ATL-Overlay-Children"
|
|
264
|
+
>
|
|
265
|
+
{children}
|
|
266
|
+
</View>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function handleOnScroll({
|
|
271
|
+
nativeEvent,
|
|
272
|
+
}: NativeSyntheticEvent<NativeScrollEvent>) {
|
|
273
|
+
setShowHeaderShadow(nativeEvent.contentOffset.y > 0);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function getModalBackgroundColor(variation: ModalBackgroundColor) {
|
|
277
|
+
switch (variation) {
|
|
278
|
+
case "surface":
|
|
279
|
+
return tokens["color-surface"];
|
|
280
|
+
case "background":
|
|
281
|
+
return tokens["color-surface--background"];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function ContentOverlayPortal(
|
|
287
|
+
modalProps: ContentOverlayProps,
|
|
288
|
+
ref: Ref<ContentOverlayRef>,
|
|
289
|
+
) {
|
|
290
|
+
return (
|
|
291
|
+
<Portal>
|
|
292
|
+
<ContentOverlayModal ref={ref} {...modalProps} />
|
|
293
|
+
</Portal>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
import { DeviceEventEmitter, KeyboardEvent } from "react-native";
|
|
3
|
+
import { useKeyboardVisibility } from "./useKeyboardVisibility";
|
|
4
|
+
|
|
5
|
+
const keyboardEvent: Partial<KeyboardEvent> = {
|
|
6
|
+
endCoordinates: { height: 350, screenX: 120, screenY: 120, width: 200 },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe("when the user is typing", () => {
|
|
10
|
+
it("sets the isKeyboardVisible to true", () => {
|
|
11
|
+
const { result } = renderHook(() => useKeyboardVisibility());
|
|
12
|
+
|
|
13
|
+
act(() => {
|
|
14
|
+
DeviceEventEmitter.emit("keyboardDidShow", keyboardEvent);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(result.current.isKeyboardVisible).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
it("the keyboardDidShow event emits the keyboard height", () => {
|
|
20
|
+
const { result } = renderHook(() => useKeyboardVisibility());
|
|
21
|
+
|
|
22
|
+
act(() => {
|
|
23
|
+
DeviceEventEmitter.emit("keyboardDidShow", keyboardEvent);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(result.current.keyboardHeight).toBe(
|
|
27
|
+
keyboardEvent.endCoordinates?.height,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("when the user not typing", () => {
|
|
33
|
+
it("sets the isKeyboardVisible to false", () => {
|
|
34
|
+
const { result } = renderHook(() => useKeyboardVisibility());
|
|
35
|
+
|
|
36
|
+
act(() => {
|
|
37
|
+
DeviceEventEmitter.emit("keyboardDidHide");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(result.current.isKeyboardVisible).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Keyboard, KeyboardEvent } from "react-native";
|
|
3
|
+
|
|
4
|
+
interface KeyboardVisibility {
|
|
5
|
+
readonly isKeyboardVisible: boolean;
|
|
6
|
+
readonly keyboardHeight: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useKeyboardVisibility(): KeyboardVisibility {
|
|
10
|
+
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
|
|
11
|
+
const [keyboardHeight, setKeyboardHeight] = useState(0);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const keyboardDidShowListener = Keyboard.addListener(
|
|
15
|
+
"keyboardDidShow",
|
|
16
|
+
(event: KeyboardEvent) => {
|
|
17
|
+
setKeyboardVisible(true);
|
|
18
|
+
setKeyboardHeight(event.endCoordinates.height);
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
const keyboardDidHideListener = Keyboard.addListener(
|
|
22
|
+
"keyboardDidHide",
|
|
23
|
+
() => {
|
|
24
|
+
setKeyboardVisible(false);
|
|
25
|
+
setKeyboardHeight(0);
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
keyboardDidHideListener.remove();
|
|
31
|
+
keyboardDidShowListener.remove();
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return { isKeyboardVisible, keyboardHeight };
|
|
36
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
import { LayoutChangeEvent } from "react-native";
|
|
3
|
+
import { useViewLayoutHeight } from "./useViewLayoutHeight";
|
|
4
|
+
|
|
5
|
+
describe("useViewLayoutHeight", () => {
|
|
6
|
+
it("should return initial values", async () => {
|
|
7
|
+
const { result } = renderHook(() => useViewLayoutHeight());
|
|
8
|
+
expect(result.current.height).toBe(0);
|
|
9
|
+
expect(result.current.heightKnown).toBe(false);
|
|
10
|
+
expect(typeof result.current.handleLayout).toBe("function");
|
|
11
|
+
});
|
|
12
|
+
it("should handle layout change event", async () => {
|
|
13
|
+
const expectedHeight = 100;
|
|
14
|
+
const layoutChangeEvent: LayoutChangeEvent = {
|
|
15
|
+
nativeEvent: {
|
|
16
|
+
layout: {
|
|
17
|
+
height: expectedHeight,
|
|
18
|
+
width: 100,
|
|
19
|
+
x: 0,
|
|
20
|
+
y: 0,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
currentTarget: 0,
|
|
24
|
+
target: 0,
|
|
25
|
+
bubbles: false,
|
|
26
|
+
cancelable: false,
|
|
27
|
+
defaultPrevented: false,
|
|
28
|
+
eventPhase: 0,
|
|
29
|
+
isTrusted: false,
|
|
30
|
+
preventDefault: function (): void {
|
|
31
|
+
throw new Error("Function not implemented.");
|
|
32
|
+
},
|
|
33
|
+
isDefaultPrevented: function (): boolean {
|
|
34
|
+
throw new Error("Function not implemented.");
|
|
35
|
+
},
|
|
36
|
+
stopPropagation: function (): void {
|
|
37
|
+
throw new Error("Function not implemented.");
|
|
38
|
+
},
|
|
39
|
+
isPropagationStopped: function (): boolean {
|
|
40
|
+
throw new Error("Function not implemented.");
|
|
41
|
+
},
|
|
42
|
+
persist: function (): void {
|
|
43
|
+
throw new Error("Function not implemented.");
|
|
44
|
+
},
|
|
45
|
+
timeStamp: 0,
|
|
46
|
+
type: "",
|
|
47
|
+
};
|
|
48
|
+
const { result } = renderHook(() => useViewLayoutHeight());
|
|
49
|
+
await act(async () => {
|
|
50
|
+
result.current.handleLayout(layoutChangeEvent);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(result.current.height).toBe(expectedHeight);
|
|
54
|
+
expect(result.current.heightKnown).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { LayoutChangeEvent } from "react-native";
|
|
3
|
+
|
|
4
|
+
export function useViewLayoutHeight(): {
|
|
5
|
+
readonly handleLayout: ({ nativeEvent }: LayoutChangeEvent) => void;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
readonly heightKnown: boolean;
|
|
8
|
+
} {
|
|
9
|
+
const [heightKnown, setHeightKnown] = useState(false);
|
|
10
|
+
const [height, setHeight] = useState(0);
|
|
11
|
+
|
|
12
|
+
const handleLayout = ({ nativeEvent }: LayoutChangeEvent): void => {
|
|
13
|
+
setHeightKnown(true);
|
|
14
|
+
setHeight(nativeEvent.layout.height);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return { handleLayout, height, heightKnown } as const;
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineMessages } from "react-intl";
|
|
2
|
+
|
|
3
|
+
export const messages = defineMessages({
|
|
4
|
+
closeOverlayA11YLabel: {
|
|
5
|
+
id: "closeOverlayA11yLabel",
|
|
6
|
+
defaultMessage: "Close {title} modal",
|
|
7
|
+
description: "Accessibility label for button to close the overlay modal",
|
|
8
|
+
},
|
|
9
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { Modalize } from "react-native-modalize";
|
|
3
|
+
|
|
4
|
+
export interface ContentOverlayProps {
|
|
5
|
+
/**
|
|
6
|
+
* Content to be passed into the overlay
|
|
7
|
+
*/
|
|
8
|
+
readonly children: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Title of overlay, appears in the header next to the close button.
|
|
11
|
+
*/
|
|
12
|
+
readonly title: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional accessibilityLabel describing the overlay.
|
|
15
|
+
* This will read out when the overlay is opened.
|
|
16
|
+
* @default "Close {title} modal"
|
|
17
|
+
*/
|
|
18
|
+
readonly accessibilityLabel?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Force overlay height to fill the screen.
|
|
21
|
+
* Width not impacted.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
readonly fullScreen?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Display the dismiss button in the header of the overlay.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
readonly showDismiss?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* If false, hides the handle and turns off dragging.
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
readonly isDraggable?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* If true, automatically adjusts the overlay height to the content height.
|
|
37
|
+
* This will disable the ability to drag the overlay to fullscreen when
|
|
38
|
+
* `isDraggable` is true.
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
readonly adjustToContentHeight?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Allows taps to be registered behind keyboard if enabled
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
readonly keyboardShouldPersistTaps?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Enables scrolling in the content body of overlay
|
|
49
|
+
*/
|
|
50
|
+
readonly scrollEnabled?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Set the background color of the modal window
|
|
53
|
+
* @default "surface"
|
|
54
|
+
*/
|
|
55
|
+
readonly modalBackgroundColor?: ModalBackgroundColor;
|
|
56
|
+
/**
|
|
57
|
+
* Callback that is called when the overlay is closed.
|
|
58
|
+
*/
|
|
59
|
+
readonly onClose?: () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Callback that is called when the overlay is opened.
|
|
62
|
+
*/
|
|
63
|
+
readonly onOpen?: () => void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Callback that is called between overlay is closed and when the "x" button is pressed
|
|
67
|
+
*/
|
|
68
|
+
readonly onBeforeExit?: () => void;
|
|
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
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Boolean to show a disabled state
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
readonly loading?: boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Define keyboard's Android behavior like iOS's one.
|
|
84
|
+
* @default Platform.select({ ios: true, android: false })
|
|
85
|
+
*/
|
|
86
|
+
readonly avoidKeyboardLikeIOS?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type ModalBackgroundColor = "surface" | "background";
|
|
90
|
+
|
|
91
|
+
export type ContentOverlayRef =
|
|
92
|
+
| {
|
|
93
|
+
open?: Modalize["open"];
|
|
94
|
+
close?: Modalize["close"];
|
|
95
|
+
}
|
|
96
|
+
| undefined;
|
package/src/index.ts
CHANGED