@axinom/mosaic-ui 0.49.0-rc.1 → 0.49.0-rc.10
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/DynamicDataList.d.ts.map +1 -1
- package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
- package/dist/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/components/Filters/Filter/Filter.d.ts +2 -1
- package/dist/components/Filters/Filter/Filter.d.ts.map +1 -1
- package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts +2 -0
- package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
- package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeader.d.ts +9 -2
- package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeader.model.d.ts +11 -12
- package/dist/components/PageHeader/PageHeader.model.d.ts.map +1 -1
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +35 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +7 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +1 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +3 -0
- package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +1 -0
- package/dist/components/PageHeader/index.d.ts +1 -1
- package/dist/components/PageHeader/index.d.ts.map +1 -1
- package/dist/index.es.js +4 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/{components/DynamicDataList/helpers/generateId.d.ts → utils/GenerateId.d.ts} +1 -1
- package/dist/utils/GenerateId.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/components/DynamicDataList/DynamicDataList.spec.tsx +2 -1
- package/src/components/DynamicDataList/DynamicDataList.tsx +1 -1
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +4 -0
- package/src/components/Explorer/Explorer.spec.tsx +26 -16
- package/src/components/Explorer/Explorer.tsx +47 -28
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +2 -2
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +8 -32
- package/src/components/Filters/Filter/Filter.tsx +3 -0
- package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.spec.tsx +16 -1
- package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.tsx +13 -1
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +34 -30
- package/src/components/List/ListRow/ListRow.scss +0 -1
- package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
- package/src/components/List/ListRow/ListRow.tsx +5 -1
- package/src/components/PageHeader/PageHeader.model.ts +10 -12
- package/src/components/PageHeader/PageHeader.scss +7 -3
- package/src/components/PageHeader/PageHeader.spec.tsx +28 -86
- package/src/components/PageHeader/PageHeader.stories.tsx +32 -7
- package/src/components/PageHeader/PageHeader.tsx +50 -77
- package/src/components/PageHeader/{PageHeaderBulkActions/PageHeaderBulkActions.scss → PageHeaderActionsGroup/PageHeaderActionsGroup.scss} +21 -21
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +105 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +224 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +13 -0
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +30 -0
- package/src/components/PageHeader/index.ts +1 -1
- package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +0 -1
- package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts +0 -22
- package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts.map +0 -1
- package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.spec.tsx +0 -369
- package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.tsx +0 -188
- /package/src/{components/DynamicDataList/helpers/generateId.ts → utils/GenerateId.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GenerateId.d.ts","sourceRoot":"","sources":["../../src/utils/GenerateId.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,IAAI,QAAO,MAKvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-ui",
|
|
3
|
-
"version": "0.49.0-rc.
|
|
3
|
+
"version": "0.49.0-rc.10",
|
|
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.22-rc.
|
|
35
|
+
"@axinom/mosaic-core": "^0.4.22-rc.10",
|
|
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": "130dddd0b1efabb2f50d5cf5455e4f48f7309b34"
|
|
109
109
|
}
|
|
@@ -9,7 +9,8 @@ import { DynamicListRow } from './DynamicListRow/DynamicListRow';
|
|
|
9
9
|
import { useDataHandler } from './helpers/useDataHandler';
|
|
10
10
|
|
|
11
11
|
jest.mock('./helpers/useDataHandler');
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
jest.mock('../../utils/GenerateId', () => ({
|
|
13
14
|
uuid: jest.fn().mockReturnValue('test-uuid'),
|
|
14
15
|
}));
|
|
15
16
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { OptionalObjectSchema } from 'yup/lib/object';
|
|
11
11
|
import { noop } from '../../helpers/utils';
|
|
12
12
|
import { Data } from '../../types/data';
|
|
13
|
+
import { uuid } from '../../utils/GenerateId';
|
|
13
14
|
import { ActionData } from '../Actions';
|
|
14
15
|
import { ObjectSchemaDefinition } from '../FormStation';
|
|
15
16
|
import { DynamicListColumn } from './DynamicDataList.model';
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
} from './DynamicListDataEntry/DynamicListDataEntry';
|
|
22
23
|
import { DynamicListHeader } from './DynamicListHeader/DynamicListHeader';
|
|
23
24
|
import { DynamicListRow } from './DynamicListRow/DynamicListRow';
|
|
24
|
-
import { uuid } from './helpers/generateId';
|
|
25
25
|
import { useColumnDefs } from './helpers/useColumnDefs';
|
|
26
26
|
import { useDataHandler } from './helpers/useDataHandler';
|
|
27
27
|
import { useRowAnimation } from './helpers/useRowAnimation';
|
|
@@ -407,6 +407,43 @@ describe('DynamicListRow', () => {
|
|
|
407
407
|
expect(input.prop('value')).toBe(dataWithPosition.position);
|
|
408
408
|
});
|
|
409
409
|
|
|
410
|
+
describe('DynamicListRow column text alignments', () => {
|
|
411
|
+
const alignments: {
|
|
412
|
+
horizontal: 'left' | 'right' | 'center' | undefined;
|
|
413
|
+
vertical: 'center' | 'start' | 'end' | undefined;
|
|
414
|
+
}[] = [
|
|
415
|
+
{ horizontal: 'center', vertical: 'center' },
|
|
416
|
+
{ horizontal: 'left', vertical: 'start' },
|
|
417
|
+
{ horizontal: 'right', vertical: 'end' },
|
|
418
|
+
{ horizontal: undefined, vertical: undefined },
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
alignments.forEach(({ horizontal, vertical }) => {
|
|
422
|
+
it(`should apply the correct styles for justify-content: ${horizontal}, align-items: ${vertical}`, () => {
|
|
423
|
+
const wrapper = mount(
|
|
424
|
+
<DynamicListRow
|
|
425
|
+
columns={defaultColumns}
|
|
426
|
+
columnSizes={defaultProps.columnSizes}
|
|
427
|
+
data={dataWithPosition}
|
|
428
|
+
positionKey={'position'}
|
|
429
|
+
showPositionColumn={true}
|
|
430
|
+
horizontalTextAlign={horizontal}
|
|
431
|
+
verticalTextAlign={vertical}
|
|
432
|
+
/>,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const wrapperDivs = wrapper.find('.wrapper');
|
|
436
|
+
|
|
437
|
+
wrapperDivs.forEach((node) => {
|
|
438
|
+
const style = node.prop('style');
|
|
439
|
+
|
|
440
|
+
expect(style).toHaveProperty('justifyContent', horizontal);
|
|
441
|
+
expect(style).toHaveProperty('alignItems', vertical);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
410
447
|
describe('tooltip', () => {
|
|
411
448
|
it(`renders a tooltip using the 'title' html attribute by default`, () => {
|
|
412
449
|
const wrapper = shallow(
|
|
@@ -179,6 +179,10 @@ export const DynamicListRow = <T extends Data>({
|
|
|
179
179
|
column.dataEntryRender !== undefined && allowEditing,
|
|
180
180
|
})}
|
|
181
181
|
key={column.key ?? (column.propertyName as string)}
|
|
182
|
+
style={{
|
|
183
|
+
justifyContent: horizontalTextAlign,
|
|
184
|
+
alignItems: verticalTextAlign,
|
|
185
|
+
}}
|
|
182
186
|
>
|
|
183
187
|
<div
|
|
184
188
|
className={classes.column}
|
|
@@ -24,7 +24,7 @@ import { MessageBar } from '../MessageBar';
|
|
|
24
24
|
import { StationError } from '../models';
|
|
25
25
|
import { PageHeaderAction } from '../PageHeader';
|
|
26
26
|
import { PageHeader } from '../PageHeader/PageHeader';
|
|
27
|
-
import {
|
|
27
|
+
import { PageHeaderActionsGroup } from '../PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup';
|
|
28
28
|
import * as GS from '../Utils/State/GlobalState';
|
|
29
29
|
import { Explorer, ExplorerProps } from './Explorer';
|
|
30
30
|
import {
|
|
@@ -34,6 +34,10 @@ import {
|
|
|
34
34
|
} from './Explorer.model';
|
|
35
35
|
import { StationMessage } from './useStationMessage';
|
|
36
36
|
|
|
37
|
+
jest.mock('../../utils/GenerateId', () => ({
|
|
38
|
+
uuid: jest.fn().mockReturnValue('test-uuid'),
|
|
39
|
+
}));
|
|
40
|
+
|
|
37
41
|
interface ExplorerTestData {
|
|
38
42
|
id: number;
|
|
39
43
|
title: string;
|
|
@@ -152,7 +156,6 @@ describe('Explorer', () => {
|
|
|
152
156
|
columns={[{ propertyName: 'id' }]}
|
|
153
157
|
dataProvider={provider}
|
|
154
158
|
stationKey="mock-key"
|
|
155
|
-
openBulkActionsOnStart={true}
|
|
156
159
|
bulkActions={[{ label: 'Something', onClick: jest.fn() }]}
|
|
157
160
|
/>,
|
|
158
161
|
);
|
|
@@ -166,8 +169,9 @@ describe('Explorer', () => {
|
|
|
166
169
|
await wrapper.update();
|
|
167
170
|
});
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
expect(
|
|
173
|
+
wrapper.find(PageHeaderActionsGroup).prop('groupActionsDisabled'),
|
|
174
|
+
).toBe(true);
|
|
171
175
|
});
|
|
172
176
|
|
|
173
177
|
it('enables bulk actions if filtered results are returned', async () => {
|
|
@@ -203,7 +207,10 @@ describe('Explorer', () => {
|
|
|
203
207
|
});
|
|
204
208
|
|
|
205
209
|
wrapper.update();
|
|
206
|
-
|
|
210
|
+
|
|
211
|
+
expect(
|
|
212
|
+
wrapper.find(PageHeaderActionsGroup).prop('groupActionsDisabled'),
|
|
213
|
+
).toBe(false);
|
|
207
214
|
});
|
|
208
215
|
|
|
209
216
|
it('disables bulk actions if no filtered results are returned', async () => {
|
|
@@ -239,7 +246,9 @@ describe('Explorer', () => {
|
|
|
239
246
|
});
|
|
240
247
|
|
|
241
248
|
wrapper.update();
|
|
242
|
-
expect(
|
|
249
|
+
expect(
|
|
250
|
+
wrapper.find(PageHeaderActionsGroup).prop('groupActionsDisabled'),
|
|
251
|
+
).toBe(true);
|
|
243
252
|
});
|
|
244
253
|
|
|
245
254
|
it('Reloads data when a bulk action with reloadData is triggered', async () => {
|
|
@@ -267,10 +276,10 @@ describe('Explorer', () => {
|
|
|
267
276
|
// Initial Data Loading
|
|
268
277
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
269
278
|
|
|
270
|
-
const
|
|
279
|
+
const bulkActions = wrapper.find(PageHeaderActionsGroup);
|
|
271
280
|
|
|
272
281
|
await act(async () => {
|
|
273
|
-
|
|
282
|
+
bulkActions.prop('actions')?.[0].onClick?.();
|
|
274
283
|
|
|
275
284
|
await wrapper.update();
|
|
276
285
|
});
|
|
@@ -301,9 +310,9 @@ describe('Explorer', () => {
|
|
|
301
310
|
return wrapper;
|
|
302
311
|
});
|
|
303
312
|
|
|
304
|
-
const
|
|
313
|
+
const bulkActions = wrapper.find(PageHeaderActionsGroup);
|
|
305
314
|
await act(async () => {
|
|
306
|
-
|
|
315
|
+
bulkActions.prop('actions')?.[0].onClick?.();
|
|
307
316
|
await wrapper.update();
|
|
308
317
|
});
|
|
309
318
|
|
|
@@ -336,9 +345,9 @@ describe('Explorer', () => {
|
|
|
336
345
|
return wrapper;
|
|
337
346
|
});
|
|
338
347
|
|
|
339
|
-
const
|
|
348
|
+
const bulkActions = wrapper.find(PageHeaderActionsGroup);
|
|
340
349
|
await act(async () => {
|
|
341
|
-
|
|
350
|
+
bulkActions.prop('actions')?.[0].onClick?.();
|
|
342
351
|
await wrapper.update();
|
|
343
352
|
});
|
|
344
353
|
|
|
@@ -401,7 +410,7 @@ describe('Explorer', () => {
|
|
|
401
410
|
expectComponentReceivesValue(header, 'title');
|
|
402
411
|
// expectComponentReceivesValue(header, 'actions');
|
|
403
412
|
// expectComponentReceivesValue(header, 'bulkActions');
|
|
404
|
-
expectComponentReceivesValue(header, 'openBulkActionsOnStart');
|
|
413
|
+
// expectComponentReceivesValue(header, 'openBulkActionsOnStart');
|
|
405
414
|
|
|
406
415
|
const filters = wrapper.find(Filters);
|
|
407
416
|
expectComponentReceivesValue(filters, 'filters');
|
|
@@ -2025,9 +2034,10 @@ describe('Explorer', () => {
|
|
|
2025
2034
|
});
|
|
2026
2035
|
|
|
2027
2036
|
await act(async () => {
|
|
2028
|
-
wrapper.find(
|
|
2029
|
-
|
|
2030
|
-
|
|
2037
|
+
wrapper.find(PageHeaderActionsGroup).prop('onActionsGroupToggled')!(true);
|
|
2038
|
+
wrapper.find(PageHeaderActionsGroup).prop('onActionsGroupToggled')!(
|
|
2039
|
+
false,
|
|
2040
|
+
);
|
|
2031
2041
|
});
|
|
2032
2042
|
|
|
2033
2043
|
expect(spy).toHaveBeenCalledTimes(2);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useEffect,
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
|
-
import { ActionData } from '..';
|
|
10
|
+
import { ActionData, IconName } from '..';
|
|
11
11
|
import { noop } from '../../helpers/utils';
|
|
12
12
|
import { showNotification } from '../../initialize';
|
|
13
13
|
import { Data } from '../../types/data';
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
ListSelectMode,
|
|
24
24
|
SortData,
|
|
25
25
|
} from '../List';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
PageHeader,
|
|
28
|
+
PageHeaderActionItemProps,
|
|
29
|
+
PageHeaderActionProps,
|
|
30
|
+
} from '../PageHeader';
|
|
27
31
|
import { isPageHeaderNavigationAction } from '../PageHeader/PageHeaderAction/PageHeaderAction';
|
|
28
32
|
import { PageHeaderJsActionProps } from '../PageHeader/PageHeaderAction/PageHeaderAction.model';
|
|
29
33
|
import { getState, storeState } from '../Utils/State/GlobalState';
|
|
@@ -348,24 +352,48 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
348
352
|
});
|
|
349
353
|
};
|
|
350
354
|
|
|
351
|
-
const pageHeaderActionsHandler = ():
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
355
|
+
const pageHeaderActionsHandler = (): PageHeaderActionItemProps[] => {
|
|
356
|
+
const headerActions: PageHeaderActionItemProps[] = [];
|
|
357
|
+
|
|
358
|
+
if (bulkActions && bulkActions.length > 0) {
|
|
359
|
+
headerActions.push({
|
|
360
|
+
label: 'Bulk Actions',
|
|
361
|
+
icon: IconName.Bulk,
|
|
362
|
+
kind: 'group',
|
|
363
|
+
actions: bulkActionsHandler(),
|
|
364
|
+
openActionsGroupOnStart: openBulkActionsOnStart,
|
|
365
|
+
onActionsGroupToggled: (isOpen) => {
|
|
366
|
+
setIsBulkOpen(isOpen);
|
|
367
|
+
onBulkActionsToggled(isOpen);
|
|
368
|
+
},
|
|
369
|
+
groupActionsDisabled:
|
|
370
|
+
itemSelection.items?.length === 0 || resultCount?.filtered === 0,
|
|
371
|
+
});
|
|
372
|
+
headerActions.push({ kind: 'spacer' });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
actions?.forEach((action) => {
|
|
376
|
+
headerActions.push({
|
|
377
|
+
...(isPageHeaderNavigationAction(action)
|
|
378
|
+
? action
|
|
379
|
+
: {
|
|
380
|
+
...action,
|
|
381
|
+
onClick: async () => {
|
|
382
|
+
try {
|
|
383
|
+
const result = await action.onClick();
|
|
384
|
+
if (result) {
|
|
385
|
+
setStationMessage(errMsg(result));
|
|
386
|
+
}
|
|
387
|
+
} catch (error) {
|
|
388
|
+
setStationMessage(errMsg(error, errAction));
|
|
362
389
|
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
};
|
|
390
|
+
},
|
|
391
|
+
}),
|
|
392
|
+
kind: 'action',
|
|
393
|
+
});
|
|
368
394
|
});
|
|
395
|
+
|
|
396
|
+
return headerActions;
|
|
369
397
|
};
|
|
370
398
|
|
|
371
399
|
const bulkActionsHandler = (): PageHeaderJsActionProps[] => {
|
|
@@ -421,16 +449,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
|
|
|
421
449
|
<PageHeader
|
|
422
450
|
title={title}
|
|
423
451
|
subtitle={resultsTitle}
|
|
424
|
-
actions={
|
|
425
|
-
bulkActions={bulkActions && bulkActionsHandler()}
|
|
426
|
-
openBulkActionsOnStart={openBulkActionsOnStart}
|
|
427
|
-
bulkActionsDisabled={
|
|
428
|
-
itemSelection.items?.length === 0 || resultCount?.filtered === 0
|
|
429
|
-
}
|
|
430
|
-
onBulkActionsToggled={(isOpen) => {
|
|
431
|
-
setIsBulkOpen(isOpen);
|
|
432
|
-
onBulkActionsToggled(isOpen);
|
|
433
|
-
}}
|
|
452
|
+
actions={pageHeaderActionsHandler()}
|
|
434
453
|
setTabTitle={setTabTitle}
|
|
435
454
|
/>
|
|
436
455
|
{StationMessage}
|
|
@@ -117,7 +117,7 @@ describe('NavigationExplorer', () => {
|
|
|
117
117
|
return wrapper;
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
const action = wrapper.find(PageHeaderAction);
|
|
120
|
+
const action = wrapper.find(PageHeaderAction).last();
|
|
121
121
|
expect(action.props().label).toBe('test');
|
|
122
122
|
});
|
|
123
123
|
|
|
@@ -219,7 +219,7 @@ describe('NavigationExplorer', () => {
|
|
|
219
219
|
return wrapper;
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
const createAction = wrapper.find(PageHeaderAction);
|
|
222
|
+
const createAction = wrapper.find(PageHeaderAction).last();
|
|
223
223
|
|
|
224
224
|
createAction.simulate('click');
|
|
225
225
|
|
|
@@ -3,14 +3,16 @@ import { noop } from 'lodash';
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { act } from 'react-dom/test-utils';
|
|
5
5
|
import { actWithReturn } from '../../../helpers/testing';
|
|
6
|
-
import * as app from '../../../initialize';
|
|
7
6
|
import { Column } from '../../List';
|
|
8
|
-
import {
|
|
9
|
-
import { PageHeaderBulkActions } from '../../PageHeader/PageHeaderBulkActions/PageHeaderBulkActions';
|
|
7
|
+
import { PageHeaderActionsGroup } from '../../PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup';
|
|
10
8
|
import { Explorer } from '../Explorer';
|
|
11
9
|
import { ExplorerDataProvider } from '../Explorer.model';
|
|
12
10
|
import { SelectionExplorer } from './SelectionExplorer';
|
|
13
11
|
|
|
12
|
+
jest.mock('../../../utils/GenerateId', () => ({
|
|
13
|
+
uuid: jest.fn().mockReturnValue('test-uuid'),
|
|
14
|
+
}));
|
|
15
|
+
|
|
14
16
|
interface SelectExplorerTestData {
|
|
15
17
|
id: string;
|
|
16
18
|
desc: string;
|
|
@@ -78,7 +80,7 @@ describe('SelectionExplorer', () => {
|
|
|
78
80
|
return wrapper;
|
|
79
81
|
});
|
|
80
82
|
|
|
81
|
-
const bulkActions = wrapper.find(
|
|
83
|
+
const bulkActions = wrapper.find(PageHeaderActionsGroup);
|
|
82
84
|
|
|
83
85
|
expect(bulkActions.exists()).toBe(true);
|
|
84
86
|
});
|
|
@@ -101,38 +103,12 @@ describe('SelectionExplorer', () => {
|
|
|
101
103
|
return wrapper;
|
|
102
104
|
});
|
|
103
105
|
|
|
104
|
-
const bulkActions = wrapper.find(
|
|
106
|
+
const bulkActions = wrapper.find(PageHeaderActionsGroup);
|
|
105
107
|
|
|
106
108
|
expect(bulkActions.exists()).toBe(false);
|
|
107
109
|
});
|
|
108
110
|
|
|
109
|
-
it('Does not call "showNotification" when "Apply Selection" is clicked'
|
|
110
|
-
const [provider] = getDataProvider();
|
|
111
|
-
const showNotificationSpy: jest.SpyInstance = jest.spyOn(
|
|
112
|
-
app,
|
|
113
|
-
'showNotification',
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const wrapper = await actWithReturn(async () => {
|
|
117
|
-
const wrapper = mount(
|
|
118
|
-
<SelectionExplorer
|
|
119
|
-
columns={mockListColumns}
|
|
120
|
-
dataProvider={provider}
|
|
121
|
-
stationKey="mock-key"
|
|
122
|
-
allowBulkSelect={true}
|
|
123
|
-
/>,
|
|
124
|
-
);
|
|
125
|
-
return wrapper;
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const header = wrapper.find(PageHeader);
|
|
129
|
-
await act(async () => {
|
|
130
|
-
header.prop('bulkActions')?.[0].onClick?.();
|
|
131
|
-
await wrapper.update();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
expect(showNotificationSpy).toHaveBeenCalledTimes(0);
|
|
135
|
-
});
|
|
111
|
+
it.todo('Does not call "showNotification" when "Apply Selection" is clicked');
|
|
136
112
|
|
|
137
113
|
it('sends onSelection callback when the selection of a single item is triggered', async () => {
|
|
138
114
|
const [provider] = getDataProvider();
|
|
@@ -21,6 +21,7 @@ import { SearcheableOptionsFilter } from '../SelectionTypes/SearcheableOptionsFi
|
|
|
21
21
|
import classes from './Filter.scss';
|
|
22
22
|
|
|
23
23
|
export interface FilterProps<T extends Data> {
|
|
24
|
+
selectOnFocus?: boolean;
|
|
24
25
|
options: FilterType<T>;
|
|
25
26
|
value?: FilterValue;
|
|
26
27
|
index?: number;
|
|
@@ -42,6 +43,7 @@ export const Filter = <T extends Data>({
|
|
|
42
43
|
onFilterChange,
|
|
43
44
|
onFilterClicked,
|
|
44
45
|
onValidate,
|
|
46
|
+
selectOnFocus = true,
|
|
45
47
|
}: PropsWithChildren<FilterProps<T>>): JSX.Element => {
|
|
46
48
|
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
|
47
49
|
const [hasError, setHasError] = useState<boolean>(false);
|
|
@@ -116,6 +118,7 @@ export const Filter = <T extends Data>({
|
|
|
116
118
|
}
|
|
117
119
|
onError={onError}
|
|
118
120
|
onValidate={onValidate}
|
|
121
|
+
selectOnFocus={selectOnFocus}
|
|
119
122
|
/>
|
|
120
123
|
);
|
|
121
124
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallow } from 'enzyme';
|
|
1
|
+
import { mount, shallow } from 'enzyme';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { noop } from '../../../../helpers/utils';
|
|
4
4
|
import { FreeTextFilter } from './FreeTextFilter';
|
|
@@ -42,4 +42,19 @@ describe('FreeTextFilter', () => {
|
|
|
42
42
|
|
|
43
43
|
expect(error).toBeDefined();
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
it('selects text on focus when selectOnFocus is true and there is a value', () => {
|
|
47
|
+
const mockValue = 'test value';
|
|
48
|
+
const spy = jest.fn();
|
|
49
|
+
|
|
50
|
+
const wrapper = mount(
|
|
51
|
+
<FreeTextFilter onSelect={spy} value={mockValue} selectOnFocus={true} />,
|
|
52
|
+
);
|
|
53
|
+
const input = wrapper.find('input');
|
|
54
|
+
input.simulate('focus');
|
|
55
|
+
|
|
56
|
+
const inputElement = input.getDOMNode<HTMLInputElement>();
|
|
57
|
+
expect(inputElement.selectionStart).toBe(0);
|
|
58
|
+
expect(inputElement.selectionEnd).toBe(mockValue.length);
|
|
59
|
+
});
|
|
45
60
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import React, { KeyboardEventHandler, useState } from 'react';
|
|
2
|
+
import React, { KeyboardEventHandler, useRef, useState } from 'react';
|
|
3
3
|
import { noop } from '../../../../helpers/utils';
|
|
4
4
|
import { FilterValidationResult, FilterValue } from '../../Filters.model';
|
|
5
5
|
import classes from './FreeTextFilter.scss';
|
|
@@ -17,6 +17,8 @@ export interface FreeTextFilterProps {
|
|
|
17
17
|
|
|
18
18
|
/** CSS Class name for additional styles */
|
|
19
19
|
className?: string;
|
|
20
|
+
/** Select text on focus if true (default: true) */
|
|
21
|
+
selectOnFocus?: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
|
|
@@ -25,11 +27,19 @@ export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
|
|
|
25
27
|
onError = noop,
|
|
26
28
|
onValidate: customValidate,
|
|
27
29
|
className = '',
|
|
30
|
+
selectOnFocus = true,
|
|
28
31
|
}) => {
|
|
29
32
|
const [errorMsg, setErrorMsg] = useState<string>();
|
|
30
33
|
const ENTER_KEY = 'Enter';
|
|
31
34
|
|
|
32
35
|
const [valueLocal, setValue] = useState(value || '');
|
|
36
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
37
|
+
|
|
38
|
+
const onFocusWrapper = (): void => {
|
|
39
|
+
if (selectOnFocus && valueLocal) {
|
|
40
|
+
inputRef.current?.select();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
33
43
|
|
|
34
44
|
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
|
|
35
45
|
if (e.key === ENTER_KEY) {
|
|
@@ -55,11 +65,13 @@ export const FreeTextFilter: React.FC<FreeTextFilterProps> = ({
|
|
|
55
65
|
)}
|
|
56
66
|
>
|
|
57
67
|
<input
|
|
68
|
+
ref={inputRef}
|
|
58
69
|
autoFocus
|
|
59
70
|
className={clsx(classes.inputValue, errorMsg && classes.hasError)}
|
|
60
71
|
onKeyDown={handleKeyDown}
|
|
61
72
|
value={valueLocal as string}
|
|
62
73
|
onChange={(e) => setValue(e.target.value)}
|
|
74
|
+
onFocus={onFocusWrapper}
|
|
63
75
|
/>
|
|
64
76
|
{errorMsg !== undefined && <small>{errorMsg}</small>}
|
|
65
77
|
</div>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { FormikValues, useFormikContext } from 'formik';
|
|
2
|
-
import React, { useEffect } from 'react';
|
|
2
|
+
import React, { useEffect, useMemo } from 'react';
|
|
3
3
|
import { useHistory } from 'react-router-dom';
|
|
4
4
|
import { SaveIndicatorType, setSaveIndicator } from '../../../initialize';
|
|
5
5
|
import { IconName } from '../../Icons';
|
|
6
6
|
import {
|
|
7
7
|
PageHeader,
|
|
8
|
+
PageHeaderActionItemProps,
|
|
8
9
|
PageHeaderActionType,
|
|
9
10
|
PageHeaderProps,
|
|
10
11
|
} from '../../PageHeader';
|
|
@@ -49,41 +50,44 @@ export const FormStationHeader: React.FC<
|
|
|
49
50
|
|
|
50
51
|
const title = useTitle(titleProperty, defaultTitle);
|
|
51
52
|
|
|
53
|
+
const actions: PageHeaderActionItemProps[] = useMemo(() => {
|
|
54
|
+
const actionItems: PageHeaderActionItemProps[] = [];
|
|
55
|
+
|
|
56
|
+
if (dirty) {
|
|
57
|
+
actionItems.push({
|
|
58
|
+
label: 'Undo Changes',
|
|
59
|
+
icon: IconName.Undo,
|
|
60
|
+
kind: 'action',
|
|
61
|
+
actionType: PageHeaderActionType.Context,
|
|
62
|
+
onClick: () => {
|
|
63
|
+
resetForm();
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (cancelNavigationUrl) {
|
|
69
|
+
actionItems.push({
|
|
70
|
+
label: 'Cancel',
|
|
71
|
+
icon: IconName.X,
|
|
72
|
+
kind: 'action',
|
|
73
|
+
onClick: () => {
|
|
74
|
+
resetForm();
|
|
75
|
+
// If the form has errors, Navigation needs to be wrapped in a promise or timeout.
|
|
76
|
+
Promise.resolve().then(() => history.push(cancelNavigationUrl));
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return actionItems;
|
|
82
|
+
}, [cancelNavigationUrl, dirty, history, resetForm]);
|
|
83
|
+
|
|
52
84
|
return (
|
|
53
85
|
<PageHeader
|
|
54
86
|
title={title}
|
|
55
87
|
subtitle={subtitle}
|
|
56
88
|
className={className}
|
|
57
89
|
setTabTitle={setTabTitle}
|
|
58
|
-
actions={
|
|
59
|
-
...(dirty === true // add undo action if form as been altered
|
|
60
|
-
? [
|
|
61
|
-
{
|
|
62
|
-
label: 'Undo Changes',
|
|
63
|
-
icon: IconName.Undo,
|
|
64
|
-
actionType: PageHeaderActionType.Context,
|
|
65
|
-
onClick: () => {
|
|
66
|
-
resetForm();
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
]
|
|
70
|
-
: []),
|
|
71
|
-
...(cancelNavigationUrl // add cancel action if applicable
|
|
72
|
-
? [
|
|
73
|
-
{
|
|
74
|
-
label: 'Cancel',
|
|
75
|
-
icon: IconName.X,
|
|
76
|
-
onClick: () => {
|
|
77
|
-
resetForm();
|
|
78
|
-
// If the form has errors, Navigation needs to be wrapped in a promise or timeout.
|
|
79
|
-
Promise.resolve().then(() =>
|
|
80
|
-
history.push(cancelNavigationUrl),
|
|
81
|
-
);
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
]
|
|
85
|
-
: []),
|
|
86
|
-
]}
|
|
90
|
+
actions={actions}
|
|
87
91
|
/>
|
|
88
92
|
);
|
|
89
93
|
};
|
|
@@ -566,4 +566,39 @@ describe('ListRow', () => {
|
|
|
566
566
|
expect(cell.prop('title')).toBe(mockValue);
|
|
567
567
|
});
|
|
568
568
|
});
|
|
569
|
+
|
|
570
|
+
describe('column text alignments', () => {
|
|
571
|
+
const alignments: {
|
|
572
|
+
horizontal: 'left' | 'right' | 'center';
|
|
573
|
+
vertical: 'center' | 'start' | 'end';
|
|
574
|
+
}[] = [
|
|
575
|
+
{ horizontal: 'center', vertical: 'center' },
|
|
576
|
+
{ horizontal: 'left', vertical: 'start' },
|
|
577
|
+
{ horizontal: 'right', vertical: 'end' },
|
|
578
|
+
];
|
|
579
|
+
|
|
580
|
+
alignments.forEach(({ horizontal, vertical }) => {
|
|
581
|
+
it(`should apply the correct styles for textAlign: ${horizontal}, alignSelf: ${vertical}`, () => {
|
|
582
|
+
const wrapper = mount(
|
|
583
|
+
<ListRow
|
|
584
|
+
{...mockProps}
|
|
585
|
+
columns={[{ propertyName: 'id', size: '1fr', label: 'id' }]}
|
|
586
|
+
horizontalTextAlign={horizontal}
|
|
587
|
+
verticalTextAlign={vertical}
|
|
588
|
+
/>,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const wrapperDivs = wrapper.findWhere((node) =>
|
|
592
|
+
node.prop('data-test-id')?.startsWith('list-entry-property'),
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
wrapperDivs.forEach((node) => {
|
|
596
|
+
const style = node.prop('style');
|
|
597
|
+
|
|
598
|
+
expect(style).toHaveProperty('textAlign', horizontal);
|
|
599
|
+
expect(style).toHaveProperty('alignSelf', vertical);
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
});
|
|
569
604
|
});
|