Mobile: horizontal-scroll tables + tab fade (Commit 4/4)

- Wrap wide tables (admin, settings, reports) in .table-wrap with
  min-width on the table so they scroll horizontally on narrow screens
  instead of collapsing rows.
- Add global .table-wrap { overflow-x: auto } utility.
- Mobile: add mask-image fade on .tabs right edge to hint scrollability
  when tabs overflow; flex-shrink:0 on .tab keeps labels intact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ScreenTinker 2026-04-21 15:56:01 -05:00
parent b45d81cfaa
commit 06d3e93e21
4 changed files with 28 additions and 7 deletions

View file

@ -878,6 +878,12 @@ body {
line-height: 1.4; line-height: 1.4;
} }
/* Table wrapper: enables horizontal scroll when table min-width exceeds viewport */
.table-wrap {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Mobile hamburger toggle */ /* Mobile hamburger toggle */
.mobile-menu-btn { .mobile-menu-btn {
display: none; display: none;
@ -924,8 +930,13 @@ body {
.remote-container { flex-direction: column; } .remote-container { flex-direction: column; }
.remote-controls { width: 100%; flex-direction: row; flex-wrap: wrap; } .remote-controls { width: 100%; flex-direction: row; flex-wrap: wrap; }
.modal { width: 95vw; max-height: 90vh; overflow-y: auto; } .modal { width: 95vw; max-height: 90vh; overflow-y: auto; }
.tabs { overflow-x: auto; } .tabs {
.tab { white-space: nowrap; } overflow-x: auto;
-webkit-overflow-scrolling: touch;
mask-image: linear-gradient(to right, black calc(100% - 24px), transparent 100%);
-webkit-mask-image: linear-gradient(to right, black calc(100% - 24px), transparent 100%);
}
.tab { white-space: nowrap; flex-shrink: 0; }
/* Dashboard stats stack to single column */ /* Dashboard stats stack to single column */
.dash-stats-row { flex-direction: column; } .dash-stats-row { flex-direction: column; }

View file

@ -48,7 +48,8 @@ async function loadUsers() {
const [users, plans] = await Promise.all([API('/auth/users'), fetch('/api/subscription/plans').then(r => r.json())]); const [users, plans] = await Promise.all([API('/auth/users'), fetch('/api/subscription/plans').then(r => r.json())]);
el.innerHTML = ` el.innerHTML = `
<table style="width:100%;border-collapse:collapse;font-size:13px"> <div class="table-wrap">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:720px">
<thead><tr style="border-bottom:1px solid var(--border)"> <thead><tr style="border-bottom:1px solid var(--border)">
<th style="padding:8px;text-align:left;color:var(--text-muted)">User</th> <th style="padding:8px;text-align:left;color:var(--text-muted)">User</th>
<th style="padding:8px;text-align:left;color:var(--text-muted)">Auth</th> <th style="padding:8px;text-align:left;color:var(--text-muted)">Auth</th>
@ -82,6 +83,7 @@ async function loadUsers() {
`).join('')} `).join('')}
</tbody> </tbody>
</table> </table>
</div>
<p style="color:var(--text-muted);font-size:11px;margin-top:8px">${users.length} total users</p> <p style="color:var(--text-muted);font-size:11px;margin-top:8px">${users.length} total users</p>
`; `;
@ -126,7 +128,8 @@ async function loadPlans() {
try { try {
const plans = await fetch('/api/subscription/plans').then(r => r.json()); const plans = await fetch('/api/subscription/plans').then(r => r.json());
el.innerHTML = ` el.innerHTML = `
<table style="width:100%;border-collapse:collapse;font-size:13px"> <div class="table-wrap">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:500px">
<thead><tr style="border-bottom:1px solid var(--border)"> <thead><tr style="border-bottom:1px solid var(--border)">
<th style="padding:8px;text-align:left;color:var(--text-muted)">Plan</th> <th style="padding:8px;text-align:left;color:var(--text-muted)">Plan</th>
<th style="padding:8px;text-align:right;color:var(--text-muted)">Devices</th> <th style="padding:8px;text-align:right;color:var(--text-muted)">Devices</th>
@ -146,6 +149,7 @@ async function loadPlans() {
`).join('')} `).join('')}
</tbody> </tbody>
</table> </table>
</div>
`; `;
} catch (err) { el.innerHTML = `<p style="color:var(--danger)">${esc(err.message)}</p>`; } } catch (err) { el.innerHTML = `<p style="color:var(--danger)">${esc(err.message)}</p>`; }
} }

View file

@ -103,7 +103,8 @@ export async function render(container) {
<!-- Top Content --> <!-- Top Content -->
<div class="settings-section" style="margin-bottom:20px"> <div class="settings-section" style="margin-bottom:20px">
<h3 style="font-size:14px;margin-bottom:12px">Top Content</h3> <h3 style="font-size:14px;margin-bottom:12px">Top Content</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px"> <div class="table-wrap">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:460px">
<thead><tr style="border-bottom:1px solid var(--border)"> <thead><tr style="border-bottom:1px solid var(--border)">
<th style="padding:8px;text-align:left;color:var(--text-muted)">Content</th> <th style="padding:8px;text-align:left;color:var(--text-muted)">Content</th>
<th style="padding:8px;text-align:right;color:var(--text-muted)">Plays</th> <th style="padding:8px;text-align:right;color:var(--text-muted)">Plays</th>
@ -122,11 +123,13 @@ export async function render(container) {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<!-- By Device --> <!-- By Device -->
<div class="settings-section"> <div class="settings-section">
<h3 style="font-size:14px;margin-bottom:12px">By Device</h3> <h3 style="font-size:14px;margin-bottom:12px">By Device</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px"> <div class="table-wrap">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:400px">
<thead><tr style="border-bottom:1px solid var(--border)"> <thead><tr style="border-bottom:1px solid var(--border)">
<th style="padding:8px;text-align:left;color:var(--text-muted)">Device</th> <th style="padding:8px;text-align:left;color:var(--text-muted)">Device</th>
<th style="padding:8px;text-align:right;color:var(--text-muted)">Plays</th> <th style="padding:8px;text-align:right;color:var(--text-muted)">Plays</th>
@ -143,6 +146,7 @@ export async function render(container) {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
`; `;
// Render daily chart // Render daily chart

View file

@ -333,7 +333,8 @@ async function loadUsers() {
const currentUser = JSON.parse(localStorage.getItem('user') || '{}'); const currentUser = JSON.parse(localStorage.getItem('user') || '{}');
el.innerHTML = ` el.innerHTML = `
<table style="width:100%;border-collapse:collapse;font-size:13px"> <div class="table-wrap">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:520px">
<thead> <thead>
<tr style="border-bottom:1px solid var(--border);text-align:left"> <tr style="border-bottom:1px solid var(--border);text-align:left">
<th style="padding:8px 12px;color:var(--text-muted);font-weight:500">User</th> <th style="padding:8px 12px;color:var(--text-muted);font-weight:500">User</th>
@ -368,6 +369,7 @@ async function loadUsers() {
`).join('')} `).join('')}
</tbody> </tbody>
</table> </table>
</div>
<p style="color:var(--text-muted);font-size:11px;margin-top:12px">${users.length} user(s) registered</p> <p style="color:var(--text-muted);font-size:11px;margin-top:12px">${users.length} user(s) registered</p>
`; `;