@jobber/components-native 0.22.0 → 0.24.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.
Files changed (42) hide show
  1. package/dist/src/Switch/Switch.js +16 -0
  2. package/dist/src/Switch/Switch.styles.js +20 -0
  3. package/dist/src/Switch/components/BaseSwitch/BaseSwitch.js +54 -0
  4. package/dist/src/Switch/components/BaseSwitch/index.js +1 -0
  5. package/dist/src/Switch/index.js +1 -0
  6. package/dist/src/Toast/Toast.js +64 -0
  7. package/dist/src/Toast/Toast.styles.js +30 -0
  8. package/dist/src/Toast/index.js +1 -0
  9. package/dist/src/Toast/messages.js +13 -0
  10. package/dist/src/index.js +2 -0
  11. package/dist/src/utils/test/MockSafeAreaProvider.js +10 -0
  12. package/dist/src/utils/test/index.js +1 -0
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/types/src/Switch/Switch.d.ts +16 -0
  15. package/dist/types/src/Switch/Switch.styles.d.ts +18 -0
  16. package/dist/types/src/Switch/components/BaseSwitch/BaseSwitch.d.ts +28 -0
  17. package/dist/types/src/Switch/components/BaseSwitch/index.d.ts +2 -0
  18. package/dist/types/src/Switch/index.d.ts +1 -0
  19. package/dist/types/src/Toast/Toast.d.ts +28 -0
  20. package/dist/types/src/Toast/Toast.styles.d.ts +31 -0
  21. package/dist/types/src/Toast/index.d.ts +2 -0
  22. package/dist/types/src/Toast/messages.d.ts +12 -0
  23. package/dist/types/src/index.d.ts +2 -0
  24. package/dist/types/src/utils/test/MockSafeAreaProvider.d.ts +9 -0
  25. package/dist/types/src/utils/test/index.d.ts +1 -0
  26. package/package.json +4 -2
  27. package/src/Switch/Switch.styles.ts +21 -0
  28. package/src/Switch/Switch.test.tsx +98 -0
  29. package/src/Switch/Switch.tsx +58 -0
  30. package/src/Switch/components/BaseSwitch/BaseSwitch.test.tsx +62 -0
  31. package/src/Switch/components/BaseSwitch/BaseSwitch.tsx +107 -0
  32. package/src/Switch/components/BaseSwitch/__snapshots__/BaseSwitch.test.tsx.snap +217 -0
  33. package/src/Switch/components/BaseSwitch/index.ts +2 -0
  34. package/src/Switch/index.ts +1 -0
  35. package/src/Toast/Toast.styles.ts +31 -0
  36. package/src/Toast/Toast.test.tsx +74 -0
  37. package/src/Toast/Toast.tsx +128 -0
  38. package/src/Toast/index.ts +2 -0
  39. package/src/Toast/messages.ts +14 -0
  40. package/src/index.ts +2 -0
  41. package/src/utils/test/MockSafeAreaProvider.tsx +32 -0
  42. package/src/utils/test/index.ts +1 -0
@@ -0,0 +1,217 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`renders a Switch with defaultValue false 1`] = `
4
+ <RCTSwitch
5
+ accessibilityRole="switch"
6
+ accessibilityState={
7
+ {
8
+ "checked": false,
9
+ "disabled": false,
10
+ }
11
+ }
12
+ collapsable={false}
13
+ disabled={false}
14
+ handlerTag={4}
15
+ handlerType="NativeViewGestureHandler"
16
+ onChange={[Function]}
17
+ onGestureHandlerEvent={[Function]}
18
+ onGestureHandlerStateChange={[Function]}
19
+ onResponderTerminationRequest={[Function]}
20
+ onStartShouldSetResponder={[Function]}
21
+ onTintColor="rgb(125, 176, 14)"
22
+ style={
23
+ [
24
+ {
25
+ "height": 31,
26
+ "width": 51,
27
+ },
28
+ {
29
+ "backgroundColor": "rgb(244, 244, 244)",
30
+ "borderRadius": 16,
31
+ },
32
+ ]
33
+ }
34
+ tintColor="rgb(244, 244, 244)"
35
+ value={false}
36
+ />
37
+ `;
38
+
39
+ exports[`renders a Switch with defaultValue true 1`] = `
40
+ <RCTSwitch
41
+ accessibilityRole="switch"
42
+ accessibilityState={
43
+ {
44
+ "checked": true,
45
+ "disabled": false,
46
+ }
47
+ }
48
+ collapsable={false}
49
+ disabled={false}
50
+ handlerTag={2}
51
+ handlerType="NativeViewGestureHandler"
52
+ onChange={[Function]}
53
+ onGestureHandlerEvent={[Function]}
54
+ onGestureHandlerStateChange={[Function]}
55
+ onResponderTerminationRequest={[Function]}
56
+ onStartShouldSetResponder={[Function]}
57
+ onTintColor="rgb(125, 176, 14)"
58
+ style={
59
+ [
60
+ {
61
+ "height": 31,
62
+ "width": 51,
63
+ },
64
+ {
65
+ "backgroundColor": "rgb(244, 244, 244)",
66
+ "borderRadius": 16,
67
+ },
68
+ ]
69
+ }
70
+ tintColor="rgb(244, 244, 244)"
71
+ value={true}
72
+ />
73
+ `;
74
+
75
+ exports[`renders a Switch with value false 1`] = `
76
+ <RCTSwitch
77
+ accessibilityRole="switch"
78
+ accessibilityState={
79
+ {
80
+ "checked": false,
81
+ "disabled": false,
82
+ }
83
+ }
84
+ collapsable={false}
85
+ disabled={false}
86
+ handlerTag={3}
87
+ handlerType="NativeViewGestureHandler"
88
+ onChange={[Function]}
89
+ onGestureHandlerEvent={[Function]}
90
+ onGestureHandlerStateChange={[Function]}
91
+ onResponderTerminationRequest={[Function]}
92
+ onStartShouldSetResponder={[Function]}
93
+ onTintColor="rgb(125, 176, 14)"
94
+ style={
95
+ [
96
+ {
97
+ "height": 31,
98
+ "width": 51,
99
+ },
100
+ {
101
+ "backgroundColor": "rgb(244, 244, 244)",
102
+ "borderRadius": 16,
103
+ },
104
+ ]
105
+ }
106
+ tintColor="rgb(244, 244, 244)"
107
+ value={false}
108
+ />
109
+ `;
110
+
111
+ exports[`renders a Switch with value true 1`] = `
112
+ <RCTSwitch
113
+ accessibilityRole="switch"
114
+ accessibilityState={
115
+ {
116
+ "checked": true,
117
+ "disabled": false,
118
+ }
119
+ }
120
+ collapsable={false}
121
+ disabled={false}
122
+ handlerTag={1}
123
+ handlerType="NativeViewGestureHandler"
124
+ onChange={[Function]}
125
+ onGestureHandlerEvent={[Function]}
126
+ onGestureHandlerStateChange={[Function]}
127
+ onResponderTerminationRequest={[Function]}
128
+ onStartShouldSetResponder={[Function]}
129
+ onTintColor="rgb(125, 176, 14)"
130
+ style={
131
+ [
132
+ {
133
+ "height": 31,
134
+ "width": 51,
135
+ },
136
+ {
137
+ "backgroundColor": "rgb(244, 244, 244)",
138
+ "borderRadius": 16,
139
+ },
140
+ ]
141
+ }
142
+ tintColor="rgb(244, 244, 244)"
143
+ value={true}
144
+ />
145
+ `;
146
+
147
+ exports[`renders a disabled Switch with value false 1`] = `
148
+ <RCTSwitch
149
+ accessibilityRole="switch"
150
+ accessibilityState={
151
+ {
152
+ "checked": false,
153
+ "disabled": true,
154
+ }
155
+ }
156
+ collapsable={false}
157
+ disabled={true}
158
+ handlerTag={6}
159
+ handlerType="NativeViewGestureHandler"
160
+ onChange={[Function]}
161
+ onGestureHandlerEvent={[Function]}
162
+ onGestureHandlerStateChange={[Function]}
163
+ onResponderTerminationRequest={[Function]}
164
+ onStartShouldSetResponder={[Function]}
165
+ onTintColor="rgb(125, 176, 14)"
166
+ style={
167
+ [
168
+ {
169
+ "height": 31,
170
+ "width": 51,
171
+ },
172
+ {
173
+ "backgroundColor": "rgb(244, 244, 244)",
174
+ "borderRadius": 16,
175
+ },
176
+ ]
177
+ }
178
+ tintColor="rgb(244, 244, 244)"
179
+ value={false}
180
+ />
181
+ `;
182
+
183
+ exports[`renders a disabled Switch with value true 1`] = `
184
+ <RCTSwitch
185
+ accessibilityRole="switch"
186
+ accessibilityState={
187
+ {
188
+ "checked": true,
189
+ "disabled": true,
190
+ }
191
+ }
192
+ collapsable={false}
193
+ disabled={true}
194
+ handlerTag={5}
195
+ handlerType="NativeViewGestureHandler"
196
+ onChange={[Function]}
197
+ onGestureHandlerEvent={[Function]}
198
+ onGestureHandlerStateChange={[Function]}
199
+ onResponderTerminationRequest={[Function]}
200
+ onStartShouldSetResponder={[Function]}
201
+ onTintColor="rgb(125, 176, 14)"
202
+ style={
203
+ [
204
+ {
205
+ "height": 31,
206
+ "width": 51,
207
+ },
208
+ {
209
+ "backgroundColor": "rgb(244, 244, 244)",
210
+ "borderRadius": 16,
211
+ },
212
+ ]
213
+ }
214
+ tintColor="rgb(244, 244, 244)"
215
+ value={true}
216
+ />
217
+ `;
@@ -0,0 +1,2 @@
1
+ export { BaseSwitch } from "./BaseSwitch";
2
+ export type { BaseSwitchProps } from "./BaseSwitch";
@@ -0,0 +1 @@
1
+ export { Switch } from "./Switch";
@@ -0,0 +1,31 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../utils/design";
3
+
4
+ export const styles = StyleSheet.create({
5
+ container: {
6
+ flexDirection: "row",
7
+ paddingHorizontal: tokens["space-base"],
8
+ paddingBottom: tokens["space-base"],
9
+ },
10
+ toast: {
11
+ flexDirection: "row",
12
+ justifyContent: "space-between",
13
+ width: "100%",
14
+ maxWidth: 540,
15
+ paddingVertical: tokens["space-small"],
16
+ backgroundColor: tokens["color-blue"],
17
+ borderRadius: 6,
18
+ shadowOffset: { width: 0, height: 0 },
19
+ shadowOpacity: 0.1,
20
+ shadowRadius: 6,
21
+ },
22
+ toastMessage: {
23
+ justifyContent: "center",
24
+ paddingLeft: tokens["space-base"],
25
+ paddingVertical: tokens["space-small"],
26
+ flexShrink: 1,
27
+ },
28
+ toastIcon: {
29
+ justifyContent: "center",
30
+ },
31
+ });
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+ import {
3
+ fireEvent,
4
+ render,
5
+ screen,
6
+ waitFor,
7
+ } from "@testing-library/react-native";
8
+ import { AccessibilityInfo } from "react-native";
9
+ import { Toast, showToast } from ".";
10
+ import { Button } from "../Button";
11
+ import { MockSafeAreaProvider } from "../utils/test";
12
+
13
+ interface ToastSetupProps {
14
+ bottomOffset?: number;
15
+ message?: string;
16
+ }
17
+
18
+ async function expectToastWithMessage(message: string): Promise<void> {
19
+ expect(await screen.findByText(message)).toBeDefined();
20
+
21
+ await waitFor(() => {
22
+ // We also need to assert the accessibility info because otherwise
23
+ // the test will 'overflow' and cause other tests to fail.
24
+ expect(AccessibilityInfo.announceForAccessibility).toHaveBeenCalledWith(
25
+ message,
26
+ );
27
+
28
+ jest.mocked(AccessibilityInfo.announceForAccessibility).mockClear();
29
+ });
30
+ }
31
+
32
+ function setup({ bottomOffset, message = "Notify message" }: ToastSetupProps) {
33
+ return render(
34
+ <MockSafeAreaProvider>
35
+ <Button
36
+ label="Press me"
37
+ accessibilityLabel="Button label"
38
+ onPress={() => showToast({ message, bottomTabsVisible: false })}
39
+ />
40
+ <Toast bottomOffset={bottomOffset} />
41
+ </MockSafeAreaProvider>,
42
+ );
43
+ }
44
+
45
+ describe("Toast", () => {
46
+ it("shows the toast on trigger", async () => {
47
+ const { getByLabelText } = setup({ bottomOffset: 20 });
48
+
49
+ const toastTriggerButton = getByLabelText("Button label");
50
+ fireEvent.press(toastTriggerButton);
51
+
52
+ await expectToastWithMessage("Notify message");
53
+ });
54
+
55
+ describe("When the message length exceed the defined maximum", () => {
56
+ const over60CharactersMsg =
57
+ "really really really really really really really really really really really really really really really really really really really long message";
58
+
59
+ it("should log a warning that the message length is too long", async () => {
60
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
61
+ const { getByLabelText } = setup({ message: over60CharactersMsg });
62
+
63
+ const toastTriggerButton = getByLabelText("Button label");
64
+ fireEvent.press(toastTriggerButton);
65
+
66
+ await expectToastWithMessage(over60CharactersMsg);
67
+
68
+ expect(consoleSpy).toHaveBeenCalledWith(
69
+ "Jobber Design Warning: Message length limit exceeded, your current length is 145, the maximum allowed is 60. please talk with your designer",
70
+ );
71
+ consoleSpy.mockRestore();
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,128 @@
1
+ import React from "react";
2
+ import Toast, {
3
+ ToastConfig,
4
+ ToastConfigParams,
5
+ ToastPosition,
6
+ } from "react-native-toast-message";
7
+ import { AccessibilityInfo, TouchableOpacity, View } from "react-native";
8
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
9
+ import { useIntl } from "react-intl";
10
+ import { styles } from "./Toast.styles";
11
+ import { messages } from "./messages";
12
+ import { tokens } from "../utils/design";
13
+ import { Text } from "../Text";
14
+ import { IconButton } from "../IconButton";
15
+
16
+ const MAX_TOAST_MESSAGE_LENGTH = 60;
17
+ const ANNOUNCEMENT_DELAY = 100;
18
+
19
+ function DefaultToast({ text1 }: ToastConfigParams<string>): JSX.Element {
20
+ const { bottom } = useSafeAreaInsets();
21
+ const { formatMessage } = useIntl();
22
+ const toastContainerStyles = [styles.container, { paddingBottom: bottom }];
23
+ return (
24
+ <View style={toastContainerStyles}>
25
+ <View style={styles.toast}>
26
+ <TouchableOpacity
27
+ style={styles.toastMessage}
28
+ accessibilityRole="alert"
29
+ accessibilityLabel={formatMessage(messages.toastNotificationLabel)}
30
+ >
31
+ <Text reverseTheme>{text1}</Text>
32
+ </TouchableOpacity>
33
+ <View style={styles.toastIcon}>
34
+ <IconButton
35
+ onPress={Toast.hide}
36
+ name="remove"
37
+ customColor={tokens["color-greyBlue--light"]}
38
+ accessibilityLabel={formatMessage(messages.dismissA11yLabel)}
39
+ />
40
+ </View>
41
+ </View>
42
+ </View>
43
+ );
44
+ }
45
+
46
+ const toastConfig = {
47
+ default: DefaultToast,
48
+ } as ToastConfig;
49
+
50
+ export interface JobberToastProps {
51
+ /**
52
+ * Offset from the bottom of the screen in px.
53
+ * Has effect only when the position is "bottom".
54
+ * @default 40
55
+ */
56
+ bottomOffset?: number;
57
+ }
58
+
59
+ export function JobberToast({ bottomOffset }: JobberToastProps): JSX.Element {
60
+ return <Toast bottomOffset={bottomOffset} config={toastConfig} />;
61
+ }
62
+
63
+ export interface ShowToastParams {
64
+ /**
65
+ * Message to be displayed in the toast
66
+ */
67
+ readonly message: string;
68
+
69
+ /**
70
+ * Indicates if the bottom tabs are being shown
71
+ * It is equired to define the display position of the toast.
72
+ */
73
+ readonly bottomTabsVisible: boolean;
74
+
75
+ /**
76
+ * Where the toast will be positioned
77
+ * @default "bottom"
78
+ */
79
+ readonly position?: ToastPosition;
80
+ }
81
+
82
+ export function showToast({
83
+ message,
84
+ bottomTabsVisible,
85
+ position = "bottom",
86
+ }: ShowToastParams): void {
87
+ const bottomOffset = getBottomTabsOffset(bottomTabsVisible);
88
+ const maxDuration = 10000;
89
+ const minDuration = 5000;
90
+
91
+ let visibilityTime = message.length * 200;
92
+
93
+ if (visibilityTime > maxDuration) {
94
+ visibilityTime = maxDuration;
95
+ } else if (visibilityTime < minDuration) {
96
+ visibilityTime = minDuration;
97
+ }
98
+
99
+ if (message.length > MAX_TOAST_MESSAGE_LENGTH) {
100
+ console.warn(
101
+ `Jobber Design Warning: Message length limit exceeded, your current length is ${message.length}, the maximum allowed is ${MAX_TOAST_MESSAGE_LENGTH}. please talk with your designer`,
102
+ );
103
+ }
104
+
105
+ Toast.show({
106
+ type: "default",
107
+ text1: message,
108
+ visibilityTime,
109
+ bottomOffset,
110
+ position,
111
+ onShow: () => {
112
+ setTimeout(() => {
113
+ AccessibilityInfo.announceForAccessibility(message || "");
114
+ }, ANNOUNCEMENT_DELAY);
115
+ },
116
+ });
117
+ }
118
+
119
+ function getBottomTabsOffset(bottomTabsVisible: boolean) {
120
+ const navBarHeight = tokens["space-largest"] + tokens["space-small"];
121
+ const navBarBorderTopWidth = tokens["space-minuscule"];
122
+
123
+ if (bottomTabsVisible) {
124
+ return navBarHeight + navBarBorderTopWidth + tokens["space-base"];
125
+ }
126
+
127
+ return tokens["space-base"];
128
+ }
@@ -0,0 +1,2 @@
1
+ export { JobberToast as Toast, showToast } from "./Toast";
2
+ export type { ShowToastParams } from "./Toast";
@@ -0,0 +1,14 @@
1
+ import { defineMessages } from "react-intl";
2
+
3
+ export const messages = defineMessages({
4
+ toastNotificationLabel: {
5
+ id: "toastNotificationLabel",
6
+ defaultMessage: "Toast Notification",
7
+ description: "Accessibility label for the Toast",
8
+ },
9
+ dismissA11yLabel: {
10
+ id: "dismissA11yLabel",
11
+ defaultMessage: "Dismiss toast notification",
12
+ description: "Accessibility label to dismiss the toast when pressing X",
13
+ },
14
+ });
package/src/index.ts CHANGED
@@ -18,5 +18,7 @@ export * from "./InputPressable";
18
18
  export * from "./InputText";
19
19
  export * from "./ProgressBar";
20
20
  export * from "./StatusLabel";
21
+ export * from "./Switch";
21
22
  export * from "./Text";
23
+ export * from "./Toast";
22
24
  export * from "./Typography";
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import {
3
+ EdgeInsets,
4
+ Rect,
5
+ SafeAreaProvider,
6
+ } from "react-native-safe-area-context";
7
+
8
+ interface MockSafeAreaProviderProps {
9
+ children: React.ReactNode;
10
+ frame?: Rect;
11
+ insets?: EdgeInsets;
12
+ }
13
+
14
+ export function MockSafeAreaProvider({
15
+ frame,
16
+ insets,
17
+ children,
18
+ }: MockSafeAreaProviderProps): JSX.Element {
19
+ const initialFrame: Rect = { x: 0, y: 0, width: 0, height: 0 };
20
+ const initialInsets: EdgeInsets = { bottom: 50, top: 50, left: 0, right: 0 };
21
+
22
+ return (
23
+ <SafeAreaProvider
24
+ initialMetrics={{
25
+ frame: frame || initialFrame,
26
+ insets: insets || initialInsets,
27
+ }}
28
+ >
29
+ {children}
30
+ </SafeAreaProvider>
31
+ );
32
+ }
@@ -0,0 +1 @@
1
+ export { MockSafeAreaProvider } from "./MockSafeAreaProvider";