@axinom/mosaic-ui 0.34.0-rc.3 → 0.34.0-rc.31

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 (88) hide show
  1. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  2. package/dist/components/Filters/Filter/Filter.d.ts +2 -1
  3. package/dist/components/Filters/Filter/Filter.d.ts.map +1 -1
  4. package/dist/components/Filters/Filters.d.ts.map +1 -1
  5. package/dist/components/Filters/Filters.model.d.ts +2 -0
  6. package/dist/components/Filters/Filters.model.d.ts.map +1 -1
  7. package/dist/components/FormElements/Checkbox/Checkbox.d.ts.map +1 -1
  8. package/dist/components/FormElements/Checkbox/CheckboxField.d.ts +1 -1
  9. package/dist/components/FormElements/Checkbox/CheckboxField.d.ts.map +1 -1
  10. package/dist/components/FormElements/CustomTags/CustomTagsField.d.ts.map +1 -1
  11. package/dist/components/FormElements/DateTimeField/DateTimeTextField.d.ts +1 -1
  12. package/dist/components/FormElements/DateTimeField/DateTimeTextField.d.ts.map +1 -1
  13. package/dist/components/FormElements/DynamicDataListControl/DynamicDataListField.d.ts.map +1 -1
  14. package/dist/components/FormElements/FileUploadControl/FileUploadControl.d.ts.map +1 -1
  15. package/dist/components/FormElements/FileUploadControl/FileUploadField.d.ts.map +1 -1
  16. package/dist/components/FormElements/MaskedSingleLineText/MaskedSingleLineTextField.d.ts +1 -1
  17. package/dist/components/FormElements/MaskedSingleLineText/MaskedSingleLineTextField.d.ts.map +1 -1
  18. package/dist/components/FormElements/SingleLineText/SingleLineText.d.ts.map +1 -1
  19. package/dist/components/List/List.d.ts.map +1 -1
  20. package/dist/components/List/List.model.d.ts +2 -0
  21. package/dist/components/List/List.model.d.ts.map +1 -1
  22. package/dist/components/List/ListHeader/ListHeader.d.ts +7 -1
  23. package/dist/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  24. package/dist/components/List/ListHeader/useResize.d.ts +18 -0
  25. package/dist/components/List/ListHeader/useResize.d.ts.map +1 -0
  26. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  27. package/dist/components/List/ListRow/ListRowLoader.d.ts +2 -2
  28. package/dist/components/List/ListRow/ListRowLoader.d.ts.map +1 -1
  29. package/dist/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.d.ts.map +1 -1
  30. package/dist/components/List/useColumnsSize.d.ts +21 -0
  31. package/dist/components/List/useColumnsSize.d.ts.map +1 -0
  32. package/dist/index.es.js +3 -3
  33. package/dist/index.es.js.map +1 -1
  34. package/dist/index.js +3 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/initialize.d.ts +3 -3
  37. package/dist/initialize.d.ts.map +1 -1
  38. package/package.json +3 -3
  39. package/src/components/DynamicDataList/DynamicListDataEntry/Renderers/createInputRenderer/createInputRenderer.spec.tsx +2 -2
  40. package/src/components/Explorer/Explorer.stories.tsx +16 -0
  41. package/src/components/Explorer/Explorer.tsx +33 -31
  42. package/src/components/Filters/Filter/Filter.spec.tsx +24 -1
  43. package/src/components/Filters/Filter/Filter.tsx +6 -0
  44. package/src/components/Filters/Filters.model.ts +3 -0
  45. package/src/components/Filters/Filters.stories.tsx +9 -0
  46. package/src/components/Filters/Filters.tsx +1 -0
  47. package/src/components/FormElements/BooleanView/BooleanViewField.scss +4 -6
  48. package/src/components/FormElements/BooleanView/BooleanViewField.spec.tsx +6 -6
  49. package/src/components/FormElements/BooleanView/BooleanViewField.tsx +1 -1
  50. package/src/components/FormElements/Checkbox/Checkbox.tsx +1 -1
  51. package/src/components/FormElements/Checkbox/CheckboxField.tsx +4 -5
  52. package/src/components/FormElements/CustomTags/CustomTags.scss +15 -4
  53. package/src/components/FormElements/CustomTags/CustomTags.spec.tsx +3 -3
  54. package/src/components/FormElements/CustomTags/CustomTags.tsx +3 -3
  55. package/src/components/FormElements/CustomTags/CustomTagsField.tsx +1 -2
  56. package/src/components/FormElements/DateTimeField/DateTimeTextField.tsx +3 -3
  57. package/src/components/FormElements/DynamicDataListControl/DynamicDataListField.tsx +1 -2
  58. package/src/components/FormElements/FileUploadControl/FileUploadControl.spec.tsx +35 -0
  59. package/src/components/FormElements/FileUploadControl/FileUploadControl.tsx +2 -0
  60. package/src/components/FormElements/FileUploadControl/FileUploadField.tsx +1 -2
  61. package/src/components/FormElements/FormElementContainer/FormElementContainer.scss +2 -0
  62. package/src/components/FormElements/MaskedSingleLineText/MaskedSingleLineTextField.tsx +1 -1
  63. package/src/components/FormElements/Radio/RadioField.tsx +2 -2
  64. package/src/components/FormElements/SingleLineText/SingleLineText.spec.tsx +5 -4
  65. package/src/components/FormElements/SingleLineText/SingleLineText.tsx +6 -1
  66. package/src/components/FormElements/Tags/Tags.scss +1 -1
  67. package/src/components/FormElements/ToggleButton/ToggleButton.scss +18 -7
  68. package/src/components/FormStation/FormStation.spec.tsx +12 -6
  69. package/src/components/FormStation/FormStation.tsx +6 -6
  70. package/src/components/InlineMenu/InlineMenu.scss +20 -5
  71. package/src/components/LandingPageTiles/TileLarge/TileLarge.scss +11 -6
  72. package/src/components/List/List.model.ts +3 -0
  73. package/src/components/List/List.scss +0 -2
  74. package/src/components/List/List.stories.tsx +1 -1
  75. package/src/components/List/List.tsx +17 -55
  76. package/src/components/List/ListHeader/ListHeader.scss +23 -10
  77. package/src/components/List/ListHeader/ListHeader.spec.tsx +56 -0
  78. package/src/components/List/ListHeader/ListHeader.tsx +43 -9
  79. package/src/components/List/ListHeader/useResize.ts +108 -0
  80. package/src/components/List/ListRow/ListRow.scss +8 -12
  81. package/src/components/List/ListRow/ListRow.spec.tsx +5 -21
  82. package/src/components/List/ListRow/ListRow.tsx +16 -32
  83. package/src/components/List/ListRow/ListRowLoader.tsx +14 -4
  84. package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.scss +10 -8
  85. package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.tsx +3 -1
  86. package/src/components/List/useColumnsSize.ts +120 -0
  87. package/src/initialize.ts +4 -4
  88. package/src/styles/variables.scss +11 -0
@@ -57,6 +57,12 @@ export const SingleLineText: React.FC<SingleLineTextProps> = ({
57
57
  const DUMMY_PWD = '0000000000';
58
58
  const isPasswordField = type === 'password' ? true : false;
59
59
 
60
+ useEffect(() => {
61
+ if (innerRef.current) {
62
+ innerRef.current.value = String(value || '');
63
+ }
64
+ }, [innerRef, value]);
65
+
60
66
  useEffect(() => {
61
67
  if (isPasswordField && isSet) {
62
68
  executeIfRefAvailable(innerRef, (input) => {
@@ -99,7 +105,6 @@ export const SingleLineText: React.FC<SingleLineTextProps> = ({
99
105
  name={name}
100
106
  type={type}
101
107
  ref={innerRef}
102
- value={value ?? ''}
103
108
  defaultValue={defaultValue}
104
109
  disabled={disabled}
105
110
  placeholder={disabled ? undefined : placeholder}
@@ -119,7 +119,7 @@
119
119
 
120
120
  .tagEnter {
121
121
  opacity: 0;
122
- background-color: rgba($blue, 0.25) !important;
122
+ background-color: var(--tag-enter-color, $tag-enter-color) !important;
123
123
  }
124
124
 
125
125
  .tagEnterActive {
@@ -18,7 +18,8 @@
18
18
 
19
19
  &:hover:enabled {
20
20
  transition: box-shadow 0.15s ease-in-out 0s;
21
- box-shadow: 0 0 0 2px $blue;
21
+ box-shadow: 0 0 0 2px
22
+ var(--toggle-button-stroke-color, $toggle-button-stroke-color);
22
23
  }
23
24
 
24
25
  div {
@@ -44,25 +45,35 @@
44
45
  }
45
46
  }
46
47
  &.off {
47
- border-top: 1px solid $blue;
48
- border-bottom: 1px solid $blue;
49
- border-left: 1px solid $blue;
48
+ border-top: 1px solid
49
+ var(--toggle-button-stroke-color, $toggle-button-stroke-color);
50
+ border-bottom: 1px solid
51
+ var(--toggle-button-stroke-color, $toggle-button-stroke-color);
52
+ border-left: 1px solid
53
+ var(--toggle-button-stroke-color, $toggle-button-stroke-color);
50
54
  }
51
55
  &.on {
52
- border: 1px solid $blue;
56
+ border: 1px solid
57
+ var(--toggle-button-stroke-color, $toggle-button-stroke-color);
53
58
  }
54
59
  }
55
60
 
56
61
  div.active.off {
57
62
  svg {
58
63
  .svgText {
59
- fill: $blue;
64
+ fill: var(
65
+ --toggle-button-off-text-color,
66
+ $toggle-button-off-text-color
67
+ );
60
68
  }
61
69
  }
62
70
  }
63
71
 
64
72
  div.active.on {
65
- background-color: $blue !important;
73
+ background-color: var(
74
+ --toggle-button-on-bg-color,
75
+ $toggle-button-on-bg-color
76
+ ) !important;
66
77
  }
67
78
 
68
79
  &:disabled {
@@ -5,7 +5,7 @@ import { act } from 'react-dom/test-utils';
5
5
  import { MemoryRouter, Route } from 'react-router-dom';
6
6
  import * as Yup from 'yup';
7
7
  import { noop } from '../../helpers/utils';
8
- import { IndicatorType, setSaveIndicator } from '../../initialize';
8
+ import { SaveIndicatorType, setSaveIndicator } from '../../initialize';
9
9
  import { ActionData, Actions } from '../Actions';
10
10
  import { Action } from '../Actions/Action';
11
11
  import { MessageBar } from '../MessageBar/MessageBar';
@@ -677,9 +677,12 @@ describe('Details', () => {
677
677
  );
678
678
  expect(setSaveIndicator).toHaveBeenNthCalledWith(
679
679
  1,
680
- IndicatorType.Inactive,
681
- ); // 1. inactive 2. dirty
682
- expect(setSaveIndicator).toHaveBeenNthCalledWith(3, IndicatorType.Dirty); // 1. inactive 2. dirty
680
+ SaveIndicatorType.Inactive,
681
+ ); // 1. inactive
682
+ expect(setSaveIndicator).toHaveBeenNthCalledWith(
683
+ 3,
684
+ SaveIndicatorType.Dirty,
685
+ ); // 3. dirty
683
686
 
684
687
  // submit form
685
688
  const actionSelected = wrapper
@@ -697,7 +700,10 @@ describe('Details', () => {
697
700
 
698
701
  wrapper.update();
699
702
 
700
- expect(setSaveIndicator).toHaveBeenNthCalledWith(4, IndicatorType.Saving);
703
+ expect(setSaveIndicator).toHaveBeenNthCalledWith(
704
+ 4,
705
+ SaveIndicatorType.Saving,
706
+ );
701
707
 
702
708
  // complete form submission
703
709
  await act(async () => {
@@ -707,7 +713,7 @@ describe('Details', () => {
707
713
 
708
714
  expect(setSaveIndicator).toHaveBeenNthCalledWith(
709
715
  5,
710
- IndicatorType.Inactive,
716
+ SaveIndicatorType.Inactive,
711
717
  );
712
718
 
713
719
  console.warn((setSaveIndicator as jest.Mock).mock.calls);
@@ -16,7 +16,7 @@ import React, {
16
16
  } from 'react';
17
17
  import { useHistory } from 'react-router-dom';
18
18
  import { OptionalObjectSchema } from 'yup/lib/object';
19
- import { IndicatorType, setSaveIndicator } from '../../initialize';
19
+ import { SaveIndicatorType, setSaveIndicator } from '../../initialize';
20
20
  import { Data } from '../../types/data';
21
21
  import { ErrorTypeToStationError } from '../../utils/ErrorTypeToStationError';
22
22
  import { Actions, ActionsProps } from '../Actions';
@@ -155,7 +155,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
155
155
 
156
156
  try {
157
157
  setIsFormSubmitting(true);
158
- setSaveIndicator(IndicatorType.Saving);
158
+ setSaveIndicator(SaveIndicatorType.Saving);
159
159
  setStationError(undefined);
160
160
  if (!initialData.loading && saveData) {
161
161
  const response = await saveData(values, initialData, formikHelpers);
@@ -171,7 +171,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
171
171
  ),
172
172
  );
173
173
 
174
- setSaveIndicator(IndicatorType.Dirty);
174
+ setSaveIndicator(SaveIndicatorType.Dirty);
175
175
 
176
176
  // We still throw the error, to make sure that navigation or action execution
177
177
  // will not continue after a failed save.
@@ -449,15 +449,15 @@ const FormStationHeader: React.FC<
449
449
  useEffect(() => {
450
450
  // Set the save indicator to dirty depending on the form state
451
451
  if (dirty) {
452
- setSaveIndicator(IndicatorType.Dirty);
452
+ setSaveIndicator(SaveIndicatorType.Dirty);
453
453
  } else {
454
- setSaveIndicator(IndicatorType.Inactive);
454
+ setSaveIndicator(SaveIndicatorType.Inactive);
455
455
  }
456
456
  return () => {
457
457
  // The form is not always considered "not dirty" after the save
458
458
  // so this code will make sure that the indicator is set to inactive
459
459
  // when the station is left.
460
- setSaveIndicator(IndicatorType.Inactive);
460
+ setSaveIndicator(SaveIndicatorType.Inactive);
461
461
  };
462
462
  }, [dirty]);
463
463
 
@@ -85,10 +85,16 @@ $pop-up-arrow-extrusion: -7px;
85
85
  .buttonDefault {
86
86
  background-color: transparent !important;
87
87
  svg * {
88
- stroke: $blue !important;
88
+ stroke: var(
89
+ --inline-menu-button-stroke-color,
90
+ $inline-menu-button-stroke-color
91
+ ) !important;
89
92
  }
90
93
  &:hover {
91
- background-color: $blue !important;
94
+ background-color: var(
95
+ --inline-menu-button-hover-bg-color,
96
+ $inline-menu-button-hover-bg-color
97
+ ) !important;
92
98
  svg * {
93
99
  stroke: white !important;
94
100
  }
@@ -98,10 +104,16 @@ $pop-up-arrow-extrusion: -7px;
98
104
  .buttonDefaultOpacity {
99
105
  background-color: rgba(white, 0.7) !important;
100
106
  svg * {
101
- stroke: $blue !important;
107
+ stroke: var(
108
+ --inline-menu-button-stroke-color,
109
+ $inline-menu-button-stroke-color
110
+ ) !important;
102
111
  }
103
112
  &:hover {
104
- background-color: $blue !important;
113
+ background-color: var(
114
+ --inline-menu-button-hover-bg-color,
115
+ $inline-menu-button-hover-bg-color
116
+ ) !important;
105
117
  svg * {
106
118
  stroke: white !important;
107
119
  }
@@ -109,7 +121,10 @@ $pop-up-arrow-extrusion: -7px;
109
121
  }
110
122
 
111
123
  .buttonSelected {
112
- background-color: $blue !important;
124
+ background-color: var(
125
+ --inline-menu-button-selected-bg-color,
126
+ $inline-menu-button-selected-bg-color
127
+ ) !important;
113
128
  svg * {
114
129
  stroke: white !important;
115
130
  }
@@ -4,12 +4,13 @@
4
4
  height: 100%;
5
5
  display: grid;
6
6
  grid-template-columns: 1fr;
7
- grid-template-rows: 2fr 1fr;
7
+ grid-template-rows: 130px 1fr;
8
8
  grid-column: span 4;
9
9
  grid-row: span 2;
10
10
  place-items: center;
11
11
 
12
12
  text-decoration: none;
13
+ overflow: hidden;
13
14
 
14
15
  color: var(--landingpage-largetile-color, $landingpage-largetile-color);
15
16
  background-color: var(
@@ -41,25 +42,29 @@
41
42
  $landingpage-largetile-stroke-color
42
43
  );
43
44
  }
45
+
46
+ > * {
47
+ width: 100%;
48
+ height: 100%;
49
+ }
44
50
  }
45
51
 
46
52
  .titlesSection {
47
53
  display: grid;
48
- grid-template-columns: 1fr;
49
- grid-template-rows: 1fr 1fr;
54
+ padding: 0 15px 15px 15px;
50
55
  place-items: center;
51
56
  align-self: stretch;
52
57
 
53
58
  text-align: center;
54
59
 
55
60
  .label {
56
- font-size: 26px;
57
- font-weight: regular;
61
+ font-size: 23px;
62
+ overflow: hidden;
58
63
  }
59
64
 
60
65
  .subtitle {
61
66
  font-size: 16px;
62
- margin-bottom: 30px;
67
+ align-self: end;
63
68
  }
64
69
  }
65
70
  }
@@ -46,6 +46,9 @@ export interface Column<T extends Data> {
46
46
 
47
47
  /** Specify the horizontal text alignment of the column */
48
48
  horizontalColumnAlign?: 'left' | 'center' | 'right';
49
+
50
+ /** If set to true, the column will not be resizable */
51
+ disableResizing?: boolean;
49
52
  }
50
53
 
51
54
  export interface ColumnMap {
@@ -4,10 +4,8 @@
4
4
  @include boxSizing;
5
5
 
6
6
  height: 100%;
7
- min-width: 0px;
8
7
  display: grid;
9
8
  grid-auto-rows: min-content;
10
- row-gap: 3px;
11
9
  padding: var(--list-paddings, $list-paddings);
12
10
  background-color: var(
13
11
  --explorer-background-color,
@@ -248,7 +248,7 @@ export const NotSortableColumn: StoryObj<StoryListType> = {
248
248
  args: {
249
249
  columns: [
250
250
  defaultColumns[0],
251
- { ...defaultColumns[1], sortable: false },
251
+ { ...defaultColumns[1], sortable: false, disableResizing: true },
252
252
  ...defaultColumns.slice(2),
253
253
  ],
254
254
  },
@@ -24,6 +24,7 @@ import classes from './List.scss';
24
24
  import { ListHeader } from './ListHeader/ListHeader';
25
25
  import { ListRow } from './ListRow/ListRow';
26
26
  import { ListRowLoader } from './ListRow/ListRowLoader';
27
+ import { useColumnsSize } from './useColumnsSize';
27
28
 
28
29
  export interface ListProps<T extends Data> {
29
30
  /**
@@ -104,53 +105,6 @@ export interface ListProps<T extends Data> {
104
105
  inlineMenuActions?: (data: T) => ActionData[];
105
106
  }
106
107
 
107
- /**
108
- * Generates a combined string of all columns.columnSize values, to be used as CSS value
109
- * @param columns The list of columns that should be used
110
- * @returns a string of all column sizes of the array, combined
111
- */
112
- const getColumnsSizeDefinition = function <T extends Data>(
113
- columns: Column<T>[],
114
- showActionButton: boolean,
115
- selectMode: ListSelectMode,
116
- showInlineMenu: boolean,
117
- ): string {
118
- const columnSizeDefinition = columns.map((column) => column.size ?? '1fr');
119
-
120
- const hasActionsColumn =
121
- selectMode !== ListSelectMode.None || showActionButton || showInlineMenu;
122
-
123
- if (hasActionsColumn) {
124
- columnSizeDefinition.push(
125
- getActionsColumnSizePx(showInlineMenu, showActionButton),
126
- );
127
- }
128
-
129
- return columnSizeDefinition.join(' ');
130
- };
131
-
132
- const getActionsColumnSizePx = (...enableActions: boolean[]): string => {
133
- const enabledActionsCount = enableActions.filter(
134
- (actionEnabled) => actionEnabled,
135
- ).length;
136
-
137
- const defaultActionSizePx = 50;
138
- const defaultActionsRawGapPx = 8;
139
- const calculateMultiActionsColumnSizePx = (actionsCount: number): number => {
140
- return (
141
- defaultActionSizePx * actionsCount +
142
- defaultActionsRawGapPx * (actionsCount - 1)
143
- );
144
- };
145
-
146
- const sizePx =
147
- enabledActionsCount > 1
148
- ? calculateMultiActionsColumnSizePx(enabledActionsCount)
149
- : defaultActionSizePx;
150
-
151
- return `${sizePx}px`;
152
- };
153
-
154
108
  const noItemsMessage = (
155
109
  itemsCount: number,
156
110
  isLoading: boolean,
@@ -206,7 +160,7 @@ export const List = <T extends Data>({
206
160
  isError = false,
207
161
  errorMsg = 'There was an error.',
208
162
  handleRetry = true,
209
- minimumWidth = '500px',
163
+ minimumWidth = 'fit-content',
210
164
  columnGap = '5px',
211
165
  rowGap = '0px',
212
166
  headerRowHeight = '44px',
@@ -243,14 +197,19 @@ export const List = <T extends Data>({
243
197
  });
244
198
  }, [data]);
245
199
 
246
- const columnSizes = getColumnsSizeDefinition(
247
- columns,
248
- Boolean(showActionButton),
249
- selectionMode,
250
- !!inlineMenuActions,
251
- );
200
+ const { columnSizes, resetColumnSizes, setColumnSizes, hasActionColumn } =
201
+ useColumnsSize(
202
+ columns,
203
+ Boolean(showActionButton),
204
+ selectionMode,
205
+ Boolean(inlineMenuActions),
206
+ );
252
207
 
253
- const customStyles = { gridRowGap: rowGap, minWidth: minimumWidth };
208
+ const customStyles = {
209
+ gridRowGap: rowGap,
210
+ minWidth: minimumWidth,
211
+ width: '100%',
212
+ };
254
213
 
255
214
  const itemSelectionHandler = useCallback(
256
215
  (items: ListItem<T>[]) => {
@@ -327,6 +286,9 @@ export const List = <T extends Data>({
327
286
  isCheckboxDisabled={listItems.length === 0}
328
287
  onCheckboxToggled={headerCheckboxHandler}
329
288
  onSortChanged={sortChangedHandler}
289
+ onResetColumnSizes={resetColumnSizes}
290
+ onColumnSizesChanged={setColumnSizes}
291
+ hasActionColumn={hasActionColumn}
330
292
  />
331
293
  {/* Rows */}
332
294
  {listItems.map((item: ListItem<T>, index) => (
@@ -3,14 +3,9 @@
3
3
  .container {
4
4
  padding-left: 5px;
5
5
  display: grid;
6
- grid-template-columns: repeat(auto-fit, minmax(20px, 1fr));
7
- grid-auto-rows: 44px;
8
- column-gap: 5px;
9
- justify-items: left;
10
- align-items: center;
11
6
  position: sticky;
12
7
  top: 0;
13
- z-index: 1;
8
+ //z-index: 1;
14
9
 
15
10
  background-color: var(
16
11
  --explorer-header-background-color,
@@ -22,11 +17,29 @@
22
17
 
23
18
  .columnLabel {
24
19
  box-sizing: border-box;
25
- width: 99%;
20
+ width: 100%;
26
21
  height: 100%;
27
22
  display: grid;
28
- grid-template-rows: 1fr;
29
- grid-template-columns: 1fr;
30
- border-right: solid 1px lighten($gray, 15%);
23
+ grid-template-columns: 1fr auto;
24
+ position: relative;
25
+
26
+ .resizeHandle {
27
+ cursor: col-resize;
28
+ width: 7px;
29
+ height: 100%;
30
+
31
+ z-index: 1;
32
+ border-right: var(--explorer-list-row-border, 1px solid #dddddd);
33
+ position: absolute;
34
+ right: 0;
35
+
36
+ &:hover:not(.resizeHandleDisabled) {
37
+ border-width: 3px;
38
+ }
39
+
40
+ &.resizeHandleDisabled {
41
+ cursor: default;
42
+ }
43
+ }
31
44
  }
32
45
  }
@@ -40,6 +40,9 @@ const mockProps: ListHeaderProps<TestListHeaderData> = {
40
40
  actionSize: '50px',
41
41
  horizontalTextAlign: 'left',
42
42
  verticalTextAlign: 'center',
43
+ hasActionColumn: true,
44
+ onResetColumnSizes: jest.fn(),
45
+ onColumnSizesChanged: jest.fn(),
43
46
  };
44
47
 
45
48
  describe('ListHeader', () => {
@@ -155,4 +158,57 @@ describe('ListHeader', () => {
155
158
  });
156
159
 
157
160
  it.todo('reacts meaningfully when the columns are empty');
161
+
162
+ describe('Column Resizing', () => {
163
+ it('calls onResetColumnSizes when the reset button is clicked', () => {
164
+ const spy = jest.fn();
165
+ const wrapper = shallow(
166
+ <ListHeader {...mockProps} onResetColumnSizes={spy} />,
167
+ );
168
+
169
+ const resetButton = wrapper.find('.resizeHandle').first();
170
+ resetButton.simulate('doubleClick');
171
+
172
+ expect(spy).toHaveBeenCalledTimes(1);
173
+ });
174
+
175
+ it('calls onResetColumnSizes when the reset button is clicked also on non-resizable column', () => {
176
+ const spy = jest.fn();
177
+ const wrapper = shallow(
178
+ <ListHeader
179
+ {...mockProps}
180
+ columns={[
181
+ { ...mockListColumns[0], disableResizing: true },
182
+ ...mockListColumns.slice(1),
183
+ ]}
184
+ onResetColumnSizes={spy}
185
+ />,
186
+ );
187
+
188
+ const resetButton = wrapper.find('.resizeHandle').first();
189
+ resetButton.simulate('doubleClick');
190
+
191
+ expect(spy).toHaveBeenCalledTimes(1);
192
+ });
193
+
194
+ it('calls onColumnSizesChanged when the column is resized', () => {
195
+ const spy = jest.fn();
196
+ const wrapper = mount(
197
+ <ListHeader {...mockProps} onColumnSizesChanged={spy} />,
198
+ );
199
+
200
+ const resizeHandle = wrapper.find('.resizeHandle').first();
201
+ resizeHandle.invoke('onMouseDown')!({
202
+ preventDefault: jest.fn(),
203
+ } as any);
204
+
205
+ window.dispatchEvent(
206
+ new Event('mousemove', {
207
+ clientX: 100,
208
+ } as any),
209
+ );
210
+
211
+ expect(spy).toHaveBeenCalledTimes(1);
212
+ });
213
+ });
158
214
  });
@@ -5,6 +5,7 @@ import { Column, SortData } from '../List.model';
5
5
  import { ListCheckBox } from '../ListCheckBox/ListCheckBox';
6
6
  import { ColumnLabel } from './ColumnLabel/ColumnLabel';
7
7
  import classes from './ListHeader.scss';
8
+ import { useResize } from './useResize';
8
9
 
9
10
  export interface ListHeaderProps<T extends Data> {
10
11
  /** Column definitions */
@@ -37,6 +38,12 @@ export interface ListHeaderProps<T extends Data> {
37
38
  onSortChanged?: (sort: SortData<T>) => void;
38
39
  /** CSS Class name for additional styles */
39
40
  className?: string;
41
+ /** called when the column sizes should be reset */
42
+ onResetColumnSizes: () => void;
43
+ /** called when the column sizes should be updated */
44
+ onColumnSizesChanged: (columnSizes: string) => void;
45
+ /** Whether or not the list has an action column */
46
+ hasActionColumn: boolean;
40
47
  }
41
48
 
42
49
  /**
@@ -66,6 +73,9 @@ export const ListHeader = <T extends Data>({
66
73
  onCheckboxToggled,
67
74
  onSortChanged,
68
75
  className = '',
76
+ onResetColumnSizes,
77
+ onColumnSizesChanged,
78
+ hasActionColumn,
69
79
  }: PropsWithChildren<ListHeaderProps<T>>): JSX.Element => {
70
80
  const customStyles = {
71
81
  gridAutoRows: rowHeight,
@@ -75,15 +85,18 @@ export const ListHeader = <T extends Data>({
75
85
  alignItems: verticalTextAlign,
76
86
  } as React.CSSProperties;
77
87
 
88
+ const { cols, mouseDown } = useResize(columns, onColumnSizesChanged);
89
+
78
90
  return (
79
91
  <div
80
92
  className={clsx(classes.container, 'list-header-container', className)}
81
93
  style={customStyles}
82
94
  data-test-id="list-header"
83
95
  >
84
- {columns.map((column) => (
96
+ {columns.map((column, i) => (
85
97
  <div
86
98
  key={column.propertyName as string}
99
+ ref={cols[i].ref}
87
100
  className={clsx(classes.columnLabel)}
88
101
  >
89
102
  <ColumnLabel<T>
@@ -94,16 +107,37 @@ export const ListHeader = <T extends Data>({
94
107
  sortData={sortData}
95
108
  onSortChanged={onSortChanged}
96
109
  />
110
+ <div
111
+ onMouseDown={
112
+ !column.disableResizing
113
+ ? (e) => {
114
+ e.preventDefault();
115
+ mouseDown(i);
116
+ }
117
+ : undefined
118
+ }
119
+ onDoubleClick={() => onResetColumnSizes()}
120
+ className={clsx(classes.resizeHandle, {
121
+ [classes.resizeHandleDisabled]: column.disableResizing,
122
+ })}
123
+ />
97
124
  </div>
98
125
  ))}
99
- {showItemCheckbox && (
100
- <ListCheckBox
101
- height={actionSize}
102
- width={actionSize}
103
- isChecked={itemSelected}
104
- isDisabled={isCheckboxDisabled}
105
- onCheckBoxToggled={onCheckboxToggled}
106
- />
126
+ {hasActionColumn && (
127
+ <div
128
+ ref={cols[cols.length - 1].ref}
129
+ className={clsx(classes.columnLabel)}
130
+ >
131
+ {showItemCheckbox && (
132
+ <ListCheckBox
133
+ height={actionSize}
134
+ width={actionSize}
135
+ isChecked={itemSelected}
136
+ isDisabled={isCheckboxDisabled}
137
+ onCheckBoxToggled={onCheckboxToggled}
138
+ />
139
+ )}
140
+ </div>
107
141
  )}
108
142
  </div>
109
143
  );