fix(soundtouch-web): surface play button on TuneIn program rows

The Preact TuneInBrowser hid the play affordance whenever an item
also had a navigate link. TuneIn programs have BOTH (drill into
episodes + play latest episode, after backend PR #317), so the
button never appeared on program rows — only the chevron.

Old vanilla UI showed both. Restored:

- navigate(item) keeps its current behaviour (path wins for row
  clicks, falls through to play if there's no path) — that lets
  pure-leaf items (stations) still play on whole-row click.
- New explicit .tunein-play-btn rendered conditionally when an item
  has BOTH a navigate link and a playback link. Stops event
  propagation so clicking it triggers play (device picker overlay)
  instead of bubbling to the row's navigate handler.
- CSS: pill-shaped 32px button using the same --accent / --text-dim
  tokens the rest of the UI uses; hover state swaps to --accent /
  --accent-fg to avoid same-on-same contrast in either theme.

The chevron stays as the row's "drill in" indicator for any
navigable item, including programs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-18 22:34:26 +02:00
co-authored by Claude Opus 4.7
parent 22f999edaf
commit cd387888aa
2 changed files with 32 additions and 0 deletions
@@ -408,6 +408,19 @@ img { display: block; max-width: 100%; }
.tunein-item-name { display: block; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tunein-item-desc { display: block; font-size: .75rem; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tunein-item-arrow { color: var(--text-dim); font-size: .9rem; flex-shrink: 0; }
.tunein-play-btn {
background: transparent;
border: 1px solid var(--text-dim);
color: var(--text);
border-radius: 999px;
width: 32px; height: 32px;
display: inline-flex; align-items: center; justify-content: center;
cursor: pointer; flex-shrink: 0;
font-size: .85rem; line-height: 1;
padding: 0;
transition: background .15s, border-color .15s;
}
.tunein-play-btn:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
/* ── Device picker overlay ───────────────────────────────────────────────── */
.overlay {
@@ -114,6 +114,15 @@ export function TuneInBrowser({ devices }) {
<ul class="tunein-list">
${items.map((item, i) => {
const isNav = !!navPath(item);
const play = playbackInfo(item);
// Programs have BOTH a navigate link (drill into
// episodes) and a playback link (play latest
// episode). Surface both: a dedicated play button
// (stopPropagation so it doesn't trigger the row's
// navigate) plus the chevron. Pure-leaf items
// (stations) fall through to the single ▶ arrow,
// because the whole row is already a play target.
const showPlayBtn = isNav && play;
return html`
<li key=${item._links?.self?.href || i} class="tunein-item" onClick=${() => navigate(item)}>
${item.imageUrl && html`<img class="tunein-thumb" src=${item.imageUrl} alt="" />`}
@@ -121,6 +130,16 @@ export function TuneInBrowser({ devices }) {
<span class="tunein-item-name">${item.name}</span>
${item.subtitle && html`<span class="tunein-item-desc">${item.subtitle}</span>`}
</div>
${showPlayBtn && html`
<button
class="tunein-play-btn"
title="Play"
onClick=${(e) => {
e.stopPropagation();
setPendingPlay({ ...play, name: item.name, image: item.imageUrl });
}}
></button>
`}
<span class="tunein-item-arrow">${isNav ? '' : '▶'}</span>
</li>
`;