@jobber/components-native 0.33.1 → 0.35.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/AtlantisContext/AtlantisContext.js +2 -0
  2. package/dist/src/InputCurrency/InputCurrency.js +93 -0
  3. package/dist/src/InputCurrency/constants.js +1 -0
  4. package/dist/src/InputCurrency/index.js +3 -0
  5. package/dist/src/InputCurrency/messages.js +8 -0
  6. package/dist/src/InputCurrency/utils.js +58 -0
  7. package/dist/src/InputTime/InputTime.js +83 -0
  8. package/dist/src/InputTime/InputTime.style.js +7 -0
  9. package/dist/src/InputTime/index.js +2 -0
  10. package/dist/src/InputTime/messages.js +8 -0
  11. package/dist/src/InputTime/utils/index.js +16 -0
  12. package/dist/src/index.js +2 -0
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/types/src/AtlantisContext/AtlantisContext.d.ts +4 -0
  15. package/dist/types/src/InputCurrency/InputCurrency.d.ts +32 -0
  16. package/dist/types/src/InputCurrency/constants.d.ts +1 -0
  17. package/dist/types/src/InputCurrency/index.d.ts +3 -0
  18. package/dist/types/src/InputCurrency/messages.d.ts +7 -0
  19. package/dist/types/src/InputCurrency/utils.d.ts +9 -0
  20. package/dist/types/src/InputTime/InputTime.d.ts +61 -0
  21. package/dist/types/src/InputTime/InputTime.style.d.ts +6 -0
  22. package/dist/types/src/InputTime/index.d.ts +2 -0
  23. package/dist/types/src/InputTime/messages.d.ts +7 -0
  24. package/dist/types/src/InputTime/utils/index.d.ts +11 -0
  25. package/dist/types/src/index.d.ts +2 -0
  26. package/package.json +7 -3
  27. package/src/AtlantisContext/AtlantisContext.test.tsx +1 -0
  28. package/src/AtlantisContext/AtlantisContext.tsx +7 -0
  29. package/src/InputCurrency/InputCurrency.test.tsx +158 -0
  30. package/src/InputCurrency/InputCurrency.tsx +206 -0
  31. package/src/InputCurrency/constants.ts +1 -0
  32. package/src/InputCurrency/index.ts +3 -0
  33. package/src/InputCurrency/messages.ts +10 -0
  34. package/src/InputCurrency/utils.ts +92 -0
  35. package/src/InputTime/InputTime.style.ts +8 -0
  36. package/src/InputTime/InputTime.test.tsx +323 -0
  37. package/src/InputTime/InputTime.tsx +221 -0
  38. package/src/InputTime/index.tsx +2 -0
  39. package/src/InputTime/messages.ts +9 -0
  40. package/src/InputTime/utils/index.ts +26 -0
  41. package/src/InputTime/utils/utils.test.ts +47 -0
  42. package/src/index.ts +2 -0
@@ -26,6 +26,10 @@ export interface AtlantisContextProps {
26
26
  * Grabs the decimal separator and group separator based on locale
27
27
  */
28
28
  readonly floatSeparators: Record<"decimal" | "group", string>;
29
+ /**
30
+ * The currency symbol Atlantis components will use
31
+ */
32
+ readonly currencySymbol: string;
29
33
  }
30
34
  export declare const defaultValues: AtlantisContextProps;
31
35
  export declare const AtlantisContext: import("react").Context<AtlantisContextProps>;
@@ -0,0 +1,32 @@
1
+ /// <reference types="react" />
2
+ import { FormatNumberOptions } from "react-intl";
3
+ import { ControllerRenderProps, FieldValues } from "react-hook-form";
4
+ import { InputTextProps } from "../InputText";
5
+ export interface InputCurrencyProps extends Omit<InputTextProps, "keyboard" | "onChangeText" | "value" | "defaultValue"> {
6
+ /**
7
+ * Whether to display the user's currency symbol or not
8
+ * Default value is true
9
+ */
10
+ readonly showCurrencySymbol?: boolean;
11
+ /**
12
+ * The minimum decimal places for the currency number
13
+ * Default value is 2
14
+ */
15
+ readonly decimalPlaces?: number;
16
+ /**
17
+ * The maximum decimal places for the currency number
18
+ * Default value is 5
19
+ */
20
+ readonly maxDecimalPlaces?: number;
21
+ /**
22
+ * The maximum length of the input
23
+ * Default value is 10
24
+ */
25
+ readonly maxLength?: number;
26
+ onChange?(newValue?: number | string | undefined): void;
27
+ value?: number;
28
+ defaultValue?: number;
29
+ keyboard?: "decimal-pad" | "numbers-and-punctuation";
30
+ }
31
+ export declare const getInternalValue: (props: InputCurrencyProps, field: ControllerRenderProps<FieldValues, string>, formatNumber: (value: number | bigint, opts?: FormatNumberOptions | undefined) => string) => string;
32
+ export declare function InputCurrency(props: InputCurrencyProps): JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_CURRENCY_SYMBOL = "$";
@@ -0,0 +1,3 @@
1
+ export { InputCurrency } from "./InputCurrency";
2
+ export * from "./utils";
3
+ export * from "./constants";
@@ -0,0 +1,7 @@
1
+ export declare const messages: {
2
+ notANumberError: {
3
+ id: string;
4
+ defaultMessage: string;
5
+ description: string;
6
+ };
7
+ };
@@ -0,0 +1,9 @@
1
+ export declare function countDecimal(value: number): number;
2
+ export declare function limitInputWholeDigits(value: number, maxInputLength: number): number;
3
+ export declare function configureDecimal(decimalCount: number, maxDecimalPlaces: number, transformedValue: string, decimalPlaces: number): number;
4
+ export declare function convertToNumber(value: string): string | number;
5
+ export declare const checkLastChar: (stringValue: string) => boolean;
6
+ export declare const isValidNumber: (numberedValue: string | number | undefined) => boolean;
7
+ export declare const getDecimalNumbers: (value: string, decimalSeparator: string) => string;
8
+ export declare const parseGivenInput: (value: string | undefined, decimalSeparator: string) => [number, string | undefined, string];
9
+ export declare const NUMBER_VALIDATION_REGEX: RegExp;
@@ -0,0 +1,61 @@
1
+ /// <reference types="react" />
2
+ import { UseControllerProps } from "react-hook-form";
3
+ import { XOR } from "ts-xor";
4
+ import { Clearable, InputFieldWrapperProps } from "../InputFieldWrapper";
5
+ interface InputTimeBaseProps extends Pick<InputFieldWrapperProps, "invalid" | "disabled" | "placeholder"> {
6
+ /**
7
+ * Defaulted to "always" so user can clear the time whenever there's a value.
8
+ */
9
+ readonly clearable?: Extract<Clearable, "always" | "never">;
10
+ /**
11
+ * Add a custom value to display when no time is selected
12
+ * @default undefined
13
+ */
14
+ readonly emptyValueLabel?: string;
15
+ /**
16
+ * Adjusts the UX of the time picker based on where you'd use it.
17
+ *
18
+ * - `"granular"` - allows the user to pick a very specific time
19
+ * - `"scheduling"` - only allows user to select between 5 minutes interval.
20
+ * If your design is catered towards "scheduling", you should use this type.
21
+ *
22
+ * @default scheduling
23
+ */
24
+ readonly type?: "granular" | "scheduling";
25
+ /**
26
+ * Hide or show the timer icon.
27
+ */
28
+ readonly showIcon?: boolean;
29
+ }
30
+ export interface InputTimeFormControlled extends InputTimeBaseProps {
31
+ /**
32
+ * Adding a `name` would make this component "Form controlled" and must be
33
+ * nested within a `<Form />` component.
34
+ *
35
+ * Cannot be declared if `value` prop is used.
36
+ */
37
+ readonly name: string;
38
+ /**
39
+ * Shows an error message below the field and highlights it red when the
40
+ * value is invalid. Only applies when nested within a `<Form />` component.
41
+ */
42
+ readonly validations?: UseControllerProps["rules"];
43
+ /**
44
+ * The callback that fires whenever a time gets selected.
45
+ */
46
+ readonly onChange?: (value?: Date | null) => void;
47
+ }
48
+ interface InputTimeDevControlled extends InputTimeBaseProps {
49
+ /**
50
+ * The value shown on the field. This gets automatically formatted to the
51
+ * account's time format.
52
+ */
53
+ readonly value: Date | string | undefined;
54
+ /**
55
+ * The callback that fires whenever a time gets selected.
56
+ */
57
+ readonly onChange: (value?: Date) => void;
58
+ }
59
+ export type InputTimeProps = XOR<InputTimeFormControlled, InputTimeDevControlled>;
60
+ export declare function InputTime(props: InputTimeProps): JSX.Element;
61
+ export {};
@@ -0,0 +1,6 @@
1
+ export declare const styles: {
2
+ container: {
3
+ width: string;
4
+ justifyContent: "center";
5
+ };
6
+ };
@@ -0,0 +1,2 @@
1
+ export { InputTime } from "./InputTime";
2
+ export { roundUpToNearestMinutes } from "./utils";
@@ -0,0 +1,7 @@
1
+ export declare const messages: {
2
+ time: {
3
+ id: string;
4
+ defaultMessage: string;
5
+ description: string;
6
+ };
7
+ };
@@ -0,0 +1,11 @@
1
+ export type MinutesIncrement = 15 | 30 | 60;
2
+ /**
3
+ * Rounds up the time by increment.
4
+ * - 15 mins - rounds to the next quarter time of `00:15`, `00:30`, `00:45`,
5
+ * and `01:00`
6
+ * - 30 mins - rounds to the next half hour be it `00:30` or `01:00`
7
+ * - 60 mins - rounds to the next hour. I.e., `02:01` gets rounded up
8
+ * to `03:00`.
9
+ */
10
+ export declare function roundUpToNearestMinutes(date: Date, minutes: MinutesIncrement): Date;
11
+ export declare function getTimeZoneOffsetInMinutes(timeZone: string, date?: Date): number;
@@ -18,10 +18,12 @@ export * from "./Heading";
18
18
  export * from "./Icon";
19
19
  export * from "./IconButton";
20
20
  export * from "./InputFieldWrapper";
21
+ export * from "./InputCurrency";
21
22
  export * from "./InputNumber";
22
23
  export * from "./InputPassword";
23
24
  export * from "./InputPressable";
24
25
  export * from "./InputSearch";
26
+ export * from "./InputTime";
25
27
  export * from "./InputText";
26
28
  export * from "./TextList";
27
29
  export * from "./ProgressBar";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.33.1",
3
+ "version": "0.35.0",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -67,8 +67,12 @@
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@babel/core": "^7.4.5",
70
+ "@react-native-community/datetimepicker": ">=6.7.0",
71
+ "date-fns": "^2.0.0",
72
+ "date-fns-tz": "*",
70
73
  "react": "^18",
71
- "react-native": ">=0.69.2"
74
+ "react-native": ">=0.69.2",
75
+ "react-native-modal-datetime-picker": " >=13.0.0"
72
76
  },
73
- "gitHead": "c2fde72a97bf0054a5d4d8fe37563b60573b5766"
77
+ "gitHead": "5028c2bd4c97479b567b031432a331e3884afcaa"
74
78
  }
@@ -19,6 +19,7 @@ const providerValues: AtlantisContextProps = {
19
19
  return;
20
20
  },
21
21
  floatSeparators: { decimal: ".", group: "," },
22
+ currencySymbol: "€",
22
23
  };
23
24
 
24
25
  describe("AtlantisContext", () => {
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
2
  import { createContext, useContext } from "react";
3
3
  import RNLocalize from "react-native-localize";
4
+ import { DEFAULT_CURRENCY_SYMBOL } from "../InputCurrency/constants";
4
5
 
5
6
  export interface AtlantisContextProps {
6
7
  /**
@@ -34,6 +35,11 @@ export interface AtlantisContextProps {
34
35
  * Grabs the decimal separator and group separator based on locale
35
36
  */
36
37
  readonly floatSeparators: Record<"decimal" | "group", string>;
38
+
39
+ /**
40
+ * The currency symbol Atlantis components will use
41
+ */
42
+ readonly currencySymbol: string;
37
43
  }
38
44
 
39
45
  export const defaultValues: AtlantisContextProps = {
@@ -45,6 +51,7 @@ export const defaultValues: AtlantisContextProps = {
45
51
  return;
46
52
  },
47
53
  floatSeparators: { group: ",", decimal: "." },
54
+ currencySymbol: DEFAULT_CURRENCY_SYMBOL,
48
55
  };
49
56
 
50
57
  export const AtlantisContext = createContext(defaultValues);
@@ -0,0 +1,158 @@
1
+ import React from "react";
2
+ import { fireEvent, render, waitFor } from "@testing-library/react-native";
3
+ import RNLocalize from "react-native-localize";
4
+ import { InputCurrency } from "./InputCurrency";
5
+ import { AtlantisContext, AtlantisContextProps } from "../AtlantisContext";
6
+
7
+ const mockCurrencySymbol = "£";
8
+ const atlantisContext: AtlantisContextProps = {
9
+ currencySymbol: mockCurrencySymbol,
10
+ timeFormat: "p",
11
+ timeZone: RNLocalize.getTimeZone(),
12
+ isOnline: true,
13
+ onLogError: err => {
14
+ return err;
15
+ },
16
+ floatSeparators: { group: ",", decimal: "." },
17
+ };
18
+ const placeHolder = "Price";
19
+ describe.each([{ includeATLContext: true }, { includeATLContext: false }])(
20
+ "Has AtlantisContext: $includeATLContext",
21
+ ({ includeATLContext }) => {
22
+ function setup({
23
+ showCurrencySymbol,
24
+ name,
25
+ }: {
26
+ showCurrencySymbol?: boolean;
27
+ name: string;
28
+ }) {
29
+ if (includeATLContext) {
30
+ return render(
31
+ <AtlantisContext.Provider value={atlantisContext}>
32
+ <InputCurrency
33
+ placeholder={placeHolder}
34
+ name={name}
35
+ showCurrencySymbol={showCurrencySymbol}
36
+ />
37
+ </AtlantisContext.Provider>,
38
+ );
39
+ } else {
40
+ return render(
41
+ <InputCurrency
42
+ placeholder={placeHolder}
43
+ name={name}
44
+ showCurrencySymbol={showCurrencySymbol}
45
+ />,
46
+ );
47
+ }
48
+ }
49
+
50
+ it("renders with currency symbol when the value is input", () => {
51
+ const { getByText, getByLabelText } = setup({
52
+ name: "sample",
53
+ });
54
+ const value = 123.459119;
55
+ const expectedCurrencySymbol = includeATLContext
56
+ ? mockCurrencySymbol
57
+ : "$";
58
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
59
+
60
+ expect(getByText(expectedCurrencySymbol)).toBeDefined();
61
+ });
62
+
63
+ it("renders without currency symbol", () => {
64
+ const { queryByText, getByLabelText } = setup({
65
+ name: "sample",
66
+ showCurrencySymbol: false,
67
+ });
68
+ const value = 123.459119;
69
+
70
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
71
+ expect(queryByText(mockCurrencySymbol)).toBeNull();
72
+ });
73
+
74
+ it("displays a maximum 5 decimal places", async () => {
75
+ const value = 123.459119;
76
+ const { getByLabelText, getByDisplayValue } = setup({
77
+ name: "sample",
78
+ });
79
+
80
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
81
+ fireEvent(getByLabelText("Price"), "blur");
82
+ fireEvent(getByLabelText("Price"), "blur");
83
+
84
+ await waitFor(() => {
85
+ expect(getByDisplayValue("123.45912")).toBeDefined();
86
+ });
87
+ });
88
+
89
+ it("displays a minimum of 2 decimal places", async () => {
90
+ const value = 123.11;
91
+ const { getByLabelText, getByDisplayValue } = setup({ name: "sample" });
92
+
93
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
94
+ fireEvent(getByLabelText("Price"), "blur");
95
+
96
+ await waitFor(() => {
97
+ expect(getByDisplayValue("123.11")).toBeDefined();
98
+ });
99
+ });
100
+
101
+ it("displays a negative value", async () => {
102
+ const value = -509.543;
103
+ const { getByLabelText, getByDisplayValue } = setup({ name: "sample" });
104
+
105
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
106
+ fireEvent(getByLabelText("Price"), "blur");
107
+
108
+ await waitFor(() => {
109
+ expect(getByDisplayValue(`${value}`)).toBeDefined();
110
+ });
111
+ });
112
+
113
+ it("internationalizes the display Value in the input", async () => {
114
+ const value = 5098.543;
115
+ const { getByLabelText, getByDisplayValue } = setup({ name: "sample" });
116
+
117
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
118
+ fireEvent(getByLabelText("Price"), "blur");
119
+
120
+ await waitFor(() => {
121
+ expect(getByDisplayValue("5,098.543")).toBeDefined();
122
+ });
123
+ });
124
+
125
+ it("limits the whole number integer to 10 whole integers by default", async () => {
126
+ const value = 12345678998;
127
+ const { getByLabelText, getByDisplayValue } = setup({ name: "sample" });
128
+
129
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
130
+
131
+ await waitFor(() => {
132
+ expect(getByDisplayValue("1,234,567,899")).toBeDefined();
133
+ });
134
+ });
135
+
136
+ it("rounds the decimal point if there are more decimal numbers than the maxDecimalCount", async () => {
137
+ const value = 123456.789988;
138
+ const { getByLabelText, getByDisplayValue } = setup({ name: "sample" });
139
+
140
+ fireEvent.changeText(getByLabelText(placeHolder), `${value}`);
141
+ fireEvent(getByLabelText("Price"), "blur");
142
+
143
+ await waitFor(() => {
144
+ expect(getByDisplayValue("123,456.78999")).toBeDefined();
145
+ });
146
+ });
147
+
148
+ it("displays 0 on blur if there is no inputted value or the field.value is undefined", async () => {
149
+ const { getByLabelText, getByDisplayValue } = setup({ name: "sample" });
150
+
151
+ fireEvent(getByLabelText("Price"), "blur");
152
+
153
+ await waitFor(() => {
154
+ expect(getByDisplayValue("0")).toBeDefined();
155
+ });
156
+ });
157
+ },
158
+ );
@@ -0,0 +1,206 @@
1
+ import React, { useState } from "react";
2
+ import { FormatNumberOptions, useIntl } from "react-intl";
3
+ import { Platform } from "react-native";
4
+ import { ControllerRenderProps, FieldValues } from "react-hook-form";
5
+ import {
6
+ NUMBER_VALIDATION_REGEX,
7
+ checkLastChar,
8
+ configureDecimal,
9
+ convertToNumber,
10
+ isValidNumber,
11
+ limitInputWholeDigits,
12
+ parseGivenInput,
13
+ } from "./utils";
14
+ import { messages } from "./messages";
15
+ import { useAtlantisContext } from "../AtlantisContext";
16
+ import { InputText, InputTextProps } from "../InputText";
17
+ import { useFormController } from "../hooks/useFormController";
18
+
19
+ export interface InputCurrencyProps
20
+ extends Omit<
21
+ InputTextProps,
22
+ "keyboard" | "onChangeText" | "value" | "defaultValue"
23
+ > {
24
+ /**
25
+ * Whether to display the user's currency symbol or not
26
+ * Default value is true
27
+ */
28
+ readonly showCurrencySymbol?: boolean;
29
+
30
+ /**
31
+ * The minimum decimal places for the currency number
32
+ * Default value is 2
33
+ */
34
+ readonly decimalPlaces?: number;
35
+
36
+ /**
37
+ * The maximum decimal places for the currency number
38
+ * Default value is 5
39
+ */
40
+ readonly maxDecimalPlaces?: number;
41
+
42
+ /**
43
+ * The maximum length of the input
44
+ * Default value is 10
45
+ */
46
+ readonly maxLength?: number;
47
+
48
+ onChange?(newValue?: number | string | undefined): void;
49
+ value?: number;
50
+ defaultValue?: number;
51
+ keyboard?: "decimal-pad" | "numbers-and-punctuation";
52
+ }
53
+ export const getInternalValue = (
54
+ props: InputCurrencyProps,
55
+ field: ControllerRenderProps<FieldValues, string>,
56
+ formatNumber: (
57
+ value: number | bigint,
58
+ opts?: FormatNumberOptions | undefined,
59
+ ) => string,
60
+ ): string => {
61
+ if (!props.value && !field.value) return "";
62
+ return (
63
+ props.value?.toString() ??
64
+ formatNumber(field.value, {
65
+ maximumFractionDigits: props.maxDecimalPlaces,
66
+ })
67
+ );
68
+ };
69
+
70
+ const getKeyboard = (props: InputCurrencyProps) => {
71
+ if (Platform.OS === "ios") {
72
+ //since we are checking for which keyboard to use here, just implement default keyboard here instead of in params
73
+ return props.keyboard ?? "numbers-and-punctuation";
74
+ } else {
75
+ return "numeric";
76
+ }
77
+ };
78
+
79
+ export function InputCurrency(props: InputCurrencyProps): JSX.Element {
80
+ const {
81
+ showCurrencySymbol = true,
82
+ maxDecimalPlaces = 5,
83
+ decimalPlaces = 2,
84
+ maxLength = 10,
85
+ } = props;
86
+ const intl = useIntl();
87
+ const { floatSeparators, currencySymbol } = useAtlantisContext();
88
+
89
+ const { field } = useFormController({
90
+ name: props.name,
91
+ value: props.value,
92
+ });
93
+ const internalValue = getInternalValue(props, field, intl.formatNumber);
94
+ const [displayValue, setDisplayValue] = useState<string | undefined>(
95
+ internalValue,
96
+ );
97
+
98
+ const setOnChangeAndDisplayValues = (
99
+ onChangeValue: number | string | undefined,
100
+ valueToDisplay: string | undefined,
101
+ ) => {
102
+ props.onChange?.(onChangeValue);
103
+ setDisplayValue(valueToDisplay);
104
+ };
105
+
106
+ const checkDecimalAndI18nOfDisplayValue = (
107
+ numberedValue: number,
108
+ decimalNumbers: string,
109
+ decimalCount: number,
110
+ ) => {
111
+ const transformedValue = limitInputWholeDigits(numberedValue, maxLength);
112
+ const stringValue =
113
+ decimalNumbers !== ""
114
+ ? transformedValue.toString() + "." + decimalNumbers.slice(1)
115
+ : transformedValue.toString();
116
+ if (checkLastChar(stringValue)) {
117
+ const roundedDecimal = configureDecimal(
118
+ decimalCount,
119
+ maxDecimalPlaces,
120
+ stringValue,
121
+ decimalPlaces,
122
+ );
123
+ const internationalizedValueToDisplay = intl.formatNumber(
124
+ roundedDecimal,
125
+ {
126
+ maximumFractionDigits: maxDecimalPlaces,
127
+ },
128
+ );
129
+ setOnChangeAndDisplayValues(
130
+ roundedDecimal,
131
+ internationalizedValueToDisplay,
132
+ );
133
+ } else {
134
+ const internationalizedValueToDisplay =
135
+ intl.formatNumber(transformedValue, {
136
+ maximumFractionDigits: maxDecimalPlaces,
137
+ }) + decimalNumbers;
138
+ setOnChangeAndDisplayValues(
139
+ transformedValue.toString() + "." + decimalNumbers.slice(1),
140
+ internationalizedValueToDisplay,
141
+ );
142
+ }
143
+ };
144
+
145
+ const handleChange = (newValue: string | undefined) => {
146
+ const [decimalCount, wholeIntegerValue, decimalNumbers] = parseGivenInput(
147
+ newValue,
148
+ floatSeparators.decimal,
149
+ );
150
+
151
+ const numberedValue = wholeIntegerValue
152
+ ? convertToNumber(wholeIntegerValue)
153
+ : wholeIntegerValue;
154
+
155
+ if (isValidNumber(numberedValue) && typeof numberedValue === "number") {
156
+ checkDecimalAndI18nOfDisplayValue(
157
+ numberedValue,
158
+ decimalNumbers,
159
+ decimalCount,
160
+ );
161
+ } else {
162
+ const value = numberedValue?.toString() + decimalNumbers;
163
+ setOnChangeAndDisplayValues(value, value);
164
+ }
165
+ };
166
+
167
+ const { formatMessage } = useIntl();
168
+
169
+ return (
170
+ <>
171
+ <InputText
172
+ {...props}
173
+ prefix={showCurrencySymbol ? { label: currencySymbol } : undefined}
174
+ keyboard={getKeyboard(props)}
175
+ value={props.value?.toString() || displayValue}
176
+ defaultValue={props.defaultValue?.toString()}
177
+ onChangeText={handleChange}
178
+ transform={{
179
+ output: val => {
180
+ return val
181
+ ?.split(floatSeparators.group)
182
+ .join("")
183
+ .replace(floatSeparators.decimal, ".");
184
+ },
185
+ }}
186
+ validations={{
187
+ pattern: {
188
+ value: NUMBER_VALIDATION_REGEX,
189
+ message: formatMessage(messages.notANumberError),
190
+ },
191
+ ...props.validations,
192
+ }}
193
+ onBlur={() => {
194
+ props.onBlur?.();
195
+ if (
196
+ field.value === 0 ||
197
+ field.value === "" ||
198
+ field.value === undefined
199
+ ) {
200
+ setDisplayValue("0");
201
+ }
202
+ }}
203
+ />
204
+ </>
205
+ );
206
+ }
@@ -0,0 +1 @@
1
+ export const DEFAULT_CURRENCY_SYMBOL = "$";
@@ -0,0 +1,3 @@
1
+ export { InputCurrency } from "./InputCurrency";
2
+ export * from "./utils";
3
+ export * from "./constants";
@@ -0,0 +1,10 @@
1
+ import { defineMessages } from "react-intl";
2
+
3
+ export const messages = defineMessages({
4
+ notANumberError: {
5
+ id: "notANumberError",
6
+ defaultMessage: "Enter a number",
7
+ description:
8
+ "Error message shown when a non-numeric value is typed in number input",
9
+ },
10
+ });