@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/dist/index.cjs.js +138 -78
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +138 -78
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/country-dropdown/CountryDropdown.js +1 -0
- package/src/components/atoms/dropdown/Dropdown.js +158 -123
- package/src/components/atoms/form-select/FormSelect.js +4 -2
- package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.js +1 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
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) ? (
|