diff --git a/docs/frontend/filter.md b/docs/frontend/filter.md new file mode 100644 index 0000000000..5787ad9808 --- /dev/null +++ b/docs/frontend/filter.md @@ -0,0 +1,148 @@ +# Filter Component + +The Filter component provides a standardized way to implement filtering functionality across your application. It creates a consistent user experience by managing filter fields, filter triggers, and reset functionality. + +## Features + +- Unified interface for multiple filter fields +- Configurable filter and reset buttons +- Namespace support for unique identification +- Flexible field composition + +## Installation + +#### Dokan lite + +```jsx +import Filter from '../components'; +``` + +#### Dokan pro or external plugins + +```jsx +import { Filter } from '@dokan/components'; +``` + +## Usage + +```jsx +, + + ]} + onFilter={handleFilter} + onReset={clearFilter} + showFilter={true} + showReset={true} + namespace="product_table_filters" +/> +``` + +## Props + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `fields` | `ReactNode[]` | No | Array of filter field components to be rendered | +| `onFilter` | `() => void` | No | Handler function called when the filter button is clicked | +| `onReset` | `() => void` | No | Handler function called when the reset button is clicked | +| `showFilter` | `boolean` | No | Controls visibility of the filter button (default: true) | +| `showReset` | `boolean` | No | Controls visibility of the reset button (default: true) | +| `namespace` | `string` | Yes | Unique identifier for the filter group | + +## Example Implementation + +```jsx +import { Filter, CustomerFilter, DateFilter } from '@your-package/components'; + +const MyTableComponent = () => { + const [filterArgs, setFilterArgs] = useState({}); + const [searchedCustomer, setSearchedCustomer] = useState(null); + + const handleFilter = () => { + // Implement your filter logic here + fetchFilteredData(filterArgs); + }; + + const clearFilter = () => { + setFilterArgs({}); + setSearchedCustomer(null); + }; + + const handleCustomerSearch = (customer) => { + setSearchedCustomer(customer); + }; + + return ( +
+ , + + ]} + onFilter={handleFilter} + onReset={clearFilter} + showFilter={true} + showReset={true} + namespace="my_table_filters" + /> + {/* Table component */} +
+ ); +}; +``` + +## Creating Custom Filter Fields + +When creating custom filter field components to use with the Filter component: + +1. Each field component should manage its own state +2. Field components should update the parent's filterArgs through props +3. Include a unique key prop for each field +4. Handle reset functionality through props + +Example custom filter field: + +```jsx +const CustomFilter = ({ filterArgs, setFilterArgs }) => { + const handleChange = (value) => { + setFilterArgs({ + ...filterArgs, + customField: value + }); + }; + + return ( + handleChange(e.target.value)} + /> + ); +}; +``` + +## Best Practices + +1. Always provide unique keys for field components +2. Implement proper type checking for filter arguments +3. Handle edge cases in reset functionality +4. Use consistent naming conventions for filter arguments +5. Include error handling in filter and reset handlers + +## Notes + +- The Filter component is designed to work with table components but can be used in other contexts +- All filter fields should be controlled components +- The namespace prop is used internally for generating unique identifiers +- Filter and reset functionality should be implemented in the parent component diff --git a/package.json b/package.json index cecb8571d4..d34fcf0595 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "vue-wp-list-table": "^1.3.0", "vue2-daterange-picker": "^0.6.8", "vuedraggable": "^2.24.3", - "wp-readme-to-markdown": "^1.0.1" + "wp-readme-to-markdown": "^1.0.1", + "tailwind-merge": "^2.6.0" }, "dependencies": { "@getdokan/dokan-ui": "^1.0.18", diff --git a/src/components/Filter.tsx b/src/components/Filter.tsx new file mode 100644 index 0000000000..955c661e1f --- /dev/null +++ b/src/components/Filter.tsx @@ -0,0 +1,78 @@ +import { Button } from '@getdokan/dokan-ui'; +import { __ } from '@wordpress/i18n'; +import { twMerge } from 'tailwind-merge'; +// @ts-ignore +// eslint-disable-next-line import/no-unresolved +import { snakeCase, kebabCase } from '../utilities'; + +interface FilterProps { + /** Namespace for the filter, used to generate unique IDs */ + namespace: string; + /** Array of React nodes representing the filter fields */ + fields?: React.ReactNode[]; + /** Whether to show the reset button */ + showReset?: boolean; + /** Whether to show the filter button */ + showFilter?: boolean; + /** Callback function to handle filter action */ + onFilter?: () => void; + /** Callback function to handle reset action */ + onReset?: () => void; + /** Additional class names for the filter container */ + className?: string; +} + +const Filter = ( { + namespace = '', + fields = [], + showReset = true, + showFilter = true, + onFilter = () => {}, + onReset = () => {}, + className = '', +}: FilterProps ) => { + const snakeCaseNamespace = snakeCase( namespace ); + const filterId = `dokan_${ snakeCaseNamespace }_filters`; + + // @ts-ignore + const filteredFields = wp.hooks.applyFilters( filterId, fields ); + + return ( +
+ { filteredFields.map( ( fieldNode: React.ReactNode, index ) => { + return ( +
+ { fieldNode } +
+ ); + } ) } + + { showFilter && ( +
+ ); +}; + +export default Filter; diff --git a/src/components/index.tsx b/src/components/index.tsx index 56aed60b58..0c86eab8a0 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -7,3 +7,5 @@ export { filterSortAndPaginate, isItemValid } from '@wordpress/dataviews/wp'; + +export { default as Filter } from './Filter';