diff --git a/package.json b/package.json
index 08e993b3d..2d45ae1df 100644
--- a/package.json
+++ b/package.json
@@ -93,6 +93,7 @@
"react-hook-form": "^7.52.1",
"react-i18next": "^15.0.0",
"react-imask": "^7.6.1",
+ "react-number-format": "^5.4.2",
"react-pdf": "^8.0.2",
"react-router-dom": "^6.25.1",
"typescript": "^5.5.4",
diff --git a/src/components/elements/input/NumberInput.stories.tsx b/src/components/elements/input/NumberInput.stories.tsx
index fa1ca5953..3c7d33fe6 100644
--- a/src/components/elements/input/NumberInput.stories.tsx
+++ b/src/components/elements/input/NumberInput.stories.tsx
@@ -1,4 +1,4 @@
-import { Box } from '@mui/material';
+import { useArgs } from '@storybook/preview-api';
import { Meta, StoryObj } from '@storybook/react';
import NumberInput from './NumberInput';
@@ -6,13 +6,11 @@ import NumberInput from './NumberInput';
export default {
component: NumberInput,
argTypes: { label: { control: 'text' } },
- decorators: [
- (Story) => (
-
-
-
- ),
- ],
+ render: (args: any) => {
+ const [{ value }, updateArgs] = useArgs();
+ const onChange = (event: any) => updateArgs({ value: event.target.value });
+ return ;
+ },
} as Meta;
type Story = StoryObj;
diff --git a/src/components/elements/input/NumberInput.tsx b/src/components/elements/input/NumberInput.tsx
index fbb7e0f1e..4def94c2b 100644
--- a/src/components/elements/input/NumberInput.tsx
+++ b/src/components/elements/input/NumberInput.tsx
@@ -1,38 +1,46 @@
-import { Box } from '@mui/system';
+import { InputAdornment } from '@mui/material';
import { isFinite, isNil } from 'lodash-es';
-import {
- KeyboardEventHandler,
- useCallback,
- useState,
- WheelEventHandler,
-} from 'react';
+import { ChangeEventHandler, useState } from 'react';
+import {
+ NumberFormatValues,
+ NumericFormat,
+ OnValueChange,
+} from 'react-number-format';
import TextInput, { TextInputProps } from './TextInput';
-const NumberInput = ({
+// protect from integer overflows
+const withValueLimit = ({ floatValue }: NumberFormatValues) => {
+ if (floatValue) {
+ return floatValue > 1
+ ? floatValue < Number.MAX_SAFE_INTEGER
+ : floatValue > Number.MIN_SAFE_INTEGER;
+ }
+ return true;
+};
+
+interface Props extends TextInputProps {
+ onChange: ChangeEventHandler;
+ currency?: boolean;
+}
+
+const NumberInput: React.FC = ({
inputProps,
min = 0,
max,
InputProps,
currency = false,
- disableArrowKeys = false,
value,
error,
+ helperText,
ariaLabelledBy,
+ onChange,
+ defaultValue,
+ type,
...props
-}: TextInputProps & { currency?: boolean; disableArrowKeys?: boolean }) => {
+}) => {
const [errorMessage, setErrorMessage] = useState(null);
- const currencyInputProps = currency
- ? {
- startAdornment: $,
- sx: {
- pl: 1,
- '.MuiInputBase-input': { textAlign: 'left' },
- },
- }
- : {};
-
const handleBlur = () => {
if (isNil(value) || value === '') {
setErrorMessage(null);
@@ -61,49 +69,52 @@ const NumberInput = ({
}
};
- // Prevent form submission on Enter. Enter should toggle the state.
- const onKeyDown: KeyboardEventHandler = useCallback((e) => {
- if (e.key.match(/(ArrowDown|ArrowUp)/)) {
- e.preventDefault();
- }
- }, []);
-
- const preventValueChangeOnScroll: WheelEventHandler =
- useCallback((e) => {
- // Prevent the input value change
- (e.target as HTMLInputElement).blur();
+ const decimalScale = currency ? 2 : 0;
+ const prefix = currency ? '$' : undefined;
- // Prevent the page/container scrolling
- e.stopPropagation();
+ const handleChange: OnValueChange = (v) => {
+ const syntheticEvent = {
+ target: {
+ value: v.value,
+ name: props.name, // If you have a name prop
+ },
+ // Add other event properties you might need
+ preventDefault: () => {},
+ stopPropagation: () => {},
+ } as React.ChangeEvent;
- // Refocus immediately, on the next tick (after the current
- // function is done)
- setTimeout(() => {
- (e.target as HTMLInputElement).focus();
- }, 0);
- }, []);
+ onChange(syntheticEvent);
+ };
return (
- {prefix}
+ ) : undefined,
+ ...InputProps,
+ }}
+ defaultValue={(defaultValue || '') as string}
+ type={type as any}
{...props}
- error={error || !!errorMessage}
- // If there is a server error, show that instead of the local message
- helperText={error ? undefined : errorMessage || props.helperText}
/>
);
};
diff --git a/src/modules/form/components/DynamicField.tsx b/src/modules/form/components/DynamicField.tsx
index a8af043eb..d8292298f 100644
--- a/src/modules/form/components/DynamicField.tsx
+++ b/src/modules/form/components/DynamicField.tsx
@@ -278,7 +278,6 @@ const DynamicField: React.FC = ({
onChange={onChangeEvent}
horizontal={horizontal}
currency={item.type === ItemType.Currency}
- disableArrowKeys={item.type === ItemType.Currency}
{...commonInputProps}
inputWidth={120}
/>
diff --git a/yarn.lock b/yarn.lock
index 8a985c0fe..305cd524b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8483,6 +8483,11 @@ react-is@^18.3.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+react-number-format@^5.4.2:
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.2.tgz#aec282241f36cee31da13dc5e0f364c0fc6902ab"
+ integrity sha512-cg//jVdS49PYDgmcYoBnMMHl4XNTMuV723ZnHD2aXYtWWWqbVF3hjQ8iB+UZEuXapLbeA8P8H+1o6ZB1lcw3vg==
+
react-pdf@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-8.0.2.tgz#ff4a0e9260c47f1a261b878a2cc0408080eecc06"