Merge branch 'wave/v1221-a' into release/v1.22.1

This commit is contained in:
Marc-André Bombeck committed 2026-06-27 18:07:00 +02:00
commit 1561fc67b0
8 files changed
+107 -92

No files matched your search

+1 -1
View File
@@ -5518,7 +5518,7 @@
"subtitle": "Version, Lizenz und Update-Status."
},
"module-availability": {
"title": "Modulverfügbarkeit",
"title": "Module",
"subtitle": "Module für jedes Konto auf diesem Server aktivieren oder deaktivieren."
}
},
+1 -1
View File
@@ -5518,7 +5518,7 @@
"subtitle": "Version, license, and update status."
},
"module-availability": {
"title": "Module availability",
"title": "Modules",
"subtitle": "Turn modules on or off for every account on this server."
}
},
+1 -1
View File
@@ -5518,7 +5518,7 @@
"subtitle": "Versión, licencia y estado de actualizaciones."
},
"module-availability": {
"title": "Disponibilidad de módulos",
"title": "Módulos",
"subtitle": "Activa o desactiva módulos para todas las cuentas de este servidor."
}
},
+1 -1
View File
@@ -5518,7 +5518,7 @@
"subtitle": "Version, licence et état des mises à jour."
},
"module-availability": {
"title": "Disponibilité des modules",
"title": "Modules",
"subtitle": "Activez ou désactivez les modules pour tous les comptes de ce serveur."
}
},
+1 -1
View File
@@ -5518,7 +5518,7 @@
"subtitle": "Versione, licenza e stato degli aggiornamenti."
},
"module-availability": {
"title": "Disponibilità dei moduli",
"title": "Moduli",
"subtitle": "Attiva o disattiva i moduli per tutti gli account su questo server."
}
},
+1 -1
View File
@@ -5518,7 +5518,7 @@
"subtitle": "Wersja, licencja i stan aktualizacji."
},
"module-availability": {
"title": "Dostępność modułów",
"title": "Moduły",
"subtitle": "Włącz lub wyłącz moduły dla wszystkich kont na tym serwerze."
}
},
+60 -53
View File
@@ -37,7 +37,6 @@ import {
} from "lucide-react";
import { cn } from "@/lib/utils";
import { scrollBehaviorForUser } from "@/lib/motion";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { isAdminSectionSlug, type AdminSectionSlug } from "./section-slugs";
@@ -188,16 +187,15 @@ export function AdminShell({ active, children }: AdminShellProps) {
'[aria-current="page"]',
);
if (!activeChip) return;
activeChip.scrollIntoView({
block: "nearest",
// v1.4.36 W4b — `inline: "start"` pins the active chip to the
// left edge of the scroller. `inline: "center"` over-scrolled
// and the first one or two chips were unreachable on narrow
// viewports.
inline: "start",
// v1.4.43 W5-H5 — respect `prefers-reduced-motion`.
behavior: scrollBehaviorForUser(),
});
// Adjust only the strip's own horizontal offset, instantly. The old
// `scrollIntoView` walked every scrollable ancestor and could nudge the
// document vertically, and a smooth behaviour animated the strip from a
// reset `scrollLeft: 0` to the target on every tap — an unwanted "scroll
// from the start" sweep. `scrollTo({ left, behavior: "auto" })` confines
// the motion to the strip's horizontal axis and jumps without animation.
const maxScroll = strip.scrollWidth - strip.clientWidth;
const target = Math.max(0, Math.min(activeChip.offsetLeft, maxScroll));
strip.scrollTo({ left: target, behavior: "auto" });
}, [activeSlug]);
// The section bodies are already role-gated, but the shell frame
@@ -311,51 +309,60 @@ export function AdminShell({ active, children }: AdminShellProps) {
{headingBlock}
</div>
{/* Desktop sticky sidebar — starts at the cards row (row 2). */}
{/* Desktop sticky sidebar — starts at the cards row (row 2).
The sticky lives on the `<aside>` grid item itself, with
`self-start` so it shrinks to its content instead of stretching
to the full row height. A sticky CHILD inside a stretched grid
item resolves its containing block to the stretched item, which
in some engines lets the nav scroll away with the content rather
than pin. Sticking the grid item directly — sized to content and
offset `top-6` to clear the scroll viewport's padding — keeps the
nav fixed while only the content column scrolls. `max-h` +
`overflow-y-auto` let a long section list scroll within the
pinned panel on short viewports. */}
<aside
aria-label={t("admin.shell.sectionsNav")}
className="hidden md:col-start-1 md:row-start-2 md:block"
className="no-scrollbar hidden max-h-[calc(100dvh-5.5rem)] overflow-y-auto md:sticky md:top-6 md:col-start-1 md:row-start-2 md:block md:self-start"
>
<div className="no-scrollbar sticky top-6 max-h-[calc(100dvh-5.5rem)] overflow-y-auto">
<ul className="space-y-1">
<li>
<Link
href="/admin"
aria-current={activeSlug === null ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
activeSlug === null
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<ScrollText className="h-4 w-4" aria-hidden="true" />
{t("admin.shell.overview")}
</Link>
</li>
{ADMIN_SECTIONS.map((section) => {
const isActive = section.slug === activeSlug;
const Icon = section.icon;
return (
<li key={section.slug}>
<Link
href={`/admin/${section.slug}`}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
{t(section.titleKey)}
</Link>
</li>
);
})}
</ul>
</div>
<ul className="space-y-1">
<li>
<Link
href="/admin"
aria-current={activeSlug === null ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
activeSlug === null
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<ScrollText className="h-4 w-4" aria-hidden="true" />
{t("admin.shell.overview")}
</Link>
</li>
{ADMIN_SECTIONS.map((section) => {
const isActive = section.slug === activeSlug;
const Icon = section.icon;
return (
<li key={section.slug}>
<Link
href={`/admin/${section.slug}`}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
{t(section.titleKey)}
</Link>
</li>
);
})}
</ul>
</aside>
{/* Main column (cards) — row 2 / col 2. Same
+41 -33
View File
@@ -43,7 +43,6 @@ import {
} from "lucide-react";
import { cn } from "@/lib/utils";
import { scrollBehaviorForUser } from "@/lib/motion";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import type { ModuleKey } from "@/lib/modules/registry";
@@ -366,11 +365,11 @@ export function SettingsShell({
// box, which is the origin for `scrollLeft`.
const maxScroll = strip.scrollWidth - strip.clientWidth;
const target = Math.max(0, Math.min(active.offsetLeft, maxScroll));
strip.scrollTo({
left: target,
// v1.4.43 W5-H5 — respect `prefers-reduced-motion`.
behavior: scrollBehaviorForUser(),
});
// Position the active chip instantly. A smooth behaviour here animates
// the strip from its reset `scrollLeft: 0` to the target on every tap,
// which reads as an unwanted "scroll from the start" sweep when swapping
// sections. An instant jump keeps the chip in view without the sweep.
strip.scrollTo({ left: target, behavior: "auto" });
}, [activeSlug]);
// v1.18.6.1 — resolve the heading once. Pages pass an explicit `heading`
@@ -497,36 +496,45 @@ export function SettingsShell({
</div>
) : null}
{/* Desktop sticky sidebar — starts at the cards row (row 2). */}
{/* Desktop sticky sidebar — starts at the cards row (row 2).
The sticky lives on the `<aside>` grid item itself, with
`self-start` so it shrinks to its content instead of stretching
to the full row height. A sticky CHILD inside a stretched grid
item resolves its containing block to the stretched item, which
in some engines lets the nav scroll away with the content rather
than pin. Sticking the grid item directly — sized to content and
offset `top-6` to clear the scroll viewport's padding — keeps the
nav fixed while only the content column scrolls. `max-h` +
`overflow-y-auto` let a long section list scroll within the
pinned panel on short viewports. */}
<aside
aria-label={t("settings.shell.sectionsNav")}
className="hidden md:col-start-1 md:row-start-2 md:block"
className="no-scrollbar hidden max-h-[calc(100dvh-5.5rem)] overflow-y-auto md:sticky md:top-6 md:col-start-1 md:row-start-2 md:block md:self-start"
>
<div className="no-scrollbar sticky top-6 max-h-[calc(100dvh-5.5rem)] overflow-y-auto">
<ul className="space-y-1">
{visibleSections.map((section) => {
const isActive = section.slug === activeSlug;
const Icon = section.icon;
return (
<li key={section.slug}>
<Link
href={`/settings/${section.slug}`}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
{t(section.titleKey)}
</Link>
</li>
);
})}
</ul>
</div>
<ul className="space-y-1">
{visibleSections.map((section) => {
const isActive = section.slug === activeSlug;
const Icon = section.icon;
return (
<li key={section.slug}>
<Link
href={`/settings/${section.slug}`}
aria-current={isActive ? "page" : undefined}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors",
isActive
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-accent",
)}
>
<Icon className="h-4 w-4" aria-hidden="true" />
{t(section.titleKey)}
</Link>
</li>
);
})}
</ul>
</aside>
{/* Main column — page renders its own h1 + subtitle. The