mirror of
https://github.com/vernu/textbee.git
synced 2026-08-01 09:59:02 -04:00
Device and API key cards rendered full timestamps ("Registered at: May 18,
2023, 1:42 PM"), which wrapped and dominated the cards at 375px.
New shared RelativeTime renders "7 days ago" with the exact date and time in a
tooltip. Formatting is delegated to date-fns, already a dependency and already
used by the webhook table, so no new package and no hand-rolled date parsing.
The only local rule is a "Just now" threshold under one minute.
Details:
- formatDistanceToNowStrict, not formatDistanceToNow, so it reads "3 minutes
ago" rather than "about 3 minutes ago".
- Renders a real <time dateTime> element, keeping the machine-readable value
in the DOM.
- The trigger is focusable, so the exact time is reachable by keyboard and not
hover-only.
- Invalid or missing values render a caller-supplied fallback, so a key that
has never been used still reads "Last used never" instead of "Invalid Date".
Labels shortened to match: "Registered at:" to "Registered", "Created at:" to
"Created".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
'use client'
|
|
|
|
import { format, formatDistanceToNowStrict, isValid } from 'date-fns'
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from '@/components/ui/tooltip'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
// Anything under this reads as "Just now" rather than "34 seconds ago".
|
|
const JUST_NOW_MS = 60 * 1000
|
|
|
|
export function toRelativeLabel(date: Date, now: Date = new Date()): string {
|
|
if (now.getTime() - date.getTime() < JUST_NOW_MS) return 'Just now'
|
|
// Strict, so it says "3 minutes ago" instead of "about 3 minutes ago".
|
|
return formatDistanceToNowStrict(date, { addSuffix: true })
|
|
}
|
|
|
|
export function toExactLabel(date: Date): string {
|
|
return format(date, "MMM d, yyyy 'at' h:mm a")
|
|
}
|
|
|
|
/**
|
|
* A timestamp shown as "7 days ago", with the exact date and time on hover.
|
|
*
|
|
* Long absolute timestamps ("May 18, 2023, 1:42 PM") wrapped and dominated the
|
|
* device and API key cards at mobile widths. Formatting is delegated to
|
|
* date-fns; the only local rule is the "Just now" threshold.
|
|
*/
|
|
export default function RelativeTime({
|
|
value,
|
|
fallback = '-',
|
|
className,
|
|
}: {
|
|
value: string | number | Date | null | undefined
|
|
fallback?: string
|
|
className?: string
|
|
}) {
|
|
if (value === null || value === undefined || value === '') {
|
|
return <span className={className}>{fallback}</span>
|
|
}
|
|
|
|
const date = value instanceof Date ? value : new Date(value)
|
|
if (!isValid(date)) {
|
|
return <span className={className}>{fallback}</span>
|
|
}
|
|
|
|
return (
|
|
<TooltipProvider delayDuration={200}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
{/* A real <time> keeps the machine-readable value in the DOM, and
|
|
tabIndex means keyboard users can reach the exact timestamp too. */}
|
|
<time
|
|
dateTime={date.toISOString()}
|
|
tabIndex={0}
|
|
className={cn(
|
|
'cursor-default underline decoration-dotted underline-offset-2',
|
|
className
|
|
)}
|
|
>
|
|
{toRelativeLabel(date)}
|
|
</time>
|
|
</TooltipTrigger>
|
|
<TooltipContent>{toExactLabel(date)}</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
)
|
|
}
|