{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-range-picker",
  "title": "DateRangePicker",
  "description": "Custom date range picker: mono pill trigger, preset rail, hand-built month grid with range wash.",
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/popover.json"
  ],
  "files": [
    {
      "path": "src/components/date-range-picker/date-range-picker.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { useState } from \"react\";\n\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface DateRange {\n  from: Date;\n  to: Date;\n}\n\nexport interface DateRangePreset {\n  label: string;\n  /** Days back from today, inclusive. */\n  days: number;\n}\n\nexport type DateRangePickerProps = Omit<\n  ComponentProps<\"button\">,\n  \"value\" | \"onChange\"\n> & {\n  value: DateRange;\n  onValueChange: (range: DateRange) => void;\n  /** Quick ranges in the popup rail. */\n  presets?: DateRangePreset[];\n  /** Days after today stay unselectable. */\n  disableFuture?: boolean;\n};\n\n/* ─────────────────────────────────────────────────────────\n * PICKER STORYBOARD\n *\n *  trigger   a mono pill reading the range (\"Jul 1 – Jul\n *            18\"); hover warms the hairline\n *  open      the panel rises 4px: preset rail on the\n *            left, one month grid on the right\n *  pick      first click plants the start (square primary\n *            marker); the tentative range washes\n *            primary/10 under the pointer as it moves;\n *            the second click lands the end and closes\n *  presets   one click, done — the rail is the fast path\n *  today     wears a hairline ring, never color\n *  motion    the month swaps with a 150ms crossfade;\n *            everything static under reduced motion\n * ───────────────────────────────────────────────────────── */\nconst WEEKDAYS = [\"mo\", \"tu\", \"we\", \"th\", \"fr\", \"sa\", \"su\"] as const;\n\nconst MONTH_LABEL = new Intl.DateTimeFormat(\"en-US\", {\n  month: \"long\",\n  year: \"numeric\",\n});\n\nconst RANGE_LABEL = new Intl.DateTimeFormat(\"en-US\", {\n  day: \"numeric\",\n  month: \"short\",\n});\n\nconst DEFAULT_PRESETS: DateRangePreset[] = [\n  { days: 7, label: \"last 7 days\" },\n  { days: 14, label: \"last 14 days\" },\n  { days: 30, label: \"last 30 days\" },\n];\n\nconst startOfDay = (date: Date) =>\n  new Date(date.getFullYear(), date.getMonth(), date.getDate());\n\nconst sameDay = (a: Date, b: Date) =>\n  a.getFullYear() === b.getFullYear() &&\n  a.getMonth() === b.getMonth() &&\n  a.getDate() === b.getDate();\n\nconst formatRange = (range: DateRange) =>\n  `${RANGE_LABEL.format(range.from)} – ${RANGE_LABEL.format(range.to)}`;\n\n/** The 42 cells of a month view, Monday-first. */\nexport const monthCells = (month: Date): Date[] => {\n  const year = month.getFullYear();\n  const monthIndex = month.getMonth();\n  const first = new Date(year, monthIndex, 1);\n  const lead = (first.getDay() + 6) % 7;\n  return Array.from(\n    { length: 42 },\n    (_, index) => new Date(year, monthIndex, 1 - lead + index)\n  );\n};\n\nconst dayState = (\n  day: Date,\n  range: { from: Date; to: Date | null },\n  hovered: Date | null\n) => {\n  const from = startOfDay(range.from);\n  const to = range.to ? startOfDay(range.to) : null;\n  const isStart = sameDay(day, from);\n  const isEnd = to ? sameDay(day, to) : false;\n\n  // A landed range, or the tentative wash toward the pointer.\n  const end = to ?? hovered;\n  let inRange = false;\n\n  if (end) {\n    const [lo, hi] = end.getTime() < from.getTime() ? [end, from] : [from, end];\n    inRange = day.getTime() > lo.getTime() && day.getTime() < hi.getTime();\n  }\n\n  return {\n    inRange,\n    isEnd: isEnd || (!to && hovered && sameDay(day, hovered)),\n    isStart,\n  };\n};\n\nexport const DateRangePicker = ({\n  className,\n  disableFuture = true,\n  onValueChange,\n  presets = DEFAULT_PRESETS,\n  value,\n  ...props\n}: DateRangePickerProps) => {\n  const [open, setOpen] = useState(false);\n  const [month, setMonth] = useState(() => startOfDay(value.from));\n  const [pending, setPending] = useState<Date | null>(null);\n  const [hovered, setHovered] = useState<Date | null>(null);\n\n  const today = startOfDay(new Date());\n  const range = pending\n    ? { from: pending, to: null }\n    : { from: value.from, to: value.to };\n\n  const landRange = (from: Date, to: Date) => {\n    const [lo, hi] = to.getTime() < from.getTime() ? [to, from] : [from, to];\n    onValueChange({ from: lo, to: hi });\n    setPending(null);\n    setHovered(null);\n    setOpen(false);\n  };\n\n  const pickDay = (day: Date) => {\n    if (pending) {\n      landRange(pending, day);\n      return;\n    }\n\n    setPending(day);\n  };\n\n  const pickPreset = (preset: DateRangePreset) => {\n    const from = new Date(\n      today.getFullYear(),\n      today.getMonth(),\n      today.getDate() - (preset.days - 1)\n    );\n    landRange(from, today);\n  };\n\n  const handleOpenChange = (next: boolean) => {\n    if (next) {\n      setMonth(startOfDay(value.from));\n    } else {\n      setPending(null);\n      setHovered(null);\n    }\n\n    setOpen(next);\n  };\n\n  return (\n    <Popover onOpenChange={handleOpenChange} open={open}>\n      <PopoverTrigger\n        className={cn(\n          \"inline-flex h-6 items-center gap-1.5 rounded-full border border-border/60 px-2.5 font-mono text-muted-foreground text-xs tabular-nums transition-colors hover:border-border hover:text-foreground\",\n          className\n        )}\n        data-slot=\"date-range-picker\"\n        {...props}\n      >\n        <svg\n          aria-hidden=\"true\"\n          className=\"size-3\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeLinecap=\"round\"\n          strokeWidth=\"2\"\n          viewBox=\"0 0 24 24\"\n        >\n          <rect height=\"16\" rx=\"1\" width=\"18\" x=\"3\" y=\"5\" />\n          <path d=\"M3 10h18M8 3v4M16 3v4\" />\n        </svg>\n        {formatRange(value)}\n      </PopoverTrigger>\n      <PopoverContent className=\"flex gap-3\">\n        <div\n          className=\"flex flex-col gap-0.5 border-border/40 border-r pr-3\"\n          data-slot=\"date-range-picker-presets\"\n        >\n          {presets.map((preset) => (\n            <button\n              className=\"rounded-sm px-2 py-1 text-left text-[11px] text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n              key={preset.label}\n              onClick={() => pickPreset(preset)}\n              type=\"button\"\n            >\n              {preset.label}\n            </button>\n          ))}\n        </div>\n\n        <div className=\"flex flex-col gap-2\">\n          <div className=\"flex items-center justify-between\">\n            <button\n              aria-label=\"Previous month\"\n              className=\"rounded-sm px-1.5 py-0.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n              onClick={() =>\n                setMonth(new Date(month.getFullYear(), month.getMonth() - 1, 1))\n              }\n              type=\"button\"\n            >\n              ‹\n            </button>\n            <span\n              className=\"fade-in-0 animate-in font-mono text-foreground text-xs tabular-nums duration-150 motion-reduce:animate-none\"\n              key={MONTH_LABEL.format(month)}\n            >\n              {MONTH_LABEL.format(month).toLowerCase()}\n            </span>\n            <button\n              aria-label=\"Next month\"\n              className=\"rounded-sm px-1.5 py-0.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n              onClick={() =>\n                setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1))\n              }\n              type=\"button\"\n            >\n              ›\n            </button>\n          </div>\n\n          <div className=\"grid grid-cols-7 gap-0.5\">\n            {WEEKDAYS.map((weekday) => (\n              <span\n                className=\"flex size-7 items-center justify-center font-mono text-[10px] text-muted-foreground/60\"\n                key={weekday}\n              >\n                {weekday}\n              </span>\n            ))}\n            {monthCells(month).map((day) => {\n              const outside = day.getMonth() !== month.getMonth();\n              const future = disableFuture && day.getTime() > today.getTime();\n              const { inRange, isEnd, isStart } = dayState(day, range, hovered);\n\n              return (\n                <button\n                  className={cn(\n                    \"flex size-7 items-center justify-center rounded-sm font-mono text-xs tabular-nums transition-colors\",\n                    outside ? \"text-muted-foreground/30\" : \"text-foreground/80\",\n                    sameDay(day, today) && \"ring-1 ring-border ring-inset\",\n                    inRange && \"bg-primary/10 text-foreground\",\n                    (isStart || isEnd) &&\n                      \"bg-primary font-medium text-primary-foreground\",\n                    future\n                      ? \"cursor-not-allowed opacity-30\"\n                      : \"hover:bg-accent hover:text-foreground\"\n                  )}\n                  disabled={future}\n                  key={day.toISOString()}\n                  onClick={() => pickDay(day)}\n                  onMouseEnter={() => pending && setHovered(day)}\n                  type=\"button\"\n                >\n                  {day.getDate()}\n                </button>\n              );\n            })}\n          </div>\n        </div>\n      </PopoverContent>\n    </Popover>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}