@axinom/mosaic-ui 0.45.0-rc.3 → 0.45.0-rc.5
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/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/components/List/List.d.ts +5 -3
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/List/List.model.d.ts +6 -0
- package/dist/components/List/List.model.d.ts.map +1 -1
- package/dist/components/List/helpers.d.ts +9 -0
- package/dist/components/List/helpers.d.ts.map +1 -0
- package/dist/index.es.js +2 -2
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +11 -0
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +5 -0
- package/src/components/Explorer/Explorer.tsx +5 -0
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +2 -2
- package/src/components/List/List.model.ts +7 -0
- package/src/components/List/List.spec.tsx +83 -1
- package/src/components/List/List.tsx +86 -94
- package/src/components/List/helpers.ts +39 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.45.0-rc.
|
|
3
|
+
"version": "0.45.0-rc.5",
|
|
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.18-rc.
|
|
35
|
+
"@axinom/mosaic-core": "^0.4.18-rc.5",
|
|
36
36
|
"@faker-js/faker": "^7.4.0",
|
|
37
37
|
"@popperjs/core": "^2.11.8",
|
|
38
38
|
"clsx": "^1.1.0",
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"publishConfig": {
|
|
106
106
|
"access": "public"
|
|
107
107
|
},
|
|
108
|
-
"gitHead": "
|
|
108
|
+
"gitHead": "dc25615bd1c2a5624c53a9070a12884da3f2c5d2"
|
|
109
109
|
}
|
|
@@ -320,6 +320,17 @@ describe('DynamicListRow', () => {
|
|
|
320
320
|
dataWithPosition.position,
|
|
321
321
|
Number(mockNewPosition),
|
|
322
322
|
);
|
|
323
|
+
|
|
324
|
+
input.prop('onBlur')?.({
|
|
325
|
+
// @ts-expect-error not full event args object
|
|
326
|
+
currentTarget: { value: mockNewPosition },
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(inputSpy).toHaveBeenCalledTimes(2);
|
|
330
|
+
expect(inputSpy).toHaveBeenCalledWith(
|
|
331
|
+
dataWithPosition.position,
|
|
332
|
+
Number(mockNewPosition),
|
|
333
|
+
);
|
|
323
334
|
});
|
|
324
335
|
|
|
325
336
|
it('onPositionInputChanged is not emitted if current position and new position are the same', () => {
|
|
@@ -160,6 +160,11 @@ export const DynamicListRow = <T extends Data>({
|
|
|
160
160
|
event.key === 'Enter' &&
|
|
161
161
|
onPositionInputChangedHandler(Number(event.currentTarget.value))
|
|
162
162
|
}
|
|
163
|
+
onBlur={(event) => {
|
|
164
|
+
onPositionInputChangedHandler(
|
|
165
|
+
Number(event.currentTarget.value),
|
|
166
|
+
);
|
|
167
|
+
}}
|
|
163
168
|
/>
|
|
164
169
|
</div>
|
|
165
170
|
</div>
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
Column,
|
|
20
20
|
ItemSelectEventArgs,
|
|
21
21
|
List,
|
|
22
|
+
ListElement,
|
|
22
23
|
ListSelectMode,
|
|
23
24
|
SortData,
|
|
24
25
|
} from '../List';
|
|
@@ -227,6 +228,8 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
227
228
|
getState<SortData<T>>(stationKey, 'sort') ?? defaultSortOrder,
|
|
228
229
|
);
|
|
229
230
|
|
|
231
|
+
const listRef = React.useRef<ListElement>(null);
|
|
232
|
+
|
|
230
233
|
useEffect(() => {
|
|
231
234
|
if (
|
|
232
235
|
globalStateOptions.filters &&
|
|
@@ -436,10 +439,12 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
436
439
|
onFiltersChange={(args) => {
|
|
437
440
|
onFiltersChange(args);
|
|
438
441
|
setActiveFilters(args);
|
|
442
|
+
listRef.current?.resetSelection();
|
|
439
443
|
}}
|
|
440
444
|
/>
|
|
441
445
|
<div>
|
|
442
446
|
<List<T>
|
|
447
|
+
ref={listRef}
|
|
443
448
|
columns={columns}
|
|
444
449
|
data={data}
|
|
445
450
|
isLoading={isLoading}
|
|
@@ -121,8 +121,8 @@ const generateBulkActions = <T extends Data>(
|
|
|
121
121
|
): ExplorerBulkAction<T>[] =>
|
|
122
122
|
generateItemArray(amount, (index) => ({
|
|
123
123
|
label: `Bulk Action ${index + 1}`,
|
|
124
|
-
onClick: () => {
|
|
125
|
-
action('bulkActionClicked')();
|
|
124
|
+
onClick: (args) => {
|
|
125
|
+
action('bulkActionClicked')(args);
|
|
126
126
|
return slowFunc();
|
|
127
127
|
},
|
|
128
128
|
icon: IconName.ChevronRight,
|
|
@@ -5,7 +5,7 @@ import { MemoryRouter } from 'react-router';
|
|
|
5
5
|
import { Data } from '../../types/data';
|
|
6
6
|
import { TextButton } from '../Buttons/TextButton/TextButton';
|
|
7
7
|
import { List } from './List';
|
|
8
|
-
import { Column, ListSelectMode, SortData } from './List.model';
|
|
8
|
+
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';
|
|
@@ -366,6 +366,88 @@ describe('List', () => {
|
|
|
366
366
|
});
|
|
367
367
|
});
|
|
368
368
|
|
|
369
|
+
it('clears the SINGLE_ITEMS selection when resetSelection is called', () => {
|
|
370
|
+
const ref = React.createRef<ListElement>();
|
|
371
|
+
const spy = jest.fn();
|
|
372
|
+
|
|
373
|
+
const wrapper = mount(
|
|
374
|
+
<List
|
|
375
|
+
ref={ref}
|
|
376
|
+
columns={mockListColumns}
|
|
377
|
+
data={mockListData}
|
|
378
|
+
selectionMode={ListSelectMode.Multi}
|
|
379
|
+
onItemSelected={spy}
|
|
380
|
+
/>,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
let rows = wrapper.find(ListRow);
|
|
384
|
+
|
|
385
|
+
act(() => {
|
|
386
|
+
rows.at(0).prop('onItemSelected')!(true);
|
|
387
|
+
rows.at(1).prop('onItemSelected')!(true);
|
|
388
|
+
rows.at(2).prop('onItemSelected')!(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
wrapper.update();
|
|
392
|
+
|
|
393
|
+
rows = wrapper.find(ListRow);
|
|
394
|
+
|
|
395
|
+
expect(rows.at(0).prop('itemSelected')).toBeTruthy();
|
|
396
|
+
expect(rows.at(1).prop('itemSelected')).toBeTruthy();
|
|
397
|
+
expect(rows.at(2).prop('itemSelected')).toBeTruthy();
|
|
398
|
+
|
|
399
|
+
act(() => {
|
|
400
|
+
ref.current?.resetSelection();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
wrapper.update();
|
|
404
|
+
|
|
405
|
+
rows = wrapper.find(ListRow);
|
|
406
|
+
|
|
407
|
+
expect(rows.at(0).prop('itemSelected')).toBeFalsy();
|
|
408
|
+
expect(rows.at(1).prop('itemSelected')).toBeFalsy();
|
|
409
|
+
expect(rows.at(2).prop('itemSelected')).toBeFalsy();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('clears the SELECT_ALL selection when resetSelection is called', () => {
|
|
413
|
+
const ref = React.createRef<ListElement>();
|
|
414
|
+
const spy = jest.fn();
|
|
415
|
+
|
|
416
|
+
const wrapper = mount(
|
|
417
|
+
<List
|
|
418
|
+
ref={ref}
|
|
419
|
+
columns={mockListColumns}
|
|
420
|
+
data={mockListData}
|
|
421
|
+
selectionMode={ListSelectMode.Multi}
|
|
422
|
+
onItemSelected={spy}
|
|
423
|
+
/>,
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
let headerRow = wrapper.find(ListHeader);
|
|
427
|
+
|
|
428
|
+
act(() => {
|
|
429
|
+
headerRow.prop('onCheckboxToggled')!(true);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
wrapper.update();
|
|
433
|
+
|
|
434
|
+
headerRow = wrapper.find(ListHeader);
|
|
435
|
+
|
|
436
|
+
expect(headerRow.prop('itemSelected')).toBeTruthy();
|
|
437
|
+
|
|
438
|
+
act(() => {
|
|
439
|
+
ref.current?.resetSelection();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
wrapper.update();
|
|
443
|
+
|
|
444
|
+
wrapper.update();
|
|
445
|
+
|
|
446
|
+
headerRow = wrapper.find(ListHeader);
|
|
447
|
+
|
|
448
|
+
expect(headerRow.prop('itemSelected')).toBeFalsy();
|
|
449
|
+
});
|
|
450
|
+
|
|
369
451
|
it('does not use onItemSelected if provided with a generateItemLink', () => {
|
|
370
452
|
const spy = jest.fn();
|
|
371
453
|
const wrapper = mount(
|
|
@@ -5,6 +5,7 @@ import React, {
|
|
|
5
5
|
ReactElement,
|
|
6
6
|
useCallback,
|
|
7
7
|
useEffect,
|
|
8
|
+
useImperativeHandle,
|
|
8
9
|
useRef,
|
|
9
10
|
useState,
|
|
10
11
|
} from 'react';
|
|
@@ -16,6 +17,7 @@ import { TextButton } from '../Buttons/TextButton/TextButton';
|
|
|
16
17
|
import {
|
|
17
18
|
Column,
|
|
18
19
|
ItemSelectEventArgs,
|
|
20
|
+
ListElement,
|
|
19
21
|
ListItem,
|
|
20
22
|
ListSelectMode,
|
|
21
23
|
SortData,
|
|
@@ -24,6 +26,7 @@ import classes from './List.scss';
|
|
|
24
26
|
import { ListHeader } from './ListHeader/ListHeader';
|
|
25
27
|
import { ListRow } from './ListRow/ListRow';
|
|
26
28
|
import { ListRowLoader } from './ListRow/ListRowLoader';
|
|
29
|
+
import { getActionButtonVisibility, isTrigger, useSort } from './helpers';
|
|
27
30
|
import { useColumnsSize } from './useColumnsSize';
|
|
28
31
|
|
|
29
32
|
export interface ListProps<T extends Data> {
|
|
@@ -105,85 +108,40 @@ export interface ListProps<T extends Data> {
|
|
|
105
108
|
inlineMenuActions?: (data: T) => ActionData[];
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
return { sort, sortChangedHandler } as const;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Renders various sets of data in a tabular format
|
|
149
|
-
* @example
|
|
150
|
-
* <List<DataInterface>
|
|
151
|
-
* columns={[{propertyName: 'id', size: '1fr', label: 'Id'}]}
|
|
152
|
-
* data={[{id: '1',desc: 'Description 1',title: 'Item 1'}]}
|
|
153
|
-
* itemClicked={(item)=> {console.log(item)}}
|
|
154
|
-
* />
|
|
155
|
-
*/
|
|
156
|
-
export const List = <T extends Data>({
|
|
157
|
-
columns,
|
|
158
|
-
data = [],
|
|
159
|
-
isLoading = false,
|
|
160
|
-
isError = false,
|
|
161
|
-
errorMsg = 'There was an error.',
|
|
162
|
-
handleRetry = true,
|
|
163
|
-
minimumWidth = '500px',
|
|
164
|
-
columnGap = '5px',
|
|
165
|
-
rowGap = '0px',
|
|
166
|
-
headerRowHeight = '44px',
|
|
167
|
-
listRowHeight = '50px',
|
|
168
|
-
listRowActionSize = '50px',
|
|
169
|
-
headerRowActionSize = '28px',
|
|
170
|
-
horizontalTextAlign = 'left',
|
|
171
|
-
verticalTextAlign = 'center',
|
|
172
|
-
keyProperty = 'id' as keyof T,
|
|
173
|
-
showActionButton = true,
|
|
174
|
-
loadingTriggerOffset = 10,
|
|
175
|
-
defaultSortOrder,
|
|
176
|
-
selectionMode = ListSelectMode.None,
|
|
177
|
-
enableSelectAll = true,
|
|
178
|
-
onItemClicked = noop,
|
|
179
|
-
onItemSelected = noop,
|
|
180
|
-
onRequestMoreData = noop,
|
|
181
|
-
onSortChanged = noop,
|
|
182
|
-
onRetry = noop,
|
|
183
|
-
generateItemLink,
|
|
184
|
-
className = '',
|
|
185
|
-
inlineMenuActions,
|
|
186
|
-
}: PropsWithChildren<ListProps<T>>): JSX.Element => {
|
|
111
|
+
const ListRenderer = <T extends Data>(
|
|
112
|
+
{
|
|
113
|
+
columns,
|
|
114
|
+
data = [],
|
|
115
|
+
isLoading = false,
|
|
116
|
+
isError = false,
|
|
117
|
+
errorMsg = 'There was an error.',
|
|
118
|
+
handleRetry = true,
|
|
119
|
+
minimumWidth = '500px',
|
|
120
|
+
columnGap = '5px',
|
|
121
|
+
rowGap = '0px',
|
|
122
|
+
headerRowHeight = '44px',
|
|
123
|
+
listRowHeight = '50px',
|
|
124
|
+
listRowActionSize = '50px',
|
|
125
|
+
headerRowActionSize = '28px',
|
|
126
|
+
horizontalTextAlign = 'left',
|
|
127
|
+
verticalTextAlign = 'center',
|
|
128
|
+
keyProperty = 'id' as keyof T,
|
|
129
|
+
showActionButton = true,
|
|
130
|
+
loadingTriggerOffset = 10,
|
|
131
|
+
defaultSortOrder,
|
|
132
|
+
selectionMode = ListSelectMode.None,
|
|
133
|
+
enableSelectAll = true,
|
|
134
|
+
onItemClicked = noop,
|
|
135
|
+
onItemSelected = noop,
|
|
136
|
+
onRequestMoreData = noop,
|
|
137
|
+
onSortChanged = noop,
|
|
138
|
+
onRetry = noop,
|
|
139
|
+
generateItemLink,
|
|
140
|
+
className = '',
|
|
141
|
+
inlineMenuActions,
|
|
142
|
+
}: PropsWithChildren<ListProps<T>>,
|
|
143
|
+
ref?: React.ForwardedRef<ListElement>,
|
|
144
|
+
): JSX.Element => {
|
|
187
145
|
const [listItems, setListItems] = useState<ListItem<T>[]>([]);
|
|
188
146
|
const isAllItemsChecked = useRef<boolean>(false);
|
|
189
147
|
|
|
@@ -225,11 +183,13 @@ export const List = <T extends Data>({
|
|
|
225
183
|
|
|
226
184
|
const headerCheckboxHandler = useCallback(
|
|
227
185
|
(checked: boolean): void => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
186
|
+
setListItems((prevState) =>
|
|
187
|
+
prevState.map((i) => ({
|
|
188
|
+
selected: checked,
|
|
189
|
+
data: i.data,
|
|
190
|
+
})),
|
|
191
|
+
);
|
|
192
|
+
|
|
233
193
|
isAllItemsChecked.current = checked;
|
|
234
194
|
|
|
235
195
|
if (checked) {
|
|
@@ -243,7 +203,7 @@ export const List = <T extends Data>({
|
|
|
243
203
|
});
|
|
244
204
|
}
|
|
245
205
|
},
|
|
246
|
-
[
|
|
206
|
+
[onItemSelected],
|
|
247
207
|
);
|
|
248
208
|
|
|
249
209
|
const itemSelectedHandler = (selected: boolean, index: number): void => {
|
|
@@ -261,6 +221,24 @@ export const List = <T extends Data>({
|
|
|
261
221
|
|
|
262
222
|
const { sort, sortChangedHandler } = useSort(defaultSortOrder, onSortChanged);
|
|
263
223
|
|
|
224
|
+
useImperativeHandle(ref, () => ({
|
|
225
|
+
resetSelection: () => {
|
|
226
|
+
setListItems((prevState) =>
|
|
227
|
+
prevState.map((i) => ({
|
|
228
|
+
selected: false,
|
|
229
|
+
data: i.data,
|
|
230
|
+
})),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
isAllItemsChecked.current = false;
|
|
234
|
+
|
|
235
|
+
onItemSelected({
|
|
236
|
+
mode: 'SINGLE_ITEMS',
|
|
237
|
+
items: [],
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
}));
|
|
241
|
+
|
|
264
242
|
return (
|
|
265
243
|
<div
|
|
266
244
|
className={clsx(classes.wrapper, 'list-wrapper', className)}
|
|
@@ -349,13 +327,27 @@ export const List = <T extends Data>({
|
|
|
349
327
|
);
|
|
350
328
|
};
|
|
351
329
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
330
|
+
/**
|
|
331
|
+
* Renders various sets of data in a tabular format
|
|
332
|
+
* @example
|
|
333
|
+
* <List<DataInterface>
|
|
334
|
+
* columns={[{propertyName: 'id', size: '1fr', label: 'Id'}]}
|
|
335
|
+
* data={[{id: '1',desc: 'Description 1',title: 'Item 1'}]}
|
|
336
|
+
* itemClicked={(item)=> {console.log(item)}}
|
|
337
|
+
* />
|
|
338
|
+
*/
|
|
339
|
+
export const List = React.forwardRef(ListRenderer);
|
|
359
340
|
|
|
360
|
-
|
|
361
|
-
|
|
341
|
+
const noItemsMessage = (
|
|
342
|
+
itemsCount: number,
|
|
343
|
+
isLoading: boolean,
|
|
344
|
+
isError: boolean,
|
|
345
|
+
): ReactElement | undefined => {
|
|
346
|
+
if (!isLoading && !isError && itemsCount === 0) {
|
|
347
|
+
return (
|
|
348
|
+
<div className={clsx(classes.NoData)} data-test-id="list-empty">
|
|
349
|
+
<p>No items found</p>
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Data } from '../../types';
|
|
3
|
+
import { ListItem, SortData } from './List.model';
|
|
4
|
+
|
|
5
|
+
export const isTrigger = function <T extends Data>(
|
|
6
|
+
index: number,
|
|
7
|
+
listItems: ListItem<T>[],
|
|
8
|
+
loadingTriggerOffset: number,
|
|
9
|
+
): boolean {
|
|
10
|
+
return listItems.length - loadingTriggerOffset === index + 1 ? true : false;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const useSort = <T>(
|
|
14
|
+
defaultSortOrder: SortData<T> | undefined,
|
|
15
|
+
onSortChanged: (sort: SortData<T>) => void,
|
|
16
|
+
): {
|
|
17
|
+
readonly sort: SortData<T> | undefined;
|
|
18
|
+
readonly sortChangedHandler: (sort: SortData<T>) => void;
|
|
19
|
+
} => {
|
|
20
|
+
const [sort, setSort] = useState<SortData<T> | undefined>(defaultSortOrder);
|
|
21
|
+
|
|
22
|
+
const sortChangedHandler = (sort: SortData<T>): void => {
|
|
23
|
+
setSort(sort);
|
|
24
|
+
onSortChanged && onSortChanged(sort);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return { sort, sortChangedHandler } as const;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function getActionButtonVisibility<T>(
|
|
31
|
+
data: T,
|
|
32
|
+
showActionButton: boolean | ((item: T) => boolean),
|
|
33
|
+
): boolean {
|
|
34
|
+
if (typeof showActionButton === 'boolean') {
|
|
35
|
+
return showActionButton;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return showActionButton(data);
|
|
39
|
+
}
|