Form/form-patterns-

PreviousNext

A form/form-patterns- example

Docs
shadcnblocksblock

Preview

Loading preview…
code/example/form/form-patterns-7.tsx
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import type { CheckedState } from "@radix-ui/react-checkbox";
import { useForm } from "react-hook-form";
import { z } from "zod";

import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

export const title = "Search Filter Form";

const categories = [
  { id: "electronics", label: "Electronics" },
  { id: "clothing", label: "Clothing" },
  { id: "books", label: "Books" },
  { id: "home", label: "Home & Garden" },
] as const;

const formSchema = z.object({
  query: z.string(),
  sortBy: z.string(),
  priceMin: z.coerce.number().min(0).optional(),
  priceMax: z.coerce.number().min(0).optional(),
  categories: z.array(z.string()),
  inStock: z.boolean(),
});

type FormInput = z.input<typeof formSchema>;
type FormOutput = z.output<typeof formSchema>;

const Example = () => {
  const form = useForm<FormInput, unknown, FormOutput>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      query: "",
      sortBy: "relevance",
      priceMin: undefined,
      priceMax: undefined,
      categories: [],
      inStock: false,
    },
  });

  function onSubmit(values: FormOutput) {
    console.log(values);
  }

  return (
    <div className="w-full max-w-md">
      <Form {...form}>
        <form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
          <div>
            <h2 className="text-2xl font-bold">Search Products</h2>
            <p className="text-sm text-muted-foreground">
              Filter and find what you're looking for
            </p>
          </div>
          <FormField
            control={form.control}
            name="query"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Search</FormLabel>
                <FormControl>
                  <Input
                    className="bg-background"
                    placeholder="Search products..."
                    type="search"
                    {...field}
                  />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="sortBy"
            render={({ field }) => (
              <FormItem>
                <FormLabel>Sort By</FormLabel>
                <Select
                  defaultValue={field.value}
                  onValueChange={field.onChange}
                >
                  <FormControl>
                    <SelectTrigger className="bg-background">
                      <SelectValue />
                    </SelectTrigger>
                  </FormControl>
                  <SelectContent>
                    <SelectItem value="relevance">Relevance</SelectItem>
                    <SelectItem value="price-low">
                      Price: Low to High
                    </SelectItem>
                    <SelectItem value="price-high">
                      Price: High to Low
                    </SelectItem>
                    <SelectItem value="newest">Newest First</SelectItem>
                  </SelectContent>
                </Select>
                <FormMessage />
              </FormItem>
            )}
          />
          <div className="space-y-4">
            <h3 className="text-sm font-medium">Price Range</h3>
            <div className="grid grid-cols-2 gap-4">
              <FormField
                control={form.control}
                name="priceMin"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Min</FormLabel>
                    <FormControl>
                      <Input
                        className="bg-background"
                        placeholder="$0"
                        type="number"
                        {...field}
                        value={String(field.value ?? "")}
                        onChange={(e) => field.onChange(e.target.value)}
                      />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
              <FormField
                control={form.control}
                name="priceMax"
                render={({ field }) => (
                  <FormItem>
                    <FormLabel>Max</FormLabel>
                    <FormControl>
                      <Input
                        className="bg-background"
                        placeholder="$1000"
                        type="number"
                        {...field}
                        value={String(field.value ?? "")}
                        onChange={(e) => field.onChange(e.target.value)}
                      />
                    </FormControl>
                    <FormMessage />
                  </FormItem>
                )}
              />
            </div>
          </div>
          <FormField
            control={form.control}
            name="categories"
            render={() => (
              <FormItem>
                <div className="mb-4">
                  <FormLabel className="text-base">Categories</FormLabel>
                  <FormDescription>
                    Select the categories you want to search in.
                  </FormDescription>
                </div>
                {categories.map((category) => (
                  <FormField
                    control={form.control}
                    key={category.id}
                    name="categories"
                    render={({ field }) => {
                      return (
                        <FormItem
                          className="flex flex-row items-start space-y-0 space-x-3"
                          key={category.id}
                        >
                          <FormControl>
                            <Checkbox
                              checked={field.value.includes(category.id)}
                              onCheckedChange={(checked: CheckedState) => {
                                return checked === true
                                  ? field.onChange([
                                      ...field.value,
                                      category.id,
                                    ])
                                  : field.onChange(
                                      field.value.filter(
                                        (value) => value !== category.id,
                                      ),
                                    );
                              }}
                            />
                          </FormControl>
                          <FormLabel className="font-normal">
                            {category.label}
                          </FormLabel>
                        </FormItem>
                      );
                    }}
                  />
                ))}
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="inStock"
            render={({ field }) => (
              <FormItem className="flex flex-row items-start space-y-0 space-x-3">
                <FormControl>
                  <Checkbox
                    checked={field.value}
                    onCheckedChange={(checked: CheckedState) =>
                      field.onChange(checked === true)
                    }
                  />
                </FormControl>
                <div className="space-y-1 leading-none">
                  <FormLabel>In Stock Only</FormLabel>
                  <FormDescription>
                    Show only products that are currently available.
                  </FormDescription>
                </div>
              </FormItem>
            )}
          />
          <div className="flex gap-2">
            <Button className="flex-1" type="submit">
              Apply Filters
            </Button>
            <Button
              onClick={() => form.reset()}
              type="button"
              variant="outline"
            >
              Reset
            </Button>
          </div>
        </form>
      </Form>
    </div>
  );
};

export default Example;

Installation

npx shadcn@latest add @shadcnblocks/form-form-patterns-7

Usage

import { FormFormPatterns7 } from "@/components/form-form-patterns-7"
<FormFormPatterns7 />