@axinom/mosaic-ui 0.34.0-rc.6 → 0.34.0-rc.8

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 (47) 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/List/List.d.ts.map +1 -1
  8. package/dist/components/List/List.model.d.ts +2 -0
  9. package/dist/components/List/List.model.d.ts.map +1 -1
  10. package/dist/components/List/ListHeader/ListHeader.d.ts +7 -1
  11. package/dist/components/List/ListHeader/ListHeader.d.ts.map +1 -1
  12. package/dist/components/List/ListHeader/useResize.d.ts +18 -0
  13. package/dist/components/List/ListHeader/useResize.d.ts.map +1 -0
  14. package/dist/components/List/ListRow/ListRow.d.ts.map +1 -1
  15. package/dist/components/List/ListRow/ListRowLoader.d.ts +2 -2
  16. package/dist/components/List/ListRow/ListRowLoader.d.ts.map +1 -1
  17. package/dist/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.d.ts.map +1 -1
  18. package/dist/components/List/useColumnsSize.d.ts +21 -0
  19. package/dist/components/List/useColumnsSize.d.ts.map +1 -0
  20. package/dist/index.es.js +3 -3
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/index.js +3 -3
  23. package/dist/index.js.map +1 -1
  24. package/package.json +3 -3
  25. package/src/components/Explorer/Explorer.stories.tsx +16 -0
  26. package/src/components/Explorer/Explorer.tsx +33 -31
  27. package/src/components/Filters/Filter/Filter.spec.tsx +24 -1
  28. package/src/components/Filters/Filter/Filter.tsx +6 -0
  29. package/src/components/Filters/Filters.model.ts +3 -0
  30. package/src/components/Filters/Filters.stories.tsx +9 -0
  31. package/src/components/Filters/Filters.tsx +1 -0
  32. package/src/components/FormElements/FormElementContainer/FormElementContainer.scss +2 -0
  33. package/src/components/List/List.model.ts +3 -0
  34. package/src/components/List/List.scss +0 -2
  35. package/src/components/List/List.stories.tsx +1 -1
  36. package/src/components/List/List.tsx +17 -55
  37. package/src/components/List/ListHeader/ListHeader.scss +23 -10
  38. package/src/components/List/ListHeader/ListHeader.spec.tsx +56 -0
  39. package/src/components/List/ListHeader/ListHeader.tsx +43 -9
  40. package/src/components/List/ListHeader/useResize.ts +108 -0
  41. package/src/components/List/ListRow/ListRow.scss +8 -12
  42. package/src/components/List/ListRow/ListRow.spec.tsx +5 -21
  43. package/src/components/List/ListRow/ListRow.tsx +16 -31
  44. package/src/components/List/ListRow/ListRowLoader.tsx +14 -4
  45. package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.scss +10 -8
  46. package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.tsx +3 -1
  47. package/src/components/List/useColumnsSize.ts +120 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-ui",
3
- "version": "0.34.0-rc.6",
3
+ "version": "0.34.0-rc.8",
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.7-rc.6",
35
+ "@axinom/mosaic-core": "^0.4.7-rc.8",
36
36
  "@faker-js/faker": "^7.4.0",
37
37
  "@popperjs/core": "^2.9.2",
38
38
  "clsx": "^1.1.0",
@@ -102,5 +102,5 @@
102
102
  "publishConfig": {
103
103
  "access": "public"
104
104
  },
105
- "gitHead": "f7792f0bd51eb1551ef9c84dfabfaf12e1196e0a"
105
+ "gitHead": "5edc02cc390133cd76b231e57bde5394db8a5aa0"
106
106
  }
@@ -238,6 +238,22 @@ initializeUi({
238
238
  action('showNotification')(args);
239
239
  return faker.random.numeric();
240
240
  },
241
+ addIndicator: (args) => {
242
+ action('addIndicator')(args);
243
+ return Math.random();
244
+ },
245
+ removeIndicator: (args) => {
246
+ action('removeIndicator')(args);
247
+ },
248
+ showSaveIndicator: () => {
249
+ action('showSaveIndicator')();
250
+ },
251
+ hideSaveIndicator: () => {
252
+ action('hideSaveIndicator')();
253
+ },
254
+ on: (event, callback) => {
255
+ action('on')(event, callback);
256
+ },
241
257
  });
242
258
 
243
259
  export const ActionErrors: StoryObj<ExplorerStoryType> = {
@@ -433,37 +433,39 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
433
433
  setActiveFilters(args);
434
434
  }}
435
435
  />
436
- <List<T>
437
- columns={columns}
438
- data={data}
439
- isLoading={isLoading}
440
- isError={Boolean(stationMessage?.type === 'error')}
441
- handleRetry={false}
442
- minimumWidth={minimumWidth}
443
- columnGap={columnGap}
444
- rowGap={rowGap}
445
- headerRowHeight={headerRowHeight}
446
- listRowHeight={listRowHeight}
447
- listRowActionSize={listRowActionSize}
448
- headerRowActionSize={headerRowActionSize}
449
- horizontalTextAlign={horizontalTextAlign}
450
- verticalTextAlign={verticalTextAlign}
451
- keyProperty={keyProperty}
452
- showActionButton={Boolean(generateItemLink) || Boolean(onItemClicked)} // or hard code to `true`?
453
- selectionMode={mode}
454
- enableSelectAll={enableSelectAll}
455
- loadingTriggerOffset={loadingTriggerOffset}
456
- defaultSortOrder={sortOrder}
457
- generateItemLink={generateItemLink}
458
- onItemClicked={onItemClickedHandler}
459
- onItemSelected={itemSelectedHandler}
460
- onRequestMoreData={onRequestMoreData}
461
- onSortChanged={(sortData: SortData<T>) => {
462
- onSortChanged(sortData);
463
- setSortOrder(sortData);
464
- }}
465
- inlineMenuActions={inlineMenuActions && inlineMenuActionsHandler}
466
- />
436
+ <div>
437
+ <List<T>
438
+ columns={columns}
439
+ data={data}
440
+ isLoading={isLoading}
441
+ isError={Boolean(stationMessage?.type === 'error')}
442
+ handleRetry={false}
443
+ minimumWidth={minimumWidth}
444
+ columnGap={columnGap}
445
+ rowGap={rowGap}
446
+ headerRowHeight={headerRowHeight}
447
+ listRowHeight={listRowHeight}
448
+ listRowActionSize={listRowActionSize}
449
+ headerRowActionSize={headerRowActionSize}
450
+ horizontalTextAlign={horizontalTextAlign}
451
+ verticalTextAlign={verticalTextAlign}
452
+ keyProperty={keyProperty}
453
+ showActionButton={Boolean(generateItemLink) || Boolean(onItemClicked)} // or hard code to `true`?
454
+ selectionMode={mode}
455
+ enableSelectAll={enableSelectAll}
456
+ loadingTriggerOffset={loadingTriggerOffset}
457
+ defaultSortOrder={sortOrder}
458
+ generateItemLink={generateItemLink}
459
+ onItemClicked={onItemClickedHandler}
460
+ onItemSelected={itemSelectedHandler}
461
+ onRequestMoreData={onRequestMoreData}
462
+ onSortChanged={(sortData: SortData<T>) => {
463
+ onSortChanged(sortData);
464
+ setSortOrder(sortData);
465
+ }}
466
+ inlineMenuActions={inlineMenuActions && inlineMenuActionsHandler}
467
+ />
468
+ </div>
467
469
  </div>
468
470
  );
469
471
  });
@@ -1,7 +1,12 @@
1
1
  import { mount, shallow } from 'enzyme';
2
2
  import React from 'react';
3
3
  import { Button } from '../../Buttons';
4
- import { CustomFilterProps, FilterType, FilterTypes } from '../Filters.model';
4
+ import {
5
+ CustomFilterProps,
6
+ FilterType,
7
+ FilterTypes,
8
+ SelectedValueRenderer,
9
+ } from '../Filters.model';
5
10
  import { FreeTextFilter } from '../SelectionTypes/FreeTextFilter/FreeTextFilter';
6
11
  import { OptionsFilter } from '../SelectionTypes/OptionsFilter/OptionsFilter';
7
12
  import { Filter, FilterProps } from './Filter';
@@ -148,6 +153,24 @@ describe('Filter', () => {
148
153
  expect(wrapper.find('.selectedValue').exists()).toBe(false);
149
154
  });
150
155
 
156
+ it(`'selectedValueRenderer' value is displayed when the prop is set`, () => {
157
+ const renderedValue = `${sampleText} renderer`;
158
+ const selectedValueRenderer: SelectedValueRenderer = (value) => {
159
+ return `${value} renderer`;
160
+ };
161
+
162
+ const wrapper = mount(
163
+ <Filter {...defaultProps} options={freeTextFilter} value={sampleText} />,
164
+ );
165
+
166
+ expect(wrapper.find('.selectedValue').text()).toEqual(sampleText);
167
+
168
+ wrapper.setProps({ selectedValueRenderer });
169
+ wrapper.update();
170
+
171
+ expect(wrapper.find('.selectedValue').text()).toEqual(renderedValue);
172
+ });
173
+
151
174
  it('Raises onFilterChange with prop name and new undefined value when filter is closed', () => {
152
175
  const mockValue = 'test-value';
153
176
  const wrapper = mount(
@@ -26,6 +26,7 @@ export interface FilterProps<T extends Data> {
26
26
  index?: number;
27
27
  isActive: boolean;
28
28
  className?: string;
29
+ selectedValueRenderer?: (value: FilterValue) => string;
29
30
  onFilterChange: (prop: keyof T, value: FilterValue, index: number) => void;
30
31
  onValidate?: (currentValue: FilterValue) => string | null | undefined;
31
32
  onFilterClicked: () => void;
@@ -37,6 +38,7 @@ export const Filter = <T extends Data>({
37
38
  index = -1,
38
39
  isActive,
39
40
  className = '',
41
+ selectedValueRenderer,
40
42
  onFilterChange,
41
43
  onFilterClicked,
42
44
  onValidate,
@@ -72,6 +74,10 @@ export const Filter = <T extends Data>({
72
74
  };
73
75
 
74
76
  const renderValue = (value: unknown): ReactNode => {
77
+ if (selectedValueRenderer !== undefined) {
78
+ return selectedValueRenderer(value);
79
+ }
80
+
75
81
  switch (options.type) {
76
82
  case FilterTypes.Date:
77
83
  return formatDate(String(value));
@@ -15,6 +15,7 @@ export interface FilterConfig<T extends Data> {
15
15
  label: string;
16
16
  property: keyof T;
17
17
  type: FilterTypes;
18
+ selectedValueRenderer?: SelectedValueRenderer;
18
19
  onValidate?: FilterValidatorFunction<T>;
19
20
  }
20
21
 
@@ -105,3 +106,5 @@ export type FilterValidatorFunction<T> = (
105
106
  value: FilterValue,
106
107
  allValues: FilterValues<T>,
107
108
  ) => FilterValidationResult;
109
+
110
+ export type SelectedValueRenderer = (value: FilterValue) => string;
@@ -153,6 +153,15 @@ const customFilter: FilterType<{ custom: string }> = {
153
153
  label: 'Custom Filter (multi select)',
154
154
  property: 'custom',
155
155
  type: FilterTypes.Custom,
156
+ selectedValueRenderer: (value: unknown) => {
157
+ const items = value as string[];
158
+
159
+ return String(
160
+ `${items.length} item${items.length === 1 ? '' : 's'}: ${items
161
+ .join(', ')
162
+ .replace(/([a-zA-Z])(\d)/g, '$1 $2')}`,
163
+ );
164
+ },
156
165
  component: CustomFilterComponent,
157
166
  };
158
167
 
@@ -134,6 +134,7 @@ export const Filters = <T extends Data>({
134
134
  index={index}
135
135
  isActive={index === activeFilterIndex}
136
136
  onFilterChange={filtersChangedHandler}
137
+ selectedValueRenderer={filter.selectedValueRenderer}
137
138
  onValidate={(currentValue) =>
138
139
  filter.onValidate &&
139
140
  filter.onValidate(currentValue, activeFilters.current)
@@ -18,6 +18,8 @@
18
18
  align-self: start;
19
19
  font-size: var(--label-font-size, $label-font-size);
20
20
  color: var(--input-color, $input-color);
21
+ max-width: 100%;
22
+ overflow: auto;
21
23
  }
22
24
 
23
25
  .label {
@@ -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
  );