fix: usable layout when the window is small (#981)

* fix(layout): collapse browse toolbar to icons on compact width

On a narrow window the labelled filter buttons wrapped into several rows
and, being a fixed-height sticky header, starved the album grid below them
down to a clipped strip. Wrap each toolbar label in a hideable span and drop
to icon-only in compact (mobile) mode; icons keep their tooltip and
aria-label so the action stays discoverable.

* fix(window): raise minimum window size to 520x640

The old 360x480 floor let the window shrink far past the point the layout
holds together. Floor it at a laptop-friendly size where the compact layout
(icon toolbar + scrollable lists) still works.

* fix(player): scale mobile cover to fit short windows

The cover width was viewport-derived with no height bound, so on a short
window the square grew past its slot and overlapped the title. Bind it to
the shrinkable wrap via max-height and keep it square with auto sizing.

* docs: changelog for small-window layout fixes (#981)
This commit is contained in:
Frank Stellmacher
2026-06-04 11:16:48 +02:00
committed by GitHub
parent f3eb58c707
commit b0f35eabc9
10 changed files with 45 additions and 19 deletions
+1 -1
View File
@@ -158,7 +158,7 @@ export default function GenreFilterBar({
style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}
>
<Filter size={14} />
{t('common.filterGenre')}
<span className="toolbar-btn-label">{t('common.filterGenre')}</span>
{count > 0 && <span className="genre-filter-count">{count}</span>}
{count > 0 && <FilterQuickClear onActiveChip onClear={clear} />}
</button>
+1 -1
View File
@@ -25,7 +25,7 @@ export default function LosslessFilterButton({ active, onChange }: Props) {
}}
>
<Gem size={14} />
{t('albums.losslessLabel')}
<span className="toolbar-btn-label">{t('albums.losslessLabel')}</span>
{active && <FilterQuickClear onActiveChip onClear={() => onChange(false)} />}
</button>
);
+1 -1
View File
@@ -98,7 +98,7 @@ export default function SortDropdown<V extends string>({ value, options, onChang
style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}
>
<ArrowDownUp size={14} />
{current?.label ?? value}
<span className="toolbar-btn-label">{current?.label ?? value}</span>
</button>
{open && createPortal(
+1 -1
View File
@@ -62,7 +62,7 @@ export default function StarFilterButton({ active, onChange, size = 'default' }:
}}
>
<Star size={14} fill={active ? 'currentColor' : 'none'} />
{t('common.favorites')}
<span className="toolbar-btn-label">{t('common.favorites')}</span>
{active && <FilterQuickClear onActiveChip onClear={() => onChange(false)} />}
</button>
);
+1 -1
View File
@@ -144,7 +144,7 @@ export default function YearFilterButton({
}}
>
<CalendarRange size={14} />
{active && activeLabel ? activeLabel : t('albums.yearFilterLabel')}
<span className="toolbar-btn-label">{active && activeLabel ? activeLabel : t('albums.yearFilterLabel')}</span>
{active && <FilterQuickClear onActiveChip onClear={clear} />}
</button>
+12 -10
View File
@@ -365,17 +365,17 @@ export default function Albums() {
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
{selectionMode && selectedIds.size > 0 ? (
<>
<button className="btn btn-surface albums-selection-action-btn" onClick={handleEnqueueSelected}>
<button className="btn btn-surface albums-selection-action-btn" onClick={handleEnqueueSelected} data-tooltip={t('albums.enqueueSelected', { count: selectedIds.size })} data-tooltip-pos="bottom">
<ListPlus size={15} />
{t('albums.enqueueSelected', { count: selectedIds.size })}
<span className="toolbar-btn-label">{t('albums.enqueueSelected', { count: selectedIds.size })}</span>
</button>
<button className="btn btn-surface albums-selection-action-btn" onClick={handleAddOffline}>
<button className="btn btn-surface albums-selection-action-btn" onClick={handleAddOffline} data-tooltip={t('albums.addOffline')} data-tooltip-pos="bottom">
<HardDriveDownload size={15} />
{t('albums.addOffline')}
<span className="toolbar-btn-label">{t('albums.addOffline')}</span>
</button>
<button className="btn btn-surface albums-selection-action-btn" onClick={handleDownloadZips}>
<button className="btn btn-surface albums-selection-action-btn" onClick={handleDownloadZips} data-tooltip={t('albums.downloadZips')} data-tooltip-pos="bottom">
<Download size={15} />
{t('albums.downloadZips')}
<span className="toolbar-btn-label">{t('albums.downloadZips')}</span>
</button>
</>
) : (
@@ -422,9 +422,11 @@ export default function Albums() {
}}
>
<Disc3 size={14} />
{compFilter === 'all' ? t('albums.compilationLabel')
: compFilter === 'only' ? t('albums.compilationOnly')
: t('albums.compilationHide')}
<span className="toolbar-btn-label">
{compFilter === 'all' ? t('albums.compilationLabel')
: compFilter === 'only' ? t('albums.compilationOnly')
: t('albums.compilationHide')}
</span>
{compFilter !== 'all' && (
<FilterQuickClear onActiveChip onClear={() => setCompFilter('all')} />
)}
@@ -440,7 +442,7 @@ export default function Albums() {
style={selectionMode ? { background: 'var(--accent)', color: 'var(--ctp-crust)' } : {}}
>
<CheckSquare2 size={15} />
{selectionMode ? t('albums.cancelSelect') : t('albums.select')}
<span className="toolbar-btn-label">{selectionMode ? t('albums.cancelSelect') : t('albums.select')}</span>
</button>
</div>
</div>
+9 -2
View File
@@ -8,8 +8,15 @@
}
.mp-cover {
width: min(calc(100vw - 28px), 55vh);
max-width: 440px;
/* Largest square that fits BOTH the viewport width and the height the flex
column leaves for the cover. `max-height: 100%` ties the image to its
(shrinkable) wrap, so on a short window it scales down instead of growing
past its slot and overlapping the title (zunoz, RC2 small-window report).
`width/height: auto` + `aspect-ratio` keep it square while it shrinks. */
max-width: min(calc(100vw - 28px), 440px);
max-height: 100%;
width: auto;
height: auto;
aspect-ratio: 1;
object-fit: cover;
border-radius: 14px;
+9
View File
@@ -180,6 +180,15 @@
min-height: 0;
}
/* Compact mode: the sticky toolbar is `flex-shrink: 0`, so labelled filter
buttons wrap into several rows on a narrow window and starve the `flex: 1`
list below them, clipping the album grid (zunoz, RC2). Collapse the toolbar
buttons to icon-only (label hidden, icon + tooltip + aria-label kept) so the
row stays short and the list keeps its height. */
.app-shell[data-mobile] .mainstage-inpage-toolbar .toolbar-btn-label {
display: none;
}
.mainstage-inpage-scroll__viewport {
padding-bottom: var(--space-6);
padding-right: var(--space-6);