Skip to content

Commit 8087856

Browse files
authored
fix: prevent negative numbers in input fields (#870)
1 parent 95237db commit 8087856

File tree

8 files changed

+64
-3
lines changed

8 files changed

+64
-3
lines changed

docs/component-adapter/component-inventory.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ type PaginationItemsPerPage = 5 | 10 | 50
624624
| **name** | `string` | No | - |
625625
| **type** | `"number" \| "submit" \| "reset" \| "button" \| "checkbox" \| "radio" \| "search" \| "color" \| "date" \| "datetime-local" \| "email" \| "file" \| "hidden" \| "image" \| "month" \| "password" \| "range" \| "tel" \| "text" \| "time" \| "url" \| "week" \| string` | No | - |
626626
| **placeholder** | `string` | No | - |
627+
| **min** | `string \| number` | No | - |
628+
| **max** | `string \| number` | No | - |
627629
| **aria-describedby** | `string` | No | Identifies the element (or elements) that describes the object. |
628630
629631
## TextProps

src/components/Common/Fields/TextInputField/TextInputField.stories.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,33 @@ export const WithDefaultValues: Story = () => {
5555
</FormWrapper>
5656
)
5757
}
58+
59+
export const NumberInputWithMinMax: Story = () => {
60+
return (
61+
<FormWrapper>
62+
<TextInputField
63+
label="Age"
64+
name="age"
65+
type="number"
66+
min={0}
67+
max={120}
68+
description="Enter a value between 0 and 120"
69+
/>
70+
<TextInputField
71+
label="Hours Worked"
72+
name="hoursWorked"
73+
type="number"
74+
min={0}
75+
description="Cannot be negative"
76+
/>
77+
<TextInputField
78+
label="Score"
79+
name="score"
80+
type="number"
81+
min={0}
82+
max={100}
83+
description="Enter a score from 0 to 100"
84+
/>
85+
</FormWrapper>
86+
)
87+
}

src/components/Common/UI/TextInput/TextInput.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ describe('TextInput', () => {
4444
expect(screen.getByText('This field is required')).toBeInTheDocument()
4545
})
4646

47+
it('applies min and max attributes to number input', () => {
48+
renderWithProviders(<TextInput {...defaultProps} type="number" min={0} max={100} />)
49+
50+
const input = screen.getByRole('spinbutton')
51+
expect(input).toHaveAttribute('min', '0')
52+
expect(input).toHaveAttribute('max', '100')
53+
})
54+
4755
describe('Accessibility', () => {
4856
const testCases = [
4957
{

src/components/Common/UI/TextInput/TextInput.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export function TextInput(rawProps: TextInputProps) {
2727
shouldVisuallyHideLabel,
2828
adornmentEnd,
2929
adornmentStart,
30+
min,
31+
max,
3032
'aria-describedby': ariaDescribedByFromProps,
3133
...otherProps
3234
} = resolvedProps
@@ -69,6 +71,8 @@ export function TextInput(rawProps: TextInputProps) {
6971
isDisabled={isDisabled}
7072
adornmentStart={adornmentStart}
7173
adornmentEnd={adornmentEnd}
74+
min={min}
75+
max={max}
7276
/>
7377
</FieldLayout>
7478
)

src/components/Common/UI/TextInput/TextInputTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface TextInputProps
77
SharedFieldLayoutProps,
88
Pick<
99
InputHTMLAttributes<HTMLInputElement>,
10-
'name' | 'id' | 'placeholder' | 'className' | 'type'
10+
'name' | 'id' | 'placeholder' | 'className' | 'type' | 'min' | 'max'
1111
>,
1212
Pick<InputHTMLAttributes<HTMLInputElement>, 'aria-describedby'> {
1313
/**

src/components/Contractor/Payments/CreatePayment/EditContractorPaymentPresentation.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export const EditContractorPaymentPresentation = ({
114114
<Flex flexDirection="column" gap={16}>
115115
<Heading as="h3">{t('hoursSection')}</Heading>
116116
<NumberInputField
117+
min={0}
117118
name="hours"
118119
isRequired
119120
label={t('hoursLabel')}
@@ -125,17 +126,29 @@ export const EditContractorPaymentPresentation = ({
125126
{wageType === 'Fixed' && (
126127
<Flex flexDirection="column" gap={16}>
127128
<Heading as="h3">{t('fixedPaySection')}</Heading>
128-
<NumberInputField name="wage" isRequired label={t('wageLabel')} format="currency" />
129+
<NumberInputField
130+
min={0}
131+
name="wage"
132+
isRequired
133+
label={t('wageLabel')}
134+
format="currency"
135+
/>
129136
</Flex>
130137
)}
131138

132139
<Flex flexDirection="column" gap={16}>
133140
<Heading as="h3">{t('additionalEarningsSection')}</Heading>
134141
<Grid gridTemplateColumns={{ base: '1fr', small: [200, 200] }} gap={16}>
135142
{wageType === 'Hourly' && (
136-
<NumberInputField name="bonus" label={t('bonusLabel')} format="currency" />
143+
<NumberInputField
144+
min={0}
145+
name="bonus"
146+
label={t('bonusLabel')}
147+
format="currency"
148+
/>
137149
)}
138150
<NumberInputField
151+
min={0}
139152
name="reimbursement"
140153
label={t('reimbursementLabel')}
141154
format="currency"

src/components/Payroll/PayrollEditEmployee/PayrollEditEmployeePresentation.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ export const PayrollEditEmployeePresentation = ({
411411
<TextInputField
412412
key={compensationName}
413413
type="number"
414+
min={0}
414415
adornmentEnd={t('hoursUnit')}
415416
isRequired
416417
label={getCompensationLabel(compensationName)}
@@ -449,6 +450,7 @@ export const PayrollEditEmployeePresentation = ({
449450
<TextInputField
450451
key={fixedCompensation.name}
451452
type="number"
453+
min={0}
452454
adornmentStart="$"
453455
isRequired
454456
label={getFixedCompensationLabel(fixedCompensation.name)}
@@ -464,6 +466,7 @@ export const PayrollEditEmployeePresentation = ({
464466
<Grid gridTemplateColumns={{ base: '1fr', small: [320, 320] }} gap={20}>
465467
<TextInputField
466468
type="number"
469+
min={0}
467470
adornmentStart="$"
468471
isRequired
469472
label={getFixedCompensationLabel(reimbursement.name)}

src/components/Payroll/PayrollEditEmployee/TimeOffField.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const TimeOffField = ({ timeOff, employee }: TimeOffFieldProps) => {
6666
key={timeOff.name}
6767
name={`timeOffCompensations.${timeOff.name}`}
6868
type="number"
69+
min={0}
6970
adornmentEnd={t('hoursUnit')}
7071
isRequired
7172
label={timeOff.name}

0 commit comments

Comments
 (0)