Vercel/src/photo/PhotoHeader.tsx
2024-09-05 20:31:23 -05:00

172 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { clsx } from 'clsx/lite';
import {
Photo,
PhotoDateRange,
PhotoSetAttributes,
dateRangeForPhotos,
} from '.';
import ShareButton from '@/components/ShareButton';
import AnimateItems from '@/components/AnimateItems';
import { ReactNode } from 'react';
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
import PhotoPrevNext from './PhotoPrevNext';
import PhotoLink from './PhotoLink';
import { formatDate } from '@/utility/date';
import ResponsiveText from '@/components/primitives/ResponsiveText';
import { useAppState } from '@/state/AppState';
export default function PhotoHeader({
tag,
camera,
simulation,
focal,
photos,
selectedPhoto,
entity,
entityVerb = 'PHOTO',
entityDescription,
sharePath,
indexNumber,
count,
dateRange,
}: {
photos: Photo[]
selectedPhoto?: Photo
entity?: ReactNode
entityVerb?: string
entityDescription?: string
sharePath?: string
indexNumber?: number
count?: number
dateRange?: PhotoDateRange
} & PhotoSetAttributes) {
const { isGridHighDensity } = useAppState();
const { start, end } = dateRangeForPhotos(photos, dateRange);
const selectedPhotoIndex = selectedPhoto
? photos.findIndex(photo => photo.id === selectedPhoto.id)
: undefined;
const paginationLabel =
(indexNumber || (selectedPhotoIndex ?? 0 + 1)) + ' of ' +
(count ?? photos.length);
const headerType = selectedPhotoIndex === undefined
? 'photo-set'
: entity
? 'photo-detail-with-entity'
: 'photo-detail';
const renderPrevNext = () =>
<PhotoPrevNext {...{
photo: selectedPhoto,
photos,
tag,
camera,
simulation,
focal,
}} />;
const renderDateRange = () =>
<span className="text-dim uppercase text-right">
{start === end
? start
: <>{end}<br /> {start}</>}
</span>;
const renderContentA = () => entity ?? (
selectedPhoto !== undefined &&
<PhotoLink
photo={selectedPhoto}
className="uppercase font-bold text-ellipsis truncate"
>
{
selectedPhoto.title ||
formatDate(selectedPhoto.takenAt, 'tiny')
}
</PhotoLink>);
return (
<AnimateItems
type="bottom"
distanceOffset={10}
animateOnFirstLoadOnly
items={[<DivDebugBaselineGrid
key="PhotosHeader"
className={clsx(
'grid gap-0.5 sm:gap-1 items-start',
'grid-cols-4',
isGridHighDensity
? 'lg:grid-cols-6'
: 'md:grid-cols-3 lg:grid-cols-4',
)}>
{/* Content A: Filter Set or Photo Title */}
<div className={clsx(
'inline-flex uppercase',
headerType === 'photo-set'
? isGridHighDensity
? 'col-span-2 sm:col-span-1 lg:col-span-2'
: 'col-span-2 sm:col-span-1'
: headerType === 'photo-detail-with-entity'
? isGridHighDensity
? 'col-span-2 sm:col-span-1 lg:col-span-2'
: 'col-span-2 sm:col-span-1'
: isGridHighDensity
? 'col-span-3 sm:col-span-3 lg:col-span-5'
: 'col-span-3 md:col-span-2 lg:col-span-3',
)}>
{headerType === 'photo-detail-with-entity'
? renderContentA()
: <h1>{renderContentA()}</h1>}
</div>
{/* Content B: Filter Set Meta or Photo Pagination */}
<div className={clsx(
'inline-flex',
'gap-2 self-start',
'uppercase text-dim',
headerType === 'photo-set'
? isGridHighDensity
? 'col-span-2 lg:col-span-3'
: 'col-span-2 md:col-span-1 lg:col-span-2'
: headerType === 'photo-detail-with-entity'
? isGridHighDensity
? 'sm:col-span-2 lg:col-span-3'
: 'sm:col-span-2 md:col-span-1 lg:col-span-2'
: 'hidden',
)}>
{entity && <>
{headerType === 'photo-set'
? <>
{entityDescription}
{sharePath &&
<ShareButton
className="translate-y-[1.5px]"
path={sharePath}
dim
/>}
</>
: <ResponsiveText shortText={paginationLabel}>
{entityVerb} {paginationLabel}
</ResponsiveText>}
</>}
</div>
{/* Content C: Nav */}
<div className={clsx(
headerType === 'photo-set'
? 'hidden sm:flex'
: 'flex',
'justify-end',
)}>
{selectedPhoto
? renderPrevNext()
: renderDateRange()}
</div>
</DivDebugBaselineGrid>,
]}
/>
);
}