Filters Collapsible

PreviousNext

A collapsible component for filter options

Docs
shadcnui-blockscomponent

Preview

Loading preview…
components/customized/collapsible/collapsible-04.tsx
"use client";

import { Checkbox } from "@/registry/ui/checkbox";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/registry/ui/collapsible";
import { Input } from "@/registry/ui/input";
import { Label } from "@/registry/ui/label";
import { Slider } from "@/registry/ui/slider";
import {
  ChevronDown,
  CircleDollarSign,
  LucideIcon,
  Star,
  Tag,
} from "lucide-react";
import { ReactNode, useState } from "react";

type PriceRange = { from: number; to: number };

const CollapsibleFilters = () => {
  return (
    <div className="w-full max-w-xs divide-y-2">
      <PriceRangeFilter />
      <CategoryFilter />
      <RatingFilter />
    </div>
  );
};

const MIN_PRICE = 0;
const MAX_PRICE = 1000;
function PriceRangeFilter() {
  const [value, setValue] = useState<PriceRange>({
    from: MIN_PRICE,
    to: MAX_PRICE,
  });

  const handleChange = (newValue: PriceRange) => {
    setValue(newValue);
  };

  return (
    <CollapsibleFilter title="Price Range" icon={CircleDollarSign}>
      <div className="flex justify-between space-x-4">
        <Input
          type="number"
          value={value.from}
          onChange={(e) =>
            handleChange({ from: +e.target.value, to: value.to })
          }
          // onBlur={handleBlur}
          className="w-20"
        />
        <Input
          type="number"
          value={value.to}
          onChange={(e) =>
            handleChange({ from: value.from, to: +e.target.value })
          }
          // onBlur={handleBlur}
          className="w-20"
        />
      </div>
      <Slider
        min={MIN_PRICE}
        max={MAX_PRICE}
        step={10}
        value={[value.from, value.to]}
        onValueChange={([from, to]) => handleChange({ from, to })}
        className="w-full mt-4 mb-3"
      />
    </CollapsibleFilter>
  );
}

function RatingFilter() {
  const [rating, setRating] = useState<number | null>(null);
  const [hoveredRating, setHoveredRating] = useState<number | null>(null);

  return (
    <CollapsibleFilter title="Rating" icon={Star}>
      <div className="flex space-x-1 mb-1">
        {[1, 2, 3, 4, 5].map((ratingValue) => (
          <Star
            key={ratingValue}
            className={`h-6 w-6 cursor-pointer ${
              (
                hoveredRating !== null
                  ? hoveredRating >= ratingValue
                  : rating !== null && rating >= ratingValue
              )
                ? "text-yellow-400 fill-yellow-400"
                : "text-gray-300"
            }`}
            onMouseEnter={() => setHoveredRating(ratingValue)}
            onMouseLeave={() => setHoveredRating(null)}
            onClick={() =>
              setRating(ratingValue === rating ? null : ratingValue)
            }
          />
        ))}
      </div>
    </CollapsibleFilter>
  );
}

const categories = [
  "Electronics",
  "Clothing",
  "Books",
  "Home & Garden",
  "Toys",
];
function CategoryFilter() {
  return (
    <CollapsibleFilter title="Category" icon={Tag}>
      {categories.map((category) => (
        <div key={category} className="mb-2 flex items-center space-x-3">
          <Checkbox id={category} />
          <Label htmlFor={category}>{category}</Label>
        </div>
      ))}
    </CollapsibleFilter>
  );
}

const CollapsibleFilter = ({
  title,
  icon: Icon,
  children,
}: {
  title: string;
  icon?: LucideIcon;
  children: ReactNode;
}) => (
  <Collapsible defaultOpen>
    <CollapsibleTrigger className="group flex w-full items-center justify-between py-3">
      <h3 className="flex items-center gap-2 text-sm font-semibold">
        {!!Icon && <Icon className="h-5 w-5" />} {title}
      </h3>
      <ChevronDown className="h-4 w-4 group-data-[state=open]:rotate-180 transition-transform text-muted-foreground" />
    </CollapsibleTrigger>
    <CollapsibleContent className="pt-1 pb-3">{children}</CollapsibleContent>
  </Collapsible>
);

export default CollapsibleFilters;

Installation

npx shadcn@latest add @shadcnui-blocks/collapsible-04

Usage

import { Collapsible04 } from "@/components/collapsible-04"
<Collapsible04 />