# V calendar 17

> Sprint planner with two-month range selection and duration stats

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-calendar-17).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-calendar-17
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-calendar-17-light-f13e1d681ec5d502.png
- Registry item: https://ui.cnippet.dev/r/v-calendar-17.json
- Categories: calendar
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-calendar-17.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/calendar`

## Files

### `registry/default/variants/v-calendar-17.tsx`

```tsx
"use client";

import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Badge } from "@/registry/default/ui/badge";
import { Calendar } from "@/registry/default/ui/calendar";

const today = new Date();

function diffDays(a: Date, b: Date) {
  return Math.round((b.getTime() - a.getTime()) / 86400000);
}

export default function Particle() {
  const [range, setRange] = React.useState<DateRange | undefined>();

  const duration =
    range?.from && range?.to ? diffDays(range.from, range.to) + 1 : null;

  const sprintNum = range?.from
    ? Math.ceil(
        (diffDays(new Date(today.getFullYear(), 0, 1), range.from) + 1) / 14,
      )
    : null;

  return (
    <div className="flex flex-col gap-3">
      <Calendar
        disabled={{ before: today }}
        mode="range"
        numberOfMonths={2}
        onSelect={setRange}
        selected={range}
      />
      <div className="grid grid-cols-3 gap-2 rounded-lg border px-4 py-3 text-sm">
        <div className="flex flex-col gap-0.5">
          <span className="text-muted-foreground text-xs">Start</span>
          <span className="font-medium">
            {range?.from
              ? range.from.toLocaleDateString("en-US", {
                  day: "numeric",
                  month: "short",
                })
              : "—"}
          </span>
        </div>
        <div className="flex flex-col gap-0.5">
          <span className="text-muted-foreground text-xs">End</span>
          <span className="font-medium">
            {range?.to
              ? range.to.toLocaleDateString("en-US", {
                  day: "numeric",
                  month: "short",
                })
              : "—"}
          </span>
        </div>
        <div className="flex flex-col gap-0.5">
          <span className="text-muted-foreground text-xs">Duration</span>
          <span className="font-medium">
            {duration ? (
              <span className="flex items-center gap-1.5">
                {duration}d
                {sprintNum && (
                  <Badge size="sm" variant="secondary">
                    S{sprintNum}
                  </Badge>
                )}
              </span>
            ) : (
              "—"
            )}
          </span>
        </div>
      </div>
    </div>
  );
}
```
