Files
jobtrackingapp/job-tracker-ui/src/theme.ts
T
2026-03-27 19:27:18 +01:00

350 lines
9.3 KiB
TypeScript

import { alpha, createTheme, darken, lighten } from "@mui/material/styles";
type PaletteLike = Record<string, any>;
function buildPrimary(main: string) {
return {
lighter: lighten(main, 0.82),
light: lighten(main, 0.56),
main,
dark: darken(main, 0.22),
darker: darken(main, 0.72),
};
}
function buildLightPalette(accentColor: string): PaletteLike {
const textPrimary = "#1B1B1F";
const textSecondary = "#46464F";
const secondaryMain = "#5A5C78";
const divider = "#EFEDF4";
const background = "#FFFFFF";
const disabled = "#777680";
const disabledBackground = "#E4E1E6";
return {
primary: buildPrimary(accentColor || "#15803D"),
secondary: {
lighter: "#E0E0FF",
light: "#C3C4E4",
main: secondaryMain,
dark: "#43455F",
darker: "#171A31",
},
error: {
lighter: "#FFEDEA",
light: "#FFDAD6",
main: "#DE3730",
dark: "#BA1A1A",
darker: "#690005",
},
warning: {
lighter: "#FFEEE1",
light: "#FFDCBE",
main: "#AE6600",
dark: "#8B5000",
darker: "#4A2800",
},
success: {
lighter: "#DCFCE7",
light: "#BBF7D0",
main: "#16A34A",
dark: "#15803D",
darker: "#14532D",
},
info: {
lighter: "#D4F7FF",
light: "#A1EFFF",
main: "#008394",
dark: "#006876",
darker: "#00363E",
},
grey: {
50: "#FBF8FF",
100: "#F5F2FA",
200: divider,
300: "#EAE7EF",
400: disabledBackground,
500: "#DBD9E0",
600: "#C7C5D0",
700: disabled,
800: textSecondary,
900: textPrimary,
},
text: {
primary: textPrimary,
secondary: textSecondary,
disabled,
},
divider,
background: { default: background, paper: background },
action: {
hover: alpha(accentColor || "#15803D", 0.05),
disabled: alpha(disabled, 0.6),
disabledBackground: alpha(disabledBackground, 0.9),
},
};
}
function buildDarkPalette(accentColor: string): PaletteLike {
const bg = "#0B0B0E";
const paper = "#111116";
const divider = alpha("#FFFFFF", 0.10);
const textPrimary = "#EAEAF2";
const textSecondary = alpha(textPrimary, 0.72);
const secondaryMain = "#A4A6C8";
const disabled = alpha(textPrimary, 0.5);
const disabledBackground = alpha("#FFFFFF", 0.08);
return {
primary: buildPrimary(accentColor || "#15803D"),
secondary: {
lighter: alpha(secondaryMain, 0.22),
light: alpha(secondaryMain, 0.14),
main: secondaryMain,
dark: alpha(secondaryMain, 0.8),
darker: alpha(secondaryMain, 0.95),
},
error: {
lighter: alpha("#DE3730", 0.18),
light: alpha("#DE3730", 0.12),
main: "#FF5A52",
dark: "#FF3B33",
darker: "#FFC2BE",
},
warning: {
lighter: alpha("#AE6600", 0.18),
light: alpha("#AE6600", 0.12),
main: "#FFB14A",
dark: "#FF9A1F",
darker: "#FFE1B8",
},
success: {
lighter: alpha("#16A34A", 0.18),
light: alpha("#16A34A", 0.12),
main: "#4ADE80",
dark: "#22C55E",
darker: "#BBF7D0",
},
info: {
lighter: alpha("#008394", 0.18),
light: alpha("#008394", 0.12),
main: "#22D3EE",
dark: "#06B6D4",
darker: "#BAF3FF",
},
grey: {
50: bg,
100: "#0F0F13",
200: "#15151B",
300: "#1C1C24",
400: "#23232D",
500: "#2D2D3A",
600: "#3A3A4A",
700: "#4A4A5D",
800: "#B8B8C6",
900: textPrimary,
},
text: {
primary: textPrimary,
secondary: textSecondary,
disabled,
},
divider,
background: { default: bg, paper },
action: {
hover: alpha(accentColor || "#15803D", 0.16),
disabled: alpha("#FFFFFF", 0.5),
disabledBackground,
},
};
}
function buildCustomShadows(palette: any) {
const shadowColor = palette.text.primary;
const primaryColor = palette.primary.main;
return {
button: `0px 0.711px 1.422px 0px ${alpha(shadowColor, 0.05)}`,
section: `0px 1px 2px 0px ${alpha(shadowColor, 0.07)}`,
tooltip: `0px 12px 16px -4px ${alpha(shadowColor, 0.08)}, 0px 4px 6px -2px ${alpha(shadowColor, 0.03)}`,
focus: `0px 0px 0px 3px ${alpha(primaryColor, 0.2)}`,
};
}
function buildTypography() {
return {
fontFamily: "'Archivo', system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif",
letterSpacing: 0,
h1: { fontWeight: 500, fontSize: 36, lineHeight: "40px" },
h2: { fontWeight: 500, fontSize: 30, lineHeight: "34px" },
h3: { fontWeight: 500, fontSize: 26, lineHeight: "30px" },
h4: { fontWeight: 500, fontSize: 22, lineHeight: "26px" },
h5: { fontWeight: 500, fontSize: 18, lineHeight: "22px" },
h6: { fontWeight: 500, fontSize: 16, lineHeight: "20px" },
subtitle1: { fontWeight: 500, fontSize: 14, lineHeight: "18px" },
subtitle2: { fontWeight: 500, fontSize: 13, lineHeight: "17px" },
body1: { fontWeight: 400, fontSize: 14, lineHeight: "18px" },
body2: { fontWeight: 400, fontSize: 13, lineHeight: "17px" },
caption: { fontWeight: 400, fontSize: 12, lineHeight: "16px", letterSpacing: 0 },
overline: { fontWeight: 600, fontSize: 11, lineHeight: "14px", letterSpacing: "0.08em", textTransform: "uppercase" as const },
caption1: { fontWeight: 500, fontSize: 12, lineHeight: "16px", letterSpacing: 0 },
button: { textTransform: "capitalize" as const },
};
}
export const getTheme = (_mode: "light" | "dark", accentColor: string) => {
const lightPalette = buildLightPalette(accentColor);
const darkPalette = buildDarkPalette(accentColor);
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 768,
md: 1024,
lg: 1266,
xl: 1440,
},
},
cssVariables: {
cssVarPrefix: "",
colorSchemeSelector: "data-color-scheme",
},
colorSchemes: {
light: { palette: lightPalette, customShadows: buildCustomShadows(lightPalette) },
dark: { palette: darkPalette, customShadows: buildCustomShadows(darkPalette) },
},
shape: { borderRadius: 8 },
typography: buildTypography() as any,
} as any) as any;
theme.components = {
MuiCssBaseline: {
styleOverrides: {
body: {
WebkitFontSmoothing: "antialiased",
MozOsxFontSmoothing: "grayscale",
},
},
},
MuiPaper: {
defaultProps: { elevation: 0 },
styleOverrides: {
root: ({ theme }: any) => ({
border: `1px solid ${theme.vars.palette.divider}`,
boxShadow: "none",
backgroundImage: "none",
}),
},
},
MuiCard: {
defaultProps: { elevation: 0 },
styleOverrides: {
root: ({ theme }: any) => ({
border: `1px solid ${theme.vars.palette.divider}`,
borderRadius: 12,
backgroundImage: "none",
boxShadow: theme.vars.customShadows.section,
}),
},
},
MuiButton: {
defaultProps: { disableFocusRipple: true },
styleOverrides: {
root: ({ theme }: any) => ({
borderRadius: 8,
boxShadow: theme.vars.customShadows.button,
"&:hover": { boxShadow: theme.vars.customShadows.button },
}),
},
},
MuiTextField: {
defaultProps: {
size: "small",
variant: "outlined",
},
},
MuiOutlinedInput: {
defaultProps: { size: "small" },
styleOverrides: {
root: ({ theme }: any) => ({
borderRadius: 8,
boxShadow: theme.vars.customShadows.button,
background: theme.vars.palette.background.default,
paddingLeft: 10,
paddingRight: 10,
minHeight: 42,
display: "flex",
alignItems: "center",
"&.Mui-disabled": {
cursor: "not-allowed",
input: { cursor: "not-allowed" },
"& .MuiOutlinedInput-notchedOutline": { borderColor: theme.vars.palette.divider },
},
}),
notchedOutline: ({ theme }: any) => ({ borderColor: theme.vars.palette.divider }),
multiline: {
padding: 10,
alignItems: "flex-start",
minHeight: "unset",
},
input: {
paddingLeft: 0,
paddingRight: 0,
paddingTop: 9,
paddingBottom: 9,
lineHeight: 1.45,
display: "flex",
alignItems: "center",
},
inputMultiline: {
paddingTop: 0,
paddingBottom: 0,
lineHeight: 1.5,
},
},
},
MuiInputBase: {
styleOverrides: {
input: {
"&::placeholder": {
opacity: 0.72,
},
},
inputMultiline: {
"&::placeholder": {
opacity: 0.72,
},
},
},
},
MuiListItemButton: {
styleOverrides: {
root: ({ theme }: any) => ({
borderRadius: 8,
"&.Mui-selected": {
backgroundColor: theme.vars.palette.action.hover,
},
}),
},
},
MuiTableCell: {
styleOverrides: {
head: ({ theme }: any) => ({
fontWeight: 600,
color: theme.vars.palette.text.primary,
background: theme.vars.palette.grey[100],
}),
root: {
paddingTop: 10,
paddingBottom: 10,
},
},
},
} as any;
return theme;
};