@rh-support/components 2.5.0 → 2.5.2

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 (31) hide show
  1. package/lib/esm/MarkdownEditor/MarkdownEditor.d.ts +4 -0
  2. package/lib/esm/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  3. package/lib/esm/MarkdownEditor/MarkdownEditor.js +104 -6
  4. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts +15 -0
  5. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts.map +1 -0
  6. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.js +51 -0
  7. package/lib/esm/OwnerTypeaheadDropdown/index.d.ts +2 -0
  8. package/lib/esm/OwnerTypeaheadDropdown/index.d.ts.map +1 -0
  9. package/lib/esm/OwnerTypeaheadDropdown/index.js +1 -0
  10. package/lib/esm/TagsSelector/TagsSelector.d.ts.map +1 -1
  11. package/lib/esm/TagsSelector/TagsSelector.js +3 -2
  12. package/lib/esm/TagsSelector/tagSelector.css +4 -0
  13. package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.d.ts +27 -0
  14. package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.d.ts.map +1 -0
  15. package/lib/esm/TypeaheadDropdown/TypeaheadDropdown.js +143 -0
  16. package/lib/esm/TypeaheadDropdown/index.d.ts +2 -0
  17. package/lib/esm/TypeaheadDropdown/index.d.ts.map +1 -0
  18. package/lib/esm/TypeaheadDropdown/index.js +1 -0
  19. package/lib/esm/hooks/index.d.ts +1 -0
  20. package/lib/esm/hooks/index.d.ts.map +1 -1
  21. package/lib/esm/hooks/index.js +1 -0
  22. package/lib/esm/hooks/useProgressiveLoading.d.ts +10 -0
  23. package/lib/esm/hooks/useProgressiveLoading.d.ts.map +1 -0
  24. package/lib/esm/hooks/useProgressiveLoading.js +33 -0
  25. package/lib/esm/hooks/useSelectKeyboardNavigator.d.ts +9 -1
  26. package/lib/esm/hooks/useSelectKeyboardNavigator.d.ts.map +1 -1
  27. package/lib/esm/hooks/useSelectKeyboardNavigator.js +82 -3
  28. package/lib/esm/index.d.ts +1 -0
  29. package/lib/esm/index.d.ts.map +1 -1
  30. package/lib/esm/index.js +1 -0
  31. package/package.json +6 -6
@@ -37,6 +37,10 @@ export interface IMDEditorProps extends Omit<HTMLProps<HTMLTextAreaElement>, 'on
37
37
  onCommentExceedCharsLimit: (val: boolean) => void;
38
38
  fileSelectorProps?: Partial<IFileSelectorProps>;
39
39
  }
40
+ export declare const validateFile: (file: Partial<IAttachment>) => {
41
+ isValid: boolean;
42
+ errorMsg: string;
43
+ };
40
44
  declare function MarkdownEditor(props: IMDEditorProps): React.JSX.Element;
41
45
  declare namespace MarkdownEditor {
42
46
  var defaultProps: Partial<IMDEditorProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownEditor.d.ts","sourceRoot":"","sources":["../../../src/MarkdownEditor/MarkdownEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAwB,MAAM,qBAAqB,CAAC;AAgC7E,OAAO,KAAK,EAAE,EAAE,SAAS,EAA+B,MAAM,OAAO,CAAC;AAKtE,oBAAY,UAAU;IAClB,KAAK,cAAc;IACnB,QAAQ,aAAa;CACxB;AAED,UAAU,kBAAkB;IACxB,yBAAyB,EAAE,OAAO,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;IAClC,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IACnD,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IACnD,gBAAgB,EAAE,CAAC,IAAI,KAAA,KAAK,IAAI,CAAC;IACjC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,eAAe,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,KAAK,CAAC;IACtG,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,iBAAiB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnD;AAuBD,iBAAS,cAAc,CAAC,KAAK,EAAE,cAAc,qBA62B5C;kBA72BQ,cAAc;;;AAi3BvB,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"MarkdownEditor.d.ts","sourceRoot":"","sources":["../../../src/MarkdownEditor/MarkdownEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAwB,MAAM,qBAAqB,CAAC;AAgC7E,OAAO,KAAK,EAAE,EAAE,SAAS,EAA+B,MAAM,OAAO,CAAC;AAMtE,oBAAY,UAAU;IAClB,KAAK,cAAc;IACnB,QAAQ,aAAa;CACxB;AAED,UAAU,kBAAkB;IACxB,yBAAyB,EAAE,OAAO,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;IAClC,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IACnD,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IACnD,gBAAgB,EAAE,CAAC,IAAI,KAAA,KAAK,IAAI,CAAC;IACjC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC;IAC/B,eAAe,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,KAAK,CAAC;IACtG,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,iBAAiB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnD;AA0BD,eAAO,MAAM,YAAY,SAAU,OAAO,CAAC,WAAW,CAAC,KAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAc7F,CAAC;AAEF,iBAAS,cAAc,CAAC,KAAK,EAAE,cAAc,qBAi8B5C;kBAj8BQ,cAAc;;;AAq8BvB,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -27,6 +27,7 @@ import isEmpty from 'lodash/isEmpty';
27
27
  import React, { useEffect, useRef, useState } from 'react';
28
28
  import { Trans, useTranslation } from 'react-i18next';
29
29
  import { useDebounce, usePrevious, useUndo } from '../hooks';
30
+ import { ToastNotification } from '../index';
30
31
  export var EditorMode;
31
32
  (function (EditorMode) {
32
33
  EditorMode["PLAIN"] = "plaintext";
@@ -52,6 +53,21 @@ const defaultProps = {
52
53
  showFileSelectorInToolbar: false,
53
54
  },
54
55
  };
56
+ const MAX_FILE_SIZE = 2 * 1024; // 2MB in KB
57
+ const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
58
+ export const validateFile = (file) => {
59
+ // Check file type
60
+ if (!ALLOWED_FILE_TYPES.includes(file.fileType)) {
61
+ // ToastNotification.addDangerMessage(t('Invalid file type, Only JPEG, PNG, GIF files are allowed.'));
62
+ return { isValid: false, errorMsg: 'Invalid file type, Only JPEG, PNG, GIF files are allowed.' };
63
+ }
64
+ // Check file size
65
+ if (file.sizeKB > MAX_FILE_SIZE) {
66
+ // ToastNotification.addDangerMessage(t('File too large, File size must be less than 2MB'));
67
+ return { isValid: false, errorMsg: 'File too large, File size must be less than 2MB' };
68
+ }
69
+ return { isValid: true, errorMsg: '' };
70
+ };
55
71
  function MarkdownEditor(props) {
56
72
  const { t } = useTranslation();
57
73
  const { hidePreviewText, showPreviewText, mdPlaceholder, plainTextPlaceholder, textAreaClassName, bindTextArea, markedownOptions, allowEmoji, hideHeadingOptions, rows, showMarkdownPlainTextToggle, editorMode, className, charLimit, minCharsForCounter, id, value, onCommentExceedCharsLimit, fileSelectorProps: { onClipboardPaste, onFileDelete, onFileSelect, isSupportedFile, showFileSelectorInToolbar = false, isUploadingFile = false, filesList = [], attachFileBtn, } } = props, textAreaProps = __rest(props, ["hidePreviewText", "showPreviewText", "mdPlaceholder", "plainTextPlaceholder", "textAreaClassName", "bindTextArea", "markedownOptions", "allowEmoji", "hideHeadingOptions", "rows", "showMarkdownPlainTextToggle", "editorMode", "className", "charLimit", "minCharsForCounter", "id", "value", "onCommentExceedCharsLimit", "fileSelectorProps"]);
@@ -77,6 +93,12 @@ function MarkdownEditor(props) {
77
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
78
94
  }, [filesList]);
79
95
  const onFileInsert = (file) => {
96
+ const { isValid, errorMsg } = validateFile(file);
97
+ if (!isValid) {
98
+ ToastNotification.addDangerMessage(t(errorMsg));
99
+ return;
100
+ }
101
+ // Create markdown image syntax
80
102
  const template = isImageFile(file.fileType)
81
103
  ? `![${file.fileName}](${file.link})`
82
104
  : `[${file.fileName}](${file.link})`;
@@ -220,17 +242,93 @@ function MarkdownEditor(props) {
220
242
  const whitespaceEdges = selectedText.match(/^\s*|\s*$/g) || [];
221
243
  const leadingWhitespace = whitespaceEdges[0] || '';
222
244
  const trailingWhitespace = whitespaceEdges[1] || '';
245
+ const trimmedText = selectedText.trim();
246
+ let coreText = trimmedText;
247
+ // Check existing formatting
248
+ const existingFormats = {
249
+ h1: coreText.startsWith('# '),
250
+ h2: coreText.startsWith('## '),
251
+ h3: coreText.startsWith('### '),
252
+ bold: coreText.startsWith('**') && coreText.endsWith('**'),
253
+ italic: coreText.startsWith('_') && coreText.endsWith('_'),
254
+ quote: coreText.startsWith('> '),
255
+ link: coreText.match(/^\[.*\]\(.*\)$/),
256
+ };
257
+ // Remove existing formatting to get core text
258
+ if (existingFormats.h1)
259
+ coreText = coreText.substring(2);
260
+ else if (existingFormats.h2)
261
+ coreText = coreText.substring(3);
262
+ else if (existingFormats.h3)
263
+ coreText = coreText.substring(4);
264
+ if (existingFormats.bold)
265
+ coreText = coreText.substring(2, coreText.length - 2);
266
+ if (existingFormats.italic)
267
+ coreText = coreText.substring(1, coreText.length - 1);
268
+ if (existingFormats.quote)
269
+ coreText = coreText.substring(2);
270
+ if (existingFormats.link) {
271
+ const match = coreText.match(/^\[(.*)\]\((.*)\)$/);
272
+ if (match)
273
+ coreText = match[1];
274
+ }
275
+ // Determine what formatting we're adding
276
+ const newFormat = {
277
+ h1: templateStart === '# ' && templateEnd === '',
278
+ h2: templateStart === '## ' && templateEnd === '',
279
+ h3: templateStart === '### ' && templateEnd === '',
280
+ bold: templateStart === '**' && templateEnd === '**',
281
+ italic: templateStart === '_' && templateEnd === '_',
282
+ quote: templateStart === '> ' && (templateEnd === '' || templateEnd === '\n'),
283
+ link: templateEnd.includes(']('),
284
+ };
285
+ // Apply formatting
286
+ let formattedText = coreText;
287
+ // First apply inline formatting (bold, italic)
288
+ if (newFormat.italic && !existingFormats.italic) {
289
+ formattedText = `_${formattedText}_`;
290
+ }
291
+ if (existingFormats.italic && !newFormat.italic) {
292
+ formattedText = `_${formattedText}_`;
293
+ }
294
+ if (newFormat.bold && !existingFormats.bold) {
295
+ formattedText = `**${formattedText}**`;
296
+ }
297
+ if (existingFormats.bold && !newFormat.bold) {
298
+ formattedText = `**${formattedText}**`;
299
+ }
300
+ // Then apply block-level formatting (headings, quotes)
301
+ if (newFormat.h1 || existingFormats.h1) {
302
+ formattedText = `# ${formattedText}`;
303
+ }
304
+ else if (newFormat.h2 || existingFormats.h2) {
305
+ formattedText = `## ${formattedText}`;
306
+ }
307
+ else if (newFormat.h3 || existingFormats.h3) {
308
+ formattedText = `### ${formattedText}`;
309
+ }
310
+ // Handle quotes - keep them at the start of the line
311
+ if (newFormat.quote || existingFormats.quote) {
312
+ formattedText = `> ${formattedText}`;
313
+ }
314
+ // Special handling for links
315
+ if (newFormat.link) {
316
+ formattedText = `[${formattedText}](URL "TITLE")`;
317
+ }
318
+ if (existingFormats.link && !newFormat.link) {
319
+ const match = trimmedText.match(/^\[(.*)\]\((.*)\)$/);
320
+ if (match)
321
+ formattedText = `[${formattedText}](${match[2]})`;
322
+ }
223
323
  const newText = currentText.substring(0, posStart) +
224
324
  leadingWhitespace +
225
- templateStart +
226
- selectedText.trim() +
227
- templateEnd +
325
+ formattedText +
228
326
  trailingWhitespace +
229
327
  currentText.substring(posEnd);
230
328
  // Set the value in the textarea
231
329
  textareaRef.current.value = newText;
232
- textareaRef.current.selectionStart = posEnd + templateStart.length + templateEnd.length;
233
- textareaRef.current.selectionEnd = textareaRef.current.selectionStart;
330
+ textareaRef.current.selectionStart = posStart + leadingWhitespace.length;
331
+ textareaRef.current.selectionEnd = posStart + leadingWhitespace.length + formattedText.length;
234
332
  textareaRef.current.focus();
235
333
  // Call onValueChangeLocal only once
236
334
  onValueChangeLocal(newText);
@@ -315,7 +413,7 @@ function MarkdownEditor(props) {
315
413
  };
316
414
  const quote = () => {
317
415
  if (hasSelection()) {
318
- wrapSelection('> ', '\n');
416
+ wrapSelection('> ', '');
319
417
  }
320
418
  else {
321
419
  insert('> QUOTE', 2, 7);
@@ -0,0 +1,15 @@
1
+ import { IContact } from '@cee-eng/hydrajs/@types/models/contact';
2
+ import React from 'react';
3
+ import { ITypeaheadDropdownProps } from '../TypeaheadDropdown/TypeaheadDropdown';
4
+ export type IOwnerSelectorExtends = Omit<ITypeaheadDropdownProps, 'selected' | 'options' | 'onSelect' | 'onHandleLoadMore' | 'isShowMoreOptionVisible'>;
5
+ export interface IOwnerSelectorProps extends IOwnerSelectorExtends {
6
+ selected: IContact[];
7
+ options: IOwnerOption[];
8
+ onSelect: (contacts: IContact[]) => void;
9
+ }
10
+ export interface IOwnerOption {
11
+ contact: IContact;
12
+ isCustomEmail?: boolean;
13
+ }
14
+ export declare function OwnerTypeaheadDropdown(props: IOwnerSelectorProps): React.JSX.Element;
15
+ //# sourceMappingURL=OwnerTypeaheadDropdown.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,51 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import filter from 'lodash/filter';
13
+ import find from 'lodash/find';
14
+ import map from 'lodash/map';
15
+ import React, { useMemo } from 'react';
16
+ import { getHydraContactLabel } from '../Functional/CaseContactsSelectorExternal';
17
+ import { useProgressiveLoading } from '../hooks/useProgressiveLoading';
18
+ import { TypeaheadDropdown, } from '../TypeaheadDropdown/TypeaheadDropdown';
19
+ const PAGE_SIZE = 10;
20
+ function getTypeaheadOptionsFromContacts(contacts) {
21
+ return map(contacts, (o) => ({ label: getHydraContactLabel(o), value: o.ssoUsername }));
22
+ }
23
+ function getTypeaheadOptionsFromOption(contacts) {
24
+ return map(contacts, (o) => ({ label: getHydraContactLabel(o.contact), value: o.contact.ssoUsername }));
25
+ }
26
+ export function OwnerTypeaheadDropdown(props) {
27
+ const { options, selected, onSelect, onBlur, onToggleClosed } = props, restProps = __rest(props, ["options", "selected", "onSelect", "onBlur", "onToggleClosed"]);
28
+ const selectedTypeaheadOptions = useMemo(() => getTypeaheadOptionsFromContacts(selected), [selected]);
29
+ const { visibleItems, hasMoreItems, onHandleLoadMore, resetVisibleItems } = useProgressiveLoading(options, PAGE_SIZE);
30
+ const typeaheadOptions = useMemo(() => getTypeaheadOptionsFromOption(visibleItems), [visibleItems]);
31
+ /**
32
+ * Handles selecting or deselecting a contact.
33
+ *
34
+ * @param {React.MouseEvent<Element, MouseEvent>} _
35
+ * @param contactLabel The value of the selected option.
36
+ * @returns
37
+ */
38
+ const onSelectContact = (selectedOptions) => {
39
+ const selectedContacts = map(filter(options, (option) => !!find(selectedOptions, (selectedOption) => selectedOption.value === option.contact.ssoUsername)), (o) => o.contact);
40
+ onSelect(selectedContacts);
41
+ };
42
+ const onLocalBlur = (event) => {
43
+ resetVisibleItems();
44
+ !!onBlur && onBlur(event);
45
+ };
46
+ const onLocalToggleClosed = () => {
47
+ resetVisibleItems();
48
+ !!onToggleClosed && onToggleClosed();
49
+ };
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)));
51
+ }
@@ -0,0 +1,2 @@
1
+ export * from './OwnerTypeaheadDropdown';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/OwnerTypeaheadDropdown/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './OwnerTypeaheadDropdown';
@@ -1 +1 @@
1
- {"version":3,"file":"TagsSelector.d.ts","sourceRoot":"","sources":["../../../src/TagsSelector/TagsSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAmB3B,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,UAAU,MAAM;IACZ,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,EACzB,UAAU,EACV,QAAQ,EACR,WAAW,EACX,EAAE,EACF,SAAS,EACT,YAAiB,EACjB,QAAgB,GACnB,EAAE,MAAM,qBAuJR"}
1
+ {"version":3,"file":"TagsSelector.d.ts","sourceRoot":"","sources":["../../../src/TagsSelector/TagsSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAmB3B,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,UAAU,MAAM;IACZ,UAAU,EAAE,GAAG,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,EACzB,UAAU,EACV,QAAQ,EACR,WAAW,EACX,EAAE,EACF,SAAS,EACT,YAAiB,EACjB,QAAgB,GACnB,EAAE,MAAM,qBA2JR"}
@@ -22,7 +22,8 @@ export function TagsSelector({ tagOptions, onChange, placeholder, id, typeahead,
22
22
  // eslint-disable-next-line react-hooks/exhaustive-deps
23
23
  }, [query, tagOptions]);
24
24
  const { t } = useTranslation();
25
- const onToggleClick = () => {
25
+ const onToggleClick = (e) => {
26
+ e.stopPropagation();
26
27
  setIsOpen(!isOpen);
27
28
  };
28
29
  const onSelect = (selection) => {
@@ -58,7 +59,7 @@ export function TagsSelector({ tagOptions, onChange, placeholder, id, typeahead,
58
59
  e.stopPropagation();
59
60
  onSelect(label);
60
61
  } }, label.tagName)))));
61
- const toggle = (toggleRef) => (React.createElement(MenuToggle, Object.assign({ ref: toggleRef, onClick: onToggleClick, className: "pf-v5-u-flex-grow-1 tags-selector-menu-toggle", isExpanded: isOpen, onKeyDown: onInputKeyDown, disabled: isEmpty(tagOptions) || disabled }, (typeahead && { variant: 'typeahead' }), { id: id }), typeahead ? (React.createElement(TextInputGroup, { isPlain: true },
62
+ const toggle = (toggleRef) => (React.createElement(MenuToggle, Object.assign({ ref: toggleRef, onClick: onToggleClick, className: `pf-v5-u-flex-grow-1 tags-selector-menu-toggle ${isEmpty(tagOptions) || disabled ? 'menu-is-disabld' : ''}`, isExpanded: isOpen, onKeyDown: onInputKeyDown, isDisabled: isEmpty(tagOptions) || disabled }, (typeahead && { variant: 'typeahead' }), { id: id, isFullWidth: true }), typeahead ? (React.createElement(TextInputGroup, { isPlain: true },
62
63
  React.createElement(TextInputGroupMain, { id: "tags-selector-search-input", value: query, onClick: onToggleClick, onChange: (e, v) => setQuery(v), onKeyDown: onInputKeyDown, innerRef: textInputRef, isExpanded: isOpen, placeholder: placeholder || t('Search for a tag') }, selectedChipGroup),
63
64
  React.createElement(TextInputGroupUtilities, null, selected.length > 0 && (React.createElement(Button, { variant: "plain", onClick: handleClear, "aria-label": "clear-all-tags" },
64
65
  React.createElement(TimesIcon, { "aria-hidden": true })))))) : (React.createElement(Flex, { justifyContent: { default: 'justifyContentSpaceBetween' }, alignItems: { default: 'alignItemsCenter' } },
@@ -9,3 +9,7 @@
9
9
  .tags-selector-menu-toggle .pf-v5-c-menu-toggle__controls {
10
10
  padding-left: 0;
11
11
  }
12
+
13
+ .menu-is-disabld {
14
+ background-color: #d2d2d2 !important;
15
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ export interface ITypeaheadDropdownProps {
3
+ selected: ITypeaheadDropdownOption[];
4
+ options: ITypeaheadDropdownOption[];
5
+ isShowMoreOptionVisible?: boolean;
6
+ id?: string;
7
+ placeholder?: string;
8
+ inputAriaControls?: string;
9
+ isDisabled?: boolean;
10
+ multiple?: boolean;
11
+ hasClearButton?: boolean;
12
+ defaultIsOpen?: boolean;
13
+ onSelect: (options: ITypeaheadDropdownOption[]) => void;
14
+ onBlur?: (event: React.FocusEvent<HTMLDivElement>) => void;
15
+ onToggleClosed?: () => void;
16
+ onClearQuery?: () => void;
17
+ onQueryUpdated?: (query: string) => void;
18
+ onOpenChange?: (isOpen: boolean) => void;
19
+ onHandleLoadMore?: (event: any) => void;
20
+ }
21
+ export interface ITypeaheadDropdownOption {
22
+ value: any;
23
+ label: string;
24
+ }
25
+ export declare const VIEW_MORE_OPTION_VALUE = "view-more-option-value";
26
+ export declare function TypeaheadDropdown(props: ITypeaheadDropdownProps): React.JSX.Element;
27
+ //# sourceMappingURL=TypeaheadDropdown.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,143 @@
1
+ import { Button, MenuToggle, Select, SelectList, SelectOption, Spinner, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, } from '@patternfly/react-core';
2
+ import TimesCircleIcon from '@patternfly/react-icons/dist/js/icons/times-circle-icon';
3
+ import concat from 'lodash/concat';
4
+ import filter from 'lodash/filter';
5
+ import find from 'lodash/find';
6
+ import includes from 'lodash/includes';
7
+ import isEmpty from 'lodash/isEmpty';
8
+ import isEqual from 'lodash/isEqual';
9
+ import isNumber from 'lodash/isNumber';
10
+ import isString from 'lodash/isString';
11
+ import map from 'lodash/map';
12
+ import React, { useEffect, useState } from 'react';
13
+ import { Highlighter } from 'react-bootstrap-typeahead';
14
+ import { useTranslation } from 'react-i18next';
15
+ import { useSelectKeyboardNavigator } from '../hooks/useSelectKeyboardNavigator';
16
+ export const VIEW_MORE_OPTION_VALUE = 'view-more-option-value';
17
+ const VIEW_MORE_OPTION = { label: 'View more', value: VIEW_MORE_OPTION_VALUE };
18
+ export function TypeaheadDropdown(props) {
19
+ const { options, selected, hasClearButton, id, isDisabled, multiple, placeholder, isShowMoreOptionVisible, defaultIsOpen, inputAriaControls, onSelect, onBlur, onToggleClosed, onClearQuery, onQueryUpdated, onHandleLoadMore, onOpenChange, } = props;
20
+ const { t } = useTranslation();
21
+ const [isOpen, setIsOpen] = useState(defaultIsOpen !== null && defaultIsOpen !== void 0 ? defaultIsOpen : false);
22
+ const [query, setQuery] = useState(!multiple ? (!isEmpty(selected) ? selected[0].label : '') : '');
23
+ const [allOptions, setAllOptions] = useState([]);
24
+ const { focusedItemIndex, onInputKeyDown, setFocusedItemIndex } = useSelectKeyboardNavigator({
25
+ ignoreResetOnListChange: true,
26
+ isDisabled: isDisabled,
27
+ list: allOptions,
28
+ isOpen: isOpen,
29
+ setIsOpen: setIsOpen,
30
+ onSelect: (option) => onLocalSelect(undefined, option.value),
31
+ });
32
+ /**
33
+ * Allows the select toggle component to open and close itself.
34
+ */
35
+ const handleToggle = () => {
36
+ setIsOpen((open) => {
37
+ const isOpen = !open;
38
+ return isOpen;
39
+ });
40
+ };
41
+ /**
42
+ * Set the query to an empty string when the clear button is pressed.
43
+ */
44
+ const handleClearQuery = (event) => {
45
+ setQuery('');
46
+ onClearQuery && onClearQuery();
47
+ event === null || event === void 0 ? void 0 : event.stopPropagation();
48
+ event === null || event === void 0 ? void 0 : event.preventDefault();
49
+ };
50
+ /**
51
+ * Updates the query with the new value and notifies consumers that the query has been updated.
52
+ * @param _ The unused click event
53
+ * @param v The new query value
54
+ */
55
+ const updateQuery = (event, v) => {
56
+ setQuery(v);
57
+ onQueryUpdated && onQueryUpdated(v);
58
+ if (!isOpen) {
59
+ setIsOpen(true);
60
+ }
61
+ };
62
+ /**
63
+ * Used by the Select patternfly component to change the toggle open state.
64
+ * @param isOpen The state used to change the open state of the toggle
65
+ */
66
+ const localOnOpenChange = (isOpen) => {
67
+ setIsOpen(isOpen);
68
+ !!onOpenChange && onOpenChange(isOpen);
69
+ };
70
+ const isAlreadySelected = (option) => {
71
+ return includes(selected, option);
72
+ };
73
+ /**
74
+ * Handles selecting or deselecting a contact.
75
+ *
76
+ * @param {React.MouseEvent<Element, MouseEvent>} _
77
+ * @param contactLabel The value of the selected option.
78
+ * @returns
79
+ */
80
+ const onLocalSelect = (event, value) => {
81
+ if (!isString(value) && !isNumber(value)) {
82
+ return;
83
+ }
84
+ if (value === VIEW_MORE_OPTION_VALUE) {
85
+ !!onHandleLoadMore && onHandleLoadMore(event);
86
+ return;
87
+ }
88
+ const selectedOption = find(allOptions, (o) => o.value === value);
89
+ if (!selectedOption)
90
+ return;
91
+ if (!multiple) {
92
+ if (selected[0] === selectedOption)
93
+ return;
94
+ // Single selection: replace the selection with the new option
95
+ onSelect([selectedOption]);
96
+ setQuery(selectedOption.label);
97
+ }
98
+ else {
99
+ // Multiple selection: add or remove the contact
100
+ const updatedSelection = isAlreadySelected(selectedOption)
101
+ ? filter(selected, (o) => !isEqual(selectedOption, o)) // Remove
102
+ : concat(selected, selectedOption); // Add
103
+ onSelect(updatedSelection);
104
+ }
105
+ setIsOpen(false);
106
+ setFocusedItemIndex(null);
107
+ };
108
+ const toggle = (toggleRef) => (React.createElement(MenuToggle, { isFullWidth: true, variant: "typeahead", onClick: handleToggle, innerRef: toggleRef, isExpanded: isOpen, isDisabled: isDisabled },
109
+ 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" }),
111
+ React.createElement(TextInputGroupUtilities, null,
112
+ query && hasClearButton && !isDisabled && (React.createElement(Button, { "aria-label": "Clear input value", variant: "plain", onClick: handleClearQuery, isDisabled: isDisabled },
113
+ React.createElement(TimesCircleIcon, null))),
114
+ isDisabled && React.createElement(Spinner, { size: "sm" })))));
115
+ useEffect(() => {
116
+ if (isShowMoreOptionVisible) {
117
+ setAllOptions([...options, VIEW_MORE_OPTION]);
118
+ }
119
+ else {
120
+ setAllOptions(options);
121
+ }
122
+ }, [options, isShowMoreOptionVisible]);
123
+ useEffect(() => {
124
+ if (!isOpen) {
125
+ setFocusedItemIndex(null);
126
+ !!onToggleClosed && onToggleClosed();
127
+ }
128
+ // eslint-disable-next-line react-hooks/exhaustive-deps
129
+ }, [isOpen]);
130
+ 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
+ }))));
143
+ }
@@ -0,0 +1,2 @@
1
+ export * from './TypeaheadDropdown';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/TypeaheadDropdown/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './TypeaheadDropdown';
@@ -15,4 +15,5 @@ export * from './useSelectKeyboardNavigator';
15
15
  export * from './useLocalStorage';
16
16
  export * from './useSessionStorage';
17
17
  export * from './useSearchDocument';
18
+ export * from './useProgressiveLoading';
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC"}
@@ -15,3 +15,4 @@ export * from './useSelectKeyboardNavigator';
15
15
  export * from './useLocalStorage';
16
16
  export * from './useSessionStorage';
17
17
  export * from './useSearchDocument';
18
+ export * from './useProgressiveLoading';
@@ -0,0 +1,10 @@
1
+ export declare function useProgressiveLoading<T>(allItems?: T[], defaultPageSize?: number): {
2
+ pageSize: number;
3
+ visibleItems: T[];
4
+ hasMoreItems: () => boolean;
5
+ onHandleLoadMore: (event: any) => void;
6
+ setPageSize: import("react").Dispatch<import("react").SetStateAction<number>>;
7
+ setVisibleItems: import("react").Dispatch<import("react").SetStateAction<T[]>>;
8
+ resetVisibleItems: () => void;
9
+ };
10
+ //# sourceMappingURL=useProgressiveLoading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProgressiveLoading.d.ts","sourceRoot":"","sources":["../../../src/hooks/useProgressiveLoading.ts"],"names":[],"mappings":"AAGA,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,QAAQ,GAAE,CAAC,EAAO,EAAE,eAAe,SAAK;;;;;;;;EAoChF"}
@@ -0,0 +1,33 @@
1
+ import { haltEvent } from '@rh-support/utils';
2
+ import { useEffect, useState } from 'react';
3
+ export function useProgressiveLoading(allItems = [], defaultPageSize = 10) {
4
+ const [pageSize, setPageSize] = useState(defaultPageSize);
5
+ const [visibleItems, setVisibleItems] = useState(allItems.slice(0, pageSize));
6
+ useEffect(() => {
7
+ setVisibleItems(allItems.slice(0, pageSize));
8
+ }, [allItems, pageSize]);
9
+ /**
10
+ * Load the next "page" of items.
11
+ * @param event
12
+ */
13
+ const onHandleLoadMore = (event) => {
14
+ const newVisibleOptions = (visibleItems === null || visibleItems === void 0 ? void 0 : visibleItems.length) + pageSize;
15
+ if (newVisibleOptions < allItems.length) {
16
+ setVisibleItems(allItems.slice(0, newVisibleOptions));
17
+ }
18
+ else {
19
+ setVisibleItems(allItems);
20
+ }
21
+ haltEvent(event);
22
+ };
23
+ /**
24
+ * reset the visible items to the "first" page
25
+ */
26
+ const resetVisibleItems = () => setVisibleItems(allItems.slice(0, pageSize));
27
+ /**
28
+ *
29
+ * @returns Whether or not the number of visible items is less than the total number of items.
30
+ */
31
+ const hasMoreItems = () => visibleItems.length < allItems.length;
32
+ return { pageSize, visibleItems, hasMoreItems, onHandleLoadMore, setPageSize, setVisibleItems, resetVisibleItems };
33
+ }
@@ -2,10 +2,18 @@ import React from 'react';
2
2
  interface IProps {
3
3
  list: any[];
4
4
  isOpen: boolean;
5
+ ignoreResetOnListChange?: boolean;
6
+ resetIndexOnClose?: boolean;
7
+ isDisabled?: boolean;
5
8
  setIsOpen: (value: React.SetStateAction<boolean>) => void;
6
9
  onSelect: (value: any) => void;
7
10
  }
8
- export declare function useSelectKeyboardNavigator({ list, isOpen, setIsOpen, onSelect }: IProps): {
11
+ export declare function useSelectKeyboardNavigator({ list, isOpen, ignoreResetOnListChange, resetIndexOnClose, isDisabled, setIsOpen, onSelect, }: IProps): {
12
+ focusedItemIndex: number;
13
+ onInputKeyDown: (event: any) => void;
14
+ setFocusedItemIndex: React.Dispatch<React.SetStateAction<number>>;
15
+ };
16
+ export declare function useSelectKeyboardNavigatorModified({ list, isOpen, setIsOpen, onSelect }: IProps): {
9
17
  onInputKeyDown: (event: any) => void;
10
18
  focusedItemIndex: number;
11
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useSelectKeyboardNavigator.d.ts","sourceRoot":"","sources":["../../../src/hooks/useSelectKeyboardNavigator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,UAAU,MAAM;IACZ,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC1D,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,0BAA0B,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,MAAM;;;EAwDvF"}
1
+ {"version":3,"file":"useSelectKeyboardNavigator.d.ts","sourceRoot":"","sources":["../../../src/hooks/useSelectKeyboardNavigator.tsx"],"names":[],"mappings":"AACA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,UAAU,MAAM;IACZ,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC1D,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;CAClC;AAED,wBAAgB,0BAA0B,CAAC,EACvC,IAAI,EACJ,MAAM,EACN,uBAAuB,EACvB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,QAAQ,GACX,EAAE,MAAM;;;;EAqFR;AAED,wBAAgB,kCAAkC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,MAAM;;;EAqD/F"}
@@ -1,9 +1,29 @@
1
+ import { haltEvent } from '@rh-support/utils';
1
2
  import { useEffect, useState } from 'react';
2
- export function useSelectKeyboardNavigator({ list, isOpen, setIsOpen, onSelect }) {
3
+ export function useSelectKeyboardNavigator({ list, isOpen, ignoreResetOnListChange, resetIndexOnClose, isDisabled, setIsOpen, onSelect, }) {
3
4
  const [focusedItemIndex, setFocusedItemIndex] = useState(null);
5
+ /**
6
+ * Reset the item index whenever the list changes if it is not specified to be ignored.
7
+ */
4
8
  useEffect(() => {
5
- setFocusedItemIndex(null);
9
+ !ignoreResetOnListChange && setFocusedItemIndex(null);
10
+ // This hook should only run when the list has changed.
11
+ // eslint-disable-next-line react-hooks/exhaustive-deps
6
12
  }, [list.length]);
13
+ /**
14
+ * Reset the item index whenever isOpen is false and the focused index is not null.
15
+ * Should only be called when isOpen is changed
16
+ */
17
+ useEffect(() => {
18
+ if (resetIndexOnClose && !isOpen && focusedItemIndex !== null) {
19
+ setFocusedItemIndex(null);
20
+ }
21
+ // eslint-disable-next-line react-hooks/exhaustive-deps
22
+ }, [isOpen]);
23
+ /**
24
+ * Handles transitioning the focus to the next item in the list.
25
+ * @param key The stringified version of the arrow keys
26
+ */
7
27
  const handleMenuArrowKeys = (key) => {
8
28
  let indexToFocus;
9
29
  if (isOpen) {
@@ -28,12 +48,18 @@ export function useSelectKeyboardNavigator({ list, isOpen, setIsOpen, onSelect }
28
48
  setFocusedItemIndex(indexToFocus);
29
49
  }
30
50
  };
51
+ /**
52
+ * Listens for keyboard events to handle the opening/closing of the menu,
53
+ * the selection of a focused menu item and transitioning the focus to the next item.
54
+ *
55
+ * @param event The key down event
56
+ */
31
57
  const onInputKeyDown = (event) => {
32
58
  const focusedItem = focusedItemIndex !== null ? list[focusedItemIndex] : null;
33
59
  switch (event.key) {
34
60
  // Select the first available option
35
61
  case 'Enter':
36
- if (!isOpen) {
62
+ if (!isOpen && !isDisabled) {
37
63
  setIsOpen((pre) => !pre);
38
64
  }
39
65
  else if (isOpen && focusedItem !== null) {
@@ -41,6 +67,7 @@ export function useSelectKeyboardNavigator({ list, isOpen, setIsOpen, onSelect }
41
67
  }
42
68
  break;
43
69
  case 'Escape':
70
+ resetIndexOnClose && setFocusedItemIndex(null);
44
71
  setIsOpen(false);
45
72
  break;
46
73
  case 'ArrowUp':
@@ -50,5 +77,57 @@ export function useSelectKeyboardNavigator({ list, isOpen, setIsOpen, onSelect }
50
77
  break;
51
78
  }
52
79
  };
80
+ return { focusedItemIndex, onInputKeyDown, setFocusedItemIndex };
81
+ }
82
+ export function useSelectKeyboardNavigatorModified({ list, isOpen, setIsOpen, onSelect }) {
83
+ const [focusedItemIndex, setFocusedItemIndex] = useState(0);
84
+ const handleMenuArrowKeys = (key) => {
85
+ if (isOpen) {
86
+ setFocusedItemIndex((localIndex) => {
87
+ let indexToFocus;
88
+ if (key === 'ArrowUp') {
89
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
90
+ if (localIndex === 0) {
91
+ indexToFocus = list.length - 1;
92
+ }
93
+ else {
94
+ indexToFocus = localIndex - 1;
95
+ }
96
+ }
97
+ if (key === 'ArrowDown') {
98
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
99
+ if (localIndex === list.length - 1) {
100
+ indexToFocus = 0;
101
+ }
102
+ else {
103
+ indexToFocus = localIndex + 1;
104
+ }
105
+ }
106
+ return indexToFocus;
107
+ });
108
+ }
109
+ };
110
+ const onInputKeyDown = (event) => {
111
+ switch (event.key) {
112
+ // Select the first available option
113
+ case 'Enter':
114
+ const focusedItem = focusedItemIndex !== null ? list[focusedItemIndex] : null;
115
+ if (!isOpen) {
116
+ setIsOpen((pre) => !pre);
117
+ }
118
+ else if (isOpen && focusedItem !== null) {
119
+ onSelect(focusedItem);
120
+ }
121
+ break;
122
+ case 'Escape':
123
+ setIsOpen(false);
124
+ break;
125
+ case 'ArrowUp':
126
+ case 'ArrowDown':
127
+ haltEvent(event);
128
+ handleMenuArrowKeys(event.key);
129
+ break;
130
+ }
131
+ };
53
132
  return { onInputKeyDown, focusedItemIndex };
54
133
  }
@@ -32,4 +32,5 @@ export * from './TextAreaResizable';
32
32
  export * from './CustomTextInput';
33
33
  export * from './PhoneInput';
34
34
  export * from './TagsSelector';
35
+ export * from './OwnerTypeaheadDropdown';
35
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC"}
package/lib/esm/index.js CHANGED
@@ -32,3 +32,4 @@ export * from './TextAreaResizable';
32
32
  export * from './CustomTextInput';
33
33
  export * from './PhoneInput';
34
34
  export * from './TagsSelector';
35
+ export * from './OwnerTypeaheadDropdown';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rh-support/components",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "Contains all reusabel components for support app",
5
5
  "author": "Vikas Rathee <vrathee@redhat.com>",
6
6
  "license": "ISC",
@@ -44,7 +44,7 @@
44
44
  "prepublishOnly": "npm run build"
45
45
  },
46
46
  "peerDependencies": {
47
- "@cee-eng/hydrajs": "4.17.36",
47
+ "@cee-eng/hydrajs": "4.18.0",
48
48
  "@cee-eng/ui-toolkit": "1.1.8",
49
49
  "@patternfly/patternfly": "5.4.0",
50
50
  "@patternfly/react-core": "5.4.0",
@@ -62,7 +62,7 @@
62
62
  "use-deep-compare-effect": "^1.6.1"
63
63
  },
64
64
  "dependencies": {
65
- "@cee-eng/hydrajs": "4.17.36",
65
+ "@cee-eng/hydrajs": "4.18.0",
66
66
  "@cee-eng/ui-toolkit": "1.1.8",
67
67
  "@patternfly/patternfly": "5.4.0",
68
68
  "@patternfly/react-core": "5.4.0",
@@ -70,8 +70,8 @@
70
70
  "@patternfly/react-table": "5.1.1",
71
71
  "@patternfly/react-tokens": "^5.4.0",
72
72
  "@rh-support/types": "2.0.5",
73
- "@rh-support/user-permissions": "2.5.0",
74
- "@rh-support/utils": "2.5.0",
73
+ "@rh-support/user-permissions": "2.5.1",
74
+ "@rh-support/utils": "2.5.1",
75
75
  "dompurify": "^2.2.6",
76
76
  "downshift": "^6.0.5",
77
77
  "js-worker-search": "^1.4.1",
@@ -111,5 +111,5 @@
111
111
  "defaults and supports es6-module",
112
112
  "maintained node versions"
113
113
  ],
114
- "gitHead": "d867a78014ee4a08aa8d7bf9e0dc9bd08ce6ac5a"
114
+ "gitHead": "c4edba0ff68de868d7de31a8afcca922b218476a"
115
115
  }