Openstatus www.openstatus.dev
at main 128 lines 4.4 kB view raw
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}