diff --git a/cypress/e2e/content/content.spec.js b/cypress/e2e/content/content.spec.js
index 6871cd1aa8..9c76b18fa4 100644
--- a/cypress/e2e/content/content.spec.js
+++ b/cypress/e2e/content/content.spec.js
@@ -188,7 +188,7 @@ describe("Content Specs", () => {
it("Number Field", () => {
// NOTE: the timestamp is too large for the 'small int' column in the DB
// limit is 4294967295
- cy.get("#12-9b96ec-tll2gn input[type=number]")
+ cy.get("#12-9b96ec-tll2gn input[type=text]")
.focus()
/*
input type='number 'cannot be empty so rather than whitespace, it'd have a value of 0
diff --git a/package-lock.json b/package-lock.json
index 8f2a2fd6ed..b2cb78b641 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -56,6 +56,7 @@
"react-flatpickr": "^3.10.7",
"react-measure": "^2.5.2",
"react-monaco-editor": "^0.47.0",
+ "react-number-format": "^5.3.1",
"react-redux": "^7.2.4",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
@@ -11843,6 +11844,18 @@
"react": "^17.x"
}
},
+ "node_modules/react-number-format": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.1.tgz",
+ "integrity": "sha512-qpYcQLauIeEhCZUZY9jXZnnroOtdy3jYaS1zQ3M1Sr6r/KMOBEIGNIb7eKT19g2N1wbYgFgvDzs19hw5TrB8XQ==",
+ "dependencies": {
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-redux": {
"version": "7.2.8",
"license": "MIT",
@@ -23237,6 +23250,14 @@
"prop-types": "^15.8.1"
}
},
+ "react-number-format": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.1.tgz",
+ "integrity": "sha512-qpYcQLauIeEhCZUZY9jXZnnroOtdy3jYaS1zQ3M1Sr6r/KMOBEIGNIb7eKT19g2N1wbYgFgvDzs19hw5TrB8XQ==",
+ "requires": {
+ "prop-types": "^15.7.2"
+ }
+ },
"react-redux": {
"version": "7.2.8",
"requires": {
diff --git a/package.json b/package.json
index a6697fd282..381be98fc2 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,7 @@
"react-flatpickr": "^3.10.7",
"react-measure": "^2.5.2",
"react-monaco-editor": "^0.47.0",
+ "react-number-format": "^5.3.1",
"react-redux": "^7.2.4",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
diff --git a/src/apps/content-editor/src/app/components/Editor/Field/Field.tsx b/src/apps/content-editor/src/app/components/Editor/Field/Field.tsx
index 033982bfc4..addeee8bdf 100644
--- a/src/apps/content-editor/src/app/components/Editor/Field/Field.tsx
+++ b/src/apps/content-editor/src/app/components/Editor/Field/Field.tsx
@@ -52,6 +52,7 @@ import {
import { FieldTypeDate } from "../../../../../../../shell/components/FieldTypeDate";
import { FieldTypeDateTime } from "../../../../../../../shell/components/FieldTypeDateTime";
import { FieldTypeSort } from "../../../../../../../shell/components/FieldTypeSort";
+import { FieldTypeNumber } from "../../../../../../../shell/components/FieldTypeNumber";
import styles from "./Field.less";
import { MemoryRouter } from "react-router";
@@ -859,16 +860,12 @@ export const Field = ({
case "number":
return (
- onChange(evt.target.value, name)}
- error={errors && Object.values(errors)?.some((error) => !!error)}
+ onChange={onChange}
+ hasError={errors && Object.values(errors)?.some((error) => !!error)}
/>
);
diff --git a/src/shell/components/FieldTypeNumber.tsx b/src/shell/components/FieldTypeNumber.tsx
new file mode 100644
index 0000000000..5948b28d0e
--- /dev/null
+++ b/src/shell/components/FieldTypeNumber.tsx
@@ -0,0 +1,93 @@
+import { useEffect, useRef } from "react";
+import { IconButton, Stack, TextField } from "@mui/material";
+import RemoveRoundedIcon from "@mui/icons-material/RemoveRounded";
+import AddRoundedIcon from "@mui/icons-material/AddRounded";
+
+import { NumberFormatInput } from "./NumberFormatInput";
+
+type FieldTypeNumberProps = {
+ required: boolean;
+ name: string;
+ value: number;
+ onChange: (value: number, name: string) => void;
+ hasError: boolean;
+};
+export const FieldTypeNumber = ({
+ required,
+ value,
+ onChange,
+ name,
+ hasError,
+}: FieldTypeNumberProps) => {
+ const numberInputRef = useRef(null);
+
+ useEffect(() => {
+ if (value === 0) {
+ numberInputRef.current?.setSelectionRange(1, 1);
+ }
+ }, [value]);
+
+ const modifyNumberValue = (action: "increment" | "decrement") => {
+ if (value.toString().includes("e")) return;
+
+ const integerFractionalSplit = value.toString().split(".");
+
+ switch (action) {
+ case "increment":
+ integerFractionalSplit[0] = (+integerFractionalSplit[0] + 1).toString();
+ break;
+
+ case "decrement":
+ integerFractionalSplit[0] = (+integerFractionalSplit[0] - 1).toString();
+ break;
+
+ default:
+ break;
+ }
+
+ onChange(+integerFractionalSplit.join("."), name);
+ };
+
+ return (
+ {
+ onChange(+evt.target.value?.toString()?.replace(/^0+/, "") ?? 0, name);
+ }}
+ onKeyDown={(evt) => {
+ if ((evt.key === "Backspace" || evt.key === "Delete") && value === 0) {
+ evt.preventDefault();
+ }
+ }}
+ error={hasError}
+ InputProps={{
+ endAdornment: (
+
+ modifyNumberValue("decrement")}
+ >
+
+
+ modifyNumberValue("increment")}
+ >
+
+
+
+ ),
+ inputComponent: NumberFormatInput as any,
+ inputProps: {
+ thousandSeparator: true,
+ valueIsNumericString: true,
+ },
+ }}
+ />
+ );
+};
diff --git a/src/shell/components/NumberFormatInput/index.tsx b/src/shell/components/NumberFormatInput/index.tsx
new file mode 100644
index 0000000000..6efa07b581
--- /dev/null
+++ b/src/shell/components/NumberFormatInput/index.tsx
@@ -0,0 +1,32 @@
+import { forwardRef } from "react";
+import {
+ NumericFormatProps,
+ InputAttributes,
+ NumericFormat,
+} from "react-number-format";
+
+type NumberFormatInputProps = {
+ onChange: (event: { target: { name: string; value: number } }) => void;
+ name: string;
+};
+export const NumberFormatInput = forwardRef<
+ NumericFormatProps,
+ NumberFormatInputProps
+>((props, ref) => {
+ const { onChange, ...other } = props;
+
+ return (
+ {
+ onChange({
+ target: {
+ name: props.name,
+ value: values.floatValue || 0,
+ },
+ });
+ }}
+ />
+ );
+});