mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(01-17): align and pin desktop sidebars
- place both sidebars on the heading row with one main scroll owner - bound short-viewport sidebar content without horizontal layout drift
This commit is contained in:
1 parent
840e47fda5
commit
23c738efac
2 files changed
+62
-34
No files matched your search
@@ -253,9 +253,19 @@ export function AdminShell({ active, children }: AdminShellProps) {
|
||||
? t("admin.subtitle")
|
||||
: t(`admin.section.${activeSlug}.subtitle`);
|
||||
|
||||
const headingBlock = (
|
||||
const headingBlock = (mobile = false) => (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">{headingTitle}</h1>
|
||||
{mobile ? (
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={1}
|
||||
className="text-2xl font-bold tracking-tight"
|
||||
>
|
||||
{headingTitle}
|
||||
</div>
|
||||
) : (
|
||||
<h1 className="text-2xl font-bold tracking-tight">{headingTitle}</h1>
|
||||
)}
|
||||
<p className="text-muted-foreground text-sm">{headingSubtitle}</p>
|
||||
</div>
|
||||
);
|
||||
@@ -269,7 +279,7 @@ export function AdminShell({ active, children }: AdminShellProps) {
|
||||
<div className="mx-auto w-full max-w-screen-xl">
|
||||
{/* v1.18.6.1 — heading above the chip strip on mobile; on desktop it
|
||||
renders inside the grid (row 1 / content column). */}
|
||||
<div className="mb-4 md:hidden">{headingBlock}</div>
|
||||
<div className="mb-4 md:hidden">{headingBlock(true)}</div>
|
||||
|
||||
{/* Mobile section strip — horizontal scroll, hidden on md+.
|
||||
`no-scrollbar` (defined in `globals.css`) suppresses the
|
||||
@@ -359,10 +369,13 @@ export function AdminShell({ active, children }: AdminShellProps) {
|
||||
>
|
||||
{/* Heading — desktop only (mobile renders it above the strip). */}
|
||||
<div className="hidden md:col-start-2 md:row-start-1 md:block">
|
||||
{headingBlock}
|
||||
{headingBlock()}
|
||||
</div>
|
||||
|
||||
{/* Desktop sticky sidebar — starts at the cards row (row 2).
|
||||
{/* Desktop sticky sidebar — starts on the heading row and spans the
|
||||
two desktop rows. Its top therefore shares the heading's grid
|
||||
line while the content column keeps the natural heading-to-card
|
||||
gap in row 2.
|
||||
|
||||
The sticky lives on the `<aside>` grid item itself, with
|
||||
`self-start` so it shrinks to its content instead of stretching
|
||||
@@ -376,7 +389,7 @@ export function AdminShell({ active, children }: AdminShellProps) {
|
||||
pinned panel on short viewports. */}
|
||||
<aside
|
||||
aria-label={t("admin.shell.sectionsNav")}
|
||||
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"
|
||||
className="no-scrollbar hidden max-h-[calc(100dvh-10.5rem)] overflow-y-auto md:sticky md:top-6 md:col-start-1 md:row-span-2 md:row-start-1 md:block md:self-start"
|
||||
>
|
||||
<ul className="space-y-1">
|
||||
<li>
|
||||
@@ -440,7 +453,7 @@ export function AdminShell({ active, children }: AdminShellProps) {
|
||||
to carry) over-reserves on short sections and on mobile, scrolling
|
||||
the page into a dark band below the last card. AuthShell already
|
||||
owns the page's single `<main>` landmark. */}
|
||||
<div className="min-w-0 md:col-start-2 md:row-start-2">{children}</div>
|
||||
<div className="min-w-0 md:col-start-2">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -476,29 +476,41 @@ export function SettingsShell({
|
||||
}
|
||||
: null);
|
||||
|
||||
const headingBlock = resolvedHeading ? (
|
||||
<div className="space-y-6">
|
||||
{resolvedHeading.topSlot ? <div>{resolvedHeading.topSlot}</div> : null}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<h1
|
||||
id={resolvedHeading.headingId}
|
||||
className="text-2xl font-bold tracking-tight"
|
||||
>
|
||||
{resolvedHeading.title}
|
||||
</h1>
|
||||
{resolvedHeading.subtitle ? (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{resolvedHeading.subtitle}
|
||||
</p>
|
||||
const headingBlock = (mobile = false) =>
|
||||
resolvedHeading ? (
|
||||
<div className="space-y-6">
|
||||
{resolvedHeading.topSlot ? <div>{resolvedHeading.topSlot}</div> : null}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
{mobile ? (
|
||||
<div
|
||||
id={`${resolvedHeading.headingId}-mobile`}
|
||||
role="heading"
|
||||
aria-level={1}
|
||||
className="text-2xl font-bold tracking-tight"
|
||||
>
|
||||
{resolvedHeading.title}
|
||||
</div>
|
||||
) : (
|
||||
<h1
|
||||
id={resolvedHeading.headingId}
|
||||
className="text-2xl font-bold tracking-tight"
|
||||
>
|
||||
{resolvedHeading.title}
|
||||
</h1>
|
||||
)}
|
||||
{resolvedHeading.subtitle ? (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{resolvedHeading.subtitle}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
{resolvedHeading.headingAccessory ? (
|
||||
<div className="shrink-0">{resolvedHeading.headingAccessory}</div>
|
||||
) : null}
|
||||
</div>
|
||||
{resolvedHeading.headingAccessory ? (
|
||||
<div className="shrink-0">{resolvedHeading.headingAccessory}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
) : null;
|
||||
|
||||
// v1.4.25 W8 — AuthShell wraps the page in `px-4 py-6 md:px-6`
|
||||
// already, so this inner shell only carries the wider max-width.
|
||||
@@ -510,8 +522,8 @@ export function SettingsShell({
|
||||
{/* v1.18.6.1 — on mobile the heading sits above the chip strip; on
|
||||
desktop it is rendered inside the grid (row 1 / content column) so
|
||||
it does not paint twice. */}
|
||||
{headingBlock ? (
|
||||
<div className="mb-4 md:hidden">{headingBlock}</div>
|
||||
{resolvedHeading ? (
|
||||
<div className="mb-4 md:hidden">{headingBlock(true)}</div>
|
||||
) : null}
|
||||
|
||||
{/* Mobile section strip — horizontal scroll, hidden on md+.
|
||||
@@ -588,13 +600,16 @@ export function SettingsShell({
|
||||
)}
|
||||
>
|
||||
{/* Heading — desktop only here (mobile renders it above the strip). */}
|
||||
{headingBlock ? (
|
||||
{resolvedHeading ? (
|
||||
<div className="hidden md:col-start-2 md:row-start-1 md:block">
|
||||
{headingBlock}
|
||||
{headingBlock()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Desktop sticky sidebar — starts at the cards row (row 2).
|
||||
{/* Desktop sticky sidebar — starts on the heading row and spans the
|
||||
two desktop rows. Its top therefore shares the heading's grid
|
||||
line while the content column keeps the natural heading-to-card
|
||||
gap in row 2.
|
||||
|
||||
The sticky lives on the `<aside>` grid item itself, with
|
||||
`self-start` so it shrinks to its content instead of stretching
|
||||
@@ -608,7 +623,7 @@ export function SettingsShell({
|
||||
pinned panel on short viewports. */}
|
||||
<aside
|
||||
aria-label={t("settings.shell.sectionsNav")}
|
||||
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"
|
||||
className="no-scrollbar hidden max-h-[calc(100dvh-10.5rem)] overflow-y-auto md:sticky md:top-6 md:col-start-1 md:row-span-2 md:row-start-1 md:block md:self-start"
|
||||
>
|
||||
<ul className="space-y-1">
|
||||
{visibleSections.map((section, index) => {
|
||||
@@ -680,7 +695,7 @@ export function SettingsShell({
|
||||
v1.16.4 — a `<div>`, not `<main>`: the surrounding AuthShell
|
||||
already provides the page's single `<main>` landmark and a
|
||||
nested second one is an a11y violation. */}
|
||||
<div className="min-w-0 md:col-start-2 md:row-start-2">{children}</div>
|
||||
<div className="min-w-0 md:col-start-2">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user