@axinom/mosaic-ui 0.51.0-rc.9 → 0.52.0-rc.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 (100) hide show
  1. package/dist/components/Explorer/ConditionalSplit/ConditionalSplit.d.ts +8 -0
  2. package/dist/components/Explorer/ConditionalSplit/ConditionalSplit.d.ts.map +1 -0
  3. package/dist/components/Explorer/Explorer.d.ts +3 -1
  4. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  5. package/dist/components/Explorer/Explorer.model.d.ts +18 -1
  6. package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
  7. package/dist/components/Explorer/QuickEdit/QuickEditContext.d.ts +11 -0
  8. package/dist/components/Explorer/QuickEdit/QuickEditContext.d.ts.map +1 -0
  9. package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts +22 -0
  10. package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -0
  11. package/dist/components/Explorer/{InMemoryDataProvider.d.ts → helpers/InMemoryDataProvider.d.ts} +3 -3
  12. package/dist/components/Explorer/helpers/InMemoryDataProvider.d.ts.map +1 -0
  13. package/dist/components/Explorer/helpers/useActions.d.ts +31 -0
  14. package/dist/components/Explorer/helpers/useActions.d.ts.map +1 -0
  15. package/dist/components/Explorer/{useDataProvider.d.ts → helpers/useDataProvider.d.ts} +6 -6
  16. package/dist/components/Explorer/helpers/useDataProvider.d.ts.map +1 -0
  17. package/dist/components/Explorer/helpers/useFilters.d.ts +21 -0
  18. package/dist/components/Explorer/helpers/useFilters.d.ts.map +1 -0
  19. package/dist/components/Explorer/helpers/useStationMessage.d.ts +17 -0
  20. package/dist/components/Explorer/helpers/useStationMessage.d.ts.map +1 -0
  21. package/dist/components/Explorer/index.d.ts +2 -1
  22. package/dist/components/Explorer/index.d.ts.map +1 -1
  23. package/dist/components/FormStation/Create/Create.d.ts.map +1 -1
  24. package/dist/components/FormStation/FormStation.d.ts +4 -1
  25. package/dist/components/FormStation/FormStation.d.ts.map +1 -1
  26. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +1 -0
  27. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  28. package/dist/components/FormStation/SaveOnDemand/SaveOnDemand.d.ts +11 -0
  29. package/dist/components/FormStation/SaveOnDemand/SaveOnDemand.d.ts.map +1 -0
  30. package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -1
  31. package/dist/components/Icons/Icons.d.ts.map +1 -1
  32. package/dist/components/Icons/Icons.models.d.ts +28 -24
  33. package/dist/components/Icons/Icons.models.d.ts.map +1 -1
  34. package/dist/components/List/List.d.ts +1 -1
  35. package/dist/components/List/List.d.ts.map +1 -1
  36. package/dist/components/List/List.model.d.ts +4 -0
  37. package/dist/components/List/List.model.d.ts.map +1 -1
  38. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  39. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  40. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +2 -1
  41. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
  42. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +1 -1
  43. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -1
  44. package/dist/components/PageHeader/helpers/useElementWidthObserver.d.ts +6 -0
  45. package/dist/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +1 -0
  46. package/dist/index.es.js +4 -4
  47. package/dist/index.es.js.map +1 -1
  48. package/dist/index.js +4 -4
  49. package/dist/index.js.map +1 -1
  50. package/dist/initialize.d.ts +1 -1
  51. package/dist/initialize.d.ts.map +1 -1
  52. package/package.json +4 -3
  53. package/src/components/EmptyStation/EmptyStation.spec.tsx +24 -0
  54. package/src/components/Explorer/ConditionalSplit/ConditionalSplit.tsx +23 -0
  55. package/src/components/Explorer/Explorer.model.ts +19 -1
  56. package/src/components/Explorer/Explorer.scss +4 -0
  57. package/src/components/Explorer/Explorer.spec.tsx +28 -3
  58. package/src/components/Explorer/Explorer.stories.tsx +90 -5
  59. package/src/components/Explorer/Explorer.tsx +149 -185
  60. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +26 -0
  61. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +2 -2
  62. package/src/components/Explorer/QuickEdit/QuickEditContext.tsx +16 -0
  63. package/src/components/Explorer/QuickEdit/useQuickEdit.spec.tsx +461 -0
  64. package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +169 -0
  65. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +6 -0
  66. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.stories.tsx +2 -2
  67. package/src/components/Explorer/{InMemoryDataProvider.ts → helpers/InMemoryDataProvider.ts} +4 -4
  68. package/src/components/Explorer/helpers/useActions.ts +203 -0
  69. package/src/components/Explorer/{useDataProvider.tsx → helpers/useDataProvider.tsx} +11 -11
  70. package/src/components/Explorer/helpers/useFilters.tsx +77 -0
  71. package/src/components/Explorer/{useStationMessage.tsx → helpers/useStationMessage.tsx} +8 -6
  72. package/src/components/Explorer/index.ts +10 -6
  73. package/src/components/FormStation/Create/Create.tsx +1 -0
  74. package/src/components/FormStation/FormStation.spec.tsx +62 -73
  75. package/src/components/FormStation/FormStation.tsx +31 -15
  76. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +38 -18
  77. package/src/components/FormStation/SaveOnDemand/SaveOnDemand.tsx +55 -0
  78. package/src/components/FormStation/helpers/useDataProvider.ts +1 -8
  79. package/src/components/Icons/Icons.models.ts +4 -0
  80. package/src/components/Icons/Icons.tsx +78 -0
  81. package/src/components/InlineMenu/InlineMenu.spec.tsx +18 -0
  82. package/src/components/List/List.model.ts +5 -0
  83. package/src/components/List/List.tsx +29 -5
  84. package/src/components/List/ListRow/ListRow.spec.tsx +0 -10
  85. package/src/components/List/ListRow/ListRow.tsx +1 -1
  86. package/src/components/PageHeader/PageHeader.scss +1 -2
  87. package/src/components/PageHeader/PageHeader.stories.tsx +6 -2
  88. package/src/components/PageHeader/PageHeader.tsx +10 -16
  89. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +1 -0
  90. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.scss +7 -0
  91. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +1 -0
  92. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +19 -7
  93. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +19 -12
  94. package/src/components/PageHeader/helpers/useElementWidthObserver.tsx +30 -0
  95. package/src/initialize.ts +2 -2
  96. package/dist/components/Explorer/InMemoryDataProvider.d.ts.map +0 -1
  97. package/dist/components/Explorer/useDataProvider.d.ts.map +0 -1
  98. package/dist/components/Explorer/useStationMessage.d.ts +0 -15
  99. package/dist/components/Explorer/useStationMessage.d.ts.map +0 -1
  100. /package/src/components/Explorer/{InMemoryDataProvider.spec.ts → helpers/InMemoryDataProvider.spec.ts} +0 -0
@@ -0,0 +1,461 @@
1
+ import { mount } from 'enzyme';
2
+ import React from 'react';
3
+ import { act } from 'react-dom/test-utils';
4
+ import { QuickEditRegistration } from '../Explorer.model';
5
+ import { QuickEditContext, QuickEditContextType } from './QuickEditContext';
6
+ import {
7
+ useQuickEdit,
8
+ useQuickEditProps,
9
+ useQuickEditReturnType,
10
+ } from './useQuickEdit';
11
+
12
+ const getTestWrapper = (
13
+ props: useQuickEditProps<any>,
14
+ ): {
15
+ TestWrapper: React.FC<{
16
+ quickEditValuesRef: React.MutableRefObject<useQuickEditReturnType<any>>;
17
+ }>;
18
+ ref: React.MutableRefObject<useQuickEditReturnType<any>>;
19
+ } => {
20
+ const ref = { current: {} as useQuickEditReturnType<any> };
21
+
22
+ const TestWrapper: React.FC<{
23
+ quickEditValuesRef: React.MutableRefObject<useQuickEditReturnType<any>>;
24
+ }> = ({ quickEditValuesRef, children }) => {
25
+ quickEditValuesRef.current = useQuickEdit(props);
26
+
27
+ return <>{children}</>;
28
+ };
29
+
30
+ return { TestWrapper, ref };
31
+ };
32
+
33
+ const getContextConsumer = () => {
34
+ const ref = { current: {} as QuickEditContextType<any> };
35
+
36
+ const ContextConsumer: React.FC<{
37
+ contextRef: React.MutableRefObject<QuickEditContextType<any>>;
38
+ }> = ({ contextRef }) => {
39
+ contextRef.current = React.useContext(QuickEditContext);
40
+ return <></>;
41
+ };
42
+
43
+ return { ContextConsumer, ref };
44
+ };
45
+
46
+ const mockListRef = {
47
+ current: { resetSelection: jest.fn(), selectIndex: jest.fn() },
48
+ };
49
+
50
+ const mockRegistrations: QuickEditRegistration<unknown>[] = [
51
+ {
52
+ label: 'Test 1',
53
+ generateDetailsLink: () => `/test`,
54
+ component: jest.fn() as any as JSX.Element,
55
+ },
56
+ {
57
+ label: 'Test 2',
58
+ generateDetailsLink: (item) => `/test?${JSON.stringify(item)}`,
59
+ component: jest.fn() as any as JSX.Element,
60
+ },
61
+ {
62
+ label: 'Test 3',
63
+ component: jest.fn() as any as JSX.Element,
64
+ },
65
+
66
+ {
67
+ label: 'Test 4',
68
+ generateDetailsLink: false,
69
+ component: jest.fn() as any as JSX.Element,
70
+ },
71
+ ];
72
+
73
+ describe('useQuickEdit', () => {
74
+ it('should return the quickEdit action if registrations are defined', () => {
75
+ const { TestWrapper, ref } = getTestWrapper({
76
+ listRef: mockListRef,
77
+ registrations: mockRegistrations,
78
+ collapseFilters: jest.fn(),
79
+ hasData: true,
80
+ generateItemLink: jest.fn(),
81
+ });
82
+
83
+ mount(<TestWrapper quickEditValuesRef={ref} />);
84
+
85
+ expect(ref.current.quickEditAction).toBeDefined();
86
+ });
87
+
88
+ it('should not return the quickEdit action if registrations are not defined', () => {
89
+ const { TestWrapper, ref } = getTestWrapper({
90
+ listRef: mockListRef,
91
+ registrations: undefined,
92
+ collapseFilters: jest.fn(),
93
+ hasData: true,
94
+ generateItemLink: jest.fn(),
95
+ });
96
+
97
+ mount(<TestWrapper quickEditValuesRef={ref} />);
98
+
99
+ expect(ref.current.quickEditAction).toBeUndefined();
100
+ });
101
+
102
+ it('should generate actions for each registration', () => {
103
+ const { TestWrapper, ref } = getTestWrapper({
104
+ listRef: mockListRef,
105
+ registrations: mockRegistrations,
106
+ collapseFilters: jest.fn(),
107
+ hasData: true,
108
+ generateItemLink: jest.fn(),
109
+ });
110
+
111
+ mount(<TestWrapper quickEditValuesRef={ref} />);
112
+
113
+ expect(ref.current.quickEditAction?.kind).toBe('group');
114
+ expect(
115
+ ref.current.quickEditAction?.kind === 'group' &&
116
+ ref.current.quickEditAction.actions,
117
+ ).toHaveLength(mockRegistrations.length);
118
+ });
119
+
120
+ it('should set isQuickEditMode to true and collapse filters when quickEditAction is opened', async () => {
121
+ const collapseFiltersSpy = jest.fn();
122
+ const { TestWrapper, ref } = getTestWrapper({
123
+ listRef: mockListRef,
124
+ registrations: mockRegistrations,
125
+ collapseFilters: collapseFiltersSpy,
126
+ hasData: true,
127
+ generateItemLink: jest.fn(),
128
+ });
129
+
130
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
131
+
132
+ await act(async () => {
133
+ ref.current.quickEditAction?.kind === 'group' &&
134
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
135
+ });
136
+
137
+ wrapper.update();
138
+
139
+ expect(ref.current.isQuickEditMode).toBe(true);
140
+ expect(collapseFiltersSpy).toHaveBeenCalled();
141
+ });
142
+
143
+ it('should set the first registration as the current registration when quickEditAction is clicked', async () => {
144
+ const { TestWrapper, ref } = getTestWrapper({
145
+ listRef: mockListRef,
146
+ registrations: mockRegistrations,
147
+ collapseFilters: jest.fn(),
148
+ hasData: true,
149
+ generateItemLink: jest.fn(),
150
+ });
151
+
152
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
153
+
154
+ await act(async () => {
155
+ ref.current.quickEditAction?.kind === 'group' &&
156
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
157
+ });
158
+
159
+ wrapper.update();
160
+
161
+ expect(ref.current.quickEditStation).toBe(mockRegistrations[0].component);
162
+ });
163
+
164
+ it('should update the registration when a registration action is clicked', async () => {
165
+ const { TestWrapper, ref } = getTestWrapper({
166
+ listRef: mockListRef,
167
+ registrations: mockRegistrations,
168
+ collapseFilters: jest.fn(),
169
+ hasData: true,
170
+ generateItemLink: jest.fn(),
171
+ });
172
+
173
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
174
+
175
+ await act(async () => {
176
+ ref.current.quickEditAction?.kind === 'group' &&
177
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
178
+ });
179
+
180
+ wrapper.update();
181
+
182
+ await act(async () => {
183
+ ref.current.quickEditAction?.kind === 'group' &&
184
+ ref.current.quickEditAction.actions[1].onClick?.();
185
+ });
186
+
187
+ expect(ref.current.quickEditStation).toBe(mockRegistrations[1].component);
188
+ });
189
+
190
+ it('should disable the quickEditAction when there is no data', () => {
191
+ const { TestWrapper, ref } = getTestWrapper({
192
+ listRef: mockListRef,
193
+ registrations: mockRegistrations,
194
+ collapseFilters: jest.fn(),
195
+ hasData: false,
196
+ generateItemLink: jest.fn(),
197
+ });
198
+
199
+ mount(<TestWrapper quickEditValuesRef={ref} />);
200
+
201
+ expect(
202
+ ref.current.quickEditAction?.kind === 'group' &&
203
+ ref.current.quickEditAction.disabled,
204
+ ).toBe(true);
205
+ });
206
+
207
+ describe('List Actions', () => {
208
+ it('should select the first item when quick edit is opened', async () => {
209
+ const { TestWrapper, ref } = getTestWrapper({
210
+ listRef: mockListRef,
211
+ registrations: mockRegistrations,
212
+ collapseFilters: jest.fn(),
213
+ hasData: true,
214
+ generateItemLink: jest.fn(),
215
+ });
216
+
217
+ mount(<TestWrapper quickEditValuesRef={ref} />);
218
+
219
+ await act(async () => {
220
+ ref.current.quickEditAction?.kind === 'group' &&
221
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
222
+ });
223
+
224
+ expect(mockListRef.current.selectIndex).toHaveBeenCalledWith(0);
225
+ });
226
+
227
+ it('should reset the selection when quick edit is closed', async () => {
228
+ const { TestWrapper, ref } = getTestWrapper({
229
+ listRef: mockListRef,
230
+ registrations: mockRegistrations,
231
+ collapseFilters: jest.fn(),
232
+ hasData: true,
233
+ generateItemLink: jest.fn(),
234
+ });
235
+
236
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
237
+
238
+ await act(async () => {
239
+ ref.current.quickEditAction?.kind === 'group' &&
240
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
241
+ });
242
+
243
+ wrapper.update();
244
+
245
+ await act(async () => {
246
+ ref.current.quickEditAction?.kind === 'group' &&
247
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(false));
248
+ });
249
+
250
+ expect(mockListRef.current.resetSelection).toHaveBeenCalled();
251
+ });
252
+ });
253
+
254
+ describe('QuickEditContextProvider', () => {
255
+ it('should provide the correct context values', async () => {
256
+ const { TestWrapper, ref } = getTestWrapper({
257
+ listRef: mockListRef,
258
+ registrations: mockRegistrations,
259
+ collapseFilters: jest.fn(),
260
+ hasData: true,
261
+ generateItemLink: jest.fn(),
262
+ });
263
+
264
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
265
+
266
+ await act(async () => {
267
+ ref.current.quickEditAction?.kind === 'group' &&
268
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
269
+
270
+ ref.current.changeSelectedItem({ id: 1 });
271
+ });
272
+
273
+ wrapper.update();
274
+
275
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
276
+
277
+ mount(
278
+ <ref.current.QuickEditContextProvider>
279
+ <ContextConsumer contextRef={contextRef} />
280
+ </ref.current.QuickEditContextProvider>,
281
+ );
282
+
283
+ expect(contextRef.current.isQuickEditMode).toBe(true);
284
+ expect(contextRef.current.selectedItem).toEqual({ id: 1 });
285
+ expect(contextRef.current.detailsLink).toBe('/test');
286
+ });
287
+
288
+ it('should call the save callback when changeSelectedItem is called', async () => {
289
+ const { TestWrapper, ref } = getTestWrapper({
290
+ listRef: mockListRef,
291
+ registrations: mockRegistrations,
292
+ collapseFilters: jest.fn(),
293
+ hasData: true,
294
+ generateItemLink: jest.fn(),
295
+ });
296
+
297
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
298
+
299
+ await act(async () => {
300
+ ref.current.quickEditAction?.kind === 'group' &&
301
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
302
+
303
+ ref.current.changeSelectedItem({ id: 1 });
304
+ });
305
+
306
+ wrapper.update();
307
+
308
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
309
+
310
+ mount(
311
+ <ref.current.QuickEditContextProvider>
312
+ <ContextConsumer contextRef={contextRef} />
313
+ </ref.current.QuickEditContextProvider>,
314
+ );
315
+
316
+ const saveCallbackSpy = jest.fn();
317
+
318
+ await act(async () => {
319
+ contextRef.current.registerSaveCallback?.(saveCallbackSpy);
320
+
321
+ ref.current.changeSelectedItem({ id: 1 });
322
+ });
323
+
324
+ expect(saveCallbackSpy).toHaveBeenCalled();
325
+ });
326
+
327
+ it('should call the save callback when refresh is called', async () => {
328
+ const { TestWrapper, ref } = getTestWrapper({
329
+ listRef: mockListRef,
330
+ registrations: mockRegistrations,
331
+ collapseFilters: jest.fn(),
332
+ hasData: true,
333
+ generateItemLink: jest.fn(),
334
+ });
335
+
336
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
337
+
338
+ await act(async () => {
339
+ ref.current.quickEditAction?.kind === 'group' &&
340
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
341
+
342
+ ref.current.changeSelectedItem({ id: 1 });
343
+ });
344
+
345
+ wrapper.update();
346
+
347
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
348
+
349
+ mount(
350
+ <ref.current.QuickEditContextProvider>
351
+ <ContextConsumer contextRef={contextRef} />
352
+ </ref.current.QuickEditContextProvider>,
353
+ );
354
+
355
+ const saveCallbackSpy = jest.fn();
356
+
357
+ await act(async () => {
358
+ contextRef.current.registerSaveCallback?.(saveCallbackSpy);
359
+
360
+ contextRef.current.refresh?.();
361
+ });
362
+
363
+ expect(saveCallbackSpy).toHaveBeenCalled();
364
+ });
365
+
366
+ it('should generate the correct details link using registration if provided', async () => {
367
+ const { TestWrapper, ref } = getTestWrapper({
368
+ listRef: mockListRef,
369
+ registrations: [mockRegistrations[1]],
370
+ collapseFilters: jest.fn(),
371
+ hasData: true,
372
+ generateItemLink: jest.fn(),
373
+ });
374
+
375
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
376
+
377
+ await act(async () => {
378
+ ref.current.quickEditAction?.kind === 'group' &&
379
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
380
+
381
+ ref.current.changeSelectedItem({ id: 1 });
382
+ });
383
+
384
+ wrapper.update();
385
+
386
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
387
+
388
+ mount(
389
+ <ref.current.QuickEditContextProvider>
390
+ <ContextConsumer contextRef={contextRef} />
391
+ </ref.current.QuickEditContextProvider>,
392
+ );
393
+
394
+ expect(contextRef.current.detailsLink).toBe('/test?{"id":1}');
395
+ });
396
+
397
+ it('should generate the correct details link using generateItemLink prop if not provided in registration', async () => {
398
+ const generateDetailsLinkSpy = jest.fn();
399
+
400
+ const { TestWrapper, ref } = getTestWrapper({
401
+ listRef: mockListRef,
402
+ registrations: [mockRegistrations[2]],
403
+ collapseFilters: jest.fn(),
404
+ hasData: true,
405
+ generateItemLink: generateDetailsLinkSpy,
406
+ });
407
+
408
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
409
+
410
+ await act(async () => {
411
+ ref.current.quickEditAction?.kind === 'group' &&
412
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
413
+
414
+ ref.current.changeSelectedItem({ id: 1 });
415
+ });
416
+
417
+ wrapper.update();
418
+
419
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
420
+
421
+ mount(
422
+ <ref.current.QuickEditContextProvider>
423
+ <ContextConsumer contextRef={contextRef} />
424
+ </ref.current.QuickEditContextProvider>,
425
+ );
426
+
427
+ expect(generateDetailsLinkSpy).toHaveBeenCalledWith({ id: 1 });
428
+ });
429
+
430
+ it('should not generate a details link if generateDetailsLink in registration is false', async () => {
431
+ const { TestWrapper, ref } = getTestWrapper({
432
+ listRef: mockListRef,
433
+ registrations: [mockRegistrations[3]],
434
+ collapseFilters: jest.fn(),
435
+ hasData: true,
436
+ generateItemLink: jest.fn(),
437
+ });
438
+
439
+ const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
440
+
441
+ await act(async () => {
442
+ ref.current.quickEditAction?.kind === 'group' &&
443
+ (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
444
+
445
+ ref.current.changeSelectedItem({ id: 1 });
446
+ });
447
+
448
+ wrapper.update();
449
+
450
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
451
+
452
+ mount(
453
+ <ref.current.QuickEditContextProvider>
454
+ <ContextConsumer contextRef={contextRef} />
455
+ </ref.current.QuickEditContextProvider>,
456
+ );
457
+
458
+ expect(contextRef.current.detailsLink).toBeUndefined();
459
+ });
460
+ });
461
+ });
@@ -0,0 +1,169 @@
1
+ import { LocationDescriptor } from 'history';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { Data } from '../../../types';
4
+ import { IconName } from '../../Icons';
5
+ import { ListElement } from '../../List';
6
+ import {
7
+ PageHeaderActionItemProps,
8
+ PageHeaderActionType,
9
+ } from '../../PageHeader';
10
+ import { QuickEditRegistration } from '../Explorer.model';
11
+ import { QuickEditContext } from './QuickEditContext';
12
+
13
+ export interface useQuickEditProps<T extends Data> {
14
+ listRef: React.RefObject<ListElement>;
15
+ registrations: QuickEditRegistration<T>[] | undefined;
16
+ hasData: boolean;
17
+ collapseFilters: () => void;
18
+ generateItemLink?: (data: T) => LocationDescriptor<unknown>;
19
+ }
20
+
21
+ export interface useQuickEditReturnType<T extends Data> {
22
+ readonly isQuickEditMode: boolean;
23
+ readonly quickEditAction?: PageHeaderActionItemProps;
24
+ readonly QuickEditContextProvider: React.FC;
25
+ readonly quickEditStation: JSX.Element | undefined;
26
+ readonly changeSelectedItem: (
27
+ item: T,
28
+ detailsLink?: LocationDescriptor<unknown>,
29
+ ) => Promise<void>;
30
+ }
31
+
32
+ export const useQuickEdit = <T extends Data>({
33
+ hasData,
34
+ listRef,
35
+ registrations,
36
+ collapseFilters,
37
+ generateItemLink,
38
+ }: useQuickEditProps<T>): useQuickEditReturnType<T> => {
39
+ const [isQuickEditMode, setIsQuickEditMode] = useState<boolean>(false);
40
+ const [currentRegistration, setCurrentRegistration] =
41
+ useState<QuickEditRegistration<T>>();
42
+ const [selectedItem, setSelectedItem] = useState<T>();
43
+ const [detailsLink, setDetailsLink] = useState<LocationDescriptor<unknown>>();
44
+
45
+ const updateDetailsLink = useCallback(
46
+ (item?: T, registration?: QuickEditRegistration<T>) => {
47
+ if (registration?.generateDetailsLink !== false && item) {
48
+ setDetailsLink(
49
+ registration?.generateDetailsLink?.(item) ?? generateItemLink?.(item),
50
+ );
51
+ } else {
52
+ setDetailsLink(undefined);
53
+ }
54
+ },
55
+ [generateItemLink],
56
+ );
57
+
58
+ const changeSelectedItem = useCallback(
59
+ async (item: T): Promise<void> => {
60
+ if (selectedItem) {
61
+ await saveCallbackRef.current?.();
62
+ }
63
+ setSelectedItem({ ...item });
64
+ updateDetailsLink(item, currentRegistration);
65
+ },
66
+ [currentRegistration, selectedItem, updateDetailsLink],
67
+ );
68
+
69
+ useEffect(() => {
70
+ if (!isQuickEditMode) {
71
+ listRef.current?.resetSelection();
72
+ }
73
+ }, [isQuickEditMode, listRef]);
74
+
75
+ useEffect(() => {
76
+ if (isQuickEditMode) {
77
+ listRef.current?.selectIndex(0);
78
+ }
79
+ }, [isQuickEditMode, listRef]);
80
+
81
+ const saveCallbackRef = React.useRef<() => Promise<void>>();
82
+
83
+ const registerSaveCallback = useCallback(
84
+ (callback: () => Promise<void>): void => {
85
+ saveCallbackRef.current = callback;
86
+ },
87
+ [],
88
+ );
89
+
90
+ const QuickEditContextProvider: React.FC = useCallback(
91
+ ({ children }) => {
92
+ return (
93
+ <QuickEditContext.Provider
94
+ value={{
95
+ selectedItem,
96
+ registerSaveCallback,
97
+ isQuickEditMode,
98
+ detailsLink,
99
+ refresh: async () => {
100
+ if (selectedItem) {
101
+ await saveCallbackRef.current?.();
102
+ setSelectedItem({ ...selectedItem });
103
+ }
104
+ },
105
+ }}
106
+ >
107
+ {children}
108
+ </QuickEditContext.Provider>
109
+ );
110
+ },
111
+ [detailsLink, isQuickEditMode, registerSaveCallback, selectedItem],
112
+ );
113
+
114
+ const quickEditAction: PageHeaderActionItemProps | undefined = useMemo(
115
+ () =>
116
+ registrations !== undefined && registrations.length > 0
117
+ ? {
118
+ label: 'Quick Edit',
119
+ kind: 'group',
120
+ icon: IconName.QuickEdit,
121
+ disabled: !hasData && !isQuickEditMode,
122
+ openActionsGroupOnStart: isQuickEditMode,
123
+ onActionsGroupToggled: async (isOpen) => {
124
+ if (!isOpen && selectedItem) {
125
+ await saveCallbackRef.current?.();
126
+ setSelectedItem(undefined);
127
+ setDetailsLink(undefined);
128
+ setIsQuickEditMode(false);
129
+ } else {
130
+ setIsQuickEditMode(isOpen);
131
+ setCurrentRegistration(isOpen ? registrations[0] : undefined);
132
+ collapseFilters();
133
+ }
134
+ },
135
+ actions: registrations.map((registration) => ({
136
+ label: registration.label,
137
+ icon: registration.icon ?? IconName.QuickEditStation,
138
+ actionType:
139
+ isQuickEditMode &&
140
+ currentRegistration?.component === registration.component
141
+ ? PageHeaderActionType.Hightlight
142
+ : PageHeaderActionType.Context,
143
+ onClick: async () => {
144
+ await saveCallbackRef.current?.();
145
+ setCurrentRegistration(registration);
146
+ updateDetailsLink(selectedItem, registration);
147
+ },
148
+ })),
149
+ }
150
+ : undefined,
151
+ [
152
+ collapseFilters,
153
+ currentRegistration?.component,
154
+ hasData,
155
+ isQuickEditMode,
156
+ registrations,
157
+ selectedItem,
158
+ updateDetailsLink,
159
+ ],
160
+ );
161
+
162
+ return {
163
+ isQuickEditMode,
164
+ quickEditAction,
165
+ QuickEditContextProvider,
166
+ quickEditStation: currentRegistration?.component,
167
+ changeSelectedItem,
168
+ };
169
+ };
@@ -51,6 +51,12 @@ const getDataProvider = (): readonly [
51
51
  };
52
52
 
53
53
  describe('SelectionExplorer', () => {
54
+ global.ResizeObserver = jest.fn().mockImplementation(() => ({
55
+ observe: jest.fn(),
56
+ unobserve: jest.fn(),
57
+ disconnect: jest.fn(),
58
+ }));
59
+
54
60
  it('renders the component without crashing', () => {
55
61
  const [provider] = getDataProvider();
56
62
  const wrapper = shallow(
@@ -15,11 +15,11 @@ import {
15
15
  ExplorerDataProvider,
16
16
  ExplorerDataProviderConfiguration,
17
17
  } from '../Explorer.model';
18
+ import { SelectionExplorer } from '../SelectionExplorer/SelectionExplorer';
18
19
  import {
19
20
  createInMemoryDataProvider,
20
21
  findAnywhereInStringCaseInsensitive,
21
- } from '../InMemoryDataProvider';
22
- import { SelectionExplorer } from '../SelectionExplorer/SelectionExplorer';
22
+ } from '../helpers/InMemoryDataProvider';
23
23
 
24
24
  interface SelectExplorerStoryData {
25
25
  id: number;
@@ -1,7 +1,7 @@
1
- import { Data } from '../../types/data';
2
- import { FilterValue, FilterValues } from '../Filters';
3
- import { SortData } from '../List';
4
- import { ExplorerDataProvider } from './Explorer.model';
1
+ import { Data } from '../../../types/data';
2
+ import { FilterValue, FilterValues } from '../../Filters';
3
+ import { SortData } from '../../List';
4
+ import { ExplorerDataProvider } from '../Explorer.model';
5
5
 
6
6
  type FilterFunction<T extends Data> = (
7
7
  value: unknown,