release: v0.12.0 (#520)

Forty-one commits since v0.11.0. See CHANGELOG.md for the full list and the upgrade notes.
This commit is contained in:
Ralksta
2026-08-23 23:18:14 +02:00
committed by GitHub
parent 2f546f440d
commit 10ec68f493
119 changed files with 6888 additions and 758 deletions

View File

@@ -2,6 +2,7 @@ import Link from 'next/link';
import Image from 'next/image';
import { imageUrl } from '@/lib/urls';
import { SubpageSectionConfig } from '@/lib/config';
import { getServerDictionary } from '@/lib/i18n/server';
interface SubpageAlbum {
id: string;
@@ -27,22 +28,26 @@ interface SubpageGridViewProps {
sections?: SubpageSectionConfig[];
/** 1-based position among all subpages — feeds the "03 — Collection" kicker. */
index?: number;
/** `--subpage-columns` / `--subpage-gap` from the resolved grid config. */
gridStyle?: React.CSSProperties;
}
const pad2 = (n: number) => String(n).padStart(2, '0');
const photoCount = (n: number) => `${n} ${n === 1 ? 'photo' : 'photos'}`;
const photoCount = (n: number) => getServerDictionary().common.photos(n);
function AlbumGrid({
albums,
placeholderMap,
slug,
gridStyle,
}: {
albums: SubpageAlbum[];
placeholderMap: Map<string, Placeholder | null>;
slug: string;
gridStyle?: React.CSSProperties;
}) {
return (
<div className="subpage-grid">
<div className="subpage-grid" style={gridStyle}>
{albums.map((album, i) => {
const ph = placeholderMap.get(album.id) ?? null;
return (
@@ -51,7 +56,10 @@ function AlbumGrid({
href={`/${slug}/${album.slug}`}
className="subpage-grid__item"
style={ph?.dominantColor ? { backgroundColor: ph.dominantColor } : undefined}
aria-label={`${album.albumName}, ${photoCount(album.assetCount)}`}
aria-label={getServerDictionary().subpage.coverAria(
album.albumName,
photoCount(album.assetCount),
)}
>
<span className="subpage-grid__item-media">
{album.albumThumbnailAssetId ? (
@@ -105,11 +113,13 @@ export function SubpageGridView({
coverPlaceholders,
sections,
index,
gridStyle,
}: SubpageGridViewProps) {
// Build lookup maps once
const albumMap = new Map(albums.map((a) => [a.id, a]));
const placeholderMap = new Map(albums.map((a, i) => [a.id, coverPlaceholders[i] ?? null]));
const t = getServerDictionary();
const hasSections = sections && sections.length > 0;
const totalPhotos = albums.reduce((sum, a) => sum + a.assetCount, 0);
@@ -120,21 +130,21 @@ export function SubpageGridView({
<div className="subpage-header__main">
{index !== undefined && (
<p className="subpage-header__kicker" aria-hidden="true">
{pad2(index)} — Collection
{t.subpage.collectionKicker(pad2(index))}
</p>
)}
{title && <h1 className="subpage-title">{title}</h1>}
{subtitle && <p className="subpage-subtitle">{subtitle}</p>}
</div>
<p className="subpage-header__meta" aria-hidden="true">
{albums.length} {albums.length === 1 ? 'album' : 'albums'} · {photoCount(totalPhotos)}
{t.common.albums(albums.length)} · {photoCount(totalPhotos)}
</p>
</header>
)}
{/* Typographic Table of Contents */}
{hasSections && (
<nav className="subpage-toc" aria-label="Sections">
<nav className="subpage-toc" aria-label={t.subpage.sectionsNav}>
{sections.map((sec, i) => (
<span key={sec.slug} className="subpage-toc__entry">
<a href={`#${sec.slug}`} className="subpage-toc__link">
@@ -160,13 +170,23 @@ export function SubpageGridView({
{sec.description && <p className="subpage-section__desc">{sec.description}</p>}
<div className="subpage-section__rule" aria-hidden="true" />
</header>
<AlbumGrid albums={sectionAlbums} placeholderMap={placeholderMap} slug={slug} />
<AlbumGrid
albums={sectionAlbums}
placeholderMap={placeholderMap}
slug={slug}
gridStyle={gridStyle}
/>
</section>
);
})
) : (
/* Flat layout (no sections) */
<AlbumGrid albums={albums} placeholderMap={placeholderMap} slug={slug} />
<AlbumGrid
albums={albums}
placeholderMap={placeholderMap}
slug={slug}
gridStyle={gridStyle}
/>
)}
</div>
);