@applicaster/quick-brick-core 13.0.0-alpha.7138263059 → 13.0.0-alpha.7193146510
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/App/ErrorBoundary/__tests__/errorBoundary.test.jsx +232 -0
- package/App/ErrorBoundary/__tests__/store.test.js +143 -0
- package/App/ErrorBoundary/index.tsx +45 -21
- package/App/ErrorBoundary/store.ts +26 -0
- package/App/ModalProvider/ModalBottomSheet/DraggableBottomSheet/index.tsx +0 -1
- package/App/ModalProvider/ModalBottomSheet/ModalBottomSheetFrame.tsx +68 -10
- package/App/ModalProvider/ModalBottomSheet/Overlay.tsx +5 -7
- package/App/ModalProvider/ModalBottomSheet/index.tsx +17 -47
- package/App/ModalProvider/ModalPresenter.tsx +1 -1
- package/App/ModalProvider/index.tsx +7 -3
- package/App/NavigationProvider/NavigationProvider.tsx +29 -43
- package/App/NavigationProvider/__tests__/navigationProvider.test.tsx +0 -44
- package/App/NetworkStatusProvider/utils.ts +0 -1
- package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.tsx +146 -0
- package/App/appLifeCycleManager/index.tsx +4 -4
- package/App/index.tsx +4 -3
- package/App/remoteContextReloader/getRemoteContextData/getNativeRemoteContextData.ts +14 -0
- package/package.json +8 -12
- package/App/ErrorBoundary/__tests__/errorBoundary.test.js +0 -55
- package/App/ModalProvider/ModalChildrenWrapper/index.web.ts +0 -1
- package/App/NavigationProvider/__tests__/__snapshots__/navigationProvider.test.tsx.snap +0 -68
- package/App/appLifeCycleManager/__tests__/__snapshots__/appLifeCycleManager.test.js.snap +0 -30
- package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.js +0 -134
- /package/App/ModalProvider/ModalChildrenWrapper/{index.ios.ts → index.ts} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Text, TouchableOpacity } from "react-native";
|
|
3
|
+
import { render, fireEvent, act } from "@testing-library/react-native";
|
|
4
|
+
import { useErrorStore } from "../store";
|
|
5
|
+
import { ErrorBoundary } from "../index";
|
|
6
|
+
|
|
7
|
+
jest.mock(
|
|
8
|
+
"@applicaster/zapp-react-native-ui-components/Components/ErrorScreen",
|
|
9
|
+
() => ({
|
|
10
|
+
// eslint-disable-next-line react/prop-types
|
|
11
|
+
ErrorScreen: ({ dismissError, message, buttons }) => (
|
|
12
|
+
<View testID="error-screen">
|
|
13
|
+
<Text testID="error-message">{message || "Default error message"}</Text>
|
|
14
|
+
{/* eslint-disable-next-line react/prop-types */}
|
|
15
|
+
{buttons?.map((button, index) => (
|
|
16
|
+
<TouchableOpacity
|
|
17
|
+
key={index}
|
|
18
|
+
onPress={button.onPress}
|
|
19
|
+
testID={`button-${index}`}
|
|
20
|
+
>
|
|
21
|
+
<Text>{button.text}</Text>
|
|
22
|
+
</TouchableOpacity>
|
|
23
|
+
))}
|
|
24
|
+
|
|
25
|
+
{dismissError ? (
|
|
26
|
+
<TouchableOpacity onPress={dismissError} testID="dismiss-button">
|
|
27
|
+
<Text>Dismiss Error</Text>
|
|
28
|
+
</TouchableOpacity>
|
|
29
|
+
) : null}
|
|
30
|
+
</View>
|
|
31
|
+
),
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
|
|
36
|
+
sendQuickBrickEvent: jest.fn(),
|
|
37
|
+
QUICK_BRICK_EVENTS: {
|
|
38
|
+
FORCE_APP_RELOAD: "force_app_reload",
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
jest.mock("@applicaster/zapp-react-native-utils/logger", () => ({
|
|
43
|
+
coreLogger: {
|
|
44
|
+
addSubsystem: () => ({
|
|
45
|
+
error: jest.fn(),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line react/prop-types
|
|
51
|
+
const TestComponent = ({ error, errorMessage = "error !!" }) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
throw new Error(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View>
|
|
58
|
+
<Text>all is good</Text>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe("<ErrorBoundary />", () => {
|
|
64
|
+
// Clear all error states between tests
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
useErrorStore.getState().dismissError();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("when there is no error", () => {
|
|
70
|
+
it("renders children correctly", () => {
|
|
71
|
+
const { getByText } = render(
|
|
72
|
+
<ErrorBoundary>
|
|
73
|
+
<TestComponent />
|
|
74
|
+
</ErrorBoundary>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(getByText("all is good")).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("when an error is caught", () => {
|
|
82
|
+
const _consoleLogs = {
|
|
83
|
+
error: console.error,
|
|
84
|
+
log: console.log,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
beforeAll(() => {
|
|
88
|
+
console.error = jest.fn();
|
|
89
|
+
console.log = jest.fn();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterAll(() => {
|
|
93
|
+
console.error = _consoleLogs.error;
|
|
94
|
+
console.log = _consoleLogs.log;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders error state with default values (recoverable error)", () => {
|
|
98
|
+
const { getByTestId } = render(
|
|
99
|
+
<ErrorBoundary>
|
|
100
|
+
<TestComponent error />
|
|
101
|
+
</ErrorBoundary>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
105
|
+
expect(getByTestId("dismiss-button")).toBeTruthy(); // Should be recoverable by default
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("handles non-recoverable errors (no dismiss button)", () => {
|
|
109
|
+
const { getByTestId, queryByTestId } = render(
|
|
110
|
+
<ErrorBoundary recoverable={false}>
|
|
111
|
+
<TestComponent error />
|
|
112
|
+
</ErrorBoundary>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
116
|
+
expect(queryByTestId("dismiss-button")).toBeFalsy(); // Should not be recoverable
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("shows custom error message from caught error", () => {
|
|
120
|
+
const customErrorMessage = "Custom error message for testing";
|
|
121
|
+
|
|
122
|
+
const { getByTestId } = render(
|
|
123
|
+
<ErrorBoundary>
|
|
124
|
+
<TestComponent error errorMessage={customErrorMessage} />
|
|
125
|
+
</ErrorBoundary>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// The error info should be passed to the ErrorScreen
|
|
129
|
+
expect(getByTestId("error-message").props.children).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("can dismiss the error and restore the UI", () => {
|
|
133
|
+
// First render with error
|
|
134
|
+
const { getByTestId, queryByText, rerender } = render(
|
|
135
|
+
<ErrorBoundary>
|
|
136
|
+
<TestComponent error />
|
|
137
|
+
</ErrorBoundary>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Verify error screen is shown
|
|
141
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
142
|
+
|
|
143
|
+
// Dismiss the error
|
|
144
|
+
fireEvent.press(getByTestId("dismiss-button"));
|
|
145
|
+
|
|
146
|
+
// Force rerender to see the change
|
|
147
|
+
rerender(
|
|
148
|
+
<ErrorBoundary>
|
|
149
|
+
<TestComponent />
|
|
150
|
+
</ErrorBoundary>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Now the regular content should be visible
|
|
154
|
+
expect(queryByText("all is good")).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("error store functionality", () => {
|
|
159
|
+
it("properly sets error state", () => {
|
|
160
|
+
const error = new Error("Test error");
|
|
161
|
+
const errorInfo = "Test error info";
|
|
162
|
+
|
|
163
|
+
act(() => {
|
|
164
|
+
useErrorStore.getState().setError(error, errorInfo, true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const state = useErrorStore.getState();
|
|
168
|
+
expect(state.hasError).toBe(true);
|
|
169
|
+
expect(state.error).toBe(error);
|
|
170
|
+
expect(state.info).toBe(errorInfo);
|
|
171
|
+
expect(state.recoverable).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("properly resets error state", () => {
|
|
175
|
+
// First set an error
|
|
176
|
+
act(() => {
|
|
177
|
+
useErrorStore
|
|
178
|
+
.getState()
|
|
179
|
+
.setError(new Error("Test error"), "Info", true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Then dismiss it
|
|
183
|
+
act(() => {
|
|
184
|
+
useErrorStore.getState().dismissError();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const state = useErrorStore.getState();
|
|
188
|
+
expect(state.hasError).toBe(false);
|
|
189
|
+
expect(state.error).toBeUndefined();
|
|
190
|
+
expect(state.info).toBeUndefined();
|
|
191
|
+
expect(state.recoverable).toBe(true); // Default value when reset
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("remembers recoverable state", () => {
|
|
195
|
+
act(() => {
|
|
196
|
+
useErrorStore
|
|
197
|
+
.getState()
|
|
198
|
+
.setError(new Error("Test error"), "Info", false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const state = useErrorStore.getState();
|
|
202
|
+
expect(state.recoverable).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("integration with ErrorScreen", () => {
|
|
207
|
+
it("renders restart app button for non-recoverable errors", () => {
|
|
208
|
+
const { getAllByTestId } = render(
|
|
209
|
+
<ErrorBoundary recoverable={false}>
|
|
210
|
+
<TestComponent error />
|
|
211
|
+
</ErrorBoundary>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Should have the restart app button
|
|
215
|
+
const buttons = getAllByTestId(/button-/);
|
|
216
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("passes error info to the ErrorScreen component", () => {
|
|
220
|
+
const customErrorMessage = "Very specific error message";
|
|
221
|
+
|
|
222
|
+
const { getByTestId } = render(
|
|
223
|
+
<ErrorBoundary>
|
|
224
|
+
<TestComponent error errorMessage={customErrorMessage} />
|
|
225
|
+
</ErrorBoundary>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// The error info should be visible in the UI
|
|
229
|
+
expect(getByTestId("error-message")).toBeTruthy();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
import { useErrorStore } from "../store";
|
|
3
|
+
|
|
4
|
+
describe("Error Store", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
act(() => {
|
|
7
|
+
useErrorStore.getState().dismissError();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("Initial state", () => {
|
|
12
|
+
it("should have correct default values", () => {
|
|
13
|
+
const { result } = renderHook(() => useErrorStore());
|
|
14
|
+
|
|
15
|
+
expect(result.current).toMatchObject({
|
|
16
|
+
hasError: false,
|
|
17
|
+
error: undefined,
|
|
18
|
+
info: undefined,
|
|
19
|
+
recoverable: true,
|
|
20
|
+
setError: expect.any(Function),
|
|
21
|
+
dismissError: expect.any(Function),
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("setError action", () => {
|
|
27
|
+
it("should set error state with provided values", () => {
|
|
28
|
+
const testError = new Error("Test error");
|
|
29
|
+
const testInfo = "Test error info";
|
|
30
|
+
const isRecoverable = true;
|
|
31
|
+
|
|
32
|
+
const { result } = renderHook(() => useErrorStore());
|
|
33
|
+
|
|
34
|
+
act(() => {
|
|
35
|
+
result.current.setError(testError, testInfo, isRecoverable);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(result.current).toMatchObject({
|
|
39
|
+
hasError: true,
|
|
40
|
+
error: testError,
|
|
41
|
+
info: testInfo,
|
|
42
|
+
recoverable: isRecoverable,
|
|
43
|
+
setError: expect.any(Function),
|
|
44
|
+
dismissError: expect.any(Function),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should set non-recoverable error when specified", () => {
|
|
49
|
+
const testError = new Error("Test error");
|
|
50
|
+
const testInfo = "Test error info";
|
|
51
|
+
const isRecoverable = false;
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(() => useErrorStore());
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.setError(testError, testInfo, isRecoverable);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(result.current).toMatchObject({
|
|
60
|
+
recoverable: false,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should default to recoverable error when not specified", () => {
|
|
65
|
+
const testError = new Error("Test error");
|
|
66
|
+
const testInfo = "Test error info";
|
|
67
|
+
|
|
68
|
+
const { result } = renderHook(() => useErrorStore());
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
result.current.setError(testError, testInfo);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.current).toMatchObject({
|
|
75
|
+
recoverable: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("dismissError action", () => {
|
|
81
|
+
it("should reset error state to defaults", () => {
|
|
82
|
+
const testError = new Error("Test error");
|
|
83
|
+
const { result } = renderHook(() => useErrorStore());
|
|
84
|
+
|
|
85
|
+
act(() => {
|
|
86
|
+
result.current.setError(testError, "info", false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(result.current).toMatchObject({
|
|
90
|
+
hasError: true,
|
|
91
|
+
recoverable: false,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
act(() => {
|
|
95
|
+
result.current.dismissError();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result.current).toMatchObject({
|
|
99
|
+
hasError: false,
|
|
100
|
+
error: undefined,
|
|
101
|
+
info: undefined,
|
|
102
|
+
recoverable: true,
|
|
103
|
+
setError: expect.any(Function),
|
|
104
|
+
dismissError: expect.any(Function),
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("Direct store access via API", () => {
|
|
110
|
+
it("should allow direct access to store outside React components", () => {
|
|
111
|
+
act(() => {
|
|
112
|
+
useErrorStore
|
|
113
|
+
.getState()
|
|
114
|
+
.setError(new Error("API error"), "API info", false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const { result } = renderHook(() => useErrorStore());
|
|
118
|
+
|
|
119
|
+
expect(result.current).toMatchObject({
|
|
120
|
+
hasError: true,
|
|
121
|
+
error: expect.objectContaining({ message: "API error" }),
|
|
122
|
+
info: "API info",
|
|
123
|
+
recoverable: false,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should allow clearing errors via API", () => {
|
|
128
|
+
act(() => {
|
|
129
|
+
useErrorStore.getState().setError(new Error("API error"), "API info");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
useErrorStore.getState().dismissError();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const { result } = renderHook(() => useErrorStore());
|
|
137
|
+
|
|
138
|
+
expect(result.current).toMatchObject({
|
|
139
|
+
hasError: false,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -1,42 +1,66 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import pick from "lodash/pick";
|
|
2
3
|
|
|
3
4
|
import { coreLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
5
|
import { ErrorScreen } from "@applicaster/zapp-react-native-ui-components/Components/ErrorScreen";
|
|
5
6
|
|
|
7
|
+
import { useErrorStore } from "./store";
|
|
8
|
+
import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
|
|
9
|
+
|
|
6
10
|
const errorBoundaryLogger = coreLogger.addSubsystem("react_error-boundary");
|
|
7
11
|
|
|
8
12
|
type Props = {
|
|
9
13
|
children: React.ReactNode;
|
|
14
|
+
recoverable?: boolean;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
error?: Error;
|
|
15
|
-
info?: string;
|
|
17
|
+
type ErrorBoundaryContentProps = {
|
|
18
|
+
children: React.ReactNode;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const restartApp = () => {
|
|
22
|
+
QuickBrickManager.sendQuickBrickEvent(
|
|
23
|
+
QuickBrickManager.QUICK_BRICK_EVENTS.FORCE_APP_RELOAD
|
|
24
|
+
);
|
|
25
|
+
};
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
const restartButton = {
|
|
28
|
+
text: "Restart the app",
|
|
29
|
+
onPress: restartApp,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const buttons = [restartButton];
|
|
33
|
+
|
|
34
|
+
const ErrorBoundaryContent: React.FC<ErrorBoundaryContentProps> = ({
|
|
35
|
+
children,
|
|
36
|
+
}) => {
|
|
37
|
+
const { hasError, dismissError, info } = pick(
|
|
38
|
+
useErrorStore((state) => state),
|
|
39
|
+
["hasError", "dismissError", "info"]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (hasError) {
|
|
43
|
+
return (
|
|
44
|
+
<ErrorScreen
|
|
45
|
+
dismissError={dismissError}
|
|
46
|
+
message={info}
|
|
47
|
+
buttons={buttons}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
52
|
+
return <>{children}</>;
|
|
53
|
+
};
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
export class ErrorBoundary extends React.Component<Props> {
|
|
56
|
+
componentDidCatch(error, info) {
|
|
57
|
+
errorBoundaryLogger.error({ error, data: info, message: error.message });
|
|
58
|
+
const { recoverable = true } = this.props;
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
60
|
+
useErrorStore.getState().setError(error, info, recoverable);
|
|
61
|
+
}
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
render() {
|
|
64
|
+
return <ErrorBoundaryContent>{this.props.children}</ErrorBoundaryContent>;
|
|
41
65
|
}
|
|
42
66
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
type ErrorState = {
|
|
4
|
+
hasError: boolean;
|
|
5
|
+
error?: Error;
|
|
6
|
+
info?: string;
|
|
7
|
+
recoverable: boolean;
|
|
8
|
+
setError: (error: Error, info: string, recoverable?: boolean) => void;
|
|
9
|
+
dismissError: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useErrorStore = create<ErrorState>((set) => ({
|
|
13
|
+
hasError: false,
|
|
14
|
+
error: undefined,
|
|
15
|
+
info: undefined,
|
|
16
|
+
recoverable: true,
|
|
17
|
+
setError: (error: Error, info: string, recoverable = true) =>
|
|
18
|
+
set({ hasError: true, error, info, recoverable }),
|
|
19
|
+
dismissError: () =>
|
|
20
|
+
set({
|
|
21
|
+
hasError: false,
|
|
22
|
+
error: undefined,
|
|
23
|
+
info: undefined,
|
|
24
|
+
recoverable: true,
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
@@ -300,7 +300,6 @@ export class DraggableBottomSheet extends Component<Props, ComponentState> {
|
|
|
300
300
|
simultaneousHandlers={this.drawer}
|
|
301
301
|
>
|
|
302
302
|
<Animated.ScrollView
|
|
303
|
-
style={{ marginBottom: this._snapPoints.top }}
|
|
304
303
|
bounces={false}
|
|
305
304
|
onScrollBeginDrag={this._onRegisterLastScroll}
|
|
306
305
|
scrollEventThrottle={1}
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { StyleSheet, View } from "react-native";
|
|
2
|
+
import { LayoutChangeEvent, StyleSheet, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { Handle, HandleProps } from "./Handle";
|
|
5
5
|
import { DraggableBottomSheet } from "./DraggableBottomSheet";
|
|
6
|
+
import {
|
|
7
|
+
SafeAreaView,
|
|
8
|
+
useSafeAreaInsets,
|
|
9
|
+
} from "react-native-safe-area-context";
|
|
10
|
+
import {
|
|
11
|
+
isAndroidPlatform,
|
|
12
|
+
isAndroidVersionAtLeast,
|
|
13
|
+
platformSelect,
|
|
14
|
+
} from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
6
15
|
|
|
7
16
|
type Props = {
|
|
8
17
|
children: React.ReactChild;
|
|
9
18
|
backgroundColor: string;
|
|
10
19
|
cornerRadius: number;
|
|
11
|
-
contentHeight: number;
|
|
12
20
|
maxHeight: number;
|
|
13
21
|
maxWidth: number;
|
|
14
22
|
visible: boolean;
|
|
15
23
|
handleProps: HandleProps;
|
|
16
24
|
closeToast: () => void;
|
|
17
|
-
dimensions: {
|
|
18
|
-
|
|
25
|
+
dimensions: {
|
|
26
|
+
statusBarHeight: any;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
19
30
|
};
|
|
20
31
|
|
|
21
32
|
const styles = StyleSheet.create({
|
|
@@ -25,6 +36,34 @@ const styles = StyleSheet.create({
|
|
|
25
36
|
},
|
|
26
37
|
});
|
|
27
38
|
|
|
39
|
+
const defaultBottomOffset = platformSelect({
|
|
40
|
+
ios: 34,
|
|
41
|
+
android: 0,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const getSheetHeight = ({ height, bottomOffset, maxHeight, dimensions }) => {
|
|
45
|
+
return platformSelect({
|
|
46
|
+
ios: height - bottomOffset,
|
|
47
|
+
default:
|
|
48
|
+
!isAndroidVersionAtLeast(35) && isAndroidPlatform()
|
|
49
|
+
? (height || maxHeight) - (bottomOffset + dimensions.statusBarHeight)
|
|
50
|
+
: height || maxHeight,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const useGetSheetHeight = (height, maxHeight, dimensions) => {
|
|
55
|
+
const { bottom = defaultBottomOffset } = useSafeAreaInsets();
|
|
56
|
+
|
|
57
|
+
const bottomOffset = bottom;
|
|
58
|
+
|
|
59
|
+
return getSheetHeight({
|
|
60
|
+
height,
|
|
61
|
+
bottomOffset,
|
|
62
|
+
maxHeight,
|
|
63
|
+
dimensions,
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
28
67
|
export function ModalBottomSheetFrame(props: Props) {
|
|
29
68
|
const {
|
|
30
69
|
children,
|
|
@@ -34,9 +73,7 @@ export function ModalBottomSheetFrame(props: Props) {
|
|
|
34
73
|
maxWidth,
|
|
35
74
|
visible,
|
|
36
75
|
handleProps,
|
|
37
|
-
contentHeight,
|
|
38
76
|
dimensions,
|
|
39
|
-
paddingBottom,
|
|
40
77
|
} = props;
|
|
41
78
|
|
|
42
79
|
const extraStyles = {
|
|
@@ -44,21 +81,42 @@ export function ModalBottomSheetFrame(props: Props) {
|
|
|
44
81
|
borderTopLeftRadius: cornerRadius,
|
|
45
82
|
borderTopRightRadius: cornerRadius,
|
|
46
83
|
maxHeight,
|
|
47
|
-
paddingBottom,
|
|
48
84
|
maxWidth: Math.min(Number(maxWidth), dimensions.width),
|
|
49
85
|
};
|
|
50
86
|
|
|
87
|
+
const [height, setHeight] = React.useState(0);
|
|
88
|
+
|
|
89
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
90
|
+
const { height } = event.nativeEvent.layout;
|
|
91
|
+
|
|
92
|
+
if (height !== 0) {
|
|
93
|
+
setHeight((oldHeight) => oldHeight || height);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const { bottom = defaultBottomOffset } = useSafeAreaInsets();
|
|
98
|
+
|
|
99
|
+
const bottomOffset = bottom;
|
|
100
|
+
|
|
101
|
+
const sheetHeight = useGetSheetHeight(height, maxHeight, dimensions);
|
|
102
|
+
|
|
51
103
|
return (
|
|
52
104
|
<DraggableBottomSheet
|
|
53
|
-
sheetHeight={
|
|
54
|
-
visible={visible}
|
|
105
|
+
sheetHeight={sheetHeight}
|
|
106
|
+
visible={height > 0 && visible}
|
|
55
107
|
dismiss={props.closeToast}
|
|
56
108
|
hasHeader={handleProps.enabled}
|
|
57
109
|
header={(dragging) => <Handle {...handleProps} focused={dragging} />}
|
|
58
110
|
maxWidth={maxWidth}
|
|
59
111
|
{...dimensions}
|
|
112
|
+
height={dimensions.height - bottomOffset}
|
|
60
113
|
>
|
|
61
|
-
<
|
|
114
|
+
<SafeAreaView
|
|
115
|
+
edges={["left", "right", "bottom"]}
|
|
116
|
+
style={[styles.toastFrameContainer, extraStyles]}
|
|
117
|
+
>
|
|
118
|
+
<View onLayout={onLayout}>{children}</View>
|
|
119
|
+
</SafeAreaView>
|
|
62
120
|
</DraggableBottomSheet>
|
|
63
121
|
);
|
|
64
122
|
}
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { StyleSheet, TouchableOpacity, Easing, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { AnimatedInOut } from "@applicaster/zapp-react-native-ui-components/Components/AnimatedInOut";
|
|
5
|
-
import {
|
|
5
|
+
import { dismissModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
6
6
|
|
|
7
7
|
type Props = {
|
|
8
8
|
overlayColor: string;
|
|
@@ -35,21 +35,19 @@ const fadeInAnimation = {
|
|
|
35
35
|
delay,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const animationConfig =
|
|
38
|
+
const animationConfig = {
|
|
39
39
|
in: fadeInAnimation,
|
|
40
40
|
out: {
|
|
41
41
|
...fadeInAnimation,
|
|
42
|
-
onAnimationEnd,
|
|
42
|
+
onAnimationEnd: dismissModal,
|
|
43
43
|
},
|
|
44
|
-
}
|
|
44
|
+
};
|
|
45
45
|
|
|
46
46
|
export function Overlay(props: Props) {
|
|
47
|
-
const navigator = useNavigation();
|
|
48
|
-
|
|
49
47
|
return (
|
|
50
48
|
<AnimatedInOut
|
|
51
49
|
visible={props.visible}
|
|
52
|
-
animationConfig={animationConfig
|
|
50
|
+
animationConfig={animationConfig}
|
|
53
51
|
staticStyles={styles.container}
|
|
54
52
|
>
|
|
55
53
|
<TouchableOpacity
|