@jobber/components-native 0.86.1-JOB-136074-a7a2b82.0 → 0.86.2-JOB-89949-9a8f51e.6

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 (47) hide show
  1. package/dist/package.json +3 -9
  2. package/dist/src/Form/Form.js +1 -4
  3. package/dist/src/Form/components/FormCache/FormCache.js +1 -0
  4. package/dist/src/InputCurrency/InputCurrency.js +42 -30
  5. package/dist/src/InputEmail/InputEmail.js +12 -4
  6. package/dist/src/InputNumber/InputNumber.js +10 -4
  7. package/dist/src/Typography/Typography.js +13 -2
  8. package/dist/src/hooks/useAtlantisI18n/locales/en.json +1 -0
  9. package/dist/src/hooks/useAtlantisI18n/locales/es.json +1 -0
  10. package/dist/src/hooks/useFormController.js +5 -14
  11. package/dist/tsconfig.build.tsbuildinfo +1 -1
  12. package/dist/types/src/ActionLabel/ActionLabel.d.ts +3 -2
  13. package/dist/types/src/Form/components/FormCache/FormCache.d.ts +2 -2
  14. package/dist/types/src/Form/context/types.d.ts +2 -2
  15. package/dist/types/src/Form/hooks/useInternalForm.d.ts +2 -2
  16. package/dist/types/src/Form/types.d.ts +3 -3
  17. package/dist/types/src/Heading/Heading.d.ts +3 -2
  18. package/dist/types/src/Text/Text.d.ts +3 -2
  19. package/dist/types/src/Typography/Typography.d.ts +2 -2
  20. package/package.json +3 -9
  21. package/src/ActionLabel/ActionLabel.test.tsx +12 -0
  22. package/src/ActionLabel/ActionLabel.tsx +2 -2
  23. package/src/ActionLabel/__snapshots__/ActionLabel.test.tsx.snap +66 -0
  24. package/src/Form/Form.tsx +0 -6
  25. package/src/Form/components/FormCache/FormCache.tsx +4 -3
  26. package/src/Form/context/types.ts +2 -2
  27. package/src/Form/hooks/useInternalForm.ts +2 -1
  28. package/src/Form/types.ts +3 -4
  29. package/src/Heading/Heading.test.tsx +13 -0
  30. package/src/Heading/Heading.tsx +2 -2
  31. package/src/Heading/__snapshots__/Heading.test.tsx.snap +65 -0
  32. package/src/InputCurrency/InputCurrency.tsx +71 -57
  33. package/src/InputEmail/InputEmail.tsx +15 -8
  34. package/src/InputNumber/InputNumber.tsx +11 -7
  35. package/src/Text/Text.test.tsx +21 -0
  36. package/src/Text/Text.tsx +2 -2
  37. package/src/Text/__snapshots__/Text.test.tsx.snap +104 -0
  38. package/src/Typography/Typography.test.tsx +61 -0
  39. package/src/Typography/Typography.tsx +20 -4
  40. package/src/Typography/__snapshots__/Typography.test.tsx.snap +222 -0
  41. package/src/hooks/useAtlantisI18n/locales/en.json +1 -0
  42. package/src/hooks/useAtlantisI18n/locales/es.json +1 -0
  43. package/src/hooks/useFormController.ts +6 -13
  44. package/dist/src/utils/buildConfig/isEdgeToEdgeEnabled.js +0 -17
  45. package/dist/types/src/utils/buildConfig/isEdgeToEdgeEnabled.d.ts +0 -1
  46. package/src/types/buildConfig.d.ts +0 -7
  47. package/src/utils/buildConfig/isEdgeToEdgeEnabled.ts +0 -20
@@ -1,11 +1,12 @@
1
+ import React from "react";
1
2
  import type { TextAlign, TextColor } from "../Typography";
2
3
  export type ActionLabelVariation = Extract<TextColor, "interactive" | "destructive" | "learning" | "subtle" | "onPrimary">;
3
4
  type ActionLabelType = "default" | "cardTitle";
4
5
  interface ActionLabelProps {
5
6
  /**
6
- * Text to display
7
+ * Text to display. Supports strings, numbers, and nested text nodes.
7
8
  */
8
- readonly children?: string;
9
+ readonly children?: React.ReactNode;
9
10
  /**
10
11
  * Set the display text to disabled color
11
12
  */
@@ -1,9 +1,9 @@
1
- import type { FieldValues } from "react-hook-form";
1
+ import type { DeepPartial, FieldValues } from "react-hook-form";
2
2
  interface FormCacheProps<T extends FieldValues> {
3
3
  readonly localCacheId?: string | string[];
4
4
  readonly localCacheKey?: string;
5
5
  readonly localCacheExclude?: string[];
6
- readonly setLocalCache: (data: T) => void;
6
+ readonly setLocalCache: (data: DeepPartial<T>) => void;
7
7
  }
8
8
  export declare function FormCache<T extends FieldValues>({ localCacheExclude, localCacheKey, setLocalCache, }: FormCacheProps<T>): JSX.Element;
9
9
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { MutableRefObject } from "react";
2
- import type { FieldValues, UseFormReturn } from "react-hook-form";
2
+ import type { DeepPartial, FieldValues, UseFormReturn } from "react-hook-form";
3
3
  export interface UseConfirmBeforeBackProps {
4
4
  alwaysPreventBack: boolean;
5
5
  onAcceptEvent?: () => void;
@@ -18,7 +18,7 @@ interface LocalCacheOptions {
18
18
  export interface AtlantisFormContextProps {
19
19
  useConfirmBeforeBack: (props: UseConfirmBeforeBackProps) => MutableRefObject<() => void>;
20
20
  useInternalFormLocalCache: <TData extends FieldValues>(formMethods: UseFormReturn<TData>, cacheKey?: string, options?: LocalCacheOptions) => {
21
- setLocalCache: (data: TData) => void;
21
+ setLocalCache: (data: DeepPartial<TData>) => void;
22
22
  removeLocalCache: () => void;
23
23
  };
24
24
  }
@@ -1,4 +1,4 @@
1
- import type { FieldValues, UseFormHandleSubmit, UseFormReturn } from "react-hook-form";
1
+ import type { DeepPartial, FieldValues, UseFormHandleSubmit, UseFormReturn } from "react-hook-form";
2
2
  import type { MutableRefObject, RefObject } from "react";
3
3
  import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
4
4
  import type { InternalFormProps } from "../types";
@@ -13,7 +13,7 @@ interface UseInternalForm<T extends FieldValues> {
13
13
  readonly isSubmitting: boolean;
14
14
  readonly isDirty: boolean;
15
15
  readonly removeListenerRef: MutableRefObject<() => void>;
16
- readonly setLocalCache: (data: T) => void;
16
+ readonly setLocalCache: (data: DeepPartial<T>) => void;
17
17
  }
18
18
  export declare function useInternalForm<T extends FieldValues, SubmitResponseType>({ mode, reValidateMode, initialValues, formRef, localCacheKey, localCacheId, scrollViewRef, saveButtonHeight, messageBannerHeight, }: UseInternalFormProps<T, SubmitResponseType>): UseInternalForm<T>;
19
19
  export {};
@@ -1,8 +1,8 @@
1
1
  import type { MutableRefObject, RefObject } from "react";
2
- import type { ControllerProps, DeepPartial, FieldPath, FieldValues, Mode, UnpackNestedValue, UseFormReturn } from "react-hook-form";
2
+ import type { ControllerProps, DefaultValues, FieldPath, FieldValues, Mode, UseFormReturn } from "react-hook-form";
3
3
  import type { IconNames } from "@jobber/design";
4
4
  import type { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
5
- export type FormValues<T> = UnpackNestedValue<T>;
5
+ export type FormValues<T> = T;
6
6
  export type FormErrors = FormNetworkErrors | FormUserErrors;
7
7
  export type FormBannerMessage = FormWarningMessage | FormNoticeMessage;
8
8
  export declare enum FormSubmitErrorType {
@@ -81,7 +81,7 @@ export interface FormProps<T extends FieldValues, SubmitResponseType> {
81
81
  * The initial values of the form inputs
82
82
  * This should be available as soon as initialLoading is set to false
83
83
  */
84
- initialValues?: FormValues<DeepPartial<T>>;
84
+ initialValues?: DefaultValues<T>;
85
85
  /**
86
86
  * When the validation should happen.
87
87
  * Possible values are "onBlur", "onChange", "onSubmit", "onTouched", and "all".
@@ -1,11 +1,12 @@
1
+ import React from "react";
1
2
  import type { TextAlign, TextColor, TruncateLength, TypographyProps } from "../Typography";
2
3
  type HeadingColor = Extract<TextColor, "text" | "subdued" | "heading">;
3
4
  export type HeadingLevel = "title" | "subtitle" | "heading" | "subHeading";
4
5
  interface HeadingProps<T extends HeadingLevel> extends Pick<TypographyProps<"base">, "selectable"> {
5
6
  /**
6
- * Text to display.
7
+ * Text to display. Supports strings, numbers, and nested text nodes.
7
8
  */
8
- readonly children: string;
9
+ readonly children: React.ReactNode;
9
10
  /**
10
11
  * The type of heading, e.g., "Title"
11
12
  */
@@ -1,3 +1,4 @@
1
+ import React from "react";
1
2
  import type { OnTextLayoutEvent, TextAlign, TextVariation, TruncateLength, TypographyProps } from "../Typography";
2
3
  import type { TypographyUnsafeStyle } from "../Typography/Typography";
3
4
  export interface TextProps extends Pick<TypographyProps<"base">, "maxFontScaleSize" | "selectable"> {
@@ -31,9 +32,9 @@ export interface TextProps extends Pick<TypographyProps<"base">, "maxFontScaleSi
31
32
  */
32
33
  readonly align?: TextAlign;
33
34
  /**
34
- * Text to display
35
+ * Text to display. Supports strings, numbers, and nested text nodes.
35
36
  */
36
- readonly children?: string;
37
+ readonly children?: React.ReactNode;
37
38
  /**
38
39
  * Reverse theme for better display on dark background
39
40
  */
@@ -24,9 +24,9 @@ export interface TypographyProps<T extends FontFamily> {
24
24
  */
25
25
  readonly size?: TextSize;
26
26
  /**
27
- * Text to display
27
+ * Text to display. Supports strings, numbers, and nested text nodes.
28
28
  */
29
- readonly children?: string;
29
+ readonly children?: React.ReactNode;
30
30
  /**
31
31
  * The maximum amount of lines the text can occupy before being truncated with "...".
32
32
  * Uses predefined string values that correspond to a doubling scale for the amount of lines.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components-native",
3
- "version": "0.86.1-JOB-136074-a7a2b82.0+a7a2b8288",
3
+ "version": "0.86.2-JOB-89949-9a8f51e.6+9a8f51e76",
4
4
  "license": "MIT",
5
5
  "description": "React Native implementation of Atlantis",
6
6
  "repository": {
@@ -43,7 +43,7 @@
43
43
  "autolinker": "^4.0.0",
44
44
  "deepmerge": "^4.2.2",
45
45
  "lodash": "^4.17.21",
46
- "react-hook-form": "^7.30.0",
46
+ "react-hook-form": "^7.52.0",
47
47
  "react-intl": "^6.4.2",
48
48
  "react-native-keyboard-aware-scroll-view": "^0.9.5",
49
49
  "react-native-modalize": "^2.0.13",
@@ -86,7 +86,6 @@
86
86
  "react": "^18.2.0",
87
87
  "react-intl": "^6.4.2",
88
88
  "react-native": ">=0.76.0",
89
- "react-native-build-config": "^0.3.2",
90
89
  "react-native-gesture-handler": ">=2.10.0",
91
90
  "react-native-keyboard-aware-scroll-view": "^0.9.5",
92
91
  "react-native-modal-datetime-picker": " >=13.0.0",
@@ -96,10 +95,5 @@
96
95
  "react-native-safe-area-context": "^5.4.0",
97
96
  "react-native-svg": ">=12.0.0"
98
97
  },
99
- "peerDependenciesMeta": {
100
- "react-native-build-config": {
101
- "optional": true
102
- }
103
- },
104
- "gitHead": "a7a2b828816e7a2eae21ead0e0eba40d494e284a"
98
+ "gitHead": "9a8f51e766a4207a1f53367c96c62685d336868d"
105
99
  }
@@ -117,6 +117,18 @@ describe("ActionLabel", () => {
117
117
  });
118
118
  });
119
119
  });
120
+
121
+ it("supports nested inline content", () => {
122
+ const { Text } = require("../Text");
123
+ const { getByText, toJSON } = render(
124
+ <ActionLabel>
125
+ Before <Text variation="interactive">Inner</Text> After
126
+ </ActionLabel>,
127
+ );
128
+
129
+ expect(getByText("Inner")).toBeDefined();
130
+ expect(toJSON()).toMatchSnapshot();
131
+ });
120
132
  });
121
133
 
122
134
  function getStyleObject(el: ReactTestInstance) {
@@ -12,9 +12,9 @@ type ActionLabelType = "default" | "cardTitle";
12
12
 
13
13
  interface ActionLabelProps {
14
14
  /**
15
- * Text to display
15
+ * Text to display. Supports strings, numbers, and nested text nodes.
16
16
  */
17
- readonly children?: string;
17
+ readonly children?: React.ReactNode;
18
18
 
19
19
  /**
20
20
  * Set the display text to disabled color
@@ -0,0 +1,66 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ActionLabel supports nested inline content 1`] = `
4
+ <Text
5
+ accessibilityRole="text"
6
+ adjustsFontSizeToFit={false}
7
+ allowFontScaling={true}
8
+ maxFontSizeMultiplier={1.125}
9
+ selectable={false}
10
+ selectionColor="hsl(86, 100%, 46%)"
11
+ style={
12
+ [
13
+ {
14
+ "fontFamily": "inter-semibold",
15
+ },
16
+ {
17
+ "color": "hsl(107, 58%, 33%)",
18
+ },
19
+ {
20
+ "textAlign": "center",
21
+ },
22
+ {
23
+ "fontSize": 16,
24
+ "lineHeight": 18,
25
+ },
26
+ {
27
+ "letterSpacing": 0,
28
+ },
29
+ ]
30
+ }
31
+ >
32
+ Before
33
+ <Text
34
+ accessibilityRole="text"
35
+ adjustsFontSizeToFit={false}
36
+ allowFontScaling={true}
37
+ collapsable={false}
38
+ maxFontSizeMultiplier={3.125}
39
+ selectable={true}
40
+ selectionColor="hsl(86, 100%, 46%)"
41
+ style={
42
+ [
43
+ {
44
+ "fontFamily": "inter-regular",
45
+ },
46
+ {
47
+ "color": "hsl(107, 58%, 33%)",
48
+ },
49
+ {
50
+ "textAlign": "left",
51
+ },
52
+ {
53
+ "fontSize": 16,
54
+ "lineHeight": 20,
55
+ },
56
+ {
57
+ "letterSpacing": 0,
58
+ },
59
+ ]
60
+ }
61
+ >
62
+ Inner
63
+ </Text>
64
+ After
65
+ </Text>
66
+ `;
package/src/Form/Form.tsx CHANGED
@@ -26,7 +26,6 @@ import { useScrollToError } from "./hooks/useScrollToError";
26
26
  import { FormSaveButton } from "./components/FormSaveButton";
27
27
  import { useSaveButtonPosition } from "./hooks/useSaveButtonPosition";
28
28
  import { FormCache } from "./components/FormCache/FormCache";
29
- import { isEdgeToEdgeEnabled } from "../utils/buildConfig/isEdgeToEdgeEnabled";
30
29
  import { InputAccessoriesProvider } from "../InputText";
31
30
  import { tokens } from "../utils/design";
32
31
  import { ErrorMessageProvider } from "../ErrorMessageWrapper";
@@ -135,9 +134,6 @@ function InternalForm<T extends FieldValues, S>({
135
134
 
136
135
  const styles = useStyles();
137
136
 
138
- // Check if edge-to-edge is enabled using utility function
139
- const edgeToEdgeEnabled = isEdgeToEdgeEnabled();
140
-
141
137
  return (
142
138
  <FormProvider {...formMethods}>
143
139
  <>
@@ -165,7 +161,6 @@ function InternalForm<T extends FieldValues, S>({
165
161
  <KeyboardAwareScrollView
166
162
  enableResetScrollToCoords={false}
167
163
  enableAutomaticScroll={true}
168
- enableOnAndroid={edgeToEdgeEnabled}
169
164
  keyboardOpeningTime={
170
165
  Platform.OS === "ios" ? tokens["timing-slowest"] : 0
171
166
  }
@@ -173,7 +168,6 @@ function InternalForm<T extends FieldValues, S>({
173
168
  ref={scrollViewRef}
174
169
  {...keyboardProps}
175
170
  extraHeight={headerHeight}
176
- extraScrollHeight={edgeToEdgeEnabled ? 20 : 0}
177
171
  contentContainerStyle={
178
172
  !keyboardHeight && styles.scrollContentContainer
179
173
  }
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo } from "react";
2
- import type { FieldValues } from "react-hook-form";
2
+ import type { DeepPartial, FieldValues } from "react-hook-form";
3
3
  import { useFormContext, useWatch } from "react-hook-form";
4
4
  import omit from "lodash/omit";
5
5
 
@@ -7,7 +7,7 @@ interface FormCacheProps<T extends FieldValues> {
7
7
  readonly localCacheId?: string | string[];
8
8
  readonly localCacheKey?: string;
9
9
  readonly localCacheExclude?: string[];
10
- readonly setLocalCache: (data: T) => void;
10
+ readonly setLocalCache: (data: DeepPartial<T>) => void;
11
11
  }
12
12
 
13
13
  export function FormCache<T extends FieldValues>({
@@ -18,7 +18,7 @@ export function FormCache<T extends FieldValues>({
18
18
  const { control, formState } = useFormContext<T>();
19
19
  const { isDirty } = formState;
20
20
 
21
- const formData = useWatch<T>({ control });
21
+ const formData = useWatch({ control });
22
22
  const shouldExclude = useMemo(() => {
23
23
  return Array.isArray(localCacheExclude) && localCacheExclude.length > 0;
24
24
  }, [localCacheExclude]);
@@ -46,5 +46,6 @@ export function FormCache<T extends FieldValues>({
46
46
  }
47
47
  }, [formData, isDirty, localCacheExclude, setLocalCache, shouldExclude]);
48
48
 
49
+ // eslint-disable-next-line react/jsx-no-useless-fragment
49
50
  return <></>;
50
51
  }
@@ -1,5 +1,5 @@
1
1
  import type { MutableRefObject } from "react";
2
- import type { FieldValues, UseFormReturn } from "react-hook-form";
2
+ import type { DeepPartial, FieldValues, UseFormReturn } from "react-hook-form";
3
3
 
4
4
  export interface UseConfirmBeforeBackProps {
5
5
  alwaysPreventBack: boolean;
@@ -27,7 +27,7 @@ export interface AtlantisFormContextProps {
27
27
  cacheKey?: string,
28
28
  options?: LocalCacheOptions,
29
29
  ) => {
30
- setLocalCache: (data: TData) => void;
30
+ setLocalCache: (data: DeepPartial<TData>) => void;
31
31
  removeLocalCache: () => void;
32
32
  };
33
33
  }
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ DeepPartial,
2
3
  FieldValues,
3
4
  UseFormHandleSubmit,
4
5
  UseFormReturn,
@@ -31,7 +32,7 @@ interface UseInternalForm<T extends FieldValues> {
31
32
  readonly isSubmitting: boolean;
32
33
  readonly isDirty: boolean;
33
34
  readonly removeListenerRef: MutableRefObject<() => void>;
34
- readonly setLocalCache: (data: T) => void;
35
+ readonly setLocalCache: (data: DeepPartial<T>) => void;
35
36
  }
36
37
 
37
38
  export function useInternalForm<T extends FieldValues, SubmitResponseType>({
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
- DeepPartial,
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> = UnpackNestedValue<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?: FormValues<DeepPartial<T>>;
111
+ initialValues?: DefaultValues<T>;
113
112
 
114
113
  /**
115
114
  * When the validation should happen.
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { render } from "@testing-library/react-native";
3
3
  import { Heading } from "./Heading";
4
+ import { Text } from "../Text";
4
5
 
5
6
  describe("when Heading called with text as the only prop", () => {
6
7
  it("should match snapshot", () => {
@@ -81,3 +82,15 @@ describe("when Heading called with maxLines", () => {
81
82
  expect(view).toMatchSnapshot();
82
83
  });
83
84
  });
85
+
86
+ describe("when Heading contains nested inline text", () => {
87
+ it("renders nested Text within Heading", () => {
88
+ const view = render(
89
+ <Heading>
90
+ Heading before <Text emphasis="strong">Inner</Text> after
91
+ </Heading>,
92
+ ).toJSON();
93
+
94
+ expect(view).toMatchSnapshot();
95
+ });
96
+ });
@@ -19,9 +19,9 @@ export type HeadingLevel = "title" | "subtitle" | "heading" | "subHeading";
19
19
  interface HeadingProps<T extends HeadingLevel>
20
20
  extends Pick<TypographyProps<"base">, "selectable"> {
21
21
  /**
22
- * Text to display.
22
+ * Text to display. Supports strings, numbers, and nested text nodes.
23
23
  */
24
- readonly children: string;
24
+ readonly children: React.ReactNode;
25
25
 
26
26
  /**
27
27
  * The type of heading, e.g., "Title"
@@ -268,3 +268,68 @@ exports[`when Heading called with title variation should match snapshot 1`] = `
268
268
  Title Heading
269
269
  </Text>
270
270
  `;
271
+
272
+ exports[`when Heading contains nested inline text renders nested Text within Heading 1`] = `
273
+ <Text
274
+ accessibilityRole="header"
275
+ adjustsFontSizeToFit={false}
276
+ allowFontScaling={true}
277
+ collapsable={false}
278
+ selectable={true}
279
+ selectionColor="hsl(86, 100%, 46%)"
280
+ style={
281
+ [
282
+ {
283
+ "fontFamily": "inter-bold",
284
+ },
285
+ {
286
+ "color": "hsl(197, 90%, 12%)",
287
+ },
288
+ {
289
+ "textAlign": "left",
290
+ },
291
+ {
292
+ "fontSize": 20,
293
+ "lineHeight": 22,
294
+ },
295
+ {
296
+ "letterSpacing": 0,
297
+ },
298
+ ]
299
+ }
300
+ >
301
+ Heading before
302
+ <Text
303
+ accessibilityRole="text"
304
+ adjustsFontSizeToFit={false}
305
+ allowFontScaling={true}
306
+ collapsable={false}
307
+ maxFontSizeMultiplier={3.125}
308
+ selectable={true}
309
+ selectionColor="hsl(86, 100%, 46%)"
310
+ style={
311
+ [
312
+ {
313
+ "fontFamily": "inter-semibold",
314
+ },
315
+ {
316
+ "color": "hsl(198, 35%, 21%)",
317
+ },
318
+ {
319
+ "textAlign": "left",
320
+ },
321
+ {
322
+ "fontSize": 16,
323
+ "lineHeight": 20,
324
+ },
325
+ {
326
+ "letterSpacing": 0,
327
+ },
328
+ ]
329
+ }
330
+ >
331
+ Inner
332
+ </Text>
333
+ after
334
+ </Text>
335
+ `;
@@ -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
- checkDecimalAndI18nOfDisplayValue(
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
- setOnChangeAndDisplayValues(value, value);
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