@jobber/components-native 0.89.5-JOB-139254-4e3c64d.7 → 0.89.5-JOB-140604-c92b387.42
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 +4 -2
- package/dist/src/BottomSheet/BottomSheet.js +55 -35
- package/dist/src/BottomSheet/BottomSheet.style.js +13 -8
- package/dist/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.js +45 -0
- package/dist/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.styles.js +8 -0
- package/dist/src/ButtonGroup/ButtonGroup.js +1 -1
- package/dist/src/Form/Form.js +1 -2
- package/dist/src/Form/hooks/useInternalForm.js +3 -6
- package/dist/src/InputText/InputText.js +2 -2
- package/dist/src/utils/meta/meta.json +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/BottomSheet/BottomSheet.d.ts +13 -4
- package/dist/types/src/BottomSheet/BottomSheet.style.d.ts +12 -7
- package/dist/types/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.d.ts +9 -0
- package/dist/types/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.styles.d.ts +5 -0
- package/dist/types/src/Form/hooks/useInternalForm.d.ts +2 -2
- package/dist/types/src/Form/types.d.ts +0 -6
- package/dist/types/src/InputText/InputText.d.ts +1 -1
- package/package.json +4 -2
- package/src/BottomSheet/BottomSheet.stories.tsx +129 -0
- package/src/BottomSheet/BottomSheet.style.ts +13 -11
- package/src/BottomSheet/BottomSheet.test.tsx +19 -24
- package/src/BottomSheet/BottomSheet.tsx +126 -103
- package/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.styles.ts +9 -0
- package/src/BottomSheet/components/BottomSheetInputText/BottomSheetInputText.tsx +89 -0
- package/src/ButtonGroup/ButtonGroup.tsx +1 -1
- package/src/Form/Form.tsx +0 -2
- package/src/Form/hooks/useInternalForm.ts +2 -9
- package/src/Form/types.ts +0 -7
- package/src/InputText/InputText.tsx +3 -3
- package/src/ThumbnailList/__snapshots__/ThumbnailList.test.tsx.snap +216 -1
- package/src/utils/meta/meta.json +1 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import type { Modalize } from "react-native-modalize";
|
|
4
3
|
export interface BottomSheetProps {
|
|
5
4
|
readonly children: ReactNode;
|
|
6
5
|
/**
|
|
@@ -23,6 +22,16 @@ export interface BottomSheetProps {
|
|
|
23
22
|
* Callback that is called when the overlay is closed.
|
|
24
23
|
*/
|
|
25
24
|
readonly onClose?: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* Ref to the bottom sheet component.
|
|
27
|
+
*/
|
|
28
|
+
readonly ref: Ref<BottomSheetRef>;
|
|
29
|
+
}
|
|
30
|
+
export interface BottomSheetRef {
|
|
31
|
+
open: () => void;
|
|
32
|
+
close: () => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function BottomSheet({ children, showCancel, loading, heading, onOpen, onClose, ref, }: BottomSheetProps): React.JSX.Element;
|
|
35
|
+
export declare namespace BottomSheet {
|
|
36
|
+
var InputText: React.ForwardRefExoticComponent<import("..").InputTextProps & React.RefAttributes<import("..").InputTextRef>>;
|
|
26
37
|
}
|
|
27
|
-
export declare const BottomSheet: React.ForwardRefExoticComponent<BottomSheetProps & React.RefAttributes<import("react-native-modalize/lib/options").IHandles | undefined>>;
|
|
28
|
-
export type BottomSheetRef = Modalize | undefined;
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
export declare const useStyles: () => {
|
|
2
|
-
|
|
2
|
+
backdrop: {
|
|
3
3
|
backgroundColor: string;
|
|
4
|
-
};
|
|
5
|
-
overlay: {
|
|
6
|
-
backgroundColor: string;
|
|
7
|
-
height: number;
|
|
8
4
|
position: "absolute";
|
|
9
5
|
left: 0;
|
|
10
6
|
right: 0;
|
|
11
7
|
top: 0;
|
|
12
8
|
bottom: 0;
|
|
13
9
|
};
|
|
14
|
-
|
|
10
|
+
background: {
|
|
15
11
|
borderTopLeftRadius: number;
|
|
16
12
|
borderTopRightRadius: number;
|
|
17
13
|
paddingTop: number;
|
|
18
14
|
};
|
|
19
|
-
|
|
15
|
+
content: {
|
|
16
|
+
paddingBottom: number;
|
|
17
|
+
};
|
|
18
|
+
footer: {
|
|
20
19
|
paddingBottom: number;
|
|
21
20
|
};
|
|
21
|
+
footerContainer: {
|
|
22
|
+
backgroundColor: string;
|
|
23
|
+
};
|
|
22
24
|
header: {
|
|
23
25
|
paddingHorizontal: number;
|
|
24
26
|
paddingTop: number;
|
|
@@ -29,4 +31,7 @@ export declare const useStyles: () => {
|
|
|
29
31
|
marginTop: number;
|
|
30
32
|
marginBottom: number;
|
|
31
33
|
};
|
|
34
|
+
handle: {
|
|
35
|
+
display: "none";
|
|
36
|
+
};
|
|
32
37
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { InputTextProps, InputTextRef } from "../../../InputText/InputText";
|
|
3
|
+
/**
|
|
4
|
+
* BottomSheetInputText is a wrapper around InputText that provides
|
|
5
|
+
* bottom sheet keyboard handling. It implements the handleOnFocus and
|
|
6
|
+
* handleOnBlur logic from BottomSheetTextInput to ensure proper keyboard
|
|
7
|
+
* positioning within bottom sheets.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BottomSheetInputText: React.ForwardRefExoticComponent<InputTextProps & React.RefAttributes<InputTextRef>>;
|
|
@@ -2,7 +2,7 @@ import type { DeepPartial, FieldValues, UseFormHandleSubmit, UseFormReturn } fro
|
|
|
2
2
|
import type { MutableRefObject, RefObject } from "react";
|
|
3
3
|
import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
4
4
|
import type { InternalFormProps } from "../types";
|
|
5
|
-
type UseInternalFormProps<T extends FieldValues, SubmitResponseType> = Pick<InternalFormProps<T, SubmitResponseType>, "mode" | "reValidateMode" | "initialValues" | "formRef" | "localCacheKey" | "localCacheExclude" | "localCacheId"
|
|
5
|
+
type UseInternalFormProps<T extends FieldValues, SubmitResponseType> = Pick<InternalFormProps<T, SubmitResponseType>, "mode" | "reValidateMode" | "initialValues" | "formRef" | "localCacheKey" | "localCacheExclude" | "localCacheId"> & {
|
|
6
6
|
scrollViewRef?: RefObject<KeyboardAwareScrollView>;
|
|
7
7
|
readonly saveButtonHeight: number;
|
|
8
8
|
readonly messageBannerHeight: number;
|
|
@@ -15,5 +15,5 @@ interface UseInternalForm<T extends FieldValues> {
|
|
|
15
15
|
readonly removeListenerRef: MutableRefObject<() => void>;
|
|
16
16
|
readonly setLocalCache: (data: DeepPartial<T>) => void;
|
|
17
17
|
}
|
|
18
|
-
export declare function useInternalForm<T extends FieldValues, SubmitResponseType>({ mode, reValidateMode, initialValues, formRef, localCacheKey, localCacheId, scrollViewRef, saveButtonHeight, messageBannerHeight,
|
|
18
|
+
export declare function useInternalForm<T extends FieldValues, SubmitResponseType>({ mode, reValidateMode, initialValues, formRef, localCacheKey, localCacheId, scrollViewRef, saveButtonHeight, messageBannerHeight, }: UseInternalFormProps<T, SubmitResponseType>): UseInternalForm<T>;
|
|
19
19
|
export {};
|
|
@@ -131,12 +131,6 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
|
|
|
131
131
|
* If a user opens the same form the data will only be loaded if the `localCacheId` matches
|
|
132
132
|
*/
|
|
133
133
|
localCacheId?: string | string[];
|
|
134
|
-
/**
|
|
135
|
-
* If true, the local cache will be removed when the user navigates away from
|
|
136
|
-
* the dirty form even when offline. By default, cache is only removed on back when online.
|
|
137
|
-
* Defaults to false.
|
|
138
|
-
*/
|
|
139
|
-
removeLocalCacheOnBackOffline?: boolean;
|
|
140
134
|
/**
|
|
141
135
|
* Secondary Action for ButtonGroup
|
|
142
136
|
*/
|
|
@@ -69,7 +69,7 @@ export interface InputTextProps extends Pick<InputFieldWrapperProps, "toolbar" |
|
|
|
69
69
|
/**
|
|
70
70
|
* Callback that is called when the text input is blurred
|
|
71
71
|
*/
|
|
72
|
-
readonly onBlur?: () => void;
|
|
72
|
+
readonly onBlur?: (event?: FocusEvent) => void;
|
|
73
73
|
/**
|
|
74
74
|
* VoiceOver will read this string when a user selects the associated element
|
|
75
75
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.89.5-JOB-
|
|
3
|
+
"version": "0.89.5-JOB-140604-c92b387.42+c92b3875b",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native implementation of Atlantis",
|
|
6
6
|
"repository": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"ts-xor": "^1.1.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
+
"@gorhom/bottom-sheet": "^5.2.6",
|
|
56
57
|
"@react-native-community/datetimepicker": "^8.4.5",
|
|
57
58
|
"@react-native/babel-preset": "^0.81.1",
|
|
58
59
|
"@storybook/addon-a11y": "^9.1.2",
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
},
|
|
79
80
|
"peerDependencies": {
|
|
80
81
|
"@babel/core": "^7.4.5",
|
|
82
|
+
"@gorhom/bottom-sheet": "^5.2.6",
|
|
81
83
|
"@jobber/design": "*",
|
|
82
84
|
"@react-native-community/datetimepicker": ">=6.7.0",
|
|
83
85
|
"date-fns": "^2.30.0",
|
|
@@ -94,5 +96,5 @@
|
|
|
94
96
|
"react-native-safe-area-context": "^5.4.0",
|
|
95
97
|
"react-native-svg": ">=12.0.0"
|
|
96
98
|
},
|
|
97
|
-
"gitHead": "
|
|
99
|
+
"gitHead": "c92b3875b4586cfdd3541c23bf71ea7fc5050dde"
|
|
98
100
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
5
|
+
import { Button, Heading, Text } from "@jobber/components-native";
|
|
6
|
+
import { BottomSheet } from "./BottomSheet";
|
|
7
|
+
import type { BottomSheetRef } from "./BottomSheet";
|
|
8
|
+
import { BottomSheetOption } from "./components/BottomSheetOption";
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Components/Selections/BottomSheet",
|
|
12
|
+
component: BottomSheet,
|
|
13
|
+
} satisfies Meta<typeof BottomSheet>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
const BasicTemplate = () => {
|
|
19
|
+
const bottomSheetRef = useRef<BottomSheetRef>(null);
|
|
20
|
+
|
|
21
|
+
const openBottomSheet = () => {
|
|
22
|
+
bottomSheetRef.current?.open();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const closeBottomSheet = () => {
|
|
26
|
+
bottomSheetRef.current?.close();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<SafeAreaProvider>
|
|
31
|
+
<View style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
32
|
+
<Heading>Basic BottomSheet</Heading>
|
|
33
|
+
<Text>
|
|
34
|
+
Note that due to the differences between React Native Web and React
|
|
35
|
+
Native, this does not render 100% properly
|
|
36
|
+
</Text>
|
|
37
|
+
<Button label="Open Bottom Sheet" onPress={openBottomSheet} />
|
|
38
|
+
<Button label="Close Bottom Sheet" onPress={closeBottomSheet} />
|
|
39
|
+
</View>
|
|
40
|
+
<BottomSheet
|
|
41
|
+
ref={bottomSheetRef}
|
|
42
|
+
onClose={() => console.log("closed bottom sheet")}
|
|
43
|
+
onOpen={() => console.log("opened bottom sheet")}
|
|
44
|
+
>
|
|
45
|
+
<BottomSheetOption
|
|
46
|
+
icon="sendMessage"
|
|
47
|
+
iconColor="greyBlue"
|
|
48
|
+
text="Send message"
|
|
49
|
+
onPress={() => alert("send message")}
|
|
50
|
+
/>
|
|
51
|
+
<BottomSheetOption
|
|
52
|
+
icon="phone"
|
|
53
|
+
iconColor="greyBlue"
|
|
54
|
+
text="Call a friend"
|
|
55
|
+
onPress={() => alert("Calling a friend")}
|
|
56
|
+
/>
|
|
57
|
+
<BottomSheetOption
|
|
58
|
+
destructive={true}
|
|
59
|
+
icon="trash"
|
|
60
|
+
text="Remove"
|
|
61
|
+
onPress={() => alert("Removed")}
|
|
62
|
+
/>
|
|
63
|
+
</BottomSheet>
|
|
64
|
+
</SafeAreaProvider>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const HeaderFooterInputTextTemplate = () => {
|
|
69
|
+
const bottomSheetRef = useRef<BottomSheetRef>(null);
|
|
70
|
+
|
|
71
|
+
const openBottomSheet = () => {
|
|
72
|
+
bottomSheetRef.current?.open();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const closeBottomSheet = () => {
|
|
76
|
+
bottomSheetRef.current?.close();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<SafeAreaProvider>
|
|
81
|
+
<View style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
82
|
+
<Heading>Basic BottomSheet</Heading>
|
|
83
|
+
<Text>
|
|
84
|
+
Note that due to the differences between React Native Web and React
|
|
85
|
+
Native, this does not render 100% properly
|
|
86
|
+
</Text>
|
|
87
|
+
<Button label="Open Bottom Sheet" onPress={openBottomSheet} />
|
|
88
|
+
<Button label="Close Bottom Sheet" onPress={closeBottomSheet} />
|
|
89
|
+
</View>
|
|
90
|
+
<BottomSheet
|
|
91
|
+
ref={bottomSheetRef}
|
|
92
|
+
showCancel={true}
|
|
93
|
+
heading="BottomSheet Header"
|
|
94
|
+
onClose={() => console.log("closed bottom sheet")}
|
|
95
|
+
onOpen={() => console.log("opened bottom sheet")}
|
|
96
|
+
>
|
|
97
|
+
<BottomSheetOption
|
|
98
|
+
icon="sendMessage"
|
|
99
|
+
iconColor="greyBlue"
|
|
100
|
+
text="Send message"
|
|
101
|
+
onPress={() => alert("send message")}
|
|
102
|
+
/>
|
|
103
|
+
<BottomSheet.InputText placeholder="Enter your name" />
|
|
104
|
+
<BottomSheetOption
|
|
105
|
+
icon="phone"
|
|
106
|
+
iconColor="greyBlue"
|
|
107
|
+
text="Call a friend"
|
|
108
|
+
onPress={() => alert("Calling a friend")}
|
|
109
|
+
/>
|
|
110
|
+
<BottomSheetOption
|
|
111
|
+
destructive={true}
|
|
112
|
+
icon="trash"
|
|
113
|
+
text="Remove"
|
|
114
|
+
onPress={() => alert("Removed")}
|
|
115
|
+
/>
|
|
116
|
+
</BottomSheet>
|
|
117
|
+
</SafeAreaProvider>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const Basic: Story = {
|
|
122
|
+
render: BasicTemplate,
|
|
123
|
+
args: {} as Story["args"],
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const HeaderFooterInputText: Story = {
|
|
127
|
+
render: HeaderFooterInputTextTemplate,
|
|
128
|
+
args: {} as Story["args"],
|
|
129
|
+
};
|
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StyleSheet } from "react-native";
|
|
2
2
|
import { buildThemedStyles } from "../AtlantisThemeContext";
|
|
3
3
|
|
|
4
|
-
const { height } = Dimensions.get("window");
|
|
5
|
-
|
|
6
4
|
export const useStyles = buildThemedStyles(tokens => {
|
|
7
5
|
const modalBorderRadius = tokens["radius-larger"];
|
|
8
6
|
|
|
9
7
|
return {
|
|
10
|
-
|
|
11
|
-
backgroundColor: "transparent",
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
overlay: {
|
|
8
|
+
backdrop: {
|
|
15
9
|
...StyleSheet.absoluteFillObject,
|
|
16
10
|
backgroundColor: tokens["color-overlay"],
|
|
17
|
-
height,
|
|
18
11
|
},
|
|
19
|
-
|
|
12
|
+
background: {
|
|
20
13
|
borderTopLeftRadius: modalBorderRadius,
|
|
21
14
|
borderTopRightRadius: modalBorderRadius,
|
|
22
15
|
paddingTop: tokens["space-small"],
|
|
23
16
|
},
|
|
24
|
-
|
|
17
|
+
content: {
|
|
25
18
|
paddingBottom: tokens["space-small"],
|
|
26
19
|
},
|
|
20
|
+
footer: {
|
|
21
|
+
paddingBottom: tokens["space-small"],
|
|
22
|
+
},
|
|
23
|
+
footerContainer: {
|
|
24
|
+
backgroundColor: tokens["color-surface"],
|
|
25
|
+
},
|
|
27
26
|
header: {
|
|
28
27
|
paddingHorizontal: tokens["space-base"],
|
|
29
28
|
paddingTop: tokens["space-small"],
|
|
@@ -34,5 +33,8 @@ export const useStyles = buildThemedStyles(tokens => {
|
|
|
34
33
|
marginTop: tokens["space-small"],
|
|
35
34
|
marginBottom: tokens["space-smaller"],
|
|
36
35
|
},
|
|
36
|
+
handle: {
|
|
37
|
+
display: "none",
|
|
38
|
+
},
|
|
37
39
|
};
|
|
38
40
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { createRef } from "react";
|
|
2
|
-
import { act,
|
|
2
|
+
import { act, render, userEvent, waitFor } from "@testing-library/react-native";
|
|
3
3
|
import { AccessibilityInfo, View } from "react-native";
|
|
4
|
-
import { Host, Portal } from "react-native-portalize";
|
|
5
4
|
import { BottomSheet } from ".";
|
|
6
5
|
import type { BottomSheetRef } from "./BottomSheet";
|
|
7
6
|
import { waitForUntestableRender } from "../utils/test/wait";
|
|
@@ -23,22 +22,18 @@ function setup({
|
|
|
23
22
|
loading?: boolean;
|
|
24
23
|
}) {
|
|
25
24
|
return render(
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
</View>
|
|
39
|
-
</BottomSheet>
|
|
40
|
-
</Portal>
|
|
41
|
-
</Host>,
|
|
25
|
+
<BottomSheet
|
|
26
|
+
ref={ref}
|
|
27
|
+
heading={heading}
|
|
28
|
+
showCancel={showCancel}
|
|
29
|
+
loading={loading}
|
|
30
|
+
onClose={mockOnClose}
|
|
31
|
+
onOpen={mockOnOpen}
|
|
32
|
+
>
|
|
33
|
+
<View>
|
|
34
|
+
<Text>BottomSheet</Text>
|
|
35
|
+
</View>
|
|
36
|
+
</BottomSheet>,
|
|
42
37
|
);
|
|
43
38
|
}
|
|
44
39
|
|
|
@@ -78,7 +73,8 @@ it("BottomSheet can be closed with the cancel action", async () => {
|
|
|
78
73
|
ref.current?.open();
|
|
79
74
|
});
|
|
80
75
|
|
|
81
|
-
|
|
76
|
+
const cancelButton = await findByLabelText("Cancel");
|
|
77
|
+
await userEvent.press(cancelButton);
|
|
82
78
|
|
|
83
79
|
await waitFor(() => {
|
|
84
80
|
expect(queryByText("BottomSheet")).toBeNull();
|
|
@@ -134,15 +130,14 @@ describe("when there is a screen reader enabled", () => {
|
|
|
134
130
|
|
|
135
131
|
const { findByLabelText, queryByText } = setup({});
|
|
136
132
|
|
|
137
|
-
await waitForUntestableRender(
|
|
138
|
-
"Wait for AccessibilityInfo.isScreenReaderEnabled to resolve",
|
|
139
|
-
);
|
|
140
|
-
|
|
141
133
|
await act(async () => {
|
|
142
134
|
ref.current?.open();
|
|
143
135
|
});
|
|
144
136
|
|
|
145
|
-
|
|
137
|
+
const cancelButton = await findByLabelText("Cancel");
|
|
138
|
+
expect(cancelButton).toBeDefined();
|
|
139
|
+
|
|
140
|
+
await userEvent.press(cancelButton);
|
|
146
141
|
|
|
147
142
|
await waitFor(() => {
|
|
148
143
|
expect(queryByText("BottomSheet")).toBeNull();
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import type { ReactNode, Ref
|
|
2
|
-
import React, {
|
|
3
|
-
import type { Modalize } from "react-native-modalize";
|
|
4
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
1
|
+
import type { ReactNode, Ref } from "react";
|
|
2
|
+
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
|
5
3
|
import { Keyboard, View } from "react-native";
|
|
6
|
-
import {
|
|
4
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
|
+
import RNBottomSheet, {
|
|
6
|
+
BottomSheetBackdrop,
|
|
7
|
+
BottomSheetFooter,
|
|
8
|
+
BottomSheetView,
|
|
9
|
+
} from "@gorhom/bottom-sheet";
|
|
10
|
+
import type {
|
|
11
|
+
BottomSheetBackdropProps,
|
|
12
|
+
BottomSheetFooterProps,
|
|
13
|
+
} from "@gorhom/bottom-sheet";
|
|
7
14
|
import { useStyles } from "./BottomSheet.style";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
15
|
+
import { BottomSheetOption } from "./components/BottomSheetOption";
|
|
16
|
+
import { BottomSheetInputText } from "./components/BottomSheetInputText/BottomSheetInputText";
|
|
10
17
|
import { Divider } from "../Divider";
|
|
11
18
|
import { Heading } from "../Heading";
|
|
19
|
+
import { useIsScreenReaderEnabled } from "../hooks";
|
|
12
20
|
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
|
|
13
21
|
|
|
14
22
|
export interface BottomSheetProps {
|
|
@@ -38,73 +46,110 @@ export interface BottomSheetProps {
|
|
|
38
46
|
* Callback that is called when the overlay is closed.
|
|
39
47
|
*/
|
|
40
48
|
readonly onClose?: () => void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ref to the bottom sheet component.
|
|
52
|
+
*/
|
|
53
|
+
readonly ref: Ref<BottomSheetRef>;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
) {
|
|
58
|
-
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
|
59
|
-
const [open, setOpen] = useState<boolean>(false);
|
|
56
|
+
export interface BottomSheetRef {
|
|
57
|
+
open: () => void;
|
|
58
|
+
close: () => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function BottomSheet({
|
|
62
|
+
children,
|
|
63
|
+
showCancel,
|
|
64
|
+
loading = false,
|
|
65
|
+
heading,
|
|
66
|
+
onOpen,
|
|
67
|
+
onClose,
|
|
68
|
+
ref,
|
|
69
|
+
}: BottomSheetProps) {
|
|
60
70
|
const styles = useStyles();
|
|
71
|
+
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
|
72
|
+
const cancellable = (showCancel && !loading) || isScreenReaderEnabled;
|
|
73
|
+
|
|
74
|
+
const { t } = useAtlantisI18n();
|
|
75
|
+
const insets = useSafeAreaInsets();
|
|
76
|
+
const previousIndexRef = useRef(-1);
|
|
77
|
+
const bottomSheetRef = useRef<RNBottomSheet>(null);
|
|
78
|
+
|
|
79
|
+
useImperativeHandle(ref, () => ({
|
|
80
|
+
open: () => {
|
|
81
|
+
bottomSheetRef.current?.expand();
|
|
82
|
+
},
|
|
83
|
+
close: () => {
|
|
84
|
+
bottomSheetRef.current?.close();
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const handleChange = (index: number) => {
|
|
89
|
+
const previousIndex = previousIndexRef.current;
|
|
90
|
+
|
|
91
|
+
if (previousIndex === -1 && index >= 0) {
|
|
92
|
+
// Transitioned from closed to open
|
|
93
|
+
dismissKeyboard();
|
|
94
|
+
onOpen?.();
|
|
95
|
+
} else if (previousIndex >= 0 && index === -1) {
|
|
96
|
+
// Transitioned from open to closed
|
|
97
|
+
dismissKeyboard();
|
|
98
|
+
onClose?.();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
previousIndexRef.current = index;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const renderFooter = useCallback(
|
|
105
|
+
(bottomSheetFooterProps: BottomSheetFooterProps) => {
|
|
106
|
+
return (
|
|
107
|
+
<BottomSheetFooter {...bottomSheetFooterProps}>
|
|
108
|
+
<View
|
|
109
|
+
style={[styles.footerContainer, { paddingBottom: insets.bottom }]}
|
|
110
|
+
>
|
|
111
|
+
{cancellable && (
|
|
112
|
+
<View style={styles.footer}>
|
|
113
|
+
<View style={styles.footerDivider}>
|
|
114
|
+
<Divider />
|
|
115
|
+
</View>
|
|
116
|
+
<BottomSheetOption
|
|
117
|
+
text={t("cancel")}
|
|
118
|
+
icon={"remove"}
|
|
119
|
+
onPress={() => {
|
|
120
|
+
bottomSheetRef.current?.close();
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
</View>
|
|
124
|
+
)}
|
|
125
|
+
</View>
|
|
126
|
+
</BottomSheetFooter>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
[cancellable],
|
|
130
|
+
);
|
|
61
131
|
|
|
62
132
|
return (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
(ref as RefObject<BottomSheetRef>)?.current?.close();
|
|
78
|
-
}}
|
|
79
|
-
styles={styles}
|
|
80
|
-
/>
|
|
81
|
-
}
|
|
82
|
-
withHandle={false}
|
|
83
|
-
withReactModal={isScreenReaderEnabled}
|
|
84
|
-
onOpen={openModal}
|
|
85
|
-
onClose={closeModal}
|
|
133
|
+
<RNBottomSheet
|
|
134
|
+
ref={bottomSheetRef}
|
|
135
|
+
index={-1}
|
|
136
|
+
backdropComponent={Backdrop}
|
|
137
|
+
backgroundStyle={styles.background}
|
|
138
|
+
footerComponent={renderFooter}
|
|
139
|
+
enablePanDownToClose={true}
|
|
140
|
+
onChange={handleChange}
|
|
141
|
+
keyboardBlurBehavior="restore"
|
|
142
|
+
handleStyle={styles.handle}
|
|
143
|
+
>
|
|
144
|
+
<BottomSheetView
|
|
145
|
+
style={styles.content}
|
|
146
|
+
enableFooterMarginAdjustment={true}
|
|
86
147
|
>
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
>
|
|
92
|
-
{children}
|
|
93
|
-
</View>
|
|
94
|
-
</UNSAFE_WrappedModalize>
|
|
95
|
-
</>
|
|
148
|
+
{heading && <Header heading={heading} styles={styles} />}
|
|
149
|
+
{children}
|
|
150
|
+
</BottomSheetView>
|
|
151
|
+
</RNBottomSheet>
|
|
96
152
|
);
|
|
97
|
-
|
|
98
|
-
function openModal() {
|
|
99
|
-
onOpen?.();
|
|
100
|
-
setOpen(true);
|
|
101
|
-
dismissKeyboard();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function closeModal() {
|
|
105
|
-
onClose?.();
|
|
106
|
-
setOpen(false);
|
|
107
|
-
}
|
|
108
153
|
}
|
|
109
154
|
|
|
110
155
|
function Header({
|
|
@@ -121,46 +166,24 @@ function Header({
|
|
|
121
166
|
);
|
|
122
167
|
}
|
|
123
168
|
|
|
124
|
-
function Footer({
|
|
125
|
-
cancellable,
|
|
126
|
-
onCancel,
|
|
127
|
-
styles,
|
|
128
|
-
}: {
|
|
129
|
-
readonly cancellable: boolean;
|
|
130
|
-
readonly onCancel: () => void;
|
|
131
|
-
readonly styles: ReturnType<typeof useStyles>;
|
|
132
|
-
}) {
|
|
133
|
-
const insets = useSafeAreaInsets();
|
|
134
|
-
const { t } = useAtlantisI18n();
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<View style={{ marginBottom: insets.bottom }}>
|
|
138
|
-
{cancellable && (
|
|
139
|
-
<View style={styles.children}>
|
|
140
|
-
<View style={styles.footerDivider}>
|
|
141
|
-
<Divider />
|
|
142
|
-
</View>
|
|
143
|
-
<BottomSheetOption
|
|
144
|
-
text={t("cancel")}
|
|
145
|
-
icon={"remove"}
|
|
146
|
-
onPress={onCancel}
|
|
147
|
-
/>
|
|
148
|
-
</View>
|
|
149
|
-
)}
|
|
150
|
-
</View>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
169
|
function dismissKeyboard() {
|
|
155
170
|
//Dismisses the keyboard before opening the bottom sheet.
|
|
156
171
|
//In the case where an input text field is focused we don't want to show the bottom sheet behind or above keyboard
|
|
157
172
|
Keyboard.dismiss();
|
|
158
173
|
}
|
|
159
174
|
|
|
160
|
-
function
|
|
161
|
-
styles
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
175
|
+
function Backdrop(bottomSheetBackdropProps: BottomSheetBackdropProps) {
|
|
176
|
+
const styles = useStyles();
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<BottomSheetBackdrop
|
|
180
|
+
{...bottomSheetBackdropProps}
|
|
181
|
+
appearsOnIndex={0}
|
|
182
|
+
disappearsOnIndex={-1}
|
|
183
|
+
style={styles.backdrop}
|
|
184
|
+
opacity={1}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
166
187
|
}
|
|
188
|
+
|
|
189
|
+
BottomSheet.InputText = BottomSheetInputText;
|