+ );
+};
+```
+
+## 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)}
+ />
+ );
+};
+```
+
+## Important Implementation Notes
+
+### Server Request Handling
+Before making requests to the server in your `onFilter` handler, you must process the request payload through an `applyFilters` function. This is a critical step for maintaining consistent filter behavior across the application.
+
+#### Filter Hook Naming Convention
+The filter hook name should follow this pattern:
+- Start with "dokan"
+- Follow with the feature or task name
+- End with "_filter_request_param"
+- Must be in snake_case format
+
+You can use the `snakeCase` utility to ensure proper formatting:
+
+For Dokan Lite:
+```jsx
+import { snakeCase } from "@/utilities";
+```
+
+For Dokan Pro or other plugins:
+```jsx
+import { snakeCase } from "@dokan/utilities";
+```
+
+Example usage:
+```jsx
+const handleFilter = () => {
+ // Convert hook name to snake_case
+ const hookName = snakeCase('dokan_subscription_filter_request_param');
+
+ // Apply filters to your request payload before sending to server
+ const requestPayload = applyFilters(hookName, {
+ ...filterArgs,
+ per_page: perPage,
+ page: currentPage,
+ } );
+
+ // Now make your server request with the processed payload
+ fetchData(requestPayload);
+};
+```
+
+Parameters for `applyFilters`:
+- First parameter: Hook name (string, must follow naming convention)
+- Second parameter: Request payload (object)
+- Third parameter: Namespace from Filter component props (string)
+
+This step ensures:
+- Consistent filter processing across the application
+- Proper parameter formatting
+- Extensibility for future filter modifications
+- Standardized hook naming across the application
+
+## 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 15978e01d5..f0ee7976d6 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": {
"@dnd-kit/core": "^6.3.1",
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 (
+