@jobber/components-native 0.19.0 → 0.20.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.
- package/dist/src/InputPressable/InputPressable.js +28 -0
- package/dist/src/InputPressable/InputPressable.style.js +23 -0
- package/dist/src/InputPressable/index.js +1 -0
- package/dist/src/index.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/src/InputPressable/InputPressable.d.ts +71 -0
- package/dist/types/src/InputPressable/InputPressable.style.d.ts +18 -0
- package/dist/types/src/InputPressable/index.d.ts +2 -0
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/InputPressable/InputPressable.style.ts +29 -0
- package/src/InputPressable/InputPressable.test.tsx +144 -0
- package/src/InputPressable/InputPressable.tsx +161 -0
- package/src/InputPressable/index.ts +2 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { Ref } from "react";
|
|
2
|
+
import { IconNames } from "@jobber/design";
|
|
3
|
+
import { FieldError } from "react-hook-form";
|
|
4
|
+
import { Text as NativeText } from "react-native";
|
|
5
|
+
import { Clearable } from "../InputFieldWrapper";
|
|
6
|
+
interface InputPressableProps {
|
|
7
|
+
/**
|
|
8
|
+
* Current value of the component
|
|
9
|
+
*/
|
|
10
|
+
readonly value?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Placeholder item shown until a selection is made
|
|
13
|
+
*/
|
|
14
|
+
readonly placeholder?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Disables input selection
|
|
17
|
+
*/
|
|
18
|
+
readonly disabled?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Indicates if there is an validation error
|
|
21
|
+
*/
|
|
22
|
+
readonly error?: FieldError;
|
|
23
|
+
/**
|
|
24
|
+
* Indicates the current selection is invalid
|
|
25
|
+
*/
|
|
26
|
+
readonly invalid?: boolean | string;
|
|
27
|
+
/**
|
|
28
|
+
* Callback that is called when the text input is focused
|
|
29
|
+
* @param event
|
|
30
|
+
*/
|
|
31
|
+
readonly onPress?: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* VoiceOver will read this string when a user selects the element
|
|
34
|
+
*/
|
|
35
|
+
readonly accessibilityLabel?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Helps users understand what will happen when they perform an action
|
|
38
|
+
*/
|
|
39
|
+
readonly accessibilityHint?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Symbol to display before the text input
|
|
42
|
+
*/
|
|
43
|
+
readonly prefix?: {
|
|
44
|
+
icon?: IconNames;
|
|
45
|
+
label?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Symbol to display after the text input
|
|
49
|
+
*/
|
|
50
|
+
readonly suffix?: {
|
|
51
|
+
icon?: IconNames;
|
|
52
|
+
label?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Add a clear action on the input that clears the value.
|
|
56
|
+
*
|
|
57
|
+
* Since the input value isn't editable (i.e. `InputDateTime`) you can
|
|
58
|
+
* set it to `always`. If you set it to `always` you must also provide an
|
|
59
|
+
* onClear to clear the input's value
|
|
60
|
+
*/
|
|
61
|
+
readonly clearable?: Clearable;
|
|
62
|
+
/**
|
|
63
|
+
* Callback called when the user clicks the ClearAction button. Should clear
|
|
64
|
+
* the value passed. To disallow clearing set the clearable prop to never
|
|
65
|
+
*/
|
|
66
|
+
readonly onClear?: () => void;
|
|
67
|
+
}
|
|
68
|
+
export type InputPressableRef = NativeText;
|
|
69
|
+
export declare const InputPressable: React.ForwardRefExoticComponent<InputPressableProps & React.RefAttributes<NativeText>>;
|
|
70
|
+
export declare function InputPressableInternal({ value, placeholder, disabled, invalid, error, onPress, accessibilityLabel, accessibilityHint, prefix, suffix, clearable, onClear, }: InputPressableProps, ref: Ref<InputPressableRef>): JSX.Element;
|
|
71
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const styles: {
|
|
2
|
+
pressable: {
|
|
3
|
+
flex: number;
|
|
4
|
+
};
|
|
5
|
+
inputPressableStyles: {
|
|
6
|
+
paddingTop: number;
|
|
7
|
+
lineHeight: number | undefined;
|
|
8
|
+
};
|
|
9
|
+
inputEmpty: {
|
|
10
|
+
paddingTop: number;
|
|
11
|
+
};
|
|
12
|
+
inputDisabled: {
|
|
13
|
+
color: import("react-native").ColorValue | undefined;
|
|
14
|
+
};
|
|
15
|
+
inputInvalid: {
|
|
16
|
+
borderColor: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"module": "dist/src/index.js",
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"react": "^18",
|
|
50
50
|
"react-native": ">=0.69.2"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "d9b821d3c4b8557d631813a0199c3d0946d192b9"
|
|
53
53
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { JobberStyle } from "@jobber/design/foundation";
|
|
2
|
+
import { StyleSheet } from "react-native";
|
|
3
|
+
import { typographyStyles } from "../Typography/Typography.style";
|
|
4
|
+
|
|
5
|
+
const miniLabelFontSize = typographyStyles.smallSize.fontSize || 0;
|
|
6
|
+
const miniLabelPadding = JobberStyle["space-small"];
|
|
7
|
+
|
|
8
|
+
export const styles = StyleSheet.create({
|
|
9
|
+
pressable: {
|
|
10
|
+
flex: 1,
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
inputPressableStyles: {
|
|
14
|
+
paddingTop: miniLabelPadding + miniLabelFontSize,
|
|
15
|
+
lineHeight: typographyStyles.defaultSize.lineHeight,
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
inputEmpty: {
|
|
19
|
+
paddingTop: 0,
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
inputDisabled: {
|
|
23
|
+
color: typographyStyles.disabled.color,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
inputInvalid: {
|
|
27
|
+
borderColor: JobberStyle["color-critical"],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fireEvent, render } from "@testing-library/react-native";
|
|
3
|
+
import { InputPressable } from ".";
|
|
4
|
+
import { InputFieldWrapperProps } from "../InputFieldWrapper";
|
|
5
|
+
|
|
6
|
+
const MockInputFieldWrapper = jest.fn();
|
|
7
|
+
jest.mock("../InputFieldWrapper", () => ({
|
|
8
|
+
...jest.requireActual("../InputFieldWrapper"),
|
|
9
|
+
InputFieldWrapper: function Mock(props: InputFieldWrapperProps) {
|
|
10
|
+
MockInputFieldWrapper(props);
|
|
11
|
+
return jest.requireActual("../InputFieldWrapper").InputFieldWrapper(props);
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe("InputPressable", () => {
|
|
16
|
+
describe("InputFieldWrapper gets the expected props", () => {
|
|
17
|
+
it("renders an invalid InputPressable", () => {
|
|
18
|
+
const props = { invalid: true };
|
|
19
|
+
render(<InputPressable {...props} />);
|
|
20
|
+
expect(MockInputFieldWrapper).toHaveBeenCalledWith(
|
|
21
|
+
expect.objectContaining(props),
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("renders an invalid InputPressable with text", () => {
|
|
26
|
+
const props = { invalid: "this test is invalid" };
|
|
27
|
+
render(<InputPressable {...props} />);
|
|
28
|
+
expect(MockInputFieldWrapper).toHaveBeenCalledWith(
|
|
29
|
+
expect.objectContaining(props),
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("renders a valid InputText with empty string", () => {
|
|
34
|
+
const props = { invalid: "" };
|
|
35
|
+
render(<InputPressable {...props} />);
|
|
36
|
+
expect(MockInputFieldWrapper).toHaveBeenCalledWith(
|
|
37
|
+
expect.objectContaining(props),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
it("renders a disabled InputPressable", () => {
|
|
41
|
+
const props = { disabled: true };
|
|
42
|
+
render(<InputPressable {...props} />);
|
|
43
|
+
expect(MockInputFieldWrapper).toHaveBeenCalledWith(
|
|
44
|
+
expect.objectContaining(props),
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("renders an InputPressable with a placeholder", () => {
|
|
49
|
+
const props = { placeholder: "test placeholder" };
|
|
50
|
+
render(<InputPressable {...props} />);
|
|
51
|
+
expect(MockInputFieldWrapper).toHaveBeenCalledWith(
|
|
52
|
+
expect.objectContaining(props),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("renders an InputPressable with a value", () => {
|
|
58
|
+
const value = "test value";
|
|
59
|
+
const { getByText } = render(<InputPressable value={value} />);
|
|
60
|
+
expect(getByText(value, { includeHiddenElements: true })).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("renders a prefix label when specified", () => {
|
|
64
|
+
const label = "test label";
|
|
65
|
+
const { getByText } = render(
|
|
66
|
+
<InputPressable prefix={{ label }} value="hey" />,
|
|
67
|
+
);
|
|
68
|
+
expect(getByText(label)).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renders a prefix icon when specified", () => {
|
|
72
|
+
const { getByTestId } = render(
|
|
73
|
+
<InputPressable prefix={{ icon: "calendar" }} />,
|
|
74
|
+
);
|
|
75
|
+
expect(getByTestId("calendar")).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders a suffix icon when specified", () => {
|
|
79
|
+
const { getByTestId } = render(
|
|
80
|
+
<InputPressable suffix={{ icon: "calendar" }} />,
|
|
81
|
+
);
|
|
82
|
+
expect(getByTestId("calendar")).toBeDefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("calls onPress when pressed", () => {
|
|
86
|
+
const onPressMock = jest.fn();
|
|
87
|
+
const a11yLabel = "test InputPressable";
|
|
88
|
+
const { getByLabelText } = render(
|
|
89
|
+
<InputPressable
|
|
90
|
+
onPress={onPressMock}
|
|
91
|
+
value="test value"
|
|
92
|
+
accessibilityLabel={a11yLabel}
|
|
93
|
+
/>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
fireEvent.press(getByLabelText(a11yLabel));
|
|
97
|
+
expect(onPressMock).toHaveBeenCalledTimes(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("when given a value", () => {
|
|
101
|
+
const value = "A value";
|
|
102
|
+
|
|
103
|
+
it("renders an InputPressable with the value", () => {
|
|
104
|
+
const { getByText } = render(<InputPressable value={value} />);
|
|
105
|
+
expect(getByText(value, { includeHiddenElements: true })).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("renders a prefix label when specified", () => {
|
|
109
|
+
const { getByText } = render(
|
|
110
|
+
<InputPressable prefix={{ label: "test" }} value={value} />,
|
|
111
|
+
);
|
|
112
|
+
expect(getByText(value, { includeHiddenElements: true })).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("renders a suffix label when specified", () => {
|
|
116
|
+
const { getByText } = render(
|
|
117
|
+
<InputPressable suffix={{ label: "test" }} value={value} />,
|
|
118
|
+
);
|
|
119
|
+
expect(getByText(value, { includeHiddenElements: true })).toBeDefined();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("accessibilityLabel", () => {
|
|
125
|
+
it("uses accessibilityLabel if specified", () => {
|
|
126
|
+
const { getByLabelText } = render(
|
|
127
|
+
<InputPressable
|
|
128
|
+
value="test value"
|
|
129
|
+
placeholder="placeholder"
|
|
130
|
+
accessibilityLabel="accessibilityLabel"
|
|
131
|
+
/>,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(getByLabelText("accessibilityLabel")).toBeTruthy();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("uses placeholder if unspecified", () => {
|
|
138
|
+
const { getByLabelText } = render(
|
|
139
|
+
<InputPressable value="test value" placeholder="placeholder" />,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(getByLabelText("placeholder")).toBeTruthy();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React, { Ref, forwardRef, useEffect, useState } from "react";
|
|
2
|
+
import { IconNames } from "@jobber/design";
|
|
3
|
+
import { FieldError } from "react-hook-form";
|
|
4
|
+
import { Text as NativeText, Pressable } from "react-native";
|
|
5
|
+
import { styles } from "./InputPressable.style";
|
|
6
|
+
import {
|
|
7
|
+
Clearable,
|
|
8
|
+
InputFieldWrapper,
|
|
9
|
+
commonInputStyles,
|
|
10
|
+
useShowClear,
|
|
11
|
+
} from "../InputFieldWrapper";
|
|
12
|
+
|
|
13
|
+
interface InputPressableProps {
|
|
14
|
+
/**
|
|
15
|
+
* Current value of the component
|
|
16
|
+
*/
|
|
17
|
+
readonly value?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Placeholder item shown until a selection is made
|
|
21
|
+
*/
|
|
22
|
+
readonly placeholder?: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Disables input selection
|
|
26
|
+
*/
|
|
27
|
+
readonly disabled?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Indicates if there is an validation error
|
|
31
|
+
*/
|
|
32
|
+
readonly error?: FieldError;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Indicates the current selection is invalid
|
|
36
|
+
*/
|
|
37
|
+
readonly invalid?: boolean | string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Callback that is called when the text input is focused
|
|
41
|
+
* @param event
|
|
42
|
+
*/
|
|
43
|
+
readonly onPress?: () => void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* VoiceOver will read this string when a user selects the element
|
|
47
|
+
*/
|
|
48
|
+
readonly accessibilityLabel?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Helps users understand what will happen when they perform an action
|
|
52
|
+
*/
|
|
53
|
+
readonly accessibilityHint?: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Symbol to display before the text input
|
|
57
|
+
*/
|
|
58
|
+
readonly prefix?: {
|
|
59
|
+
icon?: IconNames;
|
|
60
|
+
label?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Symbol to display after the text input
|
|
65
|
+
*/
|
|
66
|
+
readonly suffix?: {
|
|
67
|
+
icon?: IconNames;
|
|
68
|
+
label?: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Add a clear action on the input that clears the value.
|
|
72
|
+
*
|
|
73
|
+
* Since the input value isn't editable (i.e. `InputDateTime`) you can
|
|
74
|
+
* set it to `always`. If you set it to `always` you must also provide an
|
|
75
|
+
* onClear to clear the input's value
|
|
76
|
+
*/
|
|
77
|
+
readonly clearable?: Clearable;
|
|
78
|
+
/**
|
|
79
|
+
* Callback called when the user clicks the ClearAction button. Should clear
|
|
80
|
+
* the value passed. To disallow clearing set the clearable prop to never
|
|
81
|
+
*/
|
|
82
|
+
readonly onClear?: () => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type InputPressableRef = NativeText;
|
|
86
|
+
export const InputPressable = forwardRef(InputPressableInternal);
|
|
87
|
+
|
|
88
|
+
export function InputPressableInternal(
|
|
89
|
+
{
|
|
90
|
+
value,
|
|
91
|
+
placeholder,
|
|
92
|
+
disabled,
|
|
93
|
+
invalid,
|
|
94
|
+
error,
|
|
95
|
+
onPress,
|
|
96
|
+
accessibilityLabel,
|
|
97
|
+
accessibilityHint,
|
|
98
|
+
prefix,
|
|
99
|
+
suffix,
|
|
100
|
+
clearable = "never",
|
|
101
|
+
onClear,
|
|
102
|
+
}: InputPressableProps,
|
|
103
|
+
ref: Ref<InputPressableRef>,
|
|
104
|
+
): JSX.Element {
|
|
105
|
+
const hasValue = !!value;
|
|
106
|
+
const [hasMiniLabel, setHasMiniLabel] = useState(Boolean(value));
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
setHasMiniLabel(Boolean(value));
|
|
110
|
+
}, [value]);
|
|
111
|
+
|
|
112
|
+
const showClear = useShowClear({
|
|
113
|
+
clearable,
|
|
114
|
+
multiline: false,
|
|
115
|
+
focused: false,
|
|
116
|
+
hasValue,
|
|
117
|
+
disabled,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<InputFieldWrapper
|
|
122
|
+
prefix={prefix}
|
|
123
|
+
suffix={suffix}
|
|
124
|
+
hasValue={hasValue}
|
|
125
|
+
hasMiniLabel={hasMiniLabel}
|
|
126
|
+
focused={false}
|
|
127
|
+
error={error}
|
|
128
|
+
invalid={invalid}
|
|
129
|
+
placeholder={placeholder}
|
|
130
|
+
disabled={disabled}
|
|
131
|
+
showClearAction={showClear}
|
|
132
|
+
onClear={onClear}
|
|
133
|
+
>
|
|
134
|
+
<Pressable
|
|
135
|
+
style={styles.pressable}
|
|
136
|
+
onPress={onPress}
|
|
137
|
+
disabled={disabled}
|
|
138
|
+
accessibilityLabel={accessibilityLabel || placeholder}
|
|
139
|
+
accessibilityHint={accessibilityHint}
|
|
140
|
+
accessibilityValue={{ text: value }}
|
|
141
|
+
>
|
|
142
|
+
<NativeText
|
|
143
|
+
style={[
|
|
144
|
+
commonInputStyles.input,
|
|
145
|
+
styles.inputPressableStyles,
|
|
146
|
+
!hasMiniLabel && commonInputStyles.inputEmpty,
|
|
147
|
+
disabled && commonInputStyles.inputDisabled,
|
|
148
|
+
(Boolean(invalid) || error) && styles.inputInvalid,
|
|
149
|
+
]}
|
|
150
|
+
testID="inputPressableText"
|
|
151
|
+
ref={ref}
|
|
152
|
+
accessibilityRole="none"
|
|
153
|
+
accessible={false}
|
|
154
|
+
importantForAccessibility="no-hide-descendants"
|
|
155
|
+
>
|
|
156
|
+
{value}
|
|
157
|
+
</NativeText>
|
|
158
|
+
</Pressable>
|
|
159
|
+
</InputFieldWrapper>
|
|
160
|
+
);
|
|
161
|
+
}
|
package/src/index.ts
CHANGED