@jobber/components-native 0.98.5 → 0.100.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/package.json +3 -6
- package/dist/src/AtlantisOverlayProvider/AtlantisOverlayProvider.js +5 -0
- package/dist/src/AtlantisOverlayProvider/index.js +1 -0
- package/dist/src/BottomSheet/BottomSheet.js +9 -11
- package/dist/src/BottomSheet/hooks/useBottomSheetBackHandler.js +2 -2
- package/dist/src/ButtonGroup/components/SecondaryActionSheet/SecondaryActionSheet.js +9 -11
- package/dist/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.js +19 -0
- package/dist/src/ContentOverlay/ContentOverlay.js +143 -107
- package/dist/src/ContentOverlay/ContentOverlay.style.js +8 -12
- package/dist/src/ContentOverlay/computeContentOverlayBehavior.js +76 -0
- package/dist/src/ContentOverlay/constants.js +1 -0
- package/dist/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.js +25 -0
- package/dist/src/ContentOverlay/index.js +1 -1
- package/dist/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.js +7 -9
- package/dist/src/InputText/InputText.js +44 -1
- package/dist/src/index.js +1 -0
- package/dist/src/utils/meta/meta.json +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/AtlantisOverlayProvider/AtlantisOverlayProvider.d.ts +6 -0
- package/dist/types/src/AtlantisOverlayProvider/index.d.ts +1 -0
- package/dist/types/src/BottomSheet/hooks/useBottomSheetBackHandler.d.ts +3 -3
- package/dist/types/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.d.ts +11 -0
- package/dist/types/src/ContentOverlay/ContentOverlay.d.ts +2 -5
- package/dist/types/src/ContentOverlay/ContentOverlay.style.d.ts +11 -10
- package/dist/types/src/ContentOverlay/computeContentOverlayBehavior.d.ts +32 -0
- package/dist/types/src/ContentOverlay/constants.d.ts +1 -0
- package/dist/types/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.d.ts +7 -0
- package/dist/types/src/ContentOverlay/index.d.ts +1 -1
- package/dist/types/src/ContentOverlay/types.d.ts +5 -12
- package/dist/types/src/index.d.ts +1 -0
- package/jestSetup.js +2 -0
- package/package.json +3 -6
- package/src/AtlantisOverlayProvider/AtlantisOverlayProvider.tsx +12 -0
- package/src/AtlantisOverlayProvider/index.ts +1 -0
- package/src/BottomSheet/BottomSheet.tsx +13 -13
- package/src/BottomSheet/hooks/useBottomSheetBackHandler.test.ts +10 -10
- package/src/BottomSheet/hooks/useBottomSheetBackHandler.ts +4 -4
- package/src/ButtonGroup/ButtonGroup.stories.tsx +10 -8
- package/src/ButtonGroup/ButtonGroup.test.tsx +7 -10
- package/src/ButtonGroup/components/SecondaryActionSheet/SecondaryActionSheet.tsx +26 -29
- package/src/ContentOverlay/BottomSheetKeyboardAwareScrollView.tsx +36 -0
- package/src/ContentOverlay/ContentOverlay.stories.tsx +32 -36
- package/src/ContentOverlay/ContentOverlay.style.ts +12 -12
- package/src/ContentOverlay/ContentOverlay.test.tsx +157 -79
- package/src/ContentOverlay/ContentOverlay.tsx +247 -205
- package/src/ContentOverlay/computeContentOverlayBehavior.test.ts +276 -0
- package/src/ContentOverlay/computeContentOverlayBehavior.ts +119 -0
- package/src/ContentOverlay/constants.ts +1 -0
- package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.test.ts +81 -0
- package/src/ContentOverlay/hooks/useBottomSheetModalBackHandler.ts +36 -0
- package/src/ContentOverlay/index.ts +4 -1
- package/src/ContentOverlay/types.ts +5 -13
- package/src/Form/Form.stories.tsx +8 -4
- package/src/Form/Form.test.tsx +51 -54
- package/src/Form/components/FormSaveButton/FormSaveButton.test.tsx +7 -10
- package/src/FormatFile/FormatFile.stories.tsx +3 -4
- package/src/FormatFile/FormatFile.test.tsx +11 -14
- package/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.test.tsx +6 -9
- package/src/FormatFile/components/FormatFileBottomSheet/FormatFileBottomSheet.tsx +21 -24
- package/src/InputDate/InputDate.test.tsx +5 -8
- package/src/InputText/InputText.test.tsx +122 -0
- package/src/InputText/InputText.tsx +62 -2
- package/src/InputTime/InputTime.stories.tsx +8 -4
- package/src/InputTime/InputTime.test.tsx +5 -8
- package/src/ThumbnailList/ThumbnailList.stories.tsx +6 -4
- package/src/ThumbnailList/ThumbnailList.test.tsx +5 -8
- package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +101 -150
- package/src/index.ts +1 -0
- package/src/utils/meta/meta.json +2 -1
- package/dist/src/ContentOverlay/UNSAFE_WrappedModalize.js +0 -23
- package/dist/types/src/ContentOverlay/UNSAFE_WrappedModalize.d.ts +0 -3
- package/src/ContentOverlay/UNSAFE_WrappedModalize.tsx +0 -41
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AtlantisOverlayProvider } from "./AtlantisOverlayProvider";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
|
|
2
2
|
/**
|
|
3
|
-
* Hook that
|
|
3
|
+
* Hook that dismisses the bottom sheet modal on the hardware back button press if it is visible
|
|
4
4
|
* @param bottomSheetRef ref to the bottom sheet component
|
|
5
5
|
*/
|
|
6
|
-
export declare function useBottomSheetBackHandler(bottomSheetRef: React.RefObject<
|
|
6
|
+
export declare function useBottomSheetBackHandler(bottomSheetRef: React.RefObject<BottomSheetModal | null>): {
|
|
7
7
|
handleSheetPositionChange: (index: number) => void;
|
|
8
8
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KeyboardAwareScrollViewProps } from "react-native-keyboard-controller";
|
|
2
|
+
import { type BottomSheetScrollViewMethods } from "@gorhom/bottom-sheet";
|
|
3
|
+
declare const BottomSheetKeyboardAwareScrollView: import("react").NamedExoticComponent<{
|
|
4
|
+
bottomOffset?: number;
|
|
5
|
+
disableScrollOnKeyboardHide?: boolean;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
extraKeyboardSpace?: number;
|
|
8
|
+
ScrollViewComponent?: React.ComponentType<import("react-native").ScrollViewProps>;
|
|
9
|
+
} & import("react-native").ScrollViewProps & import("react").RefAttributes<BottomSheetScrollViewMethods>>;
|
|
10
|
+
export { BottomSheetKeyboardAwareScrollView };
|
|
11
|
+
export type { KeyboardAwareScrollViewProps };
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type { Modalize } from "react-native-modalize";
|
|
3
2
|
import type { ContentOverlayProps } from "./types";
|
|
4
|
-
export declare
|
|
5
|
-
|
|
6
|
-
close?: Modalize["close"];
|
|
7
|
-
} | undefined>>;
|
|
3
|
+
export declare function useIsKeyboardHandledByScrollView(): boolean;
|
|
4
|
+
export declare function ContentOverlay({ children, title, accessibilityLabel, fullScreen, showDismiss, isDraggable, adjustToContentHeight, keyboardShouldPersistTaps, scrollEnabled, modalBackgroundColor, onClose, onOpen, onBeforeExit, loading, ref, }: ContentOverlayProps): React.JSX.Element;
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
export declare const useStyles: () => {
|
|
2
|
+
handleWrapper: {
|
|
3
|
+
paddingBottom: number;
|
|
4
|
+
paddingTop: number;
|
|
5
|
+
};
|
|
2
6
|
handle: {
|
|
3
7
|
width: number;
|
|
4
8
|
height: number;
|
|
5
9
|
backgroundColor: string;
|
|
6
|
-
top: number;
|
|
7
10
|
borderRadius: number;
|
|
8
11
|
};
|
|
9
|
-
|
|
12
|
+
backdrop: {
|
|
10
13
|
backgroundColor: string;
|
|
11
14
|
};
|
|
12
|
-
|
|
15
|
+
background: {
|
|
13
16
|
borderTopLeftRadius: number;
|
|
14
17
|
borderTopRightRadius: number;
|
|
15
18
|
};
|
|
16
|
-
modalForLargeScreens: {
|
|
17
|
-
width: number;
|
|
18
|
-
alignSelf: "center";
|
|
19
|
-
};
|
|
20
19
|
header: {
|
|
21
20
|
flexDirection: "row";
|
|
22
|
-
backgroundColor: string;
|
|
23
|
-
paddingTop: number;
|
|
24
21
|
zIndex: number;
|
|
22
|
+
minHeight: number;
|
|
25
23
|
borderTopLeftRadius: number;
|
|
26
24
|
borderTopRightRadius: number;
|
|
27
|
-
minHeight: number;
|
|
28
25
|
};
|
|
29
26
|
headerShadow: {
|
|
27
|
+
position: "absolute";
|
|
28
|
+
top: number;
|
|
29
|
+
height: number;
|
|
30
|
+
width: "100%";
|
|
30
31
|
shadowColor: string;
|
|
31
32
|
shadowOffset: {
|
|
32
33
|
width: number;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ContentOverlayConfig {
|
|
2
|
+
fullScreen: boolean;
|
|
3
|
+
adjustToContentHeight: boolean;
|
|
4
|
+
isDraggable: boolean;
|
|
5
|
+
hasOnBeforeExit: boolean;
|
|
6
|
+
showDismiss: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ContentOverlayState {
|
|
9
|
+
isScreenReaderEnabled: boolean;
|
|
10
|
+
position: number;
|
|
11
|
+
}
|
|
12
|
+
export type InitialHeight = "fullScreen" | "contentHeight";
|
|
13
|
+
export interface ContentOverlayBehavior {
|
|
14
|
+
initialHeight: InitialHeight;
|
|
15
|
+
isDraggable: boolean;
|
|
16
|
+
showDismiss: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Computes the abstract behavior of ContentOverlay from its props and state.
|
|
20
|
+
*
|
|
21
|
+
* This pure function documents and centralizes the complex logic that determines:
|
|
22
|
+
* - Initial height mode (fullScreen vs contentHeight)
|
|
23
|
+
* - Whether the overlay is draggable
|
|
24
|
+
* - Whether the dismiss button should be shown
|
|
25
|
+
*
|
|
26
|
+
* The logic accounts for legacy behavior where:
|
|
27
|
+
* - `onBeforeExit` silently overrides `isDraggable` to false
|
|
28
|
+
* - Default props (neither fullScreen nor adjustToContentHeight) are treated
|
|
29
|
+
* as contentHeight for the new implementation
|
|
30
|
+
* - Dismiss button visibility depends on multiple factors including position state
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeContentOverlayBehavior(config: ContentOverlayConfig, state: ContentOverlayState): ContentOverlayBehavior;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const KEYBOARD_TOP_PADDING_AUTO_SCROLL = 20;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that dismisses the bottom sheet on the hardware back button press if it is visible
|
|
3
|
+
* @param bottomSheetModalRef ref to the bottom sheet modal component
|
|
4
|
+
*/
|
|
5
|
+
export declare function useBottomSheetModalBackHandler(onCloseController: () => void): {
|
|
6
|
+
handleSheetPositionChange: (index: number) => void;
|
|
7
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ContentOverlay } from "./ContentOverlay";
|
|
1
|
+
export { ContentOverlay, useIsKeyboardHandledByScrollView, } from "./ContentOverlay";
|
|
2
2
|
export type { ContentOverlayRef, ModalBackgroundColor } from "./types";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import type { Modalize } from "react-native-modalize";
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
3
2
|
export interface ContentOverlayProps {
|
|
4
3
|
/**
|
|
5
4
|
* Content to be passed into the overlay
|
|
@@ -64,24 +63,18 @@ export interface ContentOverlayProps {
|
|
|
64
63
|
* Callback that is called between overlay is closed and when the "x" button is pressed
|
|
65
64
|
*/
|
|
66
65
|
readonly onBeforeExit?: () => void;
|
|
67
|
-
/**
|
|
68
|
-
* Define the behavior of the keyboard when having inputs inside the modal.
|
|
69
|
-
* @default padding
|
|
70
|
-
*/
|
|
71
|
-
readonly keyboardAvoidingBehavior?: "height" | "padding" | "position";
|
|
72
66
|
/**
|
|
73
67
|
* Boolean to show a disabled state
|
|
74
68
|
* @default false
|
|
75
69
|
*/
|
|
76
70
|
readonly loading?: boolean;
|
|
77
71
|
/**
|
|
78
|
-
*
|
|
79
|
-
* @default Platform.select({ ios: true, android: false })
|
|
72
|
+
* Ref to the content overlay component.
|
|
80
73
|
*/
|
|
81
|
-
readonly
|
|
74
|
+
readonly ref?: Ref<ContentOverlayRef>;
|
|
82
75
|
}
|
|
83
76
|
export type ModalBackgroundColor = "surface" | "background";
|
|
84
77
|
export type ContentOverlayRef = {
|
|
85
|
-
open?:
|
|
86
|
-
close?:
|
|
78
|
+
open?: () => void;
|
|
79
|
+
close?: () => void;
|
|
87
80
|
} | undefined;
|
|
@@ -2,6 +2,7 @@ export * from "./ActionItem";
|
|
|
2
2
|
export * from "./ActionLabel";
|
|
3
3
|
export * from "./ActivityIndicator";
|
|
4
4
|
export * from "./AtlantisContext";
|
|
5
|
+
export * from "./AtlantisOverlayProvider";
|
|
5
6
|
export * from "./AtlantisThemeContext";
|
|
6
7
|
export * from "./AutoLink";
|
|
7
8
|
export * from "./Banner";
|
package/jestSetup.js
CHANGED
|
@@ -16,6 +16,8 @@ jest.mock("./dist/src/Button/components/InternalButtonLoading", () => {
|
|
|
16
16
|
};
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
// NOTE: this is the old way we used to mock reanimated. We actually do not need to mock it anymore.
|
|
20
|
+
// To ensure correct test behaviour, please add `jest.unmock("react-native-reanimated")` to your test suite.
|
|
19
21
|
jest.mock("react-native-reanimated", () => {
|
|
20
22
|
const reanimated = require("react-native-reanimated/mock");
|
|
21
23
|
const timing = () => ({ start: () => undefined });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.100.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
"react-hook-form": "^7.52.0",
|
|
47
47
|
"react-intl": "^7.1.11",
|
|
48
48
|
"react-native-keyboard-controller": "^1.20.7",
|
|
49
|
-
"react-native-modalize": "^2.0.13",
|
|
50
|
-
"react-native-portalize": "^1.0.7",
|
|
51
49
|
"react-native-toast-message": "^2.1.6",
|
|
52
50
|
"react-native-uuid": "^1.4.9",
|
|
53
51
|
"ts-xor": "^1.1.0"
|
|
@@ -69,6 +67,7 @@
|
|
|
69
67
|
"date-fns-tz": "^2.0.0",
|
|
70
68
|
"react-native": "^0.82.1",
|
|
71
69
|
"react-native-gesture-handler": "^2.29.1",
|
|
70
|
+
"react-native-keyboard-controller": "^1.12.0",
|
|
72
71
|
"react-native-modal-datetime-picker": "^18.0.0",
|
|
73
72
|
"react-native-reanimated": "^3.7.1",
|
|
74
73
|
"react-native-safe-area-context": "^5.4.0",
|
|
@@ -90,11 +89,9 @@
|
|
|
90
89
|
"react-native-gesture-handler": ">=2.22.0",
|
|
91
90
|
"react-native-keyboard-controller": "^1.20.7",
|
|
92
91
|
"react-native-modal-datetime-picker": " >=13.0.0",
|
|
93
|
-
"react-native-modalize": "^2.0.13",
|
|
94
|
-
"react-native-portalize": "^1.0.7",
|
|
95
92
|
"react-native-reanimated": "^3.0.0",
|
|
96
93
|
"react-native-safe-area-context": "^5.4.0",
|
|
97
94
|
"react-native-svg": ">=12.0.0"
|
|
98
95
|
},
|
|
99
|
-
"gitHead": "
|
|
96
|
+
"gitHead": "2ef76c22620e1627eca8cf576b35e78228262574"
|
|
100
97
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
3
|
+
|
|
4
|
+
interface AtlantisOverlayProviderProps {
|
|
5
|
+
readonly children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function AtlantisOverlayProvider({
|
|
9
|
+
children,
|
|
10
|
+
}: AtlantisOverlayProviderProps) {
|
|
11
|
+
return <BottomSheetModalProvider>{children}</BottomSheetModalProvider>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AtlantisOverlayProvider } from "./AtlantisOverlayProvider";
|
|
@@ -2,8 +2,9 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
|
3
3
|
import { Keyboard, View } from "react-native";
|
|
4
4
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
6
|
BottomSheetBackdrop,
|
|
7
|
+
BottomSheetModal,
|
|
7
8
|
BottomSheetView,
|
|
8
9
|
} from "@gorhom/bottom-sheet";
|
|
9
10
|
import type { BottomSheetBackdropProps } from "@gorhom/bottom-sheet";
|
|
@@ -67,13 +68,14 @@ export function BottomSheet({
|
|
|
67
68
|
const { t } = useAtlantisI18n();
|
|
68
69
|
const insets = useSafeAreaInsets();
|
|
69
70
|
const previousIndexRef = useRef(-1);
|
|
70
|
-
const bottomSheetRef = useRef<
|
|
71
|
+
const bottomSheetRef = useRef<BottomSheetModal>(null);
|
|
71
72
|
const { handleSheetPositionChange } =
|
|
72
73
|
useBottomSheetBackHandler(bottomSheetRef);
|
|
73
74
|
|
|
74
75
|
useImperativeHandle(ref, () => ({
|
|
75
76
|
open: () => {
|
|
76
|
-
|
|
77
|
+
dismissKeyboard();
|
|
78
|
+
bottomSheetRef.current?.present();
|
|
77
79
|
},
|
|
78
80
|
close: () => {
|
|
79
81
|
close();
|
|
@@ -81,7 +83,7 @@ export function BottomSheet({
|
|
|
81
83
|
}));
|
|
82
84
|
|
|
83
85
|
const close = useCallback(() => {
|
|
84
|
-
bottomSheetRef.current?.
|
|
86
|
+
bottomSheetRef.current?.dismiss();
|
|
85
87
|
}, []);
|
|
86
88
|
|
|
87
89
|
const handleChange = (index: number) => {
|
|
@@ -91,26 +93,24 @@ export function BottomSheet({
|
|
|
91
93
|
const previousIndex = previousIndexRef.current;
|
|
92
94
|
|
|
93
95
|
if (previousIndex === -1 && index >= 0) {
|
|
94
|
-
// Transitioned from closed to open
|
|
95
|
-
dismissKeyboard();
|
|
96
96
|
onOpen?.();
|
|
97
|
-
} else if (previousIndex >= 0 && index === -1) {
|
|
98
|
-
// Transitioned from open to closed
|
|
99
|
-
dismissKeyboard();
|
|
100
|
-
onClose?.();
|
|
101
97
|
}
|
|
102
98
|
|
|
103
99
|
previousIndexRef.current = index;
|
|
104
100
|
};
|
|
105
101
|
|
|
106
102
|
return (
|
|
107
|
-
<
|
|
103
|
+
<BottomSheetModal
|
|
108
104
|
ref={bottomSheetRef}
|
|
109
|
-
index={-1}
|
|
110
105
|
backdropComponent={Backdrop}
|
|
111
106
|
backgroundStyle={styles.background}
|
|
112
107
|
enablePanDownToClose={true}
|
|
113
108
|
onChange={handleChange}
|
|
109
|
+
onDismiss={() => {
|
|
110
|
+
previousIndexRef.current = -1;
|
|
111
|
+
dismissKeyboard();
|
|
112
|
+
onClose?.();
|
|
113
|
+
}}
|
|
114
114
|
keyboardBlurBehavior="restore"
|
|
115
115
|
handleStyle={styles.handle}
|
|
116
116
|
>
|
|
@@ -126,7 +126,7 @@ export function BottomSheet({
|
|
|
126
126
|
<Footer styles={styles} close={close} cancelLabel={t("cancel")} />
|
|
127
127
|
)}
|
|
128
128
|
</BottomSheetView>
|
|
129
|
-
</
|
|
129
|
+
</BottomSheetModal>
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -2,7 +2,7 @@ import { createRef } from "react";
|
|
|
2
2
|
import type { RefObject } from "react";
|
|
3
3
|
import { act, renderHook } from "@testing-library/react-native";
|
|
4
4
|
import { BackHandler } from "react-native";
|
|
5
|
-
import type
|
|
5
|
+
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
|
|
6
6
|
import { useBottomSheetBackHandler } from "./useBottomSheetBackHandler";
|
|
7
7
|
|
|
8
8
|
describe("useBottomSheetBackHandler", () => {
|
|
@@ -20,7 +20,7 @@ describe("useBottomSheetBackHandler", () => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it("should register BackHandler listener when sheet becomes visible", async () => {
|
|
23
|
-
const bottomSheetRef = createRef<
|
|
23
|
+
const bottomSheetRef = createRef<BottomSheetModal | null>();
|
|
24
24
|
const { result } = renderHook(() =>
|
|
25
25
|
useBottomSheetBackHandler(bottomSheetRef),
|
|
26
26
|
);
|
|
@@ -35,13 +35,13 @@ describe("useBottomSheetBackHandler", () => {
|
|
|
35
35
|
);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
it("should call
|
|
39
|
-
const
|
|
38
|
+
it("should call dismiss() when back button is pressed", async () => {
|
|
39
|
+
const mockDismiss = jest.fn();
|
|
40
40
|
const bottomSheetRef = {
|
|
41
41
|
current: {
|
|
42
|
-
|
|
43
|
-
} as unknown as
|
|
44
|
-
} as RefObject<
|
|
42
|
+
dismiss: mockDismiss,
|
|
43
|
+
} as unknown as BottomSheetModal,
|
|
44
|
+
} as RefObject<BottomSheetModal | null>;
|
|
45
45
|
|
|
46
46
|
const { result } = renderHook(() =>
|
|
47
47
|
useBottomSheetBackHandler(bottomSheetRef),
|
|
@@ -54,12 +54,12 @@ describe("useBottomSheetBackHandler", () => {
|
|
|
54
54
|
const registeredCallback = mockAddEventListener.mock.calls[0][1];
|
|
55
55
|
const returnValue = registeredCallback();
|
|
56
56
|
|
|
57
|
-
expect(
|
|
57
|
+
expect(mockDismiss).toHaveBeenCalled();
|
|
58
58
|
expect(returnValue).toBe(true);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it("should remove listener when sheet is dismissed", async () => {
|
|
62
|
-
const bottomSheetRef = createRef<
|
|
62
|
+
const bottomSheetRef = createRef<BottomSheetModal | null>();
|
|
63
63
|
const { result } = renderHook(() =>
|
|
64
64
|
useBottomSheetBackHandler(bottomSheetRef),
|
|
65
65
|
);
|
|
@@ -76,7 +76,7 @@ describe("useBottomSheetBackHandler", () => {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it("should not register listener when index is negative", async () => {
|
|
79
|
-
const bottomSheetRef = createRef<
|
|
79
|
+
const bottomSheetRef = createRef<BottomSheetModal | null>();
|
|
80
80
|
const { result } = renderHook(() =>
|
|
81
81
|
useBottomSheetBackHandler(bottomSheetRef),
|
|
82
82
|
);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useCallback, useRef } from "react";
|
|
2
2
|
import { BackHandler, type NativeEventSubscription } from "react-native";
|
|
3
|
-
import type
|
|
3
|
+
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Hook that
|
|
6
|
+
* Hook that dismisses the bottom sheet modal on the hardware back button press if it is visible
|
|
7
7
|
* @param bottomSheetRef ref to the bottom sheet component
|
|
8
8
|
*/
|
|
9
9
|
export function useBottomSheetBackHandler(
|
|
10
|
-
bottomSheetRef: React.RefObject<
|
|
10
|
+
bottomSheetRef: React.RefObject<BottomSheetModal | null>,
|
|
11
11
|
): {
|
|
12
12
|
handleSheetPositionChange: (index: number) => void;
|
|
13
13
|
} {
|
|
@@ -24,7 +24,7 @@ export function useBottomSheetBackHandler(
|
|
|
24
24
|
backHandlerSubscriptionRef.current = BackHandler.addEventListener(
|
|
25
25
|
"hardwareBackPress",
|
|
26
26
|
() => {
|
|
27
|
-
bottomSheetRef.current?.
|
|
27
|
+
bottomSheetRef.current?.dismiss();
|
|
28
28
|
|
|
29
29
|
return true;
|
|
30
30
|
},
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
3
|
-
import { Host } from "react-native-portalize";
|
|
4
3
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AtlantisOverlayProvider,
|
|
6
|
+
ButtonGroup,
|
|
7
|
+
} from "@jobber/components-native";
|
|
6
8
|
|
|
7
9
|
const meta = {
|
|
8
10
|
title: "Components/Actions/ButtonGroup",
|
|
@@ -31,7 +33,7 @@ type SecondaryActionStory = StoryObj<
|
|
|
31
33
|
export const Basic: ButtonGroupStory = {
|
|
32
34
|
render: args => (
|
|
33
35
|
<SafeAreaProvider>
|
|
34
|
-
<
|
|
36
|
+
<AtlantisOverlayProvider>
|
|
35
37
|
<ButtonGroup {...args}>
|
|
36
38
|
<ButtonGroup.PrimaryAction
|
|
37
39
|
label={"Create"}
|
|
@@ -49,7 +51,7 @@ export const Basic: ButtonGroupStory = {
|
|
|
49
51
|
onPress={() => console.log("delete")}
|
|
50
52
|
/>
|
|
51
53
|
</ButtonGroup>
|
|
52
|
-
</
|
|
54
|
+
</AtlantisOverlayProvider>
|
|
53
55
|
</SafeAreaProvider>
|
|
54
56
|
),
|
|
55
57
|
args: {},
|
|
@@ -58,7 +60,7 @@ export const Basic: ButtonGroupStory = {
|
|
|
58
60
|
export const Primary: PrimaryActionStory = {
|
|
59
61
|
render: args => (
|
|
60
62
|
<SafeAreaProvider>
|
|
61
|
-
<
|
|
63
|
+
<AtlantisOverlayProvider>
|
|
62
64
|
<ButtonGroup>
|
|
63
65
|
<ButtonGroup.PrimaryAction {...args} />
|
|
64
66
|
<ButtonGroup.SecondaryAction
|
|
@@ -72,7 +74,7 @@ export const Primary: PrimaryActionStory = {
|
|
|
72
74
|
onPress={() => console.log("delete")}
|
|
73
75
|
/>
|
|
74
76
|
</ButtonGroup>
|
|
75
|
-
</
|
|
77
|
+
</AtlantisOverlayProvider>
|
|
76
78
|
</SafeAreaProvider>
|
|
77
79
|
),
|
|
78
80
|
args: {
|
|
@@ -85,7 +87,7 @@ export const Primary: PrimaryActionStory = {
|
|
|
85
87
|
export const Secondary: SecondaryActionStory = {
|
|
86
88
|
render: args => (
|
|
87
89
|
<SafeAreaProvider>
|
|
88
|
-
<
|
|
90
|
+
<AtlantisOverlayProvider>
|
|
89
91
|
<ButtonGroup
|
|
90
92
|
bottomSheetHeading="What would you like to do"
|
|
91
93
|
showCancelInBottomSheet={true}
|
|
@@ -97,7 +99,7 @@ export const Secondary: SecondaryActionStory = {
|
|
|
97
99
|
/>
|
|
98
100
|
<ButtonGroup.SecondaryAction {...args} />
|
|
99
101
|
</ButtonGroup>
|
|
100
|
-
</
|
|
102
|
+
</AtlantisOverlayProvider>
|
|
101
103
|
</SafeAreaProvider>
|
|
102
104
|
),
|
|
103
105
|
args: {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
|
3
|
-
import { Host } from "react-native-portalize";
|
|
4
3
|
import { Alert } from "react-native";
|
|
5
4
|
import type { ButtonGroupProps } from "./ButtonGroup";
|
|
6
5
|
import { ButtonGroup } from "./ButtonGroup";
|
|
@@ -12,15 +11,13 @@ const mockOnOpen = jest.fn();
|
|
|
12
11
|
|
|
13
12
|
function ButtonGroupForTest(props: ButtonGroupProps) {
|
|
14
13
|
return (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</ButtonGroup>
|
|
23
|
-
</Host>
|
|
14
|
+
<ButtonGroup
|
|
15
|
+
bottomSheetHeading={props.bottomSheetHeading}
|
|
16
|
+
showCancelInBottomSheet={props.showCancelInBottomSheet}
|
|
17
|
+
onOpenBottomSheet={mockOnOpen}
|
|
18
|
+
>
|
|
19
|
+
{props.children}
|
|
20
|
+
</ButtonGroup>
|
|
24
21
|
);
|
|
25
22
|
}
|
|
26
23
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { RefObject } from "react";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { View } from "react-native";
|
|
4
|
-
import { Portal } from "react-native-portalize";
|
|
5
4
|
import type { ButtonGroupSecondaryActionProps } from "../../types";
|
|
6
5
|
import { BottomSheetOption } from "../../../BottomSheet/components/BottomSheetOption";
|
|
7
6
|
import type { BottomSheetRef } from "../../../BottomSheet/BottomSheet";
|
|
@@ -25,34 +24,32 @@ export function SecondaryActionSheet({
|
|
|
25
24
|
onCloseBottomSheet,
|
|
26
25
|
}: SecondaryActionSheetProps) {
|
|
27
26
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
>
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
const { label, onPress, icon, iconColor, destructive } = action;
|
|
27
|
+
<BottomSheet
|
|
28
|
+
heading={heading}
|
|
29
|
+
showCancel={showCancel}
|
|
30
|
+
ref={secondaryActionsRef}
|
|
31
|
+
onOpen={onOpenBottomSheet}
|
|
32
|
+
onClose={onCloseBottomSheet}
|
|
33
|
+
>
|
|
34
|
+
<View>
|
|
35
|
+
{actions.map((action, index) => {
|
|
36
|
+
const { label, onPress, icon, iconColor, destructive } = action;
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
</Portal>
|
|
38
|
+
return (
|
|
39
|
+
<BottomSheetOption
|
|
40
|
+
destructive={destructive}
|
|
41
|
+
key={index}
|
|
42
|
+
text={label}
|
|
43
|
+
onPress={() => {
|
|
44
|
+
secondaryActionsRef?.current?.close();
|
|
45
|
+
onPress();
|
|
46
|
+
}}
|
|
47
|
+
icon={icon}
|
|
48
|
+
iconColor={iconColor}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
})}
|
|
52
|
+
</View>
|
|
53
|
+
</BottomSheet>
|
|
57
54
|
);
|
|
58
55
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { memo } from "react";
|
|
2
|
+
import type { KeyboardAwareScrollViewProps } from "react-native-keyboard-controller";
|
|
3
|
+
import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
|
|
4
|
+
import {
|
|
5
|
+
type BottomSheetScrollViewMethods,
|
|
6
|
+
SCROLLABLE_TYPE,
|
|
7
|
+
createBottomSheetScrollableComponent,
|
|
8
|
+
} from "@gorhom/bottom-sheet";
|
|
9
|
+
import Reanimated from "react-native-reanimated";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A keyboard-aware scroll view component that integrates with @gorhom/bottom-sheet.
|
|
13
|
+
*
|
|
14
|
+
* This component wraps `KeyboardAwareScrollView` from `react-native-keyboard-controller`
|
|
15
|
+
* with the bottom sheet HOCs to ensure proper keyboard handling on Android when using
|
|
16
|
+
* TextInputs inside a BottomSheet.
|
|
17
|
+
*
|
|
18
|
+
* @see https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-aware-scroll-view#gorhombottom-sheet
|
|
19
|
+
*/
|
|
20
|
+
const AnimatedScrollView =
|
|
21
|
+
Reanimated.createAnimatedComponent<KeyboardAwareScrollViewProps>(
|
|
22
|
+
KeyboardAwareScrollView,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const BottomSheetScrollViewComponent = createBottomSheetScrollableComponent<
|
|
26
|
+
BottomSheetScrollViewMethods,
|
|
27
|
+
KeyboardAwareScrollViewProps
|
|
28
|
+
>(SCROLLABLE_TYPE.SCROLLVIEW, AnimatedScrollView);
|
|
29
|
+
|
|
30
|
+
const BottomSheetKeyboardAwareScrollView = memo(BottomSheetScrollViewComponent);
|
|
31
|
+
|
|
32
|
+
BottomSheetKeyboardAwareScrollView.displayName =
|
|
33
|
+
"BottomSheetKeyboardAwareScrollView";
|
|
34
|
+
|
|
35
|
+
export { BottomSheetKeyboardAwareScrollView };
|
|
36
|
+
export type { KeyboardAwareScrollViewProps };
|