From 441d9dfc954748fffb6cdd395c18fbf82ece8e1e Mon Sep 17 00:00:00 2001 From: Kristine White Date: Tue, 16 Dec 2025 09:59:30 -0800 Subject: [PATCH] fix: prevent negative numbers in input fields --- docs/component-adapter/component-inventory.md | 2 ++ .../TextInputField/TextInputField.stories.tsx | 30 +++++++++++++++++++ .../Common/UI/TextInput/TextInput.test.tsx | 8 +++++ .../Common/UI/TextInput/TextInput.tsx | 4 +++ .../Common/UI/TextInput/TextInputTypes.ts | 2 +- .../EditContractorPaymentPresentation.tsx | 12 ++++++-- .../PayrollEditEmployeePresentation.tsx | 3 ++ .../PayrollEditEmployee/TimeOffField.tsx | 1 + 8 files changed, 59 insertions(+), 3 deletions(-) diff --git a/docs/component-adapter/component-inventory.md b/docs/component-adapter/component-inventory.md index 6736aa037..de10ab7ba 100644 --- a/docs/component-adapter/component-inventory.md +++ b/docs/component-adapter/component-inventory.md @@ -616,6 +616,8 @@ The props for this component are defined in [BaseListProps](#baselistprops). | **name** | `string` | No | - | | **type** | `"number" \| "submit" \| "reset" \| "button" \| "checkbox" \| "color" \| "date" \| "datetime-local" \| "email" \| "file" \| "hidden" \| "image" \| "month" \| "password" \| "radio" \| "range" \| "search" \| "tel" \| "text" \| "time" \| "url" \| "week" \| string` | No | - | | **placeholder** | `string` | No | - | +| **min** | `string \| number` | No | - | +| **max** | `string \| number` | No | - | | **aria-describedby** | `string` | No | Identifies the element (or elements) that describes the object. | ## TextProps diff --git a/src/components/Common/Fields/TextInputField/TextInputField.stories.tsx b/src/components/Common/Fields/TextInputField/TextInputField.stories.tsx index 59271b5e8..77253a64b 100644 --- a/src/components/Common/Fields/TextInputField/TextInputField.stories.tsx +++ b/src/components/Common/Fields/TextInputField/TextInputField.stories.tsx @@ -55,3 +55,33 @@ export const WithDefaultValues: Story = () => { ) } + +export const NumberInputWithMinMax: Story = () => { + return ( + + + + + + ) +} diff --git a/src/components/Common/UI/TextInput/TextInput.test.tsx b/src/components/Common/UI/TextInput/TextInput.test.tsx index ac1eeebda..482733b81 100644 --- a/src/components/Common/UI/TextInput/TextInput.test.tsx +++ b/src/components/Common/UI/TextInput/TextInput.test.tsx @@ -44,6 +44,14 @@ describe('TextInput', () => { expect(screen.getByText('This field is required')).toBeInTheDocument() }) + it('applies min and max attributes to number input', () => { + renderWithProviders() + + const input = screen.getByRole('spinbutton') + expect(input).toHaveAttribute('min', '0') + expect(input).toHaveAttribute('max', '100') + }) + describe('Accessibility', () => { const testCases = [ { diff --git a/src/components/Common/UI/TextInput/TextInput.tsx b/src/components/Common/UI/TextInput/TextInput.tsx index 205aa5c9a..0e9b250ca 100644 --- a/src/components/Common/UI/TextInput/TextInput.tsx +++ b/src/components/Common/UI/TextInput/TextInput.tsx @@ -27,6 +27,8 @@ export function TextInput(rawProps: TextInputProps) { shouldVisuallyHideLabel, adornmentEnd, adornmentStart, + min, + max, 'aria-describedby': ariaDescribedByFromProps, ...otherProps } = resolvedProps @@ -69,6 +71,8 @@ export function TextInput(rawProps: TextInputProps) { isDisabled={isDisabled} adornmentStart={adornmentStart} adornmentEnd={adornmentEnd} + min={min} + max={max} /> ) diff --git a/src/components/Common/UI/TextInput/TextInputTypes.ts b/src/components/Common/UI/TextInput/TextInputTypes.ts index 0cf1e3843..4f1aa6b73 100644 --- a/src/components/Common/UI/TextInput/TextInputTypes.ts +++ b/src/components/Common/UI/TextInput/TextInputTypes.ts @@ -7,7 +7,7 @@ export interface TextInputProps SharedFieldLayoutProps, Pick< InputHTMLAttributes, - 'name' | 'id' | 'placeholder' | 'className' | 'type' + 'name' | 'id' | 'placeholder' | 'className' | 'type' | 'min' | 'max' >, Pick, 'aria-describedby'> { /** diff --git a/src/components/Contractor/Payments/CreatePayment/EditContractorPaymentPresentation.tsx b/src/components/Contractor/Payments/CreatePayment/EditContractorPaymentPresentation.tsx index 436477eac..f8556b648 100644 --- a/src/components/Contractor/Payments/CreatePayment/EditContractorPaymentPresentation.tsx +++ b/src/components/Contractor/Payments/CreatePayment/EditContractorPaymentPresentation.tsx @@ -84,6 +84,7 @@ export const EditContractorPaymentPresentation = ({ onSave, onCancel }: EditPaym @@ -93,16 +94,23 @@ export const EditContractorPaymentPresentation = ({ onSave, onCancel }: EditPaym {wageType === 'Fixed' && ( {t('fixedPaySection')} - + )} {t('additionalEarningsSection')} - + diff --git a/src/components/Payroll/PayrollEditEmployee/PayrollEditEmployeePresentation.tsx b/src/components/Payroll/PayrollEditEmployee/PayrollEditEmployeePresentation.tsx index 51906a80f..63dd51a5b 100644 --- a/src/components/Payroll/PayrollEditEmployee/PayrollEditEmployeePresentation.tsx +++ b/src/components/Payroll/PayrollEditEmployee/PayrollEditEmployeePresentation.tsx @@ -407,6 +407,7 @@ export const PayrollEditEmployeePresentation = ({ { key={timeOff.name} name={`timeOffCompensations.${timeOff.name}`} type="number" + min={0} adornmentEnd={t('hoursUnit')} isRequired label={timeOff.name}