@axinom/mosaic-ui 0.49.0-rc.1 → 0.49.0-rc.11

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 (74) hide show
  1. package/dist/components/DynamicDataList/DynamicDataList.d.ts +3 -1
  2. package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
  3. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts +3 -1
  4. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
  5. package/dist/components/Explorer/Explorer.d.ts +2 -0
  6. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  7. package/dist/components/Filters/Filter/Filter.d.ts +2 -1
  8. package/dist/components/Filters/Filter/Filter.d.ts.map +1 -1
  9. package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts +2 -0
  10. package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts.map +1 -1
  11. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  12. package/dist/components/List/List.d.ts +2 -0
  13. package/dist/components/List/List.d.ts.map +1 -1
  14. package/dist/components/List/ListRow/ListRow.d.ts +4 -2
  15. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  16. package/dist/components/List/ListRow/ListRowLoader.d.ts +1 -1
  17. package/dist/components/List/ListRow/ListRowLoader.d.ts.map +1 -1
  18. package/dist/components/PageHeader/PageHeader.d.ts +9 -2
  19. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  20. package/dist/components/PageHeader/PageHeader.model.d.ts +11 -12
  21. package/dist/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  22. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +35 -0
  23. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -0
  24. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +7 -0
  25. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +1 -0
  26. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +3 -0
  27. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +1 -0
  28. package/dist/components/PageHeader/index.d.ts +1 -1
  29. package/dist/components/PageHeader/index.d.ts.map +1 -1
  30. package/dist/index.es.js +5 -4
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.js +5 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/{components/DynamicDataList/helpers/generateId.d.ts → utils/GenerateId.d.ts} +1 -1
  35. package/dist/utils/GenerateId.d.ts.map +1 -0
  36. package/package.json +3 -3
  37. package/src/components/DynamicDataList/DynamicDataList.spec.tsx +2 -1
  38. package/src/components/DynamicDataList/DynamicDataList.stories.tsx +2 -1
  39. package/src/components/DynamicDataList/DynamicDataList.tsx +5 -1
  40. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +11 -5
  41. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
  42. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +23 -14
  43. package/src/components/Explorer/Explorer.spec.tsx +26 -16
  44. package/src/components/Explorer/Explorer.stories.tsx +1 -0
  45. package/src/components/Explorer/Explorer.tsx +52 -28
  46. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +2 -2
  47. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +8 -32
  48. package/src/components/Filters/Filter/Filter.tsx +3 -0
  49. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.spec.tsx +16 -1
  50. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.tsx +13 -1
  51. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +34 -30
  52. package/src/components/List/List.stories.tsx +2 -1
  53. package/src/components/List/List.tsx +5 -1
  54. package/src/components/List/ListRow/ListRow.scss +11 -4
  55. package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
  56. package/src/components/List/ListRow/ListRow.tsx +44 -17
  57. package/src/components/List/ListRow/ListRowLoader.tsx +2 -2
  58. package/src/components/PageHeader/PageHeader.model.ts +10 -12
  59. package/src/components/PageHeader/PageHeader.scss +7 -3
  60. package/src/components/PageHeader/PageHeader.spec.tsx +28 -86
  61. package/src/components/PageHeader/PageHeader.stories.tsx +32 -7
  62. package/src/components/PageHeader/PageHeader.tsx +50 -77
  63. package/src/components/PageHeader/{PageHeaderBulkActions/PageHeaderBulkActions.scss → PageHeaderActionsGroup/PageHeaderActionsGroup.scss} +21 -21
  64. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +105 -0
  65. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +224 -0
  66. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +13 -0
  67. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +30 -0
  68. package/src/components/PageHeader/index.ts +1 -1
  69. package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +0 -1
  70. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts +0 -22
  71. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts.map +0 -1
  72. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.spec.tsx +0 -369
  73. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.tsx +0 -188
  74. /package/src/{components/DynamicDataList/helpers/generateId.ts → utils/GenerateId.ts} +0 -0
@@ -3,4 +3,4 @@
3
3
  * @returns UUID
4
4
  */
5
5
  export declare const uuid: () => string;
6
- //# sourceMappingURL=generateId.d.ts.map
6
+ //# sourceMappingURL=GenerateId.d.ts.map
@@ -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.1",
3
+ "version": "0.49.0-rc.11",
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.1",
35
+ "@axinom/mosaic-core": "^0.4.22-rc.11",
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": "12934f9943fdd118ce15a3f5f274826e649d23fc"
108
+ "gitHead": "9af527f2f2a4e4442b81530d50f4787ea5b9e4db"
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
- jest.mock('./helpers/generateId', () => ({
12
+
13
+ jest.mock('../../utils/GenerateId', () => ({
13
14
  uuid: jest.fn().mockReturnValue('test-uuid'),
14
15
  }));
15
16
 
@@ -44,7 +44,7 @@ const generateData = (amount: number): DynamicListStoryData[] =>
44
44
  position: amount - index, // Position and ID is jumbled for demonstration purposes
45
45
  id: index,
46
46
  desc: `Description ${index}: ${faker.lorem.words(
47
- faker.datatype.number({ min: 10, max: 20 }),
47
+ faker.datatype.number({ min: 10, max: 50 }),
48
48
  )}`,
49
49
  title: `Item ${index}: ${faker.random.words(
50
50
  faker.datatype.number({ min: 1, max: 3 }),
@@ -87,6 +87,7 @@ const groups = createGroups({
87
87
  'headerRowActionSize',
88
88
  'horizontalTextAlign',
89
89
  'verticalTextAlign',
90
+ 'textWrap',
90
91
  'rowClassNameProvider',
91
92
  ],
92
93
  });
@@ -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';
@@ -53,6 +53,8 @@ export interface DynamicDataListProps<T extends Data> {
53
53
  horizontalTextAlign?: 'left' | 'right' | 'center';
54
54
  /** Vertical alignment of text */
55
55
  verticalTextAlign?: 'start' | 'center' | 'end';
56
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
57
+ textWrap?: boolean;
56
58
  /** Property that contains the value used in reordering the list (default: undefined) */
57
59
  positionPropertyName?: keyof T;
58
60
  /** If sets, sets the label for the position column (default: 'Position') */
@@ -127,6 +129,7 @@ export const DynamicDataList = <T extends Data>({
127
129
  stickyHeader = true,
128
130
  disabled = false,
129
131
  className = '',
132
+ textWrap = false,
130
133
  onChange = noop,
131
134
  onAddTransformData = (data) => data as T,
132
135
  rowClassNameProvider,
@@ -233,6 +236,7 @@ export const DynamicDataList = <T extends Data>({
233
236
  actionSize={listRowActionSize}
234
237
  horizontalTextAlign={horizontalTextAlign}
235
238
  verticalTextAlign={verticalTextAlign}
239
+ textWrap={textWrap}
236
240
  allowRemove={allowNewData}
237
241
  positionKey={positionPropertyName}
238
242
  allowDragging={allowRowDragging}
@@ -9,7 +9,6 @@
9
9
 
10
10
  display: grid;
11
11
  padding: 1px 0px 1px 0px;
12
- grid-auto-rows: var(--dynamic-list-row-height, $dynamic-list-row-height);
13
12
  column-gap: var(--dynamic-list-column-gap, $dynamic-list-column-gap);
14
13
 
15
14
  border-bottom: var(--dynamic-list-row-border, $dynamic-list-row-border);
@@ -26,7 +25,7 @@
26
25
  align-items: center;
27
26
 
28
27
  .wrapper {
29
- min-height: 50px;
28
+ min-height: var(--dynamic-list-row-height, $dynamic-list-row-height);
30
29
  min-width: 100%;
31
30
  display: grid;
32
31
  align-items: center;
@@ -41,9 +40,16 @@
41
40
  max-width: 100%;
42
41
  max-height: 100%;
43
42
 
44
- white-space: nowrap;
45
- text-overflow: ellipsis;
46
- overflow: hidden;
43
+ span {
44
+ padding: 5px 0;
45
+ display: grid;
46
+ }
47
+
48
+ &.nowrap {
49
+ white-space: nowrap;
50
+ text-overflow: ellipsis;
51
+ overflow: hidden;
52
+ }
47
53
  }
48
54
 
49
55
  .position,
@@ -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(
@@ -28,6 +28,8 @@ export interface DynamicListRowProps<T extends Data> {
28
28
  horizontalTextAlign?: 'left' | 'right' | 'center';
29
29
  /** Vertical alignment of text */
30
30
  verticalTextAlign?: 'start' | 'center' | 'end';
31
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
32
+ textWrap?: boolean;
31
33
  /** If set to true, the remove action button will be rendered (default: undefined) */
32
34
  allowRemove?: boolean;
33
35
  /** If set to true, editable fields will be highlighted and row click events will be fired (default: false) */
@@ -85,6 +87,7 @@ export const DynamicListRow = <T extends Data>({
85
87
  showPositionColumn = false,
86
88
  showActionColumn = false,
87
89
  allowEditing = false,
90
+ textWrap = false,
88
91
  }: PropsWithChildren<DynamicListRowProps<T>>): JSX.Element => {
89
92
  const customStyles = {
90
93
  gridAutoRows: `minmax(50px, ${rowHeight})`,
@@ -170,7 +173,7 @@ export const DynamicListRow = <T extends Data>({
170
173
  </div>
171
174
  )}
172
175
  {columns.map((column: DynamicListColumn<T>) => {
173
- const columnData: React.ReactNode = renderData<T>(column, data);
176
+ const { columnData, tooltip } = renderData<T>(column, data);
174
177
 
175
178
  return (
176
179
  <div
@@ -179,14 +182,16 @@ export const DynamicListRow = <T extends Data>({
179
182
  column.dataEntryRender !== undefined && allowEditing,
180
183
  })}
181
184
  key={column.key ?? (column.propertyName as string)}
185
+ style={{
186
+ justifyContent: horizontalTextAlign,
187
+ alignItems: verticalTextAlign,
188
+ }}
182
189
  >
183
190
  <div
184
- className={classes.column}
185
- title={
186
- column.tooltip !== false
187
- ? getTooltipText(columnData)
188
- : undefined
189
- }
191
+ className={clsx(classes.column, {
192
+ [classes.nowrap]: !textWrap,
193
+ })}
194
+ title={tooltip}
190
195
  data-test-id={`dynamic-list-property:${
191
196
  column.propertyName as string
192
197
  }`}
@@ -226,22 +231,26 @@ export const DynamicListRow = <T extends Data>({
226
231
  </div>
227
232
  );
228
233
  };
229
-
230
- const renderData = function <T extends Data>(
234
+ const renderData = <T extends Data>(
231
235
  column: DynamicListColumn<T>,
232
236
  data: T,
233
- ): React.ReactNode {
237
+ ): { columnData: React.ReactNode; tooltip: string | undefined } => {
238
+ const getTooltip = (value: unknown): string | undefined =>
239
+ column.tooltip !== false ? getTooltipText(value) : undefined;
240
+
234
241
  if (!column.propertyName) {
235
- return column.render!(undefined, data);
242
+ const columnData = column.render?.(undefined, data);
243
+ return { columnData, tooltip: getTooltip(columnData) };
236
244
  }
237
245
  const value: unknown = data[column.propertyName];
238
246
  if (column.render) {
239
- return column.render(value, data);
247
+ const columnData = column.render(value, data);
248
+ return { columnData, tooltip: getTooltip(columnData) };
240
249
  }
241
250
 
242
251
  if (value === null || value === undefined) {
243
- return '';
252
+ return { columnData: <span />, tooltip: undefined };
244
253
  }
245
254
 
246
- return String(value);
255
+ return { columnData: String(value), tooltip: getTooltip(String(value)) };
247
256
  };
@@ -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 { PageHeaderBulkActions } from '../PageHeader/PageHeaderBulkActions/PageHeaderBulkActions';
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
- wrapper.update();
170
- expect(wrapper.find(PageHeader).prop('bulkActionsDisabled')).toBe(true);
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
- expect(wrapper.find(PageHeader).prop('bulkActionsDisabled')).toBe(false);
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(wrapper.find(PageHeader).prop('bulkActionsDisabled')).toBe(true);
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 header = wrapper.find(PageHeader);
279
+ const bulkActions = wrapper.find(PageHeaderActionsGroup);
271
280
 
272
281
  await act(async () => {
273
- header.prop('bulkActions')?.[0].onClick?.();
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 header = wrapper.find(PageHeader);
313
+ const bulkActions = wrapper.find(PageHeaderActionsGroup);
305
314
  await act(async () => {
306
- header.prop('bulkActions')?.[0].onClick?.();
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 header = wrapper.find(PageHeader);
348
+ const bulkActions = wrapper.find(PageHeaderActionsGroup);
340
349
  await act(async () => {
341
- header.prop('bulkActions')?.[0].onClick?.();
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(PageHeaderBulkActions).prop('onBulkActionsToggled')!(true);
2029
-
2030
- wrapper.find(PageHeaderBulkActions).prop('onBulkActionsToggled')!(false);
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);
@@ -65,6 +65,7 @@ const groups = createGroups({
65
65
  'rowGap',
66
66
  'verticalTextAlign',
67
67
  'horizontalTextAlign',
68
+ 'textWrap',
68
69
  'headerRowActionSize',
69
70
  'listRowActionSize',
70
71
  ],
@@ -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 { PageHeader, PageHeaderActionProps } from '../PageHeader';
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';
@@ -88,6 +92,9 @@ export interface ExplorerProps<T extends Data> {
88
92
  /** Vertical alignment of text */
89
93
  verticalTextAlign?: 'start' | 'center' | 'end';
90
94
 
95
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: true) */
96
+ textWrap?: boolean;
97
+
91
98
  /** Defines when the loading of the next page is triggered. The number represents the number of row left, before a load is triggered. (default: 10) */
92
99
  loadingTriggerOffset?: number;
93
100
 
@@ -179,6 +186,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
179
186
  headerRowActionSize,
180
187
  horizontalTextAlign,
181
188
  verticalTextAlign,
189
+ textWrap,
182
190
 
183
191
  columns,
184
192
  filterOptions,
@@ -348,24 +356,48 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
348
356
  });
349
357
  };
350
358
 
351
- const pageHeaderActionsHandler = (): PageHeaderActionProps[] => {
352
- return (actions ?? []).map((action) => {
353
- return isPageHeaderNavigationAction(action)
354
- ? action
355
- : {
356
- ...action,
357
- onClick: async () => {
358
- try {
359
- const result = await action.onClick();
360
- if (result) {
361
- setStationMessage(errMsg(result));
359
+ const pageHeaderActionsHandler = (): PageHeaderActionItemProps[] => {
360
+ const headerActions: PageHeaderActionItemProps[] = [];
361
+
362
+ if (bulkActions && bulkActions.length > 0) {
363
+ headerActions.push({
364
+ label: 'Bulk Actions',
365
+ icon: IconName.Bulk,
366
+ kind: 'group',
367
+ actions: bulkActionsHandler(),
368
+ openActionsGroupOnStart: openBulkActionsOnStart,
369
+ onActionsGroupToggled: (isOpen) => {
370
+ setIsBulkOpen(isOpen);
371
+ onBulkActionsToggled(isOpen);
372
+ },
373
+ groupActionsDisabled:
374
+ itemSelection.items?.length === 0 || resultCount?.filtered === 0,
375
+ });
376
+ headerActions.push({ kind: 'spacer' });
377
+ }
378
+
379
+ actions?.forEach((action) => {
380
+ headerActions.push({
381
+ ...(isPageHeaderNavigationAction(action)
382
+ ? action
383
+ : {
384
+ ...action,
385
+ onClick: async () => {
386
+ try {
387
+ const result = await action.onClick();
388
+ if (result) {
389
+ setStationMessage(errMsg(result));
390
+ }
391
+ } catch (error) {
392
+ setStationMessage(errMsg(error, errAction));
362
393
  }
363
- } catch (error) {
364
- setStationMessage(errMsg(error, errAction));
365
- }
366
- },
367
- };
394
+ },
395
+ }),
396
+ kind: 'action',
397
+ });
368
398
  });
399
+
400
+ return headerActions;
369
401
  };
370
402
 
371
403
  const bulkActionsHandler = (): PageHeaderJsActionProps[] => {
@@ -421,16 +453,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
421
453
  <PageHeader
422
454
  title={title}
423
455
  subtitle={resultsTitle}
424
- actions={actions && pageHeaderActionsHandler()}
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
- }}
456
+ actions={pageHeaderActionsHandler()}
434
457
  setTabTitle={setTabTitle}
435
458
  />
436
459
  {StationMessage}
@@ -460,6 +483,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
460
483
  headerRowActionSize={headerRowActionSize}
461
484
  horizontalTextAlign={horizontalTextAlign}
462
485
  verticalTextAlign={verticalTextAlign}
486
+ textWrap={textWrap}
463
487
  keyProperty={keyProperty}
464
488
  showActionButton={Boolean(generateItemLink) || Boolean(onItemClicked)} // or hard code to `true`?
465
489
  selectionMode={mode}
@@ -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 { PageHeader } from '../../PageHeader';
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(PageHeaderBulkActions);
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(PageHeaderBulkActions);
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', async () => {
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
  });