Hide App navigation on a narrow screen until the user opens it.

A width at or below Nextcloud's 1024px breakpoint keeps the tree off the page so the content stays usable, and the navigation button brings it back.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-29 18:56:44 +02:00
co-authored by Cursor
parent 4e05fb1c6a
commit fad9806585
5 changed files with 162 additions and 5 deletions
+92 -3
View File
@@ -170,6 +170,7 @@ var shellTmpl = template.Must(template.New("shell").Parse(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.Title}}</title>
{{if .Theme}}
<link rel="stylesheet" href="/apps/theming/css/default.css">
@@ -219,7 +220,32 @@ var shellTmpl = template.Must(template.New("shell").Parse(`<!DOCTYPE html>
}
body { margin: 0; font: 15px/1.4 var(--font-face, system-ui, sans-serif); }
#app-nav { display: flex; flex-direction: column; min-height: 100vh; background: transparent; }
#nav-header { padding: 8px 12px; border-bottom: 1px solid var(--color-border); }
#nav-header {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px;
border-bottom: 1px solid var(--color-border);
}
#nav-header-text { flex: 1; min-width: 0; }
#nav-toggle {
display: none;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
margin: 0;
padding: 0;
border: 0;
flex: none;
background: transparent;
color: inherit;
cursor: pointer;
border-radius: var(--border-radius-element);
}
#nav-toggle:hover { background: var(--color-background-hover); }
#nav-toggle svg { width: 20px; height: 20px; }
#nav-backdrop { display: none; }
#app-nav-body { display: flex; flex: 1; min-height: 0; }
nav.app-navigation {
width: var(--navigation-width, 300px);
@@ -294,14 +320,47 @@ var shellTmpl = template.Must(template.New("shell").Parse(`<!DOCTYPE html>
li[data-expanded="false"] > .app-navigation-entry__children { display: none; }
li[data-expanded="false"] button.fold svg { transform: rotate(-90deg); }
#nav-missing { margin: 0 0 1rem; }
@media (max-width: 1024px) {
#nav-header {
position: sticky;
top: 0;
z-index: 2100;
background-color: var(--color-main-background);
}
#nav-toggle { display: flex; }
nav.app-navigation {
display: none;
position: fixed;
z-index: 2000;
top: var(--nav-header-offset, 0px);
bottom: 0;
inset-inline-start: 0;
width: min(var(--navigation-width, 300px), 85vw);
}
body.nav-open nav.app-navigation { display: block; }
body.nav-open #nav-backdrop {
display: block;
position: fixed;
z-index: 1990;
top: var(--nav-header-offset, 0px);
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.45);
}
}
</style>
</head>
<body data-selected="{{.Selected}}" data-address="{{.Address}}">
{{.Dialog}}
<div id="app-nav">
<div id="nav-header">{{.Header}}</div>
<div id="nav-header">
<button type="button" id="nav-toggle" aria-controls="app-navigation" aria-expanded="false" aria-label="Open navigation"><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/></svg></button>
<div id="nav-header-text">{{.Header}}</div>
</div>
<div id="nav-backdrop"></div>
<div id="app-nav-body">
<nav class="app-navigation" aria-label="App navigation">
<nav id="app-navigation" class="app-navigation" aria-label="App navigation">
<ul class="app-navigation-list">{{template "items" .Items}}</ul>
</nav>
<main>
@@ -325,6 +384,36 @@ var shellTmpl = template.Must(template.New("shell").Parse(`<!DOCTYPE html>
btn.setAttribute("aria-expanded", open ? "false" : "true");
});
});
var toggle = document.getElementById("nav-toggle");
var backdrop = document.getElementById("nav-backdrop");
function placeNav() {
var header = document.getElementById("nav-header");
if (!header) return;
document.documentElement.style.setProperty("--nav-header-offset", header.offsetHeight + "px");
}
function setNavOpen(open) {
placeNav();
document.body.classList.toggle("nav-open", open);
if (!toggle) return;
toggle.setAttribute("aria-expanded", open ? "true" : "false");
toggle.setAttribute("aria-label", open ? "Close navigation" : "Open navigation");
}
placeNav();
window.addEventListener("resize", placeNav);
var narrow = window.matchMedia("(max-width: 1024px)");
if (narrow.addEventListener) narrow.addEventListener("change", placeNav);
if (toggle) {
toggle.addEventListener("click", function () {
setNavOpen(!document.body.classList.contains("nav-open"));
});
}
if (backdrop) backdrop.addEventListener("click", function () { setNavOpen(false); });
document.addEventListener("keydown", function (e) {
if (e.key !== "Escape") return;
var dialog = document.getElementById("exapp-dialog");
if (dialog && dialog.open) return;
setNavOpen(false);
});
})();
</script>
</body>