@thecb/components 6.0.0-beta.1 → 6.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "6.0.0-beta.1",
3
+ "version": "6.0.0-beta.4",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -93,12 +93,15 @@ const Dropdown = ({
93
93
  maxHeight,
94
94
  widthFitOptions = false,
95
95
  disabled,
96
- hasTitles = false
96
+ hasTitles = false,
97
+ autoEraseTypeAhead = true, // legacy behavior as of 05/22
98
+ ariaLabelledby
97
99
  }) => {
98
100
  const [inputValue, setInputValue] = useState("");
99
101
  const [optionsState, setOptionsState] = useState([]);
100
102
  const [filteredOptions, setFilteredOptions] = useState([]);
101
103
  const [optionsChanged, setOptionsChanged] = useState(true);
104
+ const [selectedRef, setSelectedRef] = useState(undefined);
102
105
 
103
106
  if (optionsState !== options) {
104
107
  setOptionsState(options);
@@ -161,6 +164,16 @@ const Dropdown = ({
161
164
  e.preventDefault();
162
165
  setInputValue(inputValue.slice(0, -1));
163
166
  break;
167
+ case "Home":
168
+ e.preventDefault();
169
+ optionRefs.current[0].current.focus();
170
+ break;
171
+ case "End":
172
+ e.preventDefault();
173
+ optionRefs.current[
174
+ optionRefs?.current?.length ?? 0 - 1
175
+ ].current.focus();
176
+ break;
164
177
  }
165
178
  if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
166
179
  e.preventDefault();
@@ -169,7 +182,17 @@ const Dropdown = ({
169
182
  };
170
183
 
171
184
  useEffect(() => {
172
- if (isOpen && optionRefs.current[0].current) {
185
+ console.log(
186
+ "option refs in isopen useffect",
187
+ optionRefs.current[0].current
188
+ );
189
+ console.log("selected refs in isopen useffect", selectedRef);
190
+ console.log("value in isopen useffect", value);
191
+ if (isOpen && selectedRef !== undefined) {
192
+ // WAI-ARIA requires the selected option to receive focus
193
+ selectedRef.current.focus();
194
+ } else if (isOpen && optionRefs.current[0].current) {
195
+ // If no selected option, first option receives focus
173
196
  optionRefs.current[0].current.focus();
174
197
  }
175
198
  clearTimeout(timer);
@@ -177,8 +200,10 @@ const Dropdown = ({
177
200
  }, [isOpen]);
178
201
 
179
202
  useEffect(() => {
180
- clearTimeout(timer);
181
- setTimer(setTimeout(() => setInputValue(""), 2000));
203
+ if (autoEraseTypeAhead) {
204
+ clearTimeout(timer);
205
+ setTimer(setTimeout(() => setInputValue(""), 2000));
206
+ }
182
207
  setFilteredOptions(
183
208
  options.filter(
184
209
  option =>
@@ -212,6 +237,10 @@ const Dropdown = ({
212
237
  width="100%"
213
238
  hoverStyles={`background-color: ${themeValues.hoverColor};`}
214
239
  aria-expanded={isOpen}
240
+ role="combobox"
241
+ aria-owns={`${ariaLabelledby}_listbox`}
242
+ aria-haspopup="listbox"
243
+ aria-labelledby={ariaLabelledby}
215
244
  extraStyles={
216
245
  disabled &&
217
246
  `color: #6e727e;
@@ -235,7 +264,6 @@ const Dropdown = ({
235
264
  : GREY_CHATEAU
236
265
  }
237
266
  borderRadius="2px"
238
- tabIndex={0}
239
267
  dataQa={placeholder}
240
268
  extraStyles={`height: 48px;
241
269
  ${disabled &&
@@ -245,26 +273,20 @@ const Dropdown = ({
245
273
  `}
246
274
  >
247
275
  <Stack direction="row" bottomItem={2}>
248
- {isOpen ? (
249
- <SearchInput
250
- aria-label={inputValue || "Dropdown awaiting search value"}
251
- value={inputValue}
252
- onChange={noop}
253
- themeValues={themeValues}
254
- />
255
- ) : (
256
- <Text
257
- variant="p"
258
- extraStyles={
259
- disabled &&
260
- `color: #6e727e;
261
- background-color: #f7f7f7;
262
- pointer-events: none;`
263
- }
264
- >
265
- {getSelection()}
266
- </Text>
267
- )}
276
+ <SearchInput
277
+ aria-label={getSelection()}
278
+ placeholder={getSelection()}
279
+ value={inputValue}
280
+ onChange={noop}
281
+ themeValues={themeValues}
282
+ role="searchbox"
283
+ type="text"
284
+ aria-multiline="false"
285
+ aria-autocomplete="list"
286
+ aria-controls={`${ariaLabelledby}_listbox`}
287
+ aria-activedescendant="selected_option"
288
+ tabIndex={0}
289
+ />
268
290
  <IconWrapper open={isOpen}>
269
291
  <DropdownIcon />
270
292
  </IconWrapper>
@@ -277,35 +299,44 @@ const Dropdown = ({
277
299
  ref={dropdownRef}
278
300
  widthFitOptions={widthFitOptions}
279
301
  tabIndex={0}
302
+ role="listbox"
303
+ id={`${ariaLabelledby}_listbox`}
280
304
  >
281
305
  <Stack childGap="0">
282
- {filteredOptions.map((choice, i) => (
283
- <DropdownItemWrapper
284
- key={choice.value}
285
- ref={optionRefs.current[i]}
286
- as="button"
287
- tabIndex={-1}
288
- onClick={
289
- disabledValues.includes(choice.value)
290
- ? evt => evt.preventDefault()
291
- : () => onSelect(choice.value)
292
- }
293
- selected={choice.value === value}
294
- disabled={disabledValues.includes(choice.value)}
295
- data-qa={choice.text}
296
- themeValues={themeValues}
297
- title={hasTitles ? choice.text : null}
298
- >
299
- <Text
300
- variant="p"
301
- color={
302
- choice.value === value
303
- ? WHITE
304
- : disabledValues.includes(choice.value)
305
- ? STORM_GREY
306
- : MINESHAFT_GREY
306
+ {filteredOptions.map((choice, i) => {
307
+ if (selectedRef === undefined && choice.value === value) {
308
+ setSelectedRef(optionRefs.current[i]);
309
+ }
310
+ return (
311
+ <DropdownItemWrapper
312
+ id={choice.value === value ? "selected_option" : choice.value}
313
+ key={choice.value}
314
+ ref={optionRefs.current[i]}
315
+ as="button"
316
+ tabIndex={-1}
317
+ onClick={
318
+ disabledValues.includes(choice.value)
319
+ ? evt => evt.preventDefault()
320
+ : () => onSelect(choice.value)
307
321
  }
308
- extraStyles={`padding-left: 16px;
322
+ selected={choice.value === value}
323
+ aria-selected={choice.value === value}
324
+ disabled={disabledValues.includes(choice.value)}
325
+ data-qa={choice.text}
326
+ themeValues={themeValues}
327
+ title={hasTitles ? choice.text : null}
328
+ role="option"
329
+ >
330
+ <Text
331
+ variant="p"
332
+ color={
333
+ choice.value === value
334
+ ? WHITE
335
+ : disabledValues.includes(choice.value)
336
+ ? STORM_GREY
337
+ : MINESHAFT_GREY
338
+ }
339
+ extraStyles={`padding-left: 16px;
309
340
  cursor: ${
310
341
  disabledValues.includes(choice.value)
311
342
  ? "default"
@@ -314,11 +345,12 @@ const Dropdown = ({
314
345
  white-space: nowrap;
315
346
  overflow: hidden;
316
347
  text-overflow: ellipsis;`}
317
- >
318
- {choice.text}
319
- </Text>
320
- </DropdownItemWrapper>
321
- ))}
348
+ >
349
+ {choice.text}
350
+ </Text>
351
+ </DropdownItemWrapper>
352
+ );
353
+ })}
322
354
  </Stack>
323
355
  </DropdownContentWrapper>
324
356
  ) : (
@@ -58,7 +58,7 @@ const FormSelect = ({
58
58
  </Cluster>
59
59
  </Box>
60
60
  <Dropdown
61
- aria-labelledby={labelTextWhenNoError.replace(/\s+/g, "-")}
61
+ ariaLabelledby={labelTextWhenNoError.replace(/\s+/g, "-")}
62
62
  maxHeight={dropdownMaxHeight}
63
63
  hasTitles={hasTitles}
64
64
  placeholder={options[0] ? options[0].text : ""}