Skip to content

Commit f9066ec

Browse files
authored
ui: DataGrid: Add context menu item to autofit columns in DataGrid (#3481)
- Add new button to auto fit column witdth to content. - Added section headers to conext menu. <img width="741" height="334" alt="image" src="https://github.com/user-attachments/assets/def808eb-a0ce-4f4a-bd4a-8f70fd5964f6" />
1 parent 8b0ff27 commit f9066ec

File tree

2 files changed

+83
-4
lines changed

2 files changed

+83
-4
lines changed

ui/src/components/widgets/data_grid/data_grid.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import {Box} from '../../../widgets/box';
2323
import {Button} from '../../../widgets/button';
2424
import {Chip} from '../../../widgets/chip';
2525
import {LinearProgress} from '../../../widgets/linear_progress';
26-
import {MenuDivider, MenuItem} from '../../../widgets/menu';
26+
import {MenuDivider, MenuItem, MenuTitle} from '../../../widgets/menu';
2727
import {Stack, StackAuto} from '../../../widgets/stack';
2828
import {
2929
renderSortMenuItems,
3030
Grid,
31+
GridApi,
3132
GridColumn,
3233
GridCell,
3334
GridHeaderCell,
@@ -275,6 +276,7 @@ export class DataGrid implements m.ClassComponent<DataGridAttrs> {
275276
// Track pagination state from virtual scrolling
276277
private paginationOffset: number = 0;
277278
private paginationLimit: number = 100;
279+
private gridApi?: GridApi;
278280

279281
oninit({attrs}: m.Vnode<DataGridAttrs>) {
280282
if (attrs.initialSorting) {
@@ -391,6 +393,7 @@ export class DataGrid implements m.ClassComponent<DataGridAttrs> {
391393
})();
392394

393395
const menuItems: m.Children = [];
396+
sortControls && menuItems.push(m(MenuTitle, {label: 'Sorting'}));
394397
sortControls &&
395398
menuItems.push(
396399
...renderSortMenuItems(sort, (direction) => {
@@ -409,6 +412,7 @@ export class DataGrid implements m.ClassComponent<DataGridAttrs> {
409412

410413
if (filterControls && sortControls && menuItems.length > 0) {
411414
menuItems.push(m(MenuDivider));
415+
menuItems.push(m(MenuTitle, {label: 'Filters'}));
412416
}
413417

414418
if (filterControls) {
@@ -439,13 +443,25 @@ export class DataGrid implements m.ClassComponent<DataGridAttrs> {
439443
if (columnReordering) {
440444
if (menuItems.length > 0) {
441445
menuItems.push(m(MenuDivider));
446+
menuItems.push(m(MenuTitle, {label: 'Column'}));
447+
}
448+
449+
if (this.gridApi) {
450+
const gridApi = this.gridApi;
451+
menuItems.push(
452+
m(MenuItem, {
453+
label: 'Fit to content',
454+
icon: 'fit_width',
455+
onclick: () => gridApi.autoFitColumn(column.name),
456+
}),
457+
);
442458
}
443459

444460
// Hide current column (only if more than 1 visible)
445461
if (orderedColumns.length > 1) {
446462
menuItems.push(
447463
m(MenuItem, {
448-
label: 'Hide column',
464+
label: 'Hide',
449465
icon: Icons.Hide,
450466
onclick: () => {
451467
const newOrder = columnOrder.filter(
@@ -760,6 +776,9 @@ export class DataGrid implements m.ClassComponent<DataGridAttrs> {
760776
onColumnOrderChanged(newOrder);
761777
}
762778
: undefined,
779+
onReady: (api) => {
780+
this.gridApi = api;
781+
},
763782
}),
764783
);
765784
}

ui/src/widgets/grid.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ export class GridHeaderCell implements m.ClassComponent<GridHeaderCellAttrs> {
101101
PopupMenu,
102102
{
103103
trigger: m(Button, {
104-
className: 'pf-visible-on-hover pf-grid-header-cell__menu-button',
104+
className:
105+
'pf-visible-on-hover pf-grid-header-cell__menu-button pf-grid--no-measure',
105106
icon: Icons.ContextMenuAlt,
106107
rounded: true,
107108
ariaLabel: 'Column menu',
@@ -227,6 +228,23 @@ export interface GridVirtualization {
227228
readonly rowHeightPx: number;
228229
}
229230

231+
/**
232+
* Imperative API for Grid component.
233+
* Provides methods to control the grid programmatically.
234+
*/
235+
export interface GridApi {
236+
/**
237+
* Auto-fit a column to its content width.
238+
* @param columnKey The key of the column to auto-fit
239+
*/
240+
autoFitColumn(columnKey: string): void;
241+
242+
/**
243+
* Auto-fit all columns to their content widths.
244+
*/
245+
autoFitAllColumns(): void;
246+
}
247+
230248
/**
231249
* Attributes for the Grid component.
232250
*/
@@ -243,6 +261,7 @@ export interface GridAttrs {
243261
to: string | number | undefined,
244262
position: ReorderPosition,
245263
) => void;
264+
readonly onReady?: (api: GridApi) => void;
246265
}
247266

248267
/**
@@ -550,6 +569,38 @@ export class Grid implements m.ClassComponent<GridAttrs> {
550569
},
551570
},
552571
]);
572+
573+
// Call onReady callback with imperative API
574+
if (vnode.attrs.onReady) {
575+
vnode.attrs.onReady({
576+
autoFitColumn: (columnKey: string) => {
577+
const gridDom = vnode.dom as HTMLElement;
578+
const column = columns.find((c) => c.key === columnKey);
579+
if (!column) return;
580+
581+
this.measureAndApplyWidths(gridDom, [
582+
{
583+
key: column.key,
584+
minWidth: column.minWidth ?? COL_WIDTH_MIN_PX,
585+
maxWidth: Infinity,
586+
},
587+
]);
588+
m.redraw();
589+
},
590+
autoFitAllColumns: () => {
591+
const gridDom = vnode.dom as HTMLElement;
592+
this.measureAndApplyWidths(
593+
gridDom,
594+
columns.map((column) => ({
595+
key: column.key,
596+
minWidth: column.minWidth ?? COL_WIDTH_MIN_PX,
597+
maxWidth: Infinity,
598+
})),
599+
);
600+
m.redraw();
601+
},
602+
});
603+
}
553604
}
554605

555606
onupdate(vnode: m.VnodeDOM<GridAttrs, this>) {
@@ -596,9 +647,18 @@ export class Grid implements m.ClassComponent<GridAttrs> {
596647
const gridClone = gridDom.cloneNode(true) as HTMLElement;
597648
gridDom.appendChild(gridClone);
598649

650+
// Hide any elements that are not part of the measurement - these are
651+
// elements with class .pf-grid--no-measure
652+
const noMeasureElements = gridClone.querySelectorAll(
653+
'.pf-grid--no-measure',
654+
);
655+
noMeasureElements.forEach((el) => {
656+
(el as HTMLElement).style.display = 'none';
657+
});
658+
599659
// Now read the actual widths (this will cause a reflow)
600660
// Find all the cells in this column (header + data rows)
601-
const allCells = gridClone.querySelectorAll(`.pf-grid__cell-container`); // TODO pick a better selector for this
661+
const allCells = gridClone.querySelectorAll(`.pf-grid__cell-container`);
602662

603663
// Only continue if we have more cells than just the header
604664
if (allCells.length <= columns.length) {

0 commit comments

Comments
 (0)