@axinom/mosaic-ui 0.49.0-rc.9 → 0.50.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 (30) 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/List/List.d.ts +2 -0
  8. package/dist/components/List/List.d.ts.map +1 -1
  9. package/dist/components/List/ListRow/ListRow.d.ts +4 -2
  10. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  11. package/dist/components/List/ListRow/ListRowLoader.d.ts +1 -1
  12. package/dist/components/List/ListRow/ListRowLoader.d.ts.map +1 -1
  13. package/dist/index.es.js +5 -4
  14. package/dist/index.es.js.map +1 -1
  15. package/dist/index.js +5 -4
  16. package/dist/index.js.map +1 -1
  17. package/package.json +3 -3
  18. package/src/components/DynamicDataList/DynamicDataList.stories.tsx +2 -1
  19. package/src/components/DynamicDataList/DynamicDataList.tsx +4 -0
  20. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +11 -5
  21. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +37 -0
  22. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +23 -14
  23. package/src/components/Explorer/Explorer.stories.tsx +1 -0
  24. package/src/components/Explorer/Explorer.tsx +5 -0
  25. package/src/components/List/List.stories.tsx +2 -1
  26. package/src/components/List/List.tsx +5 -1
  27. package/src/components/List/ListRow/ListRow.scss +11 -4
  28. package/src/components/List/ListRow/ListRow.spec.tsx +35 -0
  29. package/src/components/List/ListRow/ListRow.tsx +44 -17
  30. package/src/components/List/ListRow/ListRowLoader.tsx +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.49.0-rc.9",
3
+ "version": "0.50.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.22-rc.9",
35
+ "@axinom/mosaic-core": "^0.4.23-rc.0",
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": "a448af71faaf175908d9228bc1c1d32be65f9e2d"
108
+ "gitHead": "718cd482ef715fc9adde1ea97413b9ef512cf951"
109
109
  }
@@ -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
  });
@@ -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
  };
@@ -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
  ],
@@ -92,6 +92,9 @@ export interface ExplorerProps<T extends Data> {
92
92
  /** Vertical alignment of text */
93
93
  verticalTextAlign?: 'start' | 'center' | 'end';
94
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
+
95
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) */
96
99
  loadingTriggerOffset?: number;
97
100
 
@@ -183,6 +186,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
183
186
  headerRowActionSize,
184
187
  horizontalTextAlign,
185
188
  verticalTextAlign,
189
+ textWrap,
186
190
 
187
191
  columns,
188
192
  filterOptions,
@@ -479,6 +483,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
479
483
  headerRowActionSize={headerRowActionSize}
480
484
  horizontalTextAlign={horizontalTextAlign}
481
485
  verticalTextAlign={verticalTextAlign}
486
+ textWrap={textWrap}
482
487
  keyProperty={keyProperty}
483
488
  showActionButton={Boolean(generateItemLink) || Boolean(onItemClicked)} // or hard code to `true`?
484
489
  selectionMode={mode}
@@ -54,7 +54,7 @@ const generateData = (amount: number): ListStoryData[] =>
54
54
  generateItemArray(amount, (index) => ({
55
55
  id: index + 1,
56
56
  desc: `Description ${index + 1}: ${faker.lorem.words(
57
- faker.datatype.number({ min: 10, max: 20 }),
57
+ faker.datatype.number({ min: 10, max: 50 }),
58
58
  )}`,
59
59
  title: `Item ${index + 1}: ${faker.random.words(
60
60
  faker.datatype.number({ min: 1, max: 3 }),
@@ -92,6 +92,7 @@ const groups = createGroups({
92
92
  Styling: [
93
93
  'horizontalTextAlign',
94
94
  'verticalTextAlign',
95
+ 'textWrap',
95
96
  'minimumWidth',
96
97
  'columnGap',
97
98
  'rowGap',
@@ -65,6 +65,8 @@ export interface ListProps<T extends Data> {
65
65
  horizontalTextAlign?: 'left' | 'right' | 'center';
66
66
  /** Vertical alignment of text */
67
67
  verticalTextAlign?: 'start' | 'center' | 'end';
68
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
69
+ textWrap?: boolean;
68
70
  /**
69
71
  * Determines which select mode the list is in. (default: ListSelectMode.None)
70
72
  * If 'Single' is selected, a check mark will be rendered for each row of data
@@ -122,7 +124,7 @@ const ListRenderer = <T extends Data>(
122
124
  columnGap = '5px',
123
125
  rowGap = '0px',
124
126
  headerRowHeight = '44px',
125
- listRowHeight = '50px',
127
+ listRowHeight,
126
128
  listRowActionSize = '50px',
127
129
  headerRowActionSize = '28px',
128
130
  horizontalTextAlign = 'left',
@@ -134,6 +136,7 @@ const ListRenderer = <T extends Data>(
134
136
  selectionMode = ListSelectMode.None,
135
137
  enableSelectAll = true,
136
138
  enableSelectAllDeselect = false,
139
+ textWrap,
137
140
  onItemClicked = noop,
138
141
  onItemSelected = noop,
139
142
  onRequestMoreData = noop,
@@ -300,6 +303,7 @@ const ListRenderer = <T extends Data>(
300
303
  actionSize={listRowActionSize}
301
304
  horizontalTextAlign={horizontalTextAlign}
302
305
  verticalTextAlign={verticalTextAlign}
306
+ textWrap={textWrap}
303
307
  selectionMode={selectionMode}
304
308
  showActionButton={
305
309
  selectionMode === ListSelectMode.None &&
@@ -29,17 +29,24 @@
29
29
 
30
30
  .cellWrapper {
31
31
  display: grid;
32
- align-content: center;
33
32
  width: 100%;
34
33
  height: 100%;
35
34
  }
36
35
 
37
36
  .cell {
38
- white-space: nowrap;
39
- text-overflow: ellipsis;
40
- overflow: hidden;
41
37
  max-width: 100%;
42
38
  max-height: 100%;
39
+
40
+ span {
41
+ padding: 5px 0;
42
+ display: grid;
43
+ }
44
+
45
+ &.nowrap {
46
+ white-space: nowrap;
47
+ text-overflow: ellipsis;
48
+ overflow: hidden;
49
+ }
43
50
  }
44
51
 
45
52
  .actions {
@@ -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
  });
@@ -19,13 +19,15 @@ export interface ListRowProps<T extends Data> {
19
19
  /** Header row height */
20
20
  columnGap: string;
21
21
  /** List row height */
22
- rowHeight: string;
22
+ rowHeight?: string;
23
23
  /** Size of action button and checkbox */
24
24
  actionSize: string;
25
25
  /** Horizontal alignment of text */
26
26
  horizontalTextAlign: 'left' | 'right' | 'center';
27
27
  /** Vertical alignment of text */
28
28
  verticalTextAlign: 'start' | 'center' | 'end';
29
+ /** If set to true, column text overflow will be wrapped to a new line. Otherwise, it will be truncated with an ellipsis (default: false) */
30
+ textWrap?: boolean;
29
31
  /** List data */
30
32
  data: T;
31
33
  /** The column definition */
@@ -81,32 +83,54 @@ export const setLocale = (locale: string): void => {
81
83
 
82
84
  setLocale(navigator.language);
83
85
 
84
- function renderData<T extends Data>(
86
+ const renderData = <T extends Data>(
85
87
  column: Column<T>,
86
88
  rowData: T,
87
- ): React.ReactNode {
89
+ ): { columnData: React.ReactNode; tooltip: string | undefined } => {
90
+ const getTooltip = (value: unknown): string | undefined =>
91
+ column.tooltip !== false ? getTooltipText(value) : undefined;
92
+
88
93
  if (!column.propertyName) {
89
- return column.render!(undefined, rowData);
94
+ const columnData = column.render?.(undefined, rowData);
95
+ return {
96
+ columnData,
97
+ tooltip: getTooltip(columnData),
98
+ };
90
99
  }
91
100
  const value: unknown = rowData[column.propertyName];
92
101
  if (column.render) {
93
- return column.render(value, rowData);
102
+ const columnData = column.render(value, rowData);
103
+ return {
104
+ columnData,
105
+ tooltip: getTooltip(columnData),
106
+ };
94
107
  }
95
108
 
96
109
  if (value === null || value === undefined) {
97
- return '';
110
+ return { columnData: <span />, tooltip: undefined };
98
111
  }
99
112
 
100
113
  if (typeof value === 'number') {
101
- return numberFormatter.format(value);
114
+ const columnData = numberFormatter.format(value);
115
+ return {
116
+ columnData: <span>{columnData}</span>,
117
+ tooltip: getTooltip(columnData),
118
+ };
102
119
  }
103
120
 
104
121
  if (value instanceof Date) {
105
- return dateFormatter.format(value);
122
+ const columnData = dateFormatter.format(value);
123
+ return {
124
+ columnData: <span>{columnData}</span>,
125
+ tooltip: getTooltip(columnData),
126
+ };
106
127
  }
107
128
 
108
- return String(value);
109
- }
129
+ return {
130
+ columnData: <span>{String(value)}</span>,
131
+ tooltip: getTooltip(String(value)),
132
+ };
133
+ };
110
134
 
111
135
  /**
112
136
  * Renders the rows for the list component
@@ -128,6 +152,7 @@ export const ListRow = <T extends Data>({
128
152
  actionSize,
129
153
  horizontalTextAlign,
130
154
  verticalTextAlign,
155
+ textWrap = false,
131
156
  data,
132
157
  itemSelected = false,
133
158
  isTrigger = false,
@@ -143,7 +168,7 @@ export const ListRow = <T extends Data>({
143
168
  inlineMenuActions,
144
169
  }: PropsWithChildren<ListRowProps<T>>): JSX.Element => {
145
170
  const customRootStyles = {
146
- gridAutoRows: rowHeight,
171
+ gridAutoRows: `minmax(50px, ${rowHeight})`,
147
172
  gridColumnGap: columnGap,
148
173
  justifyItems: horizontalTextAlign,
149
174
  alignItems: verticalTextAlign,
@@ -199,7 +224,7 @@ export const ListRow = <T extends Data>({
199
224
  typeof onItemClicked !== 'function';
200
225
 
201
226
  const generateCells: JSX.Element[] = columns.map((column: Column<T>) => {
202
- const columnData: React.ReactNode = renderData<T>(column, data);
227
+ const { columnData, tooltip } = renderData<T>(column, data);
203
228
 
204
229
  return (
205
230
  <div
@@ -207,12 +232,14 @@ export const ListRow = <T extends Data>({
207
232
  className={classes.cellWrapper}
208
233
  >
209
234
  <div
210
- className={classes.cell}
211
- title={
212
- column.tooltip !== false ? getTooltipText(columnData) : undefined
213
- }
235
+ className={clsx(classes.cell, { [classes.nowrap]: !textWrap })}
236
+ title={tooltip}
214
237
  data-test-id={`list-entry-property:${column.propertyName as string}`}
215
- style={{ justifySelf: column.horizontalColumnAlign }}
238
+ style={{
239
+ justifySelf: column.horizontalColumnAlign, // Horizontal alignment based on column config
240
+ alignSelf: verticalTextAlign, // Vertical alignment based on props
241
+ textAlign: horizontalTextAlign, // Additional text alignment inside the cell
242
+ }}
216
243
  >
217
244
  {columnData}
218
245
  </div>
@@ -10,7 +10,7 @@ export interface ListRowLoaderProps<T extends Data> {
10
10
  /** Space between columns */
11
11
  columnGap: string;
12
12
  /** List row height */
13
- rowHeight: string;
13
+ rowHeight?: string;
14
14
  /** The column definition */
15
15
  columns: Column<T>[];
16
16
  }
@@ -25,7 +25,7 @@ export const ListRowLoader = <T extends Data>({
25
25
  const rows = 5;
26
26
 
27
27
  const customRowStyles = {
28
- gridAutoRows: rowHeight,
28
+ gridAutoRows: `minmax(50px, ${rowHeight})`,
29
29
  gridTemplateColumns: columnSizes,
30
30
  gridColumnGap: columnGap,
31
31
  } as React.CSSProperties;