@jobber/components 4.46.5 → 4.48.0
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.
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { ReactElement } from "react";
|
|
2
|
+
import { XOR } from "ts-xor";
|
|
2
3
|
import { ComboboxOption } from "../../Combobox.types";
|
|
3
|
-
interface
|
|
4
|
+
interface ComboboxCloseProps {
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Callback function invoked upon the selection of an option. Provides the selected option(s) as an argument.
|
|
6
7
|
*/
|
|
7
|
-
readonly
|
|
8
|
+
readonly onSelect: (selection: ComboboxOption[]) => void;
|
|
9
|
+
}
|
|
10
|
+
interface ComoboboxSelectProps {
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* Callback function invoked upon the Combobox menu closing. Provides the selected option(s) as an argument.
|
|
14
|
+
*/
|
|
15
|
+
readonly onClose: (selection: ComboboxOption[]) => void;
|
|
16
|
+
}
|
|
17
|
+
interface ComboboxContentBaseProps {
|
|
8
18
|
/**
|
|
9
|
-
*
|
|
19
|
+
* List of selectable options to display.
|
|
10
20
|
*/
|
|
11
|
-
readonly
|
|
21
|
+
readonly options: ComboboxOption[];
|
|
12
22
|
/**
|
|
13
23
|
* Optional action button(s) to display at the bottom of the list.
|
|
14
24
|
*/
|
|
@@ -22,12 +32,13 @@ interface ComboboxContentProps {
|
|
|
22
32
|
* @default ""
|
|
23
33
|
* @type string
|
|
24
34
|
*/
|
|
25
|
-
readonly selected: ComboboxOption
|
|
35
|
+
readonly selected: ComboboxOption[];
|
|
26
36
|
/**
|
|
27
37
|
* The encapsulating noun for the content of the combobox. Used
|
|
28
38
|
* in the empty state, and search placeholder. Should be pluralized.
|
|
29
39
|
*/
|
|
30
40
|
readonly subjectNoun?: string;
|
|
31
41
|
}
|
|
42
|
+
type ComboboxContentProps = ComboboxContentBaseProps & XOR<ComboboxCloseProps, ComoboboxSelectProps>;
|
|
32
43
|
export declare function ComboboxContent(props: ComboboxContentProps): JSX.Element;
|
|
33
44
|
export {};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import React, { SetStateAction } from "react";
|
|
2
2
|
import { ComboboxOption } from "../../Combobox.types";
|
|
3
3
|
interface ComboboxListProps {
|
|
4
|
-
options: ComboboxOption[];
|
|
5
|
-
showEmptyState: boolean;
|
|
6
|
-
selected: ComboboxOption
|
|
7
|
-
optionsListRef: React.RefObject<HTMLUListElement>;
|
|
8
|
-
|
|
9
|
-
selectionHandler: (option: ComboboxOption) => void;
|
|
10
|
-
searchValue: string;
|
|
11
|
-
|
|
4
|
+
readonly options: ComboboxOption[];
|
|
5
|
+
readonly showEmptyState: boolean;
|
|
6
|
+
readonly selected: ComboboxOption[];
|
|
7
|
+
readonly optionsListRef: React.RefObject<HTMLUListElement>;
|
|
8
|
+
readonly setFirstSelectedElement: React.Dispatch<SetStateAction<HTMLElement | null>>;
|
|
9
|
+
readonly selectionHandler: (option: ComboboxOption) => void;
|
|
10
|
+
readonly searchValue: string;
|
|
11
|
+
readonly multiselect: boolean;
|
|
12
|
+
readonly subjectNoun?: string;
|
|
12
13
|
}
|
|
13
14
|
export declare function ComboboxList(props: ComboboxListProps): JSX.Element;
|
|
14
15
|
export {};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { ComboboxOption } from "../Combobox.types";
|
|
3
|
-
|
|
3
|
+
interface useComboboxContent {
|
|
4
4
|
searchValue: string;
|
|
5
5
|
setSearchValue: React.Dispatch<React.SetStateAction<string>>;
|
|
6
|
-
|
|
6
|
+
setFirstSelectedElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
|
7
7
|
filteredOptions: ComboboxOption[];
|
|
8
8
|
optionsListRef: React.RefObject<HTMLUListElement>;
|
|
9
|
-
|
|
9
|
+
selectedOptions: ComboboxOption[];
|
|
10
|
+
setInternalSelected: (selected: ComboboxOption[]) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function useComboboxContent(options: ComboboxOption[], open: boolean, selected: ComboboxOption[], onClose: ((selection: ComboboxOption[]) => void) | undefined): useComboboxContent;
|
|
13
|
+
export {};
|
package/dist/Combobox/index.js
CHANGED
|
@@ -44,7 +44,7 @@ function ComboboxSearch(props) {
|
|
|
44
44
|
}, [props.open]);
|
|
45
45
|
return (React__default["default"].createElement("div", { className: styles$4.search },
|
|
46
46
|
React__default["default"].createElement("input", { type: "search", ref: searchRef, className: styles$4.searchInput, placeholder: props.placeholder ? `Search ${props.placeholder}` : "Search", onChange: (event) => handleSearch(event), value: props.searchValue }),
|
|
47
|
-
props.searchValue && (React__default["default"].createElement("button", { className: styles$4.clearSearch, onClick: clearSearch, "data-testid": "ATL-Combobox-Content-Search-Clear", "aria-label": "Clear search" },
|
|
47
|
+
props.searchValue && (React__default["default"].createElement("button", { className: styles$4.clearSearch, onClick: clearSearch, type: "button", "data-testid": "ATL-Combobox-Content-Search-Clear", "aria-label": "Clear search" },
|
|
48
48
|
React__default["default"].createElement(Icon.Icon, { name: "remove", size: "small" })))));
|
|
49
49
|
function clearSearch() {
|
|
50
50
|
var _a;
|
|
@@ -56,22 +56,22 @@ function ComboboxSearch(props) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
var css_248z$3 = "._5n7VvEn39-Y- {\n position: relative;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n}\n\n.KhX5VfZjAog- {\n display: -ms-flexbox;\n display: flex;\n max-height: calc((16px / 2) * 33.33);\n max-height: calc(calc(16px / 2) * 33.33);\n max-height: calc(var(--space-small) * 33.33);\n margin: 0;\n padding: 0;\n overflow: scroll;\n list-style: none;\n -ms-flex-direction: column;\n flex-direction: column;\n gap: calc(16px / 4);\n gap: var(--space-smaller);\n}\n\n.NNCJVHYx6lc- {\n display: -ms-flexbox;\n display: flex;\n min-height: calc((calc(16px * 3) - calc(16px / 4)));\n min-height: calc((var(--space-largest) - var(--space-smaller)));\n box-sizing: border-box;\n margin: 0 calc(16px / 2);\n margin: 0 var(--space-small);\n padding: calc(16px / 2);\n padding: var(--space-small);\n border-radius: calc(16px / 4);\n border-radius: var(--radius-large);\n color: rgb(1, 41, 57);\n color: var(--color-heading);\n font-weight: 500;\n cursor: pointer;\n transition: all 200ms;\n transition: all var(--timing-base);\n -ms-flex-pack: justify;\n justify-content: space-between;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.
|
|
60
|
-
var styles$3 = {"container":"_5n7VvEn39-Y-","optionsList":"KhX5VfZjAog-","option":"NNCJVHYx6lc-","
|
|
59
|
+
var css_248z$3 = "._5n7VvEn39-Y- {\n position: relative;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n}\n\n.KhX5VfZjAog- {\n display: -ms-flexbox;\n display: flex;\n max-height: calc((16px / 2) * 33.33);\n max-height: calc(calc(16px / 2) * 33.33);\n max-height: calc(var(--space-small) * 33.33);\n margin: 0;\n padding: 0;\n overflow: scroll;\n list-style: none;\n -ms-flex-direction: column;\n flex-direction: column;\n gap: calc(16px / 4);\n gap: var(--space-smaller);\n}\n\n.NNCJVHYx6lc- {\n display: -ms-flexbox;\n display: flex;\n min-height: calc((calc(16px * 3) - calc(16px / 4)));\n min-height: calc((var(--space-largest) - var(--space-smaller)));\n box-sizing: border-box;\n margin: 0 calc(16px / 2);\n margin: 0 var(--space-small);\n padding: calc(16px / 2);\n padding: var(--space-small);\n border-radius: calc(16px / 4);\n border-radius: var(--radius-large);\n color: rgb(1, 41, 57);\n color: var(--color-heading);\n font-weight: 500;\n cursor: pointer;\n transition: all 200ms;\n transition: all var(--timing-base);\n -ms-flex-pack: justify;\n justify-content: space-between;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.NNCJVHYx6lc-:hover,\n.NNCJVHYx6lc-:focus {\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n}\n\n.NNCJVHYx6lc-:focus {\n box-shadow: 0px 0px calc(16px / 4) calc(16px / 8)\n rgb(231, 213, 87);\n box-shadow: var(--shadow-focus);\n}\n\n._2IRm1sf-TUU-,\n.DTuoA8WPNzc- {\n padding: calc(16px / 2) calc(16px * 1);\n padding: var(--space-small) var(--space-base);\n}\n\n._5n7VvEn39-Y-::after {\n content: \"\";\n display: block;\n position: absolute;\n right: 0;\n bottom: calc(16px / 2);\n bottom: var(--space-small);\n left: 0;\n height: calc(16px * 1);\n height: var(--space-base);\n background: linear-gradient(\n 180deg,\n rgba(255, 255, 255, 0) 0%,\n rgb(255, 255, 255) 100%\n );\n}\n\n._5n7VvEn39-Y-::before {\n content: \"\";\n display: block;\n position: absolute;\n top: calc(16px / 2);\n top: var(--space-small);\n right: 0;\n left: 0;\n height: calc(16px * 1);\n height: var(--space-base);\n background: linear-gradient(\n to bottom,\n rgba(255, 255, 255, 1),\n rgba(255, 255, 255, 0)\n );\n}\n";
|
|
60
|
+
var styles$3 = {"container":"_5n7VvEn39-Y-","optionsList":"KhX5VfZjAog-","option":"NNCJVHYx6lc-","filterMessage":"_2IRm1sf-TUU-","emptyStateMessage":"DTuoA8WPNzc-"};
|
|
61
61
|
styleInject_es.styleInject(css_248z$3);
|
|
62
62
|
|
|
63
63
|
function ComboboxList(props) {
|
|
64
|
+
let hasSeenFirstSelected = false;
|
|
64
65
|
return (React__default["default"].createElement("div", { className: styles$3.container },
|
|
65
|
-
!props.showEmptyState && props.options.length > 0 && (React__default["default"].createElement("ul", { className: styles$3.optionsList, role: "listbox", ref: props.optionsListRef }, !props.showEmptyState &&
|
|
66
|
+
!props.showEmptyState && props.options.length > 0 && (React__default["default"].createElement("ul", { className: styles$3.optionsList, role: "listbox", "aria-multiselectable": props.multiselect, ref: props.optionsListRef }, !props.showEmptyState &&
|
|
66
67
|
props.options.map(option => {
|
|
67
|
-
|
|
68
|
-
const isSelected = option.id.toString() === ((_a = props.selected) === null || _a === void 0 ? void 0 : _a.id.toString());
|
|
68
|
+
const isSelected = props.selected.some(selection => selection.id.toString() === option.id.toString());
|
|
69
69
|
return (React__default["default"].createElement("li", { ref: listItem => {
|
|
70
|
-
if (isSelected)
|
|
71
|
-
props.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}) },
|
|
70
|
+
if (isSelected && !hasSeenFirstSelected) {
|
|
71
|
+
props.setFirstSelectedElement(listItem);
|
|
72
|
+
hasSeenFirstSelected = true;
|
|
73
|
+
}
|
|
74
|
+
}, key: option.id, tabIndex: -1, role: "option", "aria-selected": isSelected, onClick: () => props.selectionHandler(option), className: classnames__default["default"](styles$3.option) },
|
|
75
75
|
option.label,
|
|
76
76
|
isSelected && React__default["default"].createElement(Icon.Icon, { name: "checkmark", color: "blue" })));
|
|
77
77
|
}))),
|
|
@@ -104,24 +104,38 @@ function ComboboxContextProvider(props) {
|
|
|
104
104
|
props.children)));
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
function useComboboxContent(options, open) {
|
|
107
|
+
function useComboboxContent(options, open, selected, onClose) {
|
|
108
108
|
const [searchValue, setSearchValue] = React.useState("");
|
|
109
|
-
const [
|
|
109
|
+
const [firstSelectedElement, setFirstSelectedElement] = React.useState(null);
|
|
110
110
|
const optionsListRef = React.useRef(null);
|
|
111
111
|
const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase()));
|
|
112
|
+
const [internalSelected, setInternalSelected] = React.useState(selected);
|
|
113
|
+
const selectedOptions = onClose ? internalSelected : selected;
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
if (!open && onClose) {
|
|
116
|
+
onClose(selectedOptions);
|
|
117
|
+
}
|
|
118
|
+
}, [open, onClose, selectedOptions]);
|
|
112
119
|
React.useEffect(() => {
|
|
113
|
-
if (open &&
|
|
114
|
-
|
|
120
|
+
if (open && firstSelectedElement) {
|
|
121
|
+
firstSelectedElement === null || firstSelectedElement === void 0 ? void 0 : firstSelectedElement.scrollIntoView({
|
|
115
122
|
block: "nearest",
|
|
116
123
|
});
|
|
117
124
|
}
|
|
118
|
-
}, [open,
|
|
125
|
+
}, [open, firstSelectedElement]);
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
if (selectedOptions.length === 0) {
|
|
128
|
+
setFirstSelectedElement(null);
|
|
129
|
+
}
|
|
130
|
+
}, [selectedOptions]);
|
|
119
131
|
return {
|
|
120
132
|
searchValue,
|
|
121
133
|
setSearchValue,
|
|
122
|
-
|
|
134
|
+
setFirstSelectedElement,
|
|
123
135
|
filteredOptions,
|
|
124
136
|
optionsListRef,
|
|
137
|
+
selectedOptions,
|
|
138
|
+
setInternalSelected,
|
|
125
139
|
};
|
|
126
140
|
}
|
|
127
141
|
|
|
@@ -202,23 +216,42 @@ function useComboboxAccessibility(selectionCallback, filteredOptions, optionsLis
|
|
|
202
216
|
}
|
|
203
217
|
|
|
204
218
|
function ComboboxContent(props) {
|
|
205
|
-
const { open, setOpen, wrapperRef } = React__default["default"].useContext(ComboboxContext);
|
|
219
|
+
const { open, setOpen, wrapperRef, multiselect } = React__default["default"].useContext(ComboboxContext);
|
|
206
220
|
const optionsExist = props.options.length > 0;
|
|
207
|
-
const { searchValue, setSearchValue,
|
|
221
|
+
const { searchValue, setSearchValue, setFirstSelectedElement, filteredOptions, optionsListRef, selectedOptions, setInternalSelected, } = useComboboxContent(props.options, open, props.selected, props.onClose);
|
|
208
222
|
const { popperRef, popperStyles, attributes } = useComboboxAccessibility(handleSelection, filteredOptions, optionsListRef, open, setOpen, wrapperRef);
|
|
209
223
|
const template = (React__default["default"].createElement("div", Object.assign({ ref: popperRef, id: "ATL-Combobox-Content", "data-testid": "ATL-Combobox-Content", tabIndex: 0, className: classnames__default["default"](styles$5.content, { [styles$5.hidden]: !open }), style: popperStyles.popper }, attributes.popper),
|
|
210
224
|
React__default["default"].createElement(ComboboxSearch, { open: open, placeholder: props.subjectNoun, searchValue: searchValue, setSearchValue: setSearchValue }),
|
|
211
|
-
React__default["default"].createElement(ComboboxList, { showEmptyState: !optionsExist, options: filteredOptions, selected:
|
|
225
|
+
React__default["default"].createElement(ComboboxList, { multiselect: multiselect, showEmptyState: !optionsExist, options: filteredOptions, selected: selectedOptions, optionsListRef: optionsListRef, setFirstSelectedElement: setFirstSelectedElement, selectionHandler: handleSelection, searchValue: searchValue, subjectNoun: props.subjectNoun }),
|
|
212
226
|
props.children && (React__default["default"].createElement("div", { className: styles$5.actions, role: "group" }, React__default["default"].Children.toArray(props.children).map((child, index, childrenArray) => (React__default["default"].createElement("div", { key: index, className: classnames__default["default"]({
|
|
213
227
|
[styles$5.actionPadding]: index === childrenArray.length - 1,
|
|
214
228
|
}) }, child)))))));
|
|
215
229
|
return ReactDOM__default["default"].createPortal(template, document.body);
|
|
216
230
|
function handleSelection(selection) {
|
|
217
|
-
props.onSelect
|
|
231
|
+
const callbackHandler = props.onSelect
|
|
232
|
+
? props.onSelect
|
|
233
|
+
: setInternalSelected;
|
|
234
|
+
if (multiselect) {
|
|
235
|
+
handleMultiSelect(callbackHandler, selectedOptions, selection);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
handleSingleSelect(callbackHandler, selection);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function handleSingleSelect(selectCallback, selection) {
|
|
242
|
+
selectCallback([selection]);
|
|
218
243
|
setSearchValue("");
|
|
219
244
|
setOpen(false);
|
|
220
245
|
}
|
|
221
246
|
}
|
|
247
|
+
function handleMultiSelect(selectCallback, selected, selection) {
|
|
248
|
+
if (selected.some(s => s.id === selection.id)) {
|
|
249
|
+
selectCallback(selected.filter(s => s.id !== selection.id));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
selectCallback([...selected, selection]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
222
255
|
|
|
223
256
|
var css_248z$1 = ".Ow9wSNnGP-g- {\n width: 100%;\n box-sizing: border-box;\n padding: 0 calc(16px / 2);\n padding: 0 var(--space-small);\n background-color: rgba(255, 255, 255, 1);\n background-color: var(--color-surface);\n}\n\n.ncp0nKHQ7d8- {\n position: relative;\n width: 100%;\n min-height: calc((calc(16px * 3) - calc(16px / 2)));\n min-height: calc((var(--space-largest) - var(--space-small)));\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n border: none;\n border-radius: calc(16px / 4);\n border-radius: var(--radius-large);\n text-align: left;\n background-color: rgba(255, 255, 255, 1);\n background-color: var(--color-surface);\n cursor: pointer;\n transition: all 200ms;\n transition: all var(--timing-base);\n}\n\n.ncp0nKHQ7d8-:focus {\n box-shadow: 0px 0px calc(16px / 4) calc(16px / 8)\n rgb(231, 213, 87);\n box-shadow: var(--shadow-focus);\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n}\n\n.ncp0nKHQ7d8-:hover {\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n}\n\n.ncp0nKHQ7d8- span {\n margin-left: calc(16px / 2);\n margin-left: var(--space-small);\n}\n";
|
|
224
257
|
var styles$1 = {"actionContainer":"Ow9wSNnGP-g-","actionButton":"ncp0nKHQ7d8-"};
|
|
@@ -226,7 +259,7 @@ styleInject_es.styleInject(css_248z$1);
|
|
|
226
259
|
|
|
227
260
|
function ComboboxAction(props) {
|
|
228
261
|
return (React__default["default"].createElement("div", { className: styles$1.actionContainer },
|
|
229
|
-
React__default["default"].createElement("button", { className: styles$1.actionButton, onClick: props.onClick, "aria-label": props.label },
|
|
262
|
+
React__default["default"].createElement("button", { className: styles$1.actionButton, onClick: props.onClick, type: "button", "aria-label": props.label },
|
|
230
263
|
React__default["default"].createElement(Typography.Typography, { element: "span", size: "base", textColor: "green", fontWeight: "semiBold" }, props.label))));
|
|
231
264
|
}
|
|
232
265
|
|
|
@@ -241,7 +274,7 @@ styleInject_es.styleInject(css_248z);
|
|
|
241
274
|
|
|
242
275
|
function ComboboxTriggerChip(props) {
|
|
243
276
|
const { open, setOpen } = React__default["default"].useContext(ComboboxContext);
|
|
244
|
-
return (React__default["default"].createElement("button", { className: styles.trigger, role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": "ATL-Combobox-Content", "aria-autocomplete": "list", onClick: () => {
|
|
277
|
+
return (React__default["default"].createElement("button", { className: styles.trigger, role: "combobox", type: "button", "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": "ATL-Combobox-Content", "aria-autocomplete": "list", onClick: () => {
|
|
245
278
|
setOpen(!open);
|
|
246
279
|
} },
|
|
247
280
|
React__default["default"].createElement(Typography.Typography, { fontWeight: "semiBold", textColor: "heading" }, props.label),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobber/components",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.48.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"> 1%",
|
|
85
85
|
"IE 10"
|
|
86
86
|
],
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "d5d7b2d2ed40fd4fd526f001715b74373966506b"
|
|
88
88
|
}
|