`;
-exports[`DataTable::subHeader should render correctly when a subheader is enabled 1`] = `
-.c0 {
- position: relative;
- display: flex;
- flex: 1 1 auto;
- box-sizing: border-box;
- align-items: center;
- padding: 4px 16px 4px 24px;
- width: 100%;
- justify-content: flex-end;
- flex-wrap: wrap;
- background-color: #FFFFFF;
- min-height: 52px;
-}
-
-
-`;
-
-exports[`DataTable::subHeader should render correctly with center align 1`] = `
-.c0 {
- position: relative;
- display: flex;
- flex: 1 1 auto;
- box-sizing: border-box;
- align-items: center;
- padding: 4px 16px 4px 24px;
- width: 100%;
- justify-content: center;
- flex-wrap: wrap;
- background-color: #FFFFFF;
- min-height: 52px;
-}
-
-
-`;
-
-exports[`DataTable::subHeader should render correctly with left align 1`] = `
-.c0 {
- position: relative;
- display: flex;
- flex: 1 1 auto;
- box-sizing: border-box;
- align-items: center;
- padding: 4px 16px 4px 24px;
- width: 100%;
- justify-content: flex-start;
- flex-wrap: wrap;
- background-color: #FFFFFF;
- min-height: 52px;
-}
-
-
-`;
-
-exports[`DataTable::subHeader should render correctly with right align 1`] = `
-.c0 {
- position: relative;
- display: flex;
- flex: 1 1 auto;
- box-sizing: border-box;
- align-items: center;
- padding: 4px 16px 4px 24px;
- width: 100%;
- justify-content: flex-end;
- flex-wrap: wrap;
- background-color: #FFFFFF;
- min-height: 52px;
-}
-
-
-`;
-
-exports[`DataTable::subHeader should render when subHeaderWrap is false 1`] = `
-.c0 {
- position: relative;
- display: flex;
- flex: 1 1 auto;
- box-sizing: border-box;
- align-items: center;
- padding: 4px 16px 4px 24px;
- width: 100%;
- justify-content: flex-end;
- flex-wrap: nowrap;
- background-color: #FFFFFF;
- min-height: 52px;
-}
-
-
-`;
-
exports[`data prop changes should update state if the data prop changes 1`] = `
.c2 {
position: relative;
@@ -20161,19 +21368,21 @@ exports[`data prop changes should update state if the data prop changes 1`] = `
class="c5 c6 rdt_TableCol"
data-column-id="1"
>
-
@@ -20654,19 +21863,21 @@ exports[`should render correctly when conditionalRowStyles is used with an expan
class="c7 c8 rdt_TableCol"
data-column-id="1"
>
-
@@ -21004,19 +22215,21 @@ exports[`should render correctly when conditionalRowStyles is used with an expan
class="c7 c8 rdt_TableCol"
data-column-id="1"
>
-
@@ -21282,19 +22495,21 @@ exports[`should render correctly when conditionalRowStyles with classNames is us
class="c5 c6 rdt_TableCol"
data-column-id="1"
>
-
@@ -21496,19 +22711,21 @@ exports[`should render correctly when disabled 1`] = `
class="c5 c6 rdt_TableCol"
data-column-id="1"
>
-
@@ -21707,19 +22924,21 @@ exports[`should render the correctly when using selector function 1`] = `
class="c5 c6 rdt_TableCol"
data-column-id="1"
>
-
@@ -21899,19 +23118,21 @@ exports[`should render the correctly when using selector function and a format f
class="c5 c6 rdt_TableCol"
data-column-id="1"
>
-
diff --git a/src/DataTable/defaultProps.tsx b/src/DataTable/defaultProps.tsx
index 3908f027..1ba2cd5c 100644
--- a/src/DataTable/defaultProps.tsx
+++ b/src/DataTable/defaultProps.tsx
@@ -68,6 +68,7 @@ export const defaultProps = {
subHeaderAlign: Alignment.RIGHT,
subHeaderWrap: true,
subHeaderComponent: null,
+ filterServer: false,
fixedHeader: false,
fixedHeaderScrollHeight: '100vh',
pagination: false,
@@ -94,6 +95,7 @@ export const defaultProps = {
direction: Direction.AUTO,
onChangePage: noop,
onChangeRowsPerPage: noop,
+ onFilter: noop,
onRowClicked: noop,
onRowDoubleClicked: noop,
onRowMouseEnter: noop,
diff --git a/src/DataTable/tableReducer.ts b/src/DataTable/tableReducer.ts
index a9c9beaf..8bd9ea4e 100644
--- a/src/DataTable/tableReducer.ts
+++ b/src/DataTable/tableReducer.ts
@@ -134,6 +134,22 @@ export function tableReducer
(state: TableState, action: Action): TableS
};
}
+ case 'FILTER_CHANGE': {
+ const { filterText, selectedColumn } = action;
+ const keyName = selectedColumn.name?.toString() || selectedColumn.id?.toString() || 'noname';
+ let filters = { ...state.filters };
+ if (!filterText && keyName in filters) {
+ delete filters[keyName];
+ } else {
+ filters = { ...filters, [keyName]: { column: selectedColumn, value: filterText } };
+ }
+ return {
+ ...state,
+ filters,
+ filterActive: Object.keys(filters).length > 0,
+ };
+ }
+
case 'CHANGE_PAGE': {
const { page, paginationServer, visibleOnly, persistSelectedOnPageChange } = action;
const mergeSelections = paginationServer && persistSelectedOnPageChange;
diff --git a/src/DataTable/types.ts b/src/DataTable/types.ts
index 66a7ecb9..8de9625d 100644
--- a/src/DataTable/types.ts
+++ b/src/DataTable/types.ts
@@ -28,6 +28,11 @@ export type PaginationComponentProps = {
};
export type PaginationComponent = React.ComponentType;
+export type FilterComponentProps = {
+ name: string;
+ onChange: (e: React.ChangeEvent) => void;
+}
+
export type TableProps = {
actions?: React.ReactNode | React.ReactNode[];
className?: string;
@@ -54,6 +59,7 @@ export type TableProps = {
expandableRowsHideExpander?: boolean;
expandOnRowClicked?: boolean;
expandOnRowDoubleClicked?: boolean;
+ filterServer?: boolean;
fixedHeader?: boolean;
fixedHeaderScrollHeight?: string;
highlightOnHover?: boolean;
@@ -64,6 +70,7 @@ export type TableProps = {
noTableHead?: boolean;
onChangePage?: PaginationChangePage;
onChangeRowsPerPage?: PaginationChangeRowsPerPage;
+ onFilter?: (filters: { [k: string]: { column: TableColumn; value: string } }) => void;
onRowClicked?: (row: T, e: React.MouseEvent) => void;
onRowDoubleClicked?: (row: T, e: React.MouseEvent) => void;
onRowMouseEnter?: (row: T, e: React.MouseEvent) => void;
@@ -132,8 +139,11 @@ export type TableColumnBase = {
omit?: boolean;
right?: boolean;
sortable?: boolean;
+ filterable?: boolean;
+ filterValues?: Primitive[] | {value: Primitive, label: string}[];
style?: CSSObject;
width?: string;
+ filterValue?: string
wrap?: boolean;
};
@@ -245,6 +255,9 @@ export type TableState = {
contextMessage: ContextMessage;
selectedCount: number;
selectedRows: T[];
+ filteredData: T[];
+ filterActive: boolean;
+ filters: { [k: string]: { column: TableColumn; value: string } };
selectedColumn: TableColumn;
sortDirection: SortOrder;
currentPage: number;
@@ -345,6 +358,18 @@ export interface SortAction {
clearSelectedOnSort: boolean;
}
+export interface FilterAction {
+ type: 'FILTER_CHANGE';
+ filterServer: boolean;
+ filterText: string;
+ selectedColumn: TableColumn;
+ clearSelectedOnSort: boolean;
+ //pagination: boolean;
+ //paginationServer: boolean;
+ //visibleOnly: boolean;
+ //persistSelectedOnSort: boolean;
+}
+
export interface PaginationPageAction {
type: 'CHANGE_PAGE';
page: number;
@@ -374,6 +399,8 @@ export type Action =
| SingleRowAction
| MultiRowAction
| SortAction
+ | FilterAction
| PaginationPageAction
| PaginationRowsPerPageAction
+ | FilterAction
| ClearSelectedRowsAction;
diff --git a/stories/DataTable/KitchenSink.stories.js b/stories/DataTable/KitchenSink.stories.js
index f1f25780..aa761b16 100644
--- a/stories/DataTable/KitchenSink.stories.js
+++ b/stories/DataTable/KitchenSink.stories.js
@@ -20,12 +20,14 @@ const columns = [
name: 'Title',
selector: row => row.title,
sortable: true,
+ filterable: true,
reorder: true,
},
{
name: 'Director',
selector: row => row.director,
sortable: true,
+ filterable: true,
reorder: true,
},
{
@@ -33,6 +35,8 @@ const columns = [
selector: row => row.year,
sortable: true,
reorder: true,
+ filterable: true,
+ filterValues: [1998, 1999, {value: '2000', label: 'Millenium'}, 2001],
},
];
@@ -74,6 +78,8 @@ const KitchenSinkStory = ({
);
return (
+ <>
+ JavaScript
+ />
+ >
);
};
diff --git a/stories/DataTable/sorting/remote.mdx b/stories/DataTable/sorting/remote.mdx
index 46b883af..7dd44886 100644
--- a/stories/DataTable/sorting/remote.mdx
+++ b/stories/DataTable/sorting/remote.mdx
@@ -15,12 +15,14 @@ const columns = [
selector: row => row.title,
sortable: true,
sortField: 'title',
+ filterable: true,
},
{
name: 'Director',
selector: row => row.director,
sortable: true,
sortField: 'director',
+ filterable: true,
},
{
name: 'Year',
diff --git a/stories/DataTable/sorting/remote.stories.js b/stories/DataTable/sorting/remote.stories.js
index a3282a97..8e0e479c 100644
--- a/stories/DataTable/sorting/remote.stories.js
+++ b/stories/DataTable/sorting/remote.stories.js
@@ -3,6 +3,7 @@ import doc from './remote.mdx';
import { orderBy } from 'lodash';
import initData from '../../constants/sampleMovieData';
import DataTable from '../../../src/index';
+import { getProperty } from '../../../src/DataTable/util';
const columns = [
{
@@ -10,12 +11,15 @@ const columns = [
selector: row => row.title,
sortable: true,
sortField: 'title',
+ filterable: true,
+ filterValue: 'god',
},
{
name: 'Director',
selector: row => row.director,
sortable: true,
sortField: 'director',
+ filterable: true,
},
{
name: 'Year',
@@ -41,11 +45,32 @@ export const RemoteSort = () => {
}, 100);
};
+ const handleFilter = filters => {
+ // simulate server sort
+
+ setLoading(true);
+
+ // instead of setTimeout this is where you would handle your API call.
+ setTimeout(() => {
+ const filteredRows = initData.filter((row, idx) =>
+ Object.entries(filters) //
+ .reduce((acc, [_, { column, value }]) => {
+ return new RegExp(`.*${value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*`, 'i').test(getProperty(row, column.selector, null, idx)?.toString() ?? '')
+ ? acc
+ : false;
+ }, true),
+ );
+ setData(filteredRows);
+ setLoading(false);
+ }, 100);
+ };
+
return (
row.title,
sortable: true,
+ filterable: true,
},
{
name: 'Director',
selector: row => row.director,
sortable: true,
+ filterable: true,
},
{
name: 'Year',
diff --git a/stories/columns.stories.mdx b/stories/columns.stories.mdx
index 5c20d6e1..f184babf 100644
--- a/stories/columns.stories.mdx
+++ b/stories/columns.stories.mdx
@@ -18,15 +18,17 @@ import { Meta } from '@storybook/addon-docs';
| right | boolean | no | Right aligns the content in the cell. useful for numbers |
| center | boolean | no | Center aligns the content in the cell |
| compact | boolean | no | Sets cell padding to 0 |
-| ignoreRowClick | boolean | no | Prevents the `onRowClicked` and `onRowDoubleClicked` event from being passed on the specific TableCell column. This is useful for a menu or button where you do not want the `onRowClicked` triggered, such as when using `onRowClicked` for navigation or routing |
+| ignoreRowClick | boolean | no | Prevents the `onRowClicked` and `onRowDoubleClicked` event from being passed on the specific TableCell column. This is useful for a menu or button where you do not want the `onRowClicked` triggered, such as when using `onRowClicked` for navigation or routing |
| button | boolean | no | This is like `ignoreRowClick` except it will apply additional styling for button placement. you do not need to set `ignoreRowClick` when using `button` |
| wrap | boolean | no | Whether the cell content should be allowed to wrap.
**Note:** `cell` negates `wrap` |
| allowOverflow | boolean | no | Allows content in the cell to overflow. useful for menus/layovers that do not rely on "smart" positioning |
| hide | integer or `sm`, `md`, `lg` | no | Specify a screen size (breakpoint) as an integer (in pixels) that hides the column when resizing the browser window. You can also use the preset values of: `sm` (small), `md`(medium), and `lg`(large). The default breakpoints are defined [here](#built-in-breakpoints) |
| omit | boolean | no | Omits the column from the table. useful if you need to hide access to data. |
+| filterable | boolean | no | If the column is filterable.
**Note:** `selector` and `name` is required for the column to filter |
+| filterValues | Primitive[] or `or {value: Primitive, label: string}[]`| no | If this list is defined a select box with its values is rendered as filter cell instead of an input element |
| sortable | boolean | no | If the column is sortable.
**Note:** `selector` is required for the column to sort |
| sortField | string | no | This field is typically used with [onSort](/docs/api-props--page#sorting) and when you want to give a specific column a string based name. Another example is remote sorting and when using [sortSever](/docs/api-props--page#sorting) together with `onSort` to call an API for sorting. `sortField` will gives the field you can use to build your sort query url. In this case you would provide the `sortField` to build a URL: `https://api.github.com/search/repositories?q=blog&sort=${column.sortField}&order=${sortDirection}` |
-| sortFunction | `(a, b) => {}` | no | By default RDT uses `Array.sort`, however, you can override the default behavior by passing in a custom sort function. [defining a custom sort function](/docs/api-custom-sorting--page#custom-sorting---sortfunction) |
+| sortFunction | `(a, b) => {}` | no | By default RDT uses `Array.sort`, however, you can override the default behavior by passing in a custom sort function. [defining a custom sort function](/docs/api-custom-sorting--page#custom-sorting---sortfunction) |
| reorder | boolean | no | Allows a column to be dragged and reordered |
| style | object | no | Allows you to customize the css of the cell using css-in-js [style objects](https://www.styled-components.com/docs/advanced#style-objects) |
| conditionalCellStyles | array | no | Allows an array of [conditional style objects](/docs/api-custom-conditional-formatting--page#conditional-style-object) to conditionally apply css styles to a cell |
diff --git a/stories/development.stories.mdx b/stories/development.stories.mdx
index 37f2fa2b..cea505cf 100644
--- a/stories/development.stories.mdx
+++ b/stories/development.stories.mdx
@@ -1,4 +1,4 @@
-import { Meta } from '@storybook/addon-docs';
+# import { Meta } from '@storybook/addon-docs';
diff --git a/stories/intro.stories.mdx b/stories/intro.stories.mdx
index 215511a6..cfc2a94f 100644
--- a/stories/intro.stories.mdx
+++ b/stories/intro.stories.mdx
@@ -17,6 +17,7 @@ If you want to achieve balance with the force and want a simple but flexible tab
- Declarative configuration
- Built-in and configurable:
- Sorting
+ - Column Filtering
- Selectable Rows
- Expandable Rows
- Pagination
diff --git a/stories/props.stories.mdx b/stories/props.stories.mdx
index 5ac4860b..8e459c78 100644
--- a/stories/props.stories.mdx
+++ b/stories/props.stories.mdx
@@ -42,6 +42,13 @@ import { Meta } from '@storybook/addon-docs';
| sortFunction | _See description_ | no | null | Pass in your own custom sort function. **Your function must return an new array reference**, otherwise RDT will not re-render. For example, `Array.sort` sorts the array in place but because of JavaScript object equality it will be the same reference and will not re-render. A common pattern is to return `yourArray.slice(0)` which creates a new array.
**Type:** `(rows, selector, direction) => {}` |
| onSort | _See description_ | no | | Callback to access the sort state when a column is clicked. Returns `(column, sortDirection, event)`. See [Columns](/docs/api-columns--page) for Column Definitions API.
**Type:** `(selectedColumn, sortDirection, sortedRows) => {}` |
+# Filtering
+
+| Property | Type | Required | Default | Description |
+| ------------------ | --------------------------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| filterServer | boolean | no | null | Disables internal filtering for use with server-side/remote filtering or when you want to manually control the filter behavior. place your filtering logic and/or api calls in an `onFilter` handler. |
+| onFilter | `(filters: { [k: string]: { column: TableColumn; value: string } }) => void` | no | | callback to filter data whenever a filter expression is entered. Filter is an object that has one key for each column with a filter expression entered. Each key is equal to the columns `name`, `column` hold the column definition and `value` is the current filter expression for this column. |
+
# Row Selection
| Property | Type | Required | Default | Description |
diff --git a/stories/simpleExamples.stories.mdx b/stories/simpleExamples.stories.mdx
index 5a6f59c7..6c8e6240 100644
--- a/stories/simpleExamples.stories.mdx
+++ b/stories/simpleExamples.stories.mdx
@@ -86,6 +86,49 @@ function MyComponent() {
};
```
+## Basic Filtering
+
+Adding sorting a table has never been easier:
+
+```js
+import DataTable from 'react-data-table-component';
+
+const columns = [
+ {
+ name: 'Title',
+ selector: row => row.title,
+ filterable: true,
+ },
+ {
+ name: 'Year',
+ selector: row => row.year,
+ filterable: true,
+ },
+];
+
+const data = [
+ {
+ id: 1,
+ title: 'Beetlejuice',
+ year: '1988',
+ },
+ {
+ id: 2,
+ title: 'Ghostbusters',
+ year: '1984',
+ },
+]
+
+function MyComponent() {
+ return (
+
+ );
+};
+```
+
## Selectable Rows
Adding selectable rows is usually quite a bit of code. Let's simplify:
diff --git a/yarn.lock b/yarn.lock
index c1bcd6d8..9ce62473 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12977,9 +12977,9 @@ __metadata:
languageName: node
linkType: hard
-"react-data-table-component@workspace:.":
+"react-data-table-component-with-filter@workspace:.":
version: 0.0.0-use.local
- resolution: "react-data-table-component@workspace:."
+ resolution: "react-data-table-component-with-filter@workspace:."
dependencies:
"@babel/core": "npm:^7.23.7"
"@babel/eslint-parser": "npm:^7.23.3"