Blocks

Filters 5

Filters
1 ColumnText Align CenterFilter TopButtonsFiltersModal
Filters 5

Code

relume-filters5.tsx
import React, { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Cancel, FilterList, KeyboardArrowDown, Search } from "relume-icons";

type FilterValues = {
  filterOne: string;
  filterTwo: string[];
  filterThree: string[];
  filterFour: string[];
};

const tabs = [
  { value: "View all", trigger: "View all" },
  { value: "Category one", trigger: "Category one" },
  { value: "Category two", trigger: "Category two" },
  { value: "Category three", trigger: "Category three" },
  { value: "Category four", trigger: "Category four" },
];

const options = [
  { value: "option-1", label: "Option one" },
  { value: "option-2", label: "Option two" },
  { value: "option-3", label: "Option three" },
  { value: "option-4", label: "Option four" },
  { value: "option-5", label: "Option five" },
];

const containerVariants = {
  initial: {
    height: 0,
    opacity: 0,
    transition: { duration: 0.2, ease: "easeInOut" },
  },
  animate: {
    height: "auto",
    opacity: 1,
    transition: { duration: 0.2, ease: "easeInOut" },
  },
  exit: {
    height: 0,
    opacity: 0,
    transition: { duration: 0.2, ease: "easeInOut", opacity: { duration: 0.1 } },
  },
};

export const Filters5 = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [selectedValues, setSelectedValues] = useState<FilterValues>({
    filterOne: "",
    filterTwo: ["option-1"],
    filterThree: ["option-1"],
    filterFour: ["option-1"],
  });

  const handleClear = (filter: keyof FilterValues) => {
    setSelectedValues((prev) => ({
      ...prev,
      [filter]: Array.isArray(prev[filter]) ? [] : "",
    }));
  };

  const handleCheckboxChange = (filter: keyof Omit<FilterValues, "filterOne">, value: string) => {
    setSelectedValues((prev) => {
      const currentItems = prev[filter];
      return {
        ...prev,
        [filter]: currentItems.includes(value)
          ? currentItems.filter((item) => item !== value)
          : [...currentItems, value],
      };
    });
  };

  const getSelectedLabels = (filter: keyof FilterValues) => {
    if (!selectedValues[filter].length) return "Select options";
    if (selectedValues[filter].length === 1) {
      return options.find((opt) => opt.value === selectedValues[filter][0])?.label;
    }
    return `${selectedValues[filter].length} selected`;
  };

  return (
    <section className="px-[5%] py-16 md:py-24 lg:py-28">
      <div className="container">
        <div className="mb-12 w-full text-center md:mb-18 lg:mb-20">
          <h1 className="mb-5 text-h2 font-bold md:mb-6">Collection</h1>
          <p className="text-medium">Lorem ipsum dolor sit amet.</p>
        </div>
        <div className="mb-8 grid auto-cols-fr grid-cols-2 grid-rows-[auto_auto] items-center justify-between gap-x-6 gap-y-8 lg:grid-cols-[1fr_max-content_1fr] lg:gap-y-0">
          <Button
            className="w-fit gap-3 self-start"
            variant="secondary"
            onClick={() => setIsOpen(!isOpen)}
          >
            <FilterList className="size-6 text-scheme-text" />
            <span className="underline">Filters</span>
          </Button>
          <Tabs
            defaultValue={tabs[0].value}
            className="scrollbar-none flex w-full flex-col items-center justify-center overflow-scroll [grid-area:2/1/3/3] md:overflow-auto lg:[grid-area:auto/auto/auto/auto]"
          >
            <TabsList className="flex w-screen items-center justify-start pl-[5vw] md:w-full md:justify-center md:overflow-hidden md:pl-0">
              {tabs.map((tab, index) => (
                <TabsTrigger
                  key={index}
                  value={tab.value}
                  className="rounded-button px-4 py-2 data-[state=inactive]:border-transparent"
                >
                  {tab.trigger}
                </TabsTrigger>
              ))}
            </TabsList>
          </Tabs>
          <DropdownMenu>
            <DropdownMenuTrigger className="-mr-2 flex items-center gap-2 justify-self-end">
              <p className="whitespace-nowrap">Sort by</p>
              <KeyboardArrowDown className="shrink-0 text-scheme-text transition-transform duration-300" />
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              {[
                "Most Popular",
                "Most Recent",
                "Name: A to Z",
                "Name: Z to A",
                "Price: Low to High",
                "Price: High to Low",
              ].map((option) => (
                <DropdownMenuItem key={option}>{option}</DropdownMenuItem>
              ))}
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
        <AnimatePresence initial={false} mode="wait">
          {isOpen && (
            <motion.div
              variants={containerVariants}
              initial="initial"
              animate="animate"
              exit="exit"
              className="overflow-hidden"
            >
              <div className="mb-8 grid grid-cols-1 gap-x-8 gap-y-4 lg:grid-cols-4">
                <div>
                  <div className="mb-2 flex items-center justify-between">
                    <span>Filter One</span>
                    <Button
                      variant="link"
                      size="link"
                      onClick={() => handleClear("filterOne")}
                      className="text-small"
                    >
                      Clear
                    </Button>
                  </div>
                  <Input
                    placeholder="Keyword"
                    icon={<Search className="size-6 text-scheme-text" />}
                    value={selectedValues.filterOne}
                    onChange={(e) =>
                      setSelectedValues((prev) => ({
                        ...prev,
                        filterOne: e.target.value,
                      }))
                    }
                  />
                </div>
                <div>
                  <div className="mb-2 flex items-center justify-between">
                    <span>Filter Two</span>
                    <Button
                      variant="link"
                      size="link"
                      onClick={() => handleClear("filterTwo")}
                      className="text-small"
                    >
                      Clear
                    </Button>
                  </div>
                  <Select value="" onValueChange={() => {}}>
                    <SelectTrigger>
                      <SelectValue placeholder={getSelectedLabels("filterTwo")} />
                    </SelectTrigger>
                    <SelectContent>
                      {options.map((option) => (
                        <div key={option.value} className="flex items-center space-x-2 p-2">
                          <Checkbox
                            id={`filter-two-${option.value}`}
                            checked={selectedValues.filterTwo.includes(option.value)}
                            onCheckedChange={() => handleCheckboxChange("filterTwo", option.value)}
                          />
                          <Label
                            className="grow cursor-pointer"
                            htmlFor={`filter-two-${option.value}`}
                            onClick={(e: React.MouseEvent<HTMLLabelElement>) => {
                              e.preventDefault();
                              handleCheckboxChange("filterTwo", option.value);
                            }}
                          >
                            {option.label}
                          </Label>
                        </div>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
                <div>
                  <div className="mb-2 flex items-center justify-between">
                    <span>Filter Three</span>
                    <Button
                      variant="link"
                      size="link"
                      onClick={() => handleClear("filterThree")}
                      className="text-small"
                    >
                      Clear
                    </Button>
                  </div>
                  <Select value="" onValueChange={() => {}}>
                    <SelectTrigger>
                      <SelectValue placeholder={getSelectedLabels("filterThree")} />
                    </SelectTrigger>
                    <SelectContent>
                      {options.map((option) => (
                        <div key={option.value} className="flex items-center space-x-2 px-2 py-1.5">
                          <Checkbox
                            id={`filter-three-${option.value}`}
                            checked={selectedValues.filterThree.includes(option.value)}
                            onCheckedChange={() =>
                              handleCheckboxChange("filterThree", option.value)
                            }
                          />
                          <Label
                            className="grow cursor-pointer"
                            htmlFor={`filter-three-${option.value}`}
                            onClick={(e: React.MouseEvent<HTMLLabelElement>) => {
                              e.preventDefault();
                              handleCheckboxChange("filterThree", option.value);
                            }}
                          >
                            {option.label}
                          </Label>
                        </div>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
                <div>
                  <div className="mb-2 flex items-center justify-between">
                    <span>Filter Four</span>
                    <Button
                      variant="link"
                      size="link"
                      onClick={() => handleClear("filterFour")}
                      className="text-small"
                    >
                      Clear
                    </Button>
                  </div>
                  <Select value="" onValueChange={() => {}}>
                    <SelectTrigger>
                      <SelectValue placeholder={getSelectedLabels("filterFour")} />
                    </SelectTrigger>
                    <SelectContent>
                      {options.map((option) => (
                        <div key={option.value} className="flex items-center space-x-2 px-2 py-1.5">
                          <Checkbox
                            id={`filter-four-${option.value}`}
                            checked={selectedValues.filterFour.includes(option.value)}
                            onCheckedChange={() => handleCheckboxChange("filterFour", option.value)}
                          />
                          <Label
                            className="grow cursor-pointer"
                            htmlFor={`filter-four-${option.value}`}
                            onClick={(e: React.MouseEvent<HTMLLabelElement>) => {
                              e.preventDefault();
                              handleCheckboxChange("filterFour", option.value);
                            }}
                          >
                            {option.label}
                          </Label>
                        </div>
                      ))}
                    </SelectContent>
                  </Select>
                </div>
              </div>
            </motion.div>
          )}
        </AnimatePresence>
        <div>
          <div className="mb-8 flex w-full items-center justify-between gap-8">
            <div className="flex items-center bg-scheme-foreground py-2 pr-3 pl-4">
              <span>Tag</span>
              <Cancel className="ml-2 size-5.5 flex-none cursor-pointer" />
            </div>
            <span className="text-small">Showing 0 of 100</span>
          </div>
          <div className="min-h-[200vh] w-full border-2 border-dashed border-neutral-darkest/15" />
          <div className="border border-scheme-border p-[2.9rem] text-center">
            <h6 className="mb-2 text-h6 font-bold">No results found.</h6>
            <p>There are no results with this criteria. Try changing your search.</p>
          </div>
        </div>
      </div>
    </section>
  );
};

npm i @radix-ui/react-accordion @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label @radix-ui/react-radio-group @radix-ui/react-select @radix-ui/react-slider @radix-ui/react-slot @radix-ui/react-switch @radix-ui/react-tabs class-variance-authority@0.7.1 clsx@^2.1.1 luxon@^3.5.0 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2