mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 16:18:57 +00:00
296 lines
8.6 KiB
TypeScript
296 lines
8.6 KiB
TypeScript
|
|
import { createTheme } from '@mui/material/styles';
|
|||
|
|
import type { Shadows } from '@mui/material/styles';
|
|||
|
|
import { getCssVar, CSS_VAR_FALLBACKS } from './css-vars';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* MUI adapter over the CSS-variable token file (src/styles/theme.css).
|
|||
|
|
* Mirrors shoc-frontend-new dev src/lib/theme/mui-theme.ts (ADR 0003).
|
|||
|
|
* Token changes are values-only edits in theme.css — never here.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
declare module '@mui/material/styles' {
|
|||
|
|
interface Palette {
|
|||
|
|
header: Palette['primary'];
|
|||
|
|
}
|
|||
|
|
interface PaletteOptions {
|
|||
|
|
header?: PaletteOptions['primary'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const v = (name: string, fallback: string) => getCssVar(name, fallback);
|
|||
|
|
|
|||
|
|
const primary = v('--primary', CSS_VAR_FALLBACKS.primary);
|
|||
|
|
const primaryHover = v('--primary-hover', CSS_VAR_FALLBACKS.primaryHover);
|
|||
|
|
const primaryForeground = v('--primary-foreground', CSS_VAR_FALLBACKS.primaryForeground);
|
|||
|
|
const destructive = v('--destructive', CSS_VAR_FALLBACKS.destructive);
|
|||
|
|
const success = v('--success', CSS_VAR_FALLBACKS.success);
|
|||
|
|
const warning = v('--warning', CSS_VAR_FALLBACKS.warning);
|
|||
|
|
const background = v('--background', CSS_VAR_FALLBACKS.background);
|
|||
|
|
const foreground = v('--foreground', CSS_VAR_FALLBACKS.foreground);
|
|||
|
|
const border = v('--border', CSS_VAR_FALLBACKS.border);
|
|||
|
|
const muted = v('--muted', CSS_VAR_FALLBACKS.muted);
|
|||
|
|
const mutedForeground = v('--muted-foreground', CSS_VAR_FALLBACKS.mutedForeground);
|
|||
|
|
const inputBackground = v('--input-background', CSS_VAR_FALLBACKS.inputBackground);
|
|||
|
|
const ring = v('--ring', CSS_VAR_FALLBACKS.ring);
|
|||
|
|
const bgPage = v('--color-bg-page', CSS_VAR_FALLBACKS.bgPage);
|
|||
|
|
const radius = parseInt(v('--radius', `${CSS_VAR_FALLBACKS.radius}px`), 10);
|
|||
|
|
const headerEnd = v('--color-header-bg-end', CSS_VAR_FALLBACKS.headerEnd);
|
|||
|
|
const headerText = v('--color-header-text', CSS_VAR_FALLBACKS.headerText);
|
|||
|
|
|
|||
|
|
const shadowSm = v('--shadow-sm', '0 1px 3px rgba(22, 30, 56, 0.06), 0 1px 2px rgba(22, 30, 56, 0.04)');
|
|||
|
|
const shadowMd = v('--shadow-md', '0 4px 6px -1px rgba(22, 30, 56, 0.08), 0 2px 4px -2px rgba(22, 30, 56, 0.06)');
|
|||
|
|
const shadowLg = v('--shadow-lg', '0 10px 15px -3px rgba(22, 30, 56, 0.1), 0 4px 6px -4px rgba(22, 30, 56, 0.08)');
|
|||
|
|
|
|||
|
|
const fontSans = '"DM Sans", ui-sans-serif, system-ui, sans-serif';
|
|||
|
|
const fontDisplay = '"Montserrat", ui-sans-serif, system-ui, sans-serif';
|
|||
|
|
|
|||
|
|
// MUI's 25-slot elevation tuple, fully overridden: 0=none, 1–2=sm, 3–5=md, 6+=lg.
|
|||
|
|
const shadows = [
|
|||
|
|
'none',
|
|||
|
|
shadowSm,
|
|||
|
|
shadowSm,
|
|||
|
|
shadowMd,
|
|||
|
|
shadowMd,
|
|||
|
|
shadowMd,
|
|||
|
|
...Array<string>(19).fill(shadowLg),
|
|||
|
|
] as Shadows;
|
|||
|
|
|
|||
|
|
export const muiTheme = createTheme({
|
|||
|
|
palette: {
|
|||
|
|
mode: 'light',
|
|||
|
|
primary: {
|
|||
|
|
main: primary,
|
|||
|
|
dark: primaryHover,
|
|||
|
|
contrastText: primaryForeground,
|
|||
|
|
},
|
|||
|
|
// Repurposed as the muted/neutral swatch (SHOC convention), not a brand color.
|
|||
|
|
secondary: {
|
|||
|
|
main: muted,
|
|||
|
|
contrastText: foreground,
|
|||
|
|
},
|
|||
|
|
error: {
|
|||
|
|
main: destructive,
|
|||
|
|
contrastText: '#ffffff',
|
|||
|
|
},
|
|||
|
|
success: {
|
|||
|
|
main: success,
|
|||
|
|
contrastText: '#ffffff',
|
|||
|
|
},
|
|||
|
|
warning: {
|
|||
|
|
main: warning,
|
|||
|
|
contrastText: '#ffffff',
|
|||
|
|
},
|
|||
|
|
background: {
|
|||
|
|
default: bgPage,
|
|||
|
|
paper: background,
|
|||
|
|
},
|
|||
|
|
text: {
|
|||
|
|
primary: foreground,
|
|||
|
|
secondary: mutedForeground,
|
|||
|
|
},
|
|||
|
|
divider: border,
|
|||
|
|
header: {
|
|||
|
|
main: headerEnd,
|
|||
|
|
contrastText: headerText,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
typography: {
|
|||
|
|
fontFamily: fontSans,
|
|||
|
|
fontSize: 14,
|
|||
|
|
fontWeightRegular: 400,
|
|||
|
|
fontWeightMedium: 500,
|
|||
|
|
fontWeightBold: 600,
|
|||
|
|
h1: { fontFamily: fontDisplay, fontWeight: 800, fontSize: '1.75rem', lineHeight: 1.25 },
|
|||
|
|
h2: { fontFamily: fontDisplay, fontWeight: 700, fontSize: '1.375rem', lineHeight: 1.25 },
|
|||
|
|
h3: { fontFamily: fontDisplay, fontWeight: 600, fontSize: '1.125rem', lineHeight: 1.25 },
|
|||
|
|
h4: { fontFamily: fontDisplay, fontWeight: 600, fontSize: '1rem', lineHeight: 1.25 },
|
|||
|
|
h5: { fontFamily: fontDisplay, fontWeight: 600, fontSize: '0.875rem', lineHeight: 1.25 },
|
|||
|
|
h6: { fontFamily: fontDisplay, fontWeight: 600, fontSize: '0.8125rem', lineHeight: 1.25 },
|
|||
|
|
subtitle1: { fontWeight: 600, fontSize: '1rem' },
|
|||
|
|
subtitle2: { fontWeight: 500, fontSize: '0.875rem', color: mutedForeground },
|
|||
|
|
body1: { fontSize: '0.875rem', lineHeight: 1.5 },
|
|||
|
|
body2: { fontSize: '0.8125rem', lineHeight: 1.5, color: mutedForeground },
|
|||
|
|
caption: { fontSize: '0.75rem', color: mutedForeground },
|
|||
|
|
button: { textTransform: 'none' as const, fontWeight: 600, fontSize: '0.875rem' },
|
|||
|
|
overline: {
|
|||
|
|
fontSize: '0.6875rem',
|
|||
|
|
fontWeight: 600,
|
|||
|
|
letterSpacing: '0.08em',
|
|||
|
|
textTransform: 'uppercase' as const,
|
|||
|
|
color: mutedForeground,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
shape: {
|
|||
|
|
borderRadius: radius,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
shadows,
|
|||
|
|
|
|||
|
|
components: {
|
|||
|
|
MuiCssBaseline: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
body: {
|
|||
|
|
fontSize: 14,
|
|||
|
|
backgroundColor: bgPage,
|
|||
|
|
color: foreground,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiButton: {
|
|||
|
|
defaultProps: {
|
|||
|
|
disableElevation: true,
|
|||
|
|
},
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
borderRadius: 'var(--radius-md)',
|
|||
|
|
textTransform: 'none' as const,
|
|||
|
|
fontWeight: 600,
|
|||
|
|
fontSize: '0.875rem',
|
|||
|
|
transition: 'background-color var(--duration-fast) var(--ease-default), box-shadow var(--duration-fast) var(--ease-default)',
|
|||
|
|
'&:focus-visible': {
|
|||
|
|
outline: `2px solid ${ring}`,
|
|||
|
|
outlineOffset: 2,
|
|||
|
|
},
|
|||
|
|
// MUI v9: containedPrimary slot removed — class selector instead
|
|||
|
|
'&.MuiButton-containedPrimary:hover': {
|
|||
|
|
backgroundColor: primaryHover,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
outlined: {
|
|||
|
|
borderColor: border,
|
|||
|
|
'&:hover': {
|
|||
|
|
borderColor: 'var(--border-hover)',
|
|||
|
|
backgroundColor: 'var(--secondary)',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiTextField: {
|
|||
|
|
defaultProps: {
|
|||
|
|
size: 'small' as const,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiOutlinedInput: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
backgroundColor: inputBackground,
|
|||
|
|
borderRadius: 'var(--radius-md)',
|
|||
|
|
'& .MuiOutlinedInput-notchedOutline': {
|
|||
|
|
borderColor: border,
|
|||
|
|
},
|
|||
|
|
'&:hover .MuiOutlinedInput-notchedOutline': {
|
|||
|
|
borderColor: 'var(--border-hover)',
|
|||
|
|
},
|
|||
|
|
'&.Mui-focused': {
|
|||
|
|
boxShadow: `0 0 0 3px color-mix(in srgb, ${ring} 25%, transparent)`,
|
|||
|
|
},
|
|||
|
|
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
|||
|
|
borderColor: ring,
|
|||
|
|
borderWidth: 2,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
input: {
|
|||
|
|
padding: '8.5px 13px',
|
|||
|
|
fontSize: '0.875rem',
|
|||
|
|
'&.MuiOutlinedInput-inputAdornedEnd': {
|
|||
|
|
paddingRight: 40,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiTableCell: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
fontSize: '0.875rem',
|
|||
|
|
borderColor: border,
|
|||
|
|
padding: '10px 16px',
|
|||
|
|
},
|
|||
|
|
head: {
|
|||
|
|
fontWeight: 600,
|
|||
|
|
backgroundColor: 'var(--color-bg-muted)',
|
|||
|
|
color: foreground,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiTableRow: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
'&:last-child .MuiTableCell-root': {
|
|||
|
|
borderBottom: 0,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiChip: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
borderRadius: 'var(--radius-sm)',
|
|||
|
|
fontWeight: 500,
|
|||
|
|
fontSize: '0.75rem',
|
|||
|
|
},
|
|||
|
|
outlined: {
|
|||
|
|
borderColor: border,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiPaper: {
|
|||
|
|
defaultProps: {
|
|||
|
|
elevation: 0,
|
|||
|
|
},
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
backgroundImage: 'none',
|
|||
|
|
borderRadius: radius,
|
|||
|
|
},
|
|||
|
|
outlined: {
|
|||
|
|
borderColor: border,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiDialog: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
paper: {
|
|||
|
|
borderRadius: 'var(--radius-xl)',
|
|||
|
|
boxShadow: shadowLg,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
MuiAlert: {
|
|||
|
|
styleOverrides: {
|
|||
|
|
root: {
|
|||
|
|
borderRadius: 'var(--radius-md)',
|
|||
|
|
fontSize: '0.875rem',
|
|||
|
|
// MUI v9: standard* slots removed — class selectors instead
|
|||
|
|
'&.MuiAlert-standardSuccess': {
|
|||
|
|
backgroundColor: 'var(--status-completed-bg)',
|
|||
|
|
color: 'var(--status-completed-text)',
|
|||
|
|
},
|
|||
|
|
'&.MuiAlert-standardError': {
|
|||
|
|
backgroundColor: 'var(--doc-no-bg)',
|
|||
|
|
color: 'var(--doc-no-text)',
|
|||
|
|
},
|
|||
|
|
'&.MuiAlert-standardWarning': {
|
|||
|
|
backgroundColor: 'var(--status-pending-bg)',
|
|||
|
|
color: 'var(--status-pending-text)',
|
|||
|
|
},
|
|||
|
|
'&.MuiAlert-standardInfo': {
|
|||
|
|
backgroundColor: 'var(--color-primary-tint)',
|
|||
|
|
color: 'var(--sidebar-accent-foreground)',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|