diff --git a/cypress/component/DataViewCheckboxFilter.cy.tsx b/cypress/component/DataViewCheckboxFilter.cy.tsx
new file mode 100644
index 0000000..eab26fb
--- /dev/null
+++ b/cypress/component/DataViewCheckboxFilter.cy.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { DataViewCheckboxFilter, DataViewCheckboxFilterProps } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
+import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
+
+describe('DataViewCheckboxFilter component', () => {
+ const defaultProps: DataViewCheckboxFilterProps = {
+ filterId: 'test-checkbox-filter',
+ title: 'Test checkbox filter',
+ value: [ 'workspace-one' ],
+ options: [
+ { label: 'Workspace one', value: 'workspace-one' },
+ { label: 'Workspace two', value: 'workspace-two' },
+ { label: 'Workspace three', value: 'workspace-three' },
+ ],
+ };
+
+ it('renders a checkbox filter with options', () => {
+ const onChange = cy.stub().as('onChange');
+
+ cy.mount(
+ } />
+ );
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]')
+ .contains('Test checkbox filter')
+ .should('be.visible');
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]')
+ .should('exist')
+ .contains('1');
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').click();
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]').should('be.visible');
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
+ .find('li')
+ .should('have.length', 3)
+ .first()
+ .contains('Workspace one');
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
+ .find('li')
+ .first()
+ .find('input[type="checkbox"]')
+ .should('be.checked');
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-menu"]')
+ .find('li')
+ .eq(1)
+ .find('input[type="checkbox"]')
+ .click();
+
+ cy.get('@onChange').should('have.been.calledWith', Cypress.sinon.match.object, [ 'workspace-two', 'workspace-one' ]);
+ });
+
+ it('renders a checkbox filter with no options selected', () => {
+ const emptyProps = { ...defaultProps, value: [] };
+
+ cy.mount(
+ } />
+ );
+
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-toggle"]').contains('Test checkbox filter');
+ cy.get('[data-ouia-component-id="DataViewCheckboxFilter-badge"]').should('not.exist');
+ });
+});
diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx
index 46cec1e..afdbcac 100644
--- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx
+++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/FiltersExample.tsx
@@ -5,8 +5,9 @@ import { useDataViewFilters, useDataViewPagination } from '@patternfly/react-dat
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
-import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
+import { DataViewFilterOption, DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
+import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
const perPageOptions = [
{ title: '5', value: 5 },
@@ -17,38 +18,51 @@ interface Repository {
name: string;
branch: string | null;
prs: string | null;
- workspaces: string;
+ workspace: string;
lastCommit: string;
}
interface RepositoryFilters {
name: string,
- branch: string
+ branch: string,
+ workspace: string[]
}
const repositories: Repository[] = [
- { name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspaces: 'Workspace one', lastCommit: 'Timestamp one' },
- { name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspaces: 'Workspace two', lastCommit: 'Timestamp two' },
- { name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspaces: 'Workspace three', lastCommit: 'Timestamp three' },
- { name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspaces: 'Workspace four', lastCommit: 'Timestamp four' },
- { name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspaces: 'Workspace five', lastCommit: 'Timestamp five' },
- { name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspaces: 'Workspace six', lastCommit: 'Timestamp six' }
+ { name: 'Repository one', branch: 'Branch one', prs: 'Pull request one', workspace: 'Workspace one', lastCommit: 'Timestamp one' },
+ { name: 'Repository two', branch: 'Branch two', prs: 'Pull request two', workspace: 'Workspace two', lastCommit: 'Timestamp two' },
+ { name: 'Repository three', branch: 'Branch three', prs: 'Pull request three', workspace: 'Workspace one', lastCommit: 'Timestamp three' },
+ { name: 'Repository four', branch: 'Branch four', prs: 'Pull request four', workspace: 'Workspace one', lastCommit: 'Timestamp four' },
+ { name: 'Repository five', branch: 'Branch five', prs: 'Pull request five', workspace: 'Workspace two', lastCommit: 'Timestamp five' },
+ { name: 'Repository six', branch: 'Branch six', prs: 'Pull request six', workspace: 'Workspace three', lastCommit: 'Timestamp six' }
];
-const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspaces', 'Last commit' ];
+const filterOptions: DataViewFilterOption[] = [
+ { label: 'Workspace one', value: 'workspace-one' },
+ { label: 'Workspace two', value: 'workspace-two' },
+ { label: 'Workspace three', value: 'workspace-three' }
+];
+
+const columns = [ 'Name', 'Branch', 'Pull requests', 'Workspace', 'Last commit' ];
const ouiaId = 'LayoutExample';
const MyTable: React.FunctionComponent = () => {
const [ searchParams, setSearchParams ] = useSearchParams();
+ const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ initialFilters: { name: '', branch: '', workspace: [] }, searchParams, setSearchParams });
const pagination = useDataViewPagination({ perPage: 5 });
const { page, perPage } = pagination;
- const { filters, onSetFilters, clearAllFilters } = useDataViewFilters({ initialFilters: { name: '', branch: '' }, searchParams, setSearchParams });
- const pageRows = useMemo(() => repositories
- .filter(item => (!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) && (!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase())))
+ const filteredData = useMemo(() => repositories.filter(item =>
+ (!filters.name || item.name?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) &&
+ (!filters.branch || item.branch?.toLocaleLowerCase().includes(filters.branch?.toLocaleLowerCase())) &&
+ (!filters.workspace || filters.workspace.length === 0 || filters.workspace.includes(String(filterOptions.find(option => option.label === item.workspace)?.value)))
+ ), [ filters ]);
+
+ const pageRows = useMemo(() => filteredData
.slice((page - 1) * perPage, ((page - 1) * perPage) + perPage)
- .map(item => Object.values(item)), [ page, perPage, filters ]);
+ .map(item => Object.values(item)),
+ [ page, perPage, filteredData ]);
return (
@@ -58,7 +72,7 @@ const MyTable: React.FunctionComponent = () => {
pagination={
}
@@ -66,6 +80,7 @@ const MyTable: React.FunctionComponent = () => {
onSetFilters(values)} values={filters}>
+
}
/>
@@ -76,7 +91,7 @@ const MyTable: React.FunctionComponent = () => {
}
diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
index cf26c55..1e31d79 100644
--- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
+++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
@@ -11,7 +11,7 @@ source: react
# If you use typescript, the name of the interface to display props for
# These are found through the sourceProps function provided in patternfly-docs.source.js
sortValue: 3
-propComponents: ['DataViewFilters', 'DataViewTextFilter']
+propComponents: ['DataViewFilters', 'DataViewTextFilter', 'DataViewCheckboxFilter']
sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Functionality/Functionality.md
---
import { useMemo } from 'react';
@@ -23,6 +23,7 @@ import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataVi
import { DataViewTable } from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
+import { DataViewCheckboxFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewCheckboxFilter';
This is a list of functionality you can use to manage data displayed in the **data view**.
@@ -92,7 +93,7 @@ The `useDataViewSelection` hook manages the selection state of the data view.
Enables filtering of data records in the data view and displays the applied filter chips.
### Toolbar usage
-The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters` and `DataViewTextFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page.
+The data view toolbar can include a set of filters by passing a React node to the `filters` property. You can use predefined components `DataViewFilters`, `DataViewTextFilter` and `DataViewCheckboxFilter` to customize and handle filtering directly in the toolbar. The `DataViewFilters` is a wrapper allowing conditional filtering using multiple attributes. If you need just a single filter, you can use `DataViewTextFilter`, `DataViewCheckboxFilter` or a different filter component alone. Props of these filter components are listed at the bottom of this page.
You can decide between passing `value` and `onChange` event to every filter separately or pass `values` and `onChange` to the `DataViewFilters` wrapper which make them available to its children. Props directly passed to child filters have a higher priority than the "inherited" ones.
@@ -101,7 +102,7 @@ You can decide between passing `value` and `onChange` event to every filter sepa
The `useDataViewFilters` hook manages the filter state of the data view. It allows you to define default filter values, synchronize filter state with URL parameters, and handle filter changes efficiently.
**Initial values:**
-- `initialFilters` object with default filter values
+- `initialFilters` object with default filter values (if the filter param allows multiple values, pass an array)
- optional `searchParams` object for managing URL-based filter state
- optional `setSearchParams` function to update the URL when filters are modified
diff --git a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx
new file mode 100644
index 0000000..2290651
--- /dev/null
+++ b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import DataViewCheckboxFilter, { DataViewCheckboxFilterProps } from './DataViewCheckboxFilter';
+import DataViewToolbar from '../DataViewToolbar';
+
+describe('DataViewCheckboxFilter component', () => {
+ const defaultProps: DataViewCheckboxFilterProps = {
+ filterId: 'test-checkbox-filter',
+ title: 'Test Checkbox Filter',
+ value: [ 'workspace-one' ],
+ options: [
+ { label: 'Workspace one', value: 'workspace-one' },
+ { label: 'Workspace two', value: 'workspace-two' },
+ { label: 'Workspace three', value: 'workspace-three' },
+ ],
+ };
+
+ it('should render correctly', () => {
+ const { container } = render(
+ } />
+ );
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx
new file mode 100644
index 0000000..7235165
--- /dev/null
+++ b/packages/module/src/DataViewCheckboxFilter/DataViewCheckboxFilter.tsx
@@ -0,0 +1,175 @@
+import React from 'react';
+import {
+ Badge,
+ Menu,
+ MenuContent,
+ MenuItem,
+ MenuList,
+ MenuProps,
+ MenuToggle,
+ Popper,
+ ToolbarChip,
+ ToolbarFilter,
+} from '@patternfly/react-core';
+import { FilterIcon } from '@patternfly/react-icons';
+import { DataViewFilterOption } from '../DataViewFilters';
+
+const isToolbarChip = (chip: string | ToolbarChip): chip is ToolbarChip =>
+ typeof chip === 'object' && 'key' in chip;
+
+export const isDataViewFilterOption = (obj: unknown): obj is DataViewFilterOption =>
+ !!obj &&
+ typeof obj === 'object' &&
+ 'label' in obj &&
+ 'value' in obj &&
+ typeof (obj as DataViewFilterOption).value === 'string';
+
+/** extends MenuProps */
+export interface DataViewCheckboxFilterProps extends Omit {
+ /** Unique key for the filter attribute */
+ filterId: string;
+ /** Array of current filter values */
+ value?: string[];
+ /** Filter title displayed in the toolbar */
+ title: string;
+ /** Placeholder text of the menu */
+ placeholder?: string;
+ /** Filter options displayed */
+ options: (DataViewFilterOption | string)[];
+ /** Callback for updating when item selection changes. */
+ onChange?: (event?: React.MouseEvent, values?: string[]) => void;
+ /** Controls visibility of the filter in the toolbar */
+ showToolbarItem?: boolean;
+ /** Controls visibility of the filter icon */
+ showIcon?: boolean;
+ /** Controls visibility of the selected items badge */
+ showBadge?: boolean;
+ /** Custom OUIA ID */
+ ouiaId?: string;
+}
+
+export const DataViewCheckboxFilter: React.FC = ({
+ filterId,
+ title,
+ value = [],
+ onChange,
+ placeholder,
+ options = [],
+ showToolbarItem,
+ showIcon = !placeholder,
+ showBadge = !placeholder,
+ ouiaId = 'DataViewCheckboxFilter',
+ ...props
+}: DataViewCheckboxFilterProps) => {
+ const [ isOpen, setIsOpen ] = React.useState(false);
+ const toggleRef = React.useRef(null);
+ const menuRef = React.useRef(null);
+ const containerRef = React.useRef(null);
+
+ const normalizeOptions = React.useMemo(
+ () =>
+ options.map(option =>
+ typeof option === 'string'
+ ? { label: option, value: option }
+ : option
+ ),
+ [ options ]
+ );
+
+ const handleToggleClick = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ setTimeout(() => {
+ const firstElement = menuRef.current?.querySelector('li > button:not(:disabled)') as HTMLElement;
+ firstElement?.focus();
+ }, 0);
+ setIsOpen(prev => !prev);
+ };
+
+ const handleSelect = (event?: React.MouseEvent, itemId?: string | number) => {
+ const activeItem = String(itemId);
+ const isSelected = value.includes(activeItem);
+
+ onChange?.(
+ event,
+ isSelected ? value.filter(item => item !== activeItem) : [ activeItem, ...value ]
+ );
+ };
+
+ const handleClickOutside = (event: MouseEvent) =>
+ isOpen &&
+ menuRef.current && toggleRef.current &&
+ !menuRef.current.contains(event.target as Node) && !toggleRef.current.contains(event.target as Node)
+ && setIsOpen(false);
+
+
+ React.useEffect(() => {
+ window.addEventListener('click', handleClickOutside);
+ return () => {
+ window.removeEventListener('click', handleClickOutside);
+ };
+ }, [ isOpen ]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ return (
+ {
+ const activeOption = normalizeOptions.find(option => option.value === item);
+ return ({ key: activeOption?.value as string, node: activeOption?.label })
+ })}
+ deleteChip={(_, chip) =>
+ onChange?.(undefined, value.filter(item => item !== (isToolbarChip(chip) ? chip.key : chip)))
+ }
+ categoryName={title}
+ showToolbarItem={showToolbarItem}
+ >
+ : undefined}
+ badge={value.length > 0 && showBadge ? {value.length} : undefined}
+ style={{ width: '200px' }}
+ >
+ {placeholder ?? title}
+
+ }
+ triggerRef={toggleRef}
+ popper={
+
+ }
+ popperRef={menuRef}
+ appendTo={containerRef.current || undefined}
+ aria-label={`${title ?? filterId} filter`}
+ isVisible={isOpen}
+ />
+
+ );
+};
+
+export default DataViewCheckboxFilter;
diff --git a/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap b/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap
new file mode 100644
index 0000000..e6cede5
--- /dev/null
+++ b/packages/module/src/DataViewCheckboxFilter/__snapshots__/DataViewCheckboxFilter.test.tsx.snap
@@ -0,0 +1,194 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DataViewCheckboxFilter component should render correctly 1`] = `
+
+`;
diff --git a/packages/module/src/DataViewCheckboxFilter/index.ts b/packages/module/src/DataViewCheckboxFilter/index.ts
new file mode 100644
index 0000000..218b16f
--- /dev/null
+++ b/packages/module/src/DataViewCheckboxFilter/index.ts
@@ -0,0 +1,2 @@
+export { default } from './DataViewCheckboxFilter';
+export * from './DataViewCheckboxFilter';
diff --git a/packages/module/src/DataViewFilters/DataViewFilters.tsx b/packages/module/src/DataViewFilters/DataViewFilters.tsx
index 29e950e..addc63f 100644
--- a/packages/module/src/DataViewFilters/DataViewFilters.tsx
+++ b/packages/module/src/DataViewFilters/DataViewFilters.tsx
@@ -1,9 +1,16 @@
-import React, { useMemo, useState, useRef, useEffect, ReactElement } from 'react';
+import React, { useMemo, useState, useRef, useEffect, ReactElement, ReactNode } from 'react';
import {
Menu, MenuContent, MenuItem, MenuList, MenuToggle, Popper, ToolbarGroup, ToolbarToggleGroup, ToolbarToggleGroupProps,
} from '@patternfly/react-core';
import { FilterIcon } from '@patternfly/react-icons';
+export interface DataViewFilterOption {
+ /** Filter option label */
+ label: ReactNode;
+ /** Filter option value */
+ value: string;
+}
+
// helper interface to generate attribute menu
interface DataViewFilterIdentifiers {
filterId: string;
@@ -57,6 +64,19 @@ export const DataViewFilters = ({
filterItems.length > 0 && setActiveAttributeMenu(filterItems[0].title);
}, [ filterItems ]);
+ const handleClickOutside = (event: MouseEvent) =>
+ isAttributeMenuOpen &&
+ !attributeMenuRef.current?.contains(event.target as Node) &&
+ !attributeToggleRef.current?.contains(event.target as Node)
+ && setIsAttributeMenuOpen(false);
+
+ useEffect(() => {
+ window.addEventListener('click', handleClickOutside);
+ return () => {
+ window.removeEventListener('click', handleClickOutside);
+ };
+ }, [ isAttributeMenuOpen ]); // eslint-disable-line react-hooks/exhaustive-deps
+
const attributeToggle = (
({
isVisible={isAttributeMenuOpen}
/>
- {React.Children.map(children, (child) => (
- React.isValidElement(child) ? (
- React.cloneElement(child as ReactElement<{
+ {React.Children.map(children, (child) =>
+ React.isValidElement(child)
+ ? React.cloneElement(child as ReactElement<{
showToolbarItem: boolean;
onChange: (_e: unknown, values: unknown) => void;
value: unknown;
@@ -114,9 +134,8 @@ export const DataViewFilters = ({
value: values?.[child.props.filterId],
...child.props
})
- ) : child
- ))}
-
+ : child
+ )}
);
diff --git a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx
index e2d1e43..ce49a3d 100644
--- a/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx
+++ b/packages/module/src/DataViewTextFilter/DataViewTextFilter.tsx
@@ -31,6 +31,7 @@ export const DataViewTextFilter: React.FC = ({
...props
}: DataViewTextFilterProps) => (
0 ? [ { key: title, node: value } ] : []}
deleteChip={() => onChange?.(undefined, '')}
diff --git a/packages/module/src/Hooks/filters.ts b/packages/module/src/Hooks/filters.ts
index 4f81e17..177e5f3 100644
--- a/packages/module/src/Hooks/filters.ts
+++ b/packages/module/src/Hooks/filters.ts
@@ -16,15 +16,19 @@ export const useDataViewFilters = ({
}: UseDataViewFiltersProps) => {
const isUrlSyncEnabled = useMemo(() => searchParams && !!setSearchParams, [ searchParams, setSearchParams ]);
- const getInitialFilters = useCallback((): T => isUrlSyncEnabled ? Object.keys(initialFilters).reduce((loadedFilters, key) => {
- const urlValue = searchParams?.get(key);
- loadedFilters[key as keyof T] = urlValue
- ? (urlValue as T[keyof T] | T[keyof T])
- : initialFilters[key as keyof T];
- return loadedFilters;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, { ...initialFilters }) : initialFilters, [ isUrlSyncEnabled, JSON.stringify(initialFilters), searchParams?.toString() ]);
+ const getInitialFilters = useCallback((): T => isUrlSyncEnabled
+ ? Object.keys(initialFilters).reduce((loadedFilters, key) => {
+ const urlValue = searchParams?.get(key);
+ const isArrayFilter = Array.isArray(initialFilters[key]);
+ // eslint-disable-next-line no-nested-ternary
+ loadedFilters[key] = urlValue
+ ? (isArrayFilter && !Array.isArray(urlValue) ? [ urlValue ] : urlValue)
+ : initialFilters[key];
+
+ return loadedFilters;
+ }, { ...initialFilters })
+ : initialFilters, [ isUrlSyncEnabled, initialFilters, searchParams ]);
const [ filters, setFilters ] = useState(getInitialFilters());
const updateSearchParams = useCallback(
@@ -32,11 +36,8 @@ export const useDataViewFilters = ({
if (isUrlSyncEnabled) {
const params = new URLSearchParams(searchParams);
Object.entries(newFilters).forEach(([ key, value ]) => {
- if (value) {
- params.set(key, Array.isArray(value) ? value.join(',') : value);
- } else {
- params.delete(key);
- }
+ params.delete(key);
+ (Array.isArray(value) ? value : [ value ]).forEach((val) => value && params.append(key, val));
});
setSearchParams?.(params);
}
diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts
index a34586c..aca7f76 100644
--- a/packages/module/src/index.ts
+++ b/packages/module/src/index.ts
@@ -25,5 +25,8 @@ export * from './DataViewTable';
export { default as DataViewEventsContext } from './DataViewEventsContext';
export * from './DataViewEventsContext';
+export { default as DataViewCheckboxFilter } from './DataViewCheckboxFilter';
+export * from './DataViewCheckboxFilter';
+
export { default as DataView } from './DataView';
export * from './DataView';