From 7a17104a0ee3741f8fe5c61e9bb12136f1c77e1e Mon Sep 17 00:00:00 2001 From: Udaybir Singh Date: Tue, 30 Jun 2026 15:22:27 +0530 Subject: [PATCH 1/2] feat(DataTableToolbar): add reusable toolbar component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DataTableToolbar — a pure presentational layout shell that provisions named slots for toolbar actions: - Left section: primaryActions, secondaryActions - Right section: filter, search, columnVisibility, viewSwitch All slots are optional ReactNode render props. Key decisions: - Uses Sistent theme tokens exclusively (no hardcoded px/rem) - Responsive via theme.breakpoints.down('sm') — no useMediaQuery - Accepts sx prop for consumer overrides - Replaces ToolWrapper's fixed height with flexible minHeight Part of meshery/meshery#18965 follow-up. Signed-off-by: Udaybir Singh --- src/__testing__/DataTableToolbar.test.tsx | 64 +++++++++++++++++++ .../DataTableToolbar/DataTableToolbar.tsx | 62 ++++++++++++++++++ .../DataTableToolbar.types.ts | 24 +++++++ src/custom/DataTableToolbar/index.tsx | 2 + src/custom/index.tsx | 3 + 5 files changed, 155 insertions(+) create mode 100644 src/__testing__/DataTableToolbar.test.tsx create mode 100644 src/custom/DataTableToolbar/DataTableToolbar.tsx create mode 100644 src/custom/DataTableToolbar/DataTableToolbar.types.ts create mode 100644 src/custom/DataTableToolbar/index.tsx diff --git a/src/__testing__/DataTableToolbar.test.tsx b/src/__testing__/DataTableToolbar.test.tsx new file mode 100644 index 000000000..b913d27a1 --- /dev/null +++ b/src/__testing__/DataTableToolbar.test.tsx @@ -0,0 +1,64 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +import { DataTableToolbar } from '../custom/DataTableToolbar'; +import { SistentThemeProvider } from '../theme'; + +const renderWithTheme = (ui: React.ReactElement) => + render({ui}); + +describe('DataTableToolbar', () => { + it('renders primaryActions content', () => { + renderWithTheme(Add} />); + expect(screen.getByRole('button', { name: 'Add' })).toBeTruthy(); + }); + + it('renders secondaryActions content', () => { + renderWithTheme(Export} />); + expect(screen.getByRole('button', { name: 'Export' })).toBeTruthy(); + }); + + it('renders search slot', () => { + renderWithTheme(} />); + expect(screen.getByPlaceholderText('Search')).toBeTruthy(); + }); + + it('renders filter slot', () => { + renderWithTheme(Filter} />); + expect(screen.getByText('Filter')).toBeTruthy(); + }); + + it('renders columnVisibility slot', () => { + renderWithTheme(Columns} />); + expect(screen.getByText('Columns')).toBeTruthy(); + }); + + it('renders viewSwitch slot', () => { + renderWithTheme(Grid/Table} />); + expect(screen.getByText('Grid/Table')).toBeTruthy(); + }); + + it('renders all slots simultaneously', () => { + renderWithTheme( + Add} + search={} + filter={
Filter
} + /> + ); + expect(screen.getByRole('button', { name: 'Add' })).toBeTruthy(); + expect(screen.getByPlaceholderText('Search')).toBeTruthy(); + expect(screen.getByText('Filter')).toBeTruthy(); + }); + + it('renders without any props (empty state)', () => { + const { container } = renderWithTheme(); + expect(container.firstChild).toBeTruthy(); + }); + + it('applies custom sx styles', () => { + const { container } = renderWithTheme(); + const root = container.firstChild as HTMLElement; + expect(root).toBeTruthy(); + }); +}); diff --git a/src/custom/DataTableToolbar/DataTableToolbar.tsx b/src/custom/DataTableToolbar/DataTableToolbar.tsx new file mode 100644 index 000000000..06965a920 --- /dev/null +++ b/src/custom/DataTableToolbar/DataTableToolbar.tsx @@ -0,0 +1,62 @@ +import { Box } from '../../base'; +import { styled } from '../../theme'; +import type { DataTableToolbarProps } from './DataTableToolbar.types'; + +const ToolbarRoot = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: theme.spacing(4), + minHeight: theme.spacing(8), + padding: theme.spacing(1.5), + backgroundColor: theme.palette.background.card, + borderRadius: theme.spacing(1), + boxShadow: theme.shadows[2], + zIndex: 101, + + [theme.breakpoints.down('sm')]: { + height: 'auto', + flexWrap: 'wrap', + padding: theme.spacing(1), + gap: theme.spacing(1) + } +})); + +const Section = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1) +})); + +export function DataTableToolbar({ + primaryActions, + secondaryActions, + search, + filter, + columnVisibility, + viewSwitch, + sx +}: DataTableToolbarProps): JSX.Element { + const hasLeftContent = Boolean(primaryActions) || Boolean(secondaryActions); + const hasRightContent = + Boolean(filter) || Boolean(search) || Boolean(columnVisibility) || Boolean(viewSwitch); + + return ( + + {hasLeftContent && ( +
+ {primaryActions} + {secondaryActions} +
+ )} + {hasRightContent && ( +
+ {filter} + {search} + {columnVisibility} + {viewSwitch} +
+ )} +
+ ); +} diff --git a/src/custom/DataTableToolbar/DataTableToolbar.types.ts b/src/custom/DataTableToolbar/DataTableToolbar.types.ts new file mode 100644 index 000000000..9eaf9955f --- /dev/null +++ b/src/custom/DataTableToolbar/DataTableToolbar.types.ts @@ -0,0 +1,24 @@ +import type { SxProps, Theme } from '@mui/material'; + +export interface DataTableToolbarProps { + /** Left side: primary action buttons (Add, Create, Import) */ + primaryActions?: React.ReactNode; + + /** Left side next to primary: secondary actions (Export, bulk delete) */ + secondaryActions?: React.ReactNode; + + /** Right side: SearchBar component */ + search?: React.ReactNode; + + /** Right side: UniversalFilter component */ + filter?: React.ReactNode; + + /** Right side: Column visibility control */ + columnVisibility?: React.ReactNode; + + /** Right side: Grid/table view toggle */ + viewSwitch?: React.ReactNode; + + /** Custom styles for migration compatibility */ + sx?: SxProps; +} diff --git a/src/custom/DataTableToolbar/index.tsx b/src/custom/DataTableToolbar/index.tsx new file mode 100644 index 000000000..47a241fc8 --- /dev/null +++ b/src/custom/DataTableToolbar/index.tsx @@ -0,0 +1,2 @@ +export { DataTableToolbar } from './DataTableToolbar'; +export type { DataTableToolbarProps } from './DataTableToolbar.types'; diff --git a/src/custom/index.tsx b/src/custom/index.tsx index 51a104ce5..074966387 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -14,6 +14,7 @@ import { } from './CustomColumnVisibilityControl/CustomColumnVisibilityControl'; import { CustomImage } from './CustomImage'; import { CustomTooltip, InfoTooltip } from './CustomTooltip'; +import { DataTableToolbar } from './DataTableToolbar'; import { CustomDialog, StyledDialogActions, @@ -94,6 +95,7 @@ export { CustomImage, CustomTooltip, DataTableEllipsisMenu, + DataTableToolbar, EmptyState, EmptyStateCard, ErrorBoundary, @@ -154,6 +156,7 @@ export type { CustomColumn, CustomColumnVisibilityControlProps, CustomDialogProps, + DataTableToolbarProps, FlipCardProps, IPopperListener, ResponsiveDataTableProps, From 8e0caca7e0f21b2baf1881f4437a06e7bd69237e Mon Sep 17 00:00:00 2001 From: Udaybir Singh Date: Tue, 30 Jun 2026 15:33:46 +0530 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Udaybir Singh --- src/__testing__/DataTableToolbar.test.tsx | 2 +- src/custom/DataTableToolbar/DataTableToolbar.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/__testing__/DataTableToolbar.test.tsx b/src/__testing__/DataTableToolbar.test.tsx index b913d27a1..4de42a409 100644 --- a/src/__testing__/DataTableToolbar.test.tsx +++ b/src/__testing__/DataTableToolbar.test.tsx @@ -59,6 +59,6 @@ describe('DataTableToolbar', () => { it('applies custom sx styles', () => { const { container } = renderWithTheme(); const root = container.firstChild as HTMLElement; - expect(root).toBeTruthy(); + expect(root).toHaveStyle('margin-top: 32px'); }); }); diff --git a/src/custom/DataTableToolbar/DataTableToolbar.tsx b/src/custom/DataTableToolbar/DataTableToolbar.tsx index 06965a920..44b956668 100644 --- a/src/custom/DataTableToolbar/DataTableToolbar.tsx +++ b/src/custom/DataTableToolbar/DataTableToolbar.tsx @@ -12,7 +12,6 @@ const ToolbarRoot = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.card, borderRadius: theme.spacing(1), boxShadow: theme.shadows[2], - zIndex: 101, [theme.breakpoints.down('sm')]: { height: 'auto',