@jobber/components 4.50.2 → 4.51.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.
@@ -37,7 +37,6 @@ interface ComboboxCloseProps {
37
37
  }
38
38
  interface ComboboxSelectProps {
39
39
  /**
40
- *
41
40
  * Callback function invoked upon the Combobox menu closing. Provides the selected option(s) as an argument.
42
41
  */
43
42
  readonly onClose: (selection: ComboboxOption[]) => void;
@@ -84,6 +83,28 @@ export interface ComboboxSearchProps {
84
83
  */
85
84
  setSearchValue: Dispatch<SetStateAction<string>>;
86
85
  }
86
+ export interface ComboboxHeaderProps {
87
+ /**
88
+ * Does the Combobox have any options visible in the list.
89
+ */
90
+ readonly hasOptionsVisible: boolean;
91
+ /**
92
+ * The noun to be used in the header label.
93
+ */
94
+ readonly subjectNoun?: string;
95
+ /**
96
+ * The number of selected options.
97
+ */
98
+ readonly selectedCount: number;
99
+ /**
100
+ * The function to call when the clear all button is clicked.
101
+ */
102
+ readonly onClearAll: () => void;
103
+ /**
104
+ * The function to call when the select all button is clicked.
105
+ */
106
+ readonly onSelectAll: () => void;
107
+ }
87
108
  export interface ComboboxListProps {
88
109
  /**
89
110
  * The options to display in the list. May be the full set of the Combobox or could be filtered.
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ import { ComboboxHeaderProps } from "@jobber/components/Combobox/Combobox.types";
3
+ export declare function ComboboxContentHeader(props: ComboboxHeaderProps): JSX.Element;
@@ -0,0 +1 @@
1
+ export { ComboboxContentHeader } from "./ComboboxContentHeader";
@@ -7,7 +7,7 @@ interface useComboboxContent {
7
7
  filteredOptions: ComboboxOption[];
8
8
  optionsListRef: React.RefObject<HTMLUListElement>;
9
9
  selectedOptions: ComboboxOption[];
10
- setInternalSelected: (selected: ComboboxOption[]) => void;
10
+ optionsSelectionHandler: (selected: ComboboxOption[]) => void;
11
11
  }
12
- export declare function useComboboxContent(options: ComboboxOption[], open: boolean, selected: ComboboxOption[], onClose: ((selection: ComboboxOption[]) => void) | undefined): useComboboxContent;
12
+ export declare function useComboboxContent(options: ComboboxOption[], open: boolean, selected: ComboboxOption[], onClose: ((selection: ComboboxOption[]) => void) | undefined, onSelect: ((selection: ComboboxOption[]) => void) | undefined): useComboboxContent;
13
13
  export {};
@@ -8,12 +8,12 @@ var ReactDOM = require('react-dom');
8
8
  var styleInject_es = require('../style-inject.es-9d2f5f4e.js');
9
9
  var Icon = require('../Icon-405a216c.js');
10
10
  var Text = require('../Text-54334431.js');
11
+ var Typography = require('../Typography-83915c54.js');
12
+ var Button = require('../Button-db6114a6.js');
11
13
  var useRefocusOnActivator = require('@jobber/hooks/useRefocusOnActivator');
12
14
  var useFocusTrap = require('@jobber/hooks/useFocusTrap');
13
15
  var reactPopper = require('react-popper');
14
16
  var useOnKeyDown = require('@jobber/hooks/useOnKeyDown');
15
- var Typography = require('../Typography-83915c54.js');
16
- var Button = require('../Button-db6114a6.js');
17
17
  var useAssert = require('@jobber/hooks/useAssert');
18
18
  require('@jobber/design');
19
19
  require('react-router-dom');
@@ -24,13 +24,13 @@ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
24
24
  var classnames__default = /*#__PURE__*/_interopDefaultLegacy(classnames);
25
25
  var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
26
26
 
27
- var css_248z$5 = ".m1w5vdUZ6rQ- {\n z-index: 1002;\n z-index: var(--elevation-tooltip);\n max-width: var(--popover--width);\n min-width: calc((16px * 4) * 3.75);\n min-width: calc(calc(16px * 4) * 3.75);\n min-width: calc(var(--space-extravagant) * 3.75);\n box-shadow: 0px calc(16px / 16) calc(16px / 4) 0px\n rgba(0, 0, 0, 0.1),\n 0px calc(16px / 4) 12px 0px rgba(0, 0, 0, 0.05);\n box-shadow: var(--shadow-base);\n margin-top: calc(16px / 2);\n margin-top: var(--space-small);\n border: calc(16px / 16) solid rgb(225, 225, 225);\n border: var(--border-base) solid var(--color-border);\n border-radius: calc(16px / 2);\n border-radius: var(--radius-larger);\n overflow: scroll;\n overflow-y: auto;\n background: rgba(255, 255, 255, 1);\n background: var(--color-surface);\n}\n\n/* CAUTION: Tests for content visibility assume this class' presence hides the content\n* so please be careful if you change this class name, or the css rules within.\n*/\n\n.m1w5vdUZ6rQ-.Zlkv2HA096A- {\n display: none;\n visibility: hidden;\n}\n\n.YQry-Rd6zfQ- {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n position: sticky;\n gap: calc(16px / 4);\n gap: var(--space-smaller);\n bottom: 0;\n width: 100%;\n background-color: rgba(255, 255, 255, 1);\n background-color: var(--color-surface);\n}\n\n.TcpqL34s4lI- {\n padding-bottom: calc(16px / 2);\n padding-bottom: var(--space-small);\n}\n";
28
- var styles$5 = {"content":"m1w5vdUZ6rQ-","hidden":"Zlkv2HA096A-","actions":"YQry-Rd6zfQ-","actionPadding":"TcpqL34s4lI-"};
29
- styleInject_es.styleInject(css_248z$5);
27
+ var css_248z$6 = ".m1w5vdUZ6rQ- {\n z-index: 1002;\n z-index: var(--elevation-tooltip);\n max-width: var(--popover--width);\n min-width: calc((16px * 4) * 3.75);\n min-width: calc(calc(16px * 4) * 3.75);\n min-width: calc(var(--space-extravagant) * 3.75);\n box-shadow: 0px calc(16px / 16) calc(16px / 4) 0px\n rgba(0, 0, 0, 0.1),\n 0px calc(16px / 4) 12px 0px rgba(0, 0, 0, 0.05);\n box-shadow: var(--shadow-base);\n margin-top: calc(16px / 2);\n margin-top: var(--space-small);\n border: calc(16px / 16) solid rgb(225, 225, 225);\n border: var(--border-base) solid var(--color-border);\n border-radius: calc(16px / 2);\n border-radius: var(--radius-larger);\n overflow: scroll;\n overflow-y: auto;\n background: rgba(255, 255, 255, 1);\n background: var(--color-surface);\n}\n\n/* CAUTION: Tests for content visibility assume this class' presence hides the content\n* so please be careful if you change this class name, or the css rules within.\n*/\n\n.m1w5vdUZ6rQ-.Zlkv2HA096A- {\n display: none;\n visibility: hidden;\n}\n\n.YQry-Rd6zfQ- {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n position: sticky;\n gap: calc(16px / 4);\n gap: var(--space-smaller);\n bottom: 0;\n width: 100%;\n background-color: rgba(255, 255, 255, 1);\n background-color: var(--color-surface);\n}\n\n.TcpqL34s4lI- {\n padding-bottom: calc(16px / 2);\n padding-bottom: var(--space-small);\n}\n";
28
+ var styles$6 = {"content":"m1w5vdUZ6rQ-","hidden":"Zlkv2HA096A-","actions":"YQry-Rd6zfQ-","actionPadding":"TcpqL34s4lI-"};
29
+ styleInject_es.styleInject(css_248z$6);
30
30
 
31
- var css_248z$4 = ".MIyb-oFZg9w- {\n position: relative;\n}\n\n.WZPK91I4aJs- {\n width: 100%;\n padding: calc(16px * 1);\n padding: var(--space-base);\n padding-right: calc((16px * 1.5) * 2.25);\n padding-right: calc(calc(16px * 1.5) * 2.25);\n padding-right: calc(var(--space-large) * 2.25);\n border: none;\n border-bottom: calc(16px / 16) solid rgb(225, 225, 225);\n border-bottom: var(--border-base) solid var(--color-border);\n font-family: \"Inter\", Helvetica, Arial, sans-serif;\n font-family: var(--typography--fontFamily-normal);\n font-size: calc((16px * 1) * 0.875);\n font-size: calc(calc(16px * 1) * 0.875);\n font-size: var(--typography--fontSize-base);\n}\n\n._7loHrVUe-Rk- {\n position: absolute;\n top: 50%;\n right: calc(16px * 1);\n right: var(--space-base);\n z-index: 1002;\n z-index: var(--elevation-tooltip);\n width: calc(16px * 1.5);\n width: var(--space-large);\n height: calc(16px * 1.5);\n height: var(--space-large);\n padding: 0;\n border: none;\n border-radius: 100%;\n border-radius: var(--radius-circle);\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n cursor: pointer;\n -webkit-transform: translateY(-50%);\n transform: translateY(-50%);\n}\n\n._7loHrVUe-Rk-: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";
32
- var styles$4 = {"search":"MIyb-oFZg9w-","searchInput":"WZPK91I4aJs-","clearSearch":"_7loHrVUe-Rk-"};
33
- styleInject_es.styleInject(css_248z$4);
31
+ var css_248z$5 = ".MIyb-oFZg9w- {\n position: relative;\n}\n\n.WZPK91I4aJs- {\n width: 100%;\n padding: calc(16px * 1);\n padding: var(--space-base);\n padding-right: calc((16px * 1.5) * 2.25);\n padding-right: calc(calc(16px * 1.5) * 2.25);\n padding-right: calc(var(--space-large) * 2.25);\n border: none;\n border-bottom: calc(16px / 16) solid rgb(225, 225, 225);\n border-bottom: var(--border-base) solid var(--color-border);\n font-family: \"Inter\", Helvetica, Arial, sans-serif;\n font-family: var(--typography--fontFamily-normal);\n font-size: calc((16px * 1) * 0.875);\n font-size: calc(calc(16px * 1) * 0.875);\n font-size: var(--typography--fontSize-base);\n}\n\n._7loHrVUe-Rk- {\n position: absolute;\n top: 50%;\n right: calc(16px * 1);\n right: var(--space-base);\n z-index: 1002;\n z-index: var(--elevation-tooltip);\n width: calc(16px * 1.5);\n width: var(--space-large);\n height: calc(16px * 1.5);\n height: var(--space-large);\n padding: 0;\n border: none;\n border-radius: 100%;\n border-radius: var(--radius-circle);\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n cursor: pointer;\n -webkit-transform: translateY(-50%);\n transform: translateY(-50%);\n}\n\n._7loHrVUe-Rk-: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";
32
+ var styles$5 = {"search":"MIyb-oFZg9w-","searchInput":"WZPK91I4aJs-","clearSearch":"_7loHrVUe-Rk-"};
33
+ styleInject_es.styleInject(css_248z$5);
34
34
 
35
35
  function ComboboxContentSearch(props) {
36
36
  const searchRef = React.useRef(null);
@@ -42,9 +42,9 @@ function ComboboxContentSearch(props) {
42
42
  }, 0);
43
43
  }
44
44
  }, [props.open]);
45
- return (React__default["default"].createElement("div", { className: styles$4.search },
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, type: "button", "data-testid": "ATL-Combobox-Content-Search-Clear", "aria-label": "Clear search" },
45
+ return (React__default["default"].createElement("div", { className: styles$5.search },
46
+ React__default["default"].createElement("input", { type: "search", ref: searchRef, className: styles$5.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$5.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,14 +56,14 @@ function ComboboxContentSearch(props) {
56
56
  }
57
57
  }
58
58
 
59
- var css_248z$3 = ".SgMzjOcdE-E- {\n position: relative;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n}\n\n.TwoTCjgcssc- {\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._2LVD-wjMRfQ- {\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._2LVD-wjMRfQ-:hover,\n._2LVD-wjMRfQ-:focus {\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n}\n\n._2LVD-wjMRfQ-: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._8T5M7MGwCRE-,\n._4y5NXkNeIQM- {\n padding: calc(16px / 2) calc(16px * 1);\n padding: var(--space-small) var(--space-base);\n}\n\n.SgMzjOcdE-E-::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.SgMzjOcdE-E-::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":"SgMzjOcdE-E-","optionsList":"TwoTCjgcssc-","option":"_2LVD-wjMRfQ-","filterMessage":"_8T5M7MGwCRE-","emptyStateMessage":"_4y5NXkNeIQM-"};
61
- styleInject_es.styleInject(css_248z$3);
59
+ var css_248z$4 = ".SgMzjOcdE-E- {\n position: relative;\n padding: calc(16px / 2) 0;\n padding: var(--space-small) 0;\n}\n\n.TwoTCjgcssc- {\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._2LVD-wjMRfQ- {\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._2LVD-wjMRfQ-:hover,\n._2LVD-wjMRfQ-:focus {\n background-color: rgb(244, 244, 244);\n background-color: var(--color-surface--background);\n}\n\n._2LVD-wjMRfQ-: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._8T5M7MGwCRE-,\n._4y5NXkNeIQM- {\n padding: calc(16px / 2) calc(16px * 1);\n padding: var(--space-small) var(--space-base);\n}\n\n.SgMzjOcdE-E-::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.SgMzjOcdE-E-::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$4 = {"container":"SgMzjOcdE-E-","optionsList":"TwoTCjgcssc-","option":"_2LVD-wjMRfQ-","filterMessage":"_8T5M7MGwCRE-","emptyStateMessage":"_4y5NXkNeIQM-"};
61
+ styleInject_es.styleInject(css_248z$4);
62
62
 
63
63
  function ComboboxContentList(props) {
64
64
  let hasSeenFirstSelected = false;
65
- return (React__default["default"].createElement("div", { className: styles$3.container },
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 &&
65
+ return (React__default["default"].createElement("div", { className: styles$4.container },
66
+ !props.showEmptyState && props.options.length > 0 && (React__default["default"].createElement("ul", { className: styles$4.optionsList, role: "listbox", "aria-multiselectable": props.multiselect, ref: props.optionsListRef }, !props.showEmptyState &&
67
67
  props.options.map(option => {
68
68
  const isSelected = props.selected.some(selection => selection.id.toString() === option.id.toString());
69
69
  return (React__default["default"].createElement("li", { ref: listItem => {
@@ -71,15 +71,15 @@ function ComboboxContentList(props) {
71
71
  props.setFirstSelectedElement(listItem);
72
72
  hasSeenFirstSelected = true;
73
73
  }
74
- }, key: option.id, tabIndex: -1, role: "option", "aria-selected": isSelected, onClick: () => props.selectionHandler(option), className: classnames__default["default"](styles$3.option) },
74
+ }, key: option.id, tabIndex: -1, role: "option", "aria-selected": isSelected, onClick: () => props.selectionHandler(option), className: classnames__default["default"](styles$4.option) },
75
75
  option.label,
76
76
  isSelected && React__default["default"].createElement(Icon.Icon, { name: "checkmark", color: "blue" })));
77
77
  }))),
78
- !props.showEmptyState && props.options.length === 0 && (React__default["default"].createElement("div", { className: styles$3.filterMessage },
78
+ !props.showEmptyState && props.options.length === 0 && (React__default["default"].createElement("div", { className: styles$4.filterMessage },
79
79
  React__default["default"].createElement(Text.Text, { variation: "subdued" },
80
80
  "No results for ",
81
81
  `“${props.searchValue}”`))),
82
- props.showEmptyState && (React__default["default"].createElement("div", { className: styles$3.emptyStateMessage },
82
+ props.showEmptyState && (React__default["default"].createElement("div", { className: styles$4.emptyStateMessage },
83
83
  React__default["default"].createElement(Text.Text, { variation: "subdued" }, getZeroIndexStateText(props.subjectNoun))))));
84
84
  }
85
85
  function getZeroIndexStateText(subjectNoun) {
@@ -89,6 +89,30 @@ function getZeroIndexStateText(subjectNoun) {
89
89
  return "No options yet";
90
90
  }
91
91
 
92
+ var css_248z$3 = ".Q-mwo-g5Fi8- {\n display: -ms-flexbox;\n display: flex;\n padding: calc(16px * 1) calc(16px / 2) 0 calc(16px * 1);\n padding: var(--space-base) var(--space-small) 0 var(--space-base);\n -ms-flex-pack: justify;\n justify-content: space-between;\n gap: calc(16px / 4);\n gap: var(--space-smaller);\n}\n";
93
+ var styles$3 = {"header":"Q-mwo-g5Fi8-"};
94
+ styleInject_es.styleInject(css_248z$3);
95
+
96
+ function ComboboxContentHeader(props) {
97
+ const hasSelected = props.selectedCount > 0;
98
+ const actionLabel = hasSelected ? "Clear" : "Select all";
99
+ const label = getLabel(hasSelected, props.selectedCount, props.subjectNoun);
100
+ const handleSelectAll = hasSelected ? props.onClearAll : props.onSelectAll;
101
+ const showAction = hasSelected || props.hasOptionsVisible;
102
+ return (React__default["default"].createElement("div", { className: styles$3.header, "data-testid": "ATL-Combobox-Header" },
103
+ React__default["default"].createElement(Typography.Typography, { textColor: "heading", fontWeight: "semiBold" }, label),
104
+ showAction && (React__default["default"].createElement(Button.Button, { size: "small", label: actionLabel, type: "tertiary", onClick: handleSelectAll }))));
105
+ }
106
+ function getLabel(hasSelected, count, subjectNoun) {
107
+ if (hasSelected) {
108
+ return `${count} selected`;
109
+ }
110
+ if (subjectNoun) {
111
+ return `Select ${subjectNoun}`;
112
+ }
113
+ return "Select";
114
+ }
115
+
92
116
  var css_248z$2 = ".W8qMZnORYxI- {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 6;\n z-index: var(--elevation-menu);\n background-color: transparent;\n}\n";
93
117
  var styles$2 = {"overlay":"W8qMZnORYxI-"};
94
118
  styleInject_es.styleInject(css_248z$2);
@@ -104,13 +128,14 @@ function ComboboxContextProvider(props) {
104
128
  props.children)));
105
129
  }
106
130
 
107
- function useComboboxContent(options, open, selected, onClose) {
131
+ function useComboboxContent(options, open, selected, onClose, onSelect) {
108
132
  const [searchValue, setSearchValue] = React.useState("");
109
133
  const [firstSelectedElement, setFirstSelectedElement] = React.useState(null);
110
134
  const optionsListRef = React.useRef(null);
111
135
  const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchValue.toLowerCase()));
112
136
  const [internalSelected, setInternalSelected] = React.useState(selected);
113
137
  const selectedOptions = onClose ? internalSelected : selected;
138
+ const optionsSelectionHandler = onSelect ? onSelect : setInternalSelected;
114
139
  React.useEffect(() => {
115
140
  if (!open && onClose) {
116
141
  onClose(selectedOptions);
@@ -135,7 +160,7 @@ function useComboboxContent(options, open, selected, onClose) {
135
160
  filteredOptions,
136
161
  optionsListRef,
137
162
  selectedOptions,
138
- setInternalSelected,
163
+ optionsSelectionHandler,
139
164
  };
140
165
  }
141
166
 
@@ -218,24 +243,26 @@ function useComboboxAccessibility(selectionCallback, filteredOptions, optionsLis
218
243
  function ComboboxContent(props) {
219
244
  const { open, setOpen, wrapperRef, multiselect } = React__default["default"].useContext(ComboboxContext);
220
245
  const optionsExist = props.options.length > 0;
221
- const { searchValue, setSearchValue, setFirstSelectedElement, filteredOptions, optionsListRef, selectedOptions, setInternalSelected, } = useComboboxContent(props.options, open, props.selected, props.onClose);
246
+ const { searchValue, setSearchValue, setFirstSelectedElement, filteredOptions, optionsListRef, selectedOptions, optionsSelectionHandler, } = useComboboxContent(props.options, open, props.selected, props.onClose, props.onSelect);
222
247
  const { popperRef, popperStyles, attributes } = useComboboxAccessibility(handleSelection, filteredOptions, optionsListRef, open, setOpen, wrapperRef);
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),
248
+ 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$6.content, { [styles$6.hidden]: !open }), style: popperStyles.popper }, attributes.popper),
224
249
  React__default["default"].createElement(ComboboxContentSearch, { open: open, placeholder: props.subjectNoun, searchValue: searchValue, setSearchValue: setSearchValue }),
250
+ multiselect && optionsExist && (React__default["default"].createElement(ComboboxContentHeader, { hasOptionsVisible: filteredOptions.length > 0, subjectNoun: props.subjectNoun, selectedCount: selectedOptions.length, onClearAll: () => {
251
+ optionsSelectionHandler([]);
252
+ }, onSelectAll: () => {
253
+ optionsSelectionHandler(filteredOptions);
254
+ } })),
225
255
  React__default["default"].createElement(ComboboxContentList, { multiselect: multiselect, showEmptyState: !optionsExist, options: filteredOptions, selected: selectedOptions, optionsListRef: optionsListRef, setFirstSelectedElement: setFirstSelectedElement, selectionHandler: handleSelection, searchValue: searchValue, subjectNoun: props.subjectNoun }),
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"]({
227
- [styles$5.actionPadding]: index === childrenArray.length - 1,
256
+ props.children && (React__default["default"].createElement("div", { className: styles$6.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"]({
257
+ [styles$6.actionPadding]: index === childrenArray.length - 1,
228
258
  }) }, child)))))));
229
259
  return ReactDOM__default["default"].createPortal(template, document.body);
230
260
  function handleSelection(selection) {
231
- const callbackHandler = props.onSelect
232
- ? props.onSelect
233
- : setInternalSelected;
234
261
  if (multiselect) {
235
- handleMultiSelect(callbackHandler, selectedOptions, selection);
262
+ handleMultiSelect(optionsSelectionHandler, selectedOptions, selection);
236
263
  }
237
264
  else {
238
- handleSingleSelect(callbackHandler, selection);
265
+ handleSingleSelect(optionsSelectionHandler, selection);
239
266
  }
240
267
  }
241
268
  function handleSingleSelect(selectCallback, selection) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jobber/components",
3
- "version": "4.50.2",
3
+ "version": "4.51.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": "358cd26e030a81b4ec1bc20de1e48cdc1a6dae5b"
87
+ "gitHead": "f98d5e7ead5cb5e673ba0b3756b6f47e717e3506"
88
88
  }