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, + }, + }); + }} + /> + ); +});