Skip to content

Commit 541a9ba

Browse files
author
Nikolai Lopin
authored
fix(a11y): Link inputs and labels when id is not provided by the user (#86)
1 parent 674284e commit 541a9ba

File tree

16 files changed

+130
-153
lines changed

16 files changed

+130
-153
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"@types/react-select": "^3.0.13",
108108
"@types/styled-system": "^5.1.9",
109109
"date-fns": "^2.11.1",
110+
"nanoid": "^3.1.23",
110111
"react-select": "^3.1.0",
111112
"react-tether": "^2.0.7",
112113
"react-transition-group": "^4.3.0",

src/components/Datepicker/__snapshots__/DatepickerRangeInput.spec.tsx.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ exports[`DatepickerRangeInput renders the default props 1`] = `
176176
class="sc-AxirZ c2"
177177
data-error="false"
178178
data-testid="start-date-input"
179+
id="random"
179180
type="text"
180181
value=""
181182
/>
@@ -202,6 +203,7 @@ exports[`DatepickerRangeInput renders the default props 1`] = `
202203
class="sc-AxirZ c2"
203204
data-error="false"
204205
data-testid="end-date-input"
206+
id="random"
205207
tabindex="-1"
206208
type="text"
207209
value=""

src/components/Datepicker/__snapshots__/DatepickerSingleInput.spec.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ exports[`DatepickerSingleInput renders the default props 1`] = `
120120
class="sc-AxjAm c1"
121121
data-error="false"
122122
data-testid="start-date-input"
123+
id="random"
123124
type="text"
124125
value=""
125126
/>

src/components/Input/Input.spec.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,21 @@ describe('Input', () => {
7272
});
7373
});
7474

75-
it('should set the htmlFor attribute for the label', () => {
76-
expect(render(<Input id="test-input-id" label="Simple Label" />).container.firstChild).toMatchSnapshot();
75+
describe('link input with the label', () => {
76+
it('uses `id` prop value if passed', () => {
77+
render(<Input id="test-input-id" label="Simple Label" />);
78+
79+
expect(screen.getByLabelText('Simple Label')).toHaveAttribute('id', 'test-input-id');
80+
expect(screen.getByText('Simple Label')).toHaveAttribute('for', 'test-input-id');
81+
});
82+
83+
it('generate id automatically if `id` prop is empty', () => {
84+
render(<Input label="Simple Label" />);
85+
const generatedId = 'random';
86+
87+
expect(screen.getByLabelText('Simple Label')).toHaveAttribute('id', generatedId);
88+
expect(screen.getByText('Simple Label')).toHaveAttribute('for', generatedId);
89+
});
7790
});
7891

7992
it('allows to be tested using accessible queries', () => {

src/components/Input/Input.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { forwardRef, useEffect, useState } from 'react';
22
import { extractClassNameProps, extractWidthProps, extractWrapperMarginProps } from '../../utils/extractProps';
3+
import { useGeneratedId } from '../../utils/hooks/useGeneratedId';
34
import { BottomLinedInput } from './BottomLinedInput';
45
import { BottomLinedInputLabel } from './BottomLinedInputLabel';
56
import { BoxedInput } from './BoxedInput';
@@ -12,7 +13,8 @@ const Input = forwardRef<HTMLDivElement, InputWrapperProps & InputProps>((props,
1213
const { marginProps, restProps: withoutMargin } = extractWrapperMarginProps(withoutClassName);
1314
const { widthProps, restProps } = extractWidthProps(withoutMargin);
1415

15-
const { label, onChange, size, id, ...rest } = restProps;
16+
const { label, onChange, size, ...rest } = restProps;
17+
const id = useGeneratedId(props.id);
1618

1719
const [hasValue, setHasValue] = useState(rest.value && rest.value.toString().length > 0);
1820

src/components/Input/__snapshots__/Input.spec.tsx.snap

Lines changed: 20 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`Input should set the htmlFor attribute for the label 1`] = `
4-
.c3 {
5-
position: absolute;
6-
pointer-events: none;
7-
background-color: transparent;
8-
line-height: 1.5;
9-
overflow: hidden;
10-
text-overflow: ellipsis;
11-
white-space: nowrap;
12-
max-width: calc(100% - 2rem);
13-
-webkit-transition: top 100ms ease-out,left 100ms ease-out, padding 100ms ease-out,font-size 100ms ease-out, color 100ms ease-out,background 100ms ease-out;
14-
transition: top 100ms ease-out,left 100ms ease-out, padding 100ms ease-out,font-size 100ms ease-out, color 100ms ease-out,background 100ms ease-out;
15-
top: 0.75rem;
16-
left: 0.5rem;
17-
padding: 0 0.25rem;
18-
font-size: 1rem;
19-
}
20-
21-
.c1 {
22-
margin: 0;
23-
box-sizing: border-box;
24-
background: #FFFFFF;
25-
border-radius: 0;
26-
color: #001E3E;
27-
font-size: 1rem;
28-
font-family: "Open Sans",sans-serif;
29-
-webkit-transition: box-shadow 100ms,border 100ms;
30-
transition: box-shadow 100ms,border 100ms;
31-
outline: none;
32-
-webkit-appearance: none;
33-
-moz-appearance: none;
34-
appearance: none;
35-
width: 100%;
36-
border-radius: 0.25rem;
37-
border: 0.0625rem solid #C6CDD4;
38-
font-size: 1rem;
39-
height: 3rem;
40-
padding: 0 0.75rem;
41-
}
42-
43-
.c1::-webkit-input-placeholder {
44-
color: #9CA7B4;
45-
}
46-
47-
.c1::-moz-placeholder {
48-
color: #9CA7B4;
49-
}
50-
51-
.c1:-ms-input-placeholder {
52-
color: #9CA7B4;
53-
}
54-
55-
.c1::placeholder {
56-
color: #9CA7B4;
57-
}
58-
59-
.c1:active,
60-
.c1:focus {
61-
border-color: #096BDB;
62-
box-shadow: inset 0 0 0 0.0625rem #096BDB;
63-
}
64-
65-
.c1:disabled {
66-
color: #C6CDD4;
67-
border-color: #C6CDD4;
68-
box-shadow: none;
69-
cursor: not-allowed;
70-
}
71-
72-
.c1:disabled::-webkit-input-placeholder {
73-
color: #C6CDD4;
74-
}
75-
76-
.c1:disabled::-moz-placeholder {
77-
color: #C6CDD4;
78-
}
79-
80-
.c1:disabled:-ms-input-placeholder {
81-
color: #C6CDD4;
82-
}
83-
84-
.c1:disabled::placeholder {
85-
color: #C6CDD4;
86-
}
87-
88-
.c1:-webkit-autofill,
89-
.c1:-webkit-autofill:hover,
90-
.c1:-webkit-autofill:focus,
91-
.c1:-webkit-autofill:active {
92-
-webkit-text-fill-color: #001E3E;
93-
-webkit-transition: background-color 99999999ms ease 99999999ms;
94-
transition: background-color 99999999ms ease 99999999ms;
95-
}
96-
97-
.c1 + .c2 {
98-
color: #9CA7B4;
99-
background: #FFFFFF;
100-
background: linear-gradient(0deg,#FFFFFF calc(50% + 0.0625rem),transparent 50%);
101-
}
102-
103-
.c1:disabled + .c2 {
104-
color: #C6CDD4;
105-
}
106-
107-
.c1:-webkit-autofill + .c2,
108-
.c1:-webkit-autofill:hover + .c2,
109-
.c1:-webkit-autofill:focus + .c2,
110-
.c1:-webkit-autofill:active + .c2 {
111-
font-weight: 600;
112-
top: -0.5rem;
113-
font-size: 0.75rem;
114-
}
115-
116-
.c1:focus:not(:disabled) + .c2 {
117-
font-weight: 600;
118-
top: -0.5rem;
119-
font-size: 0.75rem;
120-
color: #096BDB;
121-
background: #FFFFFF;
122-
background: linear-gradient(0deg,#FFFFFF calc(50% + 0.0625rem),transparent 50%);
123-
}
124-
125-
.c0 {
126-
display: inline-block;
127-
position: relative;
128-
box-sizing: border-box;
129-
}
130-
131-
<div
132-
class="c0"
133-
>
134-
<input
135-
class="sc-AxjAm c1"
136-
id="test-input-id"
137-
type="text"
138-
/>
139-
<label
140-
class="sc-AxirZ c2 c3"
141-
for="test-input-id"
142-
>
143-
Simple Label
144-
</label>
145-
</div>
146-
`;
147-
1483
exports[`Input variant "bottom-lined" renders 1`] = `
1494
.c1 {
1505
margin: 0;
@@ -261,6 +116,7 @@ exports[`Input variant "bottom-lined" renders 1`] = `
261116
>
262117
<input
263118
class="sc-AxjAm c1"
119+
id="random"
264120
type="text"
265121
/>
266122
</div>
@@ -388,6 +244,7 @@ exports[`Input variant "bottom-lined" renders the error state 1`] = `
388244
>
389245
<input
390246
class="sc-AxjAm c1"
247+
id="random"
391248
type="text"
392249
/>
393250
</div>
@@ -535,11 +392,13 @@ exports[`Input variant "bottom-lined" renders the error state with label and pla
535392
>
536393
<input
537394
class="sc-AxjAm c1"
395+
id="random"
538396
placeholder="FREE NOW"
539397
type="text"
540398
/>
541399
<label
542400
class="sc-AxirZ c2 c3"
401+
for="random"
543402
>
544403
Name
545404
</label>
@@ -662,6 +521,7 @@ exports[`Input variant "bottom-lined" renders the inverted style 1`] = `
662521
>
663522
<input
664523
class="sc-AxjAm c1"
524+
id="random"
665525
type="text"
666526
/>
667527
</div>
@@ -800,10 +660,12 @@ exports[`Input variant "bottom-lined" renders the label 1`] = `
800660
>
801661
<input
802662
class="sc-AxjAm c1"
663+
id="random"
803664
type="text"
804665
/>
805666
<label
806667
class="sc-AxirZ c2 c3"
668+
for="random"
807669
>
808670
Name
809671
</label>
@@ -946,11 +808,13 @@ exports[`Input variant "bottom-lined" renders the label and the placeholder 1`]
946808
>
947809
<input
948810
class="sc-AxjAm c1"
811+
id="random"
949812
placeholder="FREE NOW"
950813
type="text"
951814
/>
952815
<label
953816
class="sc-AxirZ c2 c3"
817+
for="random"
954818
>
955819
Name
956820
</label>
@@ -1073,6 +937,7 @@ exports[`Input variant "bottom-lined" renders the small size 1`] = `
1073937
>
1074938
<input
1075939
class="sc-AxjAm c1"
940+
id="random"
1076941
type="text"
1077942
/>
1078943
</div>
@@ -1194,6 +1059,7 @@ exports[`Input variant "boxed" renders 1`] = `
11941059
>
11951060
<input
11961061
class="sc-AxjAm c1"
1062+
id="random"
11971063
type="text"
11981064
/>
11991065
</div>
@@ -1321,6 +1187,7 @@ exports[`Input variant "boxed" renders the error state 1`] = `
13211187
>
13221188
<input
13231189
class="sc-AxjAm c1"
1190+
id="random"
13241191
type="text"
13251192
/>
13261193
</div>
@@ -1468,11 +1335,13 @@ exports[`Input variant "boxed" renders the error state with label and placeholde
14681335
>
14691336
<input
14701337
class="sc-AxjAm c1"
1338+
id="random"
14711339
placeholder="FREE NOW"
14721340
type="text"
14731341
/>
14741342
<label
14751343
class="sc-AxirZ c2 c3"
1344+
for="random"
14761345
>
14771346
Name
14781347
</label>
@@ -1595,6 +1464,7 @@ exports[`Input variant "boxed" renders the inverted style 1`] = `
15951464
>
15961465
<input
15971466
class="sc-AxjAm c1"
1467+
id="random"
15981468
type="text"
15991469
/>
16001470
</div>
@@ -1733,10 +1603,12 @@ exports[`Input variant "boxed" renders the label 1`] = `
17331603
>
17341604
<input
17351605
class="sc-AxjAm c1"
1606+
id="random"
17361607
type="text"
17371608
/>
17381609
<label
17391610
class="sc-AxirZ c2 c3"
1611+
for="random"
17401612
>
17411613
Name
17421614
</label>
@@ -1879,11 +1751,13 @@ exports[`Input variant "boxed" renders the label and the placeholder 1`] = `
18791751
>
18801752
<input
18811753
class="sc-AxjAm c1"
1754+
id="random"
18821755
placeholder="FREE NOW"
18831756
type="text"
18841757
/>
18851758
<label
18861759
class="sc-AxirZ c2 c3"
1760+
for="random"
18871761
>
18881762
Name
18891763
</label>
@@ -2006,6 +1880,7 @@ exports[`Input variant "boxed" renders the small size 1`] = `
20061880
>
20071881
<input
20081882
class="sc-AxjAm c1"
1883+
id="random"
20091884
type="text"
20101885
/>
20111886
</div>

0 commit comments

Comments
 (0)