@thecb/components 6.0.0-beta.1 → 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": "6.0.0-beta.1",
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,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,32 @@ 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;
168
173
  }
169
174
  };
170
175
 
171
176
  useEffect(() => {
172
- 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
173
189
  optionRefs.current[0].current.focus();
174
190
  }
175
191
  clearTimeout(timer);
@@ -177,8 +193,10 @@ const Dropdown = ({
177
193
  }, [isOpen]);
178
194
 
179
195
  useEffect(() => {
180
- clearTimeout(timer);
181
- setTimer(setTimeout(() => setInputValue(""), 2000));
196
+ if (autoEraseTypeAhead) {
197
+ clearTimeout(timer);
198
+ setTimer(setTimeout(() => setInputValue(""), 3000));
199
+ }
182
200
  setFilteredOptions(
183
201
  options.filter(
184
202
  option =>
@@ -195,6 +213,8 @@ const Dropdown = ({
195
213
  !disabledValues.includes(filteredOptions[0].value) &&
196
214
  filteredOptions[0].text != placeholder
197
215
  ) {
216
+ console.log("filtered options are", filteredOptions);
217
+ console.log("option refs are", optionRefs);
198
218
  onSelect(filteredOptions[0].value);
199
219
  }
200
220
  if (optionRefs.current[0].current) {
@@ -205,107 +225,112 @@ const Dropdown = ({
205
225
  }, [filteredOptions]);
206
226
 
207
227
  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 &&
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 &&
242
253
  `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
254
  background-color: #f7f7f7;
262
255
  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}
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}
280
278
  >
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)
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
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]);
307
296
  }
308
- 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;
309
334
  cursor: ${
310
335
  disabledValues.includes(choice.value)
311
336
  ? "default"
@@ -314,17 +339,23 @@ const Dropdown = ({
314
339
  white-space: nowrap;
315
340
  overflow: hidden;
316
341
  text-overflow: ellipsis;`}
317
- >
318
- {choice.text}
319
- </Text>
320
- </DropdownItemWrapper>
321
- ))}
322
- </Stack>
323
- </DropdownContentWrapper>
324
- ) : (
325
- <Fragment />
326
- )}
327
- </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>
328
359
  );
329
360
  };
330
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
  };