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.
This commit is contained in:
@@ -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]) => (
|
||||
<Box key={section || "_"} sx={{ mb: desktopNavCollapsed ? 1 : 1.25 }}>
|
||||
{section && !desktopNavCollapsed ? (
|
||||
<Typography variant="caption" sx={{ px: 1.25, color: "text.secondary", fontWeight: 600, textTransform: "uppercase" }}>
|
||||
<Typography variant="caption" sx={{ px: 1.25, color: SIDEBAR_TEXT_MUTED, fontWeight: 600, textTransform: "uppercase" }}>
|
||||
{section}
|
||||
</Typography>
|
||||
) : 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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: desktopNavCollapsed ? 0 : 36, justifyContent: "center" }}>
|
||||
<ListItemIcon sx={{ minWidth: desktopNavCollapsed ? 0 : 36, justifyContent: "center", color: selected ? SIDEBAR_SELECTED_ICON : SIDEBAR_TEXT_MUTED }}>
|
||||
{item.badgeCount && item.badgeCount > 0 ? (
|
||||
<Badge color="error" badgeContent={item.badgeCount > 99 ? "99+" : item.badgeCount}>
|
||||
{item.icon}
|
||||
@@ -166,16 +181,16 @@ export default function AppShell({
|
||||
);
|
||||
|
||||
const drawerContent = (
|
||||
<Box sx={{ height: "100%", display: "flex", flexDirection: "column" }}>
|
||||
<Box sx={{ height: "100%", display: "flex", flexDirection: "column", backgroundColor: SIDEBAR_BG }}>
|
||||
<Box sx={{ px: desktopNavCollapsed ? 1.5 : 2.25, py: desktopNavCollapsed ? 2 : 2.5, display: "flex", justifyContent: desktopNavCollapsed ? "center" : "flex-start" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1, justifyContent: desktopNavCollapsed ? "center" : "flex-start" }}>
|
||||
<JobbjaktMark style={{ width: 22, height: 22 }} />
|
||||
<JobbjaktMark style={{ width: 30, height: 30, flexShrink: 0 }} />
|
||||
{!desktopNavCollapsed ? (
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, color: SIDEBAR_SELECTED_TEXT }}>
|
||||
Jobbjakt
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>
|
||||
<Typography variant="caption" sx={{ color: SIDEBAR_TEXT_MUTED }}>
|
||||
{t("appTagline")}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -183,13 +198,13 @@ export default function AppShell({
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
<Divider sx={{ borderColor: SIDEBAR_BORDER }} />
|
||||
|
||||
{renderNavList(grouped.top)}
|
||||
|
||||
<Box sx={{ flex: 1 }} />
|
||||
|
||||
<Divider />
|
||||
<Divider sx={{ borderColor: SIDEBAR_BORDER }} />
|
||||
|
||||
{renderNavList(grouped.bottom)}
|
||||
</Box>
|
||||
@@ -406,19 +421,19 @@ export default function AppShell({
|
||||
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={(muiTheme: any) => ({
|
||||
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
|
||||
>
|
||||
<Toolbar sx={{ minHeight: { xs: 68, md: 76 } }} />
|
||||
@@ -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}
|
||||
</Drawer>
|
||||
|
||||
Reference in New Issue
Block a user