From 31d9b529f277d8a22451cd4a62f60b62b5870783 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Mon, 24 Nov 2025 11:59:07 +0530 Subject: [PATCH 1/2] feat: expose datatable filters --- apps/www/src/app/examples/table/page.tsx | 159 ++++++++++++++++++ .../docs/components/datatable/index.mdx | 15 ++ .../data-table/components/filters.tsx | 17 +- .../components/data-table/data-table.tsx | 4 +- 4 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 apps/www/src/app/examples/table/page.tsx diff --git a/apps/www/src/app/examples/table/page.tsx b/apps/www/src/app/examples/table/page.tsx new file mode 100644 index 00000000..0783c991 --- /dev/null +++ b/apps/www/src/app/examples/table/page.tsx @@ -0,0 +1,159 @@ +'use client'; +import { + DataTable, + DataTableColumnDef, + DataTableQuery, + Flex +} from '@raystack/apsara'; +import { useMemo, useState } from 'react'; + +const data: Payment[] = [ + { + id: 'm5gr84i9', + amount: 316, + status: 'success', + email: 'ken99@yahoo.com' + }, + { + id: '3u1reuv4', + amount: 242, + status: 'success', + email: 'Abe45@gmail.com' + }, + { + id: 'derv1ws0', + amount: 837, + status: 'processing', + email: 'Monserrat44@gmail.com' + }, + { + id: '5kma53ae', + amount: 874, + status: 'success', + email: 'Silas22@gmail.com' + }, + { + id: 'bhqecj4p', + amount: 721, + status: 'failed', + email: 'carmella@hotmail.com' + } +]; + +export type Payment = { + id: string; + amount: number; + status: 'pending' | 'processing' | 'success' | 'failed'; + email: string; +}; + +export const columns: DataTableColumnDef[] = [ + { + accessorKey: 'status', + header: 'Status', + cell: ({ row }) => ( +
{row.getValue('status')}
+ ), + filterOptions: [ + { + label: 'Pending', + value: 'pending' + }, + { + label: 'Processing', + value: 'processing' + }, + { + label: 'Success', + value: 'success' + }, + { + label: 'Failed', + value: 'failed' + } + ], + filterType: 'multiselect', + enableColumnFilter: true + }, + { + accessorKey: 'email', + header: 'Email', + cell: ({ row }) =>
{row.getValue('email')}
, + enableColumnFilter: true + }, + { + accessorKey: 'amount', + header: 'Amount', + cell: ({ row }) => { + const amount = parseFloat(row.getValue('amount')); + // Format the amount as a dollar amount + const formatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amount); + + return
{formatted}
; + }, + enableColumnFilter: true, + filterType: 'number' + } +]; + +const Page = () => { + const [tableQuery, setTableQuery] = useState(); + console.log('tableQuery>> ', tableQuery); + + const filteredData = useMemo(() => { + const filters = tableQuery?.filters?.map(filter => filter.name) || []; + return data.filter(item => { + let shouldShow = true; + if (filters.includes('email')) { + shouldShow = item.email.includes( + tableQuery?.filters?.[filters.indexOf('email')]?.value || '' + ); + } + if (shouldShow && filters.includes('amount')) { + shouldShow = + item.amount === + tableQuery?.filters?.[filters.indexOf('amount')]?.value; + } + if (shouldShow && filters.includes('status')) { + shouldShow = tableQuery?.filters?.[ + filters.indexOf('status') + ]?.value.includes(item.status); + } + return shouldShow; + }); + }, [tableQuery]); + + return ( + + + + + {filteredData.map(item => ( +
{`${item.email} - ${item.amount} - ${item.status}`}
+ ))} +
+
+
+ ); +}; + +export default Page; diff --git a/apps/www/src/content/docs/components/datatable/index.mdx b/apps/www/src/content/docs/components/datatable/index.mdx index 4bfc39f2..754c3fc5 100644 --- a/apps/www/src/content/docs/components/datatable/index.mdx +++ b/apps/www/src/content/docs/components/datatable/index.mdx @@ -189,3 +189,18 @@ const columns = [ }, ]; ``` + +### Using DataTable Filter + +The `DataTable.Filters` component can be used separately to filter data for custom views. + +```tsx + + + +``` diff --git a/packages/raystack/components/data-table/components/filters.tsx b/packages/raystack/components/data-table/components/filters.tsx index e1d34e76..8ad204ab 100644 --- a/packages/raystack/components/data-table/components/filters.tsx +++ b/packages/raystack/components/data-table/components/filters.tsx @@ -59,7 +59,17 @@ function AddFilter({ ) : null; } -export function Filters() { +export function Filters({ + classNames, + className +}: { + classNames?: { + container?: string; + filterChips?: string; + addFilter?: string; + }; + className?: string; +}) { const { table, tableQuery } = useDataTable(); const columns = table?.getAllColumns() as DataTableColumn[]; @@ -94,8 +104,8 @@ export function Filters() { }) || []; return ( - - + + {appliedFilters.map(filter => ( () { } columnType={filter.filterType} options={filter.options} + className={classNames?.filterChips} /> ))} diff --git a/packages/raystack/components/data-table/data-table.tsx b/packages/raystack/components/data-table/data-table.tsx index 05277fab..fb5cdc3f 100644 --- a/packages/raystack/components/data-table/data-table.tsx +++ b/packages/raystack/components/data-table/data-table.tsx @@ -10,6 +10,7 @@ import { } from '@tanstack/react-table'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Content } from './components/content'; +import { Filters } from './components/filters'; import { TableSearch } from './components/search'; import { Toolbar } from './components/toolbar'; import { TableContext } from './context'; @@ -171,5 +172,6 @@ function DataTableRoot({ export const DataTable = Object.assign(DataTableRoot, { Content: Content, Toolbar: Toolbar, - Search: TableSearch + Search: TableSearch, + Filters: Filters }); From 11e2e27bbf9782392d032fb468125c4eda2ca35c Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Mon, 24 Nov 2025 14:16:19 +0530 Subject: [PATCH 2/2] feat: support for custom trigger --- apps/www/src/app/examples/table/page.tsx | 24 ++++++- .../data-table/components/filters.tsx | 63 +++++++++++++------ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/apps/www/src/app/examples/table/page.tsx b/apps/www/src/app/examples/table/page.tsx index 0783c991..e8de99e3 100644 --- a/apps/www/src/app/examples/table/page.tsx +++ b/apps/www/src/app/examples/table/page.tsx @@ -1,10 +1,13 @@ 'use client'; import { + Button, DataTable, DataTableColumnDef, DataTableQuery, - Flex + Flex, + IconButton } from '@raystack/apsara'; +import { FilterIcon } from '@raystack/apsara/icons'; import { useMemo, useState } from 'react'; const data: Payment[] = [ @@ -144,7 +147,24 @@ const Page = () => { onTableQueryChange={setTableQuery} defaultSort={{ name: 'email', order: 'asc' }} > - + + appliedFilters.size > 0 ? ( + + + + ) : ( + + ) + } + /> {filteredData.map(item => (
= + | ReactNode + | (({ + availableFilters, + appliedFilters + }: { + availableFilters: DataTableColumn[]; + appliedFilters: Set; + }) => ReactNode); + interface AddFilterProps { columnList: DataTableColumn[]; appliedFiltersSet: Set; onAddFilter: (column: DataTableColumn) => void; + children?: Trigger; } function AddFilter({ columnList = [], appliedFiltersSet, - onAddFilter + onAddFilter, + children }: AddFilterProps) { const availableFilters = columnList?.filter( col => !appliedFiltersSet.has(col.id) ); + const trigger = useMemo(() => { + if (typeof children === 'function') + return children({ availableFilters, appliedFilters: appliedFiltersSet }); + else if (children) return children; + else if (appliedFiltersSet.size > 0) + return ( + + + + ); + else + return ( + + ); + }, [children, appliedFiltersSet, availableFilters]); + return availableFilters.length > 0 ? ( - - {appliedFiltersSet.size > 0 ? ( - - - - ) : ( - - )} - + {trigger} {availableFilters?.map(column => { const columnDef = column.columnDef; @@ -61,7 +82,8 @@ function AddFilter({ export function Filters({ classNames, - className + className, + trigger }: { classNames?: { container?: string; @@ -69,6 +91,7 @@ export function Filters({ addFilter?: string; }; className?: string; + trigger?: Trigger; }) { const { table, tableQuery } = useDataTable(); const columns = table?.getAllColumns() as DataTableColumn[]; @@ -129,7 +152,9 @@ export function Filters({ columnList={columnList} appliedFiltersSet={appliedFiltersSet} onAddFilter={onAddFilter} - /> + > + {trigger} + ); }