diff --git a/CHANGELOG.md b/CHANGELOG.md index c97252ff..953cf9f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -565,6 +565,13 @@ Foundational work: faster reviews, narrower diffs, and a safety net under the pa * The Album header showed an **Artist Bio** button on every album, but when the album-artist label is **"Various Artists"**, **"Various"**, **"VA"** or a language equivalent there is no single artist to fetch a bio for and the button opened an empty modal. Both the mobile icon and the desktop button are now hidden when the album-artist label matches that compilation heuristic. +### Album header — Artist Biography modal stays in viewport and scrolls internally + +**By [@Psychotoxical](https://github.com/Psychotoxical), thanks to zunoz for the report on the Psysonic Discord, PR [#734](https://github.com/Psychotoxical/psysonic/pull/734)** + +* The **Artist Biography** modal lived under the album page tree, where an ancestor broke **`position: fixed`** on the overlay — opening a long bio scrolled the whole page instead of staying pinned to the viewport, and the modal itself stretched past the visible area. It now mounts via **`createPortal(..., document.body)`** (same approach as **`RadioEditModal`** / **`CoverLightbox`**), so the overlay always pins to the viewport. +* A new **`.modal-content.bio-modal`** variant turns the modal into a flex column with **`overflow: hidden`** and an inner **`.bio-modal-body`** that handles the scrolling. The existing **`max-height: 80vh`** cap is now honored, and the title + close button stay pinned while the bio scrolls. + ## [1.45.0] - 2026-05-04 ## Added diff --git a/src/components/AlbumHeader.tsx b/src/components/AlbumHeader.tsx index ef996005..aeecd681 100644 --- a/src/components/AlbumHeader.tsx +++ b/src/components/AlbumHeader.tsx @@ -1,6 +1,7 @@ import { buildCoverArtUrl } from '../api/subsonicStreamUrl'; import type { EntityRatingSupportLevel, SubsonicOpenArtistRef, SubsonicSong } from '../api/subsonicTypes'; import React, { useMemo, useState } from 'react'; +import { createPortal } from 'react-dom'; import { useNavigate } from 'react-router-dom'; import { Play, Heart, ExternalLink, X, ChevronLeft, Download, ListPlus, HardDriveDownload, Share2, Highlighter, Loader2, Shuffle } from 'lucide-react'; import CachedImage from './CachedImage'; @@ -35,14 +36,17 @@ function isVariousArtistsLabel(name: string | undefined | null): boolean { function BioModal({ bio, onClose }: { bio: string; onClose: () => void }) { const { t } = useTranslation(); - return ( + return createPortal(
-
e.stopPropagation()}> +
e.stopPropagation()}>

{t('albumDetail.bioModal')}

-
+
+
+
-
+
, + document.body ); } diff --git a/src/styles/components/modal.css b/src/styles/components/modal.css index d85ee86c..3b87c63b 100644 --- a/src/styles/components/modal.css +++ b/src/styles/components/modal.css @@ -34,6 +34,21 @@ box-shadow: var(--shadow-lg); } +/* Bio modal: keep title + close visible, scroll only the bio body. */ +.modal-content.bio-modal { + display: flex; + flex-direction: column; + overflow: hidden; + padding-right: var(--space-6); +} + +.bio-modal-body { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + padding-right: var(--space-2); +} + /* Modal CSV Import - siempre centrado */ .modal-overlay--csv { position: fixed !important;