1
1
import classnames from 'classnames' ;
2
- import { useCallback , useMemo , useState } from 'preact/hooks' ;
2
+ import { useCallback , useId , useMemo , useState } from 'preact/hooks' ;
3
3
4
4
import { ArrowLeftIcon , ArrowRightIcon } from '../../../../components/icons' ;
5
+ import type { SelectNextProps } from '../../../../components/input' ;
5
6
import { IconButton , InputGroup } from '../../../../components/input' ;
6
7
import SelectNext from '../../../../components/input/SelectNext' ;
7
8
import Library from '../../Library' ;
@@ -23,65 +24,77 @@ const defaultItems: ItemType[] = [
23
24
function SelectExample ( {
24
25
disabled,
25
26
textOnly,
26
- classes,
27
27
items = defaultItems ,
28
- } : {
29
- disabled ?: boolean ;
28
+ ...rest
29
+ } : Pick <
30
+ SelectNextProps < ItemType > ,
31
+ 'aria-label' | 'aria-labelledby' | 'classes' | 'disabled'
32
+ > & {
30
33
textOnly ?: boolean ;
31
- classes ?: string ;
32
- items ?: typeof defaultItems ;
34
+ items ?: ItemType [ ] ;
33
35
} ) {
34
36
const [ value , setValue ] = useState < ItemType > ( ) ;
37
+ const buttonId = useId ( ) ;
35
38
36
39
return (
37
- < SelectNext
38
- value = { value }
39
- onChange = { setValue }
40
- classes = { classes }
41
- disabled = { disabled }
42
- buttonContent = {
43
- value ? (
44
- < >
45
- { textOnly && value . name }
46
- { ! textOnly && (
47
- < div className = "flex" >
48
- < div className = "truncate" > { value . name } </ div >
49
- < div className = "rounded px-2 ml-2 bg-grey-7 text-white" >
50
- { value . id }
51
- </ div >
52
- </ div >
53
- ) }
54
- </ >
55
- ) : disabled ? (
56
- < > This is disabled</ >
57
- ) : (
58
- < > Select one...</ >
59
- )
60
- }
61
- >
62
- { items . map ( item => (
63
- < SelectNext . Option value = { item } key = { item . id } disabled = { item . disabled } >
64
- { ( { disabled } ) =>
65
- textOnly ? (
66
- item . name
67
- ) : (
68
- < >
69
- { item . name }
70
- < div className = "grow" />
71
- < div
72
- className = { classnames ( 'rounded px-2 ml-2 text-white' , {
73
- 'bg-grey-7' : ! disabled ,
74
- 'bg-grey-4' : disabled ,
75
- } ) }
76
- >
77
- { item . id }
40
+ < >
41
+ { ! rest [ 'aria-label' ] && ! rest [ 'aria-labelledby' ] && (
42
+ < label htmlFor = { buttonId } > Select a person</ label >
43
+ ) }
44
+ < SelectNext
45
+ { ...rest }
46
+ buttonId = { buttonId }
47
+ value = { value }
48
+ onChange = { setValue }
49
+ disabled = { disabled }
50
+ buttonContent = {
51
+ value ? (
52
+ < >
53
+ { textOnly && value . name }
54
+ { ! textOnly && (
55
+ < div className = "flex" >
56
+ < div className = "truncate" > { value . name } </ div >
57
+ < div className = "rounded px-2 ml-2 bg-grey-7 text-white" >
58
+ { value . id }
59
+ </ div >
78
60
</ div >
79
- </ >
80
- )
81
- }
82
- </ SelectNext . Option >
83
- ) ) }
84
- </ SelectNext >
61
+ ) }
62
+ </ >
63
+ ) : disabled ? (
64
+ < > This is disabled</ >
65
+ ) : (
66
+ < > Select one…</ >
67
+ )
68
+ }
69
+ >
70
+ { items . map ( item => (
71
+ < SelectNext . Option
72
+ value = { item }
73
+ key = { item . id }
74
+ disabled = { item . disabled }
75
+ >
76
+ { ( { disabled } ) =>
77
+ textOnly ? (
78
+ item . name
79
+ ) : (
80
+ < >
81
+ { item . name }
82
+ < div className = "grow" />
83
+ < div
84
+ className = { classnames ( 'rounded px-2 ml-2 text-white' , {
85
+ 'bg-grey-7' : ! disabled ,
86
+ 'bg-grey-4' : disabled ,
87
+ } ) }
88
+ >
89
+ { item . id }
90
+ </ div >
91
+ </ >
92
+ )
93
+ }
94
+ </ SelectNext . Option >
95
+ ) ) }
96
+ </ SelectNext >
97
+ </ >
85
98
) ;
86
99
}
87
100
@@ -99,53 +112,58 @@ function InputGroupSelectExample({ classes }: { classes?: string }) {
99
112
const newIndex = selectedIndex - 1 ;
100
113
setSelected ( defaultItems [ newIndex ] ?? selected ) ;
101
114
} , [ selected , selectedIndex ] ) ;
115
+ const buttonId = useId ( ) ;
102
116
103
117
return (
104
- < InputGroup >
105
- < IconButton
106
- icon = { ArrowLeftIcon }
107
- title = "Previous student"
108
- variant = "dark"
109
- onClick = { previous }
110
- disabled = { selectedIndex <= 0 }
111
- />
112
- < SelectNext
113
- value = { selected }
114
- onChange = { setSelected }
115
- classes = { classes }
116
- buttonContent = {
117
- selected ? (
118
- < div className = "flex" >
119
- < div className = "truncate" > { selected . name } </ div >
120
- < div className = "rounded px-2 ml-2 bg-grey-7 text-white" >
121
- { selected . id }
118
+ < >
119
+ < label htmlFor = { buttonId } > Select a person</ label >
120
+ < InputGroup >
121
+ < IconButton
122
+ icon = { ArrowLeftIcon }
123
+ title = "Previous student"
124
+ variant = "dark"
125
+ onClick = { previous }
126
+ disabled = { selectedIndex <= 0 }
127
+ />
128
+ < SelectNext
129
+ buttonId = { buttonId }
130
+ value = { selected }
131
+ onChange = { setSelected }
132
+ classes = { classes }
133
+ buttonContent = {
134
+ selected ? (
135
+ < div className = "flex" >
136
+ < div className = "truncate" > { selected . name } </ div >
137
+ < div className = "rounded px-2 ml-2 bg-grey-7 text-white" >
138
+ { selected . id }
139
+ </ div >
122
140
</ div >
123
- </ div >
124
- ) : (
125
- < > Select one... </ >
126
- )
127
- }
128
- >
129
- { defaultItems . map ( item => (
130
- < SelectNext . Option value = { item } key = { item . id } >
131
- { item . name }
132
- < div className = "grow" />
133
- < div
134
- className = { classnames ( 'rounded px-2 ml-2 text-white bg-grey-7' ) }
135
- >
136
- { item . id }
137
- </ div >
138
- </ SelectNext . Option >
139
- ) ) }
140
- </ SelectNext >
141
- < IconButton
142
- icon = { ArrowRightIcon }
143
- title = "Next student "
144
- variant = "dark"
145
- onClick = { next }
146
- disabled = { selectedIndex >= defaultItems . length - 1 }
147
- / >
148
- </ InputGroup >
141
+ ) : (
142
+ < > Select one… </ >
143
+ )
144
+ }
145
+ >
146
+ { defaultItems . map ( item => (
147
+ < SelectNext . Option value = { item } key = { item . id } >
148
+ { item . name }
149
+ < div className = "grow" />
150
+ < div
151
+ className = { classnames ( 'rounded px-2 ml-2 text-white bg-grey-7' ) }
152
+ >
153
+ { item . id }
154
+ </ div >
155
+ </ SelectNext . Option >
156
+ ) ) }
157
+ </ SelectNext >
158
+ < IconButton
159
+ icon = { ArrowRightIcon }
160
+ title = "Next student"
161
+ variant = "dark "
162
+ onClick = { next }
163
+ disabled = { selectedIndex >= defaultItems . length - 1 }
164
+ />
165
+ </ InputGroup >
166
+ </ >
149
167
) ;
150
168
}
151
169
@@ -228,6 +246,44 @@ export default function SelectNextPage() {
228
246
</ Library . Demo >
229
247
</ Library . Example >
230
248
249
+ < Library . Example title = "Labeling SelectNext" >
250
+ < p >
251
+ There are three ways to label a < code > SelectNext</ code > . Make sure
252
+ you always use one of them.
253
+ </ p >
254
+
255
+ < Library . Demo
256
+ title = {
257
+ < >
258
+ Via{ ' ' }
259
+ < code >
260
+ { '<' } label { '/>' }
261
+ </ code > { ' ' }
262
+ linked to < code > buttonId</ code >
263
+ </ >
264
+ }
265
+ >
266
+ < div className = "w-96 mx-auto" >
267
+ < SelectExample />
268
+ </ div >
269
+ </ Library . Demo >
270
+
271
+ < Library . Demo title = "Via aria-label" >
272
+ < div className = "w-96 mx-auto" >
273
+ < SelectExample aria-label = "Select a person with aria label" />
274
+ </ div >
275
+ </ Library . Demo >
276
+
277
+ < Library . Demo title = "Via aria-labelledby" >
278
+ < div className = "w-96 mx-auto" >
279
+ < p id = "select-next-meta-label" >
280
+ Select a person with aria labelledby
281
+ </ p >
282
+ < SelectExample aria-labelledby = "select-next-meta-label" />
283
+ </ div >
284
+ </ Library . Demo >
285
+ </ Library . Example >
286
+
231
287
< Library . Example title = "Select with long content" >
232
288
< p >
233
289
< code > SelectNext</ code > makes sure the button content never
@@ -403,7 +459,7 @@ export default function SelectNextPage() {
403
459
</div>
404
460
</>
405
461
) : (
406
- <>Select one... </>
462
+ <>Select one… </>
407
463
)
408
464
}
409
465
>
0 commit comments