diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/Layout.md b/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/Layout.md
index 194c9be..6c82c21 100644
--- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/Layout.md
+++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/Layout.md
@@ -15,10 +15,11 @@ propComponents: ['DataView']
sourceLink: https://github.com/patternfly/react-data-view/blob/main/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/Layout.md
---
import { useMemo } from 'react';
-import { useDataViewPagination, useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
+import { useDataViewPagination, useDataViewSelection, useDataViewFilters } from '@patternfly/react-data-view/dist/dynamic/Hooks';
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
import DataView from '@patternfly/react-data-view/dist/dynamic/DataView';
import DataViewToolbar from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
+import { FilterLabels } from '@patternfly/react-data-view/dist/dynamic/FilterLabels';
The **data view** component renders record data in a configured layout.
diff --git a/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/PredefinedLayoutExample.tsx b/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/PredefinedLayoutExample.tsx
index 2b8adcb..b7a1ea3 100644
--- a/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/PredefinedLayoutExample.tsx
+++ b/packages/module/patternfly-docs/content/extensions/data-view/examples/Layout/PredefinedLayoutExample.tsx
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
-import { Pagination } from '@patternfly/react-core';
+import { Pagination, SearchInput, ToolbarFilter } from '@patternfly/react-core';
import { Table, Tbody, Th, Thead, Tr, Td } from '@patternfly/react-table';
-import { useDataViewPagination, useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
+import { useDataViewFilters, useDataViewPagination, useDataViewSelection } from '@patternfly/react-data-view/dist/dynamic/Hooks';
import { BulkSelect, BulkSelectValue } from '@patternfly/react-component-groups/dist/dynamic/BulkSelect';
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
@@ -43,6 +43,8 @@ export const BasicExample: React.FunctionComponent = () => {
const { page, perPage } = pagination;
const selection = useDataViewSelection({});
const { selected, onSelect, isSelected } = selection;
+ const filtersContext = useDataViewFilters({ name: '' });
+ const { filters, onSetFilters, onDeleteFilters } = filtersContext;
const pageData = useMemo(() => repositories.slice((page - 1) * perPage, ((page - 1) * perPage) + perPage), [ page, perPage ]);
@@ -74,7 +76,21 @@ export const BasicExample: React.FunctionComponent = () => {
itemCount={repositories.length}
{...pagination}
/>
- }
+ }
+ search={
+ onSetFilters(undefined, { name: '' })}
+ deleteChipGroup={(category) => onSetFilters(undefined, { name: '' })}
+ categoryName="name"
+ >
+ onSetFilters(e, { name: value })}
+ value={filters.name}
+ onClear={(e) => onSetFilters(e, { name: '' })}
+ />
+ }
/>
diff --git a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx
index 7184875..dc48164 100644
--- a/packages/module/src/DataViewToolbar/DataViewToolbar.tsx
+++ b/packages/module/src/DataViewToolbar/DataViewToolbar.tsx
@@ -10,9 +10,11 @@ export interface DataViewToolbarProps extends PropsWithChildren {
bulkSelect?: React.ReactNode;
/** React component to display pagination */
pagination?: React.ReactNode;
+ /** React component to display search input */
+ search?: React.ReactNode;
}
-export const DataViewToolbar: React.FC = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, pagination, children, ...props }: DataViewToolbarProps) => (
+export const DataViewToolbar: React.FC = ({ className, ouiaId = 'DataViewToolbar', bulkSelect, search, pagination, children, ...props }: DataViewToolbarProps) => (
{bulkSelect && (
@@ -20,6 +22,11 @@ export const DataViewToolbar: React.FC = ({ className, oui
{bulkSelect}
)}
+ {search && (
+
+ {search}
+
+ )}
{pagination && (
{pagination}
diff --git a/packages/module/src/FilterLabels/FilterLabels.tsx b/packages/module/src/FilterLabels/FilterLabels.tsx
new file mode 100644
index 0000000..eabc6f4
--- /dev/null
+++ b/packages/module/src/FilterLabels/FilterLabels.tsx
@@ -0,0 +1,139 @@
+import React from 'react';
+import { Badge, Button, ButtonVariant, Chip, ChipGroup, ChipGroupProps, ChipProps } from '@patternfly/react-core';
+import { createUseStyles } from 'react-jss';
+import clsx from 'clsx';
+
+export interface FilterLabel extends Omit {
+ /** The text of the filter label that will be displayed */
+ text: string;
+ /** Optional count associated with the filter label */
+ count?: number;
+}
+
+export interface FilterLabelGroup extends Omit {
+ /** An array of filter labels that belong to the category */
+ labels: FilterLabel[];
+}
+
+export type FilterLabelsFilter = FilterLabel | FilterLabelGroup;
+
+export interface FilterLabelsProps {
+ /** Additional class names to be applied to the FilterLabels component */
+ className?: string;
+ /** Array of filters that can be either individual labels or label groups */
+ filters: FilterLabelsFilter[];
+ /** Callback function invoked when one or multiple filters are removed */
+ onDelete: (event: React.MouseEvent, filters: FilterLabelsFilter | FilterLabelsFilter[]) => void;
+ /** Custom title for the delete all button */
+ deleteAllButtonTitle?: React.ReactNode;
+ /** Determines whether to show the delete all button */
+ showDeleteAllButton?: boolean;
+ /** Determines whether to show the delete group button */
+ showDeleteGroupButton?: boolean;
+ /** FilterLabels OUIA ID */
+ ouiaId?: string | number;
+}
+
+// Filter label group type guard
+export const isFilterLabelGroup = (group: FilterLabelsFilter): group is FilterLabelGroup => Object.prototype.hasOwnProperty.call(group, 'categoryName');
+
+// Plain filter label type guard
+export const isFilterLabel = (group: FilterLabelsFilter): group is FilterLabel => !isFilterLabelGroup(group);
+
+const useStyles = createUseStyles({
+ chipFilters: {
+ '& .pf-v5-c-chip-group:not(:last-child)': {
+ marginRight: 'var(--pf-v5-global--spacer--sm)',
+ },
+ '& .pf-v5-c-chip .pf-v5-c-badge': {
+ marginLeft: 'var(--pf-v5-global--spacer--xs)',
+ },
+ },
+});
+
+export const FilterLabels: React.FunctionComponent = ({
+ className,
+ filters,
+ onDelete,
+ deleteAllButtonTitle = 'Clear filters',
+ showDeleteAllButton = true,
+ showDeleteGroupButton = false,
+ ouiaId = 'FilterLabels'
+}: FilterLabelsProps) => {
+ const classes = useStyles();
+ const groups: FilterLabelGroup[] = filters.filter(isFilterLabelGroup);
+
+ const groupedFilters = groups.map(({ labels, ...group }) => (
+ 1 && {
+ isClosable: true,
+ onClick: (event) => {
+ event.stopPropagation();
+ onDelete(
+ event,
+ { ...group, labels }
+ );
+ },
+ })}
+ >
+ {labels.map((label: FilterLabel) => (
+ {
+ event.stopPropagation();
+ onDelete(event, { ...group, labels: [ label ] });
+ }}
+ >
+ {label.text}
+ {label.count && (
+
+ {label.count}
+
+ )}
+ {label.badge}
+
+ ))}
+
+ ));
+
+ const plainFilters = filters.filter(isFilterLabel);
+
+ return (
+
+ {groupedFilters}
+ {plainFilters?.map((label) => (
+
+ {
+ event.stopPropagation();
+ onDelete(event, [ label ]);
+ }}
+ >
+ {label.text}
+ {label.count && (
+
+ {label.count}
+
+ )}
+ {label.badge}
+
+
+ ))}
+ {(showDeleteAllButton && filters.length > 0) && (
+
+ )}
+
+ );
+};
+
+export default FilterLabels;
diff --git a/packages/module/src/FilterLabels/index.ts b/packages/module/src/FilterLabels/index.ts
new file mode 100644
index 0000000..fd9d080
--- /dev/null
+++ b/packages/module/src/FilterLabels/index.ts
@@ -0,0 +1,2 @@
+export { default } from './FilterLabels';
+export * from './FilterLabels';
diff --git a/packages/module/src/Hooks/filters.test.tsx b/packages/module/src/Hooks/filters.test.tsx
new file mode 100644
index 0000000..491e973
--- /dev/null
+++ b/packages/module/src/Hooks/filters.test.tsx
@@ -0,0 +1,3 @@
+import '@testing-library/jest-dom';
+
+
diff --git a/packages/module/src/Hooks/filters.ts b/packages/module/src/Hooks/filters.ts
new file mode 100644
index 0000000..d697a92
--- /dev/null
+++ b/packages/module/src/Hooks/filters.ts
@@ -0,0 +1,25 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState } from "react";
+
+export interface UseDataViewFiltersProps {
+ /** Array of initially selected entries */
+ initialFilters?: Record;
+}
+
+export const useDataViewFilters = (props: UseDataViewFiltersProps) => {
+ const [ filters, setFilters ] = useState>(props.initialFilters ?? {});
+
+ const onSetFilters = (_event: React.FormEvent | undefined, newFilters: Record) => {
+ setFilters(prev => ({ ...prev, ...newFilters }));
+ }
+
+ const onDeleteFilters = (_event: React.FormEvent | undefined, filtersToDelete: Record) => {
+ setFilters(prev => ({ ...filtersToDelete }));
+ }
+
+ return {
+ filters,
+ onSetFilters,
+ onDeleteFilters
+ };
+};
diff --git a/packages/module/src/Hooks/index.ts b/packages/module/src/Hooks/index.ts
index 4ed29ea..546a0da 100644
--- a/packages/module/src/Hooks/index.ts
+++ b/packages/module/src/Hooks/index.ts
@@ -1,2 +1,3 @@
export * from './pagination';
export * from './selection';
+export * from './filters';
diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts
index f87a6d0..c844636 100644
--- a/packages/module/src/index.ts
+++ b/packages/module/src/index.ts
@@ -1,6 +1,9 @@
// this file is autogenerated by generate-index.js, modifying it manually will have no effect
export * from './Hooks';
+export { default as FilterLabels } from './FilterLabels';
+export * from './FilterLabels';
+
export { default as DataViewToolbar } from './DataViewToolbar';
export * from './DataViewToolbar';