/*
* SPDX-FileCopyrightText: 2021-2023 The Refinery Authors
*
* SPDX-License-Identifier: EPL-2.0
*/
import {
alpha,
createTheme,
responsiveFontSizes,
type Theme,
type ThemeOptions,
ThemeProvider as MaterialUiThemeProvider,
type TypographyStyle,
type CSSObject,
} from '@mui/material/styles';
import { observer } from 'mobx-react-lite';
import { type ReactNode, createContext, useContext } from 'react';
import { useRootStore } from '../RootStoreProvider';
interface OuterPalette {
background: string;
border: string;
}
interface HighlightPalette {
number: string;
parameter: string;
comment: string;
activeLine: string;
selection: string;
foldPlaceholder: string;
activeLintRange: string;
occurences: {
read: string;
write: string;
};
search: {
match: string;
selected: string;
contrastText: string;
};
}
declare module '@mui/material/styles' {
interface TypographyVariants {
fontWeightEditorNormal: number;
fontWeightEditorBold: number;
editor: TypographyStyle;
}
interface TypographyVariantsOptions {
fontWeightEditorNormal?: number;
fontWeightEditorBold?: number;
editor?: TypographyStyle;
}
interface Palette {
outer: OuterPalette;
highlight: HighlightPalette;
}
interface PaletteOptions {
outer?: Partial;
highlight?: Partial;
}
}
function createResponsiveTheme(
options: ThemeOptions,
overrides: ThemeOptions = {},
): Theme {
const theme = createTheme({
...options,
typography: {
fontFamily:
'"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif',
fontWeightMedium: 600,
fontWeightEditorNormal: 400,
fontWeightEditorBold: 700,
button: {
// 24px line height for 14px button text to fix browser rounding errors.
lineHeight: 1.714286,
},
editor: {
fontFamily:
'"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace',
fontFeatureSettings: '"liga", "calt"',
// `rem` for JetBrains MonoVariable make the text too large in Safari.
fontSize: '16px',
fontWeight: 400,
lineHeight: 1.5,
letterSpacing: 0,
textRendering: 'optimizeLegibility',
},
...(options.typography ?? {}),
},
});
function shadedButtonStyle(color: string): CSSObject {
const opacity = theme.palette.action.focusOpacity;
return {
background: alpha(color, opacity),
':hover': {
background: alpha(color, opacity + theme.palette.action.hoverOpacity),
'@media(hover: none)': {
background: alpha(color, opacity),
},
},
'&.Mui-disabled': {
background: alpha(theme.palette.text.disabled, opacity),
},
};
}
const themeWithComponents = createTheme(theme, {
components: {
MuiCssBaseline: {
styleOverrides: {
body: {
overscrollBehavior: 'contain',
},
},
},
MuiButton: {
styleOverrides: {
root: {
'&.rounded': { borderRadius: '50em' },
'.MuiButton-startIcon': { marginRight: 6 },
'.MuiButton-endIcon': { marginLeft: 6 },
'&.shaded': {
...shadedButtonStyle(theme.palette.text.primary),
...(
[
'primary',
'secondary',
'error',
'warning',
'success',
'info',
] as const
).reduce((accumulator: CSSObject, color) => {
const colorCapitalized =
(color[0] ?? '').toUpperCase() + color.substring(1);
return {
...accumulator,
[`&.MuiButton-text${colorCapitalized}, &.MuiButton-outlined${colorCapitalized}`]:
shadedButtonStyle(theme.palette[color].main),
};
}, {}),
},
},
sizeSmall: { fontSize: '0.75rem' },
sizeLarge: { fontSize: '1rem' },
text: { '&.rounded': { padding: '6px 14px' } },
textSizeSmall: { '&.rounded': { padding: '4px 8px' } },
textSizeLarge: { '&.rounded': { padding: '8px 20px' } },
outlined: { '&.rounded': { padding: '5px 13px' } },
outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } },
outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } },
},
},
MuiToggleButton: {
styleOverrides: {
root: { '&.iconOnly': { borderRadius: '100%' } },
},
},
MuiToggleButtonGroup: {
styleOverrides: {
root: {
'&.rounded .MuiToggleButtonGroup-groupedHorizontal': {
':first-of-type': {
paddingLeft: 15,
borderRadius: '50em 0 0 50em',
},
':last-of-type': {
paddingRight: 15,
borderRadius: '0 50em 50em 0',
},
'&.MuiToggleButton-sizeSmall': {
':first-of-type': { paddingLeft: 9 },
':last-of-type': { paddingRight: 9 },
},
'&.MuiToggleButton-sizeLarge': {
':first-of-type': { paddingLeft: 21 },
':last-of-type': { paddingRight: 21 },
},
},
},
},
},
MuiTooltip: {
styleOverrides: {
tooltip: {
background: alpha('#212121', 0.93),
color: '#fff',
},
arrow: {
color: alpha('#212121', 0.93),
},
},
},
},
});
const themeWithOverrides = createTheme(themeWithComponents, overrides);
return responsiveFontSizes(themeWithOverrides);
}
const lightTheme = (() => {
const primaryText = '#19202b';
const disabledText = '#a0a1a7';
const darkBackground = '#f5f5f5';
return createResponsiveTheme({
palette: {
mode: 'light',
primary: { main: '#038a99' },
secondary: { main: '#e45649' },
error: { main: '#ca1243' },
warning: { main: '#c18401' },
success: { main: '#50a14f' },
info: { main: '#4078f2' },
background: {
default: '#fff',
paper: '#fff',
},
text: {
primary: primaryText,
secondary: '#696c77',
disabled: disabledText,
},
divider: alpha(primaryText, 0.16),
outer: {
background: darkBackground,
border: '#c8c8c8',
},
highlight: {
number: '#0084bc',
parameter: '#6a3e3e',
comment: disabledText,
activeLine: darkBackground,
selection: '#c8e4fb',
foldPlaceholder: alpha(primaryText, 0.08),
activeLintRange: alpha('#f2a60d', 0.28),
occurences: {
read: alpha(primaryText, 0.16),
write: alpha(primaryText, 0.16),
},
search: {
match: '#00bcd4',
selected: '#d500f9',
contrastText: '#fff',
},
},
},
});
})();
const darkTheme = (() => {
const primaryText = '#ebebff';
const secondaryText = '#abb2bf';
const darkBackground = '#21252b';
return createResponsiveTheme(
{
typography: {
fontWeightEditorNormal: 350,
fontWeightEditorBold: 650,
},
palette: {
mode: 'dark',
primary: { main: '#56b6c2' },
secondary: { main: '#be5046' },
error: { main: '#e06c75' },
warning: { main: '#e5c07b' },
success: { main: '#98c379' },
info: { main: '#61afef' },
background: {
default: '#282c34',
paper: darkBackground,
},
text: {
primary: primaryText,
secondary: secondaryText,
disabled: '#5c6370',
},
divider: alpha(secondaryText, 0.24),
outer: {
background: darkBackground,
border: '#181a1f',
},
highlight: {
number: '#6188a6',
parameter: '#c8ae9d',
comment: '#7f848e',
activeLine: '#2c313c',
selection: '#404859',
foldPlaceholder: alpha(primaryText, 0.12),
activeLintRange: alpha('#fbc346', 0.28),
occurences: {
read: alpha(primaryText, 0.14),
write: alpha(primaryText, 0.14),
},
search: {
match: '#33eaff',
selected: '#dd33fa',
contrastText: darkBackground,
},
},
},
},
{
components: {
MuiTooltip: {
styleOverrides: {
tooltip: {
color: primaryText,
},
},
},
},
},
);
})();
const ContrastThemeContext = createContext(undefined);
function ThemeAndContrastThemeProvider({
theme,
contrastTheme,
children,
}: {
theme: Theme;
contrastTheme: Theme;
children?: ReactNode;
}): JSX.Element {
return (
{children}
);
}
ThemeAndContrastThemeProvider.defaultProps = {
children: undefined,
};
export function ContrastThemeProvider({
children,
}: {
children?: ReactNode;
}): JSX.Element {
const contrastTheme = useContext(ContrastThemeContext);
if (!contrastTheme) {
throw new Error('ContrastThemeProvider must be used within ThemeProvider');
}
return (
{children}
);
}
ContrastThemeProvider.defaultProps = {
children: undefined,
};
const ThemeProvider = observer(function ThemeProvider({
children,
}: {
children?: ReactNode;
}): JSX.Element {
const {
themeStore: { darkMode },
} = useRootStore();
return (
{children}
);
});
ThemeProvider.defaultProps = {
children: undefined,
};
export default ThemeProvider;