@jobber/components-native 0.113.2 → 0.113.4-implement--94d34f8.2
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/docs/AtlantisThemeContext/AtlantisThemeContext.md +3 -1
- package/dist/docs/Select/Select.md +276 -143
- package/dist/package.json +2 -2
- package/dist/src/AtlantisThemeContext/AtlantisThemeContext.js +23 -9
- package/dist/src/AtlantisThemeContext/AtlantisThemeContext.test.js +79 -5
- package/dist/src/ContentOverlay/ContentOverlay.js +2 -2
- package/dist/src/Form/Form.js +1 -12
- package/dist/src/Form/Form.test.js +49 -4
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/AtlantisThemeContext/AtlantisThemeContext.d.ts +1 -1
- package/dist/types/src/AtlantisThemeContext/index.d.ts +1 -1
- package/dist/types/src/AtlantisThemeContext/types.d.ts +16 -3
- package/jobber-components-native.podspec +0 -1
- package/package.json +2 -2
- package/src/AtlantisThemeContext/AtlantisThemeContext.test.tsx +127 -5
- package/src/AtlantisThemeContext/AtlantisThemeContext.tsx +33 -8
- package/src/AtlantisThemeContext/docs/AtlantisThemeContextBasicExample.tsx +4 -2
- package/src/AtlantisThemeContext/docs/AtlantisThemeContextForceThemeForProviderExample.tsx +4 -2
- package/src/AtlantisThemeContext/index.ts +1 -0
- package/src/AtlantisThemeContext/types.ts +20 -3
- package/src/ContentOverlay/ContentOverlay.tsx +2 -2
- package/src/Form/Form.test.tsx +61 -4
- package/src/Form/Form.tsx +1 -11
|
@@ -11,22 +11,36 @@ import React from "react";
|
|
|
11
11
|
import { act, renderHook } from "@testing-library/react-native";
|
|
12
12
|
import { darkTokens, iosTokens } from "@jobber/design";
|
|
13
13
|
import merge from "lodash/merge";
|
|
14
|
+
import { useColorScheme } from "react-native";
|
|
14
15
|
import { AtlantisThemeContextProvider, useAtlantisTheme, } from "./AtlantisThemeContext";
|
|
16
|
+
jest.mock("react-native", () => {
|
|
17
|
+
const actual = jest.requireActual("react-native");
|
|
18
|
+
Object.defineProperty(actual, "useColorScheme", {
|
|
19
|
+
configurable: true,
|
|
20
|
+
value: jest.fn(),
|
|
21
|
+
});
|
|
22
|
+
return actual;
|
|
23
|
+
});
|
|
15
24
|
const expectedDarkTokens = merge({}, iosTokens, darkTokens);
|
|
16
25
|
const expectedLightTokens = iosTokens;
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
const mockUseColorScheme = useColorScheme;
|
|
27
|
+
function Wrapper({ children, theme, onThemeChange, dangerouslyOverrideTheme, }) {
|
|
28
|
+
return (React.createElement(AtlantisThemeContextProvider, { theme: theme, onThemeChange: onThemeChange, dangerouslyOverrideTheme: dangerouslyOverrideTheme }, children));
|
|
19
29
|
}
|
|
20
30
|
function WrapperWithOverride({ children, dangerouslyOverrideTheme, }) {
|
|
21
31
|
return (React.createElement(Wrapper, null,
|
|
22
32
|
React.createElement(AtlantisThemeContextProvider, { dangerouslyOverrideTheme: dangerouslyOverrideTheme }, children)));
|
|
23
33
|
}
|
|
24
34
|
describe("ThemeContext", () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
mockUseColorScheme.mockReturnValue("light");
|
|
37
|
+
});
|
|
25
38
|
it("defaults to the light theme", () => {
|
|
26
39
|
const { result } = renderHook(useAtlantisTheme, {
|
|
27
40
|
wrapper: (props) => (React.createElement(Wrapper, Object.assign({}, props))),
|
|
28
41
|
});
|
|
29
42
|
expect(result.current.theme).toBe("light");
|
|
43
|
+
expect(result.current.effectiveTheme).toBe("light");
|
|
30
44
|
expect(result.current.tokens).toEqual(expectedLightTokens);
|
|
31
45
|
});
|
|
32
46
|
it("updates the theme and tokens", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -35,9 +49,56 @@ describe("ThemeContext", () => {
|
|
|
35
49
|
});
|
|
36
50
|
yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("dark"); }));
|
|
37
51
|
expect(result.current.theme).toBe("dark");
|
|
52
|
+
expect(result.current.effectiveTheme).toBe("dark");
|
|
38
53
|
expect(result.current.tokens).toEqual(expectedDarkTokens);
|
|
39
54
|
yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("light"); }));
|
|
40
55
|
expect(result.current.theme).toBe("light");
|
|
56
|
+
expect(result.current.effectiveTheme).toBe("light");
|
|
57
|
+
expect(result.current.tokens).toEqual(expectedLightTokens);
|
|
58
|
+
}));
|
|
59
|
+
it("resolves the system theme from the OS color scheme", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
mockUseColorScheme.mockReturnValue("dark");
|
|
61
|
+
const { result, rerender } = renderHook(useAtlantisTheme, {
|
|
62
|
+
wrapper: (props) => (React.createElement(Wrapper, Object.assign({}, props))),
|
|
63
|
+
});
|
|
64
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("system"); }));
|
|
65
|
+
expect(result.current.theme).toBe("system");
|
|
66
|
+
expect(result.current.effectiveTheme).toBe("dark");
|
|
67
|
+
expect(result.current.tokens).toEqual(expectedDarkTokens);
|
|
68
|
+
mockUseColorScheme.mockReturnValue("light");
|
|
69
|
+
rerender({});
|
|
70
|
+
expect(result.current.theme).toBe("system");
|
|
71
|
+
expect(result.current.effectiveTheme).toBe("light");
|
|
72
|
+
expect(result.current.tokens).toEqual(expectedLightTokens);
|
|
73
|
+
}));
|
|
74
|
+
it("uses a controlled selected theme", () => {
|
|
75
|
+
mockUseColorScheme.mockReturnValue("dark");
|
|
76
|
+
const { result } = renderHook(useAtlantisTheme, {
|
|
77
|
+
wrapper: (props) => (React.createElement(Wrapper, Object.assign({}, props, { theme: "system" }))),
|
|
78
|
+
});
|
|
79
|
+
expect(result.current.theme).toBe("system");
|
|
80
|
+
expect(result.current.effectiveTheme).toBe("dark");
|
|
81
|
+
expect(result.current.tokens).toEqual(expectedDarkTokens);
|
|
82
|
+
});
|
|
83
|
+
it("reports selected theme changes from a controlled provider", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
|
+
const onThemeChange = jest.fn();
|
|
85
|
+
const { result } = renderHook(useAtlantisTheme, {
|
|
86
|
+
wrapper: (props) => (React.createElement(Wrapper, Object.assign({}, props, { theme: "light", onThemeChange: onThemeChange }))),
|
|
87
|
+
});
|
|
88
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("system"); }));
|
|
89
|
+
expect(onThemeChange).toHaveBeenCalledWith("system");
|
|
90
|
+
expect(result.current.theme).toBe("light");
|
|
91
|
+
expect(result.current.effectiveTheme).toBe("light");
|
|
92
|
+
expect(result.current.tokens).toEqual(expectedLightTokens);
|
|
93
|
+
}));
|
|
94
|
+
it("falls back to light when system color scheme is unavailable", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
mockUseColorScheme.mockReturnValue(null);
|
|
96
|
+
const { result } = renderHook(useAtlantisTheme, {
|
|
97
|
+
wrapper: (props) => (React.createElement(Wrapper, Object.assign({}, props))),
|
|
98
|
+
});
|
|
99
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("system"); }));
|
|
100
|
+
expect(result.current.theme).toBe("system");
|
|
101
|
+
expect(result.current.effectiveTheme).toBe("light");
|
|
41
102
|
expect(result.current.tokens).toEqual(expectedLightTokens);
|
|
42
103
|
}));
|
|
43
104
|
describe("when theme is forced for provider", () => {
|
|
@@ -49,16 +110,29 @@ describe("ThemeContext", () => {
|
|
|
49
110
|
yield act(() => __awaiter(void 0, void 0, void 0, function* () { return result.current.setTheme("dark"); }));
|
|
50
111
|
// This hook shouldn't be affected by it because it's set to the light theme
|
|
51
112
|
expect(result.current.theme).toBe("light");
|
|
113
|
+
expect(result.current.effectiveTheme).toBe("light");
|
|
52
114
|
expect(result.current.tokens).toEqual(expectedLightTokens);
|
|
53
115
|
}));
|
|
54
116
|
it.each([
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
117
|
+
{
|
|
118
|
+
defaultTheme: "light",
|
|
119
|
+
colorScheme: "dark",
|
|
120
|
+
expectedEffectiveTheme: "light",
|
|
121
|
+
expectedTokens: expectedLightTokens,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
defaultTheme: "dark",
|
|
125
|
+
colorScheme: "light",
|
|
126
|
+
expectedEffectiveTheme: "dark",
|
|
127
|
+
expectedTokens: expectedDarkTokens,
|
|
128
|
+
},
|
|
129
|
+
])("provides the dangerouslyOverrideTheme $defaultTheme tokens", ({ defaultTheme, colorScheme, expectedEffectiveTheme, expectedTokens, }) => {
|
|
130
|
+
mockUseColorScheme.mockReturnValue(colorScheme);
|
|
58
131
|
const { result } = renderHook(useAtlantisTheme, {
|
|
59
132
|
wrapper: (props) => (React.createElement(WrapperWithOverride, Object.assign({}, props, { dangerouslyOverrideTheme: defaultTheme }))),
|
|
60
133
|
});
|
|
61
134
|
expect(result.current.theme).toBe(defaultTheme);
|
|
135
|
+
expect(result.current.effectiveTheme).toBe(expectedEffectiveTheme);
|
|
62
136
|
expect(result.current.tokens).toEqual(expectedTokens);
|
|
63
137
|
});
|
|
64
138
|
});
|
|
@@ -54,7 +54,7 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
|
|
|
54
54
|
const [currentPosition, setCurrentPosition] = useState(-1);
|
|
55
55
|
const styles = useStyles();
|
|
56
56
|
const { t } = useAtlantisI18n();
|
|
57
|
-
const {
|
|
57
|
+
const { effectiveTheme, tokens } = useAtlantisTheme();
|
|
58
58
|
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
|
59
59
|
const behavior = computeContentOverlayBehavior({
|
|
60
60
|
fullScreen,
|
|
@@ -179,7 +179,7 @@ export function ContentOverlay({ children, title, accessibilityLabel, fullScreen
|
|
|
179
179
|
return (React.createElement(Backdrop, Object.assign({}, props, { pressBehavior: isCloseableOnOverlayTap ? "close" : "none" })));
|
|
180
180
|
}, [isCloseableOnOverlayTap]);
|
|
181
181
|
return (React.createElement(BottomSheetModal, { ref: bottomSheetModalRef, containerComponent: Platform.OS === "ios" ? Container : undefined, onChange: handleChange, style: sheetStyle, backgroundStyle: backgroundStyle, handleStyle: styles.handleWrapper, handleIndicatorStyle: handleIndicatorStyles, backdropComponent: backdropComponent, snapPoints: snapPoints, enablePanDownToClose: enablePanDownToClose !== null && enablePanDownToClose !== void 0 ? enablePanDownToClose : effectiveIsDraggable, enableContentPanningGesture: enableContentPanningGesture !== null && enableContentPanningGesture !== void 0 ? enableContentPanningGesture : effectiveIsDraggable, enableHandlePanningGesture: effectiveIsDraggable, enableDynamicSizing: !customSnapPoints && behavior.initialHeight === "contentHeight", keyboardBehavior: "interactive", keyboardBlurBehavior: "restore", topInset: topInset, onDismiss: () => onClose === null || onClose === void 0 ? void 0 : onClose() },
|
|
182
|
-
React.createElement(AtlantisThemeContextProvider, { dangerouslyOverrideTheme:
|
|
182
|
+
React.createElement(AtlantisThemeContextProvider, { dangerouslyOverrideTheme: effectiveTheme },
|
|
183
183
|
React.createElement(ContentOverlayKeyboardContext.Provider, { value: scrollEnabled }, scrollEnabled ? (React.createElement(BottomSheetKeyboardAwareScrollView, { ref: scrollViewRef, contentContainerStyle: { paddingBottom: insets.bottom }, keyboardShouldPersistTaps: keyboardShouldPersistTaps ? "handled" : "never", showsVerticalScrollIndicator: false, onScroll: handleOnScroll, stickyHeaderIndices: [0], bottomOffset: KEYBOARD_TOP_PADDING_AUTO_SCROLL },
|
|
184
184
|
renderHeader(),
|
|
185
185
|
React.createElement(View, { testID: "ATL-Overlay-Children" }, children))) : (React.createElement(BottomSheetView, null,
|
package/dist/src/Form/Form.js
CHANGED
|
@@ -96,20 +96,9 @@ function InternalForm({ children, onBeforeSubmit, onSubmit, onSubmitError, onSub
|
|
|
96
96
|
setKeyboardScreenY(windowHeight - event.height);
|
|
97
97
|
}, [windowHeight]);
|
|
98
98
|
const handleKeyboardHide = useCallback(() => {
|
|
99
|
-
var _a;
|
|
100
|
-
(_a = bottomViewRef === null || bottomViewRef === void 0 ? void 0 : bottomViewRef.current) === null || _a === void 0 ? void 0 : _a.measureInWindow(
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
102
|
-
(_x, y, _width, _height) => {
|
|
103
|
-
var _a;
|
|
104
|
-
// This fixes extra whitespace below the form if it was scrolled down while the keyboard was open
|
|
105
|
-
// i.e. a View below the form is higher than the bottom of the window
|
|
106
|
-
if (y < windowHeight) {
|
|
107
|
-
(_a = scrollViewRef === null || scrollViewRef === void 0 ? void 0 : scrollViewRef.current) === null || _a === void 0 ? void 0 : _a.scrollToEnd();
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
99
|
setKeyboardHeight(0);
|
|
111
100
|
setKeyboardScreenY(0);
|
|
112
|
-
}, [
|
|
101
|
+
}, []);
|
|
113
102
|
useEffect(() => {
|
|
114
103
|
const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
|
|
115
104
|
const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import React from "react";
|
|
11
11
|
import { act, fireEvent, render, waitFor } from "@testing-library/react-native";
|
|
12
12
|
import { Alert, Keyboard } from "react-native";
|
|
13
|
+
import { KeyboardEvents } from "react-native-keyboard-controller";
|
|
13
14
|
import { Form, FormBannerMessageType } from ".";
|
|
14
15
|
import { FormSubmitErrorType } from "./types";
|
|
15
16
|
import { atlantisContextDefaultValues } from "../AtlantisContext";
|
|
@@ -35,14 +36,34 @@ const onChangeMock = jest.fn();
|
|
|
35
36
|
const onChangeSelectMock = jest.fn();
|
|
36
37
|
const onChangeSwitchMock = jest.fn();
|
|
37
38
|
const mockScrollTo = jest.fn();
|
|
39
|
+
const mockScrollToEnd = jest.fn();
|
|
38
40
|
const mockScrollToTop = jest.fn();
|
|
41
|
+
const mockMeasureInWindow = jest.fn((callback) => {
|
|
42
|
+
callback(0, 0, 0, 0);
|
|
43
|
+
});
|
|
44
|
+
const mockScrollViewRef = {
|
|
45
|
+
get current() {
|
|
46
|
+
return { scrollTo: mockScrollTo, scrollToEnd: mockScrollToEnd };
|
|
47
|
+
},
|
|
48
|
+
// React assigns the ref while rendering KeyboardAwareScrollView. Keep the
|
|
49
|
+
// test double available to the keyboard-hide callback instead.
|
|
50
|
+
set current(value) {
|
|
51
|
+
void value;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const mockBottomViewRef = {
|
|
55
|
+
get current() {
|
|
56
|
+
return { measureInWindow: mockMeasureInWindow };
|
|
57
|
+
},
|
|
58
|
+
set current(value) {
|
|
59
|
+
void value;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
39
62
|
jest.mock("./hooks/useFormViewRefs", () => ({
|
|
40
63
|
useFormViewRefs: () => {
|
|
41
64
|
return {
|
|
42
|
-
scrollViewRef:
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
bottomViewRef: { current: {} },
|
|
65
|
+
scrollViewRef: mockScrollViewRef,
|
|
66
|
+
bottomViewRef: mockBottomViewRef,
|
|
46
67
|
scrollToTop: mockScrollToTop,
|
|
47
68
|
};
|
|
48
69
|
},
|
|
@@ -321,6 +342,30 @@ describe("Form", () => {
|
|
|
321
342
|
expect(queryByTestId("ATL-FormSafeArea")).toBeNull();
|
|
322
343
|
});
|
|
323
344
|
});
|
|
345
|
+
describe("Keyboard dismissal", () => {
|
|
346
|
+
it("does not imperatively scroll the form to its end", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
347
|
+
var _a;
|
|
348
|
+
const listeners = new Map();
|
|
349
|
+
const addListener = KeyboardEvents.addListener;
|
|
350
|
+
const registerListener = (eventName, listener) => {
|
|
351
|
+
listeners.set(eventName, listener);
|
|
352
|
+
return { remove: jest.fn() };
|
|
353
|
+
};
|
|
354
|
+
addListener
|
|
355
|
+
.mockImplementationOnce(registerListener)
|
|
356
|
+
.mockImplementationOnce(registerListener);
|
|
357
|
+
render(React.createElement(FormTest, { onSubmit: onSubmitMock }));
|
|
358
|
+
const hideListener = (_a = [...listeners.entries()].find(([eventName]) => eventName.endsWith("Hide"))) === null || _a === void 0 ? void 0 : _a[1];
|
|
359
|
+
expect(hideListener).toBeDefined();
|
|
360
|
+
if (!hideListener) {
|
|
361
|
+
throw new Error("Expected a keyboard hide listener");
|
|
362
|
+
}
|
|
363
|
+
yield act(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
364
|
+
hideListener();
|
|
365
|
+
}));
|
|
366
|
+
expect(mockScrollToEnd).not.toHaveBeenCalled();
|
|
367
|
+
}));
|
|
368
|
+
});
|
|
324
369
|
describe("Leaving the form", () => {
|
|
325
370
|
let mockUseConfirmBeforeBack;
|
|
326
371
|
let mockRemoveLocalCache;
|