Skip to content

Commit 159d953

Browse files
authored
fix(ui): prevents navbar jump when notification drawer opens (#46)
* fix(ui): prevents navbar jump when notification drawer opens * fix(ui): uses CSS scrollbar-width var for navbar compensation * fix(ui): preserves navbar padding and compensates footer * docs(css): notes daisyUI .navbar padding dependency in comment * test(e2e): adds scrollbar compensation test and scopes footer selector * fix(ui): makes CI failure indicators more noticeable * fix(ui): uses vivid red-500 for CI failure dots
1 parent 56b4ae0 commit 159d953

File tree

5 files changed

+45
-4
lines changed

5 files changed

+45
-4
lines changed

e2e/smoke.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,36 @@ test("switching tabs changes active tab indicator", async ({ page }) => {
165165
await expect(actionsTab).toHaveAttribute("aria-selected", "true");
166166
});
167167

168+
test("fixed elements compensate for scrollbar width when scroll is locked", async ({ page }) => {
169+
await setupAuth(page);
170+
await page.goto("/dashboard");
171+
172+
const navbar = page.locator(".navbar");
173+
const footer = page.locator(".app-footer");
174+
await expect(navbar).toBeVisible();
175+
await expect(footer).toBeVisible();
176+
177+
// Baseline: navbar 0.5rem (8px) from daisyUI, footer 0px (no base padding)
178+
expect(parseFloat(await navbar.evaluate((el) => getComputedStyle(el).paddingRight))).toBeCloseTo(8, 0);
179+
expect(parseFloat(await footer.evaluate((el) => getComputedStyle(el).paddingRight))).toBeCloseTo(0, 0);
180+
181+
// Simulate solid-prevent-scroll setting --scrollbar-width on body
182+
await page.evaluate(() => {
183+
document.body.style.setProperty("--scrollbar-width", "15px");
184+
});
185+
186+
// Navbar: 8px + 15px = 23px, footer: 0px + 15px = 15px
187+
expect(parseFloat(await navbar.evaluate((el) => getComputedStyle(el).paddingRight))).toBeCloseTo(23, 0);
188+
expect(parseFloat(await footer.evaluate((el) => getComputedStyle(el).paddingRight))).toBeCloseTo(15, 0);
189+
190+
// Clear — both return to baseline
191+
await page.evaluate(() => {
192+
document.body.style.removeProperty("--scrollbar-width");
193+
});
194+
expect(parseFloat(await navbar.evaluate((el) => getComputedStyle(el).paddingRight))).toBeCloseTo(8, 0);
195+
expect(parseFloat(await footer.evaluate((el) => getComputedStyle(el).paddingRight))).toBeCloseTo(0, 0);
196+
});
197+
168198
test("dashboard shows empty state with no data", async ({ page }) => {
169199
await setupAuth(page);
170200
await page.goto("/dashboard");

src/app/components/dashboard/DashboardPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ export default function DashboardPage() {
428428
</main>
429429
</div>
430430

431-
<footer class="fixed bottom-0 left-0 right-0 z-30 border-t border-base-300 bg-base-100 py-3 text-xs text-base-content/50">
431+
<footer class="app-footer fixed bottom-0 left-0 right-0 z-30 border-t border-base-300 bg-base-100 py-3 text-xs text-base-content/50">
432432
<div class="max-w-6xl mx-auto w-full px-4 grid grid-cols-3 items-center">
433433
<div />
434434
<div class="flex items-center justify-center gap-3">

src/app/components/dashboard/WorkflowRunRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export default function WorkflowRunRow(props: WorkflowRunRowProps) {
144144

145145
return (
146146
<div
147-
class={`flex items-center gap-3 ${paddingClass()} hover:bg-base-200 group ${props.isFlashing ? "animate-flash" : props.isPolling ? "animate-shimmer" : ""}`}
147+
class={`flex items-center gap-3 ${paddingClass()} group ${props.run.conclusion === "failure" ? "bg-error/5 hover:bg-error/10" : "hover:bg-base-200"} ${props.isFlashing ? "animate-flash" : props.isPolling ? "animate-shimmer" : ""}`}
148148
>
149149
<StatusIcon status={props.run.status} conclusion={props.run.conclusion} />
150150

src/app/components/shared/StatusDot.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ const STATUS_CONFIG = {
1717
pulse: true,
1818
},
1919
failure: {
20-
bg: "bg-error",
20+
bg: "bg-red-500",
2121
label: "Checks failing",
2222
pulse: false,
2323
},
2424
error: {
25-
bg: "bg-error",
25+
bg: "bg-red-500",
2626
label: "Checks failing",
2727
pulse: false,
2828
},

src/app/index.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@
107107
animation: overlay-fade-in 0.3s ease-out forwards;
108108
}
109109

110+
/* ── Fixed-element scrollbar compensation ────────────────────────────────── */
111+
/* solid-prevent-scroll sets --scrollbar-width on body when scroll is locked;
112+
fixed elements don't inherit body padding-right, so compensate explicitly.
113+
0.5rem matches daisyUI .navbar padding (padding: .5rem) */
114+
.navbar {
115+
padding-right: calc(0.5rem + var(--scrollbar-width, 0px));
116+
}
117+
.app-footer {
118+
padding-right: var(--scrollbar-width, 0px);
119+
}
120+
110121
/* ── Kobalte → daisyUI bridges ───────────────────────────────────────────── */
111122

112123
/* Kobalte Tabs: data-selected → daisyUI tab-active */

0 commit comments

Comments
 (0)