Add placeholder client logic to date primitive

This commit is contained in:
Sam Becker 2025-01-12 13:26:38 -06:00
parent 0b63cf76e7
commit 3d69e2d20c
4 changed files with 72 additions and 24 deletions

View File

@ -1,40 +1,66 @@
'use client';
import { formatDate } from '@/utility/date'; import { formatDate } from '@/utility/date';
import { clsx } from 'clsx/lite'; import { clsx } from 'clsx/lite';
import { useEffect, useState } from 'react';
export default function ResponsiveDate({ export default function ResponsiveDate({
date, date,
className, className,
titleLabel, titleLabel,
timezone: timezoneFromProps,
}: { }: {
date: Date date: Date
className?: string className?: string
titleLabel?: string titleLabel?: string
timezone?: string | null
}) { }) {
const [timezone, setTimezone] = useState(timezoneFromProps);
useEffect(() => {
if (timezoneFromProps === null) {
setTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone);
}
}, [timezoneFromProps]);
const showPlaceholderContent = timezone === null;
const titleDateFormatted = formatDate(date).toLocaleUpperCase();
const title = titleLabel const title = titleLabel
? `${titleLabel}: ${formatDate(date).toLocaleUpperCase()}` ? `${titleLabel}: ${titleDateFormatted}`
: formatDate(date).toLocaleUpperCase(); : titleDateFormatted;
const contentClass = showPlaceholderContent && 'opacity-0 select-none';
return ( return (
<span <span
title={title} title={showPlaceholderContent ? 'LOADING LOCAL TIME' : title}
className={clsx(className, 'uppercase')} className={clsx(
'uppercase rounded-md transition-colors',
showPlaceholderContent && 'bg-dim',
className,
)}
> >
{/* Small */} {/* Small */}
<span <span
className="xs:hidden" className={clsx('xs:hidden', contentClass)}
aria-hidden aria-hidden
> >
{formatDate(date, 'short')} {formatDate(date, 'short', showPlaceholderContent)}
</span> </span>
{/* Medium */} {/* Medium */}
<span <span
className="hidden xs:inline-block sm:hidden" className={clsx('hidden xs:inline-block sm:hidden', contentClass)}
aria-hidden aria-hidden
> >
{formatDate(date, 'medium')} {formatDate(date, 'medium', showPlaceholderContent)}
</span> </span>
{/* Large */} {/* Large */}
<span className="hidden sm:inline-block"> <span
{formatDate(date)} className={clsx('hidden sm:inline-block', contentClass)}
>
{formatDate(date, undefined, showPlaceholderContent)}
</span> </span>
</span> </span>
); );

View File

@ -10,6 +10,7 @@ export default function PhotoDate({
photo: Photo photo: Photo
className?: string className?: string
dateType?: 'takenAt' | 'createdAt' | 'updatedAt' dateType?: 'takenAt' | 'createdAt' | 'updatedAt'
timezone?: string | null
}) { }) {
const date = useMemo(() => { const date = useMemo(() => {
const date = new Date(dateType === 'takenAt' const date = new Date(dateType === 'takenAt'
@ -41,6 +42,7 @@ export default function PhotoDate({
date, date,
className, className,
titleLabel: getTitleLabel(), titleLabel: getTitleLabel(),
timezone: null,
}} /> }} />
); );
} }

View File

@ -164,6 +164,10 @@
@apply @apply
bg-white dark:bg-black bg-white dark:bg-black
} }
.bg-dim {
@apply
bg-gray-100 dark:bg-gray-900/75
}
.bg-content { .bg-content {
@apply @apply
bg-white border-gray-200 bg-white border-gray-200

View File

@ -1,25 +1,41 @@
import { format, parseISO, parse } from 'date-fns'; import { format, parseISO, parse } from 'date-fns';
const DATE_STRING_FORMAT_TINY = 'dd MMM yy'; const DATE_STRING_FORMAT_TINY = 'dd MMM yy';
const DATE_STRING_FORMAT_TINY_PLACEHOLDER = '00 000 00';
const DATE_STRING_FORMAT_SHORT = 'dd MMM yyyy'; const DATE_STRING_FORMAT_SHORT = 'dd MMM yyyy';
const DATE_STRING_FORMAT_SHORT_PLACEHOLDER = '00 000 0000';
const DATE_STRING_FORMAT_MEDIUM = 'dd MMM yy h:mma'; const DATE_STRING_FORMAT_MEDIUM = 'dd MMM yy h:mma';
const DATE_STRING_FORMAT = 'dd MMM yyyy h:mma'; const DATE_STRING_FORMAT_MEDIUM_PLACEHOLDER = '00 000 00 00:0000';
const DATE_STRING_FORMAT_LONG = 'dd MMM yyyy h:mma';
const DATE_STRING_FORMAT_LONG_PLACEHOLDER = '00 000 0000 00:0000';
const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss'; const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss';
type AmbiguousTimestamp = number | string; type AmbiguousTimestamp = number | string;
type Length = 'tiny' | 'short' | 'medium' | 'long'; type Length = 'tiny' | 'short' | 'medium' | 'long';
export const formatDate = (date: Date, length: Length = 'long') => { export const formatDate = (
date: Date,
length: Length = 'long',
showPlaceholder?: boolean,
) => {
switch (length) { switch (length) {
case 'tiny': case 'tiny': return showPlaceholder
return format(date, DATE_STRING_FORMAT_TINY); ? DATE_STRING_FORMAT_TINY_PLACEHOLDER
case 'short': : format(date, DATE_STRING_FORMAT_TINY);
return format(date, DATE_STRING_FORMAT_SHORT); case 'short': return showPlaceholder
case 'medium': ? DATE_STRING_FORMAT_SHORT_PLACEHOLDER
return format(date, DATE_STRING_FORMAT_MEDIUM); : format(date, DATE_STRING_FORMAT_SHORT);
default: case 'medium': return showPlaceholder
return format(date, DATE_STRING_FORMAT); ? DATE_STRING_FORMAT_MEDIUM_PLACEHOLDER
: format(date, DATE_STRING_FORMAT_MEDIUM);
default: return showPlaceholder
? DATE_STRING_FORMAT_LONG_PLACEHOLDER
: format(date, DATE_STRING_FORMAT_LONG);
} }
}; };