@jobber/components-native 0.84.3 → 0.84.4-CLEANUPre-eb76ad2.29
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/package.json +24 -15
- package/dist/src/Banner/Banner.js +2 -0
- package/dist/src/Banner/components/BannerIcon/BannerIcon.style.js +3 -0
- package/dist/src/Button/components/InternalButtonLoading/InternalButtonLoading.js +0 -1
- package/dist/src/Form/components/FormCache/FormCache.js +1 -0
- package/dist/src/FormatFile/utils/createUseCreateThumbnail.js +1 -1
- package/dist/src/InputCurrency/InputCurrency.js +42 -30
- package/dist/src/InputEmail/InputEmail.js +12 -4
- package/dist/src/InputFieldWrapper/InputFieldWrapper.style.js +1 -1
- package/dist/src/InputNumber/InputNumber.js +10 -4
- package/dist/src/InputSearch/InputSearch.js +1 -1
- package/dist/src/hooks/useAtlantisI18n/locales/en.json +1 -0
- package/dist/src/hooks/useAtlantisI18n/locales/es.json +1 -0
- package/dist/src/hooks/useFormController.js +5 -14
- package/dist/tsconfig.build.json +7 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.json +4 -6
- package/dist/types/src/Banner/components/BannerIcon/BannerIcon.style.d.ts +3 -0
- package/dist/types/src/Banner/types.d.ts +1 -1
- package/dist/types/src/Form/components/FormCache/FormCache.d.ts +2 -2
- package/dist/types/src/Form/context/types.d.ts +2 -2
- package/dist/types/src/Form/hooks/useInternalForm.d.ts +2 -2
- package/dist/types/src/Form/types.d.ts +3 -3
- package/dist/types/src/InputFieldWrapper/InputFieldWrapper.style.d.ts +1 -1
- package/dist/types/src/InputText/InputText.d.ts +2 -2
- package/package.json +24 -15
- package/src/AtlantisContext/AtlantisContext.test.tsx +1 -1
- package/src/AutoLink/AutoLink.test.tsx +2 -4
- package/src/Banner/Banner.test.tsx +12 -0
- package/src/Banner/Banner.tsx +2 -0
- package/src/Banner/components/BannerIcon/BannerIcon.style.ts +3 -0
- package/src/Banner/types.ts +1 -1
- package/src/Button/components/InternalButtonLoading/InternalButtonLoading.tsx +0 -1
- package/src/Checkbox/CheckboxGroup.test.tsx +2 -6
- package/src/ContentOverlay/hooks/useKeyboardVisibility.test.ts +1 -1
- package/src/ContentOverlay/hooks/useViewLayoutHeight.test.ts +1 -1
- package/src/EmptyState/EmptyState.test.tsx +29 -42
- package/src/Form/components/FormCache/FormCache.tsx +4 -3
- package/src/Form/components/FormMessage/FormMessage.test.tsx +40 -27
- package/src/Form/context/AtlantisFormContext.test.tsx +1 -1
- package/src/Form/context/types.ts +2 -2
- package/src/Form/hooks/useInternalForm.ts +2 -1
- package/src/Form/hooks/useScrollToError/useScrollToError.test.tsx +2 -1
- package/src/Form/types.ts +3 -4
- package/src/FormatFile/FormatFile.test.tsx +6 -6
- package/src/FormatFile/utils/createUseCreateThumbnail.ts +1 -1
- package/src/Icon/__snapshots__/Icon.test.tsx.snap +7 -0
- package/src/InputCurrency/InputCurrency.tsx +71 -57
- package/src/InputEmail/InputEmail.tsx +15 -8
- package/src/InputFieldWrapper/InputFieldWrapper.style.ts +1 -1
- package/src/InputFieldWrapper/components/Prefix/Prefix.test.tsx +5 -11
- package/src/InputNumber/InputNumber.tsx +11 -7
- package/src/InputSearch/InputSearch.tsx +1 -1
- package/src/InputText/InputText.tsx +2 -5
- package/src/ThumbnailList/ThumbnailList.test.tsx +5 -5
- package/src/hooks/useAtlantisI18n/locales/en.json +1 -0
- package/src/hooks/useAtlantisI18n/locales/es.json +1 -0
- package/src/hooks/useFormController.ts +6 -13
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renderHook } from "@testing-library/react-
|
|
1
|
+
import { renderHook } from "@testing-library/react-native";
|
|
2
2
|
import { useScrollToError } from "./useScrollToError";
|
|
3
3
|
|
|
4
4
|
const mockFormState = {
|
|
@@ -25,6 +25,7 @@ jest.mock("../../../ErrorMessageWrapper", () => ({
|
|
|
25
25
|
el: {
|
|
26
26
|
measure: jest.fn((_, callback) => callback()),
|
|
27
27
|
hasErrorMessage: true,
|
|
28
|
+
accessibilityFocus: jest.fn(),
|
|
28
29
|
},
|
|
29
30
|
},
|
|
30
31
|
register: jest.fn(),
|
package/src/Form/types.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import type { MutableRefObject, RefObject } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
ControllerProps,
|
|
4
|
-
|
|
4
|
+
DefaultValues,
|
|
5
5
|
FieldPath,
|
|
6
6
|
FieldValues,
|
|
7
7
|
Mode,
|
|
8
|
-
UnpackNestedValue,
|
|
9
8
|
UseFormReturn,
|
|
10
9
|
} from "react-hook-form";
|
|
11
10
|
import type { IconNames } from "@jobber/design";
|
|
12
11
|
import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
|
13
12
|
|
|
14
|
-
export type FormValues<T> =
|
|
13
|
+
export type FormValues<T> = T;
|
|
15
14
|
export type FormErrors = FormNetworkErrors | FormUserErrors;
|
|
16
15
|
export type FormBannerMessage = FormWarningMessage | FormNoticeMessage;
|
|
17
16
|
|
|
@@ -109,7 +108,7 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
|
|
|
109
108
|
* The initial values of the form inputs
|
|
110
109
|
* This should be available as soon as initialLoading is set to false
|
|
111
110
|
*/
|
|
112
|
-
initialValues?:
|
|
111
|
+
initialValues?: DefaultValues<T>;
|
|
113
112
|
|
|
114
113
|
/**
|
|
115
114
|
* When the validation should happen.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { RenderAPI } from "@testing-library/react-native";
|
|
3
|
-
import { fireEvent, render } from "@testing-library/react-native";
|
|
4
|
-
import { Host } from "react-native-portalize";
|
|
3
|
+
import { fireEvent, render, waitFor } from "@testing-library/react-native";
|
|
5
4
|
import { Alert } from "react-native";
|
|
5
|
+
import { Host } from "react-native-portalize";
|
|
6
6
|
import type { File } from ".";
|
|
7
7
|
import { FormatFile } from ".";
|
|
8
8
|
import {
|
|
@@ -117,12 +117,12 @@ function basicRenderTestWithValue() {
|
|
|
117
117
|
);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
it("renders ProgressBar state advancing with the upload percentage", () => {
|
|
120
|
+
it("renders ProgressBar state advancing with the upload percentage", async () => {
|
|
121
121
|
jest.useFakeTimers();
|
|
122
122
|
const { getByTestId } = renderFormatFile(file);
|
|
123
123
|
jest.advanceTimersByTime(progressBarAnimationTime);
|
|
124
|
-
const formatFileInnerProgressBar =
|
|
125
|
-
"format-file-inner-progress-bar",
|
|
124
|
+
const formatFileInnerProgressBar = await waitFor(() =>
|
|
125
|
+
getByTestId("format-file-inner-progress-bar"),
|
|
126
126
|
);
|
|
127
127
|
const innerProgressBarWidth = parseInt(
|
|
128
128
|
formatFileInnerProgressBar.props.style.width,
|
|
@@ -226,7 +226,7 @@ function basicRenderTestWithValue() {
|
|
|
226
226
|
});
|
|
227
227
|
|
|
228
228
|
it("creates a thumbnail when a media file is used", () => {
|
|
229
|
-
const expectedCalls = testId.includes("image") ?
|
|
229
|
+
const expectedCalls = testId.includes("image") ? 1 : 0;
|
|
230
230
|
expect(mockCreateThumbnail).toHaveBeenCalledTimes(expectedCalls);
|
|
231
231
|
});
|
|
232
232
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useIsMounted } from "@jobber/hooks
|
|
1
|
+
import { useIsMounted } from "@jobber/hooks";
|
|
2
2
|
import { useCallback, useEffect, useState } from "react";
|
|
3
3
|
import type { UseCreateThumbnail } from "../context/types";
|
|
4
4
|
import type { CreateThumbnail, FormattedFile } from "../types";
|
|
@@ -80,6 +80,56 @@ const getKeyboard = (props: InputCurrencyProps) => {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
const computeDisplayFromNumericInput = (
|
|
84
|
+
numberedValue: number,
|
|
85
|
+
decimalNumbers: string,
|
|
86
|
+
decimalCount: number,
|
|
87
|
+
maxLength: number,
|
|
88
|
+
maxDecimalPlaces: number,
|
|
89
|
+
decimalPlaces: number,
|
|
90
|
+
formatNumber: (
|
|
91
|
+
value: number,
|
|
92
|
+
opts?: FormatNumberOptions | undefined,
|
|
93
|
+
) => string,
|
|
94
|
+
): {
|
|
95
|
+
onChangeValue: number | string;
|
|
96
|
+
displayValue: string;
|
|
97
|
+
} => {
|
|
98
|
+
const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
|
|
99
|
+
const stringValue =
|
|
100
|
+
decimalNumbers !== ""
|
|
101
|
+
? transformedValue.toString() + "." + decimalNumbers.slice(1)
|
|
102
|
+
: transformedValue.toString();
|
|
103
|
+
|
|
104
|
+
if (checkLastChar(stringValue)) {
|
|
105
|
+
const roundedDecimal = configureDecimal(
|
|
106
|
+
decimalCount,
|
|
107
|
+
maxDecimalPlaces,
|
|
108
|
+
stringValue,
|
|
109
|
+
decimalPlaces,
|
|
110
|
+
);
|
|
111
|
+
const internationalizedValueToDisplay = formatNumber(roundedDecimal, {
|
|
112
|
+
maximumFractionDigits: maxDecimalPlaces,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
onChangeValue: roundedDecimal,
|
|
117
|
+
displayValue: internationalizedValueToDisplay,
|
|
118
|
+
};
|
|
119
|
+
} else {
|
|
120
|
+
const internationalizedValueToDisplay =
|
|
121
|
+
formatNumber(transformedValue, {
|
|
122
|
+
maximumFractionDigits: maxDecimalPlaces,
|
|
123
|
+
}) + decimalNumbers;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
onChangeValue:
|
|
127
|
+
transformedValue.toString() + "." + decimalNumbers.slice(1),
|
|
128
|
+
displayValue: internationalizedValueToDisplay,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
83
133
|
export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
84
134
|
const {
|
|
85
135
|
showCurrencySymbol = true,
|
|
@@ -99,54 +149,6 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
99
149
|
internalValue,
|
|
100
150
|
);
|
|
101
151
|
|
|
102
|
-
const setOnChangeAndDisplayValues = (
|
|
103
|
-
onChangeValue: number | string | undefined,
|
|
104
|
-
valueToDisplay: string | undefined,
|
|
105
|
-
) => {
|
|
106
|
-
props.onChange?.(onChangeValue);
|
|
107
|
-
setDisplayValue(valueToDisplay);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const checkDecimalAndI18nOfDisplayValue = (
|
|
111
|
-
numberedValue: number,
|
|
112
|
-
decimalNumbers: string,
|
|
113
|
-
decimalCount: number,
|
|
114
|
-
) => {
|
|
115
|
-
const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
|
|
116
|
-
const stringValue =
|
|
117
|
-
decimalNumbers !== ""
|
|
118
|
-
? transformedValue.toString() + "." + decimalNumbers.slice(1)
|
|
119
|
-
: transformedValue.toString();
|
|
120
|
-
|
|
121
|
-
if (checkLastChar(stringValue)) {
|
|
122
|
-
const roundedDecimal = configureDecimal(
|
|
123
|
-
decimalCount,
|
|
124
|
-
maxDecimalPlaces,
|
|
125
|
-
stringValue,
|
|
126
|
-
decimalPlaces,
|
|
127
|
-
);
|
|
128
|
-
const internationalizedValueToDisplay = intl.formatNumber(
|
|
129
|
-
roundedDecimal,
|
|
130
|
-
{
|
|
131
|
-
maximumFractionDigits: maxDecimalPlaces,
|
|
132
|
-
},
|
|
133
|
-
);
|
|
134
|
-
setOnChangeAndDisplayValues(
|
|
135
|
-
roundedDecimal,
|
|
136
|
-
internationalizedValueToDisplay,
|
|
137
|
-
);
|
|
138
|
-
} else {
|
|
139
|
-
const internationalizedValueToDisplay =
|
|
140
|
-
intl.formatNumber(transformedValue, {
|
|
141
|
-
maximumFractionDigits: maxDecimalPlaces,
|
|
142
|
-
}) + decimalNumbers;
|
|
143
|
-
setOnChangeAndDisplayValues(
|
|
144
|
-
transformedValue.toString() + "." + decimalNumbers.slice(1),
|
|
145
|
-
internationalizedValueToDisplay,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
152
|
const handleChange = (newValue: string | undefined) => {
|
|
151
153
|
const [decimalCount, wholeIntegerValue, decimalNumbers] = parseGivenInput(
|
|
152
154
|
newValue,
|
|
@@ -158,19 +160,37 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
158
160
|
: wholeIntegerValue;
|
|
159
161
|
|
|
160
162
|
if (isValidNumber(numberedValue) && typeof numberedValue === "number") {
|
|
161
|
-
|
|
163
|
+
const result = computeDisplayFromNumericInput(
|
|
162
164
|
numberedValue,
|
|
163
165
|
decimalNumbers,
|
|
164
166
|
decimalCount,
|
|
167
|
+
maxLength,
|
|
168
|
+
maxDecimalPlaces,
|
|
169
|
+
decimalPlaces,
|
|
170
|
+
intl.formatNumber,
|
|
165
171
|
);
|
|
172
|
+
const { onChangeValue, displayValue: valueToDisplay } = result;
|
|
173
|
+
props.onChange?.(onChangeValue);
|
|
174
|
+
setDisplayValue(valueToDisplay);
|
|
166
175
|
} else {
|
|
167
176
|
const value = numberedValue?.toString() + decimalNumbers;
|
|
168
|
-
|
|
177
|
+
props.onChange?.(value);
|
|
178
|
+
setDisplayValue(value);
|
|
169
179
|
}
|
|
170
180
|
};
|
|
171
181
|
|
|
172
182
|
const { t } = useAtlantisI18n();
|
|
173
183
|
|
|
184
|
+
const defaultValidations = {
|
|
185
|
+
pattern: {
|
|
186
|
+
value: NUMBER_VALIDATION_REGEX,
|
|
187
|
+
message: t("errors.notANumber"),
|
|
188
|
+
},
|
|
189
|
+
} as const;
|
|
190
|
+
const mergedValidations = props.validations
|
|
191
|
+
? Object.assign({}, defaultValidations, props.validations)
|
|
192
|
+
: defaultValidations;
|
|
193
|
+
|
|
174
194
|
return (
|
|
175
195
|
<InputText
|
|
176
196
|
{...props}
|
|
@@ -187,13 +207,7 @@ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
|
|
|
187
207
|
.replace(floatSeparators.decimal, ".");
|
|
188
208
|
},
|
|
189
209
|
}}
|
|
190
|
-
validations={
|
|
191
|
-
pattern: {
|
|
192
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
193
|
-
message: t("errors.notANumber"),
|
|
194
|
-
},
|
|
195
|
-
...props.validations,
|
|
196
|
-
}}
|
|
210
|
+
validations={mergedValidations}
|
|
197
211
|
onBlur={() => {
|
|
198
212
|
props.onBlur?.();
|
|
199
213
|
|
|
@@ -2,11 +2,25 @@ import type { Ref } from "react";
|
|
|
2
2
|
import React, { forwardRef } from "react";
|
|
3
3
|
import type { InputTextProps, InputTextRef } from "../InputText";
|
|
4
4
|
import { InputText } from "../InputText";
|
|
5
|
+
import { useAtlantisI18n } from "../hooks/useAtlantisI18n";
|
|
5
6
|
|
|
6
7
|
export const InputEmail = forwardRef(InputEmailInternal);
|
|
7
8
|
type InputEmailProps = Omit<InputTextProps, "keyboard">;
|
|
8
9
|
|
|
9
10
|
function InputEmailInternal(props: InputEmailProps, ref: Ref<InputTextRef>) {
|
|
11
|
+
const { t } = useAtlantisI18n();
|
|
12
|
+
|
|
13
|
+
const defaultValidations = {
|
|
14
|
+
pattern: {
|
|
15
|
+
value:
|
|
16
|
+
/[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?/,
|
|
17
|
+
message: t("InputEmail.enterEmail"),
|
|
18
|
+
},
|
|
19
|
+
} as const;
|
|
20
|
+
const mergedValidations = props.validations
|
|
21
|
+
? Object.assign({}, defaultValidations, props.validations)
|
|
22
|
+
: defaultValidations;
|
|
23
|
+
|
|
10
24
|
return (
|
|
11
25
|
<InputText
|
|
12
26
|
{...props}
|
|
@@ -14,14 +28,7 @@ function InputEmailInternal(props: InputEmailProps, ref: Ref<InputTextRef>) {
|
|
|
14
28
|
autoCapitalize="none"
|
|
15
29
|
autoCorrect={false}
|
|
16
30
|
keyboard={"email-address"}
|
|
17
|
-
validations={
|
|
18
|
-
pattern: {
|
|
19
|
-
value:
|
|
20
|
-
/[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?/,
|
|
21
|
-
message: "Enter a valid email address (email@example.com)",
|
|
22
|
-
},
|
|
23
|
-
...props.validations,
|
|
24
|
-
}}
|
|
31
|
+
validations={mergedValidations}
|
|
25
32
|
/>
|
|
26
33
|
);
|
|
27
34
|
}
|
|
@@ -43,6 +43,7 @@ export const useStyles = buildThemedStyles(tokens => {
|
|
|
43
43
|
right: 0,
|
|
44
44
|
bottom: 0,
|
|
45
45
|
left: 0,
|
|
46
|
+
zIndex: 1,
|
|
46
47
|
},
|
|
47
48
|
|
|
48
49
|
miniLabel: {
|
|
@@ -53,7 +54,6 @@ export const useStyles = buildThemedStyles(tokens => {
|
|
|
53
54
|
maxHeight:
|
|
54
55
|
(typographyStyles.defaultSize.lineHeight || 0) +
|
|
55
56
|
tokens["space-smaller"],
|
|
56
|
-
zIndex: 1,
|
|
57
57
|
},
|
|
58
58
|
// Prevents the miniLabel from cutting off the ClearAction button
|
|
59
59
|
miniLabelShowClearAction: {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { render, renderHook } from "@testing-library/react-native";
|
|
2
|
+
import { render, renderHook, screen } from "@testing-library/react-native";
|
|
3
3
|
import type { TextStyle } from "react-native";
|
|
4
4
|
import type { ReactTestInstance } from "react-test-renderer";
|
|
5
|
+
import { Path } from "react-native-svg";
|
|
5
6
|
import type { PrefixIconProps, PrefixLabelProps } from "./Prefix";
|
|
6
7
|
import {
|
|
7
8
|
PrefixIcon,
|
|
@@ -12,9 +13,6 @@ import {
|
|
|
12
13
|
import { useTypographyStyles } from "../../../Typography";
|
|
13
14
|
import { useStyles } from "../../InputFieldWrapper.style";
|
|
14
15
|
import { tokens } from "../../../utils/design";
|
|
15
|
-
import * as IconComponent from "../../../Icon/Icon";
|
|
16
|
-
|
|
17
|
-
const iconSpy = jest.spyOn(IconComponent, "Icon");
|
|
18
16
|
|
|
19
17
|
const mockLabel = "$";
|
|
20
18
|
|
|
@@ -180,13 +178,9 @@ describe("Prefix", () => {
|
|
|
180
178
|
setupIcon({
|
|
181
179
|
disabled: true,
|
|
182
180
|
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
name: "invoice",
|
|
187
|
-
},
|
|
188
|
-
{},
|
|
189
|
-
);
|
|
181
|
+
const icon = screen.getByTestId("invoice");
|
|
182
|
+
const path = icon.findByType(Path);
|
|
183
|
+
expect(path.props.fill).toEqual(tokens["color-disabled"]);
|
|
190
184
|
});
|
|
191
185
|
});
|
|
192
186
|
|
|
@@ -46,6 +46,16 @@ function InputNumberInternal(props: InputNumberProps, ref: Ref<InputTextRef>) {
|
|
|
46
46
|
const { inputTransform: convertToString, outputTransform: convertToNumber } =
|
|
47
47
|
useNumberTransform(props.value);
|
|
48
48
|
|
|
49
|
+
const defaultValidations = {
|
|
50
|
+
pattern: {
|
|
51
|
+
value: NUMBER_VALIDATION_REGEX,
|
|
52
|
+
message: t("errors.notANumber"),
|
|
53
|
+
},
|
|
54
|
+
} as const;
|
|
55
|
+
const mergedValidations = props.validations
|
|
56
|
+
? Object.assign({}, defaultValidations, props.validations)
|
|
57
|
+
: defaultValidations;
|
|
58
|
+
|
|
49
59
|
return (
|
|
50
60
|
<InputText
|
|
51
61
|
{...props}
|
|
@@ -58,13 +68,7 @@ function InputNumberInternal(props: InputNumberProps, ref: Ref<InputTextRef>) {
|
|
|
58
68
|
value={props.value?.toString()}
|
|
59
69
|
defaultValue={props.defaultValue?.toString()}
|
|
60
70
|
onChangeText={handleChange}
|
|
61
|
-
validations={
|
|
62
|
-
pattern: {
|
|
63
|
-
value: NUMBER_VALIDATION_REGEX,
|
|
64
|
-
message: t("errors.notANumber"),
|
|
65
|
-
},
|
|
66
|
-
...props.validations,
|
|
67
|
-
}}
|
|
71
|
+
validations={mergedValidations}
|
|
68
72
|
/>
|
|
69
73
|
);
|
|
70
74
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Ref } from "react";
|
|
2
2
|
import React, { forwardRef, useEffect } from "react";
|
|
3
3
|
import { View } from "react-native";
|
|
4
|
-
import { useDebounce } from "@jobber/hooks
|
|
4
|
+
import { useDebounce } from "@jobber/hooks";
|
|
5
5
|
import { styles } from "./InputSearch.style";
|
|
6
6
|
import type { InputTextProps, InputTextRef } from "../InputText";
|
|
7
7
|
import { InputText } from "../InputText";
|
|
@@ -8,10 +8,9 @@ import React, {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from "react";
|
|
10
10
|
import type {
|
|
11
|
-
|
|
11
|
+
FocusEvent,
|
|
12
12
|
ReturnKeyTypeOptions,
|
|
13
13
|
StyleProp,
|
|
14
|
-
TextInputFocusEventData,
|
|
15
14
|
TextInputProps,
|
|
16
15
|
TextStyle,
|
|
17
16
|
} from "react-native";
|
|
@@ -113,9 +112,7 @@ export interface InputTextProps
|
|
|
113
112
|
* Callback that is called when the text input is focused
|
|
114
113
|
* @param event
|
|
115
114
|
*/
|
|
116
|
-
readonly onFocus?: (
|
|
117
|
-
event?: NativeSyntheticEvent<TextInputFocusEventData>,
|
|
118
|
-
) => void;
|
|
115
|
+
readonly onFocus?: (event?: FocusEvent) => void;
|
|
119
116
|
|
|
120
117
|
/**
|
|
121
118
|
* Callback that is called when the text input is blurred
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { fireEvent, render } from "@testing-library/react-native";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react-native";
|
|
3
3
|
import { Host } from "react-native-portalize";
|
|
4
4
|
import { ThumbnailList } from "./ThumbnailList";
|
|
5
5
|
import type { File } from "../FormatFile/types";
|
|
@@ -61,15 +61,15 @@ beforeEach(() => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it("renders a thumbnail component with attachments", () => {
|
|
64
|
-
|
|
65
|
-
expect(
|
|
64
|
+
setup(true);
|
|
65
|
+
expect(screen.toJSON()).toMatchSnapshot();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
describe("when a an array of files is provided", () => {
|
|
69
69
|
it("calls the previewImages util on pressing a valid file", () => {
|
|
70
|
-
|
|
70
|
+
setup();
|
|
71
71
|
fireEvent.press(
|
|
72
|
-
getByLabelText(files[0].fileName ? files[0].fileName : "file"),
|
|
72
|
+
screen.getByLabelText(files[0].fileName ? files[0].fileName : "file"),
|
|
73
73
|
);
|
|
74
74
|
expect(onOpenFile).toHaveBeenCalledTimes(1);
|
|
75
75
|
});
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"goBack": "Go back",
|
|
19
19
|
"InputFieldWrapper.clear": "Clear input",
|
|
20
20
|
"InputPassword.enterPassword": "Enter a password",
|
|
21
|
+
"InputEmail.enterEmail": "Enter a valid email address (email@example.com)",
|
|
21
22
|
"loading": "Loading",
|
|
22
23
|
"menu": "Menu",
|
|
23
24
|
"more": "More",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"goBack": "Volver",
|
|
19
19
|
"InputFieldWrapper.clear": "Borrar",
|
|
20
20
|
"InputPassword.enterPassword": "Escriba una contraseña",
|
|
21
|
+
"InputEmail.enterEmail": "Introduzca una dirección de correo electrónico válida (email@example.com)",
|
|
21
22
|
"loading": "Cargando",
|
|
22
23
|
"menu": "Menú",
|
|
23
24
|
"more": "Más",
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
RegisterOptions,
|
|
5
5
|
UseControllerReturn,
|
|
6
6
|
} from "react-hook-form";
|
|
7
|
-
import { useController, useForm, useFormContext } from "react-hook-form";
|
|
7
|
+
import { get, useController, useForm, useFormContext } from "react-hook-form";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
|
|
10
10
|
interface UseFormControllerProps<T> {
|
|
@@ -44,18 +44,11 @@ export function useFormController<T>({
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
// The naming convention established by react-hook-form for arrays of fields is, for example, "emails.0.description".
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (fieldIdentifiers.length === 3) {
|
|
54
|
-
const [section, item, identifier] = fieldIdentifiers;
|
|
55
|
-
error = errors[section]?.[item]?.[identifier];
|
|
56
|
-
} else {
|
|
57
|
-
error = errors[fieldName];
|
|
58
|
-
}
|
|
47
|
+
// Preserve original behavior: only treat three-part names as nested paths.
|
|
48
|
+
// For anything else, perform a flat lookup to avoid behavioral changes.
|
|
49
|
+
const identifiers = fieldName.split(".");
|
|
50
|
+
const error =
|
|
51
|
+
identifiers.length === 3 ? get(errors, fieldName) : errors[fieldName];
|
|
59
52
|
|
|
60
53
|
return { error, field };
|
|
61
54
|
}
|