@axinom/mosaic-ui 0.35.0 → 0.36.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) 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/DynamicDataList.model.d.ts +6 -1
  4. package/dist/components/DynamicDataList/DynamicDataList.model.d.ts.map +1 -1
  5. package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts +7 -3
  6. package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts.map +1 -1
  7. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts +4 -1
  8. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
  9. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.d.ts.map +1 -1
  10. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.d.ts +7 -1
  11. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.d.ts.map +1 -1
  12. package/dist/components/DynamicDataList/helpers/useDataHandler.d.ts +1 -0
  13. package/dist/components/DynamicDataList/helpers/useDataHandler.d.ts.map +1 -1
  14. package/dist/components/DynamicDataList/helpers/useRowEditing.d.ts +8 -0
  15. package/dist/components/DynamicDataList/helpers/useRowEditing.d.ts.map +1 -0
  16. package/dist/index.es.js +4 -4
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.js +4 -4
  19. package/dist/index.js.map +1 -1
  20. package/package.json +3 -3
  21. package/src/components/DynamicDataList/DynamicDataList.model.ts +7 -2
  22. package/src/components/DynamicDataList/DynamicDataList.spec.tsx +5 -0
  23. package/src/components/DynamicDataList/DynamicDataList.stories.tsx +37 -0
  24. package/src/components/DynamicDataList/DynamicDataList.tsx +58 -28
  25. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.scss +0 -1
  26. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.spec.tsx +2 -44
  27. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.tsx +36 -28
  28. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +12 -0
  29. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +23 -12
  30. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.spec.ts +26 -1
  31. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.ts +8 -0
  32. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.ts +8 -0
  33. package/src/components/DynamicDataList/helpers/useDataHandler.ts +11 -1
  34. package/src/components/DynamicDataList/helpers/useRowEditing.ts +30 -0
  35. package/src/styles/variables.scss +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.35.0",
3
+ "version": "0.36.0-rc.0",
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.8",
35
+ "@axinom/mosaic-core": "^0.4.9-rc.0",
36
36
  "@faker-js/faker": "^7.4.0",
37
37
  "@popperjs/core": "^2.9.2",
38
38
  "clsx": "^1.1.0",
@@ -104,5 +104,5 @@
104
104
  "publishConfig": {
105
105
  "access": "public"
106
106
  },
107
- "gitHead": "cb1823de8e408b481091bfe0b0107815e8b49880"
107
+ "gitHead": "b232d67a1f8a7b0b696fb1e962cf353e09445af0"
108
108
  }
@@ -25,12 +25,17 @@ export interface DynamicListColumn<T extends Data> {
25
25
  */
26
26
  dataEntryRender?: DynamicListDataEntryRenderer;
27
27
 
28
+ /**
29
+ * Set if data editing is allowed
30
+ * Setting fieldType to either 'input' or 'select' will generate an element appropriate to that type.
31
+ * If not set, will use the dataEntryRender value.
32
+ */
33
+ dataEditRender?: DynamicListDataEntryRenderer;
34
+
28
35
  /** Performs a transformation on the value before being added to the list */
29
36
  onAddTransformer?: (value: unknown, data: T) => unknown;
30
37
  }
31
38
 
32
- export type DynamicListElevationOptions = 'above' | 'below';
33
-
34
39
  export type DynamicListDataEntryRenderer = (
35
40
  /** Value to render. */
36
41
  currentValue: unknown,
@@ -84,6 +84,7 @@ describe('DynamicDataList', () => {
84
84
  mockUseDataHandler.mockReturnValue({
85
85
  removeItem: jest.fn(),
86
86
  addItem: jest.fn(),
87
+ updateItem: jest.fn(),
87
88
  canAddItems: false,
88
89
  items: defaultData,
89
90
  nextPosition: 2,
@@ -302,6 +303,7 @@ describe('DynamicDataList', () => {
302
303
  mockUseDataHandler.mockReturnValue({
303
304
  removeItem: jest.fn(),
304
305
  addItem: jest.fn(),
306
+ updateItem: jest.fn(),
305
307
  canAddItems: true,
306
308
  items: defaultData,
307
309
  nextPosition: 2,
@@ -322,6 +324,7 @@ describe('DynamicDataList', () => {
322
324
  mockUseDataHandler.mockReturnValue({
323
325
  removeItem: jest.fn(),
324
326
  addItem: jest.fn(),
327
+ updateItem: jest.fn(),
325
328
  canAddItems: true,
326
329
  items: defaultData,
327
330
  nextPosition: mockLimit,
@@ -340,6 +343,7 @@ describe('DynamicDataList', () => {
340
343
  mockUseDataHandler.mockReturnValue({
341
344
  removeItem: jest.fn(),
342
345
  addItem: addItemSpy,
346
+ updateItem: jest.fn(),
343
347
  canAddItems: true,
344
348
  items: defaultData,
345
349
  nextPosition: 2,
@@ -369,6 +373,7 @@ describe('DynamicDataList', () => {
369
373
  mockUseDataHandler.mockReturnValue({
370
374
  removeItem: removeItemSpy,
371
375
  addItem: jest.fn(),
376
+ updateItem: jest.fn(),
372
377
  canAddItems: true,
373
378
  items: defaultData,
374
379
  nextPosition: 2,
@@ -66,6 +66,7 @@ const groups = createGroups({
66
66
  Behavior: [
67
67
  'allowRowDragging',
68
68
  'allowNewData',
69
+ 'allowEditing',
69
70
  'showHeader',
70
71
  'allowAddAndRemove',
71
72
  'allowReordering',
@@ -373,3 +374,39 @@ export const WithInlineMenu: StoryObj<StoryDDLType> = {
373
374
  ],
374
375
  },
375
376
  };
377
+
378
+ export const Editable: StoryObj<StoryDDLType> = {
379
+ name: 'Editable',
380
+ args: {
381
+ ...defaultProps,
382
+ columns: [
383
+ {
384
+ propertyName: 'id',
385
+ label: 'Id',
386
+ size: '50px',
387
+ dataEntryRender: createInputRenderer({
388
+ placeholder: 'Enter Id',
389
+ }),
390
+ },
391
+ {
392
+ propertyName: 'title',
393
+ label: 'Title',
394
+ dataEntryRender: createInputRenderer({
395
+ placeholder: 'Enter Title',
396
+ }),
397
+ },
398
+ {
399
+ propertyName: 'desc',
400
+ label: 'Description',
401
+ dataEntryRender: createInputRenderer(),
402
+ },
403
+ ],
404
+ allowNewData: true,
405
+ allowEditing: true,
406
+ defaultValuesForNewData: { desc: 'Description' },
407
+ rowValidationSchema: Yup.object({
408
+ id: Yup.number().required(),
409
+ title: Yup.string().required().max(25).label('Title'),
410
+ }),
411
+ },
412
+ };
@@ -16,6 +16,7 @@ import { DynamicListColumn } from './DynamicDataList.model';
16
16
  import classes from './DynamicDataList.scss';
17
17
  import {
18
18
  DynamicListDataEntry,
19
+ DynamicListDataEntryMode,
19
20
  DynamicListDataEntryProps,
20
21
  } from './DynamicListDataEntry/DynamicListDataEntry';
21
22
  import { DynamicListHeader } from './DynamicListHeader/DynamicListHeader';
@@ -24,6 +25,7 @@ import { uuid } from './helpers/generateId';
24
25
  import { useColumnDefs } from './helpers/useColumnDefs';
25
26
  import { useDataHandler } from './helpers/useDataHandler';
26
27
  import { useRowAnimation } from './helpers/useRowAnimation';
28
+ import { useRowEditing } from './helpers/useRowEditing';
27
29
 
28
30
  export interface DynamicDataListProps<T extends Data> {
29
31
  /**
@@ -63,6 +65,8 @@ export interface DynamicDataListProps<T extends Data> {
63
65
  allowRowDragging?: boolean;
64
66
  /** Whether or not new data can be added (default: false) */
65
67
  allowNewData?: boolean;
68
+ /** Whether or not existing data can be edited (default: false) */
69
+ allowEditing?: boolean;
66
70
  /** Custom DynamicListDataEntry component which will be rendered instead of the default component */
67
71
  customDataEntry?: React.FC<DynamicListDataEntryProps<T>>;
68
72
  /** Optional Yup validation object for validating new data */
@@ -115,6 +119,7 @@ export const DynamicDataList = <T extends Data>({
115
119
  allowReordering = true,
116
120
  allowRowDragging = true,
117
121
  allowNewData = false,
122
+ allowEditing = false,
118
123
  customDataEntry: CustomDataEntry,
119
124
  maxItemLimit,
120
125
  rowValidationSchema,
@@ -137,7 +142,7 @@ export const DynamicDataList = <T extends Data>({
137
142
  !!inlineMenuActions,
138
143
  );
139
144
 
140
- const { addItem, removeItem, canAddItems, items, nextPosition } =
145
+ const { addItem, removeItem, updateItem, canAddItems, items, nextPosition } =
141
146
  useDataHandler(
142
147
  value,
143
148
  positionPropertyName,
@@ -147,6 +152,11 @@ export const DynamicDataList = <T extends Data>({
147
152
  onAddTransformData,
148
153
  );
149
154
 
155
+ const { editingRow, isEditing, startEditing, finishEditing } = useRowEditing(
156
+ allowEditing,
157
+ updateItem,
158
+ );
159
+
150
160
  const customStyles = {
151
161
  gridRowGap: rowGap,
152
162
  minWidth: minimumWidth,
@@ -204,33 +214,54 @@ export const DynamicDataList = <T extends Data>({
204
214
  idx: number,
205
215
  provided?: DraggableProvided,
206
216
  isDragging = false,
207
- ): JSX.Element => (
208
- <AnimatedRow data={data} key={idx}>
209
- <DynamicListRow<T>
217
+ ): JSX.Element =>
218
+ editingRow !== data ? (
219
+ <AnimatedRow data={data} key={idx}>
220
+ <DynamicListRow<T>
221
+ key={idx}
222
+ columns={columns}
223
+ data={data}
224
+ columnSizes={columnSizes}
225
+ columnGap={columnGap}
226
+ rowHeight={listRowHeight}
227
+ actionSize={listRowActionSize}
228
+ horizontalTextAlign={horizontalTextAlign}
229
+ verticalTextAlign={verticalTextAlign}
230
+ allowRemove={allowNewData}
231
+ positionKey={positionPropertyName}
232
+ allowDragging={allowRowDragging}
233
+ onActionClicked={removeItem}
234
+ onPositionInputChanged={onPositionInputChangedHandler}
235
+ inlineMenuActions={inlineMenuActions}
236
+ rowClassNameProvider={rowClassNameProvider}
237
+ disabled={disabled}
238
+ provided={provided}
239
+ dragging={isDragging}
240
+ showActionColumn={showActionColumn}
241
+ showPositionColumn={showPositionColumn}
242
+ allowEditing={allowEditing}
243
+ onRowClicked={startEditing}
244
+ />
245
+ </AnimatedRow>
246
+ ) : CustomDataEntry ? (
247
+ <CustomDataEntry
210
248
  key={idx}
211
- columns={columns}
212
- data={data}
213
- columnSizes={columnSizes}
214
- columnGap={columnGap}
215
- rowHeight={listRowHeight}
216
- actionSize={listRowActionSize}
217
- horizontalTextAlign={horizontalTextAlign}
218
- verticalTextAlign={verticalTextAlign}
219
- allowRemove={allowNewData}
220
- positionKey={positionPropertyName}
221
- allowDragging={allowRowDragging}
222
- onActionClicked={(data) => removeItem(data)}
223
- onPositionInputChanged={onPositionInputChangedHandler}
224
- inlineMenuActions={inlineMenuActions}
225
- rowClassNameProvider={rowClassNameProvider}
226
- disabled={disabled}
227
- provided={provided}
228
- dragging={isDragging}
229
- showActionColumn={showActionColumn}
230
- showPositionColumn={showPositionColumn}
249
+ {...DynamicListDataEntryProps}
250
+ defaultValuesForNewData={data}
251
+ newDataPosition={undefined}
252
+ onActionClicked={finishEditing}
253
+ mode={DynamicListDataEntryMode.Edit}
231
254
  />
232
- </AnimatedRow>
233
- );
255
+ ) : (
256
+ <DynamicListDataEntry<T>
257
+ key={idx}
258
+ {...DynamicListDataEntryProps}
259
+ defaultValuesForNewData={data}
260
+ newDataPosition={undefined}
261
+ onActionClicked={finishEditing}
262
+ mode={DynamicListDataEntryMode.Edit}
263
+ />
264
+ );
234
265
 
235
266
  const DynamicListDataEntryProps: DynamicListDataEntryProps<T> = {
236
267
  columns,
@@ -241,7 +272,6 @@ export const DynamicDataList = <T extends Data>({
241
272
  actionSize: listRowActionSize,
242
273
  horizontalTextAlign,
243
274
  verticalTextAlign,
244
- allowAdd: allowNewData,
245
275
  allowReordering,
246
276
  allowDragging: allowRowDragging,
247
277
  newDataPosition: nextPosition,
@@ -287,7 +317,7 @@ export const DynamicDataList = <T extends Data>({
287
317
  ) : (
288
318
  <DynamicListDataEntry<T> {...DynamicListDataEntryProps} />
289
319
  ))}
290
- {allowReordering ? (
320
+ {allowReordering && !isEditing ? (
291
321
  <DragDropContext onDragEnd={onDragEndHandler}>
292
322
  <Droppable droppableId={uuid()}>
293
323
  {(provided) => (
@@ -2,7 +2,6 @@
2
2
 
3
3
  .container {
4
4
  width: 100%;
5
- height: 100%;
6
5
 
7
6
  display: grid;
8
7
  grid-template-rows: 1fr;
@@ -45,7 +45,6 @@ const defaultProps: DynamicListDataEntryProps<TestData> = {
45
45
  verticalTextAlign: 'center',
46
46
  positionKey: 'position',
47
47
  allowReordering: false,
48
- allowAdd: false,
49
48
  };
50
49
 
51
50
  const MockRenderer: DynamicListDataEntryRenderer = (
@@ -139,9 +138,7 @@ describe('DynamicListDataEntry', () => {
139
138
  });
140
139
 
141
140
  it(`Button component should have type of 'button'`, () => {
142
- const wrapper = mount(
143
- <DynamicListDataEntry {...defaultProps} allowAdd={true} />,
144
- );
141
+ const wrapper = mount(<DynamicListDataEntry {...defaultProps} />);
145
142
 
146
143
  const button = wrapper.find(Button);
147
144
 
@@ -220,40 +217,9 @@ describe('DynamicListDataEntry', () => {
220
217
  expect(dragIcon.exists()).toBe(false);
221
218
  });
222
219
 
223
- it(`doesn't display an action button by default`, () => {
224
- const wrapper = shallow(
225
- <DynamicListDataEntry
226
- columns={defaultColumns}
227
- columnSizes={defaultProps.columnSizes}
228
- />,
229
- );
230
-
231
- const button = wrapper.find(Button);
232
-
233
- expect(button.exists()).toBe(false);
234
- });
235
-
236
- it(`displays an action button if 'allowAdd' is set to true`, () => {
237
- const wrapper = shallow(
238
- <DynamicListDataEntry
239
- columns={defaultColumns}
240
- columnSizes={defaultProps.columnSizes}
241
- allowAdd={true}
242
- />,
243
- );
244
-
245
- const button = wrapper.find(Button);
246
-
247
- expect(button.exists()).toBe(true);
248
- });
249
-
250
220
  it(`renders "disabled" attribute for action button when set`, () => {
251
221
  const wrapper = mount(
252
- <DynamicListDataEntry
253
- {...defaultProps}
254
- allowAdd={true}
255
- disabled={true}
256
- />,
222
+ <DynamicListDataEntry {...defaultProps} disabled={true} />,
257
223
  );
258
224
 
259
225
  const button = wrapper.find(Button);
@@ -307,7 +273,6 @@ describe('DynamicListDataEntry', () => {
307
273
  <DynamicListDataEntry
308
274
  columns={defaultColumns}
309
275
  columnSizes={defaultProps.columnSizes}
310
- allowAdd={true}
311
276
  defaultValuesForNewData={defaultData}
312
277
  onActionClicked={spy}
313
278
  />,
@@ -332,7 +297,6 @@ describe('DynamicListDataEntry', () => {
332
297
  <DynamicListDataEntry
333
298
  columns={defaultColumns}
334
299
  columnSizes={defaultProps.columnSizes}
335
- allowAdd={true}
336
300
  onActionClicked={spy}
337
301
  rowValidationSchema={Yup.object<ObjectSchemaDefinition<TestData>>({
338
302
  id: Yup.number().required(),
@@ -364,7 +328,6 @@ describe('DynamicListDataEntry', () => {
364
328
  <DynamicListDataEntry
365
329
  columns={columnWithRenderer}
366
330
  columnSizes={defaultProps.columnSizes}
367
- allowAdd={true}
368
331
  onActionClicked={spy}
369
332
  />,
370
333
  );
@@ -401,7 +364,6 @@ describe('DynamicListDataEntry', () => {
401
364
  <DynamicListDataEntry
402
365
  columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
403
366
  columnSizes={defaultProps.columnSizes}
404
- allowAdd={true}
405
367
  onActionClicked={spy}
406
368
  positionKey={mockPosKey}
407
369
  allowReordering={true}
@@ -445,7 +407,6 @@ describe('DynamicListDataEntry', () => {
445
407
  <DynamicListDataEntry
446
408
  columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
447
409
  columnSizes={defaultProps.columnSizes}
448
- allowAdd={true}
449
410
  onActionClicked={spy}
450
411
  positionKey={mockPosKey}
451
412
  allowReordering={false}
@@ -486,7 +447,6 @@ describe('DynamicListDataEntry', () => {
486
447
  <DynamicListDataEntry
487
448
  columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
488
449
  columnSizes={defaultProps.columnSizes}
489
- allowAdd={true}
490
450
  onActionClicked={spy}
491
451
  positionKey={mockPosKey}
492
452
  allowReordering={true}
@@ -529,7 +489,6 @@ describe('DynamicListDataEntry', () => {
529
489
  <DynamicListDataEntry
530
490
  columns={[...columnWithRenderer, { propertyName: mockPosKey }]}
531
491
  columnSizes={defaultProps.columnSizes}
532
- allowAdd={true}
533
492
  onActionClicked={spy}
534
493
  allowReordering={true}
535
494
  newDataPosition={mockNewPos}
@@ -578,7 +537,6 @@ describe('DynamicListDataEntry', () => {
578
537
  <DynamicListDataEntry
579
538
  columns={mockColumns}
580
539
  columnSizes={defaultProps.columnSizes}
581
- allowAdd={true}
582
540
  onActionClicked={spy}
583
541
  />,
584
542
  );
@@ -15,7 +15,10 @@ import { IconName, Icons } from '../../Icons';
15
15
  import { DynamicListColumn } from '../DynamicDataList.model';
16
16
  import classes from './DynamicListDataEntry.scss';
17
17
 
18
- // TODO: Add sizing for DragIcon and Input container. Similar to ActionButton sizing
18
+ export enum DynamicListDataEntryMode {
19
+ Add,
20
+ Edit,
21
+ }
19
22
 
20
23
  export interface DynamicListDataEntryProps<T extends Data> {
21
24
  /** The column definition */
@@ -34,8 +37,6 @@ export interface DynamicListDataEntryProps<T extends Data> {
34
37
  verticalTextAlign?: 'start' | 'center' | 'end';
35
38
  /** Property name that is used to determine data position (default: undefined) */
36
39
  positionKey?: keyof T;
37
- /** If set to true, the add action button will be rendered (default: undefined) */
38
- allowAdd?: boolean;
39
40
  /** Whether add new item button should be disabled */
40
41
  disabled?: boolean;
41
42
  /** If set to true, rows can be be repositioned using the input field (default: undefined) */
@@ -58,6 +59,8 @@ export interface DynamicListDataEntryProps<T extends Data> {
58
59
  showHeader?: boolean;
59
60
  /** If set to true, the position column will be shown (default: false) */
60
61
  showPositionColumn?: boolean;
62
+ /** Determines if the component is in Add or Edit mode (default: Add) */
63
+ mode?: DynamicListDataEntryMode;
61
64
  /** Emits when the action button is clicked. Data is supplied as a parameter */
62
65
  onActionClicked?: (data: T) => void;
63
66
  /** override default add button (+) */
@@ -73,7 +76,6 @@ export const DynamicListDataEntry = <T extends Data>({
73
76
  horizontalTextAlign,
74
77
  verticalTextAlign,
75
78
  positionKey,
76
- allowAdd,
77
79
  disabled = false,
78
80
  allowReordering,
79
81
  allowDragging,
@@ -85,6 +87,7 @@ export const DynamicListDataEntry = <T extends Data>({
85
87
  sticky = true,
86
88
  showHeader,
87
89
  showPositionColumn = false,
90
+ mode = DynamicListDataEntryMode.Add,
88
91
  onActionClicked = noop,
89
92
  customAddButton,
90
93
  }: PropsWithChildren<DynamicListDataEntryProps<T>>): JSX.Element => {
@@ -221,29 +224,31 @@ export const DynamicListDataEntry = <T extends Data>({
221
224
  )}
222
225
  </div>
223
226
  ))}
224
- {allowAdd && (
225
- <div className={clsx(classes.actionButtonContainer)}>
226
- {(customAddButton && customAddButton(onAddItemHandler)) || (
227
- <Button
228
- type="button"
229
- className={clsx(classes.actionButton)}
230
- icon={IconName.Plus}
231
- buttonContext={
232
- actionButtonContext === undefined
233
- ? isDirty
234
- ? ButtonContext.Active
235
- : ButtonContext.Icon
236
- : actionButtonContext
237
- }
238
- height={actionSize}
239
- width={actionSize}
240
- onButtonClicked={() => onAddItemHandler()}
241
- dataTestId="dynamic-list-add-button"
242
- disabled={disabled}
243
- />
244
- )}
245
- </div>
246
- )}
227
+ <div className={clsx(classes.actionButtonContainer)}>
228
+ {(customAddButton && customAddButton(onAddItemHandler)) || (
229
+ <Button
230
+ type="button"
231
+ className={clsx(classes.actionButton)}
232
+ icon={
233
+ mode === DynamicListDataEntryMode.Add
234
+ ? IconName.Plus
235
+ : IconName.Checkmark
236
+ }
237
+ buttonContext={
238
+ actionButtonContext === undefined
239
+ ? isDirty
240
+ ? ButtonContext.Active
241
+ : ButtonContext.Icon
242
+ : actionButtonContext
243
+ }
244
+ height={actionSize}
245
+ width={actionSize}
246
+ onButtonClicked={onAddItemHandler}
247
+ dataTestId="dynamic-list-add-button"
248
+ disabled={disabled}
249
+ />
250
+ )}
251
+ </div>
247
252
  </div>
248
253
  );
249
254
  };
@@ -254,8 +259,11 @@ const renderField = function <T extends Data>(
254
259
  error: string | undefined,
255
260
  onValueChanged: (value: unknown) => void,
256
261
  disabled = false,
262
+ mode: DynamicListDataEntryMode = DynamicListDataEntryMode.Add,
257
263
  ): React.ReactNode {
258
- if (column.dataEntryRender) {
264
+ if (mode === DynamicListDataEntryMode.Edit && column.dataEditRender) {
265
+ return column.dataEditRender(currentValue, error, onValueChanged, disabled);
266
+ } else if (column.dataEntryRender) {
259
267
  return column.dataEntryRender(
260
268
  currentValue,
261
269
  error,
@@ -21,6 +21,18 @@
21
21
  justify-items: left;
22
22
  align-items: center;
23
23
 
24
+ .wrapper {
25
+ min-height: 50px;
26
+ min-width: 100%;
27
+ display: grid;
28
+ align-items: center;
29
+ border: 1px solid transparent;
30
+ }
31
+
32
+ &:hover .editable {
33
+ border: 1px solid var(--input-hover-color, $input-hover-color);
34
+ }
35
+
24
36
  .column {
25
37
  max-width: 100%;
26
38
  max-height: 100%;
@@ -11,8 +11,6 @@ import { InlineMenu } from '../../InlineMenu';
11
11
  import { DynamicListColumn } from '../DynamicDataList.model';
12
12
  import classes from './DynamicListRow.scss';
13
13
 
14
- // TODO: Add sizing for DragIcon and Input container. Similar to ActionButton sizing
15
-
16
14
  export interface DynamicListRowProps<T extends Data> {
17
15
  /** The column definition */
18
16
  columns: DynamicListColumn<T>[];
@@ -32,6 +30,8 @@ export interface DynamicListRowProps<T extends Data> {
32
30
  verticalTextAlign?: 'start' | 'center' | 'end';
33
31
  /** If set to true, the remove action button will be rendered (default: undefined) */
34
32
  allowRemove?: boolean;
33
+ /** If set to true, editable fields will be highlighted and row click events will be fired (default: false) */
34
+ allowEditing?: boolean;
35
35
  /** Property name that is used to determine data position (default: undefined) */
36
36
  positionKey?: keyof T;
37
37
  /** If set to true, this component can be dragged (default: undefined) */
@@ -49,6 +49,7 @@ export interface DynamicListRowProps<T extends Data> {
49
49
  currenPosition: number,
50
50
  newPosition: number,
51
51
  ) => void;
52
+ onRowClicked?: (data: T) => void;
52
53
  /** Emits when the action button is clicked. Data is supplied as a parameter */
53
54
  onActionClicked?: (data: T) => void;
54
55
  /** CSS Class name for additional styles */
@@ -75,6 +76,7 @@ export const DynamicListRow = <T extends Data>({
75
76
  disabled,
76
77
  provided,
77
78
  onPositionInputChanged = noop,
79
+ onRowClicked = noop,
78
80
  onActionClicked = noop,
79
81
  className = '',
80
82
  rowClassNameProvider,
@@ -82,6 +84,7 @@ export const DynamicListRow = <T extends Data>({
82
84
  dragging = false,
83
85
  showPositionColumn = false,
84
86
  showActionColumn = false,
87
+ allowEditing = false,
85
88
  }: PropsWithChildren<DynamicListRowProps<T>>): JSX.Element => {
86
89
  const customStyles = {
87
90
  gridAutoRows: `minmax(50px, ${rowHeight})`,
@@ -130,6 +133,7 @@ export const DynamicListRow = <T extends Data>({
130
133
  ref={provided?.innerRef}
131
134
  {...provided?.draggableProps}
132
135
  style={customStyles}
136
+ onClick={() => allowEditing && onRowClicked(data)}
133
137
  >
134
138
  {showPositionColumn && (
135
139
  <div className={classes.position}>
@@ -169,18 +173,25 @@ export const DynamicListRow = <T extends Data>({
169
173
 
170
174
  return (
171
175
  <div
176
+ className={clsx(classes.wrapper, {
177
+ [classes.editable]:
178
+ column.dataEntryRender !== undefined && allowEditing,
179
+ })}
172
180
  key={column.propertyName as string}
173
- className={classes.column}
174
- title={
175
- column.tooltip !== false
176
- ? getTooltipText(data[column.propertyName])
177
- : undefined
178
- }
179
- data-test-id={`dynamic-list-property:${
180
- column.propertyName as string
181
- }`}
182
181
  >
183
- {columnData}
182
+ <div
183
+ className={classes.column}
184
+ title={
185
+ column.tooltip !== false
186
+ ? getTooltipText(data[column.propertyName])
187
+ : undefined
188
+ }
189
+ data-test-id={`dynamic-list-property:${
190
+ column.propertyName as string
191
+ }`}
192
+ >
193
+ {columnData}
194
+ </div>
184
195
  </div>
185
196
  );
186
197
  })}
@@ -7,7 +7,10 @@ import {
7
7
  } from './DynamicListReducer.types';
8
8
 
9
9
  jest.mock('./DynamicListReducer.init');
10
- jest.mock('./DynamicListReducer.actions');
10
+ jest.mock('./DynamicListReducer.actions', () => ({
11
+ addItem: jest.fn(),
12
+ removeItem: jest.fn().mockReturnValue([]),
13
+ }));
11
14
 
12
15
  interface Data {
13
16
  position?: number;
@@ -69,6 +72,28 @@ describe('Dynamic List Reducer', () => {
69
72
  expect(DynamicListReducerInit).toHaveBeenCalledTimes(1);
70
73
  });
71
74
 
75
+ it('calls removeItem, addItem and DynamicListReducerInit when action is update', () => {
76
+ DynamicListReducer(initialState, {
77
+ type: DynamicListReducerActionType.Update,
78
+ currentItem: { position: 20, id: 20 },
79
+ newItem: { id: 10, position: 11 },
80
+ });
81
+
82
+ expect(removeItem).toHaveBeenCalledTimes(1);
83
+ expect(removeItem).toHaveBeenCalledWith(
84
+ { position: 20, id: 20 },
85
+ dataSet,
86
+ 'position',
87
+ );
88
+ expect(addItem).toHaveBeenCalledTimes(1);
89
+ expect(addItem).toHaveBeenCalledWith(
90
+ { id: 10, position: 11 },
91
+ [],
92
+ 'position',
93
+ );
94
+ expect(DynamicListReducerInit).toHaveBeenCalledTimes(1);
95
+ });
96
+
72
97
  it('calls DynamicListReducerInit with new items when action is reset', () => {
73
98
  DynamicListReducer(initialState, {
74
99
  type: DynamicListReducerActionType.Reset,
@@ -22,6 +22,14 @@ export const DynamicListReducer = <T extends Data>(
22
22
  case DynamicListReducerActionType.Remove:
23
23
  newItems = removeItem(action.item, state.items, positionPropertyName);
24
24
  break;
25
+ case DynamicListReducerActionType.Update:
26
+ newItems = removeItem(
27
+ action.currentItem,
28
+ state.items,
29
+ positionPropertyName,
30
+ );
31
+ newItems = addItem(action.newItem, newItems, positionPropertyName);
32
+ break;
25
33
  case DynamicListReducerActionType.Reset:
26
34
  newItems = action.items;
27
35
  positionPropertyName = action.positionPropertyName;