mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(ui): shrink-wrap floating player bar instead of full-width strip (#969)
* fix(ui): shrink-wrap floating player bar instead of full-width strip The bar used fixed left and right insets, which stretched its background across the whole main column. Center it with max-content width so only the pill-shaped controls are painted, and soften the drop shadow. * docs: note PR #969 in changelog and settings credits
This commit is contained in:
@@ -491,6 +491,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
* Nonsense queries no longer return unrelated fuzzy matches (e.g. long repeated letters).
|
* Nonsense queries no longer return unrelated fuzzy matches (e.g. long repeated letters).
|
||||||
|
|
||||||
|
|
||||||
|
### Floating player bar — remove full-width background strip
|
||||||
|
|
||||||
|
**By [@cucadmuh](https://github.com/cucadmuh), reported by Asra on the Psysonic Discord, PR [#969](https://github.com/Psychotoxical/psysonic/pull/969)**
|
||||||
|
|
||||||
|
* Floating mode no longer stretches the player bar between sidebar and queue with fixed `left`/`right` — only the centered pill is painted over the page instead of a full-width black band behind the rounded corners.
|
||||||
|
|
||||||
|
|
||||||
### In-page browse — virtual scroll and cover-art priority
|
### In-page browse — virtual scroll and cover-art priority
|
||||||
|
|
||||||
**By [@cucadmuh](https://github.com/cucadmuh), PR [#783](https://github.com/Psychotoxical/psysonic/pull/783)**
|
**By [@cucadmuh](https://github.com/cucadmuh), PR [#783](https://github.com/Psychotoxical/psysonic/pull/783)**
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ const CONTRIBUTOR_ENTRIES = [
|
|||||||
'Player: persist volume/repeat/queue visibility/Last.fm cache outside quota-bound queue blob (report: norp on Psysonic Discord) (PR #958)',
|
'Player: persist volume/repeat/queue visibility/Last.fm cache outside quota-bound queue blob (report: norp on Psysonic Discord) (PR #958)',
|
||||||
'Player transport: custom delay input capped to browser timer limit; preview/countdown aligned for fractional minutes (report: zunoz on Psysonic Discord) (PR #967)',
|
'Player transport: custom delay input capped to browser timer limit; preview/countdown aligned for fractional minutes (report: zunoz on Psysonic Discord) (PR #967)',
|
||||||
'Settings: in-page search indexes AudioMuse + shortcut rows; rejects junk fuzzy matches (report: zunoz on Psysonic Discord) (PR #968)',
|
'Settings: in-page search indexes AudioMuse + shortcut rows; rejects junk fuzzy matches (report: zunoz on Psysonic Discord) (PR #968)',
|
||||||
|
'Floating player bar: center shrink-wrapped pill instead of full-width background strip (report: Asra on Psysonic Discord) (PR #969)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { computeFloatingPlayerBarStyle } from './computeFloatingPlayerBarStyle';
|
||||||
|
|
||||||
|
describe('computeFloatingPlayerBarStyle', () => {
|
||||||
|
it('centers in the main column and shrink-wraps instead of stretching', () => {
|
||||||
|
const style = computeFloatingPlayerBarStyle(220, 1600, 1920);
|
||||||
|
expect(style.left).toBe(910);
|
||||||
|
expect(style.right).toBe('auto');
|
||||||
|
expect(style.transform).toBe('translateX(-50%)');
|
||||||
|
expect(style.width).toBe('max-content');
|
||||||
|
expect(style.maxWidth).toBe(1332);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accounts for a hidden queue panel', () => {
|
||||||
|
const style = computeFloatingPlayerBarStyle(220, null, 1920);
|
||||||
|
expect(style.left).toBe(1070);
|
||||||
|
expect(style.maxWidth).toBe(1652);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
const HORIZONTAL_MARGIN_PX = 24;
|
||||||
|
|
||||||
|
/** Center the floating bar in the main column; shrink-wrap width instead of stretching. */
|
||||||
|
export function computeFloatingPlayerBarStyle(
|
||||||
|
sidebarRight: number,
|
||||||
|
queueLeft: number | null,
|
||||||
|
viewportWidth: number,
|
||||||
|
): CSSProperties {
|
||||||
|
const contentLeft = sidebarRight;
|
||||||
|
const contentRight = queueLeft ?? viewportWidth;
|
||||||
|
const contentWidth = Math.max(0, contentRight - contentLeft);
|
||||||
|
const maxWidth = Math.max(100, contentWidth - HORIZONTAL_MARGIN_PX * 2);
|
||||||
|
const centerX = contentLeft + contentWidth / 2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: centerX,
|
||||||
|
right: 'auto',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
width: 'max-content',
|
||||||
|
maxWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { computeFloatingPlayerBarStyle } from './computeFloatingPlayerBarStyle';
|
||||||
|
|
||||||
/** Computes the floating player-bar position based on the current sidebar +
|
/** Computes the floating player-bar position based on the current sidebar +
|
||||||
* queue panel widths. Returns an inline-style object (left/right/width); only
|
* queue panel widths. Returns an inline-style object (left/right/width); only
|
||||||
@@ -14,17 +15,13 @@ export function useFloatingPlayerBar(
|
|||||||
if (!floatingPlayerBar) return;
|
if (!floatingPlayerBar) return;
|
||||||
|
|
||||||
const updatePosition = () => {
|
const updatePosition = () => {
|
||||||
const sidebar = document.querySelector('.sidebar') as HTMLElement;
|
const sidebar = document.querySelector('.sidebar') as HTMLElement | null;
|
||||||
const queue = document.querySelector('.queue-panel') as HTMLElement;
|
const queue = document.querySelector('.queue-panel') as HTMLElement | null;
|
||||||
|
|
||||||
const leftOffset = sidebar ? sidebar.getBoundingClientRect().right : 0;
|
const sidebarRight = sidebar ? sidebar.getBoundingClientRect().right : 0;
|
||||||
const rightOffset = queue ? window.innerWidth - queue.getBoundingClientRect().left : 0;
|
const queueLeft = queue ? queue.getBoundingClientRect().left : null;
|
||||||
|
|
||||||
setFloatingStyle({
|
setFloatingStyle(computeFloatingPlayerBarStyle(sidebarRight, queueLeft, window.innerWidth));
|
||||||
left: leftOffset + 24,
|
|
||||||
right: rightOffset + 24,
|
|
||||||
width: 'auto',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updatePosition();
|
updatePosition();
|
||||||
|
|||||||
@@ -40,3 +40,7 @@
|
|||||||
"sidebar main queue";
|
"sidebar main queue";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-shell.floating-player .resizer {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,19 +21,18 @@
|
|||||||
.player-bar.floating {
|
.player-bar.floating {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
/* left/right handled dynamically by JS */
|
/* left / max-width set by useFloatingPlayerBar — center + shrink-wrap */
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
width: auto;
|
width: max-content;
|
||||||
min-width: 100px;
|
max-width: min(100%, calc(100vw - 48px));
|
||||||
max-width: none;
|
|
||||||
grid-area: unset;
|
grid-area: unset;
|
||||||
padding: 0 24px 0 8px;
|
padding: 0 24px 0 8px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
background: var(--bg-player);
|
background: var(--bg-player);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 8px 32px rgba(0, 0, 0, 0.8),
|
0 8px 32px rgba(0, 0, 0, 0.45),
|
||||||
0 0 0 1px rgba(255, 255, 255, 0.05),
|
0 0 0 1px rgba(255, 255, 255, 0.05),
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user