@canonical/react-components 4.4.0 → 4.5.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.
- package/dist/components/MultiSelect/MultiSelect.d.ts +7 -0
- package/dist/components/MultiSelect/MultiSelect.js +44 -9
- package/dist/components/MultiSelect/MultiSelect.scss +6 -0
- package/dist/components/MultiSelect/MultiSelect.stories.d.ts +3 -0
- package/dist/components/MultiSelect/MultiSelect.stories.js +91 -1
- package/dist/components/SearchBox/SearchBox.js +8 -2
- package/dist/esm/components/MultiSelect/MultiSelect.d.ts +7 -0
- package/dist/esm/components/MultiSelect/MultiSelect.js +44 -9
- package/dist/esm/components/MultiSelect/MultiSelect.scss +6 -0
- package/dist/esm/components/MultiSelect/MultiSelect.stories.d.ts +3 -0
- package/dist/esm/components/MultiSelect/MultiSelect.stories.js +90 -0
- package/dist/esm/components/SearchBox/SearchBox.js +9 -3
- package/package.json +1 -1
|
@@ -24,12 +24,17 @@ export type MultiSelectProps = {
|
|
|
24
24
|
renderItem?: (item: MultiSelectItem) => ReactNode;
|
|
25
25
|
dropdownHeader?: ReactNode;
|
|
26
26
|
dropdownFooter?: ReactNode;
|
|
27
|
+
emptyState?: ReactNode;
|
|
28
|
+
emptyMessage?: string;
|
|
27
29
|
showDropdownFooter?: boolean;
|
|
28
30
|
variant?: "condensed" | "search";
|
|
29
31
|
scrollOverflow?: boolean;
|
|
30
32
|
isSortedAlphabetically?: boolean;
|
|
31
33
|
hasSelectedItemsFirst?: boolean;
|
|
32
34
|
id?: string;
|
|
35
|
+
onSearchChange?: (value: string) => void;
|
|
36
|
+
onOpen?: () => void;
|
|
37
|
+
onClose?: () => void;
|
|
33
38
|
};
|
|
34
39
|
type GroupFn = (items: Parameters<typeof getGroupedItems>[0]) => ReturnType<typeof getGroupedItems>;
|
|
35
40
|
type SortFn = typeof sortAlphabetically;
|
|
@@ -43,6 +48,8 @@ type MultiSelectDropdownProps = {
|
|
|
43
48
|
onDeselectItem?: (item: MultiSelectItem) => void;
|
|
44
49
|
onSelectItem?: (item: MultiSelectItem) => void;
|
|
45
50
|
footer?: ReactNode;
|
|
51
|
+
emptyState?: ReactNode;
|
|
52
|
+
emptyMessage?: string;
|
|
46
53
|
groupFn?: GroupFn;
|
|
47
54
|
sortFn?: SortFn;
|
|
48
55
|
hasSelectedItemsFirst?: boolean;
|
|
@@ -10,7 +10,7 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
10
10
|
require("./MultiSelect.scss");
|
|
11
11
|
var _index = require("../../index");
|
|
12
12
|
var _FadeInDown = require("./FadeInDown");
|
|
13
|
-
const _excluded = ["items", "selectedItems", "disabledItems", "header", "updateItems", "onSelectItem", "onDeselectItem", "isOpen", "footer", "sortFn", "groupFn", "hasSelectedItemsFirst"];
|
|
13
|
+
const _excluded = ["items", "selectedItems", "disabledItems", "header", "updateItems", "onSelectItem", "onDeselectItem", "isOpen", "footer", "emptyState", "emptyMessage", "sortFn", "groupFn", "hasSelectedItemsFirst"];
|
|
14
14
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
16
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
@@ -57,6 +57,8 @@ const MultiSelectDropdown = _ref2 => {
|
|
|
57
57
|
onDeselectItem,
|
|
58
58
|
isOpen,
|
|
59
59
|
footer,
|
|
60
|
+
emptyState,
|
|
61
|
+
emptyMessage,
|
|
60
62
|
sortFn = sortAlphabetically,
|
|
61
63
|
groupFn = getGroupedItems,
|
|
62
64
|
hasSelectedItemsFirst = true
|
|
@@ -72,6 +74,7 @@ const MultiSelectDropdown = _ref2 => {
|
|
|
72
74
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
75
|
}, [isOpen]);
|
|
74
76
|
const hasGroup = (0, _react.useMemo)(() => items.some(item => item.group), [items]);
|
|
77
|
+
const hasItems = items.length > 0;
|
|
75
78
|
const groupedItems = (0, _react.useMemo)(() => hasGroup ? groupFn(items) : [{
|
|
76
79
|
group: "Ungrouped",
|
|
77
80
|
items
|
|
@@ -100,7 +103,7 @@ const MultiSelectDropdown = _ref2 => {
|
|
|
100
103
|
}, /*#__PURE__*/_react.default.createElement("div", _extends({
|
|
101
104
|
className: "multi-select__dropdown",
|
|
102
105
|
role: "listbox"
|
|
103
|
-
}, props), header ? header : null, groupedItems.map(_ref3 => {
|
|
106
|
+
}, props), header ? header : null, hasItems ? groupedItems.map(_ref3 => {
|
|
104
107
|
let {
|
|
105
108
|
group,
|
|
106
109
|
items
|
|
@@ -124,7 +127,9 @@ const MultiSelectDropdown = _ref2 => {
|
|
|
124
127
|
onChange: handleOnChange,
|
|
125
128
|
key: item.value
|
|
126
129
|
})))));
|
|
127
|
-
})
|
|
130
|
+
}) : emptyState !== null && emptyState !== void 0 ? emptyState : emptyMessage ? /*#__PURE__*/_react.default.createElement("p", {
|
|
131
|
+
className: "multi-select__empty-state"
|
|
132
|
+
}, emptyMessage) : null, footer ? /*#__PURE__*/_react.default.createElement("div", {
|
|
128
133
|
className: "multi-select__footer"
|
|
129
134
|
}, footer) : null));
|
|
130
135
|
};
|
|
@@ -158,6 +163,8 @@ MultiSelectDropdown.propTypes = {
|
|
|
158
163
|
onDeselectItem: _propTypes.default.func,
|
|
159
164
|
onSelectItem: _propTypes.default.func,
|
|
160
165
|
footer: _propTypes.default.node,
|
|
166
|
+
emptyState: _propTypes.default.node,
|
|
167
|
+
emptyMessage: _propTypes.default.string,
|
|
161
168
|
groupFn: _propTypes.default.func,
|
|
162
169
|
sortFn: _propTypes.default.any,
|
|
163
170
|
hasSelectedItemsFirst: _propTypes.default.bool
|
|
@@ -177,21 +184,44 @@ const MultiSelect = _ref4 => {
|
|
|
177
184
|
disabledItems = [],
|
|
178
185
|
dropdownHeader,
|
|
179
186
|
dropdownFooter,
|
|
187
|
+
emptyState,
|
|
188
|
+
emptyMessage,
|
|
180
189
|
showDropdownFooter = true,
|
|
181
190
|
variant = "search",
|
|
182
191
|
scrollOverflow = false,
|
|
183
192
|
isSortedAlphabetically = true,
|
|
184
193
|
hasSelectedItemsFirst = true,
|
|
185
194
|
id,
|
|
195
|
+
onSearchChange,
|
|
196
|
+
onOpen,
|
|
197
|
+
onClose,
|
|
186
198
|
help,
|
|
187
199
|
helpClassName
|
|
188
200
|
} = _ref4;
|
|
189
201
|
const buttonRef = (0, _react.useRef)(null);
|
|
190
202
|
const [isDropdownOpen, setIsDropdownOpen] = (0, _react.useState)(false);
|
|
191
203
|
const [filter, setFilter] = (0, _react.useState)("");
|
|
204
|
+
const handleSetDropdownOpen = newState => {
|
|
205
|
+
if (newState && !isDropdownOpen) {
|
|
206
|
+
onOpen === null || onOpen === void 0 || onOpen();
|
|
207
|
+
} else if (!newState && isDropdownOpen) {
|
|
208
|
+
onClose === null || onClose === void 0 || onClose();
|
|
209
|
+
}
|
|
210
|
+
setIsDropdownOpen(newState);
|
|
211
|
+
};
|
|
192
212
|
const [internalSelectedItems, setInternalSelectedItems] = (0, _react.useState)([]);
|
|
193
213
|
const selectedItems = externalSelectedItems || internalSelectedItems;
|
|
194
214
|
const helpId = (0, _react.useId)();
|
|
215
|
+
const updateFilter = value => {
|
|
216
|
+
setFilter(value);
|
|
217
|
+
onSearchChange === null || onSearchChange === void 0 || onSearchChange(value);
|
|
218
|
+
};
|
|
219
|
+
const resetSearch = () => {
|
|
220
|
+
if (!filter.length) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
updateFilter("");
|
|
224
|
+
};
|
|
195
225
|
const updateItems = newItems => {
|
|
196
226
|
const uniqueItems = Array.from(new Set(newItems));
|
|
197
227
|
setInternalSelectedItems(uniqueItems);
|
|
@@ -222,12 +252,12 @@ const MultiSelect = _ref4 => {
|
|
|
222
252
|
className: "multi-select",
|
|
223
253
|
onToggleMenu: isOpen => {
|
|
224
254
|
if (!isOpen) {
|
|
225
|
-
|
|
255
|
+
resetSearch();
|
|
226
256
|
}
|
|
227
257
|
// Handle syncing the state when toggling the menu from within the
|
|
228
258
|
// contextual menu component e.g. when clicking outside.
|
|
229
259
|
if (isOpen !== isDropdownOpen) {
|
|
230
|
-
|
|
260
|
+
handleSetDropdownOpen(isOpen);
|
|
231
261
|
}
|
|
232
262
|
},
|
|
233
263
|
position: "left",
|
|
@@ -242,11 +272,12 @@ const MultiSelect = _ref4 => {
|
|
|
242
272
|
disabled: disabled,
|
|
243
273
|
autoComplete: "off",
|
|
244
274
|
onChange: value => {
|
|
245
|
-
|
|
275
|
+
updateFilter(value);
|
|
246
276
|
// reopen if dropdown has been closed via ESC
|
|
247
|
-
|
|
277
|
+
handleSetDropdownOpen(true);
|
|
248
278
|
},
|
|
249
|
-
|
|
279
|
+
onClear: resetSearch,
|
|
280
|
+
onFocus: () => handleSetDropdownOpen(true),
|
|
250
281
|
placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : "Search",
|
|
251
282
|
required: required,
|
|
252
283
|
type: "text",
|
|
@@ -260,7 +291,7 @@ const MultiSelect = _ref4 => {
|
|
|
260
291
|
"aria-expanded": isDropdownOpen,
|
|
261
292
|
className: "multi-select__select-button",
|
|
262
293
|
onClick: () => {
|
|
263
|
-
|
|
294
|
+
handleSetDropdownOpen(!isDropdownOpen);
|
|
264
295
|
},
|
|
265
296
|
onMouseDown: event => {
|
|
266
297
|
// If the dropdown is open when this button is clicked the
|
|
@@ -295,6 +326,8 @@ const MultiSelect = _ref4 => {
|
|
|
295
326
|
onSelectItem: onSelectItem,
|
|
296
327
|
onDeselectItem: onDeselectItem,
|
|
297
328
|
footer: footer,
|
|
329
|
+
emptyState: emptyState,
|
|
330
|
+
emptyMessage: emptyMessage,
|
|
298
331
|
sortFn: isSortedAlphabetically ? sortAlphabetically : () => 0,
|
|
299
332
|
hasSelectedItemsFirst: hasSelectedItemsFirst
|
|
300
333
|
})), help && /*#__PURE__*/_react.default.createElement("p", {
|
|
@@ -333,6 +366,8 @@ MultiSelect.propTypes = {
|
|
|
333
366
|
renderItem: _propTypes.default.func,
|
|
334
367
|
dropdownHeader: _propTypes.default.node,
|
|
335
368
|
dropdownFooter: _propTypes.default.node,
|
|
369
|
+
emptyState: _propTypes.default.node,
|
|
370
|
+
emptyMessage: _propTypes.default.string,
|
|
336
371
|
showDropdownFooter: _propTypes.default.bool,
|
|
337
372
|
variant: _propTypes.default.oneOf(["condensed", "search"]),
|
|
338
373
|
scrollOverflow: _propTypes.default.bool,
|
|
@@ -9,3 +9,6 @@ export declare const WithDisabledItems: Story;
|
|
|
9
9
|
export declare const WithoutSorting: Story;
|
|
10
10
|
export declare const Disabled: Story;
|
|
11
11
|
export declare const HelpText: Story;
|
|
12
|
+
export declare const FormikCallbacksAndEmptyState: Story;
|
|
13
|
+
export declare const EmptyMessage: Story;
|
|
14
|
+
export declare const EmptyStateNode: Story;
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = exports.WithoutSorting = exports.WithDisabledItems = exports.SearchExample = exports.HelpText = exports.Disabled = exports.CondensedExample = void 0;
|
|
6
|
+
exports.default = exports.WithoutSorting = exports.WithDisabledItems = exports.SearchExample = exports.HelpText = exports.FormikCallbacksAndEmptyState = exports.EmptyStateNode = exports.EmptyMessage = exports.Disabled = exports.CondensedExample = void 0;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _formik = require("formik");
|
|
9
|
+
var _index = require("../../index");
|
|
8
10
|
var _MultiSelect = require("./MultiSelect");
|
|
9
11
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
12
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
@@ -27,6 +29,23 @@ const meta = {
|
|
|
27
29
|
parameters: {}
|
|
28
30
|
};
|
|
29
31
|
var _default = exports.default = meta;
|
|
32
|
+
const groupedItems = [{
|
|
33
|
+
label: "Almond",
|
|
34
|
+
value: "almond",
|
|
35
|
+
group: "Nuts"
|
|
36
|
+
}, {
|
|
37
|
+
label: "Cashew",
|
|
38
|
+
value: "cashew",
|
|
39
|
+
group: "Nuts"
|
|
40
|
+
}, {
|
|
41
|
+
label: "Mango",
|
|
42
|
+
value: "mango",
|
|
43
|
+
group: "Fruit"
|
|
44
|
+
}, {
|
|
45
|
+
label: "Peach",
|
|
46
|
+
value: "peach",
|
|
47
|
+
group: "Fruit"
|
|
48
|
+
}];
|
|
30
49
|
const CondensedExample = exports.CondensedExample = {
|
|
31
50
|
args: {
|
|
32
51
|
items: [...Array.from({
|
|
@@ -119,4 +138,75 @@ const HelpText = exports.HelpText = {
|
|
|
119
138
|
args: _objectSpread(_objectSpread({}, CondensedExample.args), {}, {
|
|
120
139
|
help: /*#__PURE__*/_react.default.createElement("span", null, "This is a help text, that should appear underneath the component.")
|
|
121
140
|
})
|
|
141
|
+
};
|
|
142
|
+
const FormikCallbacksAndEmptyStateTemplate = () => {
|
|
143
|
+
const [selectedItems, setSelectedItems] = (0, _react.useState)([]);
|
|
144
|
+
const [events, setEvents] = (0, _react.useState)([]);
|
|
145
|
+
const addEvent = eventName => {
|
|
146
|
+
setEvents(previousEvents => [eventName, ...previousEvents].slice(0, 6));
|
|
147
|
+
};
|
|
148
|
+
return /*#__PURE__*/_react.default.createElement("div", {
|
|
149
|
+
style: {
|
|
150
|
+
maxWidth: "28rem"
|
|
151
|
+
}
|
|
152
|
+
}, /*#__PURE__*/_react.default.createElement(_formik.Formik, {
|
|
153
|
+
initialValues: {
|
|
154
|
+
ingredients: ""
|
|
155
|
+
},
|
|
156
|
+
onSubmit: () => {}
|
|
157
|
+
}, /*#__PURE__*/_react.default.createElement(_index.FormikField, {
|
|
158
|
+
name: "ingredients",
|
|
159
|
+
component: _MultiSelect.MultiSelect,
|
|
160
|
+
label: "Ingredients",
|
|
161
|
+
variant: "search",
|
|
162
|
+
placeholder: "Search ingredients",
|
|
163
|
+
items: groupedItems,
|
|
164
|
+
selectedItems: selectedItems,
|
|
165
|
+
onItemsUpdate: setSelectedItems,
|
|
166
|
+
onSearchChange: value => {
|
|
167
|
+
addEvent("onSearchChange(\"".concat(value, "\")"));
|
|
168
|
+
},
|
|
169
|
+
onOpen: () => addEvent("onOpen()"),
|
|
170
|
+
onClose: () => addEvent("onClose()"),
|
|
171
|
+
emptyMessage: "No ingredients found"
|
|
172
|
+
})), /*#__PURE__*/_react.default.createElement("p", {
|
|
173
|
+
style: {
|
|
174
|
+
marginBottom: "0.5rem"
|
|
175
|
+
}
|
|
176
|
+
}, "Callback log:"), /*#__PURE__*/_react.default.createElement("ul", {
|
|
177
|
+
style: {
|
|
178
|
+
margin: 0,
|
|
179
|
+
paddingLeft: "1.25rem"
|
|
180
|
+
}
|
|
181
|
+
}, events.map((event, index) => /*#__PURE__*/_react.default.createElement("li", {
|
|
182
|
+
key: "".concat(event, "-").concat(index)
|
|
183
|
+
}, event))));
|
|
184
|
+
};
|
|
185
|
+
const FormikCallbacksAndEmptyState = exports.FormikCallbacksAndEmptyState = {
|
|
186
|
+
render: FormikCallbacksAndEmptyStateTemplate
|
|
187
|
+
};
|
|
188
|
+
const EmptyMessage = exports.EmptyMessage = {
|
|
189
|
+
args: {
|
|
190
|
+
variant: "search",
|
|
191
|
+
items: groupedItems,
|
|
192
|
+
emptyMessage: "No matching ingredients.",
|
|
193
|
+
placeholder: "Try typing kiwi"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const EmptyStateNode = exports.EmptyStateNode = {
|
|
197
|
+
args: {
|
|
198
|
+
variant: "search",
|
|
199
|
+
items: groupedItems,
|
|
200
|
+
placeholder: "Try typing kiwi",
|
|
201
|
+
emptyState: /*#__PURE__*/_react.default.createElement("div", {
|
|
202
|
+
className: "u-align--center",
|
|
203
|
+
style: {
|
|
204
|
+
padding: "0.75rem 1rem"
|
|
205
|
+
}
|
|
206
|
+
}, /*#__PURE__*/_react.default.createElement("strong", null, "No ingredient matches."), /*#__PURE__*/_react.default.createElement("p", {
|
|
207
|
+
style: {
|
|
208
|
+
margin: "0.25rem 0 0"
|
|
209
|
+
}
|
|
210
|
+
}, "Use a broader term."))
|
|
211
|
+
}
|
|
122
212
|
};
|
|
@@ -41,7 +41,10 @@ const SearchBox = /*#__PURE__*/_react.default.forwardRef((_ref, forwardedRef) =>
|
|
|
41
41
|
} = _ref,
|
|
42
42
|
props = _objectWithoutProperties(_ref, _excluded);
|
|
43
43
|
const internalRef = (0, _react.useRef)(null);
|
|
44
|
+
const [internalValue, setInternalValue] = (0, _react.useState)(value !== null && value !== void 0 ? value : "");
|
|
45
|
+
const hasValue = externallyControlled ? Boolean(value) : Boolean(internalValue);
|
|
44
46
|
const resetInput = () => {
|
|
47
|
+
setInternalValue("");
|
|
45
48
|
onChange === null || onChange === void 0 || onChange("");
|
|
46
49
|
onClear === null || onClear === void 0 || onClear();
|
|
47
50
|
if (internalRef.current) {
|
|
@@ -71,7 +74,10 @@ const SearchBox = /*#__PURE__*/_react.default.forwardRef((_ref, forwardedRef) =>
|
|
|
71
74
|
disabled: disabled,
|
|
72
75
|
id: id,
|
|
73
76
|
name: name,
|
|
74
|
-
onChange: evt =>
|
|
77
|
+
onChange: evt => {
|
|
78
|
+
setInternalValue(evt.target.value);
|
|
79
|
+
onChange === null || onChange === void 0 || onChange(evt.target.value);
|
|
80
|
+
},
|
|
75
81
|
onKeyDown: onKeyDown,
|
|
76
82
|
placeholder: placeholder,
|
|
77
83
|
ref: input => {
|
|
@@ -86,7 +92,7 @@ const SearchBox = /*#__PURE__*/_react.default.forwardRef((_ref, forwardedRef) =>
|
|
|
86
92
|
type: "search",
|
|
87
93
|
defaultValue: externallyControlled ? undefined : value,
|
|
88
94
|
value: externallyControlled ? value : undefined
|
|
89
|
-
}, props)),
|
|
95
|
+
}, props)), hasValue && /*#__PURE__*/_react.default.createElement("button", {
|
|
90
96
|
className: "p-search-box__reset",
|
|
91
97
|
disabled: disabled,
|
|
92
98
|
onClick: resetInput,
|
|
@@ -24,12 +24,17 @@ export type MultiSelectProps = {
|
|
|
24
24
|
renderItem?: (item: MultiSelectItem) => ReactNode;
|
|
25
25
|
dropdownHeader?: ReactNode;
|
|
26
26
|
dropdownFooter?: ReactNode;
|
|
27
|
+
emptyState?: ReactNode;
|
|
28
|
+
emptyMessage?: string;
|
|
27
29
|
showDropdownFooter?: boolean;
|
|
28
30
|
variant?: "condensed" | "search";
|
|
29
31
|
scrollOverflow?: boolean;
|
|
30
32
|
isSortedAlphabetically?: boolean;
|
|
31
33
|
hasSelectedItemsFirst?: boolean;
|
|
32
34
|
id?: string;
|
|
35
|
+
onSearchChange?: (value: string) => void;
|
|
36
|
+
onOpen?: () => void;
|
|
37
|
+
onClose?: () => void;
|
|
33
38
|
};
|
|
34
39
|
type GroupFn = (items: Parameters<typeof getGroupedItems>[0]) => ReturnType<typeof getGroupedItems>;
|
|
35
40
|
type SortFn = typeof sortAlphabetically;
|
|
@@ -43,6 +48,8 @@ type MultiSelectDropdownProps = {
|
|
|
43
48
|
onDeselectItem?: (item: MultiSelectItem) => void;
|
|
44
49
|
onSelectItem?: (item: MultiSelectItem) => void;
|
|
45
50
|
footer?: ReactNode;
|
|
51
|
+
emptyState?: ReactNode;
|
|
52
|
+
emptyMessage?: string;
|
|
46
53
|
groupFn?: GroupFn;
|
|
47
54
|
sortFn?: SortFn;
|
|
48
55
|
hasSelectedItemsFirst?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _pt from "prop-types";
|
|
2
|
-
var _excluded = ["items", "selectedItems", "disabledItems", "header", "updateItems", "onSelectItem", "onDeselectItem", "isOpen", "footer", "sortFn", "groupFn", "hasSelectedItemsFirst"];
|
|
2
|
+
var _excluded = ["items", "selectedItems", "disabledItems", "header", "updateItems", "onSelectItem", "onDeselectItem", "isOpen", "footer", "emptyState", "emptyMessage", "sortFn", "groupFn", "hasSelectedItemsFirst"];
|
|
3
3
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
4
4
|
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
|
|
5
5
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
@@ -49,6 +49,8 @@ export var MultiSelectDropdown = _ref2 => {
|
|
|
49
49
|
onDeselectItem,
|
|
50
50
|
isOpen,
|
|
51
51
|
footer,
|
|
52
|
+
emptyState,
|
|
53
|
+
emptyMessage,
|
|
52
54
|
sortFn = sortAlphabetically,
|
|
53
55
|
groupFn = getGroupedItems,
|
|
54
56
|
hasSelectedItemsFirst = true
|
|
@@ -64,6 +66,7 @@ export var MultiSelectDropdown = _ref2 => {
|
|
|
64
66
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
67
|
}, [isOpen]);
|
|
66
68
|
var hasGroup = useMemo(() => items.some(item => item.group), [items]);
|
|
69
|
+
var hasItems = items.length > 0;
|
|
67
70
|
var groupedItems = useMemo(() => hasGroup ? groupFn(items) : [{
|
|
68
71
|
group: "Ungrouped",
|
|
69
72
|
items
|
|
@@ -92,7 +95,7 @@ export var MultiSelectDropdown = _ref2 => {
|
|
|
92
95
|
}, /*#__PURE__*/React.createElement("div", _extends({
|
|
93
96
|
className: "multi-select__dropdown",
|
|
94
97
|
role: "listbox"
|
|
95
|
-
}, props), header ? header : null, groupedItems.map(_ref3 => {
|
|
98
|
+
}, props), header ? header : null, hasItems ? groupedItems.map(_ref3 => {
|
|
96
99
|
var {
|
|
97
100
|
group,
|
|
98
101
|
items
|
|
@@ -116,7 +119,9 @@ export var MultiSelectDropdown = _ref2 => {
|
|
|
116
119
|
onChange: handleOnChange,
|
|
117
120
|
key: item.value
|
|
118
121
|
})))));
|
|
119
|
-
})
|
|
122
|
+
}) : emptyState !== null && emptyState !== void 0 ? emptyState : emptyMessage ? /*#__PURE__*/React.createElement("p", {
|
|
123
|
+
className: "multi-select__empty-state"
|
|
124
|
+
}, emptyMessage) : null, footer ? /*#__PURE__*/React.createElement("div", {
|
|
120
125
|
className: "multi-select__footer"
|
|
121
126
|
}, footer) : null));
|
|
122
127
|
};
|
|
@@ -149,6 +154,8 @@ MultiSelectDropdown.propTypes = {
|
|
|
149
154
|
onDeselectItem: _pt.func,
|
|
150
155
|
onSelectItem: _pt.func,
|
|
151
156
|
footer: _pt.node,
|
|
157
|
+
emptyState: _pt.node,
|
|
158
|
+
emptyMessage: _pt.string,
|
|
152
159
|
groupFn: _pt.func,
|
|
153
160
|
sortFn: _pt.any,
|
|
154
161
|
hasSelectedItemsFirst: _pt.bool
|
|
@@ -168,21 +175,44 @@ export var MultiSelect = _ref4 => {
|
|
|
168
175
|
disabledItems = [],
|
|
169
176
|
dropdownHeader,
|
|
170
177
|
dropdownFooter,
|
|
178
|
+
emptyState,
|
|
179
|
+
emptyMessage,
|
|
171
180
|
showDropdownFooter = true,
|
|
172
181
|
variant = "search",
|
|
173
182
|
scrollOverflow = false,
|
|
174
183
|
isSortedAlphabetically = true,
|
|
175
184
|
hasSelectedItemsFirst = true,
|
|
176
185
|
id,
|
|
186
|
+
onSearchChange,
|
|
187
|
+
onOpen,
|
|
188
|
+
onClose,
|
|
177
189
|
help,
|
|
178
190
|
helpClassName
|
|
179
191
|
} = _ref4;
|
|
180
192
|
var buttonRef = useRef(null);
|
|
181
193
|
var [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
182
194
|
var [filter, setFilter] = useState("");
|
|
195
|
+
var handleSetDropdownOpen = newState => {
|
|
196
|
+
if (newState && !isDropdownOpen) {
|
|
197
|
+
onOpen === null || onOpen === void 0 || onOpen();
|
|
198
|
+
} else if (!newState && isDropdownOpen) {
|
|
199
|
+
onClose === null || onClose === void 0 || onClose();
|
|
200
|
+
}
|
|
201
|
+
setIsDropdownOpen(newState);
|
|
202
|
+
};
|
|
183
203
|
var [internalSelectedItems, setInternalSelectedItems] = useState([]);
|
|
184
204
|
var selectedItems = externalSelectedItems || internalSelectedItems;
|
|
185
205
|
var helpId = useId();
|
|
206
|
+
var updateFilter = value => {
|
|
207
|
+
setFilter(value);
|
|
208
|
+
onSearchChange === null || onSearchChange === void 0 || onSearchChange(value);
|
|
209
|
+
};
|
|
210
|
+
var resetSearch = () => {
|
|
211
|
+
if (!filter.length) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
updateFilter("");
|
|
215
|
+
};
|
|
186
216
|
var updateItems = newItems => {
|
|
187
217
|
var uniqueItems = Array.from(new Set(newItems));
|
|
188
218
|
setInternalSelectedItems(uniqueItems);
|
|
@@ -213,12 +243,12 @@ export var MultiSelect = _ref4 => {
|
|
|
213
243
|
className: "multi-select",
|
|
214
244
|
onToggleMenu: isOpen => {
|
|
215
245
|
if (!isOpen) {
|
|
216
|
-
|
|
246
|
+
resetSearch();
|
|
217
247
|
}
|
|
218
248
|
// Handle syncing the state when toggling the menu from within the
|
|
219
249
|
// contextual menu component e.g. when clicking outside.
|
|
220
250
|
if (isOpen !== isDropdownOpen) {
|
|
221
|
-
|
|
251
|
+
handleSetDropdownOpen(isOpen);
|
|
222
252
|
}
|
|
223
253
|
},
|
|
224
254
|
position: "left",
|
|
@@ -233,11 +263,12 @@ export var MultiSelect = _ref4 => {
|
|
|
233
263
|
disabled: disabled,
|
|
234
264
|
autoComplete: "off",
|
|
235
265
|
onChange: value => {
|
|
236
|
-
|
|
266
|
+
updateFilter(value);
|
|
237
267
|
// reopen if dropdown has been closed via ESC
|
|
238
|
-
|
|
268
|
+
handleSetDropdownOpen(true);
|
|
239
269
|
},
|
|
240
|
-
|
|
270
|
+
onClear: resetSearch,
|
|
271
|
+
onFocus: () => handleSetDropdownOpen(true),
|
|
241
272
|
placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : "Search",
|
|
242
273
|
required: required,
|
|
243
274
|
type: "text",
|
|
@@ -251,7 +282,7 @@ export var MultiSelect = _ref4 => {
|
|
|
251
282
|
"aria-expanded": isDropdownOpen,
|
|
252
283
|
className: "multi-select__select-button",
|
|
253
284
|
onClick: () => {
|
|
254
|
-
|
|
285
|
+
handleSetDropdownOpen(!isDropdownOpen);
|
|
255
286
|
},
|
|
256
287
|
onMouseDown: event => {
|
|
257
288
|
// If the dropdown is open when this button is clicked the
|
|
@@ -286,6 +317,8 @@ export var MultiSelect = _ref4 => {
|
|
|
286
317
|
onSelectItem: onSelectItem,
|
|
287
318
|
onDeselectItem: onDeselectItem,
|
|
288
319
|
footer: footer,
|
|
320
|
+
emptyState: emptyState,
|
|
321
|
+
emptyMessage: emptyMessage,
|
|
289
322
|
sortFn: isSortedAlphabetically ? sortAlphabetically : () => 0,
|
|
290
323
|
hasSelectedItemsFirst: hasSelectedItemsFirst
|
|
291
324
|
})), help && /*#__PURE__*/React.createElement("p", {
|
|
@@ -323,6 +356,8 @@ MultiSelect.propTypes = {
|
|
|
323
356
|
renderItem: _pt.func,
|
|
324
357
|
dropdownHeader: _pt.node,
|
|
325
358
|
dropdownFooter: _pt.node,
|
|
359
|
+
emptyState: _pt.node,
|
|
360
|
+
emptyMessage: _pt.string,
|
|
326
361
|
showDropdownFooter: _pt.bool,
|
|
327
362
|
variant: _pt.oneOf(["condensed", "search"]),
|
|
328
363
|
scrollOverflow: _pt.bool,
|
|
@@ -9,3 +9,6 @@ export declare const WithDisabledItems: Story;
|
|
|
9
9
|
export declare const WithoutSorting: Story;
|
|
10
10
|
export declare const Disabled: Story;
|
|
11
11
|
export declare const HelpText: Story;
|
|
12
|
+
export declare const FormikCallbacksAndEmptyState: Story;
|
|
13
|
+
export declare const EmptyMessage: Story;
|
|
14
|
+
export declare const EmptyStateNode: Story;
|
|
@@ -6,6 +6,8 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
6
6
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { useState } from "react";
|
|
9
|
+
import { Formik } from "formik";
|
|
10
|
+
import { FormikField } from "../../index";
|
|
9
11
|
import { MultiSelect } from "./MultiSelect";
|
|
10
12
|
var Template = props => {
|
|
11
13
|
var [selectedItems, setSelectedItems] = useState(props.selectedItems || []);
|
|
@@ -21,6 +23,23 @@ var meta = {
|
|
|
21
23
|
parameters: {}
|
|
22
24
|
};
|
|
23
25
|
export default meta;
|
|
26
|
+
var groupedItems = [{
|
|
27
|
+
label: "Almond",
|
|
28
|
+
value: "almond",
|
|
29
|
+
group: "Nuts"
|
|
30
|
+
}, {
|
|
31
|
+
label: "Cashew",
|
|
32
|
+
value: "cashew",
|
|
33
|
+
group: "Nuts"
|
|
34
|
+
}, {
|
|
35
|
+
label: "Mango",
|
|
36
|
+
value: "mango",
|
|
37
|
+
group: "Fruit"
|
|
38
|
+
}, {
|
|
39
|
+
label: "Peach",
|
|
40
|
+
value: "peach",
|
|
41
|
+
group: "Fruit"
|
|
42
|
+
}];
|
|
24
43
|
export var CondensedExample = {
|
|
25
44
|
args: {
|
|
26
45
|
items: [...Array.from({
|
|
@@ -113,4 +132,75 @@ export var HelpText = {
|
|
|
113
132
|
args: _objectSpread(_objectSpread({}, CondensedExample.args), {}, {
|
|
114
133
|
help: /*#__PURE__*/React.createElement("span", null, "This is a help text, that should appear underneath the component.")
|
|
115
134
|
})
|
|
135
|
+
};
|
|
136
|
+
var FormikCallbacksAndEmptyStateTemplate = () => {
|
|
137
|
+
var [selectedItems, setSelectedItems] = useState([]);
|
|
138
|
+
var [events, setEvents] = useState([]);
|
|
139
|
+
var addEvent = eventName => {
|
|
140
|
+
setEvents(previousEvents => [eventName, ...previousEvents].slice(0, 6));
|
|
141
|
+
};
|
|
142
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
143
|
+
style: {
|
|
144
|
+
maxWidth: "28rem"
|
|
145
|
+
}
|
|
146
|
+
}, /*#__PURE__*/React.createElement(Formik, {
|
|
147
|
+
initialValues: {
|
|
148
|
+
ingredients: ""
|
|
149
|
+
},
|
|
150
|
+
onSubmit: () => {}
|
|
151
|
+
}, /*#__PURE__*/React.createElement(FormikField, {
|
|
152
|
+
name: "ingredients",
|
|
153
|
+
component: MultiSelect,
|
|
154
|
+
label: "Ingredients",
|
|
155
|
+
variant: "search",
|
|
156
|
+
placeholder: "Search ingredients",
|
|
157
|
+
items: groupedItems,
|
|
158
|
+
selectedItems: selectedItems,
|
|
159
|
+
onItemsUpdate: setSelectedItems,
|
|
160
|
+
onSearchChange: value => {
|
|
161
|
+
addEvent("onSearchChange(\"".concat(value, "\")"));
|
|
162
|
+
},
|
|
163
|
+
onOpen: () => addEvent("onOpen()"),
|
|
164
|
+
onClose: () => addEvent("onClose()"),
|
|
165
|
+
emptyMessage: "No ingredients found"
|
|
166
|
+
})), /*#__PURE__*/React.createElement("p", {
|
|
167
|
+
style: {
|
|
168
|
+
marginBottom: "0.5rem"
|
|
169
|
+
}
|
|
170
|
+
}, "Callback log:"), /*#__PURE__*/React.createElement("ul", {
|
|
171
|
+
style: {
|
|
172
|
+
margin: 0,
|
|
173
|
+
paddingLeft: "1.25rem"
|
|
174
|
+
}
|
|
175
|
+
}, events.map((event, index) => /*#__PURE__*/React.createElement("li", {
|
|
176
|
+
key: "".concat(event, "-").concat(index)
|
|
177
|
+
}, event))));
|
|
178
|
+
};
|
|
179
|
+
export var FormikCallbacksAndEmptyState = {
|
|
180
|
+
render: FormikCallbacksAndEmptyStateTemplate
|
|
181
|
+
};
|
|
182
|
+
export var EmptyMessage = {
|
|
183
|
+
args: {
|
|
184
|
+
variant: "search",
|
|
185
|
+
items: groupedItems,
|
|
186
|
+
emptyMessage: "No matching ingredients.",
|
|
187
|
+
placeholder: "Try typing kiwi"
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
export var EmptyStateNode = {
|
|
191
|
+
args: {
|
|
192
|
+
variant: "search",
|
|
193
|
+
items: groupedItems,
|
|
194
|
+
placeholder: "Try typing kiwi",
|
|
195
|
+
emptyState: /*#__PURE__*/React.createElement("div", {
|
|
196
|
+
className: "u-align--center",
|
|
197
|
+
style: {
|
|
198
|
+
padding: "0.75rem 1rem"
|
|
199
|
+
}
|
|
200
|
+
}, /*#__PURE__*/React.createElement("strong", null, "No ingredient matches."), /*#__PURE__*/React.createElement("p", {
|
|
201
|
+
style: {
|
|
202
|
+
margin: "0.25rem 0 0"
|
|
203
|
+
}
|
|
204
|
+
}, "Use a broader term."))
|
|
205
|
+
}
|
|
116
206
|
};
|
|
@@ -3,7 +3,7 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
|
|
|
3
3
|
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
|
|
4
4
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
5
5
|
import classNames from "classnames";
|
|
6
|
-
import React, { useRef } from "react";
|
|
6
|
+
import React, { useRef, useState } from "react";
|
|
7
7
|
import Icon from "../Icon";
|
|
8
8
|
export var Label = /*#__PURE__*/function (Label) {
|
|
9
9
|
Label["Clear"] = "Clear search field";
|
|
@@ -33,7 +33,10 @@ var SearchBox = /*#__PURE__*/React.forwardRef((_ref, forwardedRef) => {
|
|
|
33
33
|
} = _ref,
|
|
34
34
|
props = _objectWithoutProperties(_ref, _excluded);
|
|
35
35
|
var internalRef = useRef(null);
|
|
36
|
+
var [internalValue, setInternalValue] = useState(value !== null && value !== void 0 ? value : "");
|
|
37
|
+
var hasValue = externallyControlled ? Boolean(value) : Boolean(internalValue);
|
|
36
38
|
var resetInput = () => {
|
|
39
|
+
setInternalValue("");
|
|
37
40
|
_onChange === null || _onChange === void 0 || _onChange("");
|
|
38
41
|
onClear === null || onClear === void 0 || onClear();
|
|
39
42
|
if (internalRef.current) {
|
|
@@ -63,7 +66,10 @@ var SearchBox = /*#__PURE__*/React.forwardRef((_ref, forwardedRef) => {
|
|
|
63
66
|
disabled: disabled,
|
|
64
67
|
id: id,
|
|
65
68
|
name: name,
|
|
66
|
-
onChange: evt =>
|
|
69
|
+
onChange: evt => {
|
|
70
|
+
setInternalValue(evt.target.value);
|
|
71
|
+
_onChange === null || _onChange === void 0 || _onChange(evt.target.value);
|
|
72
|
+
},
|
|
67
73
|
onKeyDown: onKeyDown,
|
|
68
74
|
placeholder: placeholder,
|
|
69
75
|
ref: input => {
|
|
@@ -78,7 +84,7 @@ var SearchBox = /*#__PURE__*/React.forwardRef((_ref, forwardedRef) => {
|
|
|
78
84
|
type: "search",
|
|
79
85
|
defaultValue: externallyControlled ? undefined : value,
|
|
80
86
|
value: externallyControlled ? value : undefined
|
|
81
|
-
}, props)),
|
|
87
|
+
}, props)), hasValue && /*#__PURE__*/React.createElement("button", {
|
|
82
88
|
className: "p-search-box__reset",
|
|
83
89
|
disabled: disabled,
|
|
84
90
|
onClick: resetInput,
|