diff --git a/lib/components/Table/Table.variants.ts b/lib/components/Table/Table.variants.ts index 6bde4ddc5..3a6930009 100644 --- a/lib/components/Table/Table.variants.ts +++ b/lib/components/Table/Table.variants.ts @@ -29,7 +29,7 @@ export const rowVariants = cva( ); export const headVariants = cva([ - 'h-[18px]', + 'h-4.5', 'bg-slate-100', '[&>tr>th]:py-3', '[&>tr>th]:px-4', diff --git a/lib/components/VirtualizedTable/VirtualizedTable.test.tsx b/lib/components/VirtualizedTable/VirtualizedTable.test.tsx new file mode 100644 index 000000000..857a13ae1 --- /dev/null +++ b/lib/components/VirtualizedTable/VirtualizedTable.test.tsx @@ -0,0 +1,123 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import { VirtualizedTable } from './VirtualizedTable'; +import { ColumnDef, Props } from './VirtualizedTable.types'; + +type Item = { + id: string; + name: string; + status: string; +}; + +const columns: ColumnDef[] = [ + { + header: 'Name', + accessorKey: 'name', + }, + { + header: 'Status', + accessorKey: 'status', + }, +]; + +const data: Item[] = [ + { id: '1', name: 'Alpha', status: 'active' }, + { id: '2', name: 'Beta', status: 'inactive' }, + { id: '3', name: 'Gamma', status: 'active' }, +]; + +describe('VirtualizedTable', () => { + const setup = (pagination?: { totalItems: number }) => { + const defaultProps = { + id: 'test-table', + ariaLabel: 'Test table', + columns, + data, + } satisfies Props; + + const queryClient = new QueryClient(); + + const { container } = render( + + {pagination ? ( + + {...defaultProps} + showPagination={true} + totalItems={pagination.totalItems} + /> + ) : ( + {...defaultProps} /> + )} + , + ); + + const getTable = () => screen.getByRole('table', { name: /test table/i }); + const getWrapperBody = () => getTable().parentElement as HTMLElement; + const getLastCell = () => { + const cells = screen.getAllByRole('cell'); + + return cells[cells.length - 1]; + }; + const queryPaginationBar = () => screen.queryByText(/results/i); + + return { + component: container, + getTable, + getWrapperBody, + getLastCell, + queryPaginationBar, + }; + }; + + it('should render the component', () => { + const { getTable } = setup(); + + expect(getTable()).toBeInTheDocument(); + }); + + it('should render the rows', () => { + setup(); + + expect(screen.getByText('Alpha')).toBeInTheDocument(); + expect(screen.getByText('Beta')).toBeInTheDocument(); + expect(screen.getByText('Gamma')).toBeInTheDocument(); + }); + + it('should close the bottom borders when pagination is disabled', () => { + const { getWrapperBody, getLastCell, queryPaginationBar } = setup(); + + expect(queryPaginationBar()).not.toBeInTheDocument(); + expect(getWrapperBody()).toHaveClass('rounded-lg'); + expect(getLastCell()).toHaveClass('rounded-br-lg'); + }); + + it('should close the bottom borders when pagination is enabled but the bar is hidden for few items', () => { + const { getWrapperBody, getLastCell, queryPaginationBar } = setup({ + totalItems: data.length, + }); + + expect(queryPaginationBar()).not.toBeInTheDocument(); + expect(getWrapperBody()).toHaveClass('rounded-lg'); + expect(getLastCell()).toHaveClass('rounded-br-lg'); + }); + + it('should let the pagination bar close the bottom borders when it is visible', () => { + const { getWrapperBody, getLastCell, queryPaginationBar } = setup({ + totalItems: 25, + }); + + expect(queryPaginationBar()).toBeInTheDocument(); + expect(getWrapperBody()).not.toHaveClass('rounded-lg'); + expect(getLastCell()).not.toHaveClass('rounded-br-lg'); + }); + + it("should doesn't have violations", async () => { + const { component } = setup(); + + const results = await axe(component); + + expect(results).toHaveNoViolations(); + }); +}); diff --git a/lib/components/VirtualizedTable/VirtualizedTable.tsx b/lib/components/VirtualizedTable/VirtualizedTable.tsx index e73b6cdff..fae460fa9 100644 --- a/lib/components/VirtualizedTable/VirtualizedTable.tsx +++ b/lib/components/VirtualizedTable/VirtualizedTable.tsx @@ -105,7 +105,13 @@ const VirtualizedTableInner = ({ showDotPagination, showFormPagination, ].some(Boolean), - [], + [ + showPaginationProp, + showTotalItems, + showDropdownPagination, + showDotPagination, + showFormPagination, + ], ); return ( diff --git a/lib/components/VirtualizedTable/components/Body/Body.tsx b/lib/components/VirtualizedTable/components/Body/Body.tsx index a14b26e93..a62db1fcb 100644 --- a/lib/components/VirtualizedTable/components/Body/Body.tsx +++ b/lib/components/VirtualizedTable/components/Body/Body.tsx @@ -5,6 +5,7 @@ import { Skeleton } from '@/components/VirtualizedTable/components/Skeleton/Skel import { cn } from '@/utils'; import { useTableContext } from '../../contexts'; +import { isPaginationBarVisible } from '../../utils'; import { RowData, RowDataWithMeta } from '../../VirtualizedTable.types'; import { ExpandableRow } from '../ExpandableRow/ExpandableRow'; @@ -18,6 +19,7 @@ export const Body = ({ const { table, pageSize, + totalItems = -Infinity, tableFetching, enableExpandedRow, classNameExpandedRow, @@ -36,6 +38,7 @@ export const Body = ({ } const rows = table.getRowModel().rows ?? []; + const hasPaginationBar = showPagination && isPaginationBarVisible(totalItems); if (rows.length === 0 && emptyState) { const colSpan = table.getVisibleLeafColumns().length; @@ -156,14 +159,14 @@ export const Body = ({ rowIndex === rows.length - 1 && firstDataColumnIndex !== null && columnIndex === firstDataColumnIndex && - !showPagination && + !hasPaginationBar && !isExpanded, 'rounded-br-lg': rowIndex === rows.length - 1 && columnIndex === columns.length - 1 && - !showPagination && + !hasPaginationBar && !isExpanded, - 'dark:[tr:last-child_&]:border-b': !showPagination, + 'dark:[tr:last-child_&]:border-b': !hasPaginationBar, }, )} data-expanded={isExpanded ? true : undefined} @@ -183,7 +186,7 @@ export const Body = ({ colSpan={columns.length} id={id} isExpanded={!!isExpanded} - isLastRow={rowIndex === rows.length - 1 && !showPagination} + isLastRow={rowIndex === rows.length - 1 && !hasPaginationBar} > {meta.expandedRow ?? renderExpandedRow?.(original)} diff --git a/lib/components/VirtualizedTable/components/Pagination/Pagination.tsx b/lib/components/VirtualizedTable/components/Pagination/Pagination.tsx index a11d5bd62..2934412e9 100644 --- a/lib/components/VirtualizedTable/components/Pagination/Pagination.tsx +++ b/lib/components/VirtualizedTable/components/Pagination/Pagination.tsx @@ -1,7 +1,7 @@ import { FC } from 'react'; -import { DEFAULT_PAGE_SIZE } from '../../constants'; import { useTableContext } from '../../contexts'; +import { isPaginationBarVisible } from '../../utils'; import { DotPaginate } from '../DotPaginate/DotPaginate'; import { DropdownPaginate } from '../DropdownPaginate/DropdownPaginate'; @@ -25,7 +25,7 @@ export const Pagination: FC = ({ isFirstLoad, } = useTableContext(); - if (totalItems <= DEFAULT_PAGE_SIZE || tableFetching || isLoading) { + if (!isPaginationBarVisible(totalItems) || tableFetching || isLoading) { return null; } diff --git a/lib/components/VirtualizedTable/components/WrapperBody/WrapperBody.tsx b/lib/components/VirtualizedTable/components/WrapperBody/WrapperBody.tsx index 2a6d2ddd2..a039338dd 100644 --- a/lib/components/VirtualizedTable/components/WrapperBody/WrapperBody.tsx +++ b/lib/components/VirtualizedTable/components/WrapperBody/WrapperBody.tsx @@ -3,6 +3,7 @@ import { FC } from 'react'; import { Props } from './WrapperBody.types'; import { useTableContext } from '../../contexts'; +import { isPaginationBarVisible } from '../../utils'; export const WrapperBody: FC = ({ children, @@ -10,14 +11,18 @@ export const WrapperBody: FC = ({ isLoading, showPagination, }) => { - const { tableFetching } = useTableContext(); + const { tableFetching, totalItems = -Infinity } = useTableContext(); return (
{ + const id = useId(); + const [pokemons, setPokemons] = useState([]); + + useEffect(() => { + const init = async () => { + const result = await getPokemons({ page: 1, pageSize: 5 }); + + setPokemons(result.results); + }; + + init(); + }, []); + + useEffect(() => { + document.body.setAttribute('data-theme', 'dark'); + document.body.classList.add('bg-metal-900'); + + return () => { + document.body.removeAttribute('data-theme'); + document.body.classList.remove('bg-metal-900'); + }; + }, []); + + return ( + + + id={id} + ariaLabel="List of pokemons" + classNameWrapperTable="overflow-visible" + data={pokemons} + columns={columns} + showPagination={true} + totalItems={pokemons.length} + /> + + ); + }, +}; + type PokemonWithMeta = Pokemon & { meta?: { expandedRow?: ReactNode }; }; diff --git a/lib/components/VirtualizedTable/stories/Light.stories.tsx b/lib/components/VirtualizedTable/stories/Light.stories.tsx index 7fb51fad2..6634a38ca 100644 --- a/lib/components/VirtualizedTable/stories/Light.stories.tsx +++ b/lib/components/VirtualizedTable/stories/Light.stories.tsx @@ -304,6 +304,37 @@ export const Default: Story = { }, }; +export const PaginationWithFewItems: Story = { + render: () => { + const id = useId(); + const [pokemons, setPokemons] = useState([]); + + useEffect(() => { + const init = async () => { + const result = await getPokemons({ page: 1, pageSize: 5 }); + + setPokemons(result.results); + }; + + init(); + }, []); + + return ( + + + id={id} + ariaLabel="List of pokemons" + classNameWrapperTable="overflow-visible" + data={pokemons} + columns={columns} + showPagination={true} + totalItems={pokemons.length} + /> + + ); + }, +}; + type PokemonWithMeta = Pokemon & { meta?: { expandedRow?: ReactNode }; }; diff --git a/lib/components/VirtualizedTable/utils/index.ts b/lib/components/VirtualizedTable/utils/index.ts new file mode 100644 index 000000000..cb727655b --- /dev/null +++ b/lib/components/VirtualizedTable/utils/index.ts @@ -0,0 +1 @@ +export * from './pagination'; diff --git a/lib/components/VirtualizedTable/utils/pagination.ts b/lib/components/VirtualizedTable/utils/pagination.ts new file mode 100644 index 000000000..78fb63c4e --- /dev/null +++ b/lib/components/VirtualizedTable/utils/pagination.ts @@ -0,0 +1,5 @@ +import { DEFAULT_PAGE_SIZE } from '../constants'; + +export const isPaginationBarVisible = (totalItems: number) => { + return totalItems > DEFAULT_PAGE_SIZE; +};