date-picker-form

PreviousNext
Docs
reuicomponent

Preview

Loading preview…
registry/default/components/date-picker/form.tsx
'use client';

import * as React from 'react';
import { Alert, AlertIcon, AlertTitle } from '@/registry/default/ui/alert';
import { Button } from '@/registry/default/ui/button';
import { Calendar } from '@/registry/default/ui/calendar';
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/registry/default/ui/form';
import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/ui/popover';
import { zodResolver } from '@hookform/resolvers/zod';
import { RiCheckboxCircleFill } from '@remixicon/react';
import { format } from 'date-fns';
import { Calendar as CalendarIcon, X } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';

// Define the schema with Zod
const FormSchema = z.object({
  singleDate: z
    .string()
    .nonempty({ message: 'Date is required.' })
    .refine((val) => !isNaN(Date.parse(val)), {
      message: 'Please select a valid date.',
    }),
  rangeDate: z
    .object({
      from: z.string().optional(),
      to: z.string().optional(),
    })
    .optional()
    .refine(
      (val) =>
        !val ||
        (!val.from && !val.to) ||
        (val.from && val.to && !isNaN(Date.parse(val.from)) && !isNaN(Date.parse(val.to))),
      {
        message: 'Please select a valid date range.',
        path: ['rangeDate'],
      },
    ),
});

export default function CombinedDatePickerForm() {
  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      singleDate: '',
      rangeDate: { from: '', to: '' },
    },
  });

  const onSubmit = (data: z.infer<typeof FormSchema>) => {
    const message = [];
    if (data.singleDate) {
      message.push(`Single date: ${format(new Date(data.singleDate), 'PPP')}`);
    }
    if (data.rangeDate?.from && data.rangeDate?.to) {
      message.push(
        `Range: ${format(new Date(data.rangeDate.from), 'PPP')} - ${format(new Date(data.rangeDate.to), 'PPP')}`,
      );
    }
    const finalMessage = message.length > 0 ? message.join(' | ') : 'No dates selected';

    toast.custom((t) => (
      <Alert variant="mono" icon="primary" onClose={() => toast.dismiss(t)}>
        <AlertIcon>
          <RiCheckboxCircleFill />
        </AlertIcon>
        <AlertTitle>{finalMessage}</AlertTitle>
      </Alert>
    ));
  };

  const handleReset = () => {
    form.reset();
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="w-[300px] space-y-6">
        <FormField
          control={form.control}
          name="singleDate"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Single Date:</FormLabel>
              <FormControl>
                <Popover>
                  <PopoverTrigger asChild>
                    <div className="relative">
                      <Button
                        type="button"
                        variant="outline"
                        mode="input"
                        placeholder={!field.value}
                        className="w-full"
                      >
                        <CalendarIcon />
                        {field.value ? format(new Date(field.value), 'dd MMM, yyyy') : <span>Pick a date</span>}
                      </Button>
                      {field.value && (
                        <Button
                          type="button"
                          variant="dim"
                          size="sm"
                          className="absolute top-1/2 -end-0 -translate-y-1/2"
                          onClick={(e) => {
                            e.preventDefault();
                            form.setValue('singleDate', '', {
                              shouldValidate: true,
                            });
                          }}
                        >
                          <X />
                        </Button>
                      )}
                    </div>
                  </PopoverTrigger>
                  <PopoverContent className="w-auto p-0" align="start">
                    <Calendar
                      mode="single"
                      selected={field.value ? new Date(field.value) : undefined}
                      onSelect={(date) =>
                        form.setValue('singleDate', date?.toISOString() || '', {
                          shouldValidate: true,
                        })
                      }
                      autoFocus
                    />
                  </PopoverContent>
                </Popover>
              </FormControl>
              <FormDescription>Enter your single date to proceed</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="rangeDate"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Date Range:</FormLabel>
              <FormControl>
                <Popover>
                  <PopoverTrigger asChild>
                    <div className="relative">
                      <Button
                        variant="outline"
                        mode="input"
                        placeholder={!field.value?.from && !field.value?.to}
                        className="w-full"
                      >
                        <CalendarIcon />
                        {field.value?.from ? (
                          field.value.to ? (
                            `${format(new Date(field.value.from), 'dd MMM, yyyy')} - ${format(new Date(field.value.to), 'dd MMM, yyyy')}`
                          ) : (
                            format(new Date(field.value.from), 'dd MMM, yyyy')
                          )
                        ) : (
                          <span>Pick a date range</span>
                        )}
                      </Button>
                      {field.value?.from && (
                        <Button
                          variant="dim"
                          size="sm"
                          className="absolute top-1/2 -end-0 -translate-y-1/2"
                          onClick={(e) => {
                            e.preventDefault();
                            form.setValue('rangeDate', { from: '', to: '' }, { shouldValidate: true });
                          }}
                        >
                          <X />
                        </Button>
                      )}
                    </div>
                  </PopoverTrigger>
                  <PopoverContent className="w-auto p-0" align="start">
                    <Calendar
                      mode="range"
                      selected={
                        field.value?.from && field.value?.to
                          ? {
                              from: new Date(field.value.from),
                              to: new Date(field.value.to),
                            }
                          : undefined
                      }
                      onSelect={(range) =>
                        form.setValue(
                          'rangeDate',
                          {
                            from: range?.from?.toISOString() || '',
                            to: range?.to?.toISOString() || '',
                          },
                          { shouldValidate: true },
                        )
                      }
                      numberOfMonths={2}
                      autoFocus
                    />
                  </PopoverContent>
                </Popover>
              </FormControl>
              <FormDescription>Enter your date range to proceed</FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />

        <div className="flex items-center justify-end gap-2.5">
          <Button type="button" variant="outline" onClick={handleReset}>
            Reset
          </Button>
          <Button type="submit">Submit</Button>
        </div>
      </form>
    </Form>
  );
}

Installation

npx shadcn@latest add @reui/date-picker-form

Usage

import { DatePickerForm } from "@/components/date-picker-form"
<DatePickerForm />