@axinom/mosaic-ui 0.64.0-rc.6 → 0.64.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 (39) hide show
  1. package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +1 -1
  2. package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts.map +1 -1
  3. package/dist/components/Explorer/BulkEdit/index.d.ts +1 -0
  4. package/dist/components/Explorer/BulkEdit/index.d.ts.map +1 -1
  5. package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
  6. package/dist/components/Explorer/index.d.ts +1 -1
  7. package/dist/components/Explorer/index.d.ts.map +1 -1
  8. package/dist/components/InfoTooltip/InfoTooltip.d.ts.map +1 -1
  9. package/dist/components/InlineMenu/InlineMenu.d.ts.map +1 -1
  10. package/dist/components/List/List.d.ts +6 -1
  11. package/dist/components/List/List.d.ts.map +1 -1
  12. package/dist/components/List/ListRow/ListRow.d.ts +5 -20
  13. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  14. package/dist/components/List/ListRowRenderer/ListRowRenderer.d.ts +22 -0
  15. package/dist/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -0
  16. package/dist/index.es.js +4 -5
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.js +4 -5
  19. package/dist/index.js.map +1 -1
  20. package/package.json +2 -2
  21. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +158 -14
  22. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +2 -2
  23. package/src/components/Explorer/BulkEdit/GenerateMutation.spec.tsx +209 -0
  24. package/src/components/Explorer/BulkEdit/GenerateMutation.tsx +78 -12
  25. package/src/components/Explorer/BulkEdit/index.ts +1 -0
  26. package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +1 -0
  27. package/src/components/Explorer/index.ts +1 -0
  28. package/src/components/InfoTooltip/InfoTooltip.scss +65 -65
  29. package/src/components/InfoTooltip/InfoTooltip.tsx +35 -31
  30. package/src/components/InlineMenu/InlineMenu.tsx +39 -33
  31. package/src/components/List/List.spec.tsx +209 -1
  32. package/src/components/List/List.stories.tsx +76 -0
  33. package/src/components/List/List.tsx +52 -31
  34. package/src/components/List/ListRow/ListRow.scss +5 -0
  35. package/src/components/List/ListRow/ListRow.spec.tsx +97 -155
  36. package/src/components/List/ListRow/ListRow.tsx +31 -57
  37. package/src/components/List/ListRowRenderer/ListRowRenderer.spec.tsx +353 -0
  38. package/src/components/List/ListRowRenderer/ListRowRenderer.tsx +68 -0
  39. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.scss +32 -32
@@ -24,8 +24,9 @@ import {
24
24
  } from './List.model';
25
25
  import classes from './List.scss';
26
26
  import { ListHeader } from './ListHeader/ListHeader';
27
- import { ListRow } from './ListRow/ListRow';
27
+ import { ListRowProps } from './ListRow/ListRow';
28
28
  import { ListRowLoader } from './ListRow/ListRowLoader';
29
+ import { ListRowRenderer } from './ListRowRenderer/ListRowRenderer';
29
30
  import { getActionButtonVisibility, isTrigger, useSort } from './helpers';
30
31
  import { useColumnsSize } from './useColumnsSize';
31
32
 
@@ -110,6 +111,10 @@ export interface ListProps<T extends Data> {
110
111
  className?: string;
111
112
  /** Provide inline actions which are available through '...' context menu */
112
113
  inlineMenuActions?: (data: T) => ActionData[];
114
+ /** Function to determine if a specific row should be disabled */
115
+ isRowDisabled?: (data: T, index: number) => boolean;
116
+ /** Custom row renderer - if it returns null, the default ListRow will be used */
117
+ customRowRenderer?: (props: ListRowProps<T>) => ReactElement | null;
113
118
  }
114
119
 
115
120
  const ListRenderer = <T extends Data>(
@@ -145,6 +150,8 @@ const ListRenderer = <T extends Data>(
145
150
  generateItemLink,
146
151
  className = '',
147
152
  inlineMenuActions,
153
+ isRowDisabled,
154
+ customRowRenderer,
148
155
  }: PropsWithChildren<ListProps<T>>,
149
156
  ref?: React.ForwardedRef<ListElement>,
150
157
  ): JSX.Element => {
@@ -290,6 +297,21 @@ const ListRenderer = <T extends Data>(
290
297
  }
291
298
  }, [selectionMode]);
292
299
 
300
+ const getRowDisabledState = useCallback(
301
+ (item: ListItem<T>, index: number): boolean => {
302
+ const isDisabledDueToSelectAll =
303
+ isAllItemsChecked.current &&
304
+ !enableSelectAllDeselect &&
305
+ selectionMode === ListSelectMode.Multi;
306
+
307
+ const isDisabledByCustomLogic =
308
+ isRowDisabled?.(item.data, index) ?? false;
309
+
310
+ return isDisabledDueToSelectAll || isDisabledByCustomLogic;
311
+ },
312
+ [enableSelectAllDeselect, selectionMode, isRowDisabled],
313
+ );
314
+
293
315
  return (
294
316
  <div
295
317
  className={clsx(classes.wrapper, 'list-wrapper', className)}
@@ -319,43 +341,42 @@ const ListRenderer = <T extends Data>(
319
341
  onColumnSizesChanged={setColumnSizes}
320
342
  hasActionColumn={hasActionColumn}
321
343
  />
344
+
322
345
  {/* Rows */}
323
346
  {listItems.map((item: ListItem<T>, index) => (
324
- <ListRow<T>
347
+ <ListRowRenderer<T>
325
348
  key={String(item.data[keyProperty] || index)}
326
- columns={columns}
327
- data={item.data}
328
- itemSelected={item.selected}
349
+ customRowRenderer={customRowRenderer}
329
350
  isTrigger={isTrigger<T>(index, listItems, loadingTriggerOffset)}
330
- isRowDisabled={
331
- isAllItemsChecked.current &&
332
- !enableSelectAllDeselect &&
333
- selectionMode === ListSelectMode.Multi
334
- }
335
- columnSizes={columnSizes}
336
- columnGap={columnGap}
337
- rowHeight={listRowHeight}
338
- actionSize={listRowActionSize}
339
- horizontalTextAlign={horizontalTextAlign}
340
- verticalTextAlign={verticalTextAlign}
341
- textWrap={textWrap}
342
- selectionMode={selectionMode}
343
- showActionButton={
344
- selectionMode === ListSelectMode.None &&
345
- getActionButtonVisibility(item.data, showActionButton)
346
- }
347
- showCheckMark={selectionMode === ListSelectMode.Single}
348
- showItemCheckbox={selectionMode === ListSelectMode.Multi}
349
- onItemClicked={
350
- generateItemLink
351
- ? generateItemLink(item.data)
352
- : (data) => itemClickedHandler(data, index)
353
- }
354
351
  onTriggered={onTriggeredHandler}
355
- onItemSelected={(checked) => itemSelectedHandler(checked, index)}
356
- inlineMenuActions={inlineMenuActions}
352
+ listRowProps={{
353
+ columns,
354
+ data: item.data,
355
+ itemSelected: item.selected,
356
+ isRowDisabled: getRowDisabledState(item, index),
357
+ columnSizes,
358
+ columnGap,
359
+ rowHeight: listRowHeight,
360
+ actionSize: listRowActionSize,
361
+ horizontalTextAlign,
362
+ verticalTextAlign,
363
+ textWrap,
364
+ selectionMode,
365
+ showActionButton:
366
+ selectionMode === ListSelectMode.None &&
367
+ getActionButtonVisibility(item.data, showActionButton),
368
+ showCheckMark: selectionMode === ListSelectMode.Single,
369
+ showItemCheckbox: selectionMode === ListSelectMode.Multi,
370
+ onItemClicked: generateItemLink
371
+ ? generateItemLink(item.data)
372
+ : (data: T) => itemClickedHandler(data, index),
373
+ onItemSelected: (checked: boolean) =>
374
+ itemSelectedHandler(checked, index),
375
+ inlineMenuActions,
376
+ }}
357
377
  />
358
378
  ))}
379
+
359
380
  {isLoading && (
360
381
  <ListRowLoader
361
382
  columnSizes={columnSizes}
@@ -27,6 +27,11 @@
27
27
  }
28
28
  }
29
29
 
30
+ &.disabled {
31
+ cursor: not-allowed;
32
+ color: var(--disabled-text-color, $gray);
33
+ }
34
+
30
35
  .cellWrapper {
31
36
  display: grid;
32
37
  width: 100%;
@@ -275,194 +275,136 @@ describe('ListRow', () => {
275
275
  expect(spy).toHaveBeenCalledWith(true);
276
276
  });
277
277
 
278
- describe('Data Loading Trigger', () => {
279
- const windowIntersectionObserver = window.IntersectionObserver;
280
- const observe = jest.fn();
281
- beforeEach(() => {
282
- window.IntersectionObserver = jest.fn().mockImplementation(() => ({
283
- observe,
284
- }));
285
- });
286
-
287
- afterEach(() => {
288
- window.IntersectionObserver = windowIntersectionObserver;
289
- });
290
-
291
- it('creates the IntersectionObserver when row has isTrigger===true', () => {
292
- const spy = jest.fn();
293
- const wrapper = mount(
294
- <ListRow {...mockProps} isTrigger={true} onTriggered={spy} />,
295
- );
296
-
297
- expect(window.IntersectionObserver).toHaveBeenCalledTimes(1);
298
- expect(observe).toHaveBeenCalledWith(wrapper.getDOMNode());
299
- expect(spy).not.toHaveBeenCalled();
300
- });
301
-
302
- it('creates no IntersectionObserver when row has isTrigger===false', () => {
303
- const spy = jest.fn();
304
-
305
- mount(<ListRow {...mockProps} isTrigger={false} onTriggered={spy} />);
306
-
307
- expect(window.IntersectionObserver).not.toHaveBeenCalled();
308
- expect(spy).not.toHaveBeenCalled();
309
- });
278
+ it(`does not have the 'selected' class by default`, () => {
279
+ const wrapper = mount(<ListRow {...mockProps} />);
310
280
 
311
- it('triggers onTriggered when IntersectionObserver observes entry', () => {
312
- let intersectionFn: IntersectionObserverCallback = () => null;
313
- const spy = jest.fn();
314
-
315
- (window.IntersectionObserver as jest.Mock).mockImplementation((fn) => {
316
- // Remember the callback function
317
- intersectionFn = fn;
318
- // Return the mocked object
319
- return { observe };
320
- });
321
-
322
- mount(<ListRow {...mockProps} isTrigger={true} onTriggered={spy} />);
323
-
324
- // Manually trigger the intersection callback
325
- intersectionFn(
326
- [{ isIntersecting: true } as IntersectionObserverEntry],
327
- {} as IntersectionObserver,
328
- );
329
-
330
- expect(spy).toHaveBeenCalledTimes(1);
331
- });
332
-
333
- it(`does not have the 'selected' class by default`, () => {
334
- const wrapper = mount(<ListRow {...mockProps} />);
335
-
336
- const row = wrapper.find('.columnsRoot');
337
-
338
- expect(row.hasClass('selected')).toBe(false);
339
- });
281
+ const row = wrapper.find('.columnsRoot');
340
282
 
341
- it(`only has the 'selected' class if both itemSelect and showItemCheckbox props are true`, () => {
342
- const wrapper = mount(
343
- <ListRow {...mockProps} itemSelected={true} showItemCheckbox={true} />,
344
- );
283
+ expect(row.hasClass('selected')).toBe(false);
284
+ });
345
285
 
346
- const row = wrapper.find('.columnsRoot');
286
+ it(`only has the 'selected' class if both itemSelect and showItemCheckbox props are true`, () => {
287
+ const wrapper = mount(
288
+ <ListRow {...mockProps} itemSelected={true} showItemCheckbox={true} />,
289
+ );
347
290
 
348
- expect(row.hasClass('selected')).toBe(true);
349
- });
291
+ const row = wrapper.find('.columnsRoot');
350
292
 
351
- it(`raises onItemClicked if list selectionMode is set to None(the default mode)`, () => {
352
- const onItemClickedSpy = jest.fn();
353
- const onItemSelectedSpy = jest.fn();
293
+ expect(row.hasClass('selected')).toBe(true);
294
+ });
354
295
 
355
- const wrapper = mount(
356
- <ListRow
357
- {...mockProps}
358
- showItemCheckbox={true}
359
- onItemClicked={onItemClickedSpy}
360
- onItemSelected={onItemSelectedSpy}
361
- />,
362
- );
296
+ it(`raises onItemClicked if list selectionMode is set to None(the default mode)`, () => {
297
+ const onItemClickedSpy = jest.fn();
298
+ const onItemSelectedSpy = jest.fn();
363
299
 
364
- const row = wrapper.find('.content');
300
+ const wrapper = mount(
301
+ <ListRow
302
+ {...mockProps}
303
+ showItemCheckbox={true}
304
+ onItemClicked={onItemClickedSpy}
305
+ onItemSelected={onItemSelectedSpy}
306
+ />,
307
+ );
365
308
 
366
- row.simulate('click');
309
+ const row = wrapper.find('.content');
367
310
 
368
- expect(onItemClickedSpy).toHaveBeenCalledTimes(1);
369
- expect(onItemSelectedSpy).not.toHaveBeenCalled();
370
- });
311
+ row.simulate('click');
371
312
 
372
- it(`raises onItemClicked if list selectionMode is set to Single`, () => {
373
- const onItemClickedSpy = jest.fn();
374
- const onItemSelectedSpy = jest.fn();
313
+ expect(onItemClickedSpy).toHaveBeenCalledTimes(1);
314
+ expect(onItemSelectedSpy).not.toHaveBeenCalled();
315
+ });
375
316
 
376
- const wrapper = mount(
377
- <ListRow
378
- {...mockProps}
379
- showItemCheckbox={true}
380
- selectionMode={ListSelectMode.Single}
381
- onItemClicked={onItemClickedSpy}
382
- onItemSelected={onItemSelectedSpy}
383
- />,
384
- );
317
+ it(`raises onItemClicked if list selectionMode is set to Single`, () => {
318
+ const onItemClickedSpy = jest.fn();
319
+ const onItemSelectedSpy = jest.fn();
385
320
 
386
- const row = wrapper.find('.content');
321
+ const wrapper = mount(
322
+ <ListRow
323
+ {...mockProps}
324
+ showItemCheckbox={true}
325
+ selectionMode={ListSelectMode.Single}
326
+ onItemClicked={onItemClickedSpy}
327
+ onItemSelected={onItemSelectedSpy}
328
+ />,
329
+ );
387
330
 
388
- row.simulate('click');
331
+ const row = wrapper.find('.content');
389
332
 
390
- expect(onItemClickedSpy).toHaveBeenCalledTimes(1);
391
- expect(onItemSelectedSpy).not.toHaveBeenCalled();
392
- });
333
+ row.simulate('click');
393
334
 
394
- it(`raises onItemSelected and toggles item selection if list selectionMode is set to Multi and checkbox is not disabled`, async () => {
395
- const onItemClickedSpy = jest.fn();
396
- const onItemSelectedSpy = jest.fn();
397
- const mockChecked = false;
335
+ expect(onItemClickedSpy).toHaveBeenCalledTimes(1);
336
+ expect(onItemSelectedSpy).not.toHaveBeenCalled();
337
+ });
398
338
 
399
- const wrapper = mount(
400
- <ListRow
401
- {...mockProps}
402
- showItemCheckbox={true}
403
- selectionMode={ListSelectMode.Multi}
404
- onItemClicked={onItemClickedSpy}
405
- onItemSelected={onItemSelectedSpy}
406
- />,
407
- );
339
+ it(`raises onItemSelected and toggles item selection if list selectionMode is set to Multi and checkbox is not disabled`, async () => {
340
+ const onItemClickedSpy = jest.fn();
341
+ const onItemSelectedSpy = jest.fn();
342
+ const mockChecked = false;
408
343
 
409
- const row = wrapper.find('.content');
344
+ const wrapper = mount(
345
+ <ListRow
346
+ {...mockProps}
347
+ showItemCheckbox={true}
348
+ selectionMode={ListSelectMode.Multi}
349
+ onItemClicked={onItemClickedSpy}
350
+ onItemSelected={onItemSelectedSpy}
351
+ />,
352
+ );
410
353
 
411
- row.simulate('click');
354
+ const row = wrapper.find('.content');
412
355
 
413
- wrapper.update();
356
+ row.simulate('click');
414
357
 
415
- expect(onItemSelectedSpy).toHaveBeenCalledTimes(1);
416
- expect(onItemSelectedSpy).toHaveBeenCalledWith(!mockChecked);
417
- expect(onItemClickedSpy).not.toHaveBeenCalled();
418
- });
358
+ wrapper.update();
419
359
 
420
- it(`does not raise onItemSelected if the row is disabled`, async () => {
421
- const onItemSelectedSpy = jest.fn();
360
+ expect(onItemSelectedSpy).toHaveBeenCalledTimes(1);
361
+ expect(onItemSelectedSpy).toHaveBeenCalledWith(!mockChecked);
362
+ expect(onItemClickedSpy).not.toHaveBeenCalled();
363
+ });
422
364
 
423
- const wrapper = mount(
424
- <ListRow
425
- {...mockProps}
426
- showItemCheckbox={true}
427
- selectionMode={ListSelectMode.Multi}
428
- onItemSelected={onItemSelectedSpy}
429
- isRowDisabled={true}
430
- />,
431
- );
365
+ it(`does not raise onItemSelected if the row is disabled`, async () => {
366
+ const onItemSelectedSpy = jest.fn();
432
367
 
433
- const row = wrapper.find('.columnsRoot');
368
+ const wrapper = mount(
369
+ <ListRow
370
+ {...mockProps}
371
+ showItemCheckbox={true}
372
+ selectionMode={ListSelectMode.Multi}
373
+ onItemSelected={onItemSelectedSpy}
374
+ isRowDisabled={true}
375
+ />,
376
+ );
434
377
 
435
- row.simulate('click');
378
+ const row = wrapper.find('.columnsRoot');
436
379
 
437
- wrapper.update();
380
+ row.simulate('click');
438
381
 
439
- expect(onItemSelectedSpy).not.toHaveBeenCalled();
440
- expect(row.hasClass('disabled')).toBe(true);
441
- });
382
+ wrapper.update();
442
383
 
443
- it(`does not onItemClicked if the row is disabled`, async () => {
444
- const onItemClickedSpy = jest.fn();
384
+ expect(onItemSelectedSpy).not.toHaveBeenCalled();
385
+ expect(row.hasClass('disabled')).toBe(true);
386
+ });
445
387
 
446
- const wrapper = mount(
447
- <ListRow
448
- {...mockProps}
449
- showItemCheckbox={true}
450
- onItemClicked={onItemClickedSpy}
451
- isRowDisabled={true}
452
- />,
453
- );
388
+ it(`does not onItemClicked if the row is disabled`, async () => {
389
+ const onItemClickedSpy = jest.fn();
454
390
 
455
- const row = wrapper.find('.columnsRoot');
391
+ const wrapper = mount(
392
+ <ListRow
393
+ {...mockProps}
394
+ showItemCheckbox={true}
395
+ onItemClicked={onItemClickedSpy}
396
+ isRowDisabled={true}
397
+ />,
398
+ );
456
399
 
457
- row.simulate('click');
400
+ const row = wrapper.find('.columnsRoot');
458
401
 
459
- wrapper.update();
402
+ row.simulate('click');
460
403
 
461
- expect(onItemClickedSpy).not.toHaveBeenCalled();
462
- expect(row.hasClass('disabled')).toBe(true);
463
- });
404
+ wrapper.update();
464
405
 
465
- it.todo('sets ref if row is trigger'); // https://github.com/airbnb/enzyme/issues/2215
406
+ expect(onItemClickedSpy).not.toHaveBeenCalled();
407
+ expect(row.hasClass('disabled')).toBe(true);
466
408
  });
467
409
 
468
410
  describe('linking', () => {
@@ -1,6 +1,6 @@
1
1
  import clsx from 'clsx';
2
2
  import { LocationDescriptor } from 'history';
3
- import React, { PropsWithChildren, useCallback, useRef } from 'react';
3
+ import React, { PropsWithChildren, useCallback } from 'react';
4
4
  import { Link } from 'react-router-dom';
5
5
  import { noop } from '../../../helpers/utils';
6
6
  import { Data } from '../../../types/data';
@@ -34,8 +34,6 @@ export interface ListRowProps<T extends Data> {
34
34
  columns: Column<T>[];
35
35
  /** Whether or not the item is selected (default: false) */
36
36
  itemSelected?: boolean;
37
- /** Whether or not the item is a trigger for pagination (default: false) */
38
- isTrigger?: boolean;
39
37
  /** Determines which selection mode the list is in. (default: ListSelectMode.None) */
40
38
  selectionMode?: ListSelectMode;
41
39
  /** Defines whether an action button will be rendered (default: true) */
@@ -46,7 +44,6 @@ export interface ListRowProps<T extends Data> {
46
44
  showItemCheckbox?: boolean;
47
45
  /** Defines whether a row is disabled (default: false) */
48
46
  isRowDisabled?: boolean;
49
-
50
47
  /** Used for when a row is clicked. This can be either a callback function that will be executed
51
48
  * or a URL where the user should be navigated to.
52
49
  * When the list should navigate the user to e.g. a details page of a entry it is recommended to
@@ -56,10 +53,6 @@ export interface ListRowProps<T extends Data> {
56
53
  * A function that is getting called, when the selection state of the row changes.
57
54
  */
58
55
  onItemSelected?: (checked: boolean) => void;
59
- /**
60
- * A function that is getting called, when the element acts as a trigger (`isTrigger = true`) and gets into view.
61
- */
62
- onTriggered?: () => void;
63
56
  /** Provide inline actions which are available through '...' context menu */
64
57
  inlineMenuActions?: (data: T) => ActionData[];
65
58
  }
@@ -214,28 +207,29 @@ const renderData = <T extends Data>(
214
207
  * verticalTextAlign={'center'}
215
208
  * />
216
209
  */
217
- export const ListRow = <T extends Data>({
218
- columnSizes,
219
- columnGap,
220
- actionSize,
221
- horizontalTextAlign = 'left',
222
- verticalTextAlign = 'center',
223
- textWrap = false,
224
- rowHeight = textWrap ? 'auto' : '50px',
225
- data,
226
- itemSelected = false,
227
- isTrigger = false,
228
- columns,
229
- selectionMode = ListSelectMode.None,
230
- showActionButton = true,
231
- showCheckMark = false,
232
- showItemCheckbox = false,
233
- onItemClicked = noop,
234
- onItemSelected = noop,
235
- onTriggered = noop,
236
- isRowDisabled = false,
237
- inlineMenuActions,
238
- }: PropsWithChildren<ListRowProps<T>>): JSX.Element => {
210
+ const ListRowComponent = <T extends Data>(
211
+ {
212
+ columnSizes,
213
+ columnGap,
214
+ actionSize,
215
+ horizontalTextAlign = 'left',
216
+ verticalTextAlign = 'center',
217
+ textWrap = false,
218
+ rowHeight = textWrap ? 'auto' : '50px',
219
+ data,
220
+ itemSelected = false,
221
+ columns,
222
+ selectionMode = ListSelectMode.None,
223
+ showActionButton = true,
224
+ showCheckMark = false,
225
+ showItemCheckbox = false,
226
+ onItemClicked = noop,
227
+ onItemSelected = noop,
228
+ isRowDisabled = false,
229
+ inlineMenuActions,
230
+ }: PropsWithChildren<ListRowProps<T>>,
231
+ ref: React.Ref<HTMLDivElement>,
232
+ ): JSX.Element => {
239
233
  const customRootStyles = {
240
234
  gridAutoRows: `minmax(50px, ${rowHeight})`,
241
235
  gridColumnGap: columnGap,
@@ -244,32 +238,6 @@ export const ListRow = <T extends Data>({
244
238
  gridTemplateColumns: columnSizes,
245
239
  } as React.CSSProperties;
246
240
 
247
- // Trigger based on: https://www.youtube.com/watch?v=NZKUirTtxcg
248
- const onTriggeredHandler = useCallback(() => {
249
- onTriggered && onTriggered();
250
- }, [onTriggered]);
251
- const observer = useRef<IntersectionObserver>();
252
- const elementRef = useCallback(
253
- (node: HTMLDivElement) => {
254
- if (isTrigger === false) {
255
- return;
256
- }
257
- if (observer.current) {
258
- observer.current.disconnect();
259
- }
260
- observer.current = new IntersectionObserver(([entry]) => {
261
- if (entry.isIntersecting) {
262
- onTriggeredHandler();
263
- }
264
- });
265
-
266
- if (node) {
267
- observer.current.observe(node);
268
- }
269
- },
270
- [isTrigger, onTriggeredHandler],
271
- );
272
-
273
241
  const onItemClickedHandler = useCallback(
274
242
  (data: T) => {
275
243
  // Do nothing if row is disabled
@@ -334,7 +302,7 @@ export const ListRow = <T extends Data>({
334
302
  [classes.disabled]: isRowDisabled,
335
303
  })}
336
304
  style={customRootStyles}
337
- ref={isTrigger ? elementRef : null}
305
+ ref={ref}
338
306
  data-test-id="list-entry"
339
307
  >
340
308
  {/* Items */}
@@ -416,3 +384,9 @@ export const ListRow = <T extends Data>({
416
384
 
417
385
  return Row;
418
386
  };
387
+
388
+ export const ListRow = React.forwardRef(ListRowComponent) as <T extends Data>(
389
+ props: PropsWithChildren<ListRowProps<T>> & {
390
+ ref?: React.Ref<HTMLDivElement>;
391
+ },
392
+ ) => JSX.Element;