[feat](trx-frontend-http): give digital modes a decoder sidebar
CI / lint (push) Successful in 2m18s
CI / test (push) Successful in 8m15s
CI / frontend (push) Failing after 32s
CI / reuse (push) Successful in 3s

Thirteen decoders in a horizontal strip needed a scroller on anything
but a wide window, and the open one was marked by a single underline
among thirteen.  They are a list down the left now, all visible at once,
each keeping the state dot it already carried, with the panel for the
selected one filling the rest of the width.

No script changed: the sub-tab wiring, the aria roles, the decoder
picker and the state-dot observers all work on the same markup, so this
is layout only.

Below 760px the sidebar gives way to the picker that already existed
there.  That path needed align-content: the tab panel fills the page
height and a grid stretches its rows to match, which handed the picker a
218px row and left a 189px gap under it.

The tab icon was the signal-strength bars, which is what the S-meter
shows two rows above it; a pulse train says digital modes instead.

Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
sjg
2026-08-04 08:01:22 +02:00
parent 9f495021f0
commit c527f20a88
3 changed files with 92 additions and 2 deletions
@@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-home" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.8 7 8 2.9 13.2 7"/><path d="M4.3 5.9V13h7.4V5.9"/><path d="M6.8 13V9.3h2.4V13"/></symbol>
<symbol id="icon-bookmark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2h8v12l-4-2.5L4 14V2z"/></symbol>
<symbol id="icon-signal" viewBox="0 0 16 16" fill="currentColor"><rect x="1" y="11" width="2.5" height="4" rx="0.5"/><rect x="4.75" y="8" width="2.5" height="7" rx="0.5"/><rect x="8.5" y="5" width="2.5" height="10" rx="0.5"/><rect x="12.25" y="2" width="2.5" height="13" rx="0.5"/></symbol>
<symbol id="icon-digital" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 11.5h2.6V4.5h3.2v7h3.2v-7h3.2v7H15"/></symbol>
<symbol id="icon-map" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2a4 4 0 0 1 4 4c0 3-4 8-4 8S4 9 4 6a4 4 0 0 1 4-4z"/><circle cx="8" cy="6" r="1.2" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon-stats" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 14h12"/><rect x="3" y="8" width="2" height="6" rx="0.4" fill="currentColor" stroke="none" opacity="0.6"/><rect x="7" y="5" width="2" height="9" rx="0.4" fill="currentColor" stroke="none" opacity="0.75"/><rect x="11" y="2" width="2" height="12" rx="0.4" fill="currentColor" stroke="none" opacity="0.9"/></symbol>
<symbol id="icon-record" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><circle cx="8" cy="8" r="2.5" fill="currentColor" stroke="none"/></symbol>
@@ -53,7 +53,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<span class="tab-label">Bookmarks</span>
</button>
<button class="tab" data-tab="digital-modes">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-signal"/></svg>
<svg class="tab-icon" aria-hidden="true"><use href="#icon-digital"/></svg>
<span class="tab-label">Digital modes</span>
</button>
<button class="tab" data-tab="map">
@@ -2076,6 +2076,61 @@ small { color: var(--text-muted); }
}
.sub-tab-bar { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 0.75rem; overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* ── Digital modes: sidebar + panel ───────────────────────────────────
Thirteen decoders in a horizontal strip meant a scroller on anything but a
wide window, and which one was open was a single underline in a row of
thirteen. As a list down the side every decoder is visible at once, each
with the state dot it already carried, and the panel gets the full width. */
#tab-digital-modes {
display: grid;
grid-template-columns: minmax(8.5rem, 11rem) minmax(0, 1fr);
gap: 1rem;
align-items: start;
}
#tab-digital-modes > .sub-tab-bar {
flex-direction: column;
align-items: stretch;
gap: 0.12rem;
margin-bottom: 0;
padding-right: 0.85rem;
border-bottom: none;
border-right: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
overflow-x: hidden;
/* Stays put while a long decode list scrolls past it. */
position: sticky;
top: 0.5rem;
}
#tab-digital-modes > .sub-tab-bar .sub-tab {
display: flex;
align-items: center;
gap: 0.4rem;
width: 100%;
padding: 0.42rem 0.6rem;
border: 1px solid transparent;
border-radius: var(--radius-md);
text-align: left;
font-size: 0.88rem;
transition: color var(--dur-fast) var(--ease-standard),
background-color var(--dur-fast) var(--ease-standard),
border-color var(--dur-fast) var(--ease-standard);
}
#tab-digital-modes > .sub-tab-bar .sub-tab .decoder-state-dot {
margin-left: auto;
}
#tab-digital-modes > .sub-tab-bar .sub-tab:hover:not(.active) {
color: var(--text);
background: color-mix(in srgb, var(--btn-bg) 55%, transparent);
}
#tab-digital-modes > .sub-tab-bar .sub-tab.active {
border-color: color-mix(in srgb, var(--accent-green) 50%, var(--border-light));
background: color-mix(in srgb, var(--accent-green) 12%, transparent);
color: var(--accent-text);
}
#tab-digital-modes > .sub-tab-panel {
grid-column: 2;
grid-row: 1;
min-width: 0;
}
.sub-tab { flex-shrink: 0; background: transparent; border: none; border-bottom: 2px solid transparent; border-radius: 0; padding: 0.35rem 0.75rem; color: var(--text-muted); cursor: pointer; font-size: 0.85rem; height: auto; }
.sub-tab.active { border-bottom-color: var(--accent-green); color: var(--accent-green); font-weight: 600; }
.sub-tab:hover:not(.active) { color: var(--text); }
@@ -3722,7 +3777,15 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay {
font-weight: 600;
box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 5%, transparent);
}
/* The picker replaces the sidebar here, so the panel takes the full width.
align-content keeps the two rows at their own height: the panel fills the
tab, and stretching would otherwise hand the picker a third of it. */
#tab-digital-modes {
grid-template-columns: minmax(0, 1fr);
align-content: start;
}
#tab-digital-modes > .sub-tab-bar { display: none; }
#tab-digital-modes > .sub-tab-panel { grid-column: 1; grid-row: auto; }
.toast-region { bottom: calc(5.7rem + env(safe-area-inset-bottom)); }
.top-bar-actions {
width: 100%;