From 10c9cf426907ca9efdd2ebbd69b8dc7545296dc7 Mon Sep 17 00:00:00 2001 From: khannurien Date: Thu, 8 Oct 2026 22:10:06 +0000 Subject: [PATCH] =?UTF-8?q?v4:=20a=20playlist's=20cover=20is=20a=20banner?= =?UTF-8?q?=20over=20its=20header,=20and=20the=20edit=20form=20picks=20it?= =?UTF-8?q?=20in=20the=20same=20shape=20=E2=80=94=20the=20cover=20was=20a?= =?UTF-8?q?=2072px=20square=20beside=20the=20title,=20which=20left=20the?= =?UTF-8?q?=20title=20and=20description=20a=20narrow=20column=20under=20a?= =?UTF-8?q?=20mostly=20empty=20band:=20on=20a=20phone,=20in=20Kanagawa's?= =?UTF-8?q?=20wide=20mono,=20a=20long=20title=20broke=20into=20six=20lines?= =?UTF-8?q?.=20The=20cover=20now=20spans=20the=20header's=20width=20above?= =?UTF-8?q?=20the=20title,=20cropped=20to=203:1=20and=20no=20taller=20than?= =?UTF-8?q?=2014rem,=20inside=20the=20header's=20padding=20so=20outlined?= =?UTF-8?q?=20themes=20keep=20their=20ring=20around=20it;=20the=20title,?= =?UTF-8?q?=20buttons,=20description=20and=20meta=20take=20the=20whole=20w?= =?UTF-8?q?idth=20under=20it.=20The=20edit=20form's=20Cover=20picker=20tak?= =?UTF-8?q?es=20the=20same=20shape=20(.playlist-cover-banner,=20shared=20w?= =?UTF-8?q?ith=20.playlist-detail-img),=20and=20with=20no=20cover=20is=20a?= =?UTF-8?q?=20dashed=203:1=20placeholder.=20ImagePicker's=20size=20is=20no?= =?UTF-8?q?w=20optional,=20so=20a=20class=20can=20size=20it,=20and=20its?= =?UTF-8?q?=20radius=20takes=20any=20CSS=20length,=20here=20--radius-lg;?= =?UTF-8?q?=20a=20picker=20sized=20by=20aspect-ratio=20sets=20min-height:?= =?UTF-8?q?=200,=20or=20its=20image=20would=20hold=20it=20at=20its=20own?= =?UTF-8?q?=20height.=20669=20frontend=20tests=20pass,=20and=20tsc,=20esli?= =?UTF-8?q?nt,=20deno=20fmt=20and=20doc:lint=20are=20clean;=20checked=20in?= =?UTF-8?q?=20screenshots=20in=20Kanagawa=20dark=20(French,=20385px),=20Br?= =?UTF-8?q?utalist=20at=20390px=20with=20and=20without=20a=20cover,=20and?= =?UTF-8?q?=20Smooth=20at=201100px,=20the=20page=20and=20the=20form.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- src/App.css | 24 ++++++++++++++++---- src/components/playlist/PlaylistEditForm.tsx | 3 ++- src/components/shared/ImagePicker.tsx | 11 +++++---- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/App.css b/src/App.css index c2f564e5..8b793142 100644 --- a/src/App.css +++ b/src/App.css @@ -5344,17 +5344,31 @@ body.has-player .chat-fab { .playlist-detail-header-top { display: flex; - align-items: flex-start; + flex-direction: column; gap: 1rem; } +/* The cover is a banner over the title, cropped to 3:1 and no taller than + 14rem on wide screens, and the edit form's picker takes the same shape. It + stays inside the header's padding, so themes that outline panels with + --panel-ring keep their ring around it. */ +.playlist-detail-img, +.playlist-cover-banner { + display: block; + width: 100%; + aspect-ratio: 3 / 1; + max-height: 14rem; +} + .playlist-detail-img { - flex-shrink: 0; - width: 72px; - height: 72px; object-fit: cover; border-radius: var(--radius-lg); - border: 1px solid var(--color-border); +} + +/* Or the picker's image would hold it at its own height: a box sized by + aspect-ratio grows to fit its content. */ +.playlist-cover-banner { + min-height: 0; } .playlist-detail-title-row { diff --git a/src/components/playlist/PlaylistEditForm.tsx b/src/components/playlist/PlaylistEditForm.tsx index 647e522d..554d8feb 100644 --- a/src/components/playlist/PlaylistEditForm.tsx +++ b/src/components/playlist/PlaylistEditForm.tsx @@ -144,7 +144,8 @@ export function PlaylistEditForm( ? playlistImageUrl(playlist, mediaToken) : null)} alt="Cover" - size={72} + className="playlist-cover-banner" + borderRadius="var(--radius-lg)" onChange={(file) => { field.onChange(file); setImagePreview(URL.createObjectURL(file)); diff --git a/src/components/shared/ImagePicker.tsx b/src/components/shared/ImagePicker.tsx index 33c5fe31..5c598de1 100644 --- a/src/components/shared/ImagePicker.tsx +++ b/src/components/shared/ImagePicker.tsx @@ -16,9 +16,10 @@ export interface ImagePickerProps { /** Shows a "+" placeholder when null. */ src: string | null; alt?: string; - /** Sets the side, in pixels. */ + /** Sets the side, in pixels. Without it, `className` sizes the picker. */ size?: number; - borderRadius?: number; + /** In pixels when a number, or any CSS length, such as a radius token. */ + borderRadius?: number | string; /** Called with the file picked. The same file can be picked again. */ onChange: (file: File) => void; /** Disables the picker and shows "…" while the caller uploads. */ @@ -35,7 +36,7 @@ export interface ImagePickerProps { export function ImagePicker({ src, alt = "", - size = 80, + size, borderRadius = 8, onChange, uploading = false, @@ -43,7 +44,9 @@ export function ImagePicker({ className, }: ImagePickerProps): React.JSX.Element { const inputRef = useRef(null); - const sizeStyle = { width: size, height: size, borderRadius }; + const sizeStyle = size === undefined + ? { borderRadius } + : { width: size, height: size, borderRadius }; const pick = () => { if (!uploading) inputRef.current?.click(); };