@axinom/mosaic-ui 0.75.0-rc.5 → 0.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/dist/THIRD-PARTY-LICENSES.txt +1296 -0
  2. package/dist/cjs/components/ActionBar/ActionBar.d.ts +26 -0
  3. package/dist/cjs/components/ActionBar/ActionBar.d.ts.map +1 -0
  4. package/dist/cjs/components/ActionBar/ActionBar.model.d.ts +49 -0
  5. package/dist/cjs/components/ActionBar/ActionBar.model.d.ts.map +1 -0
  6. package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
  7. package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
  8. package/dist/cjs/components/ActionBar/helpers.d.ts +22 -0
  9. package/dist/cjs/components/ActionBar/helpers.d.ts.map +1 -0
  10. package/dist/cjs/components/ActionBar/index.d.ts +3 -0
  11. package/dist/cjs/components/ActionBar/index.d.ts.map +1 -0
  12. package/dist/cjs/components/Actions/Action/Action.d.ts.map +1 -1
  13. package/dist/cjs/components/ConfirmDialog/index.d.ts +1 -0
  14. package/dist/cjs/components/ConfirmDialog/index.d.ts.map +1 -1
  15. package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
  16. package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
  17. package/dist/cjs/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
  18. package/dist/cjs/components/Explorer/Explorer.d.ts.map +1 -1
  19. package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
  20. package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
  21. package/dist/cjs/components/Explorer/helpers/useActions.d.ts +15 -4
  22. package/dist/cjs/components/Explorer/helpers/useActions.d.ts.map +1 -1
  23. package/dist/cjs/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
  24. package/dist/cjs/components/List/List.d.ts +17 -3
  25. package/dist/cjs/components/List/List.d.ts.map +1 -1
  26. package/dist/cjs/components/List/List.model.d.ts +5 -1
  27. package/dist/cjs/components/List/List.model.d.ts.map +1 -1
  28. package/dist/cjs/components/List/ListHeader/ListHeader.d.ts +10 -1
  29. package/dist/cjs/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  30. package/dist/cjs/components/List/ListRow/ListRow.d.ts +16 -5
  31. package/dist/cjs/components/List/ListRow/ListRow.d.ts.map +1 -1
  32. package/dist/cjs/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
  33. package/dist/cjs/components/List/helpers.d.ts +11 -3
  34. package/dist/cjs/components/List/helpers.d.ts.map +1 -1
  35. package/dist/cjs/components/List/useColumnsSize.d.ts +4 -2
  36. package/dist/cjs/components/List/useColumnsSize.d.ts.map +1 -1
  37. package/dist/cjs/components/PageHeader/PageHeader.d.ts +4 -1
  38. package/dist/cjs/components/PageHeader/PageHeader.d.ts.map +1 -1
  39. package/dist/cjs/components/PageHeader/PageHeader.model.d.ts +37 -1
  40. package/dist/cjs/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  41. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
  42. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  43. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
  44. package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
  45. package/dist/cjs/components/PageHeader/helpers.d.ts +9 -0
  46. package/dist/cjs/components/PageHeader/helpers.d.ts.map +1 -0
  47. package/dist/cjs/components/index.d.ts +1 -0
  48. package/dist/cjs/components/index.d.ts.map +1 -1
  49. package/dist/cjs/hooks/index.d.ts +1 -0
  50. package/dist/cjs/hooks/index.d.ts.map +1 -1
  51. package/dist/cjs/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
  52. package/dist/cjs/index.js +4 -4
  53. package/dist/cjs/index.js.map +1 -1
  54. package/dist/esm/components/ActionBar/ActionBar.d.ts +26 -0
  55. package/dist/esm/components/ActionBar/ActionBar.d.ts.map +1 -0
  56. package/dist/esm/components/ActionBar/ActionBar.model.d.ts +49 -0
  57. package/dist/esm/components/ActionBar/ActionBar.model.d.ts.map +1 -0
  58. package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
  59. package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
  60. package/dist/esm/components/ActionBar/helpers.d.ts +22 -0
  61. package/dist/esm/components/ActionBar/helpers.d.ts.map +1 -0
  62. package/dist/esm/components/ActionBar/index.d.ts +3 -0
  63. package/dist/esm/components/ActionBar/index.d.ts.map +1 -0
  64. package/dist/esm/components/Actions/Action/Action.d.ts.map +1 -1
  65. package/dist/esm/components/ConfirmDialog/index.d.ts +1 -0
  66. package/dist/esm/components/ConfirmDialog/index.d.ts.map +1 -1
  67. package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
  68. package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
  69. package/dist/esm/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
  70. package/dist/esm/components/Explorer/Explorer.d.ts.map +1 -1
  71. package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
  72. package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
  73. package/dist/esm/components/Explorer/helpers/useActions.d.ts +15 -4
  74. package/dist/esm/components/Explorer/helpers/useActions.d.ts.map +1 -1
  75. package/dist/esm/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
  76. package/dist/esm/components/List/List.d.ts +17 -3
  77. package/dist/esm/components/List/List.d.ts.map +1 -1
  78. package/dist/esm/components/List/List.model.d.ts +5 -1
  79. package/dist/esm/components/List/List.model.d.ts.map +1 -1
  80. package/dist/esm/components/List/ListHeader/ListHeader.d.ts +10 -1
  81. package/dist/esm/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  82. package/dist/esm/components/List/ListRow/ListRow.d.ts +16 -5
  83. package/dist/esm/components/List/ListRow/ListRow.d.ts.map +1 -1
  84. package/dist/esm/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
  85. package/dist/esm/components/List/helpers.d.ts +11 -3
  86. package/dist/esm/components/List/helpers.d.ts.map +1 -1
  87. package/dist/esm/components/List/useColumnsSize.d.ts +4 -2
  88. package/dist/esm/components/List/useColumnsSize.d.ts.map +1 -1
  89. package/dist/esm/components/PageHeader/PageHeader.d.ts +4 -1
  90. package/dist/esm/components/PageHeader/PageHeader.d.ts.map +1 -1
  91. package/dist/esm/components/PageHeader/PageHeader.model.d.ts +37 -1
  92. package/dist/esm/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  93. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
  94. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  95. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
  96. package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
  97. package/dist/esm/components/PageHeader/helpers.d.ts +9 -0
  98. package/dist/esm/components/PageHeader/helpers.d.ts.map +1 -0
  99. package/dist/esm/components/index.d.ts +1 -0
  100. package/dist/esm/components/index.d.ts.map +1 -1
  101. package/dist/esm/hooks/index.d.ts +1 -0
  102. package/dist/esm/hooks/index.d.ts.map +1 -1
  103. package/dist/esm/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
  104. package/dist/esm/index.js +4 -4
  105. package/dist/esm/index.js.map +1 -1
  106. package/package.json +7 -3
  107. package/src/components/ActionBar/ActionBar.model.ts +61 -0
  108. package/src/components/ActionBar/ActionBar.module.scss +64 -0
  109. package/src/components/ActionBar/ActionBar.spec.tsx +398 -0
  110. package/src/components/ActionBar/ActionBar.stories.tsx +205 -0
  111. package/src/components/ActionBar/ActionBar.tsx +170 -0
  112. package/src/components/ActionBar/ActionBarButton/ActionBarButton.module.scss +140 -0
  113. package/src/components/ActionBar/ActionBarButton/ActionBarButton.tsx +193 -0
  114. package/src/components/ActionBar/helpers.spec.ts +41 -0
  115. package/src/components/ActionBar/helpers.ts +54 -0
  116. package/src/components/ActionBar/index.ts +2 -0
  117. package/src/components/Actions/Action/Action.spec.tsx +64 -2
  118. package/src/components/Actions/Action/Action.tsx +6 -1
  119. package/src/components/ConfirmDialog/index.ts +1 -0
  120. package/src/components/ConfirmDialog/useConfirmableAction.tsx +159 -0
  121. package/src/components/EmptyStation/EmptyStation.stories.tsx +97 -0
  122. package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +2 -0
  123. package/src/components/Explorer/Explorer.spec.tsx +97 -8
  124. package/src/components/Explorer/Explorer.tsx +26 -13
  125. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +124 -1
  126. package/src/components/Explorer/QuickEdit/useQuickEdit.spec.tsx +94 -50
  127. package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +101 -42
  128. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +46 -13
  129. package/src/components/Explorer/helpers/useActions.ts +125 -73
  130. package/src/components/Explorer/helpers/useSubtitle.spec.tsx +33 -23
  131. package/src/components/Explorer/helpers/useSubtitle.tsx +18 -26
  132. package/src/components/List/List.model.ts +5 -1
  133. package/src/components/List/List.module.scss +25 -6
  134. package/src/components/List/List.spec.tsx +498 -1
  135. package/src/components/List/List.stories.tsx +137 -8
  136. package/src/components/List/List.tsx +305 -194
  137. package/src/components/List/ListHeader/ListHeader.module.scss +14 -0
  138. package/src/components/List/ListHeader/ListHeader.spec.tsx +1 -0
  139. package/src/components/List/ListHeader/ListHeader.tsx +25 -8
  140. package/src/components/List/ListRow/ListRow.module.scss +1 -1
  141. package/src/components/List/ListRow/ListRow.spec.tsx +109 -22
  142. package/src/components/List/ListRow/ListRow.tsx +52 -13
  143. package/src/components/List/ListRowRenderer/ListRowRenderer.tsx +28 -22
  144. package/src/components/List/helpers.spec.ts +29 -0
  145. package/src/components/List/helpers.ts +17 -9
  146. package/src/components/List/useColumnsSize.ts +17 -7
  147. package/src/components/PageHeader/PageHeader.model.ts +38 -1
  148. package/src/components/PageHeader/PageHeader.module.scss +39 -18
  149. package/src/components/PageHeader/PageHeader.spec.tsx +407 -18
  150. package/src/components/PageHeader/PageHeader.stories.tsx +95 -12
  151. package/src/components/PageHeader/PageHeader.tsx +89 -38
  152. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +8 -0
  153. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.module.scss +50 -42
  154. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.spec.tsx +193 -1
  155. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.stories.tsx +36 -0
  156. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +81 -130
  157. package/src/components/PageHeader/helpers.spec.ts +60 -0
  158. package/src/components/PageHeader/helpers.ts +36 -0
  159. package/src/components/index.ts +1 -0
  160. package/src/hooks/index.ts +1 -0
  161. package/src/styles/variables.scss +41 -7
  162. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
  163. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
  164. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
  165. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
  166. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
  167. package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
  168. package/dist/cjs/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
  169. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
  170. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
  171. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
  172. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
  173. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
  174. package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
  175. package/dist/esm/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
  176. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.module.scss +0 -77
  177. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +0 -122
  178. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +0 -235
  179. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +0 -15
  180. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +0 -37
  181. /package/dist/cjs/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
  182. /package/dist/esm/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
  183. /package/src/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.tsx +0 -0
@@ -1,12 +1,10 @@
1
1
  import type { LocationDescriptor } from 'history';
2
- import React, { useCallback, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import type { Data } from '../../../types';
4
+ import type { ActionBarData } from '../../ActionBar';
4
5
  import { IconName } from '../../Icons';
5
6
  import type { ListElement } from '../../List';
6
- import {
7
- type PageHeaderActionItemProps,
8
- PageHeaderActionType,
9
- } from '../../PageHeader';
7
+ import type { PageHeaderJsActionProps } from '../../PageHeader/PageHeaderAction';
10
8
  import type { QuickEditConfig, QuickEditRegistration } from '../Explorer.model';
11
9
  import { QuickEditContext } from './QuickEditContext';
12
10
 
@@ -18,17 +16,27 @@ export interface useQuickEditProps<T extends Data> {
18
16
  quickEditConfig?: QuickEditConfig;
19
17
  collapseFilters: () => void;
20
18
  generateItemLink?: (data: T) => LocationDescriptor<unknown>;
19
+ /**
20
+ * Called before quick edit opens, so that the consumer can put away whatever
21
+ * else is on display - only one set of actions can be shown at a time.
22
+ */
23
+ onBeforeOpen?: () => void;
21
24
  }
22
25
 
23
26
  export interface useQuickEditReturnType<T extends Data> {
24
27
  readonly isQuickEditMode: boolean;
25
- readonly quickEditAction?: PageHeaderActionItemProps;
28
+ /** Toggle of quick edit, to be rendered on the page header. */
29
+ readonly quickEditToggleAction?: PageHeaderJsActionProps;
30
+ /** The registrations to choose from, to be rendered on the action bar of the List. */
31
+ readonly quickEditActionBar?: ActionBarData;
26
32
  readonly QuickEditContextProvider: React.FC;
27
33
  readonly quickEditStation: JSX.Element | undefined;
28
34
  readonly changeSelectedItem: (
29
35
  item: T,
30
36
  detailsLink?: LocationDescriptor<unknown>,
31
37
  ) => Promise<void>;
38
+ /** Closes quick edit, saving whatever is pending. Does nothing if it is not open. */
39
+ readonly closeQuickEdit: () => Promise<void>;
32
40
  }
33
41
 
34
42
  export const useQuickEdit = <T extends Data>({
@@ -39,6 +47,7 @@ export const useQuickEdit = <T extends Data>({
39
47
  quickEditConfig,
40
48
  collapseFilters,
41
49
  generateItemLink,
50
+ onBeforeOpen,
42
51
  }: useQuickEditProps<T>): useQuickEditReturnType<T> => {
43
52
  const [isQuickEditMode, setIsQuickEditMode] = useState<boolean>(false);
44
53
  const [currentRegistration, setCurrentRegistration] =
@@ -103,45 +112,98 @@ export const useQuickEdit = <T extends Data>({
103
112
  [detailsLink, isQuickEditMode, registerSaveCallback, selectedItem],
104
113
  );
105
114
 
106
- const quickEditAction: PageHeaderActionItemProps | undefined = useMemo(
115
+ const closeQuickEdit = useCallback(async (): Promise<void> => {
116
+ if (!isQuickEditMode) {
117
+ return;
118
+ }
119
+
120
+ if (selectedItem) {
121
+ await saveCallbackRef.current?.();
122
+ setSelectedItem(defaultItem);
123
+ updateDetailsLink(defaultItem, registrations?.[0]);
124
+ setDetailsLink(undefined);
125
+ listRef.current?.resetSelection();
126
+ }
127
+
128
+ setCurrentRegistration(undefined);
129
+ setIsQuickEditMode(false);
130
+ }, [
131
+ defaultItem,
132
+ isQuickEditMode,
133
+ listRef,
134
+ registrations,
135
+ selectedItem,
136
+ updateDetailsLink,
137
+ ]);
138
+
139
+ const openQuickEdit = useCallback((): void => {
140
+ onBeforeOpen?.();
141
+
142
+ if (defaultItem) {
143
+ setSelectedItem(defaultItem);
144
+ updateDetailsLink(defaultItem, registrations?.[0]);
145
+ }
146
+
147
+ setIsQuickEditMode(true);
148
+ setCurrentRegistration(registrations?.[0]);
149
+ collapseFilters();
150
+ }, [
151
+ collapseFilters,
152
+ defaultItem,
153
+ onBeforeOpen,
154
+ registrations,
155
+ updateDetailsLink,
156
+ ]);
157
+
158
+ // Marks the item quick edit opens on in the list. The list drops its
159
+ // selection when it switches to single selection, so this runs as an effect,
160
+ // after the list has made the switch.
161
+ useEffect(() => {
162
+ if (isQuickEditMode) {
163
+ listRef.current?.selectIndex(0);
164
+ }
165
+ }, [isQuickEditMode, listRef]);
166
+
167
+ const hasRegistrations =
168
+ registrations !== undefined && registrations.length > 0;
169
+
170
+ const quickEditToggleAction: PageHeaderJsActionProps | undefined = useMemo(
107
171
  () =>
108
- registrations !== undefined && registrations.length > 0
172
+ hasRegistrations
109
173
  ? {
110
174
  label: quickEditConfig?.label ?? 'Quick View',
111
- kind: 'group',
112
175
  icon: quickEditConfig?.icon ?? IconName.QuickEdit,
113
176
  disabled: !hasData && !isQuickEditMode,
114
- openActionsGroupOnStart: isQuickEditMode,
115
- onActionsGroupToggled: async (isOpen) => {
116
- if (!isOpen && selectedItem) {
117
- await saveCallbackRef.current?.();
118
- setCurrentRegistration(undefined);
119
- setSelectedItem(defaultItem);
120
- updateDetailsLink(defaultItem, registrations[0]);
121
- setDetailsLink(undefined);
122
- setIsQuickEditMode(false);
123
- listRef.current?.resetSelection();
177
+ toggled: isQuickEditMode,
178
+ onClick: async () => {
179
+ if (isQuickEditMode) {
180
+ await closeQuickEdit();
124
181
  } else {
125
- if (defaultItem) {
126
- setSelectedItem(defaultItem);
127
- updateDetailsLink(defaultItem, registrations[0]);
128
- }
129
- setIsQuickEditMode(isOpen);
130
- setCurrentRegistration(isOpen ? registrations[0] : undefined);
131
- if (isOpen) {
132
- collapseFilters();
133
- listRef.current?.selectIndex(0);
134
- }
182
+ openQuickEdit();
135
183
  }
136
184
  },
137
- actions: registrations.map((registration) => ({
185
+ }
186
+ : undefined,
187
+ [
188
+ closeQuickEdit,
189
+ hasData,
190
+ hasRegistrations,
191
+ isQuickEditMode,
192
+ openQuickEdit,
193
+ quickEditConfig?.icon,
194
+ quickEditConfig?.label,
195
+ ],
196
+ );
197
+
198
+ const quickEditActionBar: ActionBarData | undefined = useMemo(
199
+ () =>
200
+ hasRegistrations && isQuickEditMode
201
+ ? {
202
+ actions: (registrations ?? []).map((registration) => ({
138
203
  label: registration.label,
139
204
  icon: registration.icon ?? IconName.QuickEditStation,
140
- actionType:
141
- isQuickEditMode &&
142
- currentRegistration?.component === registration.component
143
- ? PageHeaderActionType.Hightlight
144
- : PageHeaderActionType.Context,
205
+ toggled:
206
+ currentRegistration?.component === registration.component,
145
207
  onClick: async () => {
146
208
  await saveCallbackRef.current?.();
147
209
  setSelectedItem({ ...selectedItem });
@@ -152,14 +214,9 @@ export const useQuickEdit = <T extends Data>({
152
214
  }
153
215
  : undefined,
154
216
  [
155
- collapseFilters,
156
217
  currentRegistration?.component,
157
- defaultItem,
158
- hasData,
218
+ hasRegistrations,
159
219
  isQuickEditMode,
160
- listRef,
161
- quickEditConfig?.icon,
162
- quickEditConfig?.label,
163
220
  registrations,
164
221
  selectedItem,
165
222
  updateDetailsLink,
@@ -168,9 +225,11 @@ export const useQuickEdit = <T extends Data>({
168
225
 
169
226
  return {
170
227
  isQuickEditMode,
171
- quickEditAction,
228
+ quickEditToggleAction,
229
+ quickEditActionBar,
172
230
  QuickEditContextProvider,
173
231
  quickEditStation: currentRegistration?.component,
174
232
  changeSelectedItem,
233
+ closeQuickEdit,
175
234
  };
176
235
  };
@@ -1,4 +1,4 @@
1
- import { render, waitFor } from '@testing-library/react';
1
+ import { fireEvent, render, waitFor } from '@testing-library/react';
2
2
  import { noop } from 'lodash';
3
3
  import React from 'react';
4
4
  import { act } from 'react-dom/test-utils';
@@ -48,6 +48,17 @@ const getDataProvider = (): readonly [
48
48
  ] as const;
49
49
  };
50
50
 
51
+ /** Finds the label element of the action with the given text. */
52
+ const findActionLabel = (
53
+ container: HTMLElement,
54
+ label: string,
55
+ ): Element | undefined =>
56
+ Array.from(
57
+ container.querySelectorAll(
58
+ '[data-test-id="action"] [data-test-id="label"]',
59
+ ),
60
+ ).find((el) => el.textContent === label);
61
+
51
62
  describe('SelectionExplorer', () => {
52
63
  it('renders the component without crashing', () => {
53
64
  const [provider] = getDataProvider();
@@ -71,19 +82,46 @@ describe('SelectionExplorer', () => {
71
82
  dataProvider={provider}
72
83
  stationKey="mock-key"
73
84
  allowBulkSelect={true}
85
+ openBulkActionsOnStart={true}
74
86
  />,
75
87
  );
76
88
 
77
89
  await waitFor(() => {
78
- const applySelectionButton = Array.from(
79
- container.querySelectorAll(
80
- '[data-test-id="action"] [data-test-id="label"]',
81
- ),
82
- ).find((el) => el.textContent === 'Apply Selection');
90
+ const applySelectionButton = findActionLabel(
91
+ container,
92
+ 'Apply Selection',
93
+ );
83
94
  expect(applySelectionButton).toBeInTheDocument();
84
95
  });
85
96
  });
86
97
 
98
+ it(`shows the 'Apply Selection' bulk action once bulk actions are opened`, async () => {
99
+ const [provider] = getDataProvider();
100
+
101
+ const { container } = render(
102
+ <SelectionExplorer
103
+ columns={mockListColumns}
104
+ dataProvider={provider}
105
+ stationKey="mock-key"
106
+ allowBulkSelect={true}
107
+ />,
108
+ );
109
+
110
+ expect(findActionLabel(container, 'Apply Selection')).toBeUndefined();
111
+
112
+ await act(async () => {
113
+ fireEvent.click(
114
+ findActionLabel(container, 'Bulk Actions')?.closest(
115
+ 'button',
116
+ ) as HTMLButtonElement,
117
+ );
118
+ });
119
+
120
+ await waitFor(() => {
121
+ expect(findActionLabel(container, 'Apply Selection')).toBeInTheDocument();
122
+ });
123
+ });
124
+
87
125
  it(`creates no bulk action if 'allowBulkSelect' is false`, async () => {
88
126
  const [provider] = getDataProvider();
89
127
  const spy = vi.fn();
@@ -94,17 +132,12 @@ describe('SelectionExplorer', () => {
94
132
  dataProvider={provider}
95
133
  stationKey="mock-key"
96
134
  allowBulkSelect={false}
135
+ openBulkActionsOnStart={true}
97
136
  onSelection={spy}
98
137
  />,
99
138
  );
100
139
 
101
- const applySelectionButton = Array.from(
102
- container.querySelectorAll(
103
- '[data-test-id="action"] [data-test-id="label"]',
104
- ),
105
- ).find((el) => el.textContent === 'Apply Selection');
106
-
107
- expect(applySelectionButton).toBeUndefined();
140
+ expect(findActionLabel(container, 'Apply Selection')).toBeUndefined();
108
141
  });
109
142
 
110
143
  it.todo('Does not call "showNotification" when "Apply Selection" is clicked');
@@ -1,7 +1,8 @@
1
- import { useMemo } from 'react';
1
+ import { useCallback, useMemo } from 'react';
2
2
  import { showNotification } from '../../../initialize';
3
3
  import type { Data } from '../../../types';
4
4
  import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
5
+ import type { ActionBarAction, ActionBarData } from '../../ActionBar';
5
6
  import { IconName } from '../../Icons';
6
7
  import type { ItemSelectEventArgs } from '../../List';
7
8
  import type { PageHeaderActionItemProps } from '../../PageHeader';
@@ -9,6 +10,7 @@ import {
9
10
  type PageHeaderJsActionProps,
10
11
  isPageHeaderNavigationAction,
11
12
  } from '../../PageHeader/PageHeaderAction';
13
+ import { toActionBarAction } from '../../PageHeader/helpers';
12
14
  import type { ErrorType } from '../../models';
13
15
  import type {
14
16
  ExplorerAction,
@@ -21,11 +23,16 @@ import type { StationMessage } from './useStationMessage';
21
23
  interface UseActionsProps<T extends Data> {
22
24
  actions?: ExplorerAction[];
23
25
  bulkActions?: ExplorerBulkAction<T>[];
24
- quickEditAction?: PageHeaderActionItemProps;
25
26
  bulkEditAction?: ExplorerBulkAction<T>;
26
- openBulkActionsOnStart: boolean | undefined;
27
+ /** Toggle of quick edit, rendered next to the toggle of the bulk actions */
28
+ quickEditToggleAction?: PageHeaderJsActionProps;
29
+ /** Actions of quick edit, shown on the action bar while quick edit is open */
30
+ quickEditActionBar?: ActionBarData;
31
+ /** Text the action bar shows next to the actions, e.g. the result counts */
32
+ actionBarLabel?: string;
33
+ /** Whether the bulk actions are currently on display */
34
+ isBulkOpen: boolean;
27
35
  resultCount: ResultCounts;
28
- activeFilterCount: number;
29
36
  itemSelection: ItemSelectEventArgs<T>;
30
37
  getBulkActionSelection: () => ItemSelection<T>;
31
38
  onReloadData: () => void;
@@ -35,10 +42,14 @@ interface UseActionsProps<T extends Data> {
35
42
  onBulkActionsToggled: (expanded: boolean) => void;
36
43
  setIsBulkOpen: (value: React.SetStateAction<boolean>) => void;
37
44
  closeBulkEdit: () => void;
45
+ closeQuickEdit: () => Promise<void>;
38
46
  }
39
47
 
40
48
  interface UseActionsReturnType {
49
+ /** Actions of the page header, which includes the toggles of the action sets */
41
50
  readonly actions: PageHeaderActionItemProps[];
51
+ /** Content of the action bar of the List, for the context that is active */
52
+ readonly actionBar: ActionBarData;
42
53
  }
43
54
 
44
55
  export const useActions = <T extends Data>({
@@ -48,56 +59,60 @@ export const useActions = <T extends Data>({
48
59
  actions,
49
60
  itemSelection,
50
61
  onBulkActionsToggled,
51
- openBulkActionsOnStart,
62
+ isBulkOpen,
52
63
  resultCount,
53
64
  setIsBulkOpen,
54
- quickEditAction,
65
+ quickEditToggleAction,
66
+ quickEditActionBar,
67
+ actionBarLabel,
55
68
  bulkActions,
56
69
  closeBulkEdit,
70
+ closeQuickEdit,
57
71
  bulkEditAction,
58
72
  }: UseActionsProps<T>): UseActionsReturnType => {
59
- const bulkActionItems: PageHeaderJsActionProps[] = useMemo(
60
- () => [
61
- ...(bulkEditAction ? [bulkEditAction] : []),
62
- ...(bulkActions ?? []).map((action) => ({
63
- ...action,
64
- onClick: async () => {
65
- if (action.showStartedNotification !== false) {
66
- showNotification({
67
- title: `Bulk Action '${action.label}' Started`,
68
- });
69
- }
70
-
71
- try {
72
- const result = await action.onClick(getBulkActionSelection());
73
- if (result) {
74
- const message = errMsg(result);
73
+ const bulkActionItems: ActionBarAction[] = useMemo(
74
+ () =>
75
+ [
76
+ ...(bulkEditAction ? [bulkEditAction] : []),
77
+ ...(bulkActions ?? []).map((action) => ({
78
+ ...action,
79
+ onClick: async () => {
80
+ if (action.showStartedNotification !== false) {
75
81
  showNotification({
76
- title:
77
- typeof message.title === 'string'
78
- ? message.title
79
- : 'An error occurred',
80
- body: message.body,
81
- options: {
82
- type: 'error',
83
- },
82
+ title: `Bulk Action '${action.label}' Started`,
84
83
  });
85
- } else {
86
- if (action.reloadData) {
87
- onReloadData();
84
+ }
85
+
86
+ try {
87
+ const result = await action.onClick(getBulkActionSelection());
88
+ if (result) {
89
+ const message = errMsg(result);
90
+ showNotification({
91
+ title:
92
+ typeof message.title === 'string'
93
+ ? message.title
94
+ : 'An error occurred',
95
+ body: message.body,
96
+ options: {
97
+ type: 'error',
98
+ },
99
+ });
100
+ } else {
101
+ if (action.reloadData) {
102
+ onReloadData();
103
+ }
88
104
  }
105
+ } catch (error) {
106
+ setStationMessage(
107
+ errMsg(
108
+ error,
109
+ 'An error occurred when trying to execute the bulk operation.',
110
+ ),
111
+ );
89
112
  }
90
- } catch (error) {
91
- setStationMessage(
92
- errMsg(
93
- error,
94
- 'An error occurred when trying to execute the bulk operation.',
95
- ),
96
- );
97
- }
98
- },
99
- })),
100
- ],
113
+ },
114
+ })),
115
+ ].map(toActionBarAction),
101
116
  [
102
117
  bulkActions,
103
118
  bulkEditAction,
@@ -107,40 +122,80 @@ export const useActions = <T extends Data>({
107
122
  ],
108
123
  );
109
124
 
125
+ const toggleBulkActions = useCallback(async (): Promise<void> => {
126
+ const isOpening = !isBulkOpen;
127
+
128
+ if (isOpening) {
129
+ // Only one set of actions can be on display, so quick edit gives way.
130
+ await closeQuickEdit();
131
+ } else {
132
+ closeBulkEdit();
133
+ }
134
+
135
+ setIsBulkOpen(isOpening);
136
+
137
+ try {
138
+ await onBulkActionsToggled(isOpening);
139
+ } catch {
140
+ // The consumer is expected to surface a problem of its own handler.
141
+ }
142
+ }, [
143
+ closeBulkEdit,
144
+ closeQuickEdit,
145
+ isBulkOpen,
146
+ onBulkActionsToggled,
147
+ setIsBulkOpen,
148
+ ]);
149
+
150
+ const actionBar: ActionBarData = useMemo(() => {
151
+ // The bar reports the results at all times, only the actions on it change
152
+ // with the context that is active.
153
+ if (isBulkOpen && bulkActionItems.length > 0) {
154
+ // Bulk actions apply to the selection, so they are unavailable while
155
+ // there is nothing to apply them to.
156
+ const noSelection =
157
+ itemSelection.items?.length === 0 || resultCount?.filtered === 0;
158
+
159
+ return {
160
+ label: actionBarLabel,
161
+ actions: bulkActionItems.map((action) => ({
162
+ ...action,
163
+ disabled: action.disabled || noSelection,
164
+ })),
165
+ };
166
+ }
167
+
168
+ return {
169
+ label: actionBarLabel,
170
+ actions: quickEditActionBar?.actions ?? [],
171
+ };
172
+ }, [
173
+ actionBarLabel,
174
+ bulkActionItems,
175
+ isBulkOpen,
176
+ itemSelection,
177
+ quickEditActionBar,
178
+ resultCount,
179
+ ]);
180
+
110
181
  const pageHeaderActions: PageHeaderActionItemProps[] = useMemo(() => {
111
182
  const headerActions: PageHeaderActionItemProps[] = [];
112
183
 
113
184
  if (bulkActionItems.length > 0) {
114
- headerActions.push({ kind: 'spacer' });
115
-
116
185
  headerActions.push({
186
+ kind: 'action',
117
187
  label: 'Bulk Actions',
118
188
  icon: IconName.Bulk,
119
- kind: 'group',
120
- actions: bulkActionItems,
121
- openActionsGroupOnStart: openBulkActionsOnStart,
122
- onActionsGroupToggled: async (isOpen) => {
123
- setIsBulkOpen(isOpen);
124
- onBulkActionsToggled(isOpen);
125
-
126
- if (!isOpen) {
127
- closeBulkEdit();
128
- }
129
- },
130
- groupActionsDisabled:
131
- itemSelection.items?.length === 0 || resultCount?.filtered === 0,
189
+ toggled: isBulkOpen,
190
+ onClick: toggleBulkActions,
132
191
  });
133
192
  }
134
193
 
135
- if (quickEditAction) {
136
- headerActions.push({ kind: 'spacer' });
137
-
138
- headerActions.push(quickEditAction);
194
+ if (quickEditToggleAction) {
195
+ headerActions.push({ ...quickEditToggleAction, kind: 'action' });
139
196
  }
140
197
 
141
198
  if (actions && actions.length > 0) {
142
- headerActions.push({ kind: 'spacer' });
143
-
144
199
  actions?.forEach((action) => {
145
200
  headerActions.push({
146
201
  ...(isPageHeaderNavigationAction(action)
@@ -173,20 +228,17 @@ export const useActions = <T extends Data>({
173
228
  return headerActions;
174
229
  }, [
175
230
  actions,
176
- bulkActionItems,
177
- closeBulkEdit,
178
- itemSelection.items?.length,
179
- onBulkActionsToggled,
231
+ bulkActionItems.length,
232
+ isBulkOpen,
180
233
  onReloadData,
181
- openBulkActionsOnStart,
182
- quickEditAction,
183
- resultCount?.filtered,
184
- setIsBulkOpen,
234
+ quickEditToggleAction,
185
235
  setStationMessage,
236
+ toggleBulkActions,
186
237
  ]);
187
238
 
188
239
  return {
189
240
  actions: pageHeaderActions,
241
+ actionBar,
190
242
  };
191
243
  };
192
244