ux: hamburger + slide-out side drawer (not a kebab popover)

Replace the <details>-based kebab popover with a proper Material-
style navigation drawer:

- Hamburger button (menu icon) at top-left.
- <aside> drawer slides in from the left via transform: translateX().
  width: min(85vw, 320px). Backdrop overlay behind it.
- Close paths: X button in the drawer header, backdrop click, Esc.
- Padding honours Android safe-area insets on all four sides.
- Model selector + label live inside the drawer; future per-app
  settings can move here too.
This commit is contained in:
João Pedro Battistella Nadas
2026-06-09 20:02:11 +02:00
parent bde042a459
commit 1fffe8b7e1
2 changed files with 108 additions and 27 deletions

View File

@@ -51,44 +51,94 @@
margin-bottom: 1rem;
}
/* Kebab menu (top-left) housing the Model dropdown — keeps the main
controls focused on the From/To pair. */
.kebab {
/* Hamburger menu (top-left) → slide-out side drawer that houses the
Model selector (and future per-app settings). */
.drawer-toggle {
position: fixed;
top: calc(1rem + env(safe-area-inset-top, 0px));
left: calc(1rem + env(safe-area-inset-left, 0px));
z-index: 50;
}
.kebab > summary {
list-style: none;
width: 40px;
height: 40px;
padding: 0;
margin: 0;
border-radius: 50%;
border: 1px solid var(--border);
background: var(--panel);
color: var(--fg);
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}
.kebab > summary::-webkit-details-marker { display: none; }
.kebab > summary::marker { display: none; }
.kebab > summary .material-symbols-outlined { font-size: 22px; }
.kebab[open] > summary { border-color: var(--accent); color: var(--accent); }
.kebab-panel {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 0.75rem;
min-width: 220px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
.drawer-toggle .material-symbols-outlined { font-size: 22px; }
.drawer-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
z-index: 200;
}
.kebab-panel label {
.drawer-backdrop.open { opacity: 1; pointer-events: auto; }
.drawer {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: min(85vw, 320px);
background: var(--bg);
border-right: 1px solid var(--border);
padding:
max(1rem, env(safe-area-inset-top, 0px))
1.25rem
max(1rem, env(safe-area-inset-bottom, 0px))
max(1.25rem, env(safe-area-inset-left, 0px));
transform: translateX(-100%);
transition: transform 0.25s ease;
z-index: 201;
box-shadow: 4px 0 14px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
gap: 1rem;
overflow-y: auto;
}
.drawer.open { transform: translateX(0); }
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.drawer-header h2 {
margin: 0;
font-size: 1rem;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--muted);
text-transform: uppercase;
}
.drawer-close {
background: transparent;
border: none;
color: var(--muted);
width: 32px;
height: 32px;
padding: 0;
margin: 0;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.drawer-close:hover { color: var(--fg); background: var(--panel); }
.drawer-close .material-symbols-outlined { font-size: 20px; }
.drawer label {
display: block;
font-size: 0.7rem;
letter-spacing: 0.06em;
@@ -432,13 +482,22 @@
</style>
</head>
<body>
<details class="kebab">
<summary aria-label="Settings" title="Settings"><span class="material-symbols-outlined">more_vert</span></summary>
<div class="kebab-panel">
<button type="button" id="drawer-toggle" class="drawer-toggle" aria-label="Open menu" title="Menu">
<span class="material-symbols-outlined">menu</span>
</button>
<div id="drawer-backdrop" class="drawer-backdrop"></div>
<aside id="drawer" class="drawer" aria-hidden="true">
<div class="drawer-header">
<h2>Settings</h2>
<button type="button" id="drawer-close" class="drawer-close" aria-label="Close menu" title="Close">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<div>
<label for="model">Model</label>
<select id="model"></select>
</div>
</details>
</aside>
<div class="wrap">
<h1>finrod</h1>
<div class="sub">Translation via Ollama · loremaster of tongues</div>
@@ -1189,6 +1248,28 @@
Vim.mapCommand("L", "action", "finrodFocusNext", {}, { context: "normal" });
}
// Side drawer (Settings) wiring — opens from the top-left hamburger.
const drawerEl = document.getElementById("drawer");
const drawerBackdrop = document.getElementById("drawer-backdrop");
const drawerToggle = document.getElementById("drawer-toggle");
const drawerCloseBtn = document.getElementById("drawer-close");
function openDrawer() {
drawerEl.classList.add("open");
drawerBackdrop.classList.add("open");
drawerEl.setAttribute("aria-hidden", "false");
}
function closeDrawer() {
drawerEl.classList.remove("open");
drawerBackdrop.classList.remove("open");
drawerEl.setAttribute("aria-hidden", "true");
}
drawerToggle.addEventListener("click", openDrawer);
drawerCloseBtn.addEventListener("click", closeDrawer);
drawerBackdrop.addEventListener("click", closeDrawer);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && drawerEl.classList.contains("open")) closeDrawer();
});
fillLangs();
loadModels().then(() => refreshLoaded(true));
</script>