Skip to content
Merged
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
13 changes: 13 additions & 0 deletions apps/www/src/content/docs/components/datatable/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,21 @@ When grouping is enabled, you can make the current group label stick under the t
```tsx
function ServerTable() {
const [data, setData] = useState([]);
const [totalRowCount, setTotalRowCount] = useState<number | undefined>();
const [query, setQuery] = useState();
const [isLoading, setIsLoading] = useState(false);
const handleQueryChange = async (query: DataTableQuery) => {
setIsLoading(true);
setQuery(query);
const response = await fetchData(query);
setData(response.data);
setTotalRowCount(response.totalRowCount);
setIsLoading(false);
};
return (
<DataTable
data={data}
totalRowCount={totalRowCount}
query={query}
columns={columns}
isLoading={isLoading}
Expand All @@ -259,6 +262,16 @@ function ServerTable() {
}
```

### Filter Summary Footer

- In `mode="client"`, footer count is computed from in-memory rows and shown as:
`X items hidden by filters`
- In `mode="server"`:
- if `totalRowCount` is provided, footer shows the same numeric pattern
- if `totalRowCount` is missing, footer shows:
`Some items might be hidden by filters`
- `Clear Filters` clears both active filters and search.

### Custom Styling

```tsx
Expand Down
6 changes: 6 additions & 0 deletions apps/www/src/content/docs/components/datatable/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export interface DataTableProps {
/** Table data (Required) */
data: Array<T>;

/**
* Total rows available on server.
* Used in server mode to show hidden-by-filter count in footer.
*/
totalRowCount?: number;

/**
* Data processing mode
* @defaultValue "client"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,60 @@ describe('DataTable', () => {
// If data is displayed, it means hasData is true, which means shouldShowFilters would be true
expect(screen.getByText('John Doe')).toBeInTheDocument();
});

it('shows server hidden count when totalRowCount is provided', () => {
render(
<DataTable
data={mockData}
columns={columnsWithFilters}
defaultSort={{ name: 'name', order: 'asc' }}
mode='server'
totalRowCount={10}
query={{
filters: [
{
name: 'name',
operator: 'neq',
value: 'John Doe'
}
]
}}
>
<DataTable.Content />
</DataTable>
);

expect(screen.getByText('7')).toBeInTheDocument();
expect(screen.getByText('items hidden by filters')).toBeInTheDocument();
expect(screen.getByText('Clear Filters')).toBeInTheDocument();
});

it('shows generic server hidden message when totalRowCount is missing', () => {
render(
<DataTable
data={mockData}
columns={columnsWithFilters}
defaultSort={{ name: 'name', order: 'asc' }}
mode='server'
query={{
filters: [
{
name: 'name',
operator: 'neq',
value: 'John Doe'
}
]
}}
>
<DataTable.Content />
</DataTable>
);

expect(
screen.getByText('Some items might be hidden by filters')
).toBeInTheDocument();
expect(screen.getByText('Clear Filters')).toBeInTheDocument();
});
});

describe('Display Settings Reset', () => {
Expand Down
65 changes: 62 additions & 3 deletions packages/raystack/components/data-table/components/content.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use client';

import { TableIcon } from '@radix-ui/react-icons';
import { Cross2Icon, TableIcon } from '@radix-ui/react-icons';
import type { HeaderGroup, Row } from '@tanstack/react-table';
import { flexRender } from '@tanstack/react-table';
import { cx } from 'class-variance-authority';
import { useCallback, useEffect, useRef } from 'react';

import { Badge } from '../../badge';
import { Button } from '../../button';
import { EmptyState } from '../../empty-state';
import { Flex } from '../../flex';
import { Skeleton } from '../../skeleton';
Expand All @@ -18,7 +19,12 @@ import {
GroupedData
} from '../data-table.types';
import { useDataTable } from '../hooks/useDataTable';
import { hasActiveQuery } from '../utils';
import {
countLeafRows,
getClientHiddenLeafRowCount,
hasActiveQuery,
hasActiveTableFiltering
} from '../utils';

function Headers<TData>({
headerGroups = [],
Expand Down Expand Up @@ -180,12 +186,14 @@ export function Content({
onRowClick,
table,
mode,
totalRowCount,
isLoading,
loadMoreData,
loadingRowCount = 3,
tableQuery,
defaultSort,
stickyGroupHeader = false
stickyGroupHeader = false,
updateTableQuery
} = useDataTable();

const headerGroups = table?.getHeaderGroups();
Expand Down Expand Up @@ -246,6 +254,26 @@ export function Content({
? (emptyState ?? <DefaultEmptyComponent />)
: null;

const hiddenLeafRowCount =
mode === 'client'
? getClientHiddenLeafRowCount(table)
: totalRowCount !== undefined
? Math.max(0, totalRowCount - countLeafRows(rows))
: null;
const hasActiveFiltering = !isLoading && hasActiveTableFiltering(table);
const showFilterSummary =
hasActiveFiltering &&
(mode === 'server' ||
(typeof hiddenLeafRowCount === 'number' && hiddenLeafRowCount > 0));

const handleClearFilters = useCallback(() => {
updateTableQuery(prev => ({
...prev,
filters: [],
search: ''
}));
}, [updateTableQuery]);

return (
<div className={cx(styles.contentRoot, classNames.root)}>
<Table className={classNames.table}>
Expand Down Expand Up @@ -283,6 +311,37 @@ export function Content({
)}
</Table.Body>
</Table>
{showFilterSummary ? (
<Flex
className={styles.filterSummaryFooter}
justify='center'
align='center'
>
{mode === 'server' && hiddenLeafRowCount === null ? (
<span className={styles.filterSummaryLabel}>
Some items might be hidden by filters
</span>
) : (
<Flex align='center' gap={2}>
<span className={styles.filterSummaryCount}>
{hiddenLeafRowCount}
</span>
<span className={styles.filterSummaryLabel}>
items hidden by filters
</span>
</Flex>
)}
<Button
variant='text'
color='neutral'
size='small'
trailingIcon={<Cross2Icon />}
onClick={handleClearFilters}
>
Clear Filters
</Button>
</Flex>
) : null}
</div>
);
}
Expand Down
31 changes: 31 additions & 0 deletions packages/raystack/components/data-table/data-table.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@
flex: 1;
}

.filterSummaryFooter {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: var(--rs-space-4);
width: 100%;
padding: var(--rs-space-9) 0;
box-sizing: border-box;
}

.filterSummaryCount {
color: var(--rs-color-foreground-base-primary);
font-family: var(--rs-font-body);
font-size: var(--rs-font-size-small);
font-style: normal;
font-weight: var(--rs-font-weight-medium);
line-height: var(--rs-line-height-small);
letter-spacing: var(--rs-letter-spacing-small);
}

.filterSummaryLabel {
color: var(--rs-color-foreground-base-secondary);
font-family: var(--rs-font-body);
font-size: var(--rs-font-size-small);
font-style: normal;
font-weight: var(--rs-font-weight-regular);
line-height: var(--rs-line-height-small);
letter-spacing: var(--rs-letter-spacing-small);
}

.contentRoot {
height: 100%;
overflow: auto;
Expand Down
3 changes: 3 additions & 0 deletions packages/raystack/components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function DataTableRoot<TData, TValue>({
query,
mode = 'client',
isLoading = false,
totalRowCount,
loadingRowCount = 3,
defaultSort,
children,
Expand Down Expand Up @@ -194,6 +195,7 @@ function DataTableRoot<TData, TValue>({
updateTableQuery,
onDisplaySettingsReset,
defaultSort,
totalRowCount,
loadingRowCount,
onRowClick,
shouldShowFilters,
Expand All @@ -209,6 +211,7 @@ function DataTableRoot<TData, TValue>({
updateTableQuery,
onDisplaySettingsReset,
defaultSort,
totalRowCount,
loadingRowCount,
onRowClick,
shouldShowFilters,
Expand Down
2 changes: 2 additions & 0 deletions packages/raystack/components/data-table/data-table.types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export interface DataTableProps<TData, TValue> {
query?: DataTableQuery; // Initial query (will be transformed to internal format)
mode?: DataTableMode;
isLoading?: boolean;
totalRowCount?: number;
loadingRowCount?: number;
onTableQueryChange?: (query: DataTableQuery) => void;
defaultSort: DataTableSort;
Expand Down Expand Up @@ -156,6 +157,7 @@ export type TableContextType<TData, TValue> = {
mode: DataTableMode;
defaultSort: DataTableSort;
tableQuery?: InternalQuery;
totalRowCount?: number;
loadingRowCount?: number;
onDisplaySettingsReset: () => void;
updateTableQuery: (fn: TableQueryUpdateFn) => void;
Expand Down
24 changes: 24 additions & 0 deletions packages/raystack/components/data-table/utils/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Row, Table } from '@tanstack/react-table';
import { TableState } from '@tanstack/table-core';
import dayjs from 'dayjs';

Expand Down Expand Up @@ -314,6 +315,29 @@ export function dataTableQueryToInternal(query: DataTableQuery): InternalQuery {
};
}

/** Leaf count from the row tree. Do not use `model.flatRows` here: with `filterFromLeafRows`, TanStack's filtered model leaves `flatRows` empty while `rows` is correct. */
export function countLeafRows<T>(rows: Row<T>[]): number {
return rows.reduce(
(n, row) => n + (row.subRows?.length ? countLeafRows(row.subRows) : 1),
0
);
}

/** Difference between pre- and post-filter leaf rows (client mode only). */
export function getClientHiddenLeafRowCount<T>(table: Table<T>): number {
const pre = table.getPreFilteredRowModel();
const post = table.getFilteredRowModel();
return Math.max(0, countLeafRows(pre.rows) - countLeafRows(post.rows));
}

export function hasActiveTableFiltering<T>(table: Table<T>): boolean {
const state = table.getState();
if (state.columnFilters?.length > 0) return true;
const gf = state.globalFilter;
if (gf === undefined || gf === null) return false;
return String(gf).trim() !== '';
}

export function getDefaultTableQuery(
defaultSort: DataTableSort,
oldQuery: DataTableQuery = {}
Expand Down
Loading