@rh-support/components 2.5.2 → 2.5.3
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/lib/esm/Functional/CaseContactsSelectorExternal.d.ts +12 -7
- package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts.map +1 -1
- package/lib/esm/Functional/CaseContactsSelectorExternal.js +67 -60
- package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts +3 -1
- package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts.map +1 -1
- package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.js +53 -6
- package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts +27 -0
- package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts.map +1 -0
- package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.js +26 -0
- package/lib/esm/SingleSelectDropdown/index.d.ts +2 -0
- package/lib/esm/SingleSelectDropdown/index.d.ts.map +1 -0
- package/lib/esm/SingleSelectDropdown/index.js +1 -0
- package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.d.ts +13 -1
- package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.d.ts.map +1 -1
- package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.js +99 -24
- package/lib/esm/hooks/index.d.ts +1 -0
- package/lib/esm/hooks/index.d.ts.map +1 -1
- package/lib/esm/hooks/index.js +1 -0
- package/lib/esm/hooks/usePatternFlySingleSelectToggle.d.ts +16 -0
- package/lib/esm/hooks/usePatternFlySingleSelectToggle.d.ts.map +1 -0
- package/lib/esm/hooks/usePatternFlySingleSelectToggle.js +17 -0
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +1 -1
- package/package.json +2 -4
- package/lib/esm/DropDownList/AsyncDropDownList.d.ts +0 -6
- package/lib/esm/DropDownList/AsyncDropDownList.d.ts.map +0 -1
- package/lib/esm/DropDownList/AsyncDropDownList.js +0 -6
- package/lib/esm/DropDownList/DropDownList.d.ts +0 -9
- package/lib/esm/DropDownList/DropDownList.d.ts.map +0 -1
- package/lib/esm/DropDownList/DropDownList.js +0 -116
- package/lib/esm/DropDownList/DropdownWrapper.d.ts +0 -16
- package/lib/esm/DropDownList/DropdownWrapper.d.ts.map +0 -1
- package/lib/esm/DropDownList/DropdownWrapper.js +0 -19
- package/lib/esm/DropDownList/MultiSelectDropdownList.d.ts +0 -4
- package/lib/esm/DropDownList/MultiSelectDropdownList.d.ts.map +0 -1
- package/lib/esm/DropDownList/MultiSelectDropdownList.js +0 -4
- package/lib/esm/DropDownList/Readme.md +0 -114
- package/lib/esm/DropDownList/SearchableList.d.ts +0 -14
- package/lib/esm/DropDownList/SearchableList.d.ts.map +0 -1
- package/lib/esm/DropDownList/SearchableList.js +0 -98
- package/lib/esm/DropDownList/SelectList.d.ts +0 -11
- package/lib/esm/DropDownList/SelectList.d.ts.map +0 -1
- package/lib/esm/DropDownList/SelectList.js +0 -35
- package/lib/esm/DropDownList/async.d.ts +0 -9
- package/lib/esm/DropDownList/async.d.ts.map +0 -1
- package/lib/esm/DropDownList/async.js +0 -111
- package/lib/esm/DropDownList/dropdownList.css +0 -59
- package/lib/esm/DropDownList/dropdownUtils.d.ts +0 -5
- package/lib/esm/DropDownList/dropdownUtils.d.ts.map +0 -1
- package/lib/esm/DropDownList/dropdownUtils.js +0 -23
- package/lib/esm/DropDownList/index.d.ts +0 -4
- package/lib/esm/DropDownList/index.d.ts.map +0 -1
- package/lib/esm/DropDownList/index.js +0 -3
- package/lib/esm/DropDownList/types.d.ts +0 -53
- package/lib/esm/DropDownList/types.d.ts.map +0 -1
- package/lib/esm/DropDownList/types.js +0 -1
- package/lib/esm/DropDownList/withMulti.d.ts +0 -5
- package/lib/esm/DropDownList/withMulti.d.ts.map +0 -1
- package/lib/esm/DropDownList/withMulti.js +0 -46
|
@@ -4,9 +4,9 @@ import { IContact } from '@cee-eng/hydrajs/@types/models/contact';
|
|
|
4
4
|
import { IDClassNameProps } from '@rh-support/types/shared';
|
|
5
5
|
import { UserAuth } from '@rh-support/user-permissions';
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import {
|
|
7
|
+
import { ITypeaheadDropdownGroupOrOption } from '../TypeaheadDropdown/TypeaheadDropdown';
|
|
8
8
|
export type INotificationContact = IContact | ICaseNotificationAddresses;
|
|
9
|
-
interface IProps
|
|
9
|
+
interface IProps extends IDClassNameProps {
|
|
10
10
|
selectedAccountNumber: string;
|
|
11
11
|
selected: (IContact | ICaseNotificationAddresses)[];
|
|
12
12
|
onChange: (selectedContacts: (IContact | ICaseNotificationAddresses)[]) => void;
|
|
@@ -16,16 +16,21 @@ interface IProps<T extends TypeaheadModel> extends IDClassNameProps, Omit<Typeah
|
|
|
16
16
|
clearButton?: boolean;
|
|
17
17
|
multiple?: boolean;
|
|
18
18
|
placeholder?: string;
|
|
19
|
-
minLength?: number;
|
|
20
19
|
isUpdating?: boolean;
|
|
21
20
|
renderToken?: (option: any, props: any, idx: any) => string | React.ReactNode;
|
|
22
21
|
isExportingPDF?: boolean;
|
|
23
|
-
newSelectionPrefix?: React.ReactNode;
|
|
24
22
|
customEmails?: IAccountNotificationAddresses[];
|
|
25
23
|
isDisabled?: boolean;
|
|
24
|
+
allowCustomEmailAdd?: boolean;
|
|
25
|
+
isInvalid?: boolean;
|
|
26
|
+
groupContactsAndEmails?: boolean;
|
|
27
|
+
onNew?: (query: string) => void;
|
|
28
|
+
canAddNew?: (options: ITypeaheadDropdownGroupOrOption[], query: string) => boolean;
|
|
29
|
+
getCreateNewText?: (query: string) => string;
|
|
26
30
|
}
|
|
27
|
-
declare const
|
|
31
|
+
export declare const CONTACTS_PAGE_SIZE = 10;
|
|
32
|
+
export declare const DEFAULT_CONTACTS_OFFSET: number;
|
|
28
33
|
declare const getHydraContactLabel: (user: INotificationContact) => string;
|
|
29
|
-
declare function CaseContactsSelectorExternal
|
|
30
|
-
export { CaseContactsSelectorExternal,
|
|
34
|
+
declare function CaseContactsSelectorExternal(props: IProps): React.JSX.Element;
|
|
35
|
+
export { CaseContactsSelectorExternal, getHydraContactLabel };
|
|
31
36
|
//# sourceMappingURL=CaseContactsSelectorExternal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CaseContactsSelectorExternal.d.ts","sourceRoot":"","sources":["../../../src/Functional/CaseContactsSelectorExternal.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAuB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"CaseContactsSelectorExternal.d.ts","sourceRoot":"","sources":["../../../src/Functional/CaseContactsSelectorExternal.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAuB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAQxD,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AAEzF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AACzE,UAAU,MAAO,SAAQ,gBAAgB;IACrC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,CAAC,QAAQ,GAAG,0BAA0B,CAAC,EAAE,CAAC;IACpD,QAAQ,EAAE,CAAC,gBAAgB,EAAE,CAAC,QAAQ,GAAG,0BAA0B,CAAC,EAAE,KAAK,IAAI,CAAC;IAChF,kBAAkB,EAAE,QAAQ,CAAC;IAC7B,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,MAAM,KAAA,EAAE,KAAK,KAAA,EAAE,GAAG,KAAA,KAAK,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,+BAA+B,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACnF,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAChD;AAED,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,uBAAuB,QAAyB,CAAC;AAU9D,QAAA,MAAM,oBAAoB,SAAU,oBAAoB,KAAG,MAa1D,CAAC;AAEF,iBAAS,4BAA4B,CAAC,KAAK,EAAE,MAAM,qBA0NlD;AAED,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -7,40 +7,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
-
var t = {};
|
|
12
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
-
t[p] = s[p];
|
|
14
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
-
t[p[i]] = s[p[i]];
|
|
18
|
-
}
|
|
19
|
-
return t;
|
|
20
|
-
};
|
|
21
10
|
import { accounts } from '@cee-eng/hydrajs';
|
|
22
|
-
import { getApiResourceObject, sortHydraContacts } from '@rh-support/utils';
|
|
11
|
+
import { getApiResourceObject, isEmailValid, sortHydraContacts } from '@rh-support/utils';
|
|
23
12
|
import differenceBy from 'lodash/differenceBy';
|
|
13
|
+
import filter from 'lodash/filter';
|
|
24
14
|
import isArray from 'lodash/isArray';
|
|
25
15
|
import isEmpty from 'lodash/isEmpty';
|
|
16
|
+
import map from 'lodash/map';
|
|
26
17
|
import sortBy from 'lodash/sortBy';
|
|
27
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
28
|
-
import { Highlighter, Menu, MenuItem, Typeahead } from 'react-bootstrap-typeahead';
|
|
29
|
-
import { Trans } from 'react-i18next';
|
|
18
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
30
19
|
import { usePrevious } from '../hooks/usePrevious';
|
|
31
|
-
|
|
20
|
+
import { OwnerTypeaheadDropdown } from '../OwnerTypeaheadDropdown';
|
|
21
|
+
export const CONTACTS_PAGE_SIZE = 10;
|
|
22
|
+
export const DEFAULT_CONTACTS_OFFSET = CONTACTS_PAGE_SIZE - 1;
|
|
32
23
|
const isCustomEmailAddress = (user) => {
|
|
33
24
|
return !!(user === null || user === void 0 ? void 0 : user.emailAddress);
|
|
34
25
|
};
|
|
35
26
|
const isContact = (user) => {
|
|
36
27
|
return !!(user === null || user === void 0 ? void 0 : user.ssoUsername) && !(user === null || user === void 0 ? void 0 : user.emailAddress);
|
|
37
28
|
};
|
|
38
|
-
const getKey = (option) => {
|
|
39
|
-
if (isCustomEmailAddress(option))
|
|
40
|
-
return option.emailAddress;
|
|
41
|
-
if (isContact(option))
|
|
42
|
-
return option.ssoUsername;
|
|
43
|
-
};
|
|
44
29
|
const getHydraContactLabel = (user) => {
|
|
45
30
|
if (isEmpty(user) || typeof user !== 'object')
|
|
46
31
|
return '';
|
|
@@ -57,7 +42,9 @@ const getHydraContactLabel = (user) => {
|
|
|
57
42
|
return label ? label : '';
|
|
58
43
|
};
|
|
59
44
|
function CaseContactsSelectorExternal(props) {
|
|
45
|
+
const { customEmails, isInvalid, groupContactsAndEmails } = props;
|
|
60
46
|
const previousSelectedAccountNumber = usePrevious(props.selectedAccountNumber);
|
|
47
|
+
const [filteredContacts, setFilteredContacts] = useState([]);
|
|
61
48
|
/**
|
|
62
49
|
* External owner selector related
|
|
63
50
|
*/
|
|
@@ -147,7 +134,7 @@ function CaseContactsSelectorExternal(props) {
|
|
|
147
134
|
];
|
|
148
135
|
props.onChange && props.onChange(customerContacts);
|
|
149
136
|
};
|
|
150
|
-
const onCustomerContactSelectBlur = (
|
|
137
|
+
const onCustomerContactSelectBlur = () => {
|
|
151
138
|
props.onChange && props.onChange(props.selected);
|
|
152
139
|
};
|
|
153
140
|
const isDisabled = (props.loggedInUserRights.isExternal() &&
|
|
@@ -155,43 +142,63 @@ function CaseContactsSelectorExternal(props) {
|
|
|
155
142
|
props.isUpdating ||
|
|
156
143
|
contactList.isFetching ||
|
|
157
144
|
props.isDisabled;
|
|
158
|
-
const
|
|
159
|
-
const contacts = results.map((item, index) => {
|
|
160
|
-
if (item.customOption) {
|
|
161
|
-
return (React.createElement(MenuItem, { className: "rbt-menu-custom-option", key: index, option: item, position: index },
|
|
162
|
-
menuProps.newSelectionPrefix,
|
|
163
|
-
React.createElement(Highlighter, { search: state.text }, item['ssoUsername'])));
|
|
164
|
-
}
|
|
165
|
-
if (item.paginationOption) {
|
|
166
|
-
return (React.createElement(React.Fragment, { key: "pagination-item" },
|
|
167
|
-
React.createElement(Menu.Divider, null),
|
|
168
|
-
React.createElement(MenuItem, { key: index, position: index, option: item, className: "rbt-menu-pagination-option", label: "Display additional results..." }, "Display additional results...")));
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
return (React.createElement(React.Fragment, { key: index },
|
|
172
|
-
index === 0 && isContact(item) && (React.createElement(React.Fragment, null,
|
|
173
|
-
React.createElement(Menu.Header, null,
|
|
174
|
-
React.createElement(Trans, null, "Contacts")))),
|
|
175
|
-
(index === 0
|
|
176
|
-
? isCustomEmailAddress(item)
|
|
177
|
-
: isCustomEmailAddress(item) && isContact(results[index - 1])) && (React.createElement(React.Fragment, null,
|
|
178
|
-
index !== 0 && React.createElement(Menu.Divider, null),
|
|
179
|
-
React.createElement(Menu.Header, null,
|
|
180
|
-
React.createElement(Trans, null, "Notification emails")))),
|
|
181
|
-
React.createElement(MenuItem, { key: index, option: item, position: index }, renderMenuItemChildren(item, state))));
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
return React.createElement(Menu, Object.assign({}, menuProps), contacts);
|
|
185
|
-
};
|
|
186
|
-
const { customEmails } = props, restProps = __rest(props, ["customEmails"]);
|
|
187
|
-
const conditionalProps = Object.assign(Object.assign({}, (props.renderToken ? { renderToken: props.renderToken } : undefined)), ((customEmails || []).length > 0 ? { renderMenu } : undefined));
|
|
188
|
-
const getCustomEmails = () => {
|
|
145
|
+
const getCustomEmails = useCallback(() => {
|
|
189
146
|
return sortBy((customEmails || []).map((item) => (Object.assign(Object.assign({}, item), { ssoUsername: item.emailAddress, firstName: item.emailAddress, lastName: item.emailAddress }))), 'emailAddress');
|
|
147
|
+
}, [customEmails]);
|
|
148
|
+
/**
|
|
149
|
+
* Get all options for the owner typeahead.
|
|
150
|
+
*/
|
|
151
|
+
const allOptions = useMemo(() => {
|
|
152
|
+
return [
|
|
153
|
+
...map(contactList.data, (contact) => ({ contact, isCustomEmail: false })),
|
|
154
|
+
...map(getCustomEmails(), (email) => ({
|
|
155
|
+
isCustomEmail: true,
|
|
156
|
+
contact: Object.assign(Object.assign({}, email), { ssoUsername: email.emailAddress, firstName: email.emailAddress, lastName: email.emailAddress }),
|
|
157
|
+
})),
|
|
158
|
+
];
|
|
159
|
+
}, [contactList.data, getCustomEmails]);
|
|
160
|
+
/**
|
|
161
|
+
* update the shown options in the dropdown and reset the amount of options show to the default.
|
|
162
|
+
* @param query The user search term
|
|
163
|
+
*/
|
|
164
|
+
const onQueryUpdated = (query) => {
|
|
165
|
+
if (isEmpty(query)) {
|
|
166
|
+
setFilteredContacts(allOptions);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const filteredContacts = filter(allOptions, (option) => getHydraContactLabel(option.contact).toLowerCase().includes(query.toLowerCase()));
|
|
170
|
+
setFilteredContacts(filteredContacts);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Reset the filtered contacts and the current offset.
|
|
175
|
+
*/
|
|
176
|
+
const onClearQuery = () => {
|
|
177
|
+
var _a;
|
|
178
|
+
const numOfCustomEmails = (_a = customEmails === null || customEmails === void 0 ? void 0 : customEmails.length) !== null && _a !== void 0 ? _a : 0;
|
|
179
|
+
if (filteredContacts.length === contactList.data.length + numOfCustomEmails)
|
|
180
|
+
return;
|
|
181
|
+
setFilteredContacts(allOptions);
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Removes contacts based on the hydra
|
|
185
|
+
*/
|
|
186
|
+
const onChipRemoved = (option) => {
|
|
187
|
+
props.onChange(filter(props.selected, (c) => c !== option));
|
|
188
|
+
};
|
|
189
|
+
const canAddNew = (options, query) => {
|
|
190
|
+
if (!props.allowCustomEmailAdd)
|
|
191
|
+
return false;
|
|
192
|
+
if ((options || []).length === 0 && isEmailValid(query))
|
|
193
|
+
return true;
|
|
194
|
+
return false;
|
|
190
195
|
};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Set the filtered contacts to the entire contact list and reset the offset to the default page size.
|
|
198
|
+
*/
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
setFilteredContacts(allOptions);
|
|
201
|
+
}, [contactList.data, customEmails, allOptions]);
|
|
202
|
+
return (React.createElement(OwnerTypeaheadDropdown, { id: props.id || '', hasClearButton: true, multiple: !!props.multiple, groupContactsAndEmails: groupContactsAndEmails, selected: !isEmpty(props.selected) ? props.selected : [], options: filteredContacts, placeholder: props.placeholder || 'Search by name or username', "data-tracking-id": "external-case-contact-selector", isDisabled: isDisabled, status: isInvalid ? 'danger' : undefined, onChipRemoved: onChipRemoved, onClearQuery: onClearQuery, onQueryUpdated: onQueryUpdated, onBlur: onCustomerContactSelectBlur, onSelect: onCustomerContactSelect, canAddNew: canAddNew, getCreateNewText: props === null || props === void 0 ? void 0 : props.getCreateNewText }));
|
|
196
203
|
}
|
|
197
|
-
export { CaseContactsSelectorExternal,
|
|
204
|
+
export { CaseContactsSelectorExternal, getHydraContactLabel };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { IContact } from '@cee-eng/hydrajs/@types/models/contact';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { ITypeaheadDropdownProps } from '../TypeaheadDropdown/TypeaheadDropdown';
|
|
4
|
-
export type IOwnerSelectorExtends = Omit<ITypeaheadDropdownProps, 'selected' | 'options' | 'onSelect' | 'onHandleLoadMore' | 'isShowMoreOptionVisible'>;
|
|
4
|
+
export type IOwnerSelectorExtends = Omit<ITypeaheadDropdownProps, 'selected' | 'options' | 'onSelect' | 'onHandleLoadMore' | 'isShowMoreOptionVisible' | 'onChipRemoved' | 'onNew'>;
|
|
5
5
|
export interface IOwnerSelectorProps extends IOwnerSelectorExtends {
|
|
6
6
|
selected: IContact[];
|
|
7
7
|
options: IOwnerOption[];
|
|
8
|
+
groupContactsAndEmails?: boolean;
|
|
8
9
|
onSelect: (contacts: IContact[]) => void;
|
|
10
|
+
onChipRemoved?: (contact: IContact) => void;
|
|
9
11
|
}
|
|
10
12
|
export interface IOwnerOption {
|
|
11
13
|
contact: IContact;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OwnerTypeaheadDropdown.d.ts","sourceRoot":"","sources":["../../../src/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OwnerTypeaheadDropdown.d.ts","sourceRoot":"","sources":["../../../src/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAKlE,OAAO,KAAkB,MAAM,OAAO,CAAC;AAKvC,OAAO,EAGH,uBAAuB,EAE1B,MAAM,wCAAwC,CAAC;AAGhD,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACpC,uBAAuB,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,kBAAkB,GAAG,yBAAyB,GAAG,eAAe,GAAG,OAAO,CACnH,CAAC;AAEF,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB;IAC9D,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,QAAQ,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,qBA6GhE"}
|
|
@@ -11,8 +11,10 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import filter from 'lodash/filter';
|
|
13
13
|
import find from 'lodash/find';
|
|
14
|
+
import isEmpty from 'lodash/isEmpty';
|
|
14
15
|
import map from 'lodash/map';
|
|
15
16
|
import React, { useMemo } from 'react';
|
|
17
|
+
import { useTranslation } from 'react-i18next';
|
|
16
18
|
import { getHydraContactLabel } from '../Functional/CaseContactsSelectorExternal';
|
|
17
19
|
import { useProgressiveLoading } from '../hooks/useProgressiveLoading';
|
|
18
20
|
import { TypeaheadDropdown, } from '../TypeaheadDropdown/TypeaheadDropdown';
|
|
@@ -20,14 +22,43 @@ const PAGE_SIZE = 10;
|
|
|
20
22
|
function getTypeaheadOptionsFromContacts(contacts) {
|
|
21
23
|
return map(contacts, (o) => ({ label: getHydraContactLabel(o), value: o.ssoUsername }));
|
|
22
24
|
}
|
|
23
|
-
function getTypeaheadOptionsFromOption(contacts) {
|
|
24
|
-
return map(contacts, (o) => ({ label: getHydraContactLabel(o.contact), value: o.contact.ssoUsername }));
|
|
25
|
-
}
|
|
26
25
|
export function OwnerTypeaheadDropdown(props) {
|
|
27
|
-
const { options, selected, onSelect, onBlur, onToggleClosed } = props, restProps = __rest(props, ["options", "selected", "onSelect", "onBlur", "onToggleClosed"]);
|
|
26
|
+
const { groupContactsAndEmails, options, selected, onSelect, onBlur, onToggleClosed, onChipRemoved } = props, restProps = __rest(props, ["groupContactsAndEmails", "options", "selected", "onSelect", "onBlur", "onToggleClosed", "onChipRemoved"]);
|
|
28
27
|
const selectedTypeaheadOptions = useMemo(() => getTypeaheadOptionsFromContacts(selected), [selected]);
|
|
28
|
+
const { t } = useTranslation();
|
|
29
29
|
const { visibleItems, hasMoreItems, onHandleLoadMore, resetVisibleItems } = useProgressiveLoading(options, PAGE_SIZE);
|
|
30
|
-
const typeaheadOptions = useMemo(() =>
|
|
30
|
+
const typeaheadOptions = useMemo(() => {
|
|
31
|
+
const contacts = [];
|
|
32
|
+
const customEmails = [];
|
|
33
|
+
for (const option of visibleItems) {
|
|
34
|
+
const typeaheadOption = { label: getHydraContactLabel(option.contact), value: option.contact.ssoUsername };
|
|
35
|
+
if (option.isCustomEmail) {
|
|
36
|
+
customEmails.push(typeaheadOption);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
contacts.push(typeaheadOption);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const allOptions = [];
|
|
43
|
+
const isContactsEmpty = isEmpty(contacts);
|
|
44
|
+
const isCustomEmailsEmpty = isEmpty(customEmails);
|
|
45
|
+
// If there are only contacts do not group the options together.
|
|
46
|
+
if (!groupContactsAndEmails) {
|
|
47
|
+
allOptions.push(...contacts);
|
|
48
|
+
allOptions.push(...customEmails);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
if (!isContactsEmpty) {
|
|
52
|
+
allOptions.push({ groupLabel: t('Contacts'), options: contacts });
|
|
53
|
+
}
|
|
54
|
+
if (!isCustomEmailsEmpty) {
|
|
55
|
+
allOptions.push({ groupLabel: t('Notification emails'), options: customEmails });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return allOptions;
|
|
59
|
+
// This only needs to run when the visible items change.
|
|
60
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
61
|
+
}, [visibleItems]);
|
|
31
62
|
/**
|
|
32
63
|
* Handles selecting or deselecting a contact.
|
|
33
64
|
*
|
|
@@ -47,5 +78,21 @@ export function OwnerTypeaheadDropdown(props) {
|
|
|
47
78
|
resetVisibleItems();
|
|
48
79
|
!!onToggleClosed && onToggleClosed();
|
|
49
80
|
};
|
|
50
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Finds the correct contact that will be removed.
|
|
83
|
+
* @param option The option that is to be removed.
|
|
84
|
+
*/
|
|
85
|
+
const onLocalChipRemoved = (option) => {
|
|
86
|
+
const contact = find(selected, (o) => o.ssoUsername === option.value);
|
|
87
|
+
if (contact && !!onChipRemoved) {
|
|
88
|
+
onChipRemoved(contact);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const onLocalNew = (query) => {
|
|
92
|
+
const newCustomOption = {
|
|
93
|
+
emailAddress: query,
|
|
94
|
+
};
|
|
95
|
+
onSelect([newCustomOption]);
|
|
96
|
+
};
|
|
97
|
+
return (React.createElement(TypeaheadDropdown, Object.assign({ "data-tracking-id": "external-case-contact-selector", selected: selectedTypeaheadOptions, options: typeaheadOptions, onSelect: onSelectContact, onHandleLoadMore: onHandleLoadMore, isShowMoreOptionVisible: hasMoreItems(), onBlur: onLocalBlur, onToggleClosed: onLocalToggleClosed, onChipRemoved: onLocalChipRemoved, onNew: onLocalNew }, restProps)));
|
|
51
98
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ISingleSelectDropdownOption {
|
|
3
|
+
value: any;
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface SingleSelectDropdownProps {
|
|
9
|
+
selected: ISingleSelectDropdownOption;
|
|
10
|
+
options: ISingleSelectDropdownOption[];
|
|
11
|
+
isDisabled?: boolean;
|
|
12
|
+
isLoading?: boolean;
|
|
13
|
+
id?: string;
|
|
14
|
+
ariaLabel?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
isInvalid?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
toggleClassName?: string;
|
|
19
|
+
dataTrackingId?: string;
|
|
20
|
+
toggleDataTrackingId?: string;
|
|
21
|
+
onSelect?: (options: ISingleSelectDropdownOption[]) => void;
|
|
22
|
+
onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
|
|
23
|
+
isScrollable?: boolean;
|
|
24
|
+
zIndex?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function SingleSelectDropdown(props: SingleSelectDropdownProps): React.JSX.Element;
|
|
27
|
+
//# sourceMappingURL=SingleSelectDropdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SingleSelectDropdown.d.ts","sourceRoot":"","sources":["../../../src/SingleSelectDropdown/SingleSelectDropdown.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAE7C,MAAM,WAAW,2BAA2B;IACxC,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,2BAA2B,CAAC;IACtC,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,2BAA2B,EAAE,KAAK,IAAI,CAAC;IAC5D,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IAC3D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,qBAyFpE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Flex, FlexItem, MenuToggle, Select, SelectList, SelectOption, Spinner, } from '@patternfly/react-core';
|
|
2
|
+
import isEqual from 'lodash/isEqual';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
export function SingleSelectDropdown(props) {
|
|
5
|
+
const { selected, options, isDisabled, isLoading, id, placeholder, isInvalid, className, toggleClassName, dataTrackingId, toggleDataTrackingId, onSelect, isScrollable, onBlur, ariaLabel, zIndex, } = props;
|
|
6
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
7
|
+
const onToggleClick = () => {
|
|
8
|
+
setIsOpen(!isOpen);
|
|
9
|
+
};
|
|
10
|
+
const toggle = (toggleRef) => (React.createElement(MenuToggle, { className: toggleClassName || 'single-select-toggle-text', ref: toggleRef, onClick: onToggleClick, isExpanded: isOpen, isDisabled: isDisabled, isFullWidth: true, status: isInvalid ? 'danger' : undefined, "data-tracking-id": toggleDataTrackingId || '' },
|
|
11
|
+
React.createElement(Flex, { justifyContent: { default: 'justifyContentSpaceBetween' } },
|
|
12
|
+
React.createElement(FlexItem, null,
|
|
13
|
+
" ",
|
|
14
|
+
selected.label || placeholder),
|
|
15
|
+
isLoading && (React.createElement(FlexItem, null,
|
|
16
|
+
React.createElement(Spinner, { size: "md" }))))));
|
|
17
|
+
const onLocalSelect = (event, value) => {
|
|
18
|
+
onSelect && onSelect(value);
|
|
19
|
+
setIsOpen(false);
|
|
20
|
+
};
|
|
21
|
+
const onLocalBlur = (event, value) => {
|
|
22
|
+
!!onBlur && onBlur(value);
|
|
23
|
+
};
|
|
24
|
+
return (React.createElement(Select, { id: id || '', className: className || '', "data-tracking-id": dataTrackingId, isOpen: isOpen, selected: selected, onSelect: onLocalSelect, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: toggle, shouldFocusToggleOnSelect: true, popperProps: { direction: 'down', enableFlip: false }, isScrollable: isScrollable, shouldFocusFirstItemOnOpen: false, onBlur: () => onLocalBlur, "aria-label": ariaLabel, zIndex: zIndex },
|
|
25
|
+
React.createElement(SelectList, null, options.map((option) => (React.createElement(SelectOption, { key: option.value, value: option, description: option.description || '', isSelected: isEqual(selected.label, option.label), isDisabled: option.disabled }, option.label))))));
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/SingleSelectDropdown/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SingleSelectDropdown';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export interface ITypeaheadDropdownProps {
|
|
3
3
|
selected: ITypeaheadDropdownOption[];
|
|
4
|
-
options:
|
|
4
|
+
options: ITypeaheadDropdownGroupOrOption[];
|
|
5
5
|
isShowMoreOptionVisible?: boolean;
|
|
6
6
|
id?: string;
|
|
7
7
|
placeholder?: string;
|
|
@@ -10,18 +10,30 @@ export interface ITypeaheadDropdownProps {
|
|
|
10
10
|
multiple?: boolean;
|
|
11
11
|
hasClearButton?: boolean;
|
|
12
12
|
defaultIsOpen?: boolean;
|
|
13
|
+
status?: 'success' | 'warning' | 'danger' | undefined;
|
|
13
14
|
onSelect: (options: ITypeaheadDropdownOption[]) => void;
|
|
14
15
|
onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
|
|
15
16
|
onToggleClosed?: () => void;
|
|
16
17
|
onClearQuery?: () => void;
|
|
18
|
+
onChipRemoved?: (option: ITypeaheadDropdownOption) => void;
|
|
17
19
|
onQueryUpdated?: (query: string) => void;
|
|
18
20
|
onOpenChange?: (isOpen: boolean) => void;
|
|
19
21
|
onHandleLoadMore?: (event: any) => void;
|
|
22
|
+
onNew?: (query: string) => void;
|
|
23
|
+
canAddNew?: (options: ITypeaheadDropdownGroupOrOption[], query: string) => boolean;
|
|
24
|
+
getCreateNewText?: (query: string) => string;
|
|
20
25
|
}
|
|
21
26
|
export interface ITypeaheadDropdownOption {
|
|
22
27
|
value: any;
|
|
23
28
|
label: string;
|
|
29
|
+
group?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ITypeaheadDropdownGroup {
|
|
32
|
+
groupLabel: string;
|
|
33
|
+
options: ITypeaheadDropdownOption[];
|
|
24
34
|
}
|
|
35
|
+
export type ITypeaheadDropdownGroupOrOption = ITypeaheadDropdownOption | ITypeaheadDropdownGroup;
|
|
25
36
|
export declare const VIEW_MORE_OPTION_VALUE = "view-more-option-value";
|
|
37
|
+
export declare const ADD_NEW_OPTION_VALUE = "new-option-value";
|
|
26
38
|
export declare function TypeaheadDropdown(props: ITypeaheadDropdownProps): React.JSX.Element;
|
|
27
39
|
//# sourceMappingURL=TypeaheadDropdown.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TypeaheadDropdown.d.ts","sourceRoot":"","sources":["../../../src/TypeaheadDropdown/TypeaheadDropdown.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TypeaheadDropdown.d.ts","sourceRoot":"","sources":["../../../src/TypeaheadDropdown/TypeaheadDropdown.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAMnD,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,wBAAwB,EAAE,CAAC;IACrC,OAAO,EAAE,+BAA+B,EAAE,CAAC;IAC3C,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IAEtD,QAAQ,EAAE,CAAC,OAAO,EAAE,wBAAwB,EAAE,KAAK,IAAI,CAAC;IACxD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IAC3D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAC3D,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,+BAA+B,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACnF,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,wBAAwB,EAAE,CAAC;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,GAAG,uBAAuB,CAAC;AACjG,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AA2BvD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,qBAqT/D"}
|
|
@@ -1,26 +1,47 @@
|
|
|
1
|
-
import { Button, MenuToggle, Select, SelectList, SelectOption, Spinner, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, } from '@patternfly/react-core';
|
|
1
|
+
import { Button, Label, LabelGroup, MenuToggle, Select, SelectGroup, SelectList, SelectOption, Spinner, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, } from '@patternfly/react-core';
|
|
2
2
|
import TimesCircleIcon from '@patternfly/react-icons/dist/js/icons/times-circle-icon';
|
|
3
|
+
import { haltEvent } from '@rh-support/utils';
|
|
3
4
|
import concat from 'lodash/concat';
|
|
4
5
|
import filter from 'lodash/filter';
|
|
5
6
|
import find from 'lodash/find';
|
|
6
|
-
import includes from 'lodash/includes';
|
|
7
7
|
import isEmpty from 'lodash/isEmpty';
|
|
8
8
|
import isEqual from 'lodash/isEqual';
|
|
9
9
|
import isNumber from 'lodash/isNumber';
|
|
10
10
|
import isString from 'lodash/isString';
|
|
11
|
-
import map from 'lodash/map';
|
|
12
11
|
import React, { useEffect, useState } from 'react';
|
|
13
12
|
import { Highlighter } from 'react-bootstrap-typeahead';
|
|
14
13
|
import { useTranslation } from 'react-i18next';
|
|
15
14
|
import { useSelectKeyboardNavigator } from '../hooks/useSelectKeyboardNavigator';
|
|
16
15
|
export const VIEW_MORE_OPTION_VALUE = 'view-more-option-value';
|
|
16
|
+
export const ADD_NEW_OPTION_VALUE = 'new-option-value';
|
|
17
17
|
const VIEW_MORE_OPTION = { label: 'View more', value: VIEW_MORE_OPTION_VALUE };
|
|
18
|
+
const ADD_NEW_OPTION = { label: 'Create new option', value: ADD_NEW_OPTION_VALUE };
|
|
19
|
+
function isDropdownGroup(option) {
|
|
20
|
+
return option.groupLabel !== undefined;
|
|
21
|
+
}
|
|
22
|
+
function isDropdownOption(option) {
|
|
23
|
+
const typedOption = option;
|
|
24
|
+
return typedOption.value !== undefined && typedOption.label !== undefined;
|
|
25
|
+
}
|
|
26
|
+
function flattenOptions(options) {
|
|
27
|
+
const flattened = [];
|
|
28
|
+
for (const option of options) {
|
|
29
|
+
if (isDropdownGroup(option)) {
|
|
30
|
+
flattened.push(...option.options); // Only add the group's items
|
|
31
|
+
}
|
|
32
|
+
else if (isDropdownOption(option)) {
|
|
33
|
+
flattened.push(option); // Add standalone item
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return flattened;
|
|
37
|
+
}
|
|
18
38
|
export function TypeaheadDropdown(props) {
|
|
19
|
-
|
|
39
|
+
var _a;
|
|
40
|
+
const { options, selected, hasClearButton, id, isDisabled, multiple, placeholder, isShowMoreOptionVisible, defaultIsOpen, inputAriaControls, status, onSelect, onBlur, onToggleClosed, onClearQuery, onChipRemoved, onQueryUpdated, onHandleLoadMore, onOpenChange, onNew, canAddNew, getCreateNewText, } = props;
|
|
20
41
|
const { t } = useTranslation();
|
|
21
42
|
const [isOpen, setIsOpen] = useState(defaultIsOpen !== null && defaultIsOpen !== void 0 ? defaultIsOpen : false);
|
|
22
43
|
const [query, setQuery] = useState(!multiple ? (!isEmpty(selected) ? selected[0].label : '') : '');
|
|
23
|
-
const [allOptions, setAllOptions] = useState([]);
|
|
44
|
+
const [allOptions, setAllOptions] = useState((_a = flattenOptions(options)) !== null && _a !== void 0 ? _a : []);
|
|
24
45
|
const { focusedItemIndex, onInputKeyDown, setFocusedItemIndex } = useSelectKeyboardNavigator({
|
|
25
46
|
ignoreResetOnListChange: true,
|
|
26
47
|
isDisabled: isDisabled,
|
|
@@ -68,7 +89,7 @@ export function TypeaheadDropdown(props) {
|
|
|
68
89
|
!!onOpenChange && onOpenChange(isOpen);
|
|
69
90
|
};
|
|
70
91
|
const isAlreadySelected = (option) => {
|
|
71
|
-
return
|
|
92
|
+
return !!find(selected, (selectedOption) => selectedOption.value === option.value);
|
|
72
93
|
};
|
|
73
94
|
/**
|
|
74
95
|
* Handles selecting or deselecting a contact.
|
|
@@ -85,6 +106,11 @@ export function TypeaheadDropdown(props) {
|
|
|
85
106
|
!!onHandleLoadMore && onHandleLoadMore(event);
|
|
86
107
|
return;
|
|
87
108
|
}
|
|
109
|
+
if (value === ADD_NEW_OPTION_VALUE) {
|
|
110
|
+
!!onNew && onNew(query);
|
|
111
|
+
setQuery('');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
88
114
|
const selectedOption = find(allOptions, (o) => o.value === value);
|
|
89
115
|
if (!selectedOption)
|
|
90
116
|
return;
|
|
@@ -101,25 +127,85 @@ export function TypeaheadDropdown(props) {
|
|
|
101
127
|
? filter(selected, (o) => !isEqual(selectedOption, o)) // Remove
|
|
102
128
|
: concat(selected, selectedOption); // Add
|
|
103
129
|
onSelect(updatedSelection);
|
|
130
|
+
setQuery('');
|
|
104
131
|
}
|
|
105
132
|
setIsOpen(false);
|
|
106
133
|
setFocusedItemIndex(null);
|
|
107
134
|
};
|
|
108
|
-
const toggle = (toggleRef) => (React.createElement(MenuToggle, { isFullWidth: true, variant: "typeahead", onClick: handleToggle, innerRef: toggleRef, isExpanded: isOpen, isDisabled: isDisabled },
|
|
135
|
+
const toggle = (toggleRef) => (React.createElement(MenuToggle, { isFullWidth: true, variant: "typeahead", onClick: handleToggle, innerRef: toggleRef, isExpanded: isOpen, isDisabled: isDisabled, status: status },
|
|
109
136
|
React.createElement(TextInputGroup, { isPlain: true },
|
|
110
|
-
React.createElement(TextInputGroupMain, { value: query, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t(`Search by name or username`), onChange: updateQuery, onClick: handleToggle, onKeyDown: onInputKeyDown, isExpanded: isOpen, "aria-controls": inputAriaControls, role: "combobox" }),
|
|
137
|
+
React.createElement(TextInputGroupMain, { value: query, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t(`Search by name or username`), onChange: updateQuery, onClick: handleToggle, onKeyDown: onInputKeyDown, isExpanded: isOpen, "aria-controls": inputAriaControls, role: "combobox" }, !!multiple && (React.createElement(LabelGroup, { "aria-label": "Current selections" }, selected.map((option, index) => (React.createElement(Label, { key: index, variant: "outline", onClick: haltEvent, onClose: (ev) => {
|
|
138
|
+
haltEvent(ev);
|
|
139
|
+
!!onChipRemoved && onChipRemoved(option);
|
|
140
|
+
} }, option.label)))))),
|
|
111
141
|
React.createElement(TextInputGroupUtilities, null,
|
|
112
142
|
query && hasClearButton && !isDisabled && (React.createElement(Button, { "aria-label": "Clear input value", variant: "plain", onClick: handleClearQuery, isDisabled: isDisabled },
|
|
113
143
|
React.createElement(TimesCircleIcon, null))),
|
|
114
144
|
isDisabled && React.createElement(Spinner, { size: "sm" })))));
|
|
145
|
+
/**
|
|
146
|
+
* Renders the options and group options.
|
|
147
|
+
* @returns {JSX.Element[]} The rendered options.
|
|
148
|
+
*/
|
|
149
|
+
const renderOptions = () => {
|
|
150
|
+
let currentIndex = 0;
|
|
151
|
+
const optionElements = [];
|
|
152
|
+
for (const option of options) {
|
|
153
|
+
if (isDropdownOption(option)) {
|
|
154
|
+
optionElements.push(getOptionElement(option, currentIndex));
|
|
155
|
+
currentIndex++;
|
|
156
|
+
}
|
|
157
|
+
else if (isDropdownGroup(option)) {
|
|
158
|
+
const typedOption = option;
|
|
159
|
+
const groupOptionElements = [];
|
|
160
|
+
for (const groupOption of typedOption.options) {
|
|
161
|
+
groupOptionElements.push(getOptionElement(groupOption, currentIndex));
|
|
162
|
+
currentIndex++;
|
|
163
|
+
}
|
|
164
|
+
optionElements.push(React.createElement(SelectGroup, { label: typedOption.groupLabel }, ...groupOptionElements));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (isShowMoreOptionVisible) {
|
|
168
|
+
optionElements.push(getOptionElement(VIEW_MORE_OPTION, currentIndex));
|
|
169
|
+
currentIndex++;
|
|
170
|
+
}
|
|
171
|
+
if (!!canAddNew && canAddNew(options, query)) {
|
|
172
|
+
optionElements.push(getOptionElement(ADD_NEW_OPTION, currentIndex));
|
|
173
|
+
}
|
|
174
|
+
return optionElements;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Renders individual options.
|
|
178
|
+
* @param option The option to render
|
|
179
|
+
* @param index The option's index within the allOptions array
|
|
180
|
+
* @returns The rendered option
|
|
181
|
+
*/
|
|
182
|
+
const getOptionElement = (option, index) => {
|
|
183
|
+
if (option.value === VIEW_MORE_OPTION_VALUE) {
|
|
184
|
+
// You cannot use the styling applied by the "isLoadButton" prop
|
|
185
|
+
// because it overwrites the pf-m-focus styling. Inorder for it to visually
|
|
186
|
+
// look the same and be able to function with keyboard accessibility,
|
|
187
|
+
// the selection option text needs to be wrapped in a span with the pf link color class.
|
|
188
|
+
return (React.createElement(SelectOption, { key: option.value, value: option.value, isFocused: focusedItemIndex === index, isSelected: false, onClick: onHandleLoadMore },
|
|
189
|
+
React.createElement("span", { className: "pf-v5-u-link-color" }, "View more")));
|
|
190
|
+
}
|
|
191
|
+
else if (option.value === ADD_NEW_OPTION_VALUE) {
|
|
192
|
+
return (React.createElement(SelectOption, { key: option.value, value: option.value, isFocused: false, isSelected: false }, !!getCreateNewText ? getCreateNewText(query) : `${option.label}: ${query}`));
|
|
193
|
+
}
|
|
194
|
+
return (React.createElement(SelectOption, { key: option.value, value: option.value, isFocused: focusedItemIndex === index, isSelected: isAlreadySelected(option) },
|
|
195
|
+
React.createElement(Highlighter, { key: option.value, search: query }, option.label)));
|
|
196
|
+
};
|
|
115
197
|
useEffect(() => {
|
|
198
|
+
const localAllOptions = [...flattenOptions(options)];
|
|
116
199
|
if (isShowMoreOptionVisible) {
|
|
117
|
-
|
|
200
|
+
localAllOptions.push(VIEW_MORE_OPTION);
|
|
118
201
|
}
|
|
119
|
-
|
|
120
|
-
|
|
202
|
+
if (!!canAddNew && canAddNew(options, query)) {
|
|
203
|
+
localAllOptions.push(ADD_NEW_OPTION);
|
|
121
204
|
}
|
|
122
|
-
|
|
205
|
+
setAllOptions(localAllOptions);
|
|
206
|
+
// There is no need to recall this when canAddNew has been changed.
|
|
207
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
208
|
+
}, [options, isShowMoreOptionVisible, query]);
|
|
123
209
|
useEffect(() => {
|
|
124
210
|
if (!isOpen) {
|
|
125
211
|
setFocusedItemIndex(null);
|
|
@@ -128,16 +214,5 @@ export function TypeaheadDropdown(props) {
|
|
|
128
214
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
129
215
|
}, [isOpen]);
|
|
130
216
|
return (React.createElement(Select, { id: id || '', "data-tracking-id": "external-case-contact-selector", role: "menu", shouldFocusFirstItemOnOpen: false, shouldFocusToggleOnSelect: false, isOpen: isOpen, onOpenChange: localOnOpenChange, toggle: toggle, popperProps: { direction: 'down', enableFlip: false }, isScrollable: true, onBlur: onBlur, onSelect: onLocalSelect },
|
|
131
|
-
React.createElement(SelectList, { isAriaMultiselectable: multiple },
|
|
132
|
-
if (option.value === VIEW_MORE_OPTION_VALUE) {
|
|
133
|
-
// You cannot use the styling applied by the "isLoadButton" prop
|
|
134
|
-
// because it overwrites the pf-m-focus styling. Inorder for it to visually
|
|
135
|
-
// look the same and be able to function with keyboard accessibility,
|
|
136
|
-
// the selection option text needs to be wrapped in a span with the pf link color class.
|
|
137
|
-
return (React.createElement(SelectOption, { key: option.value, value: option.value, isFocused: focusedItemIndex === index, isSelected: false, onClick: onHandleLoadMore },
|
|
138
|
-
React.createElement("span", { className: "pf-v5-u-link-color" }, "View more")));
|
|
139
|
-
}
|
|
140
|
-
return (React.createElement(SelectOption, { key: option.value, value: option.value, isFocused: focusedItemIndex === index, isSelected: isAlreadySelected(option) },
|
|
141
|
-
React.createElement(Highlighter, { key: option.value, search: query }, option.label)));
|
|
142
|
-
}))));
|
|
217
|
+
React.createElement(SelectList, { isAriaMultiselectable: multiple }, ...renderOptions())));
|
|
143
218
|
}
|
package/lib/esm/hooks/index.d.ts
CHANGED
|
@@ -15,5 +15,6 @@ export * from './useSelectKeyboardNavigator';
|
|
|
15
15
|
export * from './useLocalStorage';
|
|
16
16
|
export * from './useSessionStorage';
|
|
17
17
|
export * from './useSearchDocument';
|
|
18
|
+
export * from './usePatternFlySingleSelectToggle';
|
|
18
19
|
export * from './useProgressiveLoading';
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|