@thecb/components 6.0.0-beta.2 → 6.0.0-beta.20
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 +1453 -114
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1453 -114
- package/dist/index.esm.js.map +1 -1
- package/package.json +2 -1
- package/src/components/atoms/country-dropdown/CountryDropdown.js +1 -0
- package/src/components/atoms/dropdown/Dropdown.js +182 -118
- package/src/components/atoms/form-select/FormSelect.js +4 -2
- package/src/components/atoms/layouts/Box.js +1 -1
- package/src/components/atoms/state-province-dropdown/StateProvinceDropdown.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thecb/components",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.20",
|
|
4
4
|
"description": "Common lib for CityBase react components",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"@babel/runtime": "^7.15.4",
|
|
81
|
+
"core-js": "^3.22.5",
|
|
81
82
|
"formatted-input": "^1.0.0",
|
|
82
83
|
"framer-motion": "^1.11.0",
|
|
83
84
|
"numeral": "^2.0.6",
|
|
@@ -4,6 +4,8 @@ import Text from "../text";
|
|
|
4
4
|
import { noop } from "../../../util/general";
|
|
5
5
|
import DropdownIcon from "./DropdownIcon";
|
|
6
6
|
import styled from "styled-components";
|
|
7
|
+
// support for Array.prototype.at() in older browsers
|
|
8
|
+
import "core-js/proposals/relative-indexing-method";
|
|
7
9
|
|
|
8
10
|
import {
|
|
9
11
|
WHITE,
|
|
@@ -16,11 +18,14 @@ import { fallbackValues } from "./Dropdown.theme";
|
|
|
16
18
|
import { themeComponent } from "../../../util/themeUtils";
|
|
17
19
|
|
|
18
20
|
const IconWrapper = styled.div`
|
|
21
|
+
position: absolute;
|
|
19
22
|
display: flex;
|
|
20
23
|
flex-direction: column;
|
|
21
24
|
justify-content: center;
|
|
22
25
|
transition: transform 0.3s ease;
|
|
23
|
-
${({ open }) => (open ? "transform: rotate(-180deg)" : "")}
|
|
26
|
+
${({ open }) => (open ? "transform: rotate(-180deg)" : "")};
|
|
27
|
+
top: 20px;
|
|
28
|
+
right: 12px;
|
|
24
29
|
`;
|
|
25
30
|
|
|
26
31
|
const DropdownContentWrapper = styled.div`
|
|
@@ -39,9 +44,13 @@ const DropdownContentWrapper = styled.div`
|
|
|
39
44
|
&:focus {
|
|
40
45
|
outline: none;
|
|
41
46
|
}
|
|
47
|
+
|
|
48
|
+
ul {
|
|
49
|
+
padding-left: 0;
|
|
50
|
+
}
|
|
42
51
|
`;
|
|
43
52
|
|
|
44
|
-
const DropdownItemWrapper = styled.
|
|
53
|
+
const DropdownItemWrapper = styled.li`
|
|
45
54
|
background-color: ${({ selected, themeValues }) =>
|
|
46
55
|
selected ? themeValues.selectedColor : WHITE};
|
|
47
56
|
text-align: start;
|
|
@@ -51,6 +60,7 @@ const DropdownItemWrapper = styled.div`
|
|
|
51
60
|
padding: 1rem;
|
|
52
61
|
box-sizing: border-box;
|
|
53
62
|
width: 100%;
|
|
63
|
+
list-style: none;
|
|
54
64
|
cursor: ${({ disabled }) => (disabled ? "default" : "pointer")};
|
|
55
65
|
|
|
56
66
|
&:hover {
|
|
@@ -72,14 +82,6 @@ const DropdownItemWrapper = styled.div`
|
|
|
72
82
|
}
|
|
73
83
|
`;
|
|
74
84
|
|
|
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
85
|
const Dropdown = ({
|
|
84
86
|
placeholder,
|
|
85
87
|
options,
|
|
@@ -93,12 +95,17 @@ const Dropdown = ({
|
|
|
93
95
|
maxHeight,
|
|
94
96
|
widthFitOptions = false,
|
|
95
97
|
disabled,
|
|
96
|
-
hasTitles = false
|
|
98
|
+
hasTitles = false,
|
|
99
|
+
autoEraseTypeAhead = true, // legacy behavior as of 05/22
|
|
100
|
+
ariaLabelledby,
|
|
101
|
+
autocompleteValue = "" // autofill item for browsers, like country-name or address-level1 for state
|
|
97
102
|
}) => {
|
|
98
103
|
const [inputValue, setInputValue] = useState("");
|
|
99
104
|
const [optionsState, setOptionsState] = useState([]);
|
|
100
105
|
const [filteredOptions, setFilteredOptions] = useState([]);
|
|
101
106
|
const [optionsChanged, setOptionsChanged] = useState(true);
|
|
107
|
+
const [selectedRef, setSelectedRef] = useState(undefined);
|
|
108
|
+
const [focusedRef, setFocusedRef] = useState(undefined);
|
|
102
109
|
|
|
103
110
|
if (optionsState !== options) {
|
|
104
111
|
setOptionsState(options);
|
|
@@ -118,16 +125,17 @@ const Dropdown = ({
|
|
|
118
125
|
value ? options.find(option => option.value === value)?.text : placeholder;
|
|
119
126
|
|
|
120
127
|
const onKeyDown = e => {
|
|
128
|
+
console.log("current input value top of keyDown", inputValue);
|
|
121
129
|
const { key, keyCode } = e;
|
|
122
130
|
const focus = document.activeElement;
|
|
123
131
|
console.log("dropdown value is", value);
|
|
124
|
-
console.log("focus is", focus);
|
|
125
|
-
console.log("option refs are", optionRefs.current);
|
|
126
132
|
const optionEl = optionRefs.current.find(ref => ref.current === focus);
|
|
127
|
-
console.log("option el is", optionEl);
|
|
128
133
|
switch (key) {
|
|
129
134
|
case "ArrowDown":
|
|
130
135
|
e.preventDefault();
|
|
136
|
+
if (!isOpen) {
|
|
137
|
+
onClick();
|
|
138
|
+
}
|
|
131
139
|
if (optionEl) {
|
|
132
140
|
if (optionEl.current.nextElementSibling) {
|
|
133
141
|
optionEl.current.nextElementSibling.focus();
|
|
@@ -159,24 +167,48 @@ const Dropdown = ({
|
|
|
159
167
|
break;
|
|
160
168
|
case "Backspace" || "Delete":
|
|
161
169
|
e.preventDefault();
|
|
162
|
-
console.log("input value is", inputValue);
|
|
163
|
-
console.log("new input value will be", inputValue.slice(0, -1));
|
|
164
170
|
setInputValue(inputValue.slice(0, -1));
|
|
165
171
|
break;
|
|
172
|
+
case "Home":
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
optionRefs.current[0].current.focus();
|
|
175
|
+
break;
|
|
176
|
+
case "End":
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
console.log("option refs current", optionRefs.current);
|
|
179
|
+
optionRefs.current.at(-1).current.focus();
|
|
180
|
+
break;
|
|
181
|
+
case "Escape":
|
|
182
|
+
if (isOpen) {
|
|
183
|
+
onClick();
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
166
186
|
}
|
|
167
187
|
if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
|
|
168
188
|
e.preventDefault();
|
|
189
|
+
console.log("current input value inside keydown if", inputValue);
|
|
169
190
|
setInputValue(inputValue + key);
|
|
170
191
|
}
|
|
171
192
|
};
|
|
172
193
|
|
|
194
|
+
const handleItemSelection = (evt, choice, i) => {
|
|
195
|
+
if (disabledValues.includes(choice.value)) {
|
|
196
|
+
evt.preventDefault();
|
|
197
|
+
} else {
|
|
198
|
+
setSelectedRef(optionRefs.current[i]);
|
|
199
|
+
onSelect(choice.value);
|
|
200
|
+
if (isOpen) {
|
|
201
|
+
onClick();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
173
206
|
useEffect(() => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
if (isOpen && optionRefs.current[0].current) {
|
|
207
|
+
if (isOpen && selectedRef !== undefined && selectedRef.current !== null) {
|
|
208
|
+
// WAI-ARIA requires the selected option to receive focus
|
|
209
|
+
selectedRef.current.focus();
|
|
210
|
+
} else if (isOpen && optionRefs.current[0].current) {
|
|
211
|
+
// If no selected option, first option receives focus
|
|
180
212
|
optionRefs.current[0].current.focus();
|
|
181
213
|
}
|
|
182
214
|
clearTimeout(timer);
|
|
@@ -184,8 +216,10 @@ const Dropdown = ({
|
|
|
184
216
|
}, [isOpen]);
|
|
185
217
|
|
|
186
218
|
useEffect(() => {
|
|
187
|
-
|
|
188
|
-
|
|
219
|
+
if (autoEraseTypeAhead) {
|
|
220
|
+
clearTimeout(timer);
|
|
221
|
+
setTimer(setTimeout(() => setInputValue(""), 3000));
|
|
222
|
+
}
|
|
189
223
|
setFilteredOptions(
|
|
190
224
|
options.filter(
|
|
191
225
|
option =>
|
|
@@ -213,26 +247,31 @@ const Dropdown = ({
|
|
|
213
247
|
|
|
214
248
|
return (
|
|
215
249
|
<Box
|
|
216
|
-
onKeyDown={onKeyDown}
|
|
217
|
-
onClick={onClick}
|
|
218
250
|
padding="0"
|
|
251
|
+
background={isOpen ? themeValues.hoverColor : WHITE}
|
|
252
|
+
extraStyles={`position: relative;`}
|
|
253
|
+
minWidth="100%"
|
|
254
|
+
onClick={() => {
|
|
255
|
+
if (!isOpen) {
|
|
256
|
+
onClick();
|
|
257
|
+
}
|
|
258
|
+
}}
|
|
259
|
+
onKeyDown={onKeyDown}
|
|
219
260
|
width="100%"
|
|
220
|
-
hoverStyles={`background-color: ${themeValues.hoverColor};`}
|
|
221
|
-
aria-expanded={isOpen}
|
|
222
|
-
extraStyles={
|
|
223
|
-
disabled &&
|
|
224
|
-
`color: #6e727e;
|
|
225
|
-
background-color: #f7f7f7;
|
|
226
|
-
pointer-events: none;`
|
|
227
|
-
}
|
|
228
|
-
title={hasTitles ? getSelection() : null}
|
|
229
261
|
>
|
|
230
262
|
<Box
|
|
231
|
-
as="
|
|
263
|
+
as="input"
|
|
264
|
+
aria-multiline="false"
|
|
265
|
+
aria-autocomplete="list"
|
|
266
|
+
aria-controls={`${ariaLabelledby}_listbox`}
|
|
267
|
+
aria-activedescendant="focused_option"
|
|
268
|
+
aria-owns={`${ariaLabelledby}_listbox`}
|
|
269
|
+
aria-haspopup="listbox"
|
|
270
|
+
aria-labelledby={ariaLabelledby}
|
|
271
|
+
aria-expanded={isOpen}
|
|
272
|
+
autocomplete={autocompleteValue}
|
|
232
273
|
background={isOpen ? themeValues.hoverColor : WHITE}
|
|
233
|
-
|
|
234
|
-
padding="12px"
|
|
235
|
-
hoverStyles={`background-color: ${themeValues.hoverColor};`}
|
|
274
|
+
borderRadius="2px"
|
|
236
275
|
borderSize="1px"
|
|
237
276
|
borderColor={
|
|
238
277
|
isError
|
|
@@ -241,78 +280,101 @@ const Dropdown = ({
|
|
|
241
280
|
? themeValues.selectedColor
|
|
242
281
|
: GREY_CHATEAU
|
|
243
282
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
extraStyles={`height: 48px;
|
|
248
|
-
${disabled &&
|
|
249
|
-
`color: #6e727e;
|
|
250
|
-
background-color: #f7f7f7;
|
|
251
|
-
pointer-events: none;`}
|
|
252
|
-
`}
|
|
253
|
-
>
|
|
254
|
-
<Stack direction="row" bottomItem={2}>
|
|
255
|
-
{isOpen ? (
|
|
256
|
-
<SearchInput
|
|
257
|
-
aria-label={inputValue || "Dropdown awaiting search value"}
|
|
258
|
-
value={inputValue}
|
|
259
|
-
onChange={noop}
|
|
260
|
-
themeValues={themeValues}
|
|
261
|
-
/>
|
|
262
|
-
) : (
|
|
263
|
-
<Text
|
|
264
|
-
variant="p"
|
|
265
|
-
extraStyles={
|
|
266
|
-
disabled &&
|
|
267
|
-
`color: #6e727e;
|
|
283
|
+
extraStyles={
|
|
284
|
+
disabled &&
|
|
285
|
+
`color: #6e727e;
|
|
268
286
|
background-color: #f7f7f7;
|
|
269
287
|
pointer-events: none;`
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
}
|
|
289
|
+
hoverStyles={`background-color: ${themeValues.hoverColor};`}
|
|
290
|
+
isOpen={isOpen}
|
|
291
|
+
minHeight="48px"
|
|
292
|
+
minWidth="100%"
|
|
293
|
+
name={autocompleteValue}
|
|
294
|
+
onFocus={() => {
|
|
295
|
+
/*
|
|
296
|
+
if (!isOpen) {
|
|
297
|
+
onClick();
|
|
298
|
+
}
|
|
299
|
+
*/
|
|
300
|
+
}}
|
|
301
|
+
onChange={e => {
|
|
302
|
+
console.log("current input value onChange", inputValue);
|
|
303
|
+
console.log("input change event", e.target);
|
|
304
|
+
console.log("input change event value", e.target.value);
|
|
305
|
+
// support autofill and copy/paste
|
|
306
|
+
if (e.tarvet.value !== inputValue) {
|
|
307
|
+
setInputValue(e.target.value);
|
|
308
|
+
}
|
|
309
|
+
}}
|
|
310
|
+
padding="12px"
|
|
311
|
+
placeholder={getSelection()}
|
|
312
|
+
role="combobox"
|
|
313
|
+
themeValues={themeValues}
|
|
314
|
+
title={hasTitles ? getSelection() : null}
|
|
315
|
+
type="text"
|
|
316
|
+
tabIndex={0}
|
|
317
|
+
value={inputValue}
|
|
318
|
+
width="100%"
|
|
319
|
+
dataQa={placeholder}
|
|
320
|
+
/>
|
|
321
|
+
<IconWrapper open={isOpen} onClick={onClick}>
|
|
322
|
+
<DropdownIcon />
|
|
323
|
+
</IconWrapper>
|
|
324
|
+
<Fragment>
|
|
325
|
+
{isOpen ? (
|
|
326
|
+
<DropdownContentWrapper
|
|
327
|
+
maxHeight={maxHeight}
|
|
328
|
+
open={isOpen}
|
|
329
|
+
ref={dropdownRef}
|
|
330
|
+
widthFitOptions={widthFitOptions}
|
|
331
|
+
tabIndex={0}
|
|
332
|
+
role="listbox"
|
|
333
|
+
id={`${ariaLabelledby}_listbox`}
|
|
334
|
+
>
|
|
335
|
+
<Stack childGap="0" as="ul">
|
|
336
|
+
{filteredOptions.map((choice, i) => {
|
|
337
|
+
if (
|
|
338
|
+
choice.value === value &&
|
|
339
|
+
selectedRef !== optionRefs.current[i]
|
|
340
|
+
) {
|
|
341
|
+
setSelectedRef(optionRefs.current[i]);
|
|
299
342
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
343
|
+
return (
|
|
344
|
+
<DropdownItemWrapper
|
|
345
|
+
id={
|
|
346
|
+
focusedRef === optionRefs.current[i]
|
|
347
|
+
? "focused_option"
|
|
348
|
+
: choice.value
|
|
349
|
+
}
|
|
350
|
+
key={choice.value}
|
|
351
|
+
ref={optionRefs.current[i]}
|
|
352
|
+
tabIndex={-1}
|
|
353
|
+
onClick={e => handleItemSelection(e, choice, i)}
|
|
354
|
+
onKeyDown={e => {
|
|
355
|
+
if (e.keyCode === 13) {
|
|
356
|
+
handleItemSelection(e, choice, i);
|
|
357
|
+
}
|
|
358
|
+
}}
|
|
359
|
+
selected={choice.value === value}
|
|
360
|
+
aria-selected={choice.value === value}
|
|
361
|
+
disabled={disabledValues.includes(choice.value)}
|
|
362
|
+
data-qa={choice.text}
|
|
363
|
+
themeValues={themeValues}
|
|
364
|
+
title={hasTitles ? choice.text : null}
|
|
365
|
+
role="option"
|
|
366
|
+
onFocus={() => setFocusedRef(optionRefs.current[i])}
|
|
367
|
+
>
|
|
368
|
+
<Text
|
|
369
|
+
variant="p"
|
|
370
|
+
color={
|
|
371
|
+
choice.value === value
|
|
372
|
+
? WHITE
|
|
373
|
+
: disabledValues.includes(choice.value)
|
|
374
|
+
? STORM_GREY
|
|
375
|
+
: MINESHAFT_GREY
|
|
376
|
+
}
|
|
377
|
+
extraStyles={`padding-left: 16px;
|
|
316
378
|
cursor: ${
|
|
317
379
|
disabledValues.includes(choice.value)
|
|
318
380
|
? "default"
|
|
@@ -321,16 +383,18 @@ const Dropdown = ({
|
|
|
321
383
|
white-space: nowrap;
|
|
322
384
|
overflow: hidden;
|
|
323
385
|
text-overflow: ellipsis;`}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
386
|
+
>
|
|
387
|
+
{choice.text}
|
|
388
|
+
</Text>
|
|
389
|
+
</DropdownItemWrapper>
|
|
390
|
+
);
|
|
391
|
+
})}
|
|
392
|
+
</Stack>
|
|
393
|
+
</DropdownContentWrapper>
|
|
394
|
+
) : (
|
|
395
|
+
<Fragment />
|
|
396
|
+
)}
|
|
397
|
+
</Fragment>
|
|
334
398
|
</Box>
|
|
335
399
|
);
|
|
336
400
|
};
|
|
@@ -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) ? (
|