@thecb/components 6.0.0-beta.2 → 6.0.0-beta.22
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 +1454 -115
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1454 -115
- 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 +179 -119
- 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.22",
|
|
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,18 @@ const Dropdown = ({
|
|
|
93
95
|
maxHeight,
|
|
94
96
|
widthFitOptions = false,
|
|
95
97
|
disabled,
|
|
96
|
-
hasTitles = false
|
|
98
|
+
hasTitles = false,
|
|
99
|
+
autoEraseTypeAhead = true,
|
|
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);
|
|
109
|
+
const [inputChangedByAutofill, setInputChangedByAutofill] = useState(false);
|
|
102
110
|
|
|
103
111
|
if (optionsState !== options) {
|
|
104
112
|
setOptionsState(options);
|
|
@@ -118,16 +126,17 @@ const Dropdown = ({
|
|
|
118
126
|
value ? options.find(option => option.value === value)?.text : placeholder;
|
|
119
127
|
|
|
120
128
|
const onKeyDown = e => {
|
|
129
|
+
console.log("current input value top of keyDown", inputValue);
|
|
121
130
|
const { key, keyCode } = e;
|
|
122
131
|
const focus = document.activeElement;
|
|
123
132
|
console.log("dropdown value is", value);
|
|
124
|
-
console.log("focus is", focus);
|
|
125
|
-
console.log("option refs are", optionRefs.current);
|
|
126
133
|
const optionEl = optionRefs.current.find(ref => ref.current === focus);
|
|
127
|
-
console.log("option el is", optionEl);
|
|
128
134
|
switch (key) {
|
|
129
135
|
case "ArrowDown":
|
|
130
136
|
e.preventDefault();
|
|
137
|
+
if (!isOpen) {
|
|
138
|
+
onClick();
|
|
139
|
+
}
|
|
131
140
|
if (optionEl) {
|
|
132
141
|
if (optionEl.current.nextElementSibling) {
|
|
133
142
|
optionEl.current.nextElementSibling.focus();
|
|
@@ -159,24 +168,48 @@ const Dropdown = ({
|
|
|
159
168
|
break;
|
|
160
169
|
case "Backspace" || "Delete":
|
|
161
170
|
e.preventDefault();
|
|
162
|
-
console.log("input value is", inputValue);
|
|
163
|
-
console.log("new input value will be", inputValue.slice(0, -1));
|
|
164
171
|
setInputValue(inputValue.slice(0, -1));
|
|
165
172
|
break;
|
|
173
|
+
case "Home":
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
optionRefs.current[0].current.focus();
|
|
176
|
+
break;
|
|
177
|
+
case "End":
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
console.log("option refs current", optionRefs.current);
|
|
180
|
+
optionRefs.current.at(-1).current.focus();
|
|
181
|
+
break;
|
|
182
|
+
case "Escape":
|
|
183
|
+
if (isOpen) {
|
|
184
|
+
onClick();
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
166
187
|
}
|
|
167
188
|
if ((keyCode > 64 && keyCode < 91) || keyCode == 32 || keyCode == 189) {
|
|
168
189
|
e.preventDefault();
|
|
190
|
+
console.log("current input value inside keydown if", inputValue);
|
|
169
191
|
setInputValue(inputValue + key);
|
|
170
192
|
}
|
|
171
193
|
};
|
|
172
194
|
|
|
195
|
+
const handleItemSelection = (evt, choice, i) => {
|
|
196
|
+
if (disabledValues.includes(choice.value)) {
|
|
197
|
+
evt.preventDefault();
|
|
198
|
+
} else {
|
|
199
|
+
setSelectedRef(optionRefs.current[i]);
|
|
200
|
+
onSelect(choice.value);
|
|
201
|
+
if (isOpen) {
|
|
202
|
+
onClick();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
173
207
|
useEffect(() => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
if (isOpen && optionRefs.current[0].current) {
|
|
208
|
+
if (isOpen && selectedRef !== undefined && selectedRef.current !== null) {
|
|
209
|
+
// WAI-ARIA requires the selected option to receive focus
|
|
210
|
+
selectedRef.current.focus();
|
|
211
|
+
} else if (isOpen && optionRefs.current[0].current) {
|
|
212
|
+
// If no selected option, first option receives focus
|
|
180
213
|
optionRefs.current[0].current.focus();
|
|
181
214
|
}
|
|
182
215
|
clearTimeout(timer);
|
|
@@ -184,8 +217,10 @@ const Dropdown = ({
|
|
|
184
217
|
}, [isOpen]);
|
|
185
218
|
|
|
186
219
|
useEffect(() => {
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
if (autoEraseTypeAhead) {
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
setTimer(setTimeout(() => setInputValue(""), 3000));
|
|
223
|
+
}
|
|
189
224
|
setFilteredOptions(
|
|
190
225
|
options.filter(
|
|
191
226
|
option =>
|
|
@@ -197,11 +232,12 @@ const Dropdown = ({
|
|
|
197
232
|
|
|
198
233
|
useEffect(() => {
|
|
199
234
|
if (
|
|
200
|
-
!isOpen &&
|
|
235
|
+
(!isOpen || inputChangedByAutofill) &&
|
|
201
236
|
filteredOptions[0] &&
|
|
202
237
|
!disabledValues.includes(filteredOptions[0].value) &&
|
|
203
238
|
filteredOptions[0].text != placeholder
|
|
204
239
|
) {
|
|
240
|
+
setInputChangedByAutofill(false);
|
|
205
241
|
onSelect(filteredOptions[0].value);
|
|
206
242
|
}
|
|
207
243
|
if (optionRefs.current[0].current) {
|
|
@@ -213,26 +249,31 @@ const Dropdown = ({
|
|
|
213
249
|
|
|
214
250
|
return (
|
|
215
251
|
<Box
|
|
216
|
-
onKeyDown={onKeyDown}
|
|
217
|
-
onClick={onClick}
|
|
218
252
|
padding="0"
|
|
253
|
+
background={isOpen ? themeValues.hoverColor : WHITE}
|
|
254
|
+
extraStyles={`position: relative;`}
|
|
255
|
+
minWidth="100%"
|
|
256
|
+
onClick={() => {
|
|
257
|
+
if (!isOpen) {
|
|
258
|
+
onClick();
|
|
259
|
+
}
|
|
260
|
+
}}
|
|
261
|
+
onKeyDown={onKeyDown}
|
|
219
262
|
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
263
|
>
|
|
230
264
|
<Box
|
|
231
|
-
as="
|
|
265
|
+
as="input"
|
|
266
|
+
aria-multiline="false"
|
|
267
|
+
aria-autocomplete="list"
|
|
268
|
+
aria-controls={`${ariaLabelledby}_listbox`}
|
|
269
|
+
aria-activedescendant="focused_option"
|
|
270
|
+
aria-owns={`${ariaLabelledby}_listbox`}
|
|
271
|
+
aria-haspopup="listbox"
|
|
272
|
+
aria-labelledby={ariaLabelledby}
|
|
273
|
+
aria-expanded={isOpen}
|
|
274
|
+
autocomplete={autocompleteValue}
|
|
232
275
|
background={isOpen ? themeValues.hoverColor : WHITE}
|
|
233
|
-
|
|
234
|
-
padding="12px"
|
|
235
|
-
hoverStyles={`background-color: ${themeValues.hoverColor};`}
|
|
276
|
+
borderRadius="2px"
|
|
236
277
|
borderSize="1px"
|
|
237
278
|
borderColor={
|
|
238
279
|
isError
|
|
@@ -241,78 +282,95 @@ const Dropdown = ({
|
|
|
241
282
|
? themeValues.selectedColor
|
|
242
283
|
: GREY_CHATEAU
|
|
243
284
|
}
|
|
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;
|
|
285
|
+
extraStyles={
|
|
286
|
+
disabled &&
|
|
287
|
+
`color: #6e727e;
|
|
268
288
|
background-color: #f7f7f7;
|
|
269
289
|
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
|
-
|
|
290
|
+
}
|
|
291
|
+
hoverStyles={`background-color: ${themeValues.hoverColor};`}
|
|
292
|
+
isOpen={isOpen}
|
|
293
|
+
minHeight="48px"
|
|
294
|
+
minWidth="100%"
|
|
295
|
+
name={autocompleteValue}
|
|
296
|
+
onChange={e => {
|
|
297
|
+
console.log("current input value onChange", inputValue);
|
|
298
|
+
console.log("input change event", e.target);
|
|
299
|
+
console.log("input change event value", e.target.value);
|
|
300
|
+
// support autofill and copy/paste
|
|
301
|
+
if (e.target.value !== inputValue) {
|
|
302
|
+
setInputValue(e.target.value);
|
|
303
|
+
setInputChangedByAutofill(true);
|
|
304
|
+
}
|
|
305
|
+
}}
|
|
306
|
+
padding="12px"
|
|
307
|
+
placeholder={getSelection()}
|
|
308
|
+
role="combobox"
|
|
309
|
+
themeValues={themeValues}
|
|
310
|
+
title={hasTitles ? getSelection() : null}
|
|
311
|
+
type="text"
|
|
312
|
+
tabIndex={0}
|
|
313
|
+
value={inputValue}
|
|
314
|
+
width="100%"
|
|
315
|
+
dataQa={placeholder}
|
|
316
|
+
/>
|
|
317
|
+
<IconWrapper open={isOpen} onClick={onClick}>
|
|
318
|
+
<DropdownIcon />
|
|
319
|
+
</IconWrapper>
|
|
320
|
+
<Fragment>
|
|
321
|
+
{isOpen ? (
|
|
322
|
+
<DropdownContentWrapper
|
|
323
|
+
maxHeight={maxHeight}
|
|
324
|
+
open={isOpen}
|
|
325
|
+
ref={dropdownRef}
|
|
326
|
+
widthFitOptions={widthFitOptions}
|
|
327
|
+
tabIndex={0}
|
|
328
|
+
role="listbox"
|
|
329
|
+
id={`${ariaLabelledby}_listbox`}
|
|
330
|
+
>
|
|
331
|
+
<Stack childGap="0" as="ul">
|
|
332
|
+
{filteredOptions.map((choice, i) => {
|
|
333
|
+
if (
|
|
334
|
+
choice.value === value &&
|
|
335
|
+
selectedRef !== optionRefs.current[i]
|
|
336
|
+
) {
|
|
337
|
+
setSelectedRef(optionRefs.current[i]);
|
|
299
338
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
339
|
+
return (
|
|
340
|
+
<DropdownItemWrapper
|
|
341
|
+
id={
|
|
342
|
+
focusedRef === optionRefs.current[i]
|
|
343
|
+
? "focused_option"
|
|
344
|
+
: choice.value
|
|
345
|
+
}
|
|
346
|
+
key={choice.value}
|
|
347
|
+
ref={optionRefs.current[i]}
|
|
348
|
+
tabIndex={-1}
|
|
349
|
+
onClick={e => handleItemSelection(e, choice, i)}
|
|
350
|
+
onKeyDown={e => {
|
|
351
|
+
if (e.keyCode === 13) {
|
|
352
|
+
handleItemSelection(e, choice, i);
|
|
353
|
+
}
|
|
354
|
+
}}
|
|
355
|
+
selected={choice.value === value}
|
|
356
|
+
aria-selected={choice.value === value}
|
|
357
|
+
disabled={disabledValues.includes(choice.value)}
|
|
358
|
+
data-qa={choice.text}
|
|
359
|
+
themeValues={themeValues}
|
|
360
|
+
title={hasTitles ? choice.text : null}
|
|
361
|
+
role="option"
|
|
362
|
+
onFocus={() => setFocusedRef(optionRefs.current[i])}
|
|
363
|
+
>
|
|
364
|
+
<Text
|
|
365
|
+
variant="p"
|
|
366
|
+
color={
|
|
367
|
+
choice.value === value
|
|
368
|
+
? WHITE
|
|
369
|
+
: disabledValues.includes(choice.value)
|
|
370
|
+
? STORM_GREY
|
|
371
|
+
: MINESHAFT_GREY
|
|
372
|
+
}
|
|
373
|
+
extraStyles={`padding-left: 16px;
|
|
316
374
|
cursor: ${
|
|
317
375
|
disabledValues.includes(choice.value)
|
|
318
376
|
? "default"
|
|
@@ -321,16 +379,18 @@ const Dropdown = ({
|
|
|
321
379
|
white-space: nowrap;
|
|
322
380
|
overflow: hidden;
|
|
323
381
|
text-overflow: ellipsis;`}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
382
|
+
>
|
|
383
|
+
{choice.text}
|
|
384
|
+
</Text>
|
|
385
|
+
</DropdownItemWrapper>
|
|
386
|
+
);
|
|
387
|
+
})}
|
|
388
|
+
</Stack>
|
|
389
|
+
</DropdownContentWrapper>
|
|
390
|
+
) : (
|
|
391
|
+
<Fragment />
|
|
392
|
+
)}
|
|
393
|
+
</Fragment>
|
|
334
394
|
</Box>
|
|
335
395
|
);
|
|
336
396
|
};
|
|
@@ -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) ? (
|