@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.
- package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +1 -1
- package/dist/components/Explorer/BulkEdit/GenerateMutation.d.ts.map +1 -1
- package/dist/components/Explorer/BulkEdit/index.d.ts +1 -0
- package/dist/components/Explorer/BulkEdit/index.d.ts.map +1 -1
- package/dist/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
- package/dist/components/Explorer/index.d.ts +1 -1
- package/dist/components/Explorer/index.d.ts.map +1 -1
- package/dist/components/InfoTooltip/InfoTooltip.d.ts.map +1 -1
- package/dist/components/InlineMenu/InlineMenu.d.ts.map +1 -1
- package/dist/components/List/List.d.ts +6 -1
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/List/ListRow/ListRow.d.ts +5 -20
- package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/components/List/ListRowRenderer/ListRowRenderer.d.ts +22 -0
- package/dist/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -0
- package/dist/index.es.js +4 -5
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +158 -14
- package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +2 -2
- package/src/components/Explorer/BulkEdit/GenerateMutation.spec.tsx +209 -0
- package/src/components/Explorer/BulkEdit/GenerateMutation.tsx +78 -12
- package/src/components/Explorer/BulkEdit/index.ts +1 -0
- package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +1 -0
- package/src/components/Explorer/index.ts +1 -0
- package/src/components/InfoTooltip/InfoTooltip.scss +65 -65
- package/src/components/InfoTooltip/InfoTooltip.tsx +35 -31
- package/src/components/InlineMenu/InlineMenu.tsx +39 -33
- package/src/components/List/List.spec.tsx +209 -1
- package/src/components/List/List.stories.tsx +76 -0
- package/src/components/List/List.tsx +52 -31
- package/src/components/List/ListRow/ListRow.scss +5 -0
- package/src/components/List/ListRow/ListRow.spec.tsx +97 -155
- package/src/components/List/ListRow/ListRow.tsx +31 -57
- package/src/components/List/ListRowRenderer/ListRowRenderer.spec.tsx +353 -0
- package/src/components/List/ListRowRenderer/ListRowRenderer.tsx +68 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.scss +32 -32
|
@@ -16,88 +16,88 @@ $pop-up-arrow-extrusion: -7px;
|
|
|
16
16
|
stroke: var(--infotooltip-icon-color, $infotooltip-icon-color);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
.container {
|
|
22
|
+
@include boxSizing;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
min-height: 50px;
|
|
26
|
+
max-width: 650px;
|
|
27
|
+
display: grid;
|
|
28
|
+
grid: min-content 1fr / 1fr;
|
|
29
|
+
width: max-content;
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
background-color: var(
|
|
32
|
+
--infotooltip-background-color,
|
|
33
|
+
$infotooltip-background-color
|
|
34
|
+
);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
color: var(--infotooltip-text-color, $infotooltip-text-color);
|
|
37
|
+
padding: 15px;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
z-index: 10;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
.arrow,
|
|
42
|
+
.arrow::before {
|
|
43
|
+
position: absolute;
|
|
44
|
+
width: 14px;
|
|
45
|
+
height: 14px;
|
|
46
|
+
background: inherit;
|
|
47
|
+
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
.arrow {
|
|
50
|
+
z-index: -1;
|
|
51
|
+
visibility: hidden;
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
background-color: var(
|
|
54
|
+
--infotooltip-background-color,
|
|
55
|
+
$infotooltip-background-color
|
|
56
|
+
);
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
.arrow::before {
|
|
60
|
+
visibility: visible;
|
|
61
|
+
content: '';
|
|
62
|
+
transform: rotate(45deg);
|
|
63
|
+
}
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
65
|
+
&[data-popper-placement^='top'] {
|
|
66
|
+
> .arrow {
|
|
67
|
+
bottom: $pop-up-arrow-extrusion;
|
|
68
68
|
}
|
|
69
|
+
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
71
|
+
&[data-popper-placement^='bottom'] {
|
|
72
|
+
> .arrow {
|
|
73
|
+
top: $pop-up-arrow-extrusion;
|
|
74
74
|
}
|
|
75
|
+
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
77
|
+
&[data-popper-placement^='left'] {
|
|
78
|
+
> .arrow {
|
|
79
|
+
right: $pop-up-arrow-extrusion;
|
|
80
80
|
}
|
|
81
|
+
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
83
|
+
&[data-popper-placement^='right'] {
|
|
84
|
+
> .arrow {
|
|
85
|
+
left: $pop-up-arrow-extrusion;
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
90
|
+
.info-tooltip-enter {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
.info-tooltip-enter-active {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
transition: opacity 500ms ease-in;
|
|
96
|
+
}
|
|
97
|
+
.info-tooltip-exit {
|
|
98
|
+
opacity: 1;
|
|
99
|
+
}
|
|
100
|
+
.info-tooltip-exit-active {
|
|
101
|
+
opacity: 0;
|
|
102
|
+
transition: opacity 500ms ease-out;
|
|
103
103
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import React, { useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
3
4
|
import { usePopper } from 'react-popper';
|
|
4
5
|
import { CSSTransition } from 'react-transition-group';
|
|
5
6
|
import { IconName, Icons } from '../Icons';
|
|
@@ -52,40 +53,43 @@ export const InfoTooltip: React.FC<InfoTooltipProps> = ({
|
|
|
52
53
|
>
|
|
53
54
|
<Icons icon={IconName.Info} className={clsx(classes.icon)} />
|
|
54
55
|
</div>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
unmountOnExit
|
|
65
|
-
>
|
|
66
|
-
<div
|
|
67
|
-
className={clsx(classes.container)}
|
|
68
|
-
ref={setPopperElement as React.LegacyRef<HTMLDivElement>}
|
|
69
|
-
style={styles.popper}
|
|
70
|
-
{...attributes.popper}
|
|
71
|
-
onClick={(e) => e.stopPropagation()}
|
|
72
|
-
onMouseEnter={() => {
|
|
73
|
-
clearTimeout(hideTooltipTimeout);
|
|
56
|
+
{createPortal(
|
|
57
|
+
<CSSTransition
|
|
58
|
+
in={showTooltip}
|
|
59
|
+
timeout={200}
|
|
60
|
+
classNames={{
|
|
61
|
+
enter: classes['info-tooltip-enter'],
|
|
62
|
+
enterActive: classes['info-tooltip-enter-active'],
|
|
63
|
+
exit: classes['info-tooltip-exit'],
|
|
64
|
+
exitActive: classes['info-tooltip-exit-active'],
|
|
74
65
|
}}
|
|
75
|
-
|
|
76
|
-
(hideTooltipTimeout = global.setTimeout(() => {
|
|
77
|
-
setShowTooltip(false);
|
|
78
|
-
}, tooltipVisibleTime))
|
|
79
|
-
}
|
|
66
|
+
unmountOnExit
|
|
80
67
|
>
|
|
81
|
-
{children}
|
|
82
68
|
<div
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
style={styles.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
69
|
+
className={clsx(classes.container)}
|
|
70
|
+
ref={setPopperElement as React.LegacyRef<HTMLDivElement>}
|
|
71
|
+
style={styles.popper}
|
|
72
|
+
{...attributes.popper}
|
|
73
|
+
onClick={(e) => e.stopPropagation()}
|
|
74
|
+
onMouseEnter={() => {
|
|
75
|
+
clearTimeout(hideTooltipTimeout);
|
|
76
|
+
}}
|
|
77
|
+
onMouseLeave={() =>
|
|
78
|
+
(hideTooltipTimeout = global.setTimeout(() => {
|
|
79
|
+
setShowTooltip(false);
|
|
80
|
+
}, tooltipVisibleTime))
|
|
81
|
+
}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
<div
|
|
85
|
+
ref={setArrowElement as React.LegacyRef<HTMLDivElement>}
|
|
86
|
+
className={clsx(classes.arrow)}
|
|
87
|
+
style={styles.arrow}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</CSSTransition>,
|
|
91
|
+
document.body,
|
|
92
|
+
)}
|
|
89
93
|
</div>
|
|
90
94
|
);
|
|
91
95
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Placement } from '@popperjs/core';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import React, { Ref, useState } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { usePopper } from 'react-popper';
|
|
5
6
|
import { useExpand } from '../../hooks/useExpand/useExpand';
|
|
6
7
|
import { Actions, ActionsProps } from '../Actions';
|
|
@@ -104,40 +105,45 @@ export const InlineMenu: React.FC<InlineMenuProps> = ({
|
|
|
104
105
|
)}
|
|
105
106
|
dataTestId="inline-menu-button"
|
|
106
107
|
/>
|
|
107
|
-
{isExpanded &&
|
|
108
|
-
|
|
109
|
-
tabIndex={-1}
|
|
110
|
-
className={clsx(classes.container, 'sub-menu-container', className)}
|
|
111
|
-
ref={setPopperElement as React.LegacyRef<HTMLDivElement>}
|
|
112
|
-
style={styles.popper}
|
|
113
|
-
{...attributes.popper}
|
|
114
|
-
onBlur={() => {
|
|
115
|
-
onBlurTimeout = global.setTimeout(() => collapse(), 250);
|
|
116
|
-
}}
|
|
117
|
-
data-test-id="inline-menu"
|
|
118
|
-
>
|
|
119
|
-
<Actions
|
|
120
|
-
{...rest}
|
|
121
|
-
onActionClick={(e, action) => {
|
|
122
|
-
if (
|
|
123
|
-
action.confirmationMode &&
|
|
124
|
-
action.confirmationMode !== 'None'
|
|
125
|
-
) {
|
|
126
|
-
clearTimeout(onBlurTimeout);
|
|
127
|
-
} else {
|
|
128
|
-
onBlurTimeout = global.setTimeout(() => collapse(), 250);
|
|
129
|
-
}
|
|
130
|
-
onActionClick?.(e, action);
|
|
131
|
-
}}
|
|
132
|
-
/>
|
|
108
|
+
{isExpanded &&
|
|
109
|
+
createPortal(
|
|
133
110
|
<div
|
|
134
|
-
|
|
135
|
-
className={classes.
|
|
136
|
-
|
|
137
|
-
style={
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
111
|
+
tabIndex={-1}
|
|
112
|
+
className={clsx(classes.container, 'sub-menu-container', className)}
|
|
113
|
+
ref={setPopperElement as React.LegacyRef<HTMLDivElement>}
|
|
114
|
+
style={styles.popper}
|
|
115
|
+
{...attributes.popper}
|
|
116
|
+
onBlur={() => {
|
|
117
|
+
onBlurTimeout = global.setTimeout(() => collapse(), 250);
|
|
118
|
+
}}
|
|
119
|
+
data-test-id="inline-menu"
|
|
120
|
+
>
|
|
121
|
+
<Actions
|
|
122
|
+
{...rest}
|
|
123
|
+
onActionClick={(e, action) => {
|
|
124
|
+
if (
|
|
125
|
+
action.confirmationMode &&
|
|
126
|
+
action.confirmationMode !== 'None'
|
|
127
|
+
) {
|
|
128
|
+
clearTimeout(onBlurTimeout);
|
|
129
|
+
} else {
|
|
130
|
+
onBlurTimeout = global.setTimeout(() => collapse(), 250);
|
|
131
|
+
}
|
|
132
|
+
onActionClick?.(e, action);
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
<div
|
|
136
|
+
ref={setArrowElement as React.LegacyRef<HTMLDivElement>}
|
|
137
|
+
className={classes.arrow}
|
|
138
|
+
// style={styles.arrow}
|
|
139
|
+
style={{
|
|
140
|
+
...styles.arrow,
|
|
141
|
+
display: showArrow ? 'inherit' : 'none',
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
</div>,
|
|
145
|
+
document.body,
|
|
146
|
+
)}
|
|
141
147
|
</>
|
|
142
148
|
);
|
|
143
149
|
};
|
|
@@ -9,6 +9,7 @@ import { Column, ListElement, ListSelectMode, SortData } from './List.model';
|
|
|
9
9
|
import { ListHeader } from './ListHeader/ListHeader';
|
|
10
10
|
import { ListRow } from './ListRow/ListRow';
|
|
11
11
|
import { ListRowLoader } from './ListRow/ListRowLoader';
|
|
12
|
+
import { ListRowRenderer } from './ListRowRenderer/ListRowRenderer';
|
|
12
13
|
|
|
13
14
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
14
15
|
|
|
@@ -475,7 +476,10 @@ describe('List', () => {
|
|
|
475
476
|
/>,
|
|
476
477
|
);
|
|
477
478
|
|
|
478
|
-
const onTriggered = wrapper
|
|
479
|
+
const onTriggered = wrapper
|
|
480
|
+
.find(ListRowRenderer)
|
|
481
|
+
.first()
|
|
482
|
+
.prop('onTriggered');
|
|
479
483
|
onTriggered!();
|
|
480
484
|
|
|
481
485
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
@@ -605,4 +609,208 @@ describe('List', () => {
|
|
|
605
609
|
|
|
606
610
|
expect(listRow.props().actionSize).toBe(mockSize);
|
|
607
611
|
});
|
|
612
|
+
|
|
613
|
+
describe('isRowDisabled', () => {
|
|
614
|
+
const mountListWithDisabledRows = (additionalProps = {}) => {
|
|
615
|
+
return mount(
|
|
616
|
+
<List
|
|
617
|
+
columns={mockListColumns}
|
|
618
|
+
data={mockListData}
|
|
619
|
+
{...additionalProps}
|
|
620
|
+
/>,
|
|
621
|
+
);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const expectRowDisabledStates = (
|
|
625
|
+
wrapper: any,
|
|
626
|
+
expectedStates: boolean[],
|
|
627
|
+
) => {
|
|
628
|
+
const rows = wrapper.find(ListRow);
|
|
629
|
+
expectedStates.forEach((expected, index) => {
|
|
630
|
+
expect(rows.at(index).prop('isRowDisabled')).toBe(expected);
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const triggerSelectAll = (wrapper: any) => {
|
|
635
|
+
const headerRow = wrapper.find(ListHeader);
|
|
636
|
+
act(() => {
|
|
637
|
+
headerRow.prop('onCheckboxToggled')!(true);
|
|
638
|
+
});
|
|
639
|
+
wrapper.update();
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
it('should disable rows based on custom isRowDisabled function', () => {
|
|
643
|
+
const isRowDisabledMock = jest.fn(
|
|
644
|
+
(data: ListTestData, _index: number) => data.id === '2',
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
const wrapper = mountListWithDisabledRows({
|
|
648
|
+
isRowDisabled: isRowDisabledMock,
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// Verify disabled states
|
|
652
|
+
expectRowDisabledStates(wrapper, [false, true, false]);
|
|
653
|
+
|
|
654
|
+
// Verify function calls
|
|
655
|
+
expect(isRowDisabledMock).toHaveBeenCalledTimes(3);
|
|
656
|
+
mockListData.forEach((data, index) => {
|
|
657
|
+
expect(isRowDisabledMock).toHaveBeenNthCalledWith(
|
|
658
|
+
index + 1,
|
|
659
|
+
data,
|
|
660
|
+
index,
|
|
661
|
+
);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
describe('selectAll behavior', () => {
|
|
666
|
+
const selectAllProps = {
|
|
667
|
+
selectionMode: ListSelectMode.Multi,
|
|
668
|
+
enableSelectAll: true,
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
it('should disable rows when selectAll is enabled and enableSelectAllDeselect is false', () => {
|
|
672
|
+
const wrapper = mountListWithDisabledRows({
|
|
673
|
+
...selectAllProps,
|
|
674
|
+
enableSelectAllDeselect: false,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// Initially all rows should be enabled
|
|
678
|
+
expectRowDisabledStates(wrapper, [false, false, false]);
|
|
679
|
+
|
|
680
|
+
triggerSelectAll(wrapper);
|
|
681
|
+
|
|
682
|
+
// After selectAll, all rows should be disabled
|
|
683
|
+
expectRowDisabledStates(wrapper, [true, true, true]);
|
|
684
|
+
|
|
685
|
+
expect(wrapper.find(ListRow)).toHaveLength(mockListData.length);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('should not disable rows when enableSelectAllDeselect is true', () => {
|
|
689
|
+
const wrapper = mountListWithDisabledRows({
|
|
690
|
+
...selectAllProps,
|
|
691
|
+
enableSelectAllDeselect: true,
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
triggerSelectAll(wrapper);
|
|
695
|
+
|
|
696
|
+
// Rows should remain enabled when deselect is allowed
|
|
697
|
+
expectRowDisabledStates(wrapper, [false, false, false]);
|
|
698
|
+
|
|
699
|
+
expect(wrapper.find(ListRow)).toHaveLength(mockListData.length);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should combine custom isRowDisabled with selectAll disabled state', () => {
|
|
703
|
+
const isRowDisabledMock = jest.fn(
|
|
704
|
+
(data: ListTestData) => data.id === '1',
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
const wrapper = mountListWithDisabledRows({
|
|
708
|
+
...selectAllProps,
|
|
709
|
+
enableSelectAllDeselect: false,
|
|
710
|
+
isRowDisabled: isRowDisabledMock,
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
triggerSelectAll(wrapper);
|
|
714
|
+
|
|
715
|
+
// All rows should be disabled (selectAll OR custom logic)
|
|
716
|
+
expectRowDisabledStates(wrapper, [true, true, true]);
|
|
717
|
+
|
|
718
|
+
expect(wrapper.find(ListRow)).toHaveLength(mockListData.length);
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// Test different scenarios with parameterized tests
|
|
723
|
+
describe.each([
|
|
724
|
+
{
|
|
725
|
+
name: 'even indices disabled',
|
|
726
|
+
disableLogic: (_data: ListTestData, index: number) => index % 2 === 0,
|
|
727
|
+
expected: [true, false, true],
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: 'specific IDs disabled',
|
|
731
|
+
disableLogic: (data: ListTestData) => ['1', '3'].includes(data.id),
|
|
732
|
+
expected: [true, false, true],
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: 'title-based disabled',
|
|
736
|
+
disableLogic: (data: ListTestData) => data.title.includes('2'),
|
|
737
|
+
expected: [false, true, false],
|
|
738
|
+
},
|
|
739
|
+
])('custom logic: $name', ({ disableLogic, expected }) => {
|
|
740
|
+
it(`should disable rows when ${expected.filter(Boolean).length} out of ${
|
|
741
|
+
expected.length
|
|
742
|
+
} match criteria`, () => {
|
|
743
|
+
const isRowDisabledMock = jest.fn(disableLogic);
|
|
744
|
+
const wrapper = mountListWithDisabledRows({
|
|
745
|
+
isRowDisabled: isRowDisabledMock,
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
expectRowDisabledStates(wrapper, expected);
|
|
749
|
+
expect(isRowDisabledMock).toHaveBeenCalledTimes(mockListData.length);
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
describe('customRowRenderer', () => {
|
|
755
|
+
const createSimpleCustomRenderer = (testId = 'custom-row') =>
|
|
756
|
+
jest.fn(() => <div data-test-id={testId}>Custom Row Content</div>);
|
|
757
|
+
|
|
758
|
+
it('should use custom row renderer when provided', () => {
|
|
759
|
+
const customRowRenderer = createSimpleCustomRenderer();
|
|
760
|
+
const wrapper = mount(
|
|
761
|
+
<List
|
|
762
|
+
columns={mockListColumns}
|
|
763
|
+
data={mockListData}
|
|
764
|
+
customRowRenderer={customRowRenderer}
|
|
765
|
+
/>,
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
expect(customRowRenderer).toHaveBeenCalledTimes(3);
|
|
769
|
+
expect(wrapper.find('[data-test-id="custom-row"]')).toHaveLength(3);
|
|
770
|
+
expect(wrapper.find(ListRow)).toHaveLength(0); // No default ListRow components
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it('should fall back to default ListRow when custom renderer returns null', () => {
|
|
774
|
+
const customRowRenderer = jest.fn(() => null);
|
|
775
|
+
const wrapper = mount(
|
|
776
|
+
<List
|
|
777
|
+
columns={mockListColumns}
|
|
778
|
+
data={mockListData}
|
|
779
|
+
customRowRenderer={customRowRenderer}
|
|
780
|
+
/>,
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
expect(customRowRenderer).toHaveBeenCalledTimes(3);
|
|
784
|
+
expect(wrapper.find(ListRow)).toHaveLength(3);
|
|
785
|
+
expect(wrapper.find('[data-test-id="custom-row"]')).toHaveLength(0);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it('should pass ListRowProps to custom renderer', () => {
|
|
789
|
+
const customRowRenderer = jest.fn(() => (
|
|
790
|
+
<div data-test-id="custom-row">Custom</div>
|
|
791
|
+
));
|
|
792
|
+
|
|
793
|
+
mount(
|
|
794
|
+
<List
|
|
795
|
+
columns={mockListColumns}
|
|
796
|
+
data={mockListData}
|
|
797
|
+
customRowRenderer={customRowRenderer}
|
|
798
|
+
/>,
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
// Verify first call receives ListRowProps structure
|
|
802
|
+
expect(customRowRenderer).toHaveBeenNthCalledWith(
|
|
803
|
+
1,
|
|
804
|
+
expect.objectContaining({
|
|
805
|
+
data: mockListData[0],
|
|
806
|
+
columns: mockListColumns,
|
|
807
|
+
columnSizes: '1fr 1fr 1fr 50px',
|
|
808
|
+
itemSelected: false,
|
|
809
|
+
isRowDisabled: false,
|
|
810
|
+
onItemClicked: expect.any(Function),
|
|
811
|
+
onItemSelected: expect.any(Function),
|
|
812
|
+
}),
|
|
813
|
+
);
|
|
814
|
+
});
|
|
815
|
+
});
|
|
608
816
|
});
|
|
@@ -11,6 +11,7 @@ import { Message } from '../Message';
|
|
|
11
11
|
import { List, ListProps } from './List';
|
|
12
12
|
import { Column, ColumnMap, ListSelectMode, SortData } from './List.model';
|
|
13
13
|
import { sortStoryData, useLocalSort } from './List.stories.helper';
|
|
14
|
+
import { ListRowProps } from './ListRow/ListRow';
|
|
14
15
|
import { createStateRenderer } from './ListRow/Renderers';
|
|
15
16
|
|
|
16
17
|
interface ListStoryData {
|
|
@@ -88,6 +89,8 @@ const groups = createGroups({
|
|
|
88
89
|
'enableSelectAll',
|
|
89
90
|
'showActionButton',
|
|
90
91
|
'loadingTriggerOffset',
|
|
92
|
+
'isRowDisabled',
|
|
93
|
+
'customRowRenderer',
|
|
91
94
|
],
|
|
92
95
|
Styling: [
|
|
93
96
|
'horizontalTextAlign',
|
|
@@ -133,6 +136,79 @@ const meta: Meta<StoryListType> = {
|
|
|
133
136
|
random: (): boolean => (Math.random() > 0.5 ? true : false),
|
|
134
137
|
},
|
|
135
138
|
},
|
|
139
|
+
isRowDisabled: {
|
|
140
|
+
...groups.isRowDisabled,
|
|
141
|
+
description:
|
|
142
|
+
'Function to determine if a row should be disabled. Choose from predefined options or disable control for custom logic.',
|
|
143
|
+
control: {
|
|
144
|
+
type: 'select',
|
|
145
|
+
},
|
|
146
|
+
options: ['none', 'evenRows', 'oddRows', 'specificIds', 'randomRows'],
|
|
147
|
+
mapping: {
|
|
148
|
+
none: undefined,
|
|
149
|
+
evenRows: (_data: ListStoryData, index: number) => index % 2 === 0,
|
|
150
|
+
oddRows: (_data: ListStoryData, index: number) => index % 2 !== 0,
|
|
151
|
+
specificIds: (data: ListStoryData) => [1, 3, 5].includes(data.id),
|
|
152
|
+
randomRows: () => Math.random() > 0.7,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
customRowRenderer: {
|
|
156
|
+
...groups.customRowRenderer,
|
|
157
|
+
description:
|
|
158
|
+
'Custom renderer for rows. Choose from predefined options or disable control for custom logic.',
|
|
159
|
+
control: {
|
|
160
|
+
type: 'select',
|
|
161
|
+
},
|
|
162
|
+
options: ['none', 'customRenderer'],
|
|
163
|
+
mapping: {
|
|
164
|
+
none: undefined,
|
|
165
|
+
customRenderer: (props: ListRowProps<ListStoryData>) => {
|
|
166
|
+
const renderCustom = Math.random() < 0.3;
|
|
167
|
+
|
|
168
|
+
return renderCustom ? (
|
|
169
|
+
<div
|
|
170
|
+
style={{
|
|
171
|
+
display: 'grid',
|
|
172
|
+
gridTemplateColumns: props.columnSizes,
|
|
173
|
+
columnGap: props.columnGap,
|
|
174
|
+
height: '50px',
|
|
175
|
+
placeItems: 'center left',
|
|
176
|
+
paddingLeft: '5px',
|
|
177
|
+
borderBottom: '1px solid #ddd',
|
|
178
|
+
backgroundColor: '#f5f5f5',
|
|
179
|
+
opacity: props.isRowDisabled ? 0.4 : 0.7,
|
|
180
|
+
cursor: props.isRowDisabled ? 'not-allowed' : 'pointer',
|
|
181
|
+
}}
|
|
182
|
+
onClick={() =>
|
|
183
|
+
!props.isRowDisabled &&
|
|
184
|
+
typeof props.onItemClicked === 'function' &&
|
|
185
|
+
props.onItemClicked(props.data)
|
|
186
|
+
}
|
|
187
|
+
>
|
|
188
|
+
<span>{props.data.id}</span>
|
|
189
|
+
<span>{props.data.title}</span>
|
|
190
|
+
<div
|
|
191
|
+
style={{
|
|
192
|
+
display: 'grid',
|
|
193
|
+
width: '100%',
|
|
194
|
+
overflow: 'hidden',
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
<span
|
|
198
|
+
style={{
|
|
199
|
+
overflow: 'hidden',
|
|
200
|
+
textOverflow: 'ellipsis',
|
|
201
|
+
whiteSpace: 'nowrap',
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{props.data.desc}
|
|
205
|
+
</span>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
) : null;
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
136
212
|
},
|
|
137
213
|
args: {
|
|
138
214
|
columns: defaultColumns,
|