@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.
Files changed (60) hide show
  1. package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts +12 -7
  2. package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts.map +1 -1
  3. package/lib/esm/Functional/CaseContactsSelectorExternal.js +67 -60
  4. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts +3 -1
  5. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts.map +1 -1
  6. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.js +53 -6
  7. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts +27 -0
  8. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts.map +1 -0
  9. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.js +26 -0
  10. package/lib/esm/SingleSelectDropdown/index.d.ts +2 -0
  11. package/lib/esm/SingleSelectDropdown/index.d.ts.map +1 -0
  12. package/lib/esm/SingleSelectDropdown/index.js +1 -0
  13. package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.d.ts +13 -1
  14. package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.d.ts.map +1 -1
  15. package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.js +99 -24
  16. package/lib/esm/hooks/index.d.ts +1 -0
  17. package/lib/esm/hooks/index.d.ts.map +1 -1
  18. package/lib/esm/hooks/index.js +1 -0
  19. package/lib/esm/hooks/usePatternFlySingleSelectToggle.d.ts +16 -0
  20. package/lib/esm/hooks/usePatternFlySingleSelectToggle.d.ts.map +1 -0
  21. package/lib/esm/hooks/usePatternFlySingleSelectToggle.js +17 -0
  22. package/lib/esm/index.d.ts +1 -1
  23. package/lib/esm/index.d.ts.map +1 -1
  24. package/lib/esm/index.js +1 -1
  25. package/package.json +2 -4
  26. package/lib/esm/DropDownList/AsyncDropDownList.d.ts +0 -6
  27. package/lib/esm/DropDownList/AsyncDropDownList.d.ts.map +0 -1
  28. package/lib/esm/DropDownList/AsyncDropDownList.js +0 -6
  29. package/lib/esm/DropDownList/DropDownList.d.ts +0 -9
  30. package/lib/esm/DropDownList/DropDownList.d.ts.map +0 -1
  31. package/lib/esm/DropDownList/DropDownList.js +0 -116
  32. package/lib/esm/DropDownList/DropdownWrapper.d.ts +0 -16
  33. package/lib/esm/DropDownList/DropdownWrapper.d.ts.map +0 -1
  34. package/lib/esm/DropDownList/DropdownWrapper.js +0 -19
  35. package/lib/esm/DropDownList/MultiSelectDropdownList.d.ts +0 -4
  36. package/lib/esm/DropDownList/MultiSelectDropdownList.d.ts.map +0 -1
  37. package/lib/esm/DropDownList/MultiSelectDropdownList.js +0 -4
  38. package/lib/esm/DropDownList/Readme.md +0 -114
  39. package/lib/esm/DropDownList/SearchableList.d.ts +0 -14
  40. package/lib/esm/DropDownList/SearchableList.d.ts.map +0 -1
  41. package/lib/esm/DropDownList/SearchableList.js +0 -98
  42. package/lib/esm/DropDownList/SelectList.d.ts +0 -11
  43. package/lib/esm/DropDownList/SelectList.d.ts.map +0 -1
  44. package/lib/esm/DropDownList/SelectList.js +0 -35
  45. package/lib/esm/DropDownList/async.d.ts +0 -9
  46. package/lib/esm/DropDownList/async.d.ts.map +0 -1
  47. package/lib/esm/DropDownList/async.js +0 -111
  48. package/lib/esm/DropDownList/dropdownList.css +0 -59
  49. package/lib/esm/DropDownList/dropdownUtils.d.ts +0 -5
  50. package/lib/esm/DropDownList/dropdownUtils.d.ts.map +0 -1
  51. package/lib/esm/DropDownList/dropdownUtils.js +0 -23
  52. package/lib/esm/DropDownList/index.d.ts +0 -4
  53. package/lib/esm/DropDownList/index.d.ts.map +0 -1
  54. package/lib/esm/DropDownList/index.js +0 -3
  55. package/lib/esm/DropDownList/types.d.ts +0 -53
  56. package/lib/esm/DropDownList/types.d.ts.map +0 -1
  57. package/lib/esm/DropDownList/types.js +0 -1
  58. package/lib/esm/DropDownList/withMulti.d.ts +0 -5
  59. package/lib/esm/DropDownList/withMulti.d.ts.map +0 -1
  60. 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 { TypeaheadModel, TypeaheadProps } from 'react-bootstrap-typeahead';
7
+ import { ITypeaheadDropdownGroupOrOption } from '../TypeaheadDropdown/TypeaheadDropdown';
8
8
  export type INotificationContact = IContact | ICaseNotificationAddresses;
9
- interface IProps<T extends TypeaheadModel> extends IDClassNameProps, Omit<TypeaheadProps<T>, 'id' | 'selected' | 'onChange' | 'className' | 'multiple' | 'options' | 'newSelectionPrefix'> {
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 renderMenuItemChildren: (option: INotificationContact, p: any) => React.JSX.Element;
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<T extends TypeaheadModel>(props: IProps<T>): React.JSX.Element;
30
- export { CaseContactsSelectorExternal, renderMenuItemChildren, getHydraContactLabel };
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;AAMxD,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAA0C,cAAc,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAKnH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AACzE,UAAU,MAAM,CAAC,CAAC,SAAS,cAAc,CACrC,SAAQ,gBAAgB,EACpB,IAAI,CACA,cAAc,CAAC,CAAC,CAAC,EACjB,IAAI,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,oBAAoB,CAC/F;IACL,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,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,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,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACrC,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,QAAA,MAAM,sBAAsB,WAAY,oBAAoB,8BAI3D,CAAC;AAcF,QAAA,MAAM,oBAAoB,SAAU,oBAAoB,KAAG,MAa1D,CAAC;AAEF,iBAAS,4BAA4B,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,qBAqN/E;AAED,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,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
- const renderMenuItemChildren = (option, p) => (React.createElement(Highlighter, { key: getKey(option), search: p.text }, getHydraContactLabel(option)));
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 = (event) => {
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 renderMenu = (results, menuProps, state) => {
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
- return (
192
- //@ts-ignore
193
- React.createElement(Typeahead, Object.assign({}, restProps, { id: props.id || '', className: props.className || '', filterBy: ['firstName', 'lastName', 'ssoUsername'], clearButton: !!props.clearButton, multiple: !!props.multiple, isLoading: contactList.isFetching || props.isUpdating, options: [...contactList.data, ...getCustomEmails()], selected: !isEmpty(props.selected) ? props.selected : [], onChange: onCustomerContactSelect,
194
- //@ts-ignore
195
- labelKey: props.multiple ? 'ssoUsername' : getHydraContactLabel, placeholder: props.placeholder || 'Search by name or username', renderMenuItemChildren: renderMenuItemChildren, onBlur: onCustomerContactSelectBlur, minLength: props.minLength || 0, disabled: isDisabled }, conditionalProps, { "data-tracking-id": "external-case-contact-selector" })));
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, renderMenuItemChildren, getHydraContactLabel };
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAIlE,OAAO,KAAkB,MAAM,OAAO,CAAC;AAIvC,OAAO,EAEH,uBAAuB,EAE1B,MAAM,wCAAwC,CAAC;AAGhD,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACpC,uBAAuB,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,kBAAkB,GAAG,yBAAyB,CACvF,CAAC;AAEF,MAAM,WAAW,mBAAoB,SAAQ,qBAAqB;IAC9D,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,QAAQ,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAaD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,qBAqDhE"}
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(() => getTypeaheadOptionsFromOption(visibleItems), [visibleItems]);
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
- 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 }, restProps)));
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,2 @@
1
+ export * from './SingleSelectDropdown';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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: ITypeaheadDropdownOption[];
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":"AAsBA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAMnD,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,wBAAwB,EAAE,CAAC;IACrC,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,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;IAExB,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,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;CAC3C;AAED,MAAM,WAAW,wBAAwB;IACrC,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAG/D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,qBAgO/D"}
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
- const { options, selected, hasClearButton, id, isDisabled, multiple, placeholder, isShowMoreOptionVisible, defaultIsOpen, inputAriaControls, onSelect, onBlur, onToggleClosed, onClearQuery, onQueryUpdated, onHandleLoadMore, onOpenChange, } = props;
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 includes(selected, option);
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
- setAllOptions([...options, VIEW_MORE_OPTION]);
200
+ localAllOptions.push(VIEW_MORE_OPTION);
118
201
  }
119
- else {
120
- setAllOptions(options);
202
+ if (!!canAddNew && canAddNew(options, query)) {
203
+ localAllOptions.push(ADD_NEW_OPTION);
121
204
  }
122
- }, [options, isShowMoreOptionVisible]);
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 }, map(allOptions, (option, index) => {
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
  }
@@ -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