diff --git a/src/__testing__/DataTableToolbar.test.tsx b/src/__testing__/DataTableToolbar.test.tsx new file mode 100644 index 000000000..4de42a409 --- /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).toHaveStyle('margin-top: 32px'); + }); +}); diff --git a/src/custom/DataTableToolbar/DataTableToolbar.tsx b/src/custom/DataTableToolbar/DataTableToolbar.tsx new file mode 100644 index 000000000..44b956668 --- /dev/null +++ b/src/custom/DataTableToolbar/DataTableToolbar.tsx @@ -0,0 +1,61 @@ +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], + + [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,