Skip to main content
Live Demo

Instalation

npx shadcn add "https://tarcisioandrade.github.io/rich-domain/packages/react-rich-domain/public/r/filter.json"

Basic Usage

import { useCriteria } from "@/hooks/use-criteria";
import { Filter } from "@/components/filter/filter";
import type { QueryFilter } from "@/lib/filter-utils";

interface User {
  id: string;
  name: string;
  email: string;
  status: "active" | "inactive" | "pending";
  age: number;
  createdAt: Date;
}

// Define filterable fields
const userFields: QueryFilter[] = [
  {
    field: "name",
    fieldLabel: "Name",
    type: "string",
  },
  {
    field: "email",
    fieldLabel: "Email",
    type: "string",
  },
  {
    field: "status",
    fieldLabel: "Status",
    type: "string",
    options: [
      { value: "active", label: "Active" },
      { value: "inactive", label: "Inactive" },
      { value: "pending", label: "Pending" },
    ],
  },
  {
    field: "age",
    fieldLabel: "Age",
    type: "number",
  },
  {
    field: "createdAt",
    fieldLabel: "Created At",
    type: "date",
  },
];

function UserFilters() {
  const { filters, addOrReplaceByIndex, removeFilter, clearFilters } =
    useCriteria<User>();

  return (
    <Filter
      fields={userFields}
      filters={filters}
      addOrReplaceByIndex={addOrReplaceByIndex}
      removeFilter={removeFilter}
      clearFilters={clearFilters}
    />
  );
}

QueryFilter Type

interface QueryFilter {
  // Field path in the entity (e.g., "name", "profile.bio")
  field: string;

  // Display label for the field
  fieldLabel: string;

  // Field type determines available operators
  type: "string" | "number" | "date" | "boolean";

  // Optional: mark if field path traverses a collection (1:N or N:N relation)
  isCollection?: boolean;

  // Optional: mark if field can be null
  isNullable?: boolean;

  // Optional: predefined options for select fields
  options?: Array<{
    value: string;
    label: string;
    icon?: React.ReactNode;
  }>;
}

Field Types and Operators

The Filter component automatically shows the appropriate operators based on field type:
TypeOperators
stringequals, notEquals, contains, startsWith, endsWith, in, notIn, isNull, isNotNull
numberequals, notEquals, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, between, in, notIn, isNull, isNotNull
dateequals, notEquals, greaterThan, lessThan, between, isNull, isNotNull
booleanequals, notEquals, isNull, isNotNull

With Options (Select Fields)

For fields with predefined values, use the options property:
const orderFields: QueryFilter[] = [
  {
    field: "status",
    fieldLabel: "Order Status",
    type: "string",
    options: [
      { value: "draft", label: "Draft", icon: <FileIcon /> },
      { value: "confirmed", label: "Confirmed", icon: <CheckIcon /> },
      { value: "shipped", label: "Shipped", icon: <TruckIcon /> },
      { value: "delivered", label: "Delivered", icon: <PackageIcon /> },
    ],
  },
  {
    field: "priority",
    fieldLabel: "Priority",
    type: "string",
    options: [
      { value: "low", label: "Low" },
      { value: "medium", label: "Medium" },
      { value: "high", label: "High" },
    ],
  },
];

Nested Fields

Support for nested entity paths:
const userFields: QueryFilter[] = [
  {
    field: "profile.bio",
    fieldLabel: "Bio",
    type: "string",
  },
  {
    field: "address.city",
    fieldLabel: "City",
    type: "string",
  },
  {
    field: "address.country",
    fieldLabel: "Country",
    type: "string",
    options: [
      { value: "US", label: "United States" },
      { value: "BR", label: "Brazil" },
      { value: "UK", label: "United Kingdom" },
    ],
  },
];

Collection Fields

When filtering through a 1:N or N:N relation (e.g., User has many Posts), use isCollection: true:
const userFields: QueryFilter[] = [
  {
    field: "name",
    fieldLabel: "Name",
    type: "string",
  },
  {
    // Filtering through a collection requires isCollection: true
    field: "posts.title",
    fieldLabel: "Post Title",
    type: "string",
    isCollection: true, // ✅ Required for collections
  },
  {
    field: "posts.status",
    fieldLabel: "Post Status",
    type: "string",
    isCollection: true,
    options: [
      { value: "draft", label: "Draft" },
      { value: "published", label: "Published" },
    ],
  },
];
When isCollection: true, the filter will automatically use { quantifier: "some" } to generate the correct Prisma query:
// Without isCollection (incorrect for collections)
{ posts: { title: { contains: "hello" } } }  // ❌ Error

// With isCollection: true (correct)
{ posts: { some: { title: { contains: "hello" } } } }  // ✅ Works