@patternfly/react-core 5.4.0-prerelease.8 → 5.4.0-prerelease.9

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 (187) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +0 -2
  3. package/components/package.json +1 -1
  4. package/deprecated/package.json +1 -1
  5. package/dist/dynamic/components/AboutModal/package.json +1 -1
  6. package/dist/dynamic/components/Accordion/package.json +1 -1
  7. package/dist/dynamic/components/ActionList/package.json +1 -1
  8. package/dist/dynamic/components/Alert/package.json +1 -1
  9. package/dist/dynamic/components/Avatar/package.json +1 -1
  10. package/dist/dynamic/components/BackToTop/package.json +1 -1
  11. package/dist/dynamic/components/Backdrop/package.json +1 -1
  12. package/dist/dynamic/components/BackgroundImage/package.json +1 -1
  13. package/dist/dynamic/components/Badge/package.json +1 -1
  14. package/dist/dynamic/components/Banner/package.json +1 -1
  15. package/dist/dynamic/components/Brand/package.json +1 -1
  16. package/dist/dynamic/components/Breadcrumb/package.json +1 -1
  17. package/dist/dynamic/components/Button/package.json +1 -1
  18. package/dist/dynamic/components/CalendarMonth/package.json +1 -1
  19. package/dist/dynamic/components/Card/package.json +1 -1
  20. package/dist/dynamic/components/Checkbox/package.json +1 -1
  21. package/dist/dynamic/components/Chip/package.json +1 -1
  22. package/dist/dynamic/components/ClipboardCopy/package.json +1 -1
  23. package/dist/dynamic/components/CodeBlock/package.json +1 -1
  24. package/dist/dynamic/components/DataList/package.json +1 -1
  25. package/dist/dynamic/components/DatePicker/package.json +1 -1
  26. package/dist/dynamic/components/DescriptionList/package.json +1 -1
  27. package/dist/dynamic/components/Divider/package.json +1 -1
  28. package/dist/dynamic/components/DragDrop/package.json +1 -1
  29. package/dist/dynamic/components/Drawer/package.json +1 -1
  30. package/dist/dynamic/components/Dropdown/package.json +1 -1
  31. package/dist/dynamic/components/DualListSelector/package.json +1 -1
  32. package/dist/dynamic/components/EmptyState/package.json +1 -1
  33. package/dist/dynamic/components/ExpandableSection/package.json +1 -1
  34. package/dist/dynamic/components/FileUpload/package.json +1 -1
  35. package/dist/dynamic/components/Form/package.json +1 -1
  36. package/dist/dynamic/components/FormSelect/package.json +1 -1
  37. package/dist/dynamic/components/HelperText/package.json +1 -1
  38. package/dist/dynamic/components/Hint/package.json +1 -1
  39. package/dist/dynamic/components/Icon/package.json +1 -1
  40. package/dist/dynamic/components/InputGroup/package.json +1 -1
  41. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  42. package/dist/dynamic/components/Label/package.json +1 -1
  43. package/dist/dynamic/components/List/package.json +1 -1
  44. package/dist/dynamic/components/LoginPage/package.json +1 -1
  45. package/dist/dynamic/components/Masthead/package.json +1 -1
  46. package/dist/dynamic/components/Menu/package.json +1 -1
  47. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  48. package/dist/dynamic/components/Modal/package.json +1 -1
  49. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  50. package/dist/dynamic/components/Nav/package.json +1 -1
  51. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  52. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  53. package/dist/dynamic/components/NumberInput/package.json +1 -1
  54. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  55. package/dist/dynamic/components/Page/package.json +1 -1
  56. package/dist/dynamic/components/Pagination/package.json +1 -1
  57. package/dist/dynamic/components/Panel/package.json +1 -1
  58. package/dist/dynamic/components/Popover/package.json +1 -1
  59. package/dist/dynamic/components/Progress/package.json +1 -1
  60. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  61. package/dist/dynamic/components/Radio/package.json +1 -1
  62. package/dist/dynamic/components/SearchInput/package.json +1 -1
  63. package/dist/dynamic/components/Select/package.json +1 -1
  64. package/dist/dynamic/components/Sidebar/package.json +1 -1
  65. package/dist/dynamic/components/SimpleList/package.json +1 -1
  66. package/dist/dynamic/components/Skeleton/package.json +1 -1
  67. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  68. package/dist/dynamic/components/Slider/package.json +1 -1
  69. package/dist/dynamic/components/Spinner/package.json +1 -1
  70. package/dist/dynamic/components/Switch/package.json +1 -1
  71. package/dist/dynamic/components/Tabs/package.json +1 -1
  72. package/dist/dynamic/components/Text/package.json +1 -1
  73. package/dist/dynamic/components/TextArea/package.json +1 -1
  74. package/dist/dynamic/components/TextInput/package.json +1 -1
  75. package/dist/dynamic/components/TextInputGroup/package.json +1 -1
  76. package/dist/dynamic/components/Tile/package.json +1 -1
  77. package/dist/dynamic/components/TimePicker/package.json +1 -1
  78. package/dist/dynamic/components/Timestamp/package.json +1 -1
  79. package/dist/dynamic/components/Title/package.json +1 -1
  80. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  81. package/dist/dynamic/components/Toolbar/package.json +1 -1
  82. package/dist/dynamic/components/Tooltip/package.json +1 -1
  83. package/dist/dynamic/components/TreeView/package.json +1 -1
  84. package/dist/dynamic/components/Truncate/package.json +1 -1
  85. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  86. package/dist/dynamic/components/Wizard/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/ApplicationLauncher/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/ContextSelector/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/Dropdown/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/OptionsMenu/package.json +1 -1
  91. package/dist/dynamic/deprecated/components/PageHeader/package.json +1 -1
  92. package/dist/dynamic/deprecated/components/Select/package.json +1 -1
  93. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  94. package/dist/dynamic/deprecated/components/package.json +1 -1
  95. package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
  96. package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
  97. package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
  98. package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
  99. package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
  100. package/dist/dynamic/helpers/constants/package.json +1 -1
  101. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  102. package/dist/dynamic/helpers/favorites/package.json +1 -1
  103. package/dist/dynamic/helpers/fileUtils/package.json +1 -1
  104. package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
  105. package/dist/dynamic/helpers/package.json +1 -1
  106. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  107. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  108. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  109. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  110. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  111. package/dist/dynamic/helpers/util/package.json +1 -1
  112. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  113. package/dist/dynamic/layouts/Flex/package.json +1 -1
  114. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  115. package/dist/dynamic/layouts/Grid/package.json +1 -1
  116. package/dist/dynamic/layouts/Level/package.json +1 -1
  117. package/dist/dynamic/layouts/Split/package.json +1 -1
  118. package/dist/dynamic/layouts/Stack/package.json +1 -1
  119. package/dist/dynamic/next/components/DualListSelector/package.json +1 -1
  120. package/dist/dynamic/next/components/Modal/package.json +1 -1
  121. package/dist/dynamic/next/components/package.json +1 -1
  122. package/dist/dynamic/styles/package.json +1 -1
  123. package/dist/esm/components/MenuToggle/MenuToggle.d.ts.map +1 -1
  124. package/dist/esm/components/MenuToggle/MenuToggle.js +1 -1
  125. package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
  126. package/dist/esm/components/Pagination/Pagination.d.ts.map +1 -1
  127. package/dist/esm/components/Pagination/Pagination.js +2 -1
  128. package/dist/esm/components/Pagination/Pagination.js.map +1 -1
  129. package/dist/esm/components/Pagination/PaginationOptionsMenu.d.ts +1 -0
  130. package/dist/esm/components/Pagination/PaginationOptionsMenu.d.ts.map +1 -1
  131. package/dist/esm/components/Pagination/PaginationOptionsMenu.js +2 -4
  132. package/dist/esm/components/Pagination/PaginationOptionsMenu.js.map +1 -1
  133. package/dist/esm/components/Select/Select.d.ts +2 -0
  134. package/dist/esm/components/Select/Select.d.ts.map +1 -1
  135. package/dist/esm/components/Select/Select.js +3 -3
  136. package/dist/esm/components/Select/Select.js.map +1 -1
  137. package/dist/esm/components/Tabs/Tabs.d.ts +4 -0
  138. package/dist/esm/components/Tabs/Tabs.d.ts.map +1 -1
  139. package/dist/esm/components/Tabs/Tabs.js +17 -0
  140. package/dist/esm/components/Tabs/Tabs.js.map +1 -1
  141. package/dist/esm/components/Truncate/Truncate.d.ts.map +1 -1
  142. package/dist/esm/components/Truncate/Truncate.js +54 -12
  143. package/dist/esm/components/Truncate/Truncate.js.map +1 -1
  144. package/dist/js/components/MenuToggle/MenuToggle.d.ts.map +1 -1
  145. package/dist/js/components/MenuToggle/MenuToggle.js +1 -1
  146. package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
  147. package/dist/js/components/Pagination/Pagination.d.ts.map +1 -1
  148. package/dist/js/components/Pagination/Pagination.js +2 -1
  149. package/dist/js/components/Pagination/Pagination.js.map +1 -1
  150. package/dist/js/components/Pagination/PaginationOptionsMenu.d.ts +1 -0
  151. package/dist/js/components/Pagination/PaginationOptionsMenu.d.ts.map +1 -1
  152. package/dist/js/components/Pagination/PaginationOptionsMenu.js +2 -4
  153. package/dist/js/components/Pagination/PaginationOptionsMenu.js.map +1 -1
  154. package/dist/js/components/Select/Select.d.ts +2 -0
  155. package/dist/js/components/Select/Select.d.ts.map +1 -1
  156. package/dist/js/components/Select/Select.js +3 -3
  157. package/dist/js/components/Select/Select.js.map +1 -1
  158. package/dist/js/components/Tabs/Tabs.d.ts +4 -0
  159. package/dist/js/components/Tabs/Tabs.d.ts.map +1 -1
  160. package/dist/js/components/Tabs/Tabs.js +17 -0
  161. package/dist/js/components/Tabs/Tabs.js.map +1 -1
  162. package/dist/js/components/Truncate/Truncate.d.ts.map +1 -1
  163. package/dist/js/components/Truncate/Truncate.js +54 -12
  164. package/dist/js/components/Truncate/Truncate.js.map +1 -1
  165. package/dist/umd/react-core.min.js +2 -2
  166. package/helpers/package.json +1 -1
  167. package/layouts/package.json +1 -1
  168. package/next/package.json +1 -1
  169. package/package.json +6 -6
  170. package/src/components/CodeBlock/examples/CodeBlock.md +6 -0
  171. package/src/components/CodeBlock/examples/CodeBlockBasic.tsx +1 -1
  172. package/src/components/CodeBlock/examples/CodeBlockExpandable.tsx +3 -3
  173. package/src/components/MenuToggle/MenuToggle.tsx +1 -0
  174. package/src/components/Pagination/Pagination.tsx +2 -0
  175. package/src/components/Pagination/PaginationOptionsMenu.tsx +13 -14
  176. package/src/components/Pagination/__tests__/Generated/__snapshots__/PaginationOptionsMenu.test.tsx.snap +36 -38
  177. package/src/components/Pagination/__tests__/__snapshots__/Pagination.test.tsx.snap +714 -754
  178. package/src/components/Select/Select.tsx +5 -2
  179. package/src/components/Select/examples/SelectMultiTypeahead.tsx +111 -66
  180. package/src/components/Select/examples/SelectMultiTypeaheadCheckbox.tsx +103 -60
  181. package/src/components/Select/examples/SelectMultiTypeaheadCreatable.tsx +125 -79
  182. package/src/components/Select/examples/SelectTypeahead.tsx +113 -69
  183. package/src/components/Select/examples/SelectTypeaheadCreatable.tsx +115 -75
  184. package/src/components/Tabs/Tabs.tsx +21 -0
  185. package/src/components/ToggleGroup/examples/ToggleGroup.md +33 -8
  186. package/src/components/Truncate/Truncate.tsx +73 -7
  187. package/src/components/Truncate/__tests__/Truncate.test.tsx +6 -0
@@ -29,10 +29,11 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
29
29
  const [filterValue, setFilterValue] = React.useState<string>('');
30
30
  const [selectOptions, setSelectOptions] = React.useState<SelectOptionProps[]>(initialSelectOptions);
31
31
  const [focusedItemIndex, setFocusedItemIndex] = React.useState<number | null>(null);
32
- const [activeItem, setActiveItem] = React.useState<string | null>(null);
33
- const [onCreation, setOnCreation] = React.useState<boolean>(false); // Boolean to refresh filter state after new option is created
32
+ const [activeItemId, setActiveItemId] = React.useState<string | null>(null);
34
33
  const textInputRef = React.useRef<HTMLInputElement>();
35
34
 
35
+ const CREATE_NEW = 'create';
36
+
36
37
  React.useEffect(() => {
37
38
  let newSelectOptions: SelectOptionProps[] = initialSelectOptions;
38
39
 
@@ -42,9 +43,9 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
42
43
  String(menuItem.children).toLowerCase().includes(filterValue.toLowerCase())
43
44
  );
44
45
 
45
- // When no options are found after filtering, display creation option
46
- if (!newSelectOptions.length) {
47
- newSelectOptions = [{ isDisabled: false, children: `Create new option "${filterValue}"`, value: 'create' }];
46
+ // If no option matches the filter exactly, display creation option
47
+ if (!initialSelectOptions.some((option) => option.value === filterValue)) {
48
+ newSelectOptions = [...newSelectOptions, { children: `Create new option "${filterValue}"`, value: CREATE_NEW }];
48
49
  }
49
50
 
50
51
  // Open the menu when the input value changes and the new value is not empty
@@ -54,96 +55,133 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
54
55
  }
55
56
 
56
57
  setSelectOptions(newSelectOptions);
57
- setActiveItem(null);
58
+ }, [filterValue]);
59
+
60
+ const createItemId = (value: any) => `select-typeahead-${value.replace(' ', '-')}`;
61
+
62
+ const setActiveAndFocusedItem = (itemIndex: number) => {
63
+ setFocusedItemIndex(itemIndex);
64
+ const focusedItem = selectOptions[itemIndex];
65
+ setActiveItemId(createItemId(focusedItem.value));
66
+ };
67
+
68
+ const resetActiveAndFocusedItem = () => {
58
69
  setFocusedItemIndex(null);
59
- }, [filterValue, onCreation]);
70
+ setActiveItemId(null);
71
+ };
60
72
 
61
- const onToggleClick = () => {
62
- setIsOpen(!isOpen);
73
+ const closeMenu = () => {
74
+ setIsOpen(false);
75
+ resetActiveAndFocusedItem();
63
76
  };
64
77
 
65
- const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
78
+ const onInputClick = () => {
79
+ if (!isOpen) {
80
+ setIsOpen(true);
81
+ } else if (!inputValue) {
82
+ closeMenu();
83
+ }
84
+ };
85
+
86
+ const selectOption = (value: string | number, content: string | number) => {
66
87
  // eslint-disable-next-line no-console
88
+ console.log('selected', content);
89
+
90
+ setInputValue(String(content));
91
+ setFilterValue('');
92
+ setSelected(String(value));
93
+
94
+ closeMenu();
95
+ };
67
96
 
97
+ const onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
68
98
  if (value) {
69
- if (value === 'create') {
70
- if (!initialSelectOptions.some((item) => item.value === filterValue)) {
99
+ if (value === CREATE_NEW) {
100
+ if (!initialSelectOptions.some((item) => item.children === filterValue)) {
71
101
  initialSelectOptions = [...initialSelectOptions, { value: filterValue, children: filterValue }];
72
102
  }
73
103
  setSelected(filterValue);
74
- setOnCreation(!onCreation);
75
104
  setFilterValue('');
105
+ closeMenu();
76
106
  } else {
77
- // eslint-disable-next-line no-console
78
- console.log('selected', value);
79
- setInputValue(value as string);
80
- setFilterValue('');
81
- setSelected(value as string);
107
+ const optionText = selectOptions.find((option) => option.value === value)?.children;
108
+ selectOption(value, optionText as string);
82
109
  }
83
110
  }
84
-
85
- setIsOpen(false);
86
- setFocusedItemIndex(null);
87
- setActiveItem(null);
88
111
  };
89
112
 
90
113
  const onTextInputChange = (_event: React.FormEvent<HTMLInputElement>, value: string) => {
91
114
  setInputValue(value);
92
115
  setFilterValue(value);
116
+
117
+ resetActiveAndFocusedItem();
118
+
119
+ if (value !== selected) {
120
+ setSelected('');
121
+ }
93
122
  };
94
123
 
95
124
  const handleMenuArrowKeys = (key: string) => {
96
- let indexToFocus;
125
+ let indexToFocus = 0;
126
+
127
+ if (!isOpen) {
128
+ setIsOpen(true);
129
+ }
97
130
 
98
- if (isOpen) {
99
- if (key === 'ArrowUp') {
100
- // When no index is set or at the first index, focus to the last, otherwise decrement focus index
101
- if (focusedItemIndex === null || focusedItemIndex === 0) {
131
+ if (selectOptions.every((option) => option.isDisabled)) {
132
+ return;
133
+ }
134
+
135
+ if (key === 'ArrowUp') {
136
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
137
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
138
+ indexToFocus = selectOptions.length - 1;
139
+ } else {
140
+ indexToFocus = focusedItemIndex - 1;
141
+ }
142
+
143
+ // Skip disabled options
144
+ while (selectOptions[indexToFocus].isDisabled) {
145
+ indexToFocus--;
146
+ if (indexToFocus === -1) {
102
147
  indexToFocus = selectOptions.length - 1;
103
- } else {
104
- indexToFocus = focusedItemIndex - 1;
105
148
  }
106
149
  }
150
+ }
151
+
152
+ if (key === 'ArrowDown') {
153
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
154
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
155
+ indexToFocus = 0;
156
+ } else {
157
+ indexToFocus = focusedItemIndex + 1;
158
+ }
107
159
 
108
- if (key === 'ArrowDown') {
109
- // When no index is set or at the last index, focus to the first, otherwise increment focus index
110
- if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
160
+ // Skip disabled options
161
+ while (selectOptions[indexToFocus].isDisabled) {
162
+ indexToFocus++;
163
+ if (indexToFocus === selectOptions.length) {
111
164
  indexToFocus = 0;
112
- } else {
113
- indexToFocus = focusedItemIndex + 1;
114
165
  }
115
166
  }
116
-
117
- setFocusedItemIndex(indexToFocus);
118
- const focusedItem = selectOptions.filter((option) => !option.isDisabled)[indexToFocus];
119
- setActiveItem(`select-create-typeahead-${focusedItem.value.replace(' ', '-')}`);
120
167
  }
168
+
169
+ setActiveAndFocusedItem(indexToFocus);
121
170
  };
122
171
 
123
172
  const onInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
124
- const enabledMenuItems = selectOptions.filter((option) => !option.isDisabled);
125
- const [firstMenuItem] = enabledMenuItems;
126
- const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
173
+ const focusedItem = focusedItemIndex !== null ? selectOptions[focusedItemIndex] : null;
127
174
 
128
175
  switch (event.key) {
129
- // Select the first available option
130
176
  case 'Enter':
131
- if (isOpen) {
177
+ if (isOpen && focusedItem && !focusedItem.isAriaDisabled) {
132
178
  onSelect(undefined, focusedItem.value as string);
133
- setIsOpen((prevIsOpen) => !prevIsOpen);
134
- setFocusedItemIndex(null);
135
- setActiveItem(null);
136
179
  }
137
180
 
138
- setIsOpen((prevIsOpen) => !prevIsOpen);
139
- setFocusedItemIndex(null);
140
- setActiveItem(null);
181
+ if (!isOpen) {
182
+ setIsOpen(true);
183
+ }
141
184
 
142
- break;
143
- case 'Tab':
144
- case 'Escape':
145
- setIsOpen(false);
146
- setActiveItem(null);
147
185
  break;
148
186
  case 'ArrowUp':
149
187
  case 'ArrowDown':
@@ -153,6 +191,19 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
153
191
  }
154
192
  };
155
193
 
194
+ const onToggleClick = () => {
195
+ setIsOpen(!isOpen);
196
+ textInputRef?.current?.focus();
197
+ };
198
+
199
+ const onClearButtonClick = () => {
200
+ setSelected('');
201
+ setInputValue('');
202
+ setFilterValue('');
203
+ resetActiveAndFocusedItem();
204
+ textInputRef?.current?.focus();
205
+ };
206
+
156
207
  const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
157
208
  <MenuToggle
158
209
  ref={toggleRef}
@@ -165,34 +216,23 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
165
216
  <TextInputGroup isPlain>
166
217
  <TextInputGroupMain
167
218
  value={inputValue}
168
- onClick={onToggleClick}
219
+ onClick={onInputClick}
169
220
  onChange={onTextInputChange}
170
221
  onKeyDown={onInputKeyDown}
171
222
  id="create-typeahead-select-input"
172
223
  autoComplete="off"
173
224
  innerRef={textInputRef}
174
225
  placeholder="Select a state"
175
- {...(activeItem && { 'aria-activedescendant': activeItem })}
226
+ {...(activeItemId && { 'aria-activedescendant': activeItemId })}
176
227
  role="combobox"
177
228
  isExpanded={isOpen}
178
229
  aria-controls="select-create-typeahead-listbox"
179
230
  />
180
231
 
181
- <TextInputGroupUtilities>
182
- {!!inputValue && (
183
- <Button
184
- variant="plain"
185
- onClick={() => {
186
- setSelected('');
187
- setInputValue('');
188
- setFilterValue('');
189
- textInputRef?.current?.focus();
190
- }}
191
- aria-label="Clear input value"
192
- >
193
- <TimesIcon aria-hidden />
194
- </Button>
195
- )}
232
+ <TextInputGroupUtilities {...(!inputValue ? { style: { display: 'none' } } : {})}>
233
+ <Button variant="plain" onClick={onClearButtonClick} aria-label="Clear input value">
234
+ <TimesIcon aria-hidden />
235
+ </Button>
196
236
  </TextInputGroupUtilities>
197
237
  </TextInputGroup>
198
238
  </MenuToggle>
@@ -204,10 +244,11 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
204
244
  isOpen={isOpen}
205
245
  selected={selected}
206
246
  onSelect={onSelect}
207
- onOpenChange={() => {
208
- setIsOpen(false);
247
+ onOpenChange={(isOpen) => {
248
+ !isOpen && closeMenu();
209
249
  }}
210
250
  toggle={toggle}
251
+ shouldFocusFirstItemOnOpen={false}
211
252
  >
212
253
  <SelectList id="select-create-typeahead-listbox">
213
254
  {selectOptions.map((option, index) => (
@@ -215,8 +256,7 @@ export const SelectTypeaheadCreatable: React.FunctionComponent = () => {
215
256
  key={option.value || option.children}
216
257
  isFocused={focusedItemIndex === index}
217
258
  className={option.className}
218
- onClick={() => setSelected(option.value)}
219
- id={`select-typeahead-${option.value.replace(' ', '-')}`}
259
+ id={createItemId(option.value)}
220
260
  {...option}
221
261
  ref={null}
222
262
  />
@@ -384,6 +384,27 @@ class Tabs extends React.Component<TabsProps, TabsState> {
384
384
  this.direction = getLanguageDirection(this.tabList.current);
385
385
  }
386
386
 
387
+ static getDerivedStateFromProps(nextProps: TabsProps, prevState: TabsState) {
388
+ if (prevState.uncontrolledActiveKey === undefined) {
389
+ return null;
390
+ }
391
+
392
+ const childrenHasTabWithActiveEventKey = React.Children.toArray(nextProps.children)
393
+ .filter((child): child is TabElement => React.isValidElement(child))
394
+ .some(({ props }) => props.eventKey === prevState.uncontrolledActiveKey);
395
+
396
+ // if uncontrolledActiveKey is an existing eventKey of any Tab of nextProps.children --> don't update uncontrolledActiveKey
397
+ if (childrenHasTabWithActiveEventKey) {
398
+ return null;
399
+ }
400
+
401
+ // otherwise update state derived from nextProps.defaultActiveKey
402
+ return {
403
+ uncontrolledActiveKey: nextProps.defaultActiveKey,
404
+ shownKeys: nextProps.defaultActiveKey !== undefined ? [nextProps.defaultActiveKey] : [nextProps.activeKey] // only for mountOnEnter case
405
+ };
406
+ }
407
+
387
408
  render() {
388
409
  const {
389
410
  className,
@@ -13,22 +13,47 @@ import ShareSquareIcon from '@patternfly/react-icons/dist/esm/icons/share-square
13
13
 
14
14
  ## Examples
15
15
 
16
- ### Default with multiple selectable
17
- ```ts file="./ToggleGroupDefaultMultiple.tsx"
18
- ```
16
+ ### Single select toggle group
17
+
18
+ A single select toggle group allows users to toggle between multiple items.
19
+
20
+ To indicate whether a `<ToggleGroupItem>` is selected or not, use the `isSelected` property.
19
21
 
20
- ### Default with single selectable
21
22
  ```ts file="./ToggleGroupDefaultSingle.tsx"
22
23
  ```
23
24
 
24
- ### Icons
25
+ ### Multi select toggle group
26
+
27
+ A multi select toggle group allows users to select multiple items at once.
28
+
29
+ When a toggle item is disabled it cannot be selected. Click the "Disable all" button in the following example to see this in action.
30
+
31
+ ```ts file="./ToggleGroupDefaultMultiple.tsx"
32
+ ```
33
+
34
+ ### With icons
35
+
36
+ You can use a recognizable icon as a toggle item label.
37
+
38
+ To do this, pass an imported icon to the `icon` property of a `<ToggleGroupItem>`.
39
+
25
40
  ```ts file="./ToggleGroupIcon.tsx"
26
41
  ```
27
42
 
28
- ### Text and icons
43
+ ### With text and icons
44
+
45
+ Adding text to a toggle item with an icon helps clarify the button's function.
46
+
47
+ To do this, pass a descriptive label to the `text` property of a `<ToggleGroupItem>`.
48
+
29
49
  ```ts file="./ToggleGroupTextIcon.tsx"
30
50
  ```
31
51
 
32
- ### Compact variant
52
+ ### Compact toggle group
53
+
54
+ When space in a UI is limited, you can use a compact toggle group.
55
+
56
+ To apply compact styling to a `<ToggleGroup>`, use `isCompact`.
57
+
33
58
  ```ts file="./ToggleGroupCompact.tsx"
34
- ```
59
+ ```
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import styles from '@patternfly/react-styles/css/components/Truncate/truncate';
3
3
  import { css } from '@patternfly/react-styles';
4
4
  import { Tooltip, TooltipPosition } from '../Tooltip';
5
+ import { getResizeObserver } from '../../helpers/resizeObserver';
5
6
 
6
7
  export enum TruncatePosition {
7
8
  start = 'start',
@@ -52,11 +53,65 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
52
53
  trailingNumChars = 7,
53
54
  content,
54
55
  ...props
55
- }: TruncateProps) => (
56
- <Tooltip position={tooltipPosition} content={content}>
57
- <span className={css(styles.truncate, className)} {...props}>
56
+ }: TruncateProps) => {
57
+ const [isTruncated, setIsTruncated] = React.useState(true);
58
+ const [parentElement, setParentElement] = React.useState<HTMLElement>(null);
59
+ const [textElement, setTextElement] = React.useState<HTMLElement>(null);
60
+
61
+ const textRef = React.useRef<HTMLElement>(null);
62
+ const subParentRef = React.useRef<HTMLDivElement>(null);
63
+ const observer = React.useRef(null);
64
+
65
+ const getActualWidth = (element: Element) => {
66
+ const computedStyle = getComputedStyle(element);
67
+
68
+ return (
69
+ parseFloat(computedStyle.width) -
70
+ parseFloat(computedStyle.paddingLeft) -
71
+ parseFloat(computedStyle.paddingRight) -
72
+ parseFloat(computedStyle.borderRight) -
73
+ parseFloat(computedStyle.borderLeft)
74
+ );
75
+ };
76
+
77
+ const calculateTotalTextWidth = (element: Element, trailingNumChars: number, content: string) => {
78
+ const firstTextWidth = element.scrollWidth;
79
+ const firstTextLength = content.length;
80
+ return (firstTextWidth / firstTextLength) * trailingNumChars + firstTextWidth;
81
+ };
82
+
83
+ React.useEffect(() => {
84
+ if (textRef && textRef.current && !textElement) {
85
+ setTextElement(textRef.current);
86
+ }
87
+
88
+ if (subParentRef && subParentRef.current.parentElement.parentElement && !parentElement) {
89
+ setParentElement(subParentRef.current.parentElement.parentElement);
90
+ }
91
+ }, [textRef, subParentRef]);
92
+
93
+ React.useEffect(() => {
94
+ if (textElement && parentElement && !observer.current) {
95
+ const totalTextWidth = calculateTotalTextWidth(textElement, trailingNumChars, content);
96
+ const textWidth = position === 'middle' ? totalTextWidth : textElement.scrollWidth;
97
+
98
+ const handleResize = () => {
99
+ const parentWidth = getActualWidth(parentElement);
100
+ setIsTruncated(textWidth >= parentWidth);
101
+ };
102
+
103
+ const observer = getResizeObserver(parentElement, handleResize);
104
+
105
+ return () => {
106
+ observer();
107
+ };
108
+ }
109
+ }, [textElement, parentElement]);
110
+
111
+ const truncateBody = (
112
+ <span ref={subParentRef} className={css(styles.truncate, className)} {...props}>
58
113
  {(position === TruncatePosition.end || position === TruncatePosition.start) && (
59
- <span className={truncateStyles[position]}>
114
+ <span ref={textRef} className={truncateStyles[position]}>
60
115
  {content}
61
116
  {position === TruncatePosition.start && <React.Fragment>&lrm;</React.Fragment>}
62
117
  </span>
@@ -64,7 +119,9 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
64
119
  {position === TruncatePosition.middle &&
65
120
  content.slice(0, content.length - trailingNumChars).length > minWidthCharacters && (
66
121
  <React.Fragment>
67
- <span className={styles.truncateStart}>{sliceContent(content, trailingNumChars)[0]}</span>
122
+ <span ref={textRef} className={styles.truncateStart}>
123
+ {sliceContent(content, trailingNumChars)[0]}
124
+ </span>
68
125
  <span className={styles.truncateEnd}>{sliceContent(content, trailingNumChars)[1]}</span>
69
126
  </React.Fragment>
70
127
  )}
@@ -72,6 +129,15 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
72
129
  content.slice(0, content.length - trailingNumChars).length <= minWidthCharacters &&
73
130
  content}
74
131
  </span>
75
- </Tooltip>
76
- );
132
+ );
133
+
134
+ return isTruncated ? (
135
+ <Tooltip hidden={!isTruncated} position={tooltipPosition} content={content}>
136
+ {truncateBody}
137
+ </Tooltip>
138
+ ) : (
139
+ truncateBody
140
+ );
141
+ };
142
+
77
143
  Truncate.displayName = 'Truncate';
@@ -13,6 +13,12 @@ jest.mock('../../Tooltip', () => ({
13
13
  )
14
14
  }));
15
15
 
16
+ global.ResizeObserver = jest.fn().mockImplementation(() => ({
17
+ observe: jest.fn(),
18
+ unobserve: jest.fn(),
19
+ disconnect: jest.fn()
20
+ }));
21
+
16
22
  test(`renders with class ${styles.truncate}`, () => {
17
23
  render(<Truncate content={''} aria-label="test-id" />);
18
24