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

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.12",
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;
@@ -41,7 +41,7 @@ const DropdownContentWrapper = styled.div`
41
41
  }
42
42
  `;
43
43
 
44
- const DropdownItemWrapper = styled.div`
44
+ const DropdownItemWrapper = styled.li`
45
45
  background-color: ${({ selected, themeValues }) =>
46
46
  selected ? themeValues.selectedColor : WHITE};
47
47
  text-align: start;
@@ -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,6 +115,8 @@ 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;
123
122
  console.log("dropdown value is", value);
@@ -161,15 +160,37 @@ const Dropdown = ({
161
160
  e.preventDefault();
162
161
  setInputValue(inputValue.slice(0, -1));
163
162
  break;
164
- }
165
- if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
166
- e.preventDefault();
167
- 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;
173
+ case "Escape":
174
+ if (isOpen) {
175
+ onClick();
176
+ }
177
+ break;
168
178
  }
169
179
  };
170
180
 
171
181
  useEffect(() => {
172
- if (isOpen && optionRefs.current[0].current) {
182
+ console.log("option refs in isopen useffect", optionRefs);
183
+ console.log(
184
+ "option ref current in isopen useffect",
185
+ optionRefs.current[0].current
186
+ );
187
+ console.log("selected refs in isopen useffect", selectedRef);
188
+ console.log("value in isopen useffect", value);
189
+ if (isOpen && selectedRef !== undefined && selectedRef.current !== null) {
190
+ // WAI-ARIA requires the selected option to receive focus
191
+ selectedRef.current.focus();
192
+ } else if (isOpen && optionRefs.current[0].current) {
193
+ // If no selected option, first option receives focus
173
194
  optionRefs.current[0].current.focus();
174
195
  }
175
196
  clearTimeout(timer);
@@ -177,8 +198,10 @@ const Dropdown = ({
177
198
  }, [isOpen]);
178
199
 
179
200
  useEffect(() => {
180
- clearTimeout(timer);
181
- setTimer(setTimeout(() => setInputValue(""), 2000));
201
+ if (autoEraseTypeAhead) {
202
+ clearTimeout(timer);
203
+ setTimer(setTimeout(() => setInputValue(""), 3000));
204
+ }
182
205
  setFilteredOptions(
183
206
  options.filter(
184
207
  option =>
@@ -195,6 +218,8 @@ const Dropdown = ({
195
218
  !disabledValues.includes(filteredOptions[0].value) &&
196
219
  filteredOptions[0].text != placeholder
197
220
  ) {
221
+ console.log("filtered options are", filteredOptions);
222
+ console.log("option refs are", optionRefs);
198
223
  onSelect(filteredOptions[0].value);
199
224
  }
200
225
  if (optionRefs.current[0].current) {
@@ -205,107 +230,117 @@ const Dropdown = ({
205
230
  }, [filteredOptions]);
206
231
 
207
232
  return (
208
- <Box
209
- onKeyDown={onKeyDown}
210
- onClick={onClick}
211
- padding="0"
212
- width="100%"
213
- hoverStyles={`background-color: ${themeValues.hoverColor};`}
214
- aria-expanded={isOpen}
215
- extraStyles={
216
- disabled &&
217
- `color: #6e727e;
218
- background-color: #f7f7f7;
219
- pointer-events: none;`
220
- }
221
- title={hasTitles ? getSelection() : null}
222
- >
223
- <Box
224
- as="button"
225
- background={isOpen ? themeValues.hoverColor : WHITE}
226
- width="100%"
227
- padding="12px"
228
- hoverStyles={`background-color: ${themeValues.hoverColor};`}
229
- borderSize="1px"
230
- borderColor={
231
- isError
232
- ? ERROR_COLOR
233
- : isOpen
234
- ? themeValues.selectedColor
235
- : GREY_CHATEAU
236
- }
237
- borderRadius="2px"
238
- tabIndex={0}
239
- dataQa={placeholder}
240
- extraStyles={`height: 48px;
241
- ${disabled &&
233
+ <Fragment>
234
+ <Stack direction="row" bottomItem={2} extraStyles={`position: relative;`}>
235
+ <Box
236
+ as="input"
237
+ aria-multiline="false"
238
+ aria-autocomplete="list"
239
+ aria-controls={`${ariaLabelledby}_listbox`}
240
+ aria-activedescendant="focused_option"
241
+ aria-owns={`${ariaLabelledby}_listbox`}
242
+ aria-haspopup="listbox"
243
+ aria-labelledby={ariaLabelledby}
244
+ aria-expanded={isOpen}
245
+ autocomplete={autocompleteValue}
246
+ background={isOpen ? themeValues.hoverColor : WHITE}
247
+ borderRadius="2px"
248
+ borderSize="1px"
249
+ borderColor={
250
+ isError
251
+ ? ERROR_COLOR
252
+ : isOpen
253
+ ? themeValues.selectedColor
254
+ : GREY_CHATEAU
255
+ }
256
+ extraStyles={
257
+ disabled &&
242
258
  `color: #6e727e;
243
- background-color: #f7f7f7;
244
- pointer-events: none;`}
245
- `}
246
- >
247
- <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
259
  background-color: #f7f7f7;
262
260
  pointer-events: none;`
263
- }
264
- >
265
- {getSelection()}
266
- </Text>
267
- )}
268
- <IconWrapper open={isOpen}>
269
- <DropdownIcon />
270
- </IconWrapper>
271
- </Stack>
272
- </Box>
273
- {isOpen ? (
274
- <DropdownContentWrapper
275
- maxHeight={maxHeight}
276
- open={isOpen}
277
- ref={dropdownRef}
278
- widthFitOptions={widthFitOptions}
279
- tabIndex={0}
280
- >
281
- <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)
261
+ }
262
+ hoverStyles={`background-color: ${themeValues.hoverColor};`}
263
+ isOpen={isOpen}
264
+ name={autocompleteValue}
265
+ onKeyDown={onKeyDown}
266
+ onClick={onClick}
267
+ onFocus={onClick}
268
+ onChange={e => {
269
+ console.log("input change event", e.target);
270
+ console.log("input change event value", e.target.value);
271
+ setInputValue(e.target.value);
272
+ }}
273
+ padding="12px"
274
+ placeholder={getSelection()}
275
+ role="combobox"
276
+ themeValues={themeValues}
277
+ title={hasTitles ? getSelection() : null}
278
+ type="text"
279
+ tabIndex={-1}
280
+ value={inputValue}
281
+ width="100%"
282
+ dataQa={placeholder}
283
+ />
284
+ <IconWrapper open={isOpen}>
285
+ <DropdownIcon />
286
+ </IconWrapper>
287
+ </Stack>
288
+ <Fragment>
289
+ {isOpen ? (
290
+ <DropdownContentWrapper
291
+ maxHeight={maxHeight}
292
+ open={isOpen}
293
+ ref={dropdownRef}
294
+ widthFitOptions={widthFitOptions}
295
+ tabIndex={0}
296
+ role="listbox"
297
+ id={`${ariaLabelledby}_listbox`}
298
+ >
299
+ <Stack childGap="0" as="ul">
300
+ {filteredOptions.map((choice, i) => {
301
+ if (
302
+ choice.value === value &&
303
+ selectedRef !== optionRefs.current[i]
304
+ ) {
305
+ setSelectedRef(optionRefs.current[i]);
292
306
  }
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
307
- }
308
- extraStyles={`padding-left: 16px;
307
+ return (
308
+ <DropdownItemWrapper
309
+ id={
310
+ focusedRef === optionRefs.current[i]
311
+ ? "focused_option"
312
+ : choice.value
313
+ }
314
+ key={choice.value}
315
+ ref={optionRefs.current[i]}
316
+ tabIndex={-1}
317
+ onClick={
318
+ disabledValues.includes(choice.value)
319
+ ? evt => evt.preventDefault()
320
+ : () => {
321
+ setSelectedRef(optionRefs.current[i]);
322
+ onSelect(choice.value);
323
+ }
324
+ }
325
+ selected={choice.value === value}
326
+ aria-selected={choice.value === value}
327
+ disabled={disabledValues.includes(choice.value)}
328
+ data-qa={choice.text}
329
+ themeValues={themeValues}
330
+ title={hasTitles ? choice.text : null}
331
+ role="option"
332
+ onFocus={() => setFocusedRef(optionRefs.current[i])}
333
+ >
334
+ <Text
335
+ variant="p"
336
+ color={
337
+ choice.value === value
338
+ ? WHITE
339
+ : disabledValues.includes(choice.value)
340
+ ? STORM_GREY
341
+ : MINESHAFT_GREY
342
+ }
343
+ extraStyles={`padding-left: 16px;
309
344
  cursor: ${
310
345
  disabledValues.includes(choice.value)
311
346
  ? "default"
@@ -314,17 +349,19 @@ const Dropdown = ({
314
349
  white-space: nowrap;
315
350
  overflow: hidden;
316
351
  text-overflow: ellipsis;`}
317
- >
318
- {choice.text}
319
- </Text>
320
- </DropdownItemWrapper>
321
- ))}
322
- </Stack>
323
- </DropdownContentWrapper>
324
- ) : (
325
- <Fragment />
326
- )}
327
- </Box>
352
+ >
353
+ {choice.text}
354
+ </Text>
355
+ </DropdownItemWrapper>
356
+ );
357
+ })}
358
+ </Stack>
359
+ </DropdownContentWrapper>
360
+ ) : (
361
+ <Fragment />
362
+ )}
363
+ </Fragment>
364
+ </Fragment>
328
365
  );
329
366
  };
330
367
 
@@ -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) ? (
@@ -80,7 +80,7 @@ const Box = ({
80
80
  onTouchEnd={onTouchEnd}
81
81
  {...rest}
82
82
  >
83
- {safeChildren(children, <Fragment />)}
83
+ {children && safeChildren(children, <Fragment />)}
84
84
  </BoxWrapper>
85
85
  );
86
86
 
@@ -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
  };