Openstatus
www.openstatus.dev
1"use client";
2
3import {
4 HoverCard,
5 HoverCardContent,
6 HoverCardTrigger,
7} from "@/components/ui/hover-card";
8import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
9import { UTCDate } from "@date-fns/utc";
10import { HoverCardPortal } from "@radix-ui/react-hover-card";
11import { format, formatDistanceToNowStrict } from "date-fns";
12import { Copy } from "lucide-react";
13import { Check } from "lucide-react";
14import type { ComponentPropsWithoutRef } from "react";
15
16// TODO: move to TableCellDate?
17
18type HoverCardContentProps = ComponentPropsWithoutRef<typeof HoverCardContent>;
19
20interface HoverCardTimestampProps {
21 date: Date;
22 side?: HoverCardContentProps["side"];
23 sideOffset?: HoverCardContentProps["sideOffset"];
24 align?: HoverCardContentProps["align"];
25 alignOffset?: HoverCardContentProps["alignOffset"];
26 children?: React.ReactNode;
27}
28
29export function HoverCardTimestamp({
30 date,
31 side = "right",
32 align = "start",
33 alignOffset = -4,
34 sideOffset,
35 children,
36}: HoverCardTimestampProps) {
37 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
38
39 return (
40 <HoverCard openDelay={0} closeDelay={0}>
41 <HoverCardTrigger asChild>{children}</HoverCardTrigger>
42 <HoverCardPortal>
43 <HoverCardContent
44 className="z-10 w-auto p-2"
45 {...{ side, align, alignOffset, sideOffset }}
46 >
47 <dl className="flex flex-col gap-1">
48 <Row value={String(date.getTime())} label="Timestamp" />
49 <Row
50 value={format(new UTCDate(date), "LLL dd, y HH:mm:ss")}
51 label="UTC"
52 />
53 <Row value={format(date, "LLL dd, y HH:mm:ss")} label={timezone} />
54 <Row
55 value={formatDistanceToNowStrict(date, { addSuffix: true })}
56 label="Relative"
57 />
58 </dl>
59 </HoverCardContent>
60 </HoverCardPortal>
61 </HoverCard>
62 );
63}
64
65function Row({ value, label }: { value: string; label: string }) {
66 const { copy, isCopied } = useCopyToClipboard();
67
68 return (
69 <div
70 className="group flex items-center justify-between gap-4 text-sm"
71 onClick={(e) => {
72 e.stopPropagation();
73 copy(value, {});
74 }}
75 >
76 <dt className="text-muted-foreground">{label}</dt>
77 <dd className="flex items-center gap-1 truncate font-mono">
78 <span className="invisible group-hover:visible">
79 {!isCopied ? (
80 <Copy className="h-3 w-3" />
81 ) : (
82 <Check className="h-3 w-3" />
83 )}
84 </span>
85 {value}
86 </dd>
87 </div>
88 );
89}