Fix horizontal scrolling in admin UI
build / build-linux-amd64 (push) Successful in 1m56s

This commit is contained in:
2026-05-25 00:19:14 +02:00
parent bc61ec6046
commit d941ae9739
3 changed files with 43 additions and 12 deletions
+27
View File
@@ -262,6 +262,33 @@
});
}
window.devicesPageSize = devicesPageSize;
// The devices table lives inside an `overflow-x-auto` wrapper so wide
// column sets get a horizontal scrollbar instead of pushing the page
// out. CSS forces overflow-y to auto on the same axis, which would
// clip the per-row action popover (a `<details>` > `<div>` inside a
// <td>). On toggle we flip the popover to `position: fixed` and pin
// it to the summary's viewport rect so it escapes the scroll context.
// Inline `ontoggle=` is preserved across htmx swaps without re-binding.
function actionMenuToggle(details) {
const popover = details.querySelector('[data-action-menu]');
if (!popover) return;
if (!details.open) {
popover.style.position = '';
popover.style.top = '';
popover.style.left = '';
popover.style.right = '';
return;
}
const summary = details.querySelector('summary');
if (!summary) return;
const rect = summary.getBoundingClientRect();
popover.style.position = 'fixed';
popover.style.top = rect.bottom + 4 + 'px';
popover.style.right = (window.innerWidth - rect.right - 8) + 'px';
popover.style.left = 'auto';
}
window.actionMenuToggle = actionMenuToggle;
</script>
</body>
</html>
+8 -6
View File
@@ -562,12 +562,14 @@ async fn render_table(
.map_err(|e| ApiError::Internal(e.to_string()))?;
let now = chrono::Utc::now();
let mut s = String::new();
// No `overflow-hidden` on the table wrapper: the per-row action menu is
// an absolutely-positioned `<details>` popover inside a <td>, and the
// wrapper's clipping was hiding the bottom half of the menu.
// `overflow-x-auto` on the inner wrapper gives the wide table a
// horizontal scrollbar instead of pushing past the viewport. The
// action-menu popover compensates for the implicit `overflow-y: auto`
// (CSS spec) by switching to `position: fixed` on toggle — see
// `actionMenuToggle` in admin_ui/index.html.
let _ = write!(
s,
r##"<div class="rounded-md border border-slate-800 bg-slate-900">
r##"<div class="rounded-md border border-slate-800 bg-slate-900 overflow-x-auto">
<table class="w-full text-sm">
<thead class="text-xs uppercase text-slate-500 bg-slate-950">
<tr>
@@ -1065,9 +1067,9 @@ fn render_device_row(
s,
r##"
<td class="px-3 py-2">
<details class="text-right relative">
<details class="text-right relative" ontoggle="actionMenuToggle(this)">
<summary class="cursor-pointer list-none text-xs text-slate-400 hover:text-slate-200 select-none">···</summary>
<div class="absolute right-2 mt-1 z-10 w-56 bg-slate-900 border border-slate-700 rounded shadow-lg p-2 space-y-1 text-left">
<div data-action-menu class="absolute right-2 mt-1 z-50 w-56 bg-slate-900 border border-slate-700 rounded shadow-lg p-2 space-y-1 text-left">
<a class="block w-full text-left px-2 py-1 text-xs text-sky-300 hover:bg-sky-900/40 rounded"
href="/admin/connect/{id}" target="_blank" rel="noopener"
data-confirm="{confirm_connect}"
+8 -6
View File
@@ -856,12 +856,14 @@ async fn render_table(
.await
.unwrap_or_default();
let mut s = String::new();
// No `overflow-hidden` on the table wrapper: the per-row action menu is
// an absolutely-positioned `<details>` popover inside a <td>, and the
// wrapper's clipping was hiding the bottom half of the menu.
// `overflow-x-auto` on the inner wrapper gives the wide table a
// horizontal scrollbar instead of pushing past the viewport. The
// action-menu popover compensates for the implicit `overflow-y: auto`
// (CSS spec) by switching to `position: fixed` on toggle — see
// `actionMenuToggle` in admin_ui/index.html.
let _ = write!(
s,
r##"<div class="rounded-md border border-slate-800 bg-slate-900">
r##"<div class="rounded-md border border-slate-800 bg-slate-900 overflow-x-auto">
<table class="w-full text-sm">
<thead class="text-xs uppercase text-slate-500 bg-slate-950">
<tr>
@@ -1190,9 +1192,9 @@ fn render_user_row(
s,
r##"
<td class="px-3 py-2">
<details class="text-right relative">
<details class="text-right relative" ontoggle="actionMenuToggle(this)">
<summary class="cursor-pointer list-none text-xs text-slate-400 hover:text-slate-200 select-none">···</summary>
<div class="absolute right-2 mt-1 z-10 w-64 bg-slate-900 border border-slate-700 rounded shadow-lg p-2 space-y-1 text-left">
<div data-action-menu class="absolute right-2 mt-1 z-50 w-64 bg-slate-900 border border-slate-700 rounded shadow-lg p-2 space-y-1 text-left">
<form class="space-y-1" hx-post="/admin/pages/users/{id}/update-info?page={page}{q_param}" hx-target="#users-region" hx-swap="innerHTML">
<input name="display_name" value="{display_name}" placeholder="{ph_dn}" class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs"/>
<input name="email" type="email" value="{email}" placeholder="{ph_email}" class="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs"/>