Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/__testing__/DataTableToolbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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(<SistentThemeProvider>{ui}</SistentThemeProvider>);

describe('DataTableToolbar', () => {
it('renders primaryActions content', () => {
renderWithTheme(<DataTableToolbar primaryActions={<button>Add</button>} />);
expect(screen.getByRole('button', { name: 'Add' })).toBeTruthy();
});

it('renders secondaryActions content', () => {
renderWithTheme(<DataTableToolbar secondaryActions={<button>Export</button>} />);
expect(screen.getByRole('button', { name: 'Export' })).toBeTruthy();
});

it('renders search slot', () => {
renderWithTheme(<DataTableToolbar search={<input placeholder="Search" />} />);
expect(screen.getByPlaceholderText('Search')).toBeTruthy();
});

it('renders filter slot', () => {
renderWithTheme(<DataTableToolbar filter={<div>Filter</div>} />);
expect(screen.getByText('Filter')).toBeTruthy();
});

it('renders columnVisibility slot', () => {
renderWithTheme(<DataTableToolbar columnVisibility={<div>Columns</div>} />);
expect(screen.getByText('Columns')).toBeTruthy();
});

it('renders viewSwitch slot', () => {
renderWithTheme(<DataTableToolbar viewSwitch={<div>Grid/Table</div>} />);
expect(screen.getByText('Grid/Table')).toBeTruthy();
});

it('renders bulkOperations content in right section', () => {
renderWithTheme(<DataTableToolbar bulkOperations={<button>Select All</button>} />);
expect(screen.getByRole('button', { name: 'Select All' })).toBeTruthy();
});

it('renders all slots simultaneously with bulkOperations', () => {
renderWithTheme(
<DataTableToolbar
primaryActions={<button>Add</button>}
secondaryActions={<button>Export</button>}
bulkOperations={<button>Select All</button>}
search={<input placeholder="Search" />}
filter={<div>Filter</div>}
/>
);
expect(screen.getByRole('button', { name: 'Add' })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Export' })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Select All' })).toBeTruthy();
expect(screen.getByPlaceholderText('Search')).toBeTruthy();
expect(screen.getByText('Filter')).toBeTruthy();
});

it('renders search before filter in DOM order', () => {
const { container } = renderWithTheme(
<DataTableToolbar
search={<span data-testid="search">Search</span>}
filter={<span data-testid="filter">Filter</span>}
/>
);
const children = container.querySelectorAll('[data-testid="search"], [data-testid="filter"]');
expect(children.length).toBe(2);
expect(children[0].getAttribute('data-testid')).toBe('search');
expect(children[1].getAttribute('data-testid')).toBe('filter');
});

it('renders without any props (empty state)', () => {
const { container } = renderWithTheme(<DataTableToolbar />);
expect(container.firstChild).toBeTruthy();
});

it('applies custom sx styles', () => {
const { container } = renderWithTheme(<DataTableToolbar sx={{ marginTop: '32px' }} />);
const root = container.firstChild as HTMLElement;
expect(window.getComputedStyle(root).marginTop).toBe('32px');
});

it('renders searchHelperText when provided', () => {
renderWithTheme(
<DataTableToolbar searchHelperText="Search by name, kind, category" />
);
expect(screen.getByText('Search by name, kind, category')).toBeTruthy();
});

it('renders tabs content when provided', () => {
renderWithTheme(
<DataTableToolbar tabs={<div>Tab Content</div>} />
);
expect(screen.getByText('Tab Content')).toBeTruthy();
});

it('renders both searchHelperText and tabs together', () => {
renderWithTheme(
<DataTableToolbar
searchHelperText="Search items"
tabs={<div>My Tabs</div>}
/>
);
expect(screen.getByText('Search items')).toBeTruthy();
expect(screen.getByText('My Tabs')).toBeTruthy();
});
Comment thread
Uday9909 marked this conversation as resolved.
});
74 changes: 74 additions & 0 deletions src/custom/DataTableToolbar/DataTableToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Box, Typography } 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)
}));
Comment thread
Uday9909 marked this conversation as resolved.

export function DataTableToolbar({
primaryActions,
secondaryActions,
bulkOperations,
search,
filter,
columnVisibility,
viewSwitch,
searchHelperText,
tabs,
sx
}: DataTableToolbarProps): JSX.Element {
const hasLeftContent = Boolean(primaryActions);
const hasRightContent =
Boolean(bulkOperations) ||
Boolean(secondaryActions) ||
Boolean(filter) ||
Boolean(search) ||
Boolean(columnVisibility) ||
Boolean(viewSwitch);

return (
<>
<ToolbarRoot sx={sx}>
{hasLeftContent && <Section>{primaryActions}</Section>}
{hasRightContent && (
<Section>
{bulkOperations}
{secondaryActions}
{search}
{filter}
{columnVisibility}
{viewSwitch}
</Section>
)}
</ToolbarRoot>
{searchHelperText && (
<Typography variant="caption" color="textSecondary">
{searchHelperText}
</Typography>
)}
{tabs && <Box sx={{ mt: 2 }}>{tabs}</Box>}
</>
);
}
33 changes: 33 additions & 0 deletions src/custom/DataTableToolbar/DataTableToolbar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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: bulk action controls (select all, batch delete) */
bulkOperations?: 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;

/** Helper text displayed below the search bar (e.g., "Search by name, kind, category") */
searchHelperText?: string;

/** Tabs rendered below the toolbar, directly above the table */
tabs?: React.ReactNode;

/** Custom styles for migration compatibility */
sx?: SxProps<Theme>;
}
2 changes: 2 additions & 0 deletions src/custom/DataTableToolbar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DataTableToolbar } from './DataTableToolbar';
export type { DataTableToolbarProps } from './DataTableToolbar.types';
2 changes: 1 addition & 1 deletion src/custom/ResponsiveDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TooltipIcon } from './TooltipIconButton';
export const IconWrapper = styled('div', {
shouldForwardProp: (prop) => prop !== 'disabled'
})<{ disabled?: boolean }>(({ disabled = false }) => ({
width: '100%',
width: 'auto',
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: disabled ? '0.5' : '1',
display: 'flex',
Expand Down
2 changes: 1 addition & 1 deletion src/custom/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function SearchBar({
placeholder={placeholder}
data-testid="searchbar-input"
style={{
width: expanded ? '150px' : '0',
width: expanded ? '15rem' : '0',
opacity: expanded ? 1 : 0,
transition: 'width 0.3s ease, opacity 0.3s ease'
}}
Expand Down
3 changes: 3 additions & 0 deletions src/custom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from './CustomColumnVisibilityControl/CustomColumnVisibilityControl';
import { CustomImage } from './CustomImage';
import { CustomTooltip, InfoTooltip } from './CustomTooltip';
import { DataTableToolbar } from './DataTableToolbar';
import {
CustomDialog,
StyledDialogActions,
Expand Down Expand Up @@ -94,6 +95,7 @@ export {
CustomImage,
CustomTooltip,
DataTableEllipsisMenu,
DataTableToolbar,
EmptyState,
EmptyStateCard,
ErrorBoundary,
Expand Down Expand Up @@ -154,6 +156,7 @@ export type {
CustomColumn,
CustomColumnVisibilityControlProps,
CustomDialogProps,
DataTableToolbarProps,
FlipCardProps,
IPopperListener,
ResponsiveDataTableProps,
Expand Down
Loading