@jobber/components-native 0.35.0 → 0.36.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.
@@ -0,0 +1,59 @@
1
+ /// <reference types="react" />
2
+ import { XOR } from "ts-xor";
3
+ interface CommonCheckboxProps {
4
+ /**
5
+ * Label to be displayed beside the checkbox
6
+ */
7
+ readonly label?: string;
8
+ /**
9
+ * Default value for when the checkbox is uncontrolled
10
+ */
11
+ readonly defaultChecked?: boolean;
12
+ /**
13
+ * When true, the checkbox is shown as indeterminate
14
+ */
15
+ readonly indeterminate?: boolean;
16
+ /**
17
+ * Checkbox is disabled
18
+ */
19
+ readonly disabled?: boolean;
20
+ /**
21
+ * Assistive Text, shown under label
22
+ */
23
+ readonly assistiveText?: string;
24
+ /**
25
+ * Accessibility Label for the checkbox. Defaults to label
26
+ */
27
+ readonly accessibilityLabel?: string;
28
+ /**
29
+ * Press handler
30
+ */
31
+ onChange?(value: boolean): void;
32
+ /**
33
+ * If the checkbox is checked. This should be set when this is intended as a controlled component
34
+ */
35
+ readonly checked?: boolean;
36
+ /**
37
+ * Name of the checkbox; this is important when using in a form component
38
+ */
39
+ readonly name?: string;
40
+ }
41
+ interface ControlledCheckboxProps extends CommonCheckboxProps {
42
+ /**
43
+ * Press handler
44
+ */
45
+ onChange(value: boolean): void;
46
+ /**
47
+ * If the checkbox is checked. This should be set when this is intended as a controlled component
48
+ */
49
+ readonly checked: boolean;
50
+ }
51
+ interface UncontrolledCheckboxProps extends CommonCheckboxProps {
52
+ /**
53
+ * Name of the checkbox; this is important when using in a form component
54
+ */
55
+ readonly name: string;
56
+ }
57
+ export type CheckboxProps = XOR<ControlledCheckboxProps, UncontrolledCheckboxProps>;
58
+ export declare function Checkbox(props: CheckboxProps): JSX.Element;
59
+ export {};
@@ -0,0 +1,27 @@
1
+ export declare const styles: {
2
+ container: {
3
+ width: string;
4
+ };
5
+ checkBoxContainer: {
6
+ width: string;
7
+ justifyContent: "space-between";
8
+ flexDirection: "row";
9
+ alignItems: "center";
10
+ paddingVertical: number;
11
+ };
12
+ label: {
13
+ flex: number;
14
+ };
15
+ checkbox: {
16
+ borderRadius: number;
17
+ borderWidth: number;
18
+ justifyContent: "flex-end";
19
+ width: number;
20
+ height: number;
21
+ marginLeft: number;
22
+ borderColor: string;
23
+ };
24
+ disabledCheckbox: {
25
+ borderColor: string;
26
+ };
27
+ };
@@ -0,0 +1,22 @@
1
+ /// <reference types="react" />
2
+ import { XOR } from "ts-xor";
3
+ import { CheckboxProps } from "./Checkbox";
4
+ import { CheckboxElement, CheckboxGroupState } from "./types";
5
+ interface CommonCheckboxGroupProps extends Omit<CheckboxProps, "onChange"> {
6
+ /**
7
+ * Checkbox items
8
+ */
9
+ children: CheckboxElement[];
10
+ state?: CheckboxGroupState;
11
+ onChange?(groupChecks: CheckboxGroupState): void;
12
+ }
13
+ interface ControlledCheckboxGroupProps extends CommonCheckboxGroupProps {
14
+ state: CheckboxGroupState;
15
+ onChange(groupChecks: CheckboxGroupState): void;
16
+ }
17
+ interface UncontrolledCheckboxGroupProps extends CommonCheckboxGroupProps {
18
+ name: string;
19
+ }
20
+ export type CheckboxGroupProps = XOR<UncontrolledCheckboxGroupProps, ControlledCheckboxGroupProps>;
21
+ export declare function CheckboxGroup({ children, state, onChange, name, ...rest }: CheckboxGroupProps): JSX.Element;
22
+ export {};
@@ -0,0 +1,12 @@
1
+ export declare const styles: {
2
+ container: {
3
+ width: string;
4
+ justifyContent: "center";
5
+ alignItems: "center";
6
+ paddingHorizontal: number;
7
+ paddingVertical: number;
8
+ };
9
+ nestedCheckboxes: {
10
+ marginLeft: number;
11
+ };
12
+ };
@@ -0,0 +1,9 @@
1
+ import { CheckboxGroupState, CheckboxGroupStateInitializer } from "./types";
2
+ interface UpdateAction {
3
+ type: "Update";
4
+ data: Partial<CheckboxGroupState>;
5
+ }
6
+ type CheckboxGroupAction = UpdateAction;
7
+ export declare function initCheckboxGroupState(childNames: CheckboxGroupStateInitializer): CheckboxGroupState;
8
+ export declare function checkboxGroupReducer(state: CheckboxGroupState, action: CheckboxGroupAction): CheckboxGroupState;
9
+ export {};
@@ -0,0 +1,2 @@
1
+ export { Checkbox } from "./Checkbox";
2
+ export { CheckboxGroup } from "./CheckboxGroup";
@@ -0,0 +1,11 @@
1
+ import { ReactElement } from "react";
2
+ import { Checkbox, CheckboxProps } from "./Checkbox";
3
+ export interface CheckboxGroupChildrenState {
4
+ [key: string]: boolean;
5
+ }
6
+ export interface CheckboxGroupState {
7
+ parentChecked?: boolean;
8
+ childrenChecked: CheckboxGroupChildrenState;
9
+ }
10
+ export type CheckboxGroupStateInitializer = string[];
11
+ export type CheckboxElement = ReactElement<CheckboxProps, typeof Checkbox>;
@@ -7,6 +7,7 @@ export * from "./BottomSheet";
7
7
  export * from "./Button";
8
8
  export * from "./ButtonGroup";
9
9
  export * from "./Card";
10
+ export * from "./Checkbox";
10
11
  export * from "./Chip";
11
12
  export * from "./Content";
12
13
  export * from "./Divider";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -37,6 +37,7 @@
37
37
  "dependencies": {
38
38
  "@jobber/design": "^0.42.0",
39
39
  "@react-native-picker/picker": "^2.4.10",
40
+ "deepmerge": "^4.2.2",
40
41
  "lodash": "^4.17.21",
41
42
  "react-hook-form": "^7.30.0",
42
43
  "react-intl": "^6.4.2",
@@ -74,5 +75,5 @@
74
75
  "react-native": ">=0.69.2",
75
76
  "react-native-modal-datetime-picker": " >=13.0.0"
76
77
  },
77
- "gitHead": "5028c2bd4c97479b567b031432a331e3884afcaa"
78
+ "gitHead": "8c1f0b371ca033af1e54e2dbf7e1e0c2f199be00"
78
79
  }
@@ -0,0 +1,36 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../utils/design";
3
+
4
+ const checkboxDimensions = tokens["space-large"] + tokens["space-smaller"];
5
+
6
+ export const styles = StyleSheet.create({
7
+ container: {
8
+ width: "100%",
9
+ },
10
+
11
+ checkBoxContainer: {
12
+ width: "100%",
13
+ justifyContent: "space-between",
14
+ flexDirection: "row",
15
+ alignItems: "center",
16
+ paddingVertical: tokens["space-small"],
17
+ },
18
+
19
+ label: {
20
+ flex: 1,
21
+ },
22
+
23
+ checkbox: {
24
+ borderRadius: tokens["radius-base"],
25
+ borderWidth: tokens["border-thick"],
26
+ justifyContent: "flex-end",
27
+ width: checkboxDimensions,
28
+ height: checkboxDimensions,
29
+ marginLeft: tokens["space-smaller"],
30
+ borderColor: tokens["color-interactive"],
31
+ },
32
+
33
+ disabledCheckbox: {
34
+ borderColor: tokens["color-disabled"],
35
+ },
36
+ });
@@ -0,0 +1,104 @@
1
+ import React from "react";
2
+ import { fireEvent, render } from "@testing-library/react-native";
3
+ import { Checkbox } from "./Checkbox";
4
+
5
+ const accessibilityLabel = "testA11y";
6
+ const onChange = jest.fn();
7
+ afterEach(() => jest.resetAllMocks());
8
+
9
+ describe("Checkbox", () => {
10
+ it("renders checkbox label", () => {
11
+ const label = "TestLabel";
12
+ const { getByLabelText } = render(
13
+ <Checkbox name="testCheckbox" label={label} />,
14
+ );
15
+ expect(getByLabelText(label)).toBeTruthy();
16
+ });
17
+
18
+ it("renders checkbox with defaultChecked set to true", () => {
19
+ const { getByTestId, getByRole } = render(
20
+ <Checkbox name="testCheckbox" defaultChecked={true} />,
21
+ );
22
+ expect(getByRole("checkbox").props.accessibilityState).toHaveProperty(
23
+ "checked",
24
+ true,
25
+ );
26
+ expect(getByTestId("checkmark")).toBeDefined();
27
+ });
28
+
29
+ it("renders checkbox label with assistive text", () => {
30
+ const label = "TestLabel";
31
+ const assistive = "TestAssistive";
32
+ const { getByLabelText, getByText } = render(
33
+ <Checkbox name="testCheckbox" label={label} assistiveText={assistive} />,
34
+ );
35
+ expect(getByLabelText(label)).toBeTruthy();
36
+ expect(getByText(assistive)).toBeTruthy();
37
+ });
38
+
39
+ it("will call onChange with true when pressed without default checked state set", () => {
40
+ const { getByLabelText } = render(
41
+ <Checkbox
42
+ name="testCheckbox"
43
+ onChange={onChange}
44
+ accessibilityLabel={accessibilityLabel}
45
+ />,
46
+ );
47
+ const checkbox = getByLabelText(accessibilityLabel);
48
+ fireEvent.press(checkbox);
49
+ expect(onChange).toHaveBeenCalledWith(true);
50
+ });
51
+
52
+ it("will call onChange with false when checked checkbox is pressed", () => {
53
+ const { getByLabelText } = render(
54
+ <Checkbox
55
+ name="onChangeTest"
56
+ checked={true}
57
+ onChange={onChange}
58
+ accessibilityLabel={accessibilityLabel}
59
+ />,
60
+ );
61
+ const checkbox = getByLabelText(accessibilityLabel);
62
+ fireEvent.press(checkbox);
63
+ expect(onChange).toHaveBeenCalledWith(false);
64
+ });
65
+ it("will call onChange with true when unchecked checkbox is pressed", () => {
66
+ const { getByLabelText } = render(
67
+ <Checkbox
68
+ name="onChangeTest"
69
+ checked={false}
70
+ onChange={onChange}
71
+ accessibilityLabel={accessibilityLabel}
72
+ />,
73
+ );
74
+ const checkbox = getByLabelText(accessibilityLabel);
75
+ fireEvent.press(checkbox);
76
+ expect(onChange).toHaveBeenCalledWith(true);
77
+ });
78
+
79
+ it("will not call onChange when disabled", () => {
80
+ const { getByLabelText } = render(
81
+ <Checkbox
82
+ name="testCheckbox"
83
+ onChange={onChange}
84
+ disabled={true}
85
+ accessibilityLabel={accessibilityLabel}
86
+ />,
87
+ );
88
+ const checkbox = getByLabelText(accessibilityLabel);
89
+ fireEvent.press(checkbox);
90
+ expect(onChange).not.toHaveBeenCalled();
91
+ });
92
+
93
+ it("can be set as indeterminate", () => {
94
+ const { getByTestId } = render(
95
+ <Checkbox
96
+ name="testCheckbox"
97
+ onChange={onChange}
98
+ indeterminate={true}
99
+ accessibilityLabel={accessibilityLabel}
100
+ />,
101
+ );
102
+ expect(getByTestId("minus2")).toBeDefined();
103
+ });
104
+ });
@@ -0,0 +1,192 @@
1
+ import React from "react";
2
+ import { ColorValue, Pressable, View } from "react-native";
3
+ import { XOR } from "ts-xor";
4
+ import { tokens } from "@jobber/design/foundation";
5
+ import { styles } from "./Checkbox.style";
6
+ import { Text } from "../Text";
7
+ import { Icon } from "../Icon";
8
+ import { FormField } from "../FormField";
9
+
10
+ interface CommonCheckboxProps {
11
+ /**
12
+ * Label to be displayed beside the checkbox
13
+ */
14
+ readonly label?: string;
15
+
16
+ /**
17
+ * Default value for when the checkbox is uncontrolled
18
+ */
19
+ readonly defaultChecked?: boolean;
20
+
21
+ /**
22
+ * When true, the checkbox is shown as indeterminate
23
+ */
24
+ readonly indeterminate?: boolean;
25
+
26
+ /**
27
+ * Checkbox is disabled
28
+ */
29
+ readonly disabled?: boolean;
30
+
31
+ /**
32
+ * Assistive Text, shown under label
33
+ */
34
+ readonly assistiveText?: string;
35
+
36
+ /**
37
+ * Accessibility Label for the checkbox. Defaults to label
38
+ */
39
+ readonly accessibilityLabel?: string;
40
+
41
+ /**
42
+ * Press handler
43
+ */
44
+ onChange?(value: boolean): void;
45
+
46
+ /**
47
+ * If the checkbox is checked. This should be set when this is intended as a controlled component
48
+ */
49
+ readonly checked?: boolean;
50
+
51
+ /**
52
+ * Name of the checkbox; this is important when using in a form component
53
+ */
54
+ readonly name?: string;
55
+ }
56
+
57
+ interface ControlledCheckboxProps extends CommonCheckboxProps {
58
+ /**
59
+ * Press handler
60
+ */
61
+ onChange(value: boolean): void;
62
+
63
+ /**
64
+ * If the checkbox is checked. This should be set when this is intended as a controlled component
65
+ */
66
+ readonly checked: boolean;
67
+ }
68
+
69
+ interface UncontrolledCheckboxProps extends CommonCheckboxProps {
70
+ /**
71
+ * Name of the checkbox; this is important when using in a form component
72
+ */
73
+ readonly name: string;
74
+ }
75
+
76
+ export type CheckboxProps = XOR<
77
+ ControlledCheckboxProps,
78
+ UncontrolledCheckboxProps
79
+ >;
80
+
81
+ export function Checkbox(props: CheckboxProps): JSX.Element {
82
+ if (props.checked !== undefined && props.onChange !== undefined) {
83
+ return <CheckboxInternal {...props} />;
84
+ } else if (props.name) {
85
+ return (
86
+ <FormField<boolean> name={props.name} defaultValue={props.defaultChecked}>
87
+ {field => {
88
+ return (
89
+ <CheckboxInternal
90
+ {...props}
91
+ checked={field.value}
92
+ onChange={newValue => {
93
+ props?.onChange?.(newValue);
94
+ field.onChange(newValue);
95
+ }}
96
+ />
97
+ );
98
+ }}
99
+ </FormField>
100
+ );
101
+ } else {
102
+ throw new Error("Checkbox was given invalid props");
103
+ }
104
+ }
105
+
106
+ function CheckboxInternal({
107
+ label,
108
+ checked,
109
+ defaultChecked,
110
+ indeterminate = false,
111
+ disabled = false,
112
+ assistiveText,
113
+ onChange,
114
+ accessibilityLabel,
115
+ }: CheckboxProps): JSX.Element {
116
+ const internalValue = checked ?? !!defaultChecked;
117
+
118
+ const iconName = indeterminate ? "minus2" : "checkmark";
119
+ const textVariation = disabled ? "disabled" : "subdued";
120
+ const a11yStateChecked = indeterminate ? "mixed" : internalValue;
121
+
122
+ const backgroundColor = getBackgroundColor(
123
+ internalValue,
124
+ disabled,
125
+ indeterminate,
126
+ );
127
+
128
+ return (
129
+ <Pressable
130
+ accessibilityRole="checkbox"
131
+ accessibilityLabel={accessibilityLabel || label}
132
+ accessibilityState={{
133
+ disabled: disabled,
134
+ checked: a11yStateChecked,
135
+ }}
136
+ disabled={disabled}
137
+ style={({ pressed }) => [
138
+ {
139
+ opacity: pressed ? 0.2 : 1,
140
+ },
141
+ styles.container,
142
+ ]}
143
+ onPress={() => {
144
+ onChange?.(!internalValue);
145
+ }}
146
+ >
147
+ <View style={styles.checkBoxContainer}>
148
+ {label && (
149
+ <View style={styles.label}>
150
+ <Text variation={textVariation} align="start">
151
+ {label}
152
+ </Text>
153
+ </View>
154
+ )}
155
+ <View
156
+ style={[
157
+ styles.checkbox,
158
+ disabled && styles.disabledCheckbox,
159
+ {
160
+ backgroundColor,
161
+ },
162
+ ]}
163
+ >
164
+ {(internalValue || indeterminate) && (
165
+ <Icon name={iconName} color="white" />
166
+ )}
167
+ </View>
168
+ </View>
169
+ {assistiveText && (
170
+ <Text level="textSupporting" align="start" variation={textVariation}>
171
+ {assistiveText}
172
+ </Text>
173
+ )}
174
+ </Pressable>
175
+ );
176
+ }
177
+
178
+ function getBackgroundColor(
179
+ checked: boolean,
180
+ disabled: boolean,
181
+ indeterminate: boolean,
182
+ ): ColorValue {
183
+ if (checked || indeterminate) {
184
+ if (disabled) {
185
+ return tokens["color-disabled"];
186
+ } else {
187
+ return tokens["color-interactive"];
188
+ }
189
+ } else {
190
+ return tokens["color-overlay--dimmed"];
191
+ }
192
+ }
@@ -0,0 +1,15 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { tokens } from "../utils/design";
3
+
4
+ export const styles = StyleSheet.create({
5
+ container: {
6
+ width: "100%",
7
+ justifyContent: "center",
8
+ alignItems: "center",
9
+ paddingHorizontal: tokens["space-base"],
10
+ paddingVertical: tokens["space-small"],
11
+ },
12
+ nestedCheckboxes: {
13
+ marginLeft: tokens["space-large"],
14
+ },
15
+ });