@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.
Files changed (60) hide show
  1. package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
  2. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
  3. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  4. package/dist/components/Filters/Filter/Filter.d.ts +2 -1
  5. package/dist/components/Filters/Filter/Filter.d.ts.map +1 -1
  6. package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts +2 -0
  7. package/dist/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.d.ts.map +1 -1
  8. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  9. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  10. package/dist/components/PageHeader/PageHeader.d.ts +9 -2
  11. package/dist/components/PageHeader/PageHeader.d.ts.map +1 -1
  12. package/dist/components/PageHeader/PageHeader.model.d.ts +11 -12
  13. package/dist/components/PageHeader/PageHeader.model.d.ts.map +1 -1
  14. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +35 -0
  15. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -0
  16. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +7 -0
  17. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +1 -0
  18. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +3 -0
  19. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +1 -0
  20. package/dist/components/PageHeader/index.d.ts +1 -1
  21. package/dist/components/PageHeader/index.d.ts.map +1 -1
  22. package/dist/index.es.js +4 -4
  23. package/dist/index.es.js.map +1 -1
  24. package/dist/index.js +4 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/{components/DynamicDataList/helpers/generateId.d.ts → utils/GenerateId.d.ts} +1 -1
  27. package/dist/utils/GenerateId.d.ts.map +1 -0
  28. package/package.json +3 -3
  29. package/src/components/DynamicDataList/DynamicDataList.spec.tsx +2 -1
  30. package/src/components/DynamicDataList/DynamicDataList.tsx +1 -1
  31. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
  32. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +4 -0
  33. package/src/components/Explorer/Explorer.spec.tsx +26 -16
  34. package/src/components/Explorer/Explorer.tsx +47 -28
  35. package/src/components/Explorer/NavigationExplorer/NavigationExplorer.spec.tsx +2 -2
  36. package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +8 -32
  37. package/src/components/Filters/Filter/Filter.tsx +3 -0
  38. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.spec.tsx +16 -1
  39. package/src/components/Filters/SelectionTypes/FreeTextFilter/FreeTextFilter.tsx +13 -1
  40. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +34 -30
  41. package/src/components/List/ListRow/ListRow.scss +0 -1
  42. package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
  43. package/src/components/List/ListRow/ListRow.tsx +5 -1
  44. package/src/components/PageHeader/PageHeader.model.ts +10 -12
  45. package/src/components/PageHeader/PageHeader.scss +7 -3
  46. package/src/components/PageHeader/PageHeader.spec.tsx +28 -86
  47. package/src/components/PageHeader/PageHeader.stories.tsx +32 -7
  48. package/src/components/PageHeader/PageHeader.tsx +50 -77
  49. package/src/components/PageHeader/{PageHeaderBulkActions/PageHeaderBulkActions.scss → PageHeaderActionsGroup/PageHeaderActionsGroup.scss} +21 -21
  50. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +105 -0
  51. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +224 -0
  52. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +13 -0
  53. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +30 -0
  54. package/src/components/PageHeader/index.ts +1 -1
  55. package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +0 -1
  56. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts +0 -22
  57. package/dist/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.d.ts.map +0 -1
  58. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.spec.tsx +0 -369
  59. package/src/components/PageHeader/PageHeaderBulkActions/PageHeaderBulkActions.tsx +0 -188
  60. /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.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.1",
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": "12934f9943fdd118ce15a3f5f274826e649d23fc"
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
- jest.mock('./helpers/generateId', () => ({
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 { 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);
@@ -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';
@@ -348,24 +352,48 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
348
352
  });
349
353
  };
350
354
 
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));
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
- } catch (error) {
364
- setStationMessage(errMsg(error, errAction));
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={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
- }}
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 { 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
  });
@@ -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
  };
@@ -29,7 +29,6 @@
29
29
 
30
30
  .cellWrapper {
31
31
  display: grid;
32
- align-content: center;
33
32
  width: 100%;
34
33
  height: 100%;
35
34
  }
@@ -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
  });