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
86 changes: 86 additions & 0 deletions src/__testing__/DataTableToolbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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(root).toHaveStyle('margin-top: 32px');
});
Comment thread
Uday9909 marked this conversation as resolved.
});
64 changes: 64 additions & 0 deletions src/custom/DataTableToolbar/DataTableToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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)
}));
Comment thread
Uday9909 marked this conversation as resolved.

export function DataTableToolbar({
primaryActions,
secondaryActions,
bulkOperations,
search,
filter,
columnVisibility,
viewSwitch,
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>
);
}
27 changes: 27 additions & 0 deletions src/custom/DataTableToolbar/DataTableToolbar.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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;

/** 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
35 changes: 27 additions & 8 deletions src/custom/UniversalFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Drawer, styled, useMediaQuery } from '@mui/material';
import { SelectChangeEvent } from '@mui/material/Select';
import { subDays, subMonths, subYears } from 'date-fns';
import React from 'react';
import { Button } from '../base/Button';
import { Badge, Button } from '../base';
import { ClickAwayListener } from '../base/ClickAwayListener';
import { DateTimePicker } from '../base/DateTimePicker';
import { InputLabel } from '../base/InputLabel';
Expand All @@ -12,7 +12,6 @@ import { Select } from '../base/Select';
import { FilterIcon } from '../icons';
import { useTheme } from '../theme';
import PopperListener from './PopperListener';
import { TooltipIcon } from './TooltipIconButton';

export interface FilterColumn {
name: string;
Expand Down Expand Up @@ -260,15 +259,35 @@ function UniversalFilter({
</div>
);

const activeFilterCount = Object.values(selectedFilters).filter(
(value) => value && value !== 'All'
).length;

return (
<>
<div id={id} data-testid={testId}>
<TooltipIcon
title="Filter"
onClick={handleClick}
icon={<FilterIcon fill={theme.palette.icon.default} />}
arrow
/>
<Badge
badgeContent={activeFilterCount}
color="primary"
overlap="circular"
invisible={activeFilterCount === 0}
sx={{
'& .MuiBadge-badge': {
fontSize: (t) => t.typography.caption.fontSize,
height: 20,
minWidth: 20
}
}}
>
<Button
variant="outlined"
onClick={handleClick}
startIcon={<FilterIcon fill={theme.palette.icon.default} />}
data-testid={`${testId}-trigger`}
>
Filter
</Button>
</Badge>
{!isMobile ? (
<PopperListener
id={open && anchorEl ? 'transition-popper' : undefined}
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 @@
} from './CustomColumnVisibilityControl/CustomColumnVisibilityControl';
import { CustomImage } from './CustomImage';
import { CustomTooltip, InfoTooltip } from './CustomTooltip';
import { DataTableToolbar } from './DataTableToolbar';
import {
CustomDialog,
StyledDialogActions,
Expand Down Expand Up @@ -99,6 +100,7 @@
CustomImage,
CustomTooltip,
DataTableEllipsisMenu,
DataTableToolbar,
EmptyState,
EmptyStateCard,
ErrorBoundary,
Expand Down Expand Up @@ -159,6 +161,7 @@
CustomColumn,
CustomColumnVisibilityControlProps,
CustomDialogProps,
DataTableToolbarProps,

Check failure on line 164 in src/custom/index.tsx

View workflow job for this annotation

GitHub Actions / compatibility-check (20)

'DataTableToolbarProps' is not defined

Check failure on line 164 in src/custom/index.tsx

View workflow job for this annotation

GitHub Actions / compatibility-check (22)

'DataTableToolbarProps' is not defined
FlipCardProps,
IPopperListener,
ResponsiveDataTableProps,
Expand Down
Loading