From 7e4fecd13672c8813411dfbc06e0961b92670873 Mon Sep 17 00:00:00 2001
From: VictoriaBeilstenEdmands
<45741274+VictoriaBeilsten-Edmands@users.noreply.github.com>
Date: Thu, 11 Jun 2026 14:43:42 +0100
Subject: [PATCH] Add onBlur submit option to VisitInput
---
changelog.md | 1 +
.../controls/VisitInput.stories.tsx | 9 ++
src/components/controls/VisitInput.test.tsx | 111 ++++++++++++++++++
src/components/controls/VisitInput.tsx | 13 ++
4 files changed, 134 insertions(+)
diff --git a/changelog.md b/changelog.md
index 3797b903..8c3a0b64 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,7 @@
### Changed
- **Breaking** `keycloak-js` has been moved from a direct dependency to a peer and optional dependency, so must now be installed by the consuming application.
+- Added submitOnBlur boolean prop to VisitInput which defaults to false.
### Fixed
- Icon imports were causing issues downstream when components are unit tested.
diff --git a/src/components/controls/VisitInput.stories.tsx b/src/components/controls/VisitInput.stories.tsx
index f5d7e202..d8869608 100644
--- a/src/components/controls/VisitInput.stories.tsx
+++ b/src/components/controls/VisitInput.stories.tsx
@@ -34,6 +34,15 @@ export const InitialVisitWithSubmitButton: Story = {
},
};
+export const InitialVisitWithSubmitOnBlur: Story = {
+ args: {
+ onSubmit: handleSubmit,
+ visit: { proposalCode: "xx", proposalNumber: 99999, number: 7 },
+ submitButton: false,
+ submitOnBlur: true,
+ },
+};
+
export const InitialVisitWithoutReturnKeySubmission: Story = {
args: {
onSubmit: handleSubmit,
diff --git a/src/components/controls/VisitInput.test.tsx b/src/components/controls/VisitInput.test.tsx
index 47f7be14..738ce9d4 100644
--- a/src/components/controls/VisitInput.test.tsx
+++ b/src/components/controls/VisitInput.test.tsx
@@ -226,3 +226,114 @@ it("should update visit on submit", () => {
},
);
});
+
+it("should not produce visit on blur by default", () => {
+ const onSubmit = vi.fn();
+ const { getByTestId } = render();
+ const visitField = within(getByTestId("visit-field")).getByRole("textbox");
+ fireEvent.change(visitField, { target: { value: "zz12345-7" } });
+ fireEvent.blur(visitField, {
+ key: "Enter",
+ code: "Enter",
+ keyCode: 13,
+ charCode: 13,
+ });
+ expect(onSubmit).not.toHaveBeenCalledWith(
+ {
+ proposalCode: "zz",
+ proposalNumber: 12345,
+ number: 7,
+ },
+ undefined,
+ );
+});
+
+it("should produce visit on blur without submit button", () => {
+ const onSubmit = vi.fn();
+ const { getByTestId } = render(
+ ,
+ );
+ const visitField = within(getByTestId("visit-field")).getByRole("textbox");
+ fireEvent.change(visitField, { target: { value: "zz12345-7" } });
+ fireEvent.blur(visitField, {
+ key: "Enter",
+ code: "Enter",
+ keyCode: 13,
+ charCode: 13,
+ });
+ expect(onSubmit).toHaveBeenCalledWith(
+ {
+ proposalCode: "zz",
+ proposalNumber: 12345,
+ number: 7,
+ },
+ undefined,
+ );
+});
+
+it("should not produce visit on blur", () => {
+ const onSubmit = vi.fn();
+ const { getByTestId } = render(
+ ,
+ );
+ const visitField = within(getByTestId("visit-field")).getByRole("textbox");
+ fireEvent.change(visitField, { target: { value: "zz12345-7" } });
+ fireEvent.blur(visitField, {
+ key: "Enter",
+ code: "Enter",
+ keyCode: 13,
+ charCode: 13,
+ });
+ expect(onSubmit).not.toHaveBeenCalledWith(
+ {
+ proposalCode: "zz",
+ proposalNumber: 12345,
+ number: 7,
+ },
+ undefined,
+ );
+});
+
+it("should produce visit on blur", () => {
+ const onSubmit = vi.fn();
+ const { getByTestId } = render(
+ ,
+ );
+ const visitField = within(getByTestId("visit-field")).getByRole("textbox");
+ fireEvent.change(visitField, { target: { value: "zz12345-7" } });
+ fireEvent.blur(visitField, {
+ key: "Enter",
+ code: "Enter",
+ keyCode: 13,
+ charCode: 13,
+ });
+ expect(onSubmit).toHaveBeenCalledWith(
+ {
+ proposalCode: "zz",
+ proposalNumber: 12345,
+ number: 7,
+ },
+ undefined,
+ );
+});
+
+it("should not produce visit on blur with no onSubmit", () => {
+ const onSubmit = vi.fn();
+ const { getByTestId } = render();
+ const visitField = within(getByTestId("visit-field")).getByRole("textbox");
+ fireEvent.change(visitField, { target: { value: "zz12345-7" } });
+ fireEvent.blur(visitField, {
+ key: "Enter",
+ code: "Enter",
+ keyCode: 13,
+ charCode: 13,
+ });
+ expect(onSubmit).not.toHaveBeenCalledWith(
+ {
+ proposalCode: "zz",
+ proposalNumber: 12345,
+ number: 7,
+ },
+ undefined,
+ );
+});
diff --git a/src/components/controls/VisitInput.tsx b/src/components/controls/VisitInput.tsx
index ba0997d7..d5410091 100644
--- a/src/components/controls/VisitInput.tsx
+++ b/src/components/controls/VisitInput.tsx
@@ -14,6 +14,7 @@ interface VisitInputTextProps {
setIsValid: (v: boolean) => void;
handleSubmit?: () => void;
submitOnReturn?: boolean;
+ submitOnBlur?: boolean;
}
const VisitInputText: React.FC = ({
@@ -23,6 +24,7 @@ const VisitInputText: React.FC = ({
setIsValid,
handleSubmit,
submitOnReturn,
+ submitOnBlur,
}) => {
const handleInputChange = (event: ChangeEvent) => {
const value = event.target.value;
@@ -36,12 +38,19 @@ const VisitInputText: React.FC = ({
}
};
+ const handleBlur = () => {
+ if (submitOnBlur && handleSubmit) {
+ handleSubmit();
+ }
+ };
+
return (
= ({
@@ -64,6 +74,7 @@ const VisitInput: React.FC = ({
parameters,
submitButton = true,
submitOnReturn = true,
+ submitOnBlur = false,
}) => {
const [visitText, setVisitText] = useState(visitToText(visit));
const [isValid, setIsValid] = useState(true);
@@ -88,6 +99,7 @@ const VisitInput: React.FC = ({
setIsValid={setIsValid}
handleSubmit={handleSubmit}
submitOnReturn={submitOnReturn}
+ submitOnBlur={submitOnBlur}
/>