From c2394f47b6566946a526e4540528f9178201d99e Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Wed, 22 Jul 2026 00:19:25 +0300 Subject: [PATCH 1/6] Make table group-by width configurable --- src/components/table/header/groupBy.js | 2 +- src/components/table/header/groupBy.test.js | 35 +++++++++++++++++++++ src/components/table/index.d.ts | 17 ++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/components/table/header/groupBy.test.js diff --git a/src/components/table/header/groupBy.js b/src/components/table/header/groupBy.js index 4fe995689..7320e2f4b 100644 --- a/src/components/table/header/groupBy.js +++ b/src/components/table/header/groupBy.js @@ -43,7 +43,7 @@ const HeaderGroupBy = ({ grouping, groupByColumns, onGroupBy, tableMeta, dataGa, menuPortalTarget={document.body} onChange={({ value }) => onGroupBy(value)} options={Object.values(groupByOptions)} - styles={{ size: "tiny", minWidth: 120 }} + styles={{ size: "tiny", minWidth: 120, ...tableMeta.groupBySelectStyles }} value={groupByOptions[grouping] || groupByOptions.default} /> diff --git a/src/components/table/header/groupBy.test.js b/src/components/table/header/groupBy.test.js new file mode 100644 index 000000000..5df487f8a --- /dev/null +++ b/src/components/table/header/groupBy.test.js @@ -0,0 +1,35 @@ +import React from "react" +import { renderWithProviders, screen } from "testUtilities" +import HeaderGroupBy from "./groupBy" + +const longGroup = "label:kubernetes.io/metadata.name" +const groupByColumns = { + [longGroup]: { name: longGroup }, +} +const dataColumns = [{ id: longGroup, name: longGroup }] + +const renderGroupBy = tableMeta => + renderWithProviders( + {}} + tableMeta={tableMeta} + /> + ) + +describe("HeaderGroupBy", () => { + it("keeps the existing 120 px minimum by default", () => { + renderGroupBy({}) + + expect(screen.getByTestId("tableGroupByFilterControl")).toHaveStyle({ minWidth: "120px" }) + }) + + it("accepts a reusable Select style override for long values", () => { + renderGroupBy({ groupBySelectStyles: { minWidth: 240 } }) + + expect(screen.getByText(longGroup)).toBeInTheDocument() + expect(screen.getByTestId("tableGroupByFilterControl")).toHaveStyle({ minWidth: "240px" }) + }) +}) diff --git a/src/components/table/index.d.ts b/src/components/table/index.d.ts index dc35ca7d0..66f6c770f 100644 --- a/src/components/table/index.d.ts +++ b/src/components/table/index.d.ts @@ -10,6 +10,7 @@ import { } from "@tanstack/table-core" import { ComponentType, Key, MutableRefObject, ReactNode, RefObject, UIEventHandler } from "react" import { Virtualizer } from "@tanstack/react-virtual" +import { StylesConfig } from "react-select" import { supportedBulkActions } from "./header/actions/useActions" import { supportedRowActions } from "./useColumns/useRowActions" @@ -82,6 +83,21 @@ export type OverflowTooltipProps = { options?: OverflowTooltipOptions } +export type TableGroupBySelectStyles = StylesConfig & { + minWidth?: number | string + size?: string +} + +export type TableMeta = { + bulkActionsStyles?: Record + cellStyles?: Record + groupByContainerStyles?: Record + groupBySelectStyles?: TableGroupBySelectStyles + headStyles?: Record + searchContainerStyles?: Record + searchStyles?: Record +} + export type TableProps = { data: Array dataColumns: Array> @@ -137,6 +153,7 @@ export type TableProps = { */ testPrefixCallback?: (rowData: D) => string largeDataOptions?: LargeDataOptions + meta?: TableMeta | ((...args: Array) => TableMeta) } declare const Table: (props: TableProps) => JSX.Element From 95872533fcec7f12ccea27b72feb81eff5d8a21d Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Thu, 23 Jul 2026 12:12:52 +0300 Subject: [PATCH 2/6] Allow hiding the table group-by control --- src/components/table/header/index.js | 27 +++++++++++++++-------- src/components/table/header/index.test.js | 25 +++++++++++++++++++++ src/components/table/index.d.ts | 1 + src/components/table/table.js | 3 +++ 4 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 src/components/table/header/index.test.js diff --git a/src/components/table/header/index.js b/src/components/table/header/index.js index aec2f4df5..7896ff493 100644 --- a/src/components/table/header/index.js +++ b/src/components/table/header/index.js @@ -10,6 +10,7 @@ const Header = ({ hasSearch, onSearch, groupByColumns, + enableGroupByControl = true, grouping, onGroupBy, tableMeta, @@ -27,7 +28,13 @@ const Header = ({ [] ) - if (!title && !groupByColumns && !hasSearch && !bulkActions && !enableColumnVisibility) + if ( + !title && + !(enableGroupByControl && groupByColumns) && + !hasSearch && + !bulkActions && + !enableColumnVisibility + ) return null return ( @@ -59,14 +66,16 @@ const Header = ({ /> )} - + {enableGroupByControl && ( + + )} {children} ) diff --git a/src/components/table/header/index.test.js b/src/components/table/header/index.test.js new file mode 100644 index 000000000..0d706cb00 --- /dev/null +++ b/src/components/table/header/index.test.js @@ -0,0 +1,25 @@ +import React from "react" +import { renderWithProviders, screen } from "testUtilities" +import Header from "." + +const props = { + dataColumns: [{ id: "group", name: "Group" }], + groupByColumns: { group: { columns: ["group"], name: "Group" } }, + grouping: "group", + onGroupBy: () => {}, + tableMeta: {}, +} + +describe("Table Header", () => { + it("shows the Group By control by default", () => { + renderWithProviders(
) + + expect(screen.getByTestId("tableGroupBy")).toBeInTheDocument() + }) + + it("can hide the Group By control without removing grouping configuration", () => { + renderWithProviders(
) + + expect(screen.queryByTestId("tableGroupBy")).not.toBeInTheDocument() + }) +}) diff --git a/src/components/table/index.d.ts b/src/components/table/index.d.ts index 66f6c770f..58b9476c1 100644 --- a/src/components/table/index.d.ts +++ b/src/components/table/index.d.ts @@ -139,6 +139,7 @@ export type TableProps = { columnVisibility?: VisibilityTableState enableColumnVisibility?: boolean enableColumnPinning?: boolean + enableGroupByControl?: boolean onGlobalSearchChange?: (value: any) => void onRowSelected?: (value: any) => void onClickRow?: (value: any) => void diff --git a/src/components/table/table.js b/src/components/table/table.js index ee82d2e61..deebfcefd 100644 --- a/src/components/table/table.js +++ b/src/components/table/table.js @@ -49,6 +49,7 @@ const tableDefaultProps = { enableColumnPinning: false, enableColumnReordering: false, enableColumnVisibility: false, + enableGroupByControl: true, enableResizing: false, globalFilterFn: includesString, onColumnVisibilityChange: noop, @@ -117,6 +118,7 @@ const Table = memo(props => { grouping: defaultGrouping, onGroupByChange: groupingChangeCb, groupByColumns, + enableGroupByControl = tableDefaultProps.enableGroupByControl, onRowSelected, @@ -308,6 +310,7 @@ const Table = memo(props => { hasSearch={!!onSearch} onSearch={onGlobalFilterChange} groupByColumns={groupByColumns} + enableGroupByControl={enableGroupByControl} onGroupBy={onGroupingChange} grouping={grouping} tableMeta={tableMeta} From 61d5f8db6dbea13e3af95bc767b6db114cc40440 Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Thu, 23 Jul 2026 22:33:50 +0300 Subject: [PATCH 3/6] Add fleet map semantic theme colors --- src/theme/dark/colors.js | 2 ++ src/theme/default/colors.js | 2 ++ src/theme/index.d.ts | 2 ++ src/theme/index.test.js | 14 ++++++++++++++ src/theme/rawColors.js | 2 ++ 5 files changed, 22 insertions(+) create mode 100644 src/theme/index.test.js diff --git a/src/theme/dark/colors.js b/src/theme/dark/colors.js index 6cab2ab20..dcbbf1a7e 100644 --- a/src/theme/dark/colors.js +++ b/src/theme/dark/colors.js @@ -72,6 +72,8 @@ const appColors = { staleSemi: rawColors.green.green900, unseen: rawColors.yellow.yellow900, offline: rawColors.neutral.grey90, + metricGap: rawColors.neutral.metricGapDark, + mapSearchHighlight: rawColors.blue.blue150, //=========================================\\ diff --git a/src/theme/default/colors.js b/src/theme/default/colors.js index 47b424779..59fc2f145 100644 --- a/src/theme/default/colors.js +++ b/src/theme/default/colors.js @@ -70,6 +70,8 @@ const appColors = { staleSemi: rawColors.green.green900, unseen: rawColors.yellow.yellow900, offline: rawColors.neutral.grey145, + metricGap: rawColors.neutral.metricGapLight, + mapSearchHighlight: rawColors.blue.blue100, //=========================================\\ diff --git a/src/theme/index.d.ts b/src/theme/index.d.ts index 08f58fdc5..dd6b2a24a 100644 --- a/src/theme/index.d.ts +++ b/src/theme/index.d.ts @@ -25,6 +25,8 @@ export type AppColorsT = { errorLite: string errorBackground: string errorText: string + metricGap: string + mapSearchHighlight: string attention: string attentionSecondary: string separator: string diff --git a/src/theme/index.test.js b/src/theme/index.test.js new file mode 100644 index 000000000..850407273 --- /dev/null +++ b/src/theme/index.test.js @@ -0,0 +1,14 @@ +import { DarkTheme, DefaultTheme } from "./index" +import rawColors from "./rawColors" + +describe("theme colors", () => { + it("owns metric gaps as a semantic color in both themes", () => { + expect(DefaultTheme.colors.metricGap).toBe(rawColors.neutral.metricGapLight) + expect(DarkTheme.colors.metricGap).toBe(rawColors.neutral.metricGapDark) + }) + + it("owns a contrasting Map search highlight in both themes", () => { + expect(DefaultTheme.colors.mapSearchHighlight).toBe(rawColors.blue.blue100) + expect(DarkTheme.colors.mapSearchHighlight).toBe(rawColors.blue.blue150) + }) +}) diff --git a/src/theme/rawColors.js b/src/theme/rawColors.js index 65cfe35a5..d66c58f42 100644 --- a/src/theme/rawColors.js +++ b/src/theme/rawColors.js @@ -151,6 +151,8 @@ const rawColors = { gunmetal: "#282C34", darkGunmetal: "#21252B", eerieBlack: "#181c20", + metricGapDark: "#312E27", + metricGapLight: "#E0DCD5", // Grey shades grey05: "#040505", grey10: "#080A0A", From 3300aaefd22e84373838ec0a513477b12fe97270 Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Thu, 23 Jul 2026 23:49:20 +0300 Subject: [PATCH 4/6] test hidden table grouping control --- src/components/table/groupByControl.test.js | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/components/table/groupByControl.test.js diff --git a/src/components/table/groupByControl.test.js b/src/components/table/groupByControl.test.js new file mode 100644 index 000000000..1349152f3 --- /dev/null +++ b/src/components/table/groupByControl.test.js @@ -0,0 +1,41 @@ +import React from "react" +import { renderWithProviders, screen, waitFor } from "testUtilities" +import Table from "./table" + +describe("Table Group By control", () => { + it("keeps configured grouping active when the header control is hidden", async () => { + const tableRef = { current: null } + + renderWithProviders( + null, + }, + ]} + enableGroupByControl={false} + getRowId={row => row.id} + groupByColumns={{ status: { columns: ["status"], name: "Status" } }} + grouping="status" + tableRef={tableRef} + /> + ) + + await waitFor(() => expect(tableRef.current).not.toBeNull()) + + expect(screen.queryByTestId("tableGroupBy")).not.toBeInTheDocument() + expect(tableRef.current.getState().grouping).toEqual(["status"]) + expect(tableRef.current.getGroupedRowModel().rows).toHaveLength(1) + expect(tableRef.current.getGroupedRowModel().rows[0]).toMatchObject({ + groupingColumnId: "status", + groupingValue: "live", + }) + }) +}) From 93574fa81b3b67d31e43122c5e2cca43b8e484c5 Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Sat, 25 Jul 2026 04:57:55 +0300 Subject: [PATCH 5/6] Support configurable table header action order --- .../table/headerActionsOrder.test.js | 43 +++++++++++++++++++ src/components/table/index.d.ts | 2 + src/components/table/table.js | 28 +++++++----- 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 src/components/table/headerActionsOrder.test.js diff --git a/src/components/table/headerActionsOrder.test.js b/src/components/table/headerActionsOrder.test.js new file mode 100644 index 000000000..437296bc4 --- /dev/null +++ b/src/components/table/headerActionsOrder.test.js @@ -0,0 +1,43 @@ +import React from "react" +import { renderWithProviders } from "testUtilities" +import Table from "./table" + +const data = [{ id: "node-1", name: "Node 1" }] +const dataColumns = [ + { + id: "name", + accessorKey: "name", + header: "Name", + cell: ({ getValue }) => getValue(), + }, +] + +const renderTable = props => + renderWithProviders( +
row.id} + headerChildren={} + {...props} + /> + ) + +const expectBefore = (first, second) => { + expect(first.compareDocumentPosition(second) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() +} + +describe("Table header action order", () => { + it("keeps custom children before built-in actions by default", () => { + const { getByTestId } = renderTable() + + expectBefore(getByTestId("trailing-control"), getByTestId("bulk-actions")) + }) + + it("can place built-in actions before custom trailing controls", () => { + const { getByTestId } = renderTable({ headerActionsBeforeChildren: true }) + + expectBefore(getByTestId("bulk-actions"), getByTestId("trailing-control")) + }) +}) diff --git a/src/components/table/index.d.ts b/src/components/table/index.d.ts index 58b9476c1..ce2229836 100644 --- a/src/components/table/index.d.ts +++ b/src/components/table/index.d.ts @@ -140,6 +140,8 @@ export type TableProps = { enableColumnVisibility?: boolean enableColumnPinning?: boolean enableGroupByControl?: boolean + headerActionsBeforeChildren?: boolean + headerChildren?: ReactNode onGlobalSearchChange?: (value: any) => void onRowSelected?: (value: any) => void onClickRow?: (value: any) => void diff --git a/src/components/table/table.js b/src/components/table/table.js index deebfcefd..99eba7fc8 100644 --- a/src/components/table/table.js +++ b/src/components/table/table.js @@ -52,6 +52,7 @@ const tableDefaultProps = { enableGroupByControl: true, enableResizing: false, globalFilterFn: includesString, + headerActionsBeforeChildren: false, onColumnVisibilityChange: noop, onColumnOrderChange: noop, onSortingChange: noop, @@ -74,6 +75,7 @@ const Table = memo(props => { const { bulkActions, headerChildren, + headerActionsBeforeChildren = tableDefaultProps.headerActionsBeforeChildren, data, dataColumns, @@ -294,6 +296,19 @@ const Table = memo(props => { if (tableRef) tableRef.current = table const { getHasNextPage, loading, warning } = virtualizeOptions + const headerActions = ( + + ) return ( { bulkActions={bulkActions} enableCustomSearch={enableCustomSearch} > + {headerActionsBeforeChildren && headerActions} {headerChildren || null} - + {!headerActionsBeforeChildren && headerActions} Date: Mon, 27 Jul 2026 21:14:33 +0300 Subject: [PATCH 6/6] v5.5.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83e030fc1..f15347a26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netdata/netdata-ui", - "version": "5.5.5", + "version": "5.5.6", "description": "netdata UI kit", "main": "dist/index.js", "module": "dist/es6/index.js",