cbd-web: give the panes a tab control
Below 700px the panes stack and only the focused one is open, but switching was bound to `Tab` and to a tap on the collapsed strip — and a phone has no `Tab` key. The top bar now carries `library` / `queue` buttons that set the focus, shown at every width: on desktop they double as the readout of which pane the keys go to, which the inset border says only faintly. The collapsed strip is the pane's toolbar with all but the title clipped, so its buttons were the only thing a tap on it could land on — tapping to switch panes could hit the library's "play" and replace the queue. Hidden there now. The connection line truncates rather than pushing the tabs off a narrow screen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1db88e0f5a
commit
8e3c210864
|
|
@ -807,9 +807,30 @@ fn TopBar(store: Store) -> impl IntoView {
|
||||||
save_pref("theme", next);
|
save_pref("theme", next);
|
||||||
apply_theme(next);
|
apply_theme(next);
|
||||||
};
|
};
|
||||||
|
// What `Tab` does, as a tap target. On a phone the panes stack and only
|
||||||
|
// the focused one is open, so switching panes has to be reachable without
|
||||||
|
// a keyboard. It stays on screen at every width, where it doubles as the
|
||||||
|
// readout of which pane the keys are going to.
|
||||||
|
let tab = move |target: Focus, label: &'static str| {
|
||||||
|
view! {
|
||||||
|
<button
|
||||||
|
class="ghost"
|
||||||
|
class:active=move || store.focus.get() == target
|
||||||
|
aria-pressed=move || (store.focus.get() == target).to_string()
|
||||||
|
title="switch pane (Tab)"
|
||||||
|
on:click=move |_| store.focus.set(target)
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
};
|
||||||
view! {
|
view! {
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<span class="brand">"crabidy"</span>
|
<span class="brand">"crabidy"</span>
|
||||||
|
<span class="tabs">
|
||||||
|
{tab(Focus::Library, "library")}
|
||||||
|
{tab(Focus::Queue, "queue")}
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class="conn"
|
class="conn"
|
||||||
class:offline=move || !store.connected.get()
|
class:offline=move || !store.connected.get()
|
||||||
|
|
|
||||||
|
|
@ -126,12 +126,30 @@ input {
|
||||||
& .conn {
|
& .conn {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--fg-dim);
|
color: var(--fg-dim);
|
||||||
|
/* Truncate rather than push the pane tabs off a narrow screen. */
|
||||||
|
min-inline-size: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&.offline {
|
&.offline {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.1rem;
|
||||||
|
padding: 0.1rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
/* Comfortable for a thumb — this is the phone's only pane switch. */
|
||||||
|
& button {
|
||||||
|
padding: 0.3rem 0.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& .topbar-actions {
|
& .topbar-actions {
|
||||||
margin-inline-start: auto;
|
margin-inline-start: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -462,8 +480,8 @@ input {
|
||||||
/* ---- phone ------------------------------------------------------------- */
|
/* ---- phone ------------------------------------------------------------- */
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
/* One pane at a time; Tab (or tapping a pane edge) switches — the
|
/* One pane at a time; the topbar tabs switch, as does Tab or a tap on
|
||||||
unfocused pane collapses to a slim strip acting as its tab. */
|
the slim strip the unfocused pane collapses to. */
|
||||||
.panes {
|
.panes {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
|
|
@ -475,6 +493,13 @@ input {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-block-start: 1px solid var(--border);
|
border-block-start: 1px solid var(--border);
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
||||||
|
/* Only the toolbar survives the collapse, so its buttons are all a
|
||||||
|
tap on the strip can land on — and hitting "play" there replaces
|
||||||
|
the queue when the user meant to switch panes. Leave the title. */
|
||||||
|
& .toolbar button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.transport {
|
.transport {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,12 @@ buttons in
|
||||||
the transport bar, and the progress bar is clickable — a click seeks to
|
the transport bar, and the progress bar is clickable — a click seeks to
|
||||||
that point in the track. The `/` live filter is TUI-only for now.
|
that point in the track. The `/` live filter is TUI-only for now.
|
||||||
|
|
||||||
|
A pair of `library` / `queue` tabs in the top bar switches panes and shows
|
||||||
|
which one the keys go to — the same thing `Tab` does, reachable without a
|
||||||
|
keyboard. It matters on a phone: below 700px the two panes cannot sit side
|
||||||
|
by side, so the unfocused one collapses to a title strip (tapping it also
|
||||||
|
switches) and the tabs are how you get back.
|
||||||
|
|
||||||
The volume slider spans the server's whole accepted range, ending at 110%
|
The volume slider spans the server's whole accepted range, ending at 110%
|
||||||
— the server clamps there, so a slider that went further would have a
|
— the server clamps there, so a slider that went further would have a
|
||||||
dead stretch at its right end. Its tooltip reports the current level as a
|
dead stretch at its right end. Its tooltip reports the current level as a
|
||||||
|
|
|
||||||
|
|
@ -1607,3 +1607,20 @@ headroom, and more gain risks clipping — so the slider was the side to move.
|
||||||
|
|
||||||
The tooltip also reports the level as a percentage now, which is the web
|
The tooltip also reports the level as a percentage now, which is the web
|
||||||
counterpart of the TUI's `Volume: 85%`.
|
counterpart of the TUI's `Volume: 85%`.
|
||||||
|
|
||||||
|
## Web pane tabs (2026-07-27)
|
||||||
|
|
||||||
|
Below 700px the panes cannot sit side by side, so the layout already stacked
|
||||||
|
them and collapsed the unfocused one to a title strip. Switching, though, was
|
||||||
|
only bound to `Tab` and to a tap on that strip — awkward on a phone, where
|
||||||
|
there is no `Tab` key.
|
||||||
|
|
||||||
|
The top bar now carries a `library` / `queue` pair of buttons that set the
|
||||||
|
focus. They are shown at every width rather than behind a media query: on
|
||||||
|
desktop they double as the readout of which pane the keys go to, which the
|
||||||
|
inset border says only faintly.
|
||||||
|
|
||||||
|
The collapsed strip is the pane's toolbar with everything but the title
|
||||||
|
clipped away, so its buttons were the only thing a tap on it could land on —
|
||||||
|
tapping to switch panes could hit the library's "play" and replace the queue.
|
||||||
|
Those buttons are hidden on the collapsed pane now.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue