@thecb/components 6.0.0-beta.0 → 6.0.0-beta.11

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.0",
3
+ "version": "6.0.0-beta.11",
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,8 +115,11 @@ 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
123
  console.log("focus is", focus);
124
124
  console.log("option refs are", optionRefs.current);
125
125
  const optionEl = optionRefs.current.find(ref => ref.current === focus);
@@ -160,15 +160,37 @@ const Dropdown = ({
160
160
  e.preventDefault();
161
161
  setInputValue(inputValue.slice(0, -1));
162
162
  break;
163
- }
164
- if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
165
- e.preventDefault();
166
- 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;
167
178
  }
168
179
  };
169
180
 
170
181
  useEffect(() => {
171
- 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
172
194
  optionRefs.current[0].current.focus();
173
195
  }
174
196
  clearTimeout(timer);
@@ -176,8 +198,10 @@ const Dropdown = ({
176
198
  }, [isOpen]);
177
199
 
178
200
  useEffect(() => {
179
- clearTimeout(timer);
180
- setTimer(setTimeout(() => setInputValue(""), 2000));
201
+ if (autoEraseTypeAhead) {
202
+ clearTimeout(timer);
203
+ setTimer(setTimeout(() => setInputValue(""), 3000));
204
+ }
181
205
  setFilteredOptions(
182
206
  options.filter(
183
207
  option =>
@@ -194,6 +218,8 @@ const Dropdown = ({
194
218
  !disabledValues.includes(filteredOptions[0].value) &&
195
219
  filteredOptions[0].text != placeholder
196
220
  ) {
221
+ console.log("filtered options are", filteredOptions);
222
+ console.log("option refs are", optionRefs);
197
223
  onSelect(filteredOptions[0].value);
198
224
  }
199
225
  if (optionRefs.current[0].current) {
@@ -204,107 +230,117 @@ const Dropdown = ({
204
230
  }, [filteredOptions]);
205
231
 
206
232
  return (
207
- <Box
208
- onKeyDown={onKeyDown}
209
- onClick={onClick}
210
- padding="0"
211
- width="100%"
212
- hoverStyles={`background-color: ${themeValues.hoverColor};`}
213
- aria-expanded={isOpen}
214
- extraStyles={
215
- disabled &&
216
- `color: #6e727e;
217
- background-color: #f7f7f7;
218
- pointer-events: none;`
219
- }
220
- title={hasTitles ? getSelection() : null}
221
- >
222
- <Box
223
- as="button"
224
- background={isOpen ? themeValues.hoverColor : WHITE}
225
- width="100%"
226
- padding="12px"
227
- hoverStyles={`background-color: ${themeValues.hoverColor};`}
228
- borderSize="1px"
229
- borderColor={
230
- isError
231
- ? ERROR_COLOR
232
- : isOpen
233
- ? themeValues.selectedColor
234
- : GREY_CHATEAU
235
- }
236
- borderRadius="2px"
237
- tabIndex={0}
238
- dataQa={placeholder}
239
- extraStyles={`height: 48px;
240
- ${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 &&
241
258
  `color: #6e727e;
242
- background-color: #f7f7f7;
243
- pointer-events: none;`}
244
- `}
245
- >
246
- <Stack direction="row" bottomItem={2}>
247
- {isOpen ? (
248
- <SearchInput
249
- aria-label={inputValue || "Dropdown awaiting search value"}
250
- value={inputValue}
251
- onChange={noop}
252
- themeValues={themeValues}
253
- />
254
- ) : (
255
- <Text
256
- variant="p"
257
- extraStyles={
258
- disabled &&
259
- `color: #6e727e;
260
259
  background-color: #f7f7f7;
261
260
  pointer-events: none;`
262
- }
263
- >
264
- {getSelection()}
265
- </Text>
266
- )}
267
- <IconWrapper open={isOpen}>
268
- <DropdownIcon />
269
- </IconWrapper>
270
- </Stack>
271
- </Box>
272
- {isOpen ? (
273
- <DropdownContentWrapper
274
- maxHeight={maxHeight}
275
- open={isOpen}
276
- ref={dropdownRef}
277
- widthFitOptions={widthFitOptions}
278
- tabIndex={0}
279
- >
280
- <Stack childGap="0">
281
- {filteredOptions.map((choice, i) => (
282
- <DropdownItemWrapper
283
- key={choice.value}
284
- ref={optionRefs.current[i]}
285
- as="button"
286
- tabIndex={-1}
287
- onClick={
288
- disabledValues.includes(choice.value)
289
- ? evt => evt.preventDefault()
290
- : () => 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]);
291
306
  }
292
- selected={choice.value === value}
293
- disabled={disabledValues.includes(choice.value)}
294
- data-qa={choice.text}
295
- themeValues={themeValues}
296
- title={hasTitles ? choice.text : null}
297
- >
298
- <Text
299
- variant="p"
300
- color={
301
- choice.value === value
302
- ? WHITE
303
- : disabledValues.includes(choice.value)
304
- ? STORM_GREY
305
- : MINESHAFT_GREY
306
- }
307
- 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;
308
344
  cursor: ${
309
345
  disabledValues.includes(choice.value)
310
346
  ? "default"
@@ -313,17 +349,19 @@ const Dropdown = ({
313
349
  white-space: nowrap;
314
350
  overflow: hidden;
315
351
  text-overflow: ellipsis;`}
316
- >
317
- {choice.text}
318
- </Text>
319
- </DropdownItemWrapper>
320
- ))}
321
- </Stack>
322
- </DropdownContentWrapper>
323
- ) : (
324
- <Fragment />
325
- )}
326
- </Box>
352
+ >
353
+ {choice.text}
354
+ </Text>
355
+ </DropdownItemWrapper>
356
+ );
357
+ })}
358
+ </Stack>
359
+ </DropdownContentWrapper>
360
+ ) : (
361
+ <Fragment />
362
+ )}
363
+ </Fragment>
364
+ </Fragment>
327
365
  );
328
366
  };
329
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) ? (
@@ -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
  };