@thecb/components 6.0.0-beta.2 → 6.0.0-beta.5

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.2",
3
+ "version": "6.0.0-beta.5",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -74,10 +74,11 @@ const DropdownItemWrapper = styled.div`
74
74
 
75
75
  const SearchInput = styled.input`
76
76
  border: none;
77
- background-color: ${({ themeValues }) =>
78
- themeValues.hoverColor && themeValues.hoverColor};
77
+ background-color: ${({ themeValues, isOpen }) =>
78
+ isOpen ? themeValues.hoverColor && themeValues.hoverColor : "WHITE"};
79
79
  font-size: 16px;
80
80
  height: 24px;
81
+ min-width: 75%;
81
82
  `;
82
83
 
83
84
  const Dropdown = ({
@@ -93,12 +94,15 @@ const Dropdown = ({
93
94
  maxHeight,
94
95
  widthFitOptions = false,
95
96
  disabled,
96
- hasTitles = false
97
+ hasTitles = false,
98
+ autoEraseTypeAhead = true, // legacy behavior as of 05/22
99
+ ariaLabelledby
97
100
  }) => {
98
101
  const [inputValue, setInputValue] = useState("");
99
102
  const [optionsState, setOptionsState] = useState([]);
100
103
  const [filteredOptions, setFilteredOptions] = useState([]);
101
104
  const [optionsChanged, setOptionsChanged] = useState(true);
105
+ const [selectedRef, setSelectedRef] = useState(undefined);
102
106
 
103
107
  if (optionsState !== options) {
104
108
  setOptionsState(options);
@@ -159,10 +163,18 @@ const Dropdown = ({
159
163
  break;
160
164
  case "Backspace" || "Delete":
161
165
  e.preventDefault();
162
- console.log("input value is", inputValue);
163
- console.log("new input value will be", inputValue.slice(0, -1));
164
166
  setInputValue(inputValue.slice(0, -1));
165
167
  break;
168
+ case "Home":
169
+ e.preventDefault();
170
+ optionRefs.current[0].current.focus();
171
+ break;
172
+ case "End":
173
+ e.preventDefault();
174
+ optionRefs.current[
175
+ optionRefs?.current?.length ?? 0 - 1
176
+ ].current.focus();
177
+ break;
166
178
  }
167
179
  if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
168
180
  e.preventDefault();
@@ -175,8 +187,13 @@ const Dropdown = ({
175
187
  "option refs in isopen useffect",
176
188
  optionRefs.current[0].current
177
189
  );
190
+ console.log("selected refs in isopen useffect", selectedRef);
178
191
  console.log("value in isopen useffect", value);
179
- if (isOpen && optionRefs.current[0].current) {
192
+ if (isOpen && selectedRef !== undefined) {
193
+ // WAI-ARIA requires the selected option to receive focus
194
+ selectedRef.current.focus();
195
+ } else if (isOpen && optionRefs.current[0].current) {
196
+ // If no selected option, first option receives focus
180
197
  optionRefs.current[0].current.focus();
181
198
  }
182
199
  clearTimeout(timer);
@@ -184,8 +201,10 @@ const Dropdown = ({
184
201
  }, [isOpen]);
185
202
 
186
203
  useEffect(() => {
187
- clearTimeout(timer);
188
- setTimer(setTimeout(() => setInputValue(""), 2000));
204
+ if (autoEraseTypeAhead) {
205
+ clearTimeout(timer);
206
+ setTimer(setTimeout(() => setInputValue(""), 2000));
207
+ }
189
208
  setFilteredOptions(
190
209
  options.filter(
191
210
  option =>
@@ -219,6 +238,10 @@ const Dropdown = ({
219
238
  width="100%"
220
239
  hoverStyles={`background-color: ${themeValues.hoverColor};`}
221
240
  aria-expanded={isOpen}
241
+ role="combobox"
242
+ aria-owns={`${ariaLabelledby}_listbox`}
243
+ aria-haspopup="listbox"
244
+ aria-labelledby={ariaLabelledby}
222
245
  extraStyles={
223
246
  disabled &&
224
247
  `color: #6e727e;
@@ -252,26 +275,20 @@ const Dropdown = ({
252
275
  `}
253
276
  >
254
277
  <Stack direction="row" bottomItem={2}>
255
- {isOpen ? (
256
- <SearchInput
257
- aria-label={inputValue || "Dropdown awaiting search value"}
258
- value={inputValue}
259
- onChange={noop}
260
- themeValues={themeValues}
261
- />
262
- ) : (
263
- <Text
264
- variant="p"
265
- extraStyles={
266
- disabled &&
267
- `color: #6e727e;
268
- background-color: #f7f7f7;
269
- pointer-events: none;`
270
- }
271
- >
272
- {getSelection()}
273
- </Text>
274
- )}
278
+ <SearchInput
279
+ aria-label={getSelection()}
280
+ placeholder={getSelection()}
281
+ value={inputValue}
282
+ onChange={noop}
283
+ themeValues={themeValues}
284
+ role="searchbox"
285
+ type="text"
286
+ aria-multiline="false"
287
+ aria-autocomplete="list"
288
+ aria-controls={`${ariaLabelledby}_listbox`}
289
+ aria-activedescendant="selected_option"
290
+ isOpen={isOpen}
291
+ />
275
292
  <IconWrapper open={isOpen}>
276
293
  <DropdownIcon />
277
294
  </IconWrapper>
@@ -284,35 +301,50 @@ const Dropdown = ({
284
301
  ref={dropdownRef}
285
302
  widthFitOptions={widthFitOptions}
286
303
  tabIndex={0}
304
+ role="listbox"
305
+ id={`${ariaLabelledby}_listbox`}
287
306
  >
288
307
  <Stack childGap="0">
289
- {filteredOptions.map((choice, i) => (
290
- <DropdownItemWrapper
291
- key={choice.value}
292
- ref={optionRefs.current[i]}
293
- as="button"
294
- tabIndex={-1}
295
- onClick={
296
- disabledValues.includes(choice.value)
297
- ? evt => evt.preventDefault()
298
- : () => onSelect(choice.value)
299
- }
300
- selected={choice.value === value}
301
- disabled={disabledValues.includes(choice.value)}
302
- data-qa={choice.text}
303
- themeValues={themeValues}
304
- title={hasTitles ? choice.text : null}
305
- >
306
- <Text
307
- variant="p"
308
- color={
309
- choice.value === value
310
- ? WHITE
311
- : disabledValues.includes(choice.value)
312
- ? STORM_GREY
313
- : MINESHAFT_GREY
308
+ {filteredOptions.map((choice, i) => {
309
+ if (
310
+ choice.value === value &&
311
+ selectedRef !== optionRefs.current[i]
312
+ ) {
313
+ setSelectedRef(optionRefs.current[i]);
314
+ }
315
+ return (
316
+ <DropdownItemWrapper
317
+ id={choice.value === value ? "selected_option" : choice.value}
318
+ key={choice.value}
319
+ ref={optionRefs.current[i]}
320
+ as="button"
321
+ tabIndex={-1}
322
+ onClick={
323
+ disabledValues.includes(choice.value)
324
+ ? evt => evt.preventDefault()
325
+ : () => {
326
+ setSelectedRef(optionRefs.current[i]);
327
+ onSelect(choice.value);
328
+ }
314
329
  }
315
- extraStyles={`padding-left: 16px;
330
+ selected={choice.value === value}
331
+ aria-selected={choice.value === value}
332
+ disabled={disabledValues.includes(choice.value)}
333
+ data-qa={choice.text}
334
+ themeValues={themeValues}
335
+ title={hasTitles ? choice.text : null}
336
+ role="option"
337
+ >
338
+ <Text
339
+ variant="p"
340
+ color={
341
+ choice.value === value
342
+ ? WHITE
343
+ : disabledValues.includes(choice.value)
344
+ ? STORM_GREY
345
+ : MINESHAFT_GREY
346
+ }
347
+ extraStyles={`padding-left: 16px;
316
348
  cursor: ${
317
349
  disabledValues.includes(choice.value)
318
350
  ? "default"
@@ -321,11 +353,12 @@ const Dropdown = ({
321
353
  white-space: nowrap;
322
354
  overflow: hidden;
323
355
  text-overflow: ellipsis;`}
324
- >
325
- {choice.text}
326
- </Text>
327
- </DropdownItemWrapper>
328
- ))}
356
+ >
357
+ {choice.text}
358
+ </Text>
359
+ </DropdownItemWrapper>
360
+ );
361
+ })}
329
362
  </Stack>
330
363
  </DropdownContentWrapper>
331
364
  ) : (
@@ -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 : ""}