@thecb/components 5.11.4 → 6.0.0-beta.10

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": "5.11.4",
3
+ "version": "6.0.0-beta.10",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -19,6 +19,7 @@ const CountryDropdown = ({
19
19
  errorMessages={errorMessages}
20
20
  showErrors={showErrors}
21
21
  onChange={onChange}
22
+ autocompleteValue="country-name"
22
23
  />
23
24
  );
24
25
  export default CountryDropdown;
@@ -72,14 +72,6 @@ const DropdownItemWrapper = styled.div`
72
72
  }
73
73
  `;
74
74
 
75
- const SearchInput = styled.input`
76
- border: none;
77
- background-color: ${({ themeValues }) =>
78
- themeValues.hoverColor && themeValues.hoverColor};
79
- font-size: 16px;
80
- height: 24px;
81
- `;
82
-
83
75
  const Dropdown = ({
84
76
  placeholder,
85
77
  options,
@@ -93,12 +85,17 @@ const Dropdown = ({
93
85
  maxHeight,
94
86
  widthFitOptions = false,
95
87
  disabled,
96
- hasTitles = false
88
+ hasTitles = false,
89
+ autoEraseTypeAhead = true, // legacy behavior as of 05/22
90
+ ariaLabelledby,
91
+ autocompleteValue = "" // autofill item for browsers, like country-name or address-level1 for state
97
92
  }) => {
98
93
  const [inputValue, setInputValue] = useState("");
99
94
  const [optionsState, setOptionsState] = useState([]);
100
95
  const [filteredOptions, setFilteredOptions] = useState([]);
101
96
  const [optionsChanged, setOptionsChanged] = useState(true);
97
+ const [selectedRef, setSelectedRef] = useState(undefined);
98
+ const [focusedRef, setFocusedRef] = useState(undefined);
102
99
 
103
100
  if (optionsState !== options) {
104
101
  setOptionsState(options);
@@ -118,9 +115,15 @@ const Dropdown = ({
118
115
  value ? options.find(option => option.value === value)?.text : placeholder;
119
116
 
120
117
  const onKeyDown = e => {
118
+ console.log("key down event is", e.target);
119
+ console.log("key down event value is", e.target.value);
121
120
  const { key, keyCode } = e;
122
121
  const focus = document.activeElement;
122
+ console.log("dropdown value is", value);
123
+ console.log("focus is", focus);
124
+ console.log("option refs are", optionRefs.current);
123
125
  const optionEl = optionRefs.current.find(ref => ref.current === focus);
126
+ console.log("option el is", optionEl);
124
127
  switch (key) {
125
128
  case "ArrowDown":
126
129
  e.preventDefault();
@@ -157,15 +160,32 @@ const Dropdown = ({
157
160
  e.preventDefault();
158
161
  setInputValue(inputValue.slice(0, -1));
159
162
  break;
160
- }
161
- if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
162
- e.preventDefault();
163
- setInputValue(inputValue + key);
163
+ case "Home":
164
+ e.preventDefault();
165
+ optionRefs.current[0].current.focus();
166
+ break;
167
+ case "End":
168
+ e.preventDefault();
169
+ optionRefs.current[
170
+ optionRefs?.current?.length ?? 0 - 1
171
+ ].current.focus();
172
+ break;
164
173
  }
165
174
  };
166
175
 
167
176
  useEffect(() => {
168
- if (isOpen && optionRefs.current[0].current) {
177
+ console.log("option refs in isopen useffect", optionRefs);
178
+ console.log(
179
+ "option ref current in isopen useffect",
180
+ optionRefs.current[0].current
181
+ );
182
+ console.log("selected refs in isopen useffect", selectedRef);
183
+ console.log("value in isopen useffect", value);
184
+ if (isOpen && selectedRef !== undefined && selectedRef.current !== null) {
185
+ // WAI-ARIA requires the selected option to receive focus
186
+ selectedRef.current.focus();
187
+ } else if (isOpen && optionRefs.current[0].current) {
188
+ // If no selected option, first option receives focus
169
189
  optionRefs.current[0].current.focus();
170
190
  }
171
191
  clearTimeout(timer);
@@ -173,8 +193,10 @@ const Dropdown = ({
173
193
  }, [isOpen]);
174
194
 
175
195
  useEffect(() => {
176
- clearTimeout(timer);
177
- setTimer(setTimeout(() => setInputValue(""), 2000));
196
+ if (autoEraseTypeAhead) {
197
+ clearTimeout(timer);
198
+ setTimer(setTimeout(() => setInputValue(""), 3000));
199
+ }
178
200
  setFilteredOptions(
179
201
  options.filter(
180
202
  option =>
@@ -191,6 +213,8 @@ const Dropdown = ({
191
213
  !disabledValues.includes(filteredOptions[0].value) &&
192
214
  filteredOptions[0].text != placeholder
193
215
  ) {
216
+ console.log("filtered options are", filteredOptions);
217
+ console.log("option refs are", optionRefs);
194
218
  onSelect(filteredOptions[0].value);
195
219
  }
196
220
  if (optionRefs.current[0].current) {
@@ -201,107 +225,112 @@ const Dropdown = ({
201
225
  }, [filteredOptions]);
202
226
 
203
227
  return (
204
- <Box
205
- onKeyDown={onKeyDown}
206
- onClick={onClick}
207
- padding="0"
208
- width="100%"
209
- hoverStyles={`background-color: ${themeValues.hoverColor};`}
210
- aria-expanded={isOpen}
211
- extraStyles={
212
- disabled &&
213
- `color: #6e727e;
214
- background-color: #f7f7f7;
215
- pointer-events: none;`
216
- }
217
- title={hasTitles ? getSelection() : null}
218
- >
219
- <Box
220
- as="button"
221
- background={isOpen ? themeValues.hoverColor : WHITE}
222
- width="100%"
223
- padding="12px"
224
- hoverStyles={`background-color: ${themeValues.hoverColor};`}
225
- borderSize="1px"
226
- borderColor={
227
- isError
228
- ? ERROR_COLOR
229
- : isOpen
230
- ? themeValues.selectedColor
231
- : GREY_CHATEAU
232
- }
233
- borderRadius="2px"
234
- tabIndex={0}
235
- dataQa={placeholder}
236
- extraStyles={`height: 48px;
237
- ${disabled &&
228
+ <Fragment>
229
+ <Stack direction="row" bottomItem={2} extraStyles={`position: relative;`}>
230
+ <Box
231
+ as="input"
232
+ aria-multiline="false"
233
+ aria-autocomplete="list"
234
+ aria-controls={`${ariaLabelledby}_listbox`}
235
+ aria-activedescendant="focused_option"
236
+ aria-owns={`${ariaLabelledby}_listbox`}
237
+ aria-haspopup="listbox"
238
+ aria-labelledby={ariaLabelledby}
239
+ aria-expanded={isOpen}
240
+ autocomplete={autocompleteValue}
241
+ background={isOpen ? themeValues.hoverColor : WHITE}
242
+ borderRadius="2px"
243
+ borderSize="1px"
244
+ borderColor={
245
+ isError
246
+ ? ERROR_COLOR
247
+ : isOpen
248
+ ? themeValues.selectedColor
249
+ : GREY_CHATEAU
250
+ }
251
+ extraStyles={
252
+ disabled &&
238
253
  `color: #6e727e;
239
- background-color: #f7f7f7;
240
- pointer-events: none;`}
241
- `}
242
- >
243
- <Stack direction="row" bottomItem={2}>
244
- {isOpen ? (
245
- <SearchInput
246
- aria-label={inputValue || "Dropdown awaiting search value"}
247
- value={inputValue}
248
- onChange={noop}
249
- themeValues={themeValues}
250
- />
251
- ) : (
252
- <Text
253
- variant="p"
254
- extraStyles={
255
- disabled &&
256
- `color: #6e727e;
257
254
  background-color: #f7f7f7;
258
255
  pointer-events: none;`
259
- }
260
- >
261
- {getSelection()}
262
- </Text>
263
- )}
264
- <IconWrapper open={isOpen}>
265
- <DropdownIcon />
266
- </IconWrapper>
267
- </Stack>
268
- </Box>
269
- {isOpen ? (
270
- <DropdownContentWrapper
271
- maxHeight={maxHeight}
272
- open={isOpen}
273
- ref={dropdownRef}
274
- widthFitOptions={widthFitOptions}
275
- tabIndex={0}
256
+ }
257
+ hoverStyles={`background-color: ${themeValues.hoverColor};`}
258
+ isOpen={isOpen}
259
+ name={autocompleteValue}
260
+ onKeyDown={onKeyDown}
261
+ onClick={onClick}
262
+ onFocus={onClick}
263
+ onChange={e => {
264
+ console.log("input change event", e.target);
265
+ console.log("input change event value", e.target.value);
266
+ setInputValue(e.target.value);
267
+ }}
268
+ padding="12px"
269
+ placeholder={getSelection()}
270
+ role="combobox"
271
+ themeValues={themeValues}
272
+ title={hasTitles ? getSelection() : null}
273
+ type="text"
274
+ tabIndex={-1}
275
+ value={inputValue}
276
+ width="100%"
277
+ dataQa={placeholder}
276
278
  >
277
- <Stack childGap="0">
278
- {filteredOptions.map((choice, i) => (
279
- <DropdownItemWrapper
280
- key={choice.value}
281
- ref={optionRefs.current[i]}
282
- as="button"
283
- tabIndex={-1}
284
- onClick={
285
- disabledValues.includes(choice.value)
286
- ? evt => evt.preventDefault()
287
- : () => onSelect(choice.value)
288
- }
289
- selected={choice.value === value}
290
- disabled={disabledValues.includes(choice.value)}
291
- data-qa={choice.text}
292
- themeValues={themeValues}
293
- title={hasTitles ? choice.text : null}
294
- >
295
- <Text
296
- variant="p"
297
- color={
298
- choice.value === value
299
- ? WHITE
300
- : disabledValues.includes(choice.value)
301
- ? STORM_GREY
302
- : MINESHAFT_GREY
279
+ {isOpen ? (
280
+ <DropdownContentWrapper
281
+ maxHeight={maxHeight}
282
+ open={isOpen}
283
+ ref={dropdownRef}
284
+ widthFitOptions={widthFitOptions}
285
+ tabIndex={0}
286
+ role="listbox"
287
+ id={`${ariaLabelledby}_listbox`}
288
+ >
289
+ <Stack childGap="0">
290
+ {filteredOptions.map((choice, i) => {
291
+ if (
292
+ choice.value === value &&
293
+ selectedRef !== optionRefs.current[i]
294
+ ) {
295
+ setSelectedRef(optionRefs.current[i]);
303
296
  }
304
- extraStyles={`padding-left: 16px;
297
+ return (
298
+ <DropdownItemWrapper
299
+ id={
300
+ focusedRef === optionRefs.current[i]
301
+ ? "focused_option"
302
+ : choice.value
303
+ }
304
+ key={choice.value}
305
+ ref={optionRefs.current[i]}
306
+ tabIndex={-1}
307
+ onClick={
308
+ disabledValues.includes(choice.value)
309
+ ? evt => evt.preventDefault()
310
+ : () => {
311
+ setSelectedRef(optionRefs.current[i]);
312
+ onSelect(choice.value);
313
+ }
314
+ }
315
+ selected={choice.value === value}
316
+ aria-selected={choice.value === value}
317
+ disabled={disabledValues.includes(choice.value)}
318
+ data-qa={choice.text}
319
+ themeValues={themeValues}
320
+ title={hasTitles ? choice.text : null}
321
+ role="option"
322
+ onFocus={() => setFocusedRef(optionRefs.current[i])}
323
+ >
324
+ <Text
325
+ variant="p"
326
+ color={
327
+ choice.value === value
328
+ ? WHITE
329
+ : disabledValues.includes(choice.value)
330
+ ? STORM_GREY
331
+ : MINESHAFT_GREY
332
+ }
333
+ extraStyles={`padding-left: 16px;
305
334
  cursor: ${
306
335
  disabledValues.includes(choice.value)
307
336
  ? "default"
@@ -310,17 +339,23 @@ const Dropdown = ({
310
339
  white-space: nowrap;
311
340
  overflow: hidden;
312
341
  text-overflow: ellipsis;`}
313
- >
314
- {choice.text}
315
- </Text>
316
- </DropdownItemWrapper>
317
- ))}
318
- </Stack>
319
- </DropdownContentWrapper>
320
- ) : (
321
- <Fragment />
322
- )}
323
- </Box>
342
+ >
343
+ {choice.text}
344
+ </Text>
345
+ </DropdownItemWrapper>
346
+ );
347
+ })}
348
+ </Stack>
349
+ </DropdownContentWrapper>
350
+ ) : (
351
+ <Fragment />
352
+ )}
353
+ </Box>
354
+ <IconWrapper open={isOpen}>
355
+ <DropdownIcon />
356
+ </IconWrapper>
357
+ </Stack>
358
+ </Fragment>
324
359
  );
325
360
  };
326
361
 
@@ -19,7 +19,8 @@ const FormSelect = ({
19
19
  disabledValues,
20
20
  disabled,
21
21
  themeValues,
22
- hasTitles = false
22
+ hasTitles = false,
23
+ autocompleteValue // autofill item for browsers, like country-name or address-level1 for state
23
24
  }) => {
24
25
  const [open, setOpen] = useState(false);
25
26
  const dropdownRef = useRef(null);
@@ -58,7 +59,7 @@ const FormSelect = ({
58
59
  </Cluster>
59
60
  </Box>
60
61
  <Dropdown
61
- aria-labelledby={labelTextWhenNoError.replace(/\s+/g, "-")}
62
+ ariaLabelledby={labelTextWhenNoError.replace(/\s+/g, "-")}
62
63
  maxHeight={dropdownMaxHeight}
63
64
  hasTitles={hasTitles}
64
65
  placeholder={options[0] ? options[0].text : ""}
@@ -74,6 +75,7 @@ const FormSelect = ({
74
75
  }
75
76
  onClick={() => setOpen(!open)}
76
77
  disabled={disabled}
78
+ autocompleteValue={autocompleteValue}
77
79
  />
78
80
  <Stack direction="row" justify="space-between">
79
81
  {(field.hasErrors && field.dirty) || (field.hasErrors && showErrors) ? (
@@ -22,6 +22,7 @@ const FormStateDropdown = ({
22
22
  labelTextWhenNoError={labelTextWhenNoError}
23
23
  errorMessages={errorMessages}
24
24
  showErrors={showErrors}
25
+ autocompleteValue="address-level1"
25
26
  />
26
27
  );
27
28
  };