Openstatus
www.openstatus.dev
1"use client";
2
3import { useState } from "react";
4import type { DateRange } from "react-day-picker";
5
6import { Kbd } from "@/components/common/kbd";
7import { Button } from "@/components/ui/button";
8import { Calendar } from "@/components/ui/calendar";
9import { Input } from "@/components/ui/input";
10import { Label } from "@/components/ui/label";
11import { Separator } from "@/components/ui/separator";
12import { formatDateForInput } from "@/lib/formatter";
13import { endOfDay } from "date-fns";
14
15type DatePickerProps = {
16 range: DateRange;
17 onSelect: (range: DateRange) => void;
18 presets: { id: string; label: string; values: DateRange; shortcut: string }[];
19};
20
21export function DatePicker({ range, onSelect, presets }: DatePickerProps) {
22 const [today] = useState(new Date());
23 const disableBefore = presets[presets.length - 1]?.values?.from;
24
25 return (
26 <div>
27 <div className="flex flex-row">
28 <div className="relative py-4">
29 <div className="h-full">
30 <div className="flex flex-col px-1">
31 <div className="px-3 py-1 font-medium text-muted-foreground text-xs">
32 Presets
33 </div>
34 {presets.map((preset) => {
35 const isSelected =
36 range.from?.getTime() === preset.values.from?.getTime() &&
37 range.to?.getTime() === preset.values.to?.getTime();
38
39 return (
40 <Button
41 key={preset.id}
42 variant={isSelected ? "outline" : "ghost"}
43 size="sm"
44 className="w-full justify-between border border-transparent"
45 onClick={() => {
46 onSelect(preset.values);
47 }}
48 >
49 <span>{preset.label}</span>
50 <Kbd className="font-mono uppercase">{preset.shortcut}</Kbd>
51 </Button>
52 );
53 })}
54 </div>
55 </div>
56 </div>
57 <Separator orientation="vertical" className="h-auto! w-px" />
58 <div className="flex flex-1 items-center justify-center">
59 <Calendar
60 mode="range"
61 selected={range}
62 onSelect={(newDate) => {
63 if (newDate) {
64 onSelect({
65 ...newDate,
66 to: newDate.to ? endOfDay(newDate.to) : undefined,
67 });
68 }
69 }}
70 className="p-2"
71 disabled={[
72 { after: today }, // Dates before today
73 { before: disableBefore ?? today }, // Dates before last action
74 ]}
75 />
76 </div>
77 </div>
78 <Separator />
79 <div className="flex flex-col gap-2 px-3 py-4">
80 <p className="px-1 font-medium text-muted-foreground text-xs">
81 Custom Range
82 </p>
83 <div className="grid gap-2 sm:grid-cols-2">
84 <div className="grid w-full gap-1.5">
85 <Label htmlFor="from" className="px-1">
86 Start
87 </Label>
88 <Input
89 type="datetime-local"
90 id="from"
91 name="from"
92 min={formatDateForInput(disableBefore ?? today)}
93 max={formatDateForInput(today)}
94 value={range.from ? formatDateForInput(range.from) : ""}
95 onChange={(e) => {
96 const newDate = new Date(e.target.value);
97 if (!Number.isNaN(newDate.getTime())) {
98 onSelect({ ...range, from: newDate });
99 }
100 }}
101 disabled={!range.from}
102 />
103 </div>
104 <div className="grid w-full gap-1.5">
105 <Label htmlFor="to" className="px-1">
106 End
107 </Label>
108 <Input
109 type="datetime-local"
110 id="to"
111 name="to"
112 min={formatDateForInput(range.from ?? today)}
113 max={formatDateForInput(today)}
114 value={range.to ? formatDateForInput(range.to) : ""}
115 onChange={(e) => {
116 const newDate = new Date(e.target.value);
117 if (!Number.isNaN(newDate.getTime())) {
118 onSelect({ ...range, to: newDate });
119 }
120 }}
121 disabled={!range.to}
122 />
123 </div>
124 </div>
125 </div>
126 </div>
127 );
128}