From 7cfbdf504ac79a9f8263ef99c922c7706a16c1c0 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 12 Jul 2026 01:45:20 +0200 Subject: [PATCH] feat(ui): dark navy sidebar + restrained kanban status colors First pass of the /frontend-design overhaul against the mockups at F:\Pictures\website\jobtracker\new. Two highest-leverage gaps from the backlog note ("dark sidebar, KPI cards, exact status colours"): - AppShell: nav rail is now a fixed dark navy (#0f172a) regardless of the app's light/dark theme toggle, matching the mockup's signature look -- selected item gets an indigo-tinted pill + icon accent, muted slate text for the rest. Kept icon+label rows (mockup's sidebar is text-only) since the existing collapsed-sidebar mode depends on icons; that's a deliberate deviation, not an oversight. - JobbjaktMark: replaced the briefcase glyph with the gradient checkmark-in-square mark used throughout the mockups (hero, dashboard, kanban) -- also fixed a latent SVG gradient id collision across multiple rendered instances via useId(). - KanbanBoard: mockup uses color sparingly (a small dot in the column header, a 4px accent on the card's left edge) rather than tinting the whole column/card background as the previous version did. Reworked to match; also swapped card title/subtitle order (job title bold, company/location as subtitle) per the mockup. Remaining for follow-up passes: Dashboard KPI card layout and the job workspace (candidate-fit ring, AI summary card) -- both structurally close already but not yet pixel-matched. Verified: `next build` clean, all 57 frontend tests green, dark sidebar confirmed live (computed bg #0f172a) against a running dev server with light content mode forced. --- job-tracker-ui/src/assets/JobbjaktMark.tsx | 21 +++---- job-tracker-ui/src/components/KanbanBoard.tsx | 52 ++++++++---------- job-tracker-ui/src/layout/AppShell.tsx | 55 ++++++++++++------- 3 files changed, 66 insertions(+), 62 deletions(-) diff --git a/job-tracker-ui/src/assets/JobbjaktMark.tsx b/job-tracker-ui/src/assets/JobbjaktMark.tsx index ec8f502..6122b6c 100644 --- a/job-tracker-ui/src/assets/JobbjaktMark.tsx +++ b/job-tracker-ui/src/assets/JobbjaktMark.tsx @@ -1,21 +1,18 @@ -import React from "react"; +import React, { useId } from "react"; export default function JobbjaktMark(props: React.SVGProps) { + const gradientId = useId(); + return ( - + - - - + + + - - - - - - - + + ); } diff --git a/job-tracker-ui/src/components/KanbanBoard.tsx b/job-tracker-ui/src/components/KanbanBoard.tsx index 255ab62..8ff8628 100644 --- a/job-tracker-ui/src/components/KanbanBoard.tsx +++ b/job-tracker-ui/src/components/KanbanBoard.tsx @@ -4,7 +4,6 @@ import { Box, Card, CardContent, - Chip, IconButton, Menu, MenuItem, @@ -115,24 +114,21 @@ export default function KanbanBoard() { p: 1.5, borderRadius: 3, minHeight: 220, - border: `1px solid ${alpha(c, theme.palette.mode === "dark" ? 0.25 : 0.18)}`, - background: alpha(c, theme.palette.mode === "dark" ? 0.10 : 0.06), + border: "1px solid", + borderColor: "divider", + background: theme.palette.mode === "dark" ? alpha(theme.palette.common.white, 0.02) : alpha(theme.palette.text.primary, 0.015), }} > - - - {statusLabel(t, status)} + + + + + {statusLabel(t, status)} + + + + {list.length} - @@ -144,21 +140,18 @@ export default function KanbanBoard() { onDragEnd={() => setDragJobId(null)} sx={{ cursor: "grab", - borderRadius: 3, - border: `1px solid ${alpha(c, theme.palette.mode === "dark" ? 0.22 : 0.14)}`, - background: theme.palette.mode === "dark" ? "rgba(15,23,42,0.82)" : "rgba(255,255,255,0.96)", - backdropFilter: "blur(8px)", - color: theme.palette.mode === "dark" ? "#e5eefc" : "#0f172a", + borderRadius: 2.5, + borderLeft: `4px solid ${c}`, + boxShadow: theme.palette.mode === "dark" ? "none" : "0 1px 3px rgba(15,23,42,0.06)", }} > - - {j.company?.name ?? ""} + + {j.jobTitle} { e.stopPropagation(); setMenuJobId(j.id); @@ -168,13 +161,12 @@ export default function KanbanBoard() { - - {j.jobTitle} + + {[j.company?.name, j.location].filter(Boolean).join(" ยท ")} + + + {j.daysSince}d - - - {j.location ? : null} - ))} diff --git a/job-tracker-ui/src/layout/AppShell.tsx b/job-tracker-ui/src/layout/AppShell.tsx index 487c8ce..ca6be3c 100644 --- a/job-tracker-ui/src/layout/AppShell.tsx +++ b/job-tracker-ui/src/layout/AppShell.tsx @@ -47,6 +47,16 @@ function initialsFrom(s?: string) { const DESKTOP_SIDEBAR_KEY = "appShellDesktopSidebarCollapsed"; +// The nav rail stays a fixed dark navy regardless of the app's light/dark theme toggle -- +// a deliberate signature element, not derived from theme tokens. +const SIDEBAR_BG = "#0f172a"; +const SIDEBAR_BORDER = "rgba(255,255,255,0.08)"; +const SIDEBAR_TEXT_MUTED = "#94a3b8"; +const SIDEBAR_TEXT = "#e2e8f0"; +const SIDEBAR_SELECTED_BG = "rgba(99,102,241,0.18)"; +const SIDEBAR_SELECTED_TEXT = "#ffffff"; +const SIDEBAR_SELECTED_ICON = "#a5b4fc"; + export default function AppShell({ pageTitle, breadcrumbs, @@ -122,7 +132,7 @@ export default function AppShell({ {groups.map(([section, rows]) => ( {section && !desktopNavCollapsed ? ( - + {section} ) : null} @@ -135,20 +145,25 @@ export default function AppShell({ selected={selected} onClick={() => onNavigate(item.to)} title={desktopNavCollapsed ? item.label : undefined} - sx={(muiTheme: any) => ({ + sx={{ borderRadius: 2, mb: 0.5, minHeight: 44, px: desktopNavCollapsed ? 1 : 1.5, justifyContent: desktopNavCollapsed ? "center" : "flex-start", border: "1px solid transparent", + color: SIDEBAR_TEXT_MUTED, + "&:hover": { backgroundColor: "rgba(255,255,255,0.06)", color: SIDEBAR_TEXT }, "&.Mui-selected": { - backgroundColor: muiTheme.vars.palette.action.hover, - borderColor: muiTheme.vars.palette.divider, + backgroundColor: SIDEBAR_SELECTED_BG, + color: SIDEBAR_SELECTED_TEXT, }, - })} + "&.Mui-selected:hover": { + backgroundColor: SIDEBAR_SELECTED_BG, + }, + }} > - + {item.badgeCount && item.badgeCount > 0 ? ( 99 ? "99+" : item.badgeCount}> {item.icon} @@ -166,16 +181,16 @@ export default function AppShell({ ); const drawerContent = ( - + - + {!desktopNavCollapsed ? ( - + Jobbjakt - + {t("appTagline")} @@ -183,13 +198,13 @@ export default function AppShell({ - + {renderNavList(grouped.top)} - + {renderNavList(grouped.bottom)} @@ -406,19 +421,19 @@ export default function AppShell({ ({ + sx={{ display: { xs: "none", md: "block" }, width: drawerWidth, flexShrink: 0, [`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: "border-box", - borderRight: `1px solid ${muiTheme.vars.palette.grey[300]}`, - backgroundColor: muiTheme.vars.palette.background.default, + borderRight: `1px solid ${SIDEBAR_BORDER}`, + backgroundColor: SIDEBAR_BG, backgroundImage: "none", boxShadow: "none", }, - })} + }} open > @@ -430,15 +445,15 @@ export default function AppShell({ open={drawerOpen} onClose={() => onToggleDrawer(false)} ModalProps={{ keepMounted: true }} - sx={(muiTheme: any) => ({ + sx={{ display: { xs: "block", md: "none" }, [`& .MuiDrawer-paper`]: { width: drawerWidth, - borderRight: `1px solid ${muiTheme.vars.palette.grey[300]}`, - backgroundColor: muiTheme.vars.palette.background.default, + borderRight: `1px solid ${SIDEBAR_BORDER}`, + backgroundColor: SIDEBAR_BG, backgroundImage: "none", }, - })} + }} > {drawerContent}