@axinom/mosaic-ui 0.52.0-rc.8 → 0.52.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.52.0-rc.8",
3
+ "version": "0.52.0",
4
4
  "description": "UI components for building Axinom Mosaic applications",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -32,7 +32,7 @@
32
32
  "build-storybook": "storybook build"
33
33
  },
34
34
  "dependencies": {
35
- "@axinom/mosaic-core": "^0.4.25-rc.8",
35
+ "@axinom/mosaic-core": "^0.4.25",
36
36
  "@faker-js/faker": "^7.4.0",
37
37
  "@geoffcox/react-splitter": "^2.1.2",
38
38
  "@popperjs/core": "^2.11.8",
@@ -106,5 +106,5 @@
106
106
  "publishConfig": {
107
107
  "access": "public"
108
108
  },
109
- "gitHead": "f307889eea9c4a4e4f196af828da5549f1a723c8"
109
+ "gitHead": "12200181d99bdf8e61f30597d113489b172c8a17"
110
110
  }
@@ -242,10 +242,9 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
242
242
  }
243
243
  }, [defaultSortOrder, globalStateOptions.sort, sortOrder, stationKey]);
244
244
 
245
- const onFilterChangedHandler = (filters: FilterValues<T>): void => {
246
- onFiltersChange(filters);
245
+ const onFilterChangedHandler = useCallback(() => {
247
246
  listRef.current?.resetSelection();
248
- };
247
+ }, []);
249
248
 
250
249
  const {
251
250
  activeFilters,
@@ -268,22 +267,20 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
268
267
  hasMoreData,
269
268
  onReloadData,
270
269
  onRequestMoreData,
271
- onFiltersChange,
272
270
  onSortChanged,
273
271
  } = useDataProvider<T>({
274
272
  dataProvider,
275
273
  explorerRef,
276
274
  setStationMessage,
277
275
  defaultSortOrder: sortOrder,
278
- filters: activeFilters,
276
+ activeFilters,
279
277
  keyProperty,
280
278
  });
281
279
 
282
280
  const {
283
281
  isQuickEditMode,
284
282
  quickEditAction,
285
- QuickEditContextProvider,
286
- quickEditStation,
283
+ QuickEditComponent,
287
284
  changeSelectedItem,
288
285
  } = useQuickEdit({
289
286
  listRef,
@@ -456,9 +453,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
456
453
  />
457
454
  </div>
458
455
  </div>
459
- {isQuickEditMode && (
460
- <QuickEditContextProvider>{quickEditStation}</QuickEditContextProvider>
461
- )}
456
+ {QuickEditComponent}
462
457
  </ConditionalSplit>
463
458
  );
464
459
  });
@@ -143,7 +143,9 @@ describe('useQuickEdit', () => {
143
143
 
144
144
  wrapper.update();
145
145
 
146
- expect(ref.current.quickEditStation).toBe(mockRegistrations[0].component);
146
+ expect(ref.current.QuickEditComponent.props.children).toBe(
147
+ mockRegistrations[0].component,
148
+ );
147
149
  });
148
150
 
149
151
  it('should update the registration when a registration action is clicked', async () => {
@@ -163,7 +165,9 @@ describe('useQuickEdit', () => {
163
165
  ref.current.quickEditAction.actions[1].onClick?.();
164
166
  });
165
167
 
166
- expect(ref.current.quickEditStation).toBe(mockRegistrations[1].component);
168
+ expect(ref.current.QuickEditComponent.props.children).toBe(
169
+ mockRegistrations[1].component,
170
+ );
167
171
  });
168
172
 
169
173
  it('should disable the quickEditAction when there is no data', () => {
@@ -216,8 +220,18 @@ describe('useQuickEdit', () => {
216
220
  });
217
221
 
218
222
  describe('QuickEditContextProvider', () => {
223
+ const { ContextConsumer, ref: contextRef } = getContextConsumer();
224
+
225
+ const props: useQuickEditProps<any> = {
226
+ ...defaultProps,
227
+ registrations: mockRegistrations.map((registration) => ({
228
+ ...registration,
229
+ component: <ContextConsumer contextRef={contextRef} />,
230
+ })),
231
+ };
232
+
219
233
  it('should select the default item when quick edit is opened', async () => {
220
- const { TestWrapper, ref } = getTestWrapper(defaultProps);
234
+ const { TestWrapper, ref } = getTestWrapper(props);
221
235
 
222
236
  const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
223
237
 
@@ -228,13 +242,7 @@ describe('useQuickEdit', () => {
228
242
 
229
243
  wrapper.update();
230
244
 
231
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
232
-
233
- mount(
234
- <ref.current.QuickEditContextProvider>
235
- <ContextConsumer contextRef={contextRef} />
236
- </ref.current.QuickEditContextProvider>,
237
- );
245
+ mount(<>{ref.current.QuickEditComponent}</>);
238
246
 
239
247
  expect(contextRef.current.isQuickEditMode).toBe(true);
240
248
  expect(contextRef.current.selectedItem).toEqual({ id: 0 });
@@ -242,26 +250,19 @@ describe('useQuickEdit', () => {
242
250
  });
243
251
 
244
252
  it('should provide the correct context values', async () => {
245
- const { TestWrapper, ref } = getTestWrapper(defaultProps);
253
+ const { TestWrapper, ref } = getTestWrapper(props);
246
254
 
247
255
  const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
248
256
 
249
257
  await act(async () => {
250
258
  ref.current.quickEditAction?.kind === 'group' &&
251
259
  (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
252
-
253
260
  ref.current.changeSelectedItem({ id: 1 });
254
261
  });
255
262
 
256
263
  wrapper.update();
257
264
 
258
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
259
-
260
- mount(
261
- <ref.current.QuickEditContextProvider>
262
- <ContextConsumer contextRef={contextRef} />
263
- </ref.current.QuickEditContextProvider>,
264
- );
265
+ mount(<>{ref.current.QuickEditComponent}</>);
265
266
 
266
267
  expect(contextRef.current.isQuickEditMode).toBe(true);
267
268
  expect(contextRef.current.selectedItem).toEqual({ id: 1 });
@@ -269,32 +270,24 @@ describe('useQuickEdit', () => {
269
270
  });
270
271
 
271
272
  it('should call the save callback when changeSelectedItem is called', async () => {
272
- const { TestWrapper, ref } = getTestWrapper(defaultProps);
273
+ const { TestWrapper, ref } = getTestWrapper(props);
273
274
 
274
275
  const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
275
276
 
276
277
  await act(async () => {
277
278
  ref.current.quickEditAction?.kind === 'group' &&
278
279
  (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
279
-
280
280
  ref.current.changeSelectedItem({ id: 1 });
281
281
  });
282
282
 
283
283
  wrapper.update();
284
284
 
285
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
286
-
287
- mount(
288
- <ref.current.QuickEditContextProvider>
289
- <ContextConsumer contextRef={contextRef} />
290
- </ref.current.QuickEditContextProvider>,
291
- );
285
+ mount(<>{ref.current.QuickEditComponent}</>);
292
286
 
293
287
  const saveCallbackSpy = jest.fn();
294
288
 
295
289
  await act(async () => {
296
290
  contextRef.current.registerSaveCallback?.(saveCallbackSpy);
297
-
298
291
  ref.current.changeSelectedItem({ id: 1 });
299
292
  });
300
293
 
@@ -302,32 +295,23 @@ describe('useQuickEdit', () => {
302
295
  });
303
296
 
304
297
  it('should call the save callback when refresh is called', async () => {
305
- const { TestWrapper, ref } = getTestWrapper(defaultProps);
298
+ const { TestWrapper, ref } = getTestWrapper(props);
306
299
 
307
300
  const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
308
301
 
309
302
  await act(async () => {
310
303
  ref.current.quickEditAction?.kind === 'group' &&
311
304
  (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
312
-
313
- ref.current.changeSelectedItem({ id: 1 });
314
305
  });
315
306
 
316
307
  wrapper.update();
317
308
 
318
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
319
-
320
- mount(
321
- <ref.current.QuickEditContextProvider>
322
- <ContextConsumer contextRef={contextRef} />
323
- </ref.current.QuickEditContextProvider>,
324
- );
309
+ mount(<>{ref.current.QuickEditComponent}</>);
325
310
 
326
311
  const saveCallbackSpy = jest.fn();
327
312
 
328
313
  await act(async () => {
329
314
  contextRef.current.registerSaveCallback?.(saveCallbackSpy);
330
-
331
315
  contextRef.current.refresh?.();
332
316
  });
333
317
 
@@ -335,10 +319,7 @@ describe('useQuickEdit', () => {
335
319
  });
336
320
 
337
321
  it('should generate the correct details link using registration if provided', async () => {
338
- const { TestWrapper, ref } = getTestWrapper({
339
- ...defaultProps,
340
- registrations: [mockRegistrations[1]],
341
- });
322
+ const { TestWrapper, ref } = getTestWrapper(props);
342
323
 
343
324
  const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
344
325
 
@@ -346,28 +327,22 @@ describe('useQuickEdit', () => {
346
327
  ref.current.quickEditAction?.kind === 'group' &&
347
328
  (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
348
329
 
349
- ref.current.changeSelectedItem({ id: 1 });
330
+ ref.current.quickEditAction?.kind === 'group' &&
331
+ ref.current.quickEditAction.actions[1].onClick?.();
350
332
  });
351
333
 
352
- wrapper.update();
334
+ mount(<>{ref.current.QuickEditComponent}</>);
353
335
 
354
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
355
-
356
- mount(
357
- <ref.current.QuickEditContextProvider>
358
- <ContextConsumer contextRef={contextRef} />
359
- </ref.current.QuickEditContextProvider>,
360
- );
336
+ wrapper.update();
361
337
 
362
- expect(contextRef.current.detailsLink).toBe('/test?{"id":1}');
338
+ expect(contextRef.current.detailsLink).toBe('/test?{"id":0}');
363
339
  });
364
340
 
365
341
  it('should generate the correct details link using generateItemLink prop if not provided in registration', async () => {
366
342
  const generateDetailsLinkSpy = jest.fn();
367
343
 
368
344
  const { TestWrapper, ref } = getTestWrapper({
369
- ...defaultProps,
370
- registrations: [mockRegistrations[2]],
345
+ ...props,
371
346
  generateItemLink: generateDetailsLinkSpy,
372
347
  });
373
348
 
@@ -377,26 +352,23 @@ describe('useQuickEdit', () => {
377
352
  ref.current.quickEditAction?.kind === 'group' &&
378
353
  (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
379
354
 
380
- ref.current.changeSelectedItem({ id: 1 });
355
+ ref.current.quickEditAction?.kind === 'group' &&
356
+ ref.current.quickEditAction.actions[2].onClick?.();
381
357
  });
382
358
 
383
- wrapper.update();
384
-
385
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
359
+ await wrapper.update();
386
360
 
387
- mount(
388
- <ref.current.QuickEditContextProvider>
389
- <ContextConsumer contextRef={contextRef} />
390
- </ref.current.QuickEditContextProvider>,
391
- );
361
+ mount(<>{ref.current.QuickEditComponent}</>);
392
362
 
393
- expect(generateDetailsLinkSpy).toHaveBeenCalledWith({ id: 1 });
363
+ expect(generateDetailsLinkSpy).toHaveBeenCalledWith({ id: 0 });
394
364
  });
395
365
 
396
366
  it('should not generate a details link if generateDetailsLink in registration is false', async () => {
367
+ const generateDetailsLinkSpy = jest.fn();
368
+
397
369
  const { TestWrapper, ref } = getTestWrapper({
398
- ...defaultProps,
399
- registrations: [mockRegistrations[3]],
370
+ ...props,
371
+ generateItemLink: generateDetailsLinkSpy,
400
372
  });
401
373
 
402
374
  const wrapper = mount(<TestWrapper quickEditValuesRef={ref} />);
@@ -405,19 +377,15 @@ describe('useQuickEdit', () => {
405
377
  ref.current.quickEditAction?.kind === 'group' &&
406
378
  (await ref.current.quickEditAction?.onActionsGroupToggled?.(true));
407
379
 
408
- ref.current.changeSelectedItem({ id: 1 });
380
+ ref.current.quickEditAction?.kind === 'group' &&
381
+ ref.current.quickEditAction.actions[3].onClick?.();
409
382
  });
410
383
 
411
384
  wrapper.update();
412
385
 
413
- const { ContextConsumer, ref: contextRef } = getContextConsumer();
414
-
415
- mount(
416
- <ref.current.QuickEditContextProvider>
417
- <ContextConsumer contextRef={contextRef} />
418
- </ref.current.QuickEditContextProvider>,
419
- );
386
+ mount(<>{ref.current.QuickEditComponent}</>);
420
387
 
388
+ expect(generateDetailsLinkSpy).not.toHaveBeenCalled();
421
389
  expect(contextRef.current.detailsLink).toBeUndefined();
422
390
  });
423
391
  });
@@ -22,8 +22,7 @@ export interface useQuickEditProps<T extends Data> {
22
22
  export interface useQuickEditReturnType<T extends Data> {
23
23
  readonly isQuickEditMode: boolean;
24
24
  readonly quickEditAction?: PageHeaderActionItemProps;
25
- readonly QuickEditContextProvider: React.FC;
26
- readonly quickEditStation: JSX.Element | undefined;
25
+ readonly QuickEditComponent: JSX.Element;
27
26
  readonly changeSelectedItem: (
28
27
  item: T,
29
28
  detailsLink?: LocationDescriptor<unknown>,
@@ -77,8 +76,8 @@ export const useQuickEdit = <T extends Data>({
77
76
  [],
78
77
  );
79
78
 
80
- const QuickEditContextProvider: React.FC = useCallback(
81
- ({ children }) => {
79
+ const QuickEditComponent: JSX.Element = useMemo(() => {
80
+ if (isQuickEditMode) {
82
81
  return (
83
82
  <QuickEditContext.Provider
84
83
  value={{
@@ -94,12 +93,19 @@ export const useQuickEdit = <T extends Data>({
94
93
  },
95
94
  }}
96
95
  >
97
- {children}
96
+ {currentRegistration?.component}
98
97
  </QuickEditContext.Provider>
99
98
  );
100
- },
101
- [detailsLink, isQuickEditMode, registerSaveCallback, selectedItem],
102
- );
99
+ } else {
100
+ return <></>;
101
+ }
102
+ }, [
103
+ currentRegistration?.component,
104
+ detailsLink,
105
+ isQuickEditMode,
106
+ registerSaveCallback,
107
+ selectedItem,
108
+ ]);
103
109
 
104
110
  const quickEditAction: PageHeaderActionItemProps | undefined = useMemo(
105
111
  () =>
@@ -162,8 +168,7 @@ export const useQuickEdit = <T extends Data>({
162
168
  return {
163
169
  isQuickEditMode,
164
170
  quickEditAction,
165
- QuickEditContextProvider,
166
- quickEditStation: currentRegistration?.component,
171
+ QuickEditComponent,
167
172
  changeSelectedItem,
168
173
  };
169
174
  };
@@ -26,7 +26,6 @@ interface DataProviderReturnType<T> {
26
26
  readonly hasMoreData: boolean;
27
27
  readonly onReloadData: () => void;
28
28
  readonly onSortChanged: (sort: SortData<T>) => void;
29
- readonly onFiltersChange: (filters: FilterValues<T>) => void;
30
29
  readonly onRequestMoreData: () => void;
31
30
  }
32
31
 
@@ -39,7 +38,7 @@ interface DataProviderArgumentType<T extends Data> {
39
38
  dataProvider: ExplorerDataProvider<T>;
40
39
  explorerRef: React.ForwardedRef<ExplorerDataProviderConnection<T>>;
41
40
  defaultSortOrder?: SortData<T>;
42
- filters?: FilterValues<T>;
41
+ activeFilters?: FilterValues<T>;
43
42
  keyProperty?: keyof T;
44
43
  setStationMessage: React.Dispatch<
45
44
  React.SetStateAction<StationMessage | undefined>
@@ -51,7 +50,7 @@ export function useDataProvider<T extends Data>({
51
50
  explorerRef,
52
51
  setStationMessage,
53
52
  defaultSortOrder,
54
- filters,
53
+ activeFilters,
55
54
  keyProperty,
56
55
  }: DataProviderArgumentType<T>): DataProviderReturnType<T> {
57
56
  const dataProviderRef = useUpdatingRef(dataProvider);
@@ -67,7 +66,7 @@ export function useDataProvider<T extends Data>({
67
66
  const lastAttemptedPagingInfo = useRef<unknown>();
68
67
 
69
68
  const sorting = useRef<SortData<T> | undefined>(defaultSortOrder);
70
- const filterValues = useRef<FilterValues<T>>(filters || {});
69
+ const filterValues = useRef<FilterValues<T>>(activeFilters || {});
71
70
 
72
71
  const loadData = useCallback(
73
72
  async (
@@ -130,23 +129,22 @@ export function useDataProvider<T extends Data>({
130
129
  [dataProviderRef, setStationMessage],
131
130
  );
132
131
 
133
- const onSortChanged = useCallback(
134
- (sort: SortData<T>): void => {
132
+ useEffect(() => {
133
+ if (activeFilters !== filterValues.current) {
135
134
  // Re-enable more data requests
136
135
  hasMoreData.current = true;
137
- // Set new sorting order.
138
- sorting.current = sort;
136
+ // Set new filters.
137
+ filterValues.current = activeFilters ?? {};
139
138
  loadData();
140
- },
141
- [loadData],
142
- );
139
+ }
140
+ }, [activeFilters, loadData]);
143
141
 
144
- const onFiltersChange = useCallback(
145
- (filters: FilterValues<T>): void => {
142
+ const onSortChanged = useCallback(
143
+ (sort: SortData<T>): void => {
146
144
  // Re-enable more data requests
147
145
  hasMoreData.current = true;
148
- // Set new filters.
149
- filterValues.current = filters;
146
+ // Set new sorting order.
147
+ sorting.current = sort;
150
148
  loadData();
151
149
  },
152
150
  [loadData],
@@ -182,7 +180,6 @@ export function useDataProvider<T extends Data>({
182
180
  resultCount,
183
181
  onReloadData,
184
182
  onSortChanged,
185
- onFiltersChange,
186
183
  onRequestMoreData,
187
184
  data,
188
185
  hasMoreData: hasMoreData.current,
@@ -67,6 +67,7 @@ export const useFilters = <T extends Data>({
67
67
  )}
68
68
  </>
69
69
  );
70
+
70
71
  return {
71
72
  Filters: FilterComponent,
72
73
  activeFilters,
@@ -21,7 +21,7 @@
21
21
  grid: 1fr / 1fr;
22
22
 
23
23
  &.hasMessage {
24
- grid: min-content 1fr / 1fr;
24
+ grid: min-content 1fr / minmax(740px, 1fr);
25
25
  }
26
26
  }
27
27
  }