diff --git a/README.md b/README.md index eeec7040..d9f56e91 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,6 @@ Application behavior can be changed by configuring the following environment var - `NEXT_PUBLIC_MATTE_PHOTOS = 1` constrains the size of each photo, and enables a surrounding border (potentially useful for photos with tall aspect ratios) - `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage) - `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data (⚠️ re-compresses uploaded images in order to remove GPS information) -- `NEXT_PUBLIC_HIDE_TITLE_FALLBACK_TEXT = 1` prevents showing "Untitled" for photos without titles - `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order - `NEXT_PUBLIC_PUBLIC_API = 1` enables public API available at `/api` - `NEXT_PUBLIC_HIDE_REPO_LINK = 1` removes footer link to repo diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx index 82675100..150d3a64 100644 --- a/src/camera/CameraHeader.tsx +++ b/src/camera/CameraHeader.tsx @@ -1,6 +1,6 @@ import { Photo, PhotoDateRange } from '@/photo'; import { pathForCameraShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import { Camera, cameraFromPhoto } from '.'; import PhotoCamera from './PhotoCamera'; import { descriptionForCameraPhotos } from './meta'; @@ -22,9 +22,9 @@ export default function CameraHeader({ }) { const camera = cameraFromPhoto(photos[0], cameraProp); return ( - } - entityVerb="Photo" entityDescription={ descriptionForCameraPhotos(photos, undefined, count, dateRange)} photos={photos} diff --git a/src/focal/FocalLengthHeader.tsx b/src/focal/FocalLengthHeader.tsx index b40f459a..e30d68f3 100644 --- a/src/focal/FocalLengthHeader.tsx +++ b/src/focal/FocalLengthHeader.tsx @@ -1,7 +1,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { descriptionForFocalLengthPhotos } from '.'; import { pathForFocalLengthShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import PhotoFocalLength from './PhotoFocalLength'; export default function FocalLengthHeader({ @@ -20,7 +20,8 @@ export default function FocalLengthHeader({ dateRange?: PhotoDateRange }) { return ( - } entityDescription={descriptionForFocalLengthPhotos( photos, diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index dfb85f37..b0739287 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -10,11 +10,9 @@ import { import SiteGrid from '@/components/SiteGrid'; import Spinner from '@/components/Spinner'; import { getPhotosCachedAction, getPhotosAction } from '@/photo/actions'; -import { Photo } from '.'; +import { Photo, PhotoSetAttributes } from '.'; import { clsx } from 'clsx/lite'; import { useAppState } from '@/state/AppState'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { GetPhotosOptions } from './db'; export type RevalidatePhoto = ( @@ -38,9 +36,6 @@ export default function InfinitePhotoScroll({ initialOffset: number itemsPerPage: number sortBy?: GetPhotosOptions['sortBy'] - tag?: string - camera?: Camera - simulation?: FilmSimulation cacheKey: string wrapMoreButtonInGrid?: boolean useCachedPhotos?: boolean @@ -50,7 +45,7 @@ export default function InfinitePhotoScroll({ onLastPhotoVisible: () => void revalidatePhoto?: RevalidatePhoto }) => ReactNode -}) { +} & PhotoSetAttributes) { const { swrTimestamp, isUserSignedIn } = useAppState(); const key = `${swrTimestamp}-${cacheKey}`; diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx index ed70dec8..ecfd14d7 100644 --- a/src/photo/PhotoDetailPage.tsx +++ b/src/photo/PhotoDetailPage.tsx @@ -1,18 +1,15 @@ import AnimateItems from '@/components/AnimateItems'; -import { Photo, PhotoDateRange } from '.'; +import { Photo, PhotoDateRange, PhotoSetAttributes } from '.'; import PhotoLarge from './PhotoLarge'; import SiteGrid from '@/components/SiteGrid'; import PhotoGrid from './PhotoGrid'; -import { clsx } from 'clsx/lite'; -import PhotoLinks from './PhotoLinks'; import TagHeader from '@/tag/TagHeader'; -import { Camera } from '@/camera'; import CameraHeader from '@/camera/CameraHeader'; -import { FilmSimulation } from '@/simulation'; import FilmSimulationHeader from '@/simulation/FilmSimulationHeader'; import { TAG_HIDDEN } from '@/tag'; import HiddenHeader from '@/tag/HiddenHeader'; import FocalLengthHeader from '@/focal/FocalLengthHeader'; +import PhotoHeader from './PhotoHeader'; export default function PhotoDetailPage({ photo, @@ -31,77 +28,69 @@ export default function PhotoDetailPage({ photo: Photo photos: Photo[] photosGrid?: Photo[] - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number indexNumber?: number count?: number dateRange?: PhotoDateRange shouldShare?: boolean includeFavoriteInAdminMenu?: boolean -}) { +} & PhotoSetAttributes) { + let customHeader: JSX.Element | undefined; + + if (tag) { + customHeader = tag === TAG_HIDDEN + ? + : ; + } else if (camera) { + customHeader = ; + } else if (simulation) { + customHeader = ; + } else if (focal) { + customHeader = ; + } + return (
- {tag && - - : } - />} - {camera && - } - />} - {simulation && - } - />} - {focal && - } + } + /> } - contentSide={ - -
, - ]} - />} /> ); diff --git a/src/photo/PhotoGrid.tsx b/src/photo/PhotoGrid.tsx index d0d998c0..d5678a49 100644 --- a/src/photo/PhotoGrid.tsx +++ b/src/photo/PhotoGrid.tsx @@ -1,11 +1,9 @@ 'use client'; -import { Photo } from '.'; +import { Photo, PhotoSetAttributes } from '.'; import PhotoMedium from './PhotoMedium'; import { clsx } from 'clsx/lite'; import AnimateItems from '@/components/AnimateItems'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { GRID_ASPECT_RATIO, HIGH_DENSITY_GRID } from '@/site/config'; import { useAppState } from '@/state/AppState'; import SelectTileOverlay from '@/components/SelectTileOverlay'; @@ -31,10 +29,6 @@ export default function PhotoGrid({ }: { photos: Photo[] selectedPhoto?: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number photoPriority?: boolean fast?: boolean animate?: boolean @@ -46,7 +40,7 @@ export default function PhotoGrid({ canSelect?: boolean onLastPhotoVisible?: () => void onAnimationComplete?: () => void -}) { +} & PhotoSetAttributes) { const { isUserSignedIn, selectedPhotoIds, diff --git a/src/photo/PhotoGridContainer.tsx b/src/photo/PhotoGridContainer.tsx index dd29fafd..95ee522f 100644 --- a/src/photo/PhotoGridContainer.tsx +++ b/src/photo/PhotoGridContainer.tsx @@ -38,7 +38,7 @@ export default function PhotoGridContainer({ return ( {header && photo.id === selectedPhoto.id) + : undefined; + + const paginationLabel = + (indexNumber || (selectedPhotoIndex ?? 0 + 1)) + ' of ' + + (count ?? photos.length); + + const renderPrevNext = () => + ; + + const renderDateRange = () => + + {start === end + ? start + : <>{end}
– {start}} +
; + + return ( + + + {entity ?? (selectedPhoto + ? + {selectedPhoto.title || formatDate(selectedPhoto.takenAt, 'tiny')} + + : undefined)} + + + {entity && <> + {selectedPhotoIndex !== undefined + ? + {entityVerb || 'PHOTO'} {paginationLabel} + + : entityDescription} + {selectedPhotoIndex === undefined && sharePath && + } + } + +
+ {selectedPhoto + ? renderPrevNext() + : renderDateRange()} +
+ , + ]} + /> + ); +} diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index cda6ac5b..01fb21c3 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -25,10 +25,7 @@ import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; import { sortTags } from '@/tag'; import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; import PhotoLink from './PhotoLink'; -import { - SHOULD_PREFETCH_ALL_LINKS, - SHOW_PHOTO_TITLE_FALLBACK_TEXT, -} from '@/site/config'; +import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config'; import AdminPhotoMenuClient from '@/admin/AdminPhotoMenuClient'; import { RevalidatePhoto } from './InfinitePhotoScroll'; import { useRef } from 'react'; @@ -38,11 +35,13 @@ import { useAppState } from '@/state/AppState'; export default function PhotoLarge({ photo, + className, primaryTag, priority, prefetch = SHOULD_PREFETCH_ALL_LINKS, prefetchRelatedLinks = SHOULD_PREFETCH_ALL_LINKS, revalidatePhoto, + showTitle = true, showCamera = true, showSimulation = true, shouldShare = true, @@ -55,11 +54,13 @@ export default function PhotoLarge({ onVisible, }: { photo: Photo + className?: string primaryTag?: string priority?: boolean prefetch?: boolean prefetchRelatedLinks?: boolean revalidatePhoto?: RevalidatePhoto + showTitle?: boolean showCamera?: boolean showSimulation?: boolean shouldShare?: boolean @@ -85,10 +86,13 @@ export default function PhotoLarge({ const { arePhotosMatted, isUserSignedIn } = useAppState(); + const hasTitle = + showTitle && + Boolean(photo.title); + const hasTitleContent = - photo.title || - SHOW_PHOTO_TITLE_FALLBACK_TEXT || - photo.caption; + hasTitle || + Boolean(photo.caption); const hasMetaContent = showCameraContent || @@ -102,6 +106,7 @@ export default function PhotoLarge({ return (
- {(photo.title || SHOW_PHOTO_TITLE_FALLBACK_TEXT) && + {hasTitle &&
{photo.caption && -
+
{photo.caption}
} {(showCameraContent || showTagsContent) && @@ -224,7 +233,7 @@ export default function PhotoLarge({ photo={photo} className={clsx( 'text-medium', - // Prevent date collision with admin button + // Prevent collision with admin button !hasNonDateContent && isUserSignedIn && 'md:pr-7', )} /> diff --git a/src/photo/PhotoLink.tsx b/src/photo/PhotoLink.tsx index 34bcca38..088ffc1d 100644 --- a/src/photo/PhotoLink.tsx +++ b/src/photo/PhotoLink.tsx @@ -1,13 +1,11 @@ 'use client'; import { ReactNode } from 'react'; -import { Photo, titleForPhoto } from '@/photo'; +import { Photo, PhotoSetAttributes, titleForPhoto } from '@/photo'; import Link from 'next/link'; import { AnimationConfig } from '../components/AnimateItems'; import { useAppState } from '@/state/AppState'; import { pathForPhoto } from '@/site/paths'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { clsx } from 'clsx/lite'; export default function PhotoLink({ @@ -23,16 +21,12 @@ export default function PhotoLink({ children, }: { photo?: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number scroll?: boolean prefetch?: boolean nextPhotoAnimation?: AnimationConfig className?: string children?: ReactNode -}) { +} & PhotoSetAttributes) { const { setNextPhotoAnimation } = useAppState(); return ( diff --git a/src/photo/PhotoMedium.tsx b/src/photo/PhotoMedium.tsx index 72fdd0a4..df1bab48 100644 --- a/src/photo/PhotoMedium.tsx +++ b/src/photo/PhotoMedium.tsx @@ -1,12 +1,15 @@ 'use client'; -import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.'; +import { + Photo, + PhotoSetAttributes, + altTextForPhoto, + doesPhotoNeedBlurCompatibility, +} from '.'; import ImageMedium from '@/components/image/ImageMedium'; import Link from 'next/link'; import { clsx } from 'clsx/lite'; import { pathForPhoto } from '@/site/paths'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config'; import { useRef } from 'react'; import useOnVisible from '@/utility/useOnVisible'; @@ -24,16 +27,12 @@ export default function PhotoMedium({ onVisible, }: { photo: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number selected?: boolean priority?: boolean prefetch?: boolean className?: string onVisible?: () => void -}) { +} & PhotoSetAttributes) { const ref = useRef(null); useOnVisible(ref, onVisible); diff --git a/src/photo/PhotoLinks.tsx b/src/photo/PhotoPrevNext.tsx similarity index 53% rename from src/photo/PhotoLinks.tsx rename to src/photo/PhotoPrevNext.tsx index b2d86036..52e8fa3d 100644 --- a/src/photo/PhotoLinks.tsx +++ b/src/photo/PhotoPrevNext.tsx @@ -1,35 +1,38 @@ 'use client'; import { useEffect } from 'react'; -import { Photo, getNextPhoto, getPreviousPhoto } from '@/photo'; +import { + Photo, + PhotoSetAttributes, + getNextPhoto, + getPreviousPhoto, +} from '@/photo'; import PhotoLink from './PhotoLink'; import { useRouter } from 'next/navigation'; import { pathForPhoto } from '@/site/paths'; import { useAppState } from '@/state/AppState'; import { AnimationConfig } from '@/components/AnimateItems'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; +import { clsx } from 'clsx/lite'; +import { FiChevronLeft, FiChevronRight } from 'react-icons/fi'; const LISTENER_KEYUP = 'keyup'; const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 }; const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 }; -export default function PhotoLinks({ +export default function PhotoPrevNext({ photo, - photos, + photos = [], + className, tag, camera, simulation, focal, }: { - photo: Photo - photos: Photo[] - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number -}) { + photo?: Photo + photos?: Photo[] + className?: string +} & PhotoSetAttributes) { const router = useRouter(); const { @@ -37,8 +40,8 @@ export default function PhotoLinks({ shouldRespondToKeyboardCommands, } = useAppState(); - const previousPhoto = getPreviousPhoto(photo, photos); - const nextPhoto = getNextPhoto(photo, photos); + const previousPhoto = photo ? getPreviousPhoto(photo, photos) : undefined; + const nextPhoto = photo ? getNextPhoto(photo, photos) : undefined; useEffect(() => { if (shouldRespondToKeyboardCommands) { @@ -94,31 +97,47 @@ export default function PhotoLinks({ ]); return ( - <> - - PREV - - - NEXT - - +
+
+ + + PREV + + + / + + + + NEXT + +
+
); }; diff --git a/src/photo/PhotoSetHeader.tsx b/src/photo/PhotoSetHeader.tsx deleted file mode 100644 index 87b4da2f..00000000 --- a/src/photo/PhotoSetHeader.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { clsx } from 'clsx/lite'; -import { Photo, PhotoDateRange, dateRangeForPhotos } from '.'; -import ShareButton from '@/components/ShareButton'; -import AnimateItems from '@/components/AnimateItems'; -import { ReactNode } from 'react'; -import { HIGH_DENSITY_GRID } from '@/site/config'; -import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid'; - -export default function PhotoSetHeader({ - entity, - entityVerb, - entityDescription, - photos, - selectedPhoto, - sharePath, - indexNumber, - count, - dateRange, -}: { - entity: ReactNode - entityVerb?: string - entityDescription: string - photos: Photo[] - selectedPhoto?: Photo - sharePath?: string - indexNumber?: number - count?: number - dateRange?: PhotoDateRange -}) { - const { start, end } = dateRangeForPhotos(photos, dateRange); - - const selectedPhotoIndex = selectedPhoto - ? photos.findIndex(photo => photo.id === selectedPhoto.id) - : undefined; - - return ( - - - {entity} - - - {selectedPhotoIndex !== undefined - // eslint-disable-next-line max-len - ? `${entityVerb ? `${entityVerb} ` : ''}${indexNumber || (selectedPhotoIndex + 1)} of ${count ?? photos.length}` - : entityDescription} - {selectedPhotoIndex === undefined && sharePath && - } - - - {start === end - ? start - : <>{end}
– {start}} -
- ]} - /> - ); -} diff --git a/src/photo/PhotoShareModal.tsx b/src/photo/PhotoShareModal.tsx index b9a6f14c..8f577454 100644 --- a/src/photo/PhotoShareModal.tsx +++ b/src/photo/PhotoShareModal.tsx @@ -1,17 +1,11 @@ import PhotoOGTile from '@/photo/PhotoOGTile'; import { absolutePathForPhoto, pathForPhoto } from '@/site/paths'; -import { Photo } from '.'; +import { Photo, PhotoSetAttributes } from '.'; import ShareModal from '@/components/ShareModal'; -import { Camera } from '@/camera'; -import { FilmSimulation } from '@/simulation'; export default function PhotoShareModal(props: { photo: Photo - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number -}) { +} & PhotoSetAttributes) { return ( void -}) { +} & PhotoSetAttributes) { const ref = useRef(null); useOnVisible(ref, onVisible); diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index 2bc13658..cf2b1ec7 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -1,8 +1,6 @@ -import { Camera } from '@/camera'; -import { Lens } from '@/lens'; -import { FilmSimulation } from '@/simulation'; import { PRIORITY_ORDER_ENABLED } from '@/site/config'; import { parameterize } from '@/utility/string'; +import { PhotoSetAttributes } from '..'; export const GENERATE_STATIC_PARAMS_LIMIT = 1000; export const PHOTO_DEFAULT_LIMIT = 100; @@ -12,16 +10,11 @@ export type GetPhotosOptions = { limit?: number offset?: number query?: string - tag?: string - camera?: Camera - lens?: Lens - simulation?: FilmSimulation - focal?: number takenBefore?: Date takenAfterInclusive?: Date updatedBefore?: Date hidden?: 'exclude' | 'include' | 'only' -}; +} & PhotoSetAttributes; export const areOptionsSensitive = (options: GetPhotosOptions) => options.hidden === 'include' || options.hidden === 'only'; diff --git a/src/photo/index.ts b/src/photo/index.ts index 646bd13e..bf3feab3 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -1,4 +1,6 @@ +import { Camera } from '@/camera'; import { formatFocalLength } from '@/focal'; +import { Lens } from '@/lens'; import { getNextImageUrlForRequest } from '@/services/next-image'; import { FilmSimulation } from '@/simulation'; import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config'; @@ -99,6 +101,14 @@ export interface Photo extends PhotoDb { takenAtNaiveFormatted: string } +export interface PhotoSetAttributes { + tag?: string + camera?: Camera + simulation?: FilmSimulation + focal?: number + lens?: Lens // Unimplemented as a set +} + export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => { const photoDb = camelcaseKeys( photoDbRaw as unknown as Record diff --git a/src/simulation/FilmSimulationHeader.tsx b/src/simulation/FilmSimulationHeader.tsx index b0908572..df07a4f3 100644 --- a/src/simulation/FilmSimulationHeader.tsx +++ b/src/simulation/FilmSimulationHeader.tsx @@ -1,7 +1,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { FilmSimulation, descriptionForFilmSimulationPhotos } from '.'; import { pathForFilmSimulationShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation'; @@ -21,9 +21,9 @@ export default function FilmSimulationHeader({ dateRange?: PhotoDateRange }) { return ( - } - entityVerb="Photo" entityDescription={descriptionForFilmSimulationPhotos( photos, undefined, count, dateRange)} photos={photos} diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index e1a82753..f1d4772a 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -59,7 +59,6 @@ export default function SiteChecklistClient({ arePhotosMatted, isBlurEnabled, isGeoPrivacyEnabled, - showPhotoTitleFallbackText, isPriorityOrderEnabled, isAiTextGenerationEnabled, aiTextAutoGeneratedFields, @@ -507,15 +506,6 @@ export default function SiteChecklistClient({ collection/display of location-based data: {renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])} - - Set environment variable to {'"1"'} to prevent - showing {'"Untitled"'} for photos without titles: - {renderEnvVars(['NEXT_PUBLIC_HIDE_TITLE_FALLBACK_TEXT'])} - export const getPathComponents = (pathname = ''): { photoId?: string - tag?: string - camera?: Camera - simulation?: FilmSimulation - focal?: number -} => { +} & PhotoSetAttributes => { const photoIdFromPhoto = pathname.match( new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1]; const photoIdFromTag = pathname.match( diff --git a/src/tag/HiddenHeader.tsx b/src/tag/HiddenHeader.tsx index feac25fb..244013be 100644 --- a/src/tag/HiddenHeader.tsx +++ b/src/tag/HiddenHeader.tsx @@ -1,5 +1,5 @@ import { Photo, photoQuantityText } from '@/photo'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import HiddenTag from './HiddenTag'; export default function HiddenHeader({ @@ -14,7 +14,7 @@ export default function HiddenHeader({ count: number }) { return ( - } entityDescription={photoQuantityText(count, false)} diff --git a/src/tag/TagHeader.tsx b/src/tag/TagHeader.tsx index 4a9e3165..6eff23bc 100644 --- a/src/tag/TagHeader.tsx +++ b/src/tag/TagHeader.tsx @@ -2,7 +2,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import PhotoTag from './PhotoTag'; import { descriptionForTaggedPhotos, isTagFavs } from '.'; import { pathForTagShare } from '@/site/paths'; -import PhotoSetHeader from '@/photo/PhotoSetHeader'; +import PhotoHeader from '@/photo/PhotoHeader'; import FavsTag from './FavsTag'; export default function TagHeader({ @@ -21,7 +21,8 @@ export default function TagHeader({ dateRange?: PhotoDateRange }) { return ( - : } diff --git a/src/utility/date.ts b/src/utility/date.ts index f758dd8f..fdbe6454 100644 --- a/src/utility/date.ts +++ b/src/utility/date.ts @@ -1,5 +1,6 @@ import { format, parseISO, parse } from 'date-fns'; +const DATE_STRING_FORMAT_TINY = 'dd MMM yy'; const DATE_STRING_FORMAT_SHORT = 'dd MMM yyyy'; const DATE_STRING_FORMAT_MEDIUM = 'dd MMM yy h:mma'; const DATE_STRING_FORMAT = 'dd MMM yyyy h:mma'; @@ -7,10 +8,12 @@ const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss'; type AmbiguousTimestamp = number | string; -type Length = 'short' | 'medium' | 'long'; +type Length = 'tiny' | 'short' | 'medium' | 'long'; export const formatDate = (date: Date, length: Length = 'long') => { switch (length) { + case 'tiny': + return format(date, DATE_STRING_FORMAT_TINY); case 'short': return format(date, DATE_STRING_FORMAT_SHORT); case 'medium':