@@ -4,9 +4,18 @@ import {useMainSticky} from '~/helpers/main-class-hooks';
4
4
import cn from 'classnames' ;
5
5
import './form-input.scss' ;
6
6
7
+ // Accessibility issues here:
8
+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-autocomplete
9
+
7
10
const LIMIT_SUGGESTIONS = 400 ;
8
11
9
- function SuggestionItem ( { value, accept, index, activeIndex, setActiveIndex} : {
12
+ function SuggestionItem ( {
13
+ value,
14
+ accept,
15
+ index,
16
+ activeIndex,
17
+ setActiveIndex
18
+ } : {
10
19
value : string ;
11
20
accept : ( v : string ) => void ;
12
21
index : number ;
@@ -24,30 +33,42 @@ function SuggestionItem({value, accept, index, activeIndex, setActiveIndex}: {
24
33
25
34
return (
26
35
< div
27
- className = { cn ( 'suggestion' , { active} ) } ref = { ref }
36
+ className = { cn ( 'suggestion' , { active} ) }
37
+ ref = { ref }
28
38
onClick = { ( ) => accept ( value ) }
29
39
onMouseMove = { ( ) => setActiveIndex ( index ) }
30
- > { value } </ div >
40
+ >
41
+ { value }
42
+ </ div >
31
43
) ;
32
44
}
33
45
34
- function useMatches ( pattern : string , suggestions : string [ ] = [ ] ) {
46
+ function useMatches ( pattern : string , suggestions : string [ ] = [ ] ) {
35
47
const matches = React . useMemo (
36
- ( ) => pattern . length > 1 ?
37
- suggestions . filter ( ( s ) => s . toLowerCase ( ) . includes ( pattern ) ) :
38
- [ ] ,
48
+ ( ) =>
49
+ pattern . length > 1
50
+ ? suggestions . filter ( ( s ) => s . toLowerCase ( ) . includes ( pattern ) )
51
+ : [ ] ,
39
52
[ pattern , suggestions ]
40
53
) ;
41
54
const exactMatch = React . useMemo (
42
- ( ) => matches . includes ( pattern ) ||
55
+ ( ) =>
56
+ matches . includes ( pattern ) ||
43
57
( matches . length === 1 && pattern === matches [ 0 ] . toLowerCase ( ) ) ,
44
58
[ matches , pattern ]
45
59
) ;
46
60
47
61
return [ matches , exactMatch ] as const ;
48
62
}
49
63
50
- function SuggestionBox ( { matches, exactMatch, accepted, accept, activeIndex, setActiveIndex} : {
64
+ function SuggestionBox ( {
65
+ matches,
66
+ exactMatch,
67
+ accepted,
68
+ accept,
69
+ activeIndex,
70
+ setActiveIndex
71
+ } : {
51
72
matches : string [ ] ;
52
73
exactMatch : boolean ;
53
74
accepted : boolean ;
@@ -63,27 +84,40 @@ function SuggestionBox({matches, exactMatch, accepted, accept, activeIndex, setA
63
84
return (
64
85
< div className = "suggestions" >
65
86
< div className = "suggestion-box" >
66
- {
67
- ! exactMatch && ! accepted && matches . slice ( 0 , LIMIT_SUGGESTIONS ) . map ( ( match , i ) =>
68
- < SuggestionItem
69
- value = { match } accept = { accept } index = { i }
70
- activeIndex = { activeIndex } setActiveIndex = { setActiveIndex }
71
- key = { match }
72
- /> )
73
- }
74
- {
75
- matches . length > LIMIT_SUGGESTIONS &&
76
- < div className = "suggestion" > < i > List truncated</ i > </ div >
77
- }
87
+ { ! exactMatch &&
88
+ ! accepted &&
89
+ matches
90
+ . slice ( 0 , LIMIT_SUGGESTIONS )
91
+ . map ( ( match , i ) => (
92
+ < SuggestionItem
93
+ value = { match }
94
+ accept = { accept }
95
+ index = { i }
96
+ activeIndex = { activeIndex }
97
+ setActiveIndex = { setActiveIndex }
98
+ key = { match }
99
+ />
100
+ ) ) }
101
+ { matches . length > LIMIT_SUGGESTIONS && (
102
+ < div className = "suggestion" >
103
+ < i > List truncated</ i >
104
+ </ div >
105
+ ) }
78
106
</ div >
79
107
</ div >
80
108
) ;
81
109
}
82
110
83
- type InputProps = { Tag ?: keyof JSX . IntrinsicElements }
84
- & React . InputHTMLAttributes < HTMLInputElement > ;
111
+ type InputProps = {
112
+ Tag ?: keyof JSX . IntrinsicElements ;
113
+ } & React . InputHTMLAttributes < HTMLInputElement > ;
85
114
86
- function ValidatingInput ( { value, inputProps, onChange, accepted} : {
115
+ function ValidatingInput ( {
116
+ value,
117
+ inputProps,
118
+ onChange,
119
+ accepted
120
+ } : {
87
121
value : string ;
88
122
inputProps : InputProps ;
89
123
onChange : ( e : React . ChangeEvent < HTMLInputElement > ) => void ;
@@ -112,22 +146,32 @@ function ValidatingInput({value, inputProps, onChange, accepted}: {
112
146
}
113
147
114
148
// eslint-disable-next-line complexity
115
- export default function FormInput ( { label, longLabel, inputProps, suggestions} : {
149
+ export default function FormInput ( {
150
+ label,
151
+ longLabel,
152
+ inputProps,
153
+ suggestions
154
+ } : {
116
155
label : string ;
117
156
longLabel ?: string ;
118
157
inputProps : InputProps ;
119
158
suggestions ?: string [ ] ;
120
159
} ) {
121
160
const [ value , setValue ] = useState ( inputProps . value ?. toString ( ) ?? '' ) ;
122
161
const { onChange : otherOnChange , ...otherProps } = inputProps ;
123
- const [ matches , exactMatch ] = useMatches ( value . toString ( ) . toLowerCase ( ) , suggestions ) ;
162
+ const [ matches , exactMatch ] = useMatches (
163
+ value . toString ( ) . toLowerCase ( ) ,
164
+ suggestions
165
+ ) ;
124
166
const [ accepted , setAccepted ] = useState ( ! suggestions ?. length ) ;
125
167
const accept = React . useCallback (
126
168
( item : string ) => {
127
169
setValue ( item ) ;
128
170
setAccepted ( true ) ;
129
171
if ( otherOnChange ) {
130
- otherOnChange ( { target : { value : item } } as React . ChangeEvent < HTMLInputElement > ) ;
172
+ otherOnChange ( {
173
+ target : { value : item }
174
+ } as React . ChangeEvent < HTMLInputElement > ) ;
131
175
}
132
176
} ,
133
177
[ otherOnChange ]
@@ -167,23 +211,25 @@ export default function FormInput({label, longLabel, inputProps, suggestions}: {
167
211
< label className = "form-input" >
168
212
< div className = "control-group" >
169
213
{ label && < label className = "field-label" > { label } </ label > }
170
- { longLabel && < label className = "field-long-label" > { longLabel } </ label > }
214
+ { longLabel && (
215
+ < label className = "field-long-label" > { longLabel } </ label >
216
+ ) }
171
217
< ValidatingInput
172
218
value = { value }
173
219
inputProps = { { onKeyDown, ...otherProps } }
174
220
onChange = { onChange }
175
221
accepted = { accepted }
176
222
/>
177
- {
178
- suggestions &&
179
- < SuggestionBox
180
- matches = { matches }
181
- exactMatch = { exactMatch }
182
- accepted = { accepted }
183
- accept = { accept } activeIndex = { activeIndex }
184
- setActiveIndex = { setActiveIndex }
185
- />
186
- }
223
+ { suggestions && (
224
+ < SuggestionBox
225
+ matches = { matches }
226
+ exactMatch = { exactMatch }
227
+ accepted = { accepted }
228
+ accept = { accept }
229
+ activeIndex = { activeIndex }
230
+ setActiveIndex = { setActiveIndex }
231
+ />
232
+ ) }
187
233
</ div >
188
234
</ label >
189
235
) ;
0 commit comments