Table: Frozen columns (#109276)

* Table: Frozen columns

* i18n extract

* clamp the frozen columns by the total number of cols visible

* future-proof a bit: frozenColumns.left, add to kitchenSink
This commit is contained in:
Paul Marbach
2025-08-08 14:55:53 +00:00
committed by GitHub
parent 4e94e463cf
commit 992d189c0f
9 changed files with 56 additions and 5 deletions
@@ -410,6 +410,9 @@
"id": 1, "id": 1,
"options": { "options": {
"cellHeight": "sm", "cellHeight": "sm",
"frozenColumns": {
"left": 1
},
"footer": { "footer": {
"countRows": false, "countRows": false,
"enablePagination": false, "enablePagination": false,
@@ -1670,5 +1673,5 @@
"timezone": "", "timezone": "",
"title": "Panel Tests - Table - Kitchen Sink", "title": "Panel Tests - Table - Kitchen Sink",
"uid": "dcb9f5e9-8066-4397-889e-864b99555dbb", "uid": "dcb9f5e9-8066-4397-889e-864b99555dbb",
"version": 7 "version": 8
} }
@@ -87,6 +87,7 @@ export function TableNG(props: TableNGProps) {
enableSharedCrosshair = false, enableSharedCrosshair = false,
enableVirtualization, enableVirtualization,
footerOptions, footerOptions,
frozenColumns = 0,
getActions = () => [], getActions = () => [],
height, height,
initialSortBy, initialSortBy,
@@ -184,6 +185,19 @@ export function TableNG(props: TableNGProps) {
[theme] [theme]
); );
const widths = useMemo(() => computeColWidths(visibleFields, availableWidth), [visibleFields, availableWidth]); const widths = useMemo(() => computeColWidths(visibleFields, availableWidth), [visibleFields, availableWidth]);
const numColsFullyInView = useMemo(
() =>
widths.reduce(
([count, remainingWidth], nextWidth) => {
if (remainingWidth - nextWidth >= 0) {
return [count + 1, remainingWidth - nextWidth];
}
return [count, 0];
},
[0, availableWidth]
)[0],
[widths, availableWidth]
);
const headerHeight = useHeaderHeight({ const headerHeight = useHeaderHeight({
columnWidths: widths, columnWidths: widths,
fields: visibleFields, fields: visibleFields,
@@ -476,6 +490,7 @@ export function TableNG(props: TableNGProps) {
name: displayName, name: displayName,
width, width,
headerCellClass, headerCellClass,
frozen: Math.min(frozenColumns, numColsFullyInView) > i,
renderCell: renderCellContent, renderCell: renderCellContent,
renderHeaderCell: ({ column, sortDirection }) => ( renderHeaderCell: ({ column, sortDirection }) => (
<HeaderCell <HeaderCell
@@ -617,8 +632,11 @@ export function TableNG(props: TableNGProps) {
expandedRows, expandedRows,
filter, filter,
footerCalcs, footerCalcs,
frozenColumns,
getCellActions,
hasNestedFrames, hasNestedFrames,
isCountRowsSet, isCountRowsSet,
numColsFullyInView,
onCellFilterAdded, onCellFilterAdded,
panelContext, panelContext,
rowHeight, rowHeight,
@@ -631,7 +649,6 @@ export function TableNG(props: TableNGProps) {
theme, theme,
visibleFields, visibleFields,
widths, widths,
getCellActions,
]); ]);
// invalidate columns on every structureRev change. this supports width editing in the fieldConfig. // invalidate columns on every structureRev change. this supports width editing in the fieldConfig.
@@ -46,12 +46,20 @@ export const getGridStyles = (
'& > :not(.rdg-summary-row, .rdg-header-row) > .rdg-cell': { '& > :not(.rdg-summary-row, .rdg-header-row) > .rdg-cell': {
'&:hover, &[aria-selected=true]': { boxShadow: theme.shadows.z2 }, '&:hover, &[aria-selected=true]': { boxShadow: theme.shadows.z2 },
// selected cells should appear below hovered cells. // selected cells should appear below hovered cells.
'&:hover': { zIndex: theme.zIndex.tooltip - 2 }, '&:hover': { zIndex: theme.zIndex.tooltip - 4 },
'&[aria-selected=true]': { zIndex: theme.zIndex.tooltip - 3 }, '&[aria-selected=true]': { zIndex: theme.zIndex.tooltip - 5 },
}, },
'.rdg-cell.rdg-cell-frozen': { zIndex: theme.zIndex.tooltip - 2 },
'.rdg-header-row, .rdg-summary-row': { '.rdg-header-row, .rdg-summary-row': {
'.rdg-cell': { zIndex: theme.zIndex.tooltip - 1 }, '.rdg-cell': {
zIndex: theme.zIndex.tooltip - 3,
'&.rdg-cell-frozen': {
zIndex: theme.zIndex.tooltip - 1,
},
},
}, },
}), }),
gridNested: css({ gridNested: css({
@@ -129,6 +129,7 @@ export interface BaseTableProps {
onCellFilterAdded?: TableFilterActionCallback; onCellFilterAdded?: TableFilterActionCallback;
footerOptions?: TableFooterCalc; footerOptions?: TableFooterCalc;
footerValues?: FooterItem[]; footerValues?: FooterItem[];
frozenColumns?: number;
enablePagination?: boolean; enablePagination?: boolean;
cellHeight?: TableCellHeight; cellHeight?: TableCellHeight;
structureRev?: number; structureRev?: number;
@@ -77,6 +77,7 @@ export function TablePanel(props: Props) {
onColumnResize={(displayName, resizedWidth) => onColumnResize(displayName, resizedWidth, props)} onColumnResize={(displayName, resizedWidth) => onColumnResize(displayName, resizedWidth, props)}
onCellFilterAdded={panelContext.onAddAdHocFilter} onCellFilterAdded={panelContext.onAddAdHocFilter}
footerOptions={options.footer} footerOptions={options.footer}
frozenColumns={options.frozenColumns?.left}
enablePagination={options.footer?.enablePagination} enablePagination={options.footer?.enablePagination}
cellHeight={options.cellHeight} cellHeight={options.cellHeight}
timeRange={timeRange} timeRange={timeRange}
@@ -128,6 +128,15 @@ export const plugin = new PanelPlugin<Options, FieldConfig>(TablePanel)
category, category,
defaultValue: defaultOptions.showHeader, defaultValue: defaultOptions.showHeader,
}) })
.addNumberInput({
path: 'frozenColumns.left',
name: t('table-new.name-frozen-columns', 'Frozen columns'),
description: t('table-new.description-frozen-columns', 'Columns are frozen from the left side of the table'),
settings: {
placeholder: 'none',
},
category,
})
.addRadio({ .addRadio({
path: 'cellHeight', path: 'cellHeight',
name: t('table-new.name-cell-height', 'Cell height'), name: t('table-new.name-cell-height', 'Cell height'),
@@ -44,6 +44,10 @@ composableKinds: PanelCfg: {
} }
// Controls the height of the rows // Controls the height of the rows
cellHeight?: ui.TableCellHeight & (*"sm" | _) cellHeight?: ui.TableCellHeight & (*"sm" | _)
// Defines the number of columns to freeze on the left side of the table
frozenColumns?: {
left?: number | *0
}
} @cuetsy(kind="interface") } @cuetsy(kind="interface")
FieldConfig: { FieldConfig: {
ui.TableFieldOptions ui.TableFieldOptions
@@ -23,6 +23,12 @@ export interface Options {
* Represents the index of the selected frame * Represents the index of the selected frame
*/ */
frameIndex: number; frameIndex: number;
/**
* number of columns on the left side of the table that should be frozen
*/
frozenColumns?: {
left?: number;
};
/** /**
* Controls whether the panel should show the header * Controls whether the panel should show the header
*/ */
+2
View File
@@ -12717,6 +12717,7 @@
"description-column-filter": "Enables/disables field filters in table", "description-column-filter": "Enables/disables field filters in table",
"description-count-rows": "Display a single count for all data rows", "description-count-rows": "Display a single count for all data rows",
"description-fields": "Select the fields that should be calculated", "description-fields": "Select the fields that should be calculated",
"description-frozen-columns": "Columns are frozen from the left side of the table",
"description-min-column-width": "The minimum width for column auto resizing", "description-min-column-width": "The minimum width for column auto resizing",
"name-calculation": "Calculation", "name-calculation": "Calculation",
"name-cell-height": "Cell height", "name-cell-height": "Cell height",
@@ -12728,6 +12729,7 @@
"name-count-rows": "Count rows", "name-count-rows": "Count rows",
"name-enable-pagination": "Enable pagination", "name-enable-pagination": "Enable pagination",
"name-fields": "Fields", "name-fields": "Fields",
"name-frozen-columns": "Frozen columns",
"name-hide-in-table": "Hide in table", "name-hide-in-table": "Hide in table",
"name-min-column-width": "Minimum column width", "name-min-column-width": "Minimum column width",
"name-show-table-footer": "Show table footer", "name-show-table-footer": "Show table footer",