diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c4a31c..81fb01bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -255,6 +255,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Fixed +### Internet Radio — Add / Edit station modal no longer clipped on empty library + +**By [@cucadmuh](https://github.com/cucadmuh), thanks to voidboywannabe for the report on the Psysonic Discord, PR [#699](https://github.com/Psychotoxical/psysonic/pull/699)** + +* **Add Station** / **Edit** now mount **`RadioEditModal`** with **`createPortal(..., document.body)`** (same layering approach as **Search Directory**). Previously the overlay lived under nested **`.content-body`** with **`contain: paint`**, so on an **empty** station list the fixed overlay was **painted inside a short box** and looked cropped; after the first station the layout was tall enough that the bug was easy to miss. + ### Search — hide duplicate artist hits with zero albums **By [@cucadmuh](https://github.com/cucadmuh), thanks to zunoz for the report on the Psysonic Discord, PR [#697](https://github.com/Psychotoxical/psysonic/pull/697)** diff --git a/src/components/internetRadio/RadioEditModal.tsx b/src/components/internetRadio/RadioEditModal.tsx index 564720ce..892aac86 100644 --- a/src/components/internetRadio/RadioEditModal.tsx +++ b/src/components/internetRadio/RadioEditModal.tsx @@ -1,4 +1,5 @@ import React, { useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Camera, Cast, Loader2, X } from 'lucide-react'; import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl'; @@ -62,7 +63,9 @@ export default function RadioEditModal({ station, onClose, onSave }: RadioEditMo if (e.target === e.currentTarget) onClose(); }; - return ( + /* Portal to document.body: nested .content-body uses contain:paint — in-tree + * modals are clipped when the station list is empty (short layout). Same pattern as RadioDirectoryModal. */ + return createPortal(
- + , + document.body ); } diff --git a/src/config/settingsCredits.ts b/src/config/settingsCredits.ts index 15d0ca30..d4824051 100644 --- a/src/config/settingsCredits.ts +++ b/src/config/settingsCredits.ts @@ -107,6 +107,7 @@ export const CONTRIBUTORS = [ 'CachedImage / useCachedUrl: blob URL only for matching cacheKey, layout load reset on key change; fixes broken player cover flash on track switch (#606) (PR #695)', 'OpenSubsonic albumArtists in album header; track artists in player bar, mobile, mini + songToTrack (#552) (PR #696)', 'Search: filter search3 artist rows with zero albums (report: zunoz on Psysonic Discord) (PR #697)', + 'Internet Radio: portal add/edit station modal to document.body — fixes clipped modal on empty library (report: voidboywannabe on Psysonic Discord) (PR #699)', ], }, { diff --git a/src/pages/InternetRadio.tsx b/src/pages/InternetRadio.tsx index a60ff9fa..9f3547b9 100644 --- a/src/pages/InternetRadio.tsx +++ b/src/pages/InternetRadio.tsx @@ -2,7 +2,6 @@ import { getInternetRadioStations, createInternetRadioStation, updateInternetRad import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonicStreamUrl'; import { type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE } from '../api/subsonicTypes'; import React, { useEffect, useState, useRef, useMemo, useCallback } from 'react'; -import { createPortal } from 'react-dom'; import { Cast, Plus, Trash2, X, Globe, Camera, Loader2, Search, Heart, Check } from 'lucide-react'; import { useDragSource, useDragDrop } from '../contexts/DragDropContext'; import { usePlayerStore } from '../store/playerStore';