# Dashboard State Lift + Theme System Refactor ## Summary Refactored dashboard state ownership, centralized theme semantics, and simplified component styling across the application. ## Changes ### Dashboard State Refactor * Moved dashboard state management from `Dashboard.view` into `Dashboard.tsx` * Added centralized `DashboardState` initialization in parent container * Introduced memoized dashboard state setter callbacks: * `toggleFlow` * `setFlow` * `togglePeriodType` * `toggleComparison` * `setSelectedPeriodId` * `setSelectedGroupKey` * Added `DashboardStateSetters` memoized object for prop-driven state management * Removed `onFlowChange` callback pattern * Converted dashboard component into stateless view layer * Renamed component export flow: * `Dashboard.tsx` → removed * `Dashboard.view.tsx` → primary implementation ### Dashboard Models Cleanup * Removed legacy palette configuration interfaces: * `ColorDefinition` * `ThemeAwarePalette` * Removed config-level style palette support from `DashboardConfig` * Renamed `DashboardProps` → `DashboardViewProps` * Added reusable `ColorScheme` interface * Simplified component color contract: * `primary` * `surface` * `text` ### Theme Architecture Refactor * Moved `AppTheme.tsx` into `shared-theme` * Added centralized semantic theme system * Introduced `themeConfig.ts` with semantic tokens: * surface * border * text * Added `semantic` extension to MUI theme typing * Added `flows` palette extension: * outflows * inflows * Centralized flow colors inside theme primitives * Added CSS semantic variables: * `--bg-page` * `--bg-card` * `--bg-elevated` * `--border-default` * `--border-subtle` * `--text-primary` * `--text-secondary` * `--text-muted` ### Theme Mode Improvements * Added explicit `ColorMode` type * Expanded `ColorModeContext`: * `mode` * `setMode` * `toggleColorMode` * Added `CssBaseline` * Added configurable `defaultMode` * Simplified dark theme palette handling * Standardized dark surfaces and shadows * Reduced excessive dark-mode glow/shadow intensity ### Dashboard UI Styling Improvements * Replaced hardcoded dashboard palette config with theme palette usage * Updated dashboard background gradients to use alpha-based semantic colors * Replaced `colorScheme.light` usage with `colorScheme.surface` * Standardized border usage with theme divider tokens * Removed manual dark-mode conditional styling where redundant * Simplified card and progress styling logic ### Shared Theme Customization Cleanup Updated customization layers for improved consistency: * `inputs` * `navigation` * `feedback` * `surfaces` Key improvements: * Reduced dark-mode contrast harshness * Unified divider usage * Replaced hardcoded grayscale backgrounds with semantic surfaces * Simplified hover and active state styling * Reduced shadow intensity across components * Improved dark-mode readability and layering ### Home Page Styling Cleanup * Replaced manual RGBA handling with `alpha()` utility * Improved dark-mode glassmorphism consistency * Updated CTA hover shadow to use theme primary color ### Miscellaneous Cleanup * Updated imports to new theme structure * Removed unused legacy color mode components: * `ColorModeIconDropdown.tsx` * `ColorModeSelect.tsx` * Removed dashboard config style palette definitions * Simplified flow-based color derivation logic ## Result * Cleaner separation of stateful vs presentational dashboard logic * Centralized semantic theming system * Consistent dark/light mode behavior * Reduced styling duplication * Improved maintainability and extensibility of theme architecture * Simplified dashboard component contracts * Better UI consistency across surfaces and controls Reviewed-on: #6 Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com> Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
449 lines
11 KiB
TypeScript
449 lines
11 KiB
TypeScript
import { createTheme, alpha, PaletteMode, Shadows } from '@mui/material/styles';
|
|
|
|
declare module '@mui/material/Paper' {
|
|
interface PaperPropsVariantOverrides {
|
|
highlighted: true;
|
|
}
|
|
}
|
|
declare module '@mui/material/styles' {
|
|
interface ColorRange {
|
|
50: string;
|
|
100: string;
|
|
200: string;
|
|
300: string;
|
|
400: string;
|
|
500: string;
|
|
600: string;
|
|
700: string;
|
|
800: string;
|
|
900: string;
|
|
}
|
|
|
|
interface PaletteColor extends ColorRange {}
|
|
|
|
interface Palette {
|
|
baseShadow: string;
|
|
flows: {
|
|
outflows: { primary: string; surface: string; text: string };
|
|
inflows: { primary: string; surface: string; text: string };
|
|
};
|
|
}
|
|
}
|
|
|
|
const defaultTheme = createTheme();
|
|
|
|
const customShadows: Shadows = [...defaultTheme.shadows];
|
|
|
|
export const brand = {
|
|
50: 'hsl(210, 100%, 95%)',
|
|
100: 'hsl(210, 100%, 92%)',
|
|
200: 'hsl(210, 100%, 80%)',
|
|
300: 'hsl(210, 100%, 65%)',
|
|
400: 'hsl(210, 98%, 48%)',
|
|
500: 'hsl(210, 98%, 42%)',
|
|
600: 'hsl(210, 98%, 55%)',
|
|
700: 'hsl(210, 100%, 35%)',
|
|
800: 'hsl(210, 100%, 16%)',
|
|
900: 'hsl(210, 100%, 21%)',
|
|
};
|
|
|
|
export const gray = {
|
|
50: 'hsl(220, 35%, 97%)',
|
|
100: 'hsl(220, 30%, 94%)',
|
|
200: 'hsl(220, 20%, 88%)',
|
|
300: 'hsl(220, 20%, 80%)',
|
|
400: 'hsl(220, 20%, 65%)',
|
|
500: 'hsl(220, 20%, 42%)',
|
|
600: 'hsl(220, 20%, 35%)',
|
|
700: 'hsl(220, 20%, 25%)',
|
|
750: 'hsl(220, 20%, 18%)',
|
|
800: 'hsl(220, 30%, 6%)',
|
|
850: 'hsl(220, 22%, 11%)',
|
|
900: 'hsl(220, 35%, 3%)',
|
|
};
|
|
|
|
export const green = {
|
|
50: 'hsl(120, 80%, 98%)',
|
|
100: 'hsl(120, 75%, 94%)',
|
|
200: 'hsl(120, 75%, 87%)',
|
|
300: 'hsl(120, 61%, 77%)',
|
|
400: 'hsl(120, 44%, 53%)',
|
|
500: 'hsl(120, 59%, 30%)',
|
|
600: 'hsl(120, 70%, 25%)',
|
|
700: 'hsl(120, 75%, 16%)',
|
|
800: 'hsl(120, 84%, 10%)',
|
|
900: 'hsl(120, 87%, 6%)',
|
|
};
|
|
|
|
export const orange = {
|
|
50: 'hsl(45, 100%, 97%)',
|
|
100: 'hsl(45, 92%, 90%)',
|
|
200: 'hsl(45, 94%, 80%)',
|
|
300: 'hsl(45, 90%, 65%)',
|
|
400: 'hsl(45, 90%, 40%)',
|
|
500: 'hsl(45, 90%, 35%)',
|
|
600: 'hsl(45, 91%, 25%)',
|
|
700: 'hsl(45, 94%, 20%)',
|
|
800: 'hsl(45, 95%, 16%)',
|
|
900: 'hsl(45, 93%, 12%)',
|
|
};
|
|
|
|
export const red = {
|
|
50: 'hsl(0, 100%, 97%)',
|
|
100: 'hsl(0, 92%, 90%)',
|
|
200: 'hsl(0, 94%, 80%)',
|
|
300: 'hsl(0, 90%, 65%)',
|
|
400: 'hsl(0, 90%, 40%)',
|
|
500: 'hsl(0, 90%, 30%)',
|
|
600: 'hsl(0, 91%, 25%)',
|
|
700: 'hsl(0, 94%, 18%)',
|
|
800: 'hsl(0, 95%, 12%)',
|
|
900: 'hsl(0, 93%, 6%)',
|
|
};
|
|
|
|
const darkBg = 'hsl(0, 0%, 9%)';
|
|
const darkPaper = 'hsl(0, 0%, 14%)';
|
|
const darkElevated = 'hsl(0, 0%, 19%)';
|
|
|
|
export const getDesignTokens = (mode: PaletteMode) => {
|
|
customShadows[1] =
|
|
mode === 'dark'
|
|
? '0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.3)'
|
|
: 'hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px';
|
|
|
|
return {
|
|
palette: {
|
|
mode,
|
|
primary: {
|
|
light: brand[200],
|
|
main: brand[400],
|
|
dark: brand[700],
|
|
contrastText: brand[50],
|
|
...(mode === 'dark' && {
|
|
contrastText: brand[50],
|
|
light: 'hsl(210, 50%, 65%)',
|
|
main: 'hsl(210, 55%, 55%)',
|
|
dark: 'hsl(210, 50%, 35%)',
|
|
}),
|
|
},
|
|
info: {
|
|
light: brand[100],
|
|
main: brand[300],
|
|
dark: brand[600],
|
|
contrastText: gray[50],
|
|
...(mode === 'dark' && {
|
|
contrastText: 'hsl(210, 30%, 80%)',
|
|
light: 'hsl(210, 40%, 50%)',
|
|
main: 'hsl(210, 35%, 40%)',
|
|
dark: 'hsl(210, 30%, 25%)',
|
|
}),
|
|
},
|
|
warning: {
|
|
light: orange[300],
|
|
main: orange[400],
|
|
dark: orange[800],
|
|
...(mode === 'dark' && {
|
|
light: 'hsl(45, 60%, 55%)',
|
|
main: 'hsl(45, 55%, 45%)',
|
|
dark: 'hsl(45, 50%, 30%)',
|
|
}),
|
|
},
|
|
error: {
|
|
light: red[300],
|
|
main: red[400],
|
|
dark: red[800],
|
|
...(mode === 'dark' && {
|
|
light: 'hsl(0, 55%, 60%)',
|
|
main: 'hsl(0, 55%, 50%)',
|
|
dark: 'hsl(0, 50%, 35%)',
|
|
}),
|
|
},
|
|
success: {
|
|
light: green[300],
|
|
main: green[400],
|
|
dark: green[800],
|
|
...(mode === 'dark' && {
|
|
light: 'hsl(120, 40%, 55%)',
|
|
main: 'hsl(120, 40%, 45%)',
|
|
dark: 'hsl(120, 35%, 30%)',
|
|
}),
|
|
},
|
|
grey: {
|
|
...gray,
|
|
},
|
|
divider: mode === 'dark' ? 'hsla(0, 0%, 100%, 0.08)' : alpha(gray[300], 0.4),
|
|
background: {
|
|
default: 'hsl(0, 0%, 99%)',
|
|
paper: 'hsl(220, 35%, 97%)',
|
|
...(mode === 'dark' && { default: darkBg, paper: darkPaper }),
|
|
},
|
|
text: {
|
|
primary: gray[800],
|
|
secondary: gray[600],
|
|
warning: orange[400],
|
|
...(mode === 'dark' && { primary: 'hsl(0, 0%, 92%)', secondary: 'hsl(0, 0%, 60%)' }),
|
|
},
|
|
action: {
|
|
hover: alpha(gray[200], 0.2),
|
|
selected: `${alpha(gray[200], 0.3)}`,
|
|
...(mode === 'dark' && {
|
|
hover: 'hsla(0, 0%, 100%, 0.06)',
|
|
selected: 'hsla(0, 0%, 100%, 0.1)',
|
|
}),
|
|
},
|
|
flows: {
|
|
outflows: {
|
|
primary: mode === 'dark' ? 'hsl(0, 55%, 60%)' : '#d32f2f',
|
|
surface: mode === 'dark' ? 'hsla(0, 35%, 25%, 0.6)' : '#fdecea',
|
|
text: mode === 'dark' ? 'hsl(0, 60%, 80%)' : '#b71c1c',
|
|
},
|
|
inflows: {
|
|
primary: mode === 'dark' ? 'hsl(120, 40%, 55%)' : '#2e7d32',
|
|
surface: mode === 'dark' ? 'hsla(120, 25%, 22%, 0.6)' : '#e8f5e9',
|
|
text: mode === 'dark' ? 'hsl(120, 40%, 78%)' : '#1b5e20',
|
|
},
|
|
},
|
|
},
|
|
typography: {
|
|
fontFamily: 'Inter, sans-serif',
|
|
h1: {
|
|
fontSize: defaultTheme.typography.pxToRem(48),
|
|
fontWeight: 600,
|
|
lineHeight: 1.2,
|
|
letterSpacing: -0.5,
|
|
},
|
|
h2: {
|
|
fontSize: defaultTheme.typography.pxToRem(36),
|
|
fontWeight: 600,
|
|
lineHeight: 1.2,
|
|
},
|
|
h3: {
|
|
fontSize: defaultTheme.typography.pxToRem(30),
|
|
lineHeight: 1.2,
|
|
},
|
|
h4: {
|
|
fontSize: defaultTheme.typography.pxToRem(24),
|
|
fontWeight: 600,
|
|
lineHeight: 1.5,
|
|
},
|
|
h5: {
|
|
fontSize: defaultTheme.typography.pxToRem(20),
|
|
fontWeight: 600,
|
|
},
|
|
h6: {
|
|
fontSize: defaultTheme.typography.pxToRem(18),
|
|
fontWeight: 600,
|
|
},
|
|
subtitle1: {
|
|
fontSize: defaultTheme.typography.pxToRem(18),
|
|
},
|
|
subtitle2: {
|
|
fontSize: defaultTheme.typography.pxToRem(14),
|
|
fontWeight: 500,
|
|
},
|
|
body1: {
|
|
fontSize: defaultTheme.typography.pxToRem(14),
|
|
},
|
|
body2: {
|
|
fontSize: defaultTheme.typography.pxToRem(14),
|
|
fontWeight: 400,
|
|
},
|
|
caption: {
|
|
fontSize: defaultTheme.typography.pxToRem(12),
|
|
fontWeight: 400,
|
|
},
|
|
},
|
|
shape: {
|
|
borderRadius: 8,
|
|
},
|
|
shadows: customShadows,
|
|
};
|
|
};
|
|
|
|
export const colorSchemes = {
|
|
light: {
|
|
palette: {
|
|
primary: {
|
|
light: brand[200],
|
|
main: brand[400],
|
|
dark: brand[700],
|
|
contrastText: brand[50],
|
|
},
|
|
info: {
|
|
light: brand[100],
|
|
main: brand[300],
|
|
dark: brand[600],
|
|
contrastText: gray[50],
|
|
},
|
|
warning: {
|
|
light: orange[300],
|
|
main: orange[400],
|
|
dark: orange[800],
|
|
},
|
|
error: {
|
|
light: red[300],
|
|
main: red[400],
|
|
dark: red[800],
|
|
},
|
|
success: {
|
|
light: green[300],
|
|
main: green[400],
|
|
dark: green[800],
|
|
},
|
|
grey: {
|
|
...gray,
|
|
},
|
|
divider: alpha(gray[300], 0.4),
|
|
background: {
|
|
default: 'hsl(0, 0%, 99%)',
|
|
paper: 'hsl(220, 35%, 97%)',
|
|
},
|
|
text: {
|
|
primary: gray[800],
|
|
secondary: gray[600],
|
|
warning: orange[400],
|
|
},
|
|
action: {
|
|
hover: alpha(gray[200], 0.2),
|
|
selected: `${alpha(gray[200], 0.3)}`,
|
|
},
|
|
flows: {
|
|
outflows: {
|
|
primary: '#d32f2f',
|
|
surface: '#fdecea',
|
|
text: '#b71c1c',
|
|
},
|
|
inflows: {
|
|
primary: '#2e7d32',
|
|
surface: '#e8f5e9',
|
|
text: '#1b5e20',
|
|
},
|
|
},
|
|
baseShadow:
|
|
'hsla(220, 30%, 5%, 0.07) 0px 4px 16px 0px, hsla(220, 25%, 10%, 0.07) 0px 8px 16px -5px',
|
|
},
|
|
},
|
|
dark: {
|
|
palette: {
|
|
primary: {
|
|
contrastText: brand[50],
|
|
light: 'hsl(210, 50%, 65%)',
|
|
main: 'hsl(210, 55%, 55%)',
|
|
dark: 'hsl(210, 50%, 35%)',
|
|
},
|
|
info: {
|
|
contrastText: 'hsl(210, 30%, 80%)',
|
|
light: 'hsl(210, 40%, 50%)',
|
|
main: 'hsl(210, 35%, 40%)',
|
|
dark: 'hsl(210, 30%, 25%)',
|
|
},
|
|
warning: {
|
|
light: 'hsl(45, 60%, 55%)',
|
|
main: 'hsl(45, 55%, 45%)',
|
|
dark: 'hsl(45, 50%, 30%)',
|
|
},
|
|
error: {
|
|
light: 'hsl(0, 55%, 60%)',
|
|
main: 'hsl(0, 55%, 50%)',
|
|
dark: 'hsl(0, 50%, 35%)',
|
|
},
|
|
success: {
|
|
light: 'hsl(120, 40%, 55%)',
|
|
main: 'hsl(120, 40%, 45%)',
|
|
dark: 'hsl(120, 35%, 30%)',
|
|
},
|
|
grey: {
|
|
...gray,
|
|
},
|
|
divider: 'hsla(0, 0%, 100%, 0.08)',
|
|
background: {
|
|
default: darkBg,
|
|
paper: darkPaper,
|
|
},
|
|
text: {
|
|
primary: 'hsl(0, 0%, 92%)',
|
|
secondary: 'hsl(0, 0%, 60%)',
|
|
},
|
|
action: {
|
|
hover: 'hsla(0, 0%, 100%, 0.06)',
|
|
selected: 'hsla(0, 0%, 100%, 0.1)',
|
|
},
|
|
flows: {
|
|
outflows: {
|
|
primary: 'hsl(0, 55%, 60%)',
|
|
surface: 'hsla(0, 35%, 25%, 0.6)',
|
|
text: 'hsl(0, 60%, 80%)',
|
|
},
|
|
inflows: {
|
|
primary: 'hsl(120, 40%, 55%)',
|
|
surface: 'hsla(120, 25%, 22%, 0.6)',
|
|
text: 'hsl(120, 40%, 78%)',
|
|
},
|
|
},
|
|
baseShadow: '0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.3)',
|
|
},
|
|
},
|
|
};
|
|
|
|
export const typography = {
|
|
fontFamily: 'Inter, sans-serif',
|
|
h1: {
|
|
fontSize: defaultTheme.typography.pxToRem(48),
|
|
fontWeight: 600,
|
|
lineHeight: 1.2,
|
|
letterSpacing: -0.5,
|
|
},
|
|
h2: {
|
|
fontSize: defaultTheme.typography.pxToRem(36),
|
|
fontWeight: 600,
|
|
lineHeight: 1.2,
|
|
},
|
|
h3: {
|
|
fontSize: defaultTheme.typography.pxToRem(30),
|
|
lineHeight: 1.2,
|
|
},
|
|
h4: {
|
|
fontSize: defaultTheme.typography.pxToRem(24),
|
|
fontWeight: 600,
|
|
lineHeight: 1.5,
|
|
},
|
|
h5: {
|
|
fontSize: defaultTheme.typography.pxToRem(20),
|
|
fontWeight: 600,
|
|
},
|
|
h6: {
|
|
fontSize: defaultTheme.typography.pxToRem(18),
|
|
fontWeight: 600,
|
|
},
|
|
subtitle1: {
|
|
fontSize: defaultTheme.typography.pxToRem(18),
|
|
},
|
|
subtitle2: {
|
|
fontSize: defaultTheme.typography.pxToRem(14),
|
|
fontWeight: 500,
|
|
},
|
|
body1: {
|
|
fontSize: defaultTheme.typography.pxToRem(14),
|
|
},
|
|
body2: {
|
|
fontSize: defaultTheme.typography.pxToRem(14),
|
|
fontWeight: 400,
|
|
},
|
|
caption: {
|
|
fontSize: defaultTheme.typography.pxToRem(12),
|
|
fontWeight: 400,
|
|
},
|
|
};
|
|
|
|
export const shape = {
|
|
borderRadius: 8,
|
|
};
|
|
|
|
// @ts-ignore
|
|
const defaultShadows: Shadows = [
|
|
'none',
|
|
'var(--template-palette-baseShadow)',
|
|
...defaultTheme.shadows.slice(2),
|
|
];
|
|
export const shadows = defaultShadows;
|