diff options
Diffstat (limited to 'subprojects/frontend/src/theme/ThemeProvider.tsx')
-rw-r--r-- | subprojects/frontend/src/theme/ThemeProvider.tsx | 441 |
1 files changed, 242 insertions, 199 deletions
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 85844d3c..7bda1ede 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx | |||
@@ -1,15 +1,13 @@ | |||
1 | import { | 1 | import { |
2 | alpha, | 2 | alpha, |
3 | createTheme, | 3 | createTheme, |
4 | type Components, | ||
5 | type CSSObject, | ||
6 | responsiveFontSizes, | 4 | responsiveFontSizes, |
7 | type Theme, | 5 | type Theme, |
8 | type ThemeOptions, | 6 | type ThemeOptions, |
9 | ThemeProvider as MaterialUiThemeProvider, | 7 | ThemeProvider as MaterialUiThemeProvider, |
10 | type TypographyStyle, | 8 | type TypographyStyle, |
11 | type TypographyVariantsOptions, | ||
12 | useTheme, | 9 | useTheme, |
10 | type CSSObject, | ||
13 | } from '@mui/material/styles'; | 11 | } from '@mui/material/styles'; |
14 | import { observer } from 'mobx-react-lite'; | 12 | import { observer } from 'mobx-react-lite'; |
15 | import { type ReactNode, createContext, useContext } from 'react'; | 13 | import { type ReactNode, createContext, useContext } from 'react'; |
@@ -22,13 +20,11 @@ interface OuterPalette { | |||
22 | } | 20 | } |
23 | 21 | ||
24 | interface HighlightPalette { | 22 | interface HighlightPalette { |
25 | cursor: string; | ||
26 | number: string; | 23 | number: string; |
27 | parameter: string; | 24 | parameter: string; |
28 | comment: string; | 25 | comment: string; |
29 | activeLine: string; | 26 | activeLine: string; |
30 | selection: string; | 27 | selection: string; |
31 | lineNumber: string; | ||
32 | foldPlaceholder: string; | 28 | foldPlaceholder: string; |
33 | activeLintRange: string; | 29 | activeLintRange: string; |
34 | occurences: { | 30 | occurences: { |
@@ -49,11 +45,10 @@ declare module '@mui/material/styles' { | |||
49 | editor: TypographyStyle; | 45 | editor: TypographyStyle; |
50 | } | 46 | } |
51 | 47 | ||
52 | // eslint-disable-next-line @typescript-eslint/no-shadow -- Augment imported interface. | ||
53 | interface TypographyVariantsOptions { | 48 | interface TypographyVariantsOptions { |
54 | fontWeightEditorNormal: number; | 49 | fontWeightEditorNormal?: number; |
55 | fontWeightEditorBold: number; | 50 | fontWeightEditorBold?: number; |
56 | editor: TypographyStyle; | 51 | editor?: TypographyStyle; |
57 | } | 52 | } |
58 | 53 | ||
59 | interface Palette { | 54 | interface Palette { |
@@ -62,221 +57,269 @@ declare module '@mui/material/styles' { | |||
62 | } | 57 | } |
63 | 58 | ||
64 | interface PaletteOptions { | 59 | interface PaletteOptions { |
65 | outer: OuterPalette; | 60 | outer?: Partial<OuterPalette>; |
66 | highlight: HighlightPalette; | 61 | highlight?: Partial<HighlightPalette>; |
67 | } | 62 | } |
68 | } | 63 | } |
69 | 64 | ||
70 | const typography: TypographyVariantsOptions = { | 65 | function createResponsiveTheme( |
71 | fontFamily: | 66 | options: ThemeOptions, |
72 | '"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', | 67 | overrides: ThemeOptions = {}, |
73 | fontWeightMedium: 600, | 68 | ): Theme { |
74 | fontWeightEditorNormal: 400, | 69 | const theme = createTheme({ |
75 | fontWeightEditorBold: 700, | 70 | ...options, |
76 | button: { | 71 | typography: { |
77 | // 24px line height for 14px button text means 36px high buttons. | 72 | fontFamily: |
78 | // Making sure the button has whole pixel height reduces redering errors on Android. | 73 | '"InterVariable", "Inter", "Roboto", "Helvetica", "Arial", sans-serif', |
79 | lineHeight: 1.7143, | 74 | fontWeightMedium: 600, |
80 | }, | 75 | fontWeightEditorNormal: 400, |
81 | editor: { | 76 | fontWeightEditorBold: 700, |
82 | fontFamily: | 77 | button: { |
83 | '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', | 78 | // 24px line height for 14px button text to fix browser rounding errors. |
84 | fontFeatureSettings: '"liga", "calt"', | 79 | lineHeight: 1.714286, |
85 | // `rem` for JetBrains MonoVariable make the text too large in Safari. | ||
86 | fontSize: '16px', | ||
87 | fontWeight: 400, | ||
88 | lineHeight: 1.5, | ||
89 | letterSpacing: 0, | ||
90 | textRendering: 'optimizeLegibility', | ||
91 | }, | ||
92 | }; | ||
93 | |||
94 | const components: Components = { | ||
95 | MuiButton: { | ||
96 | styleOverrides: { | ||
97 | root: { | ||
98 | '&.rounded': { borderRadius: '50em' }, | ||
99 | '.MuiButton-startIcon': { marginRight: 6 }, | ||
100 | '.MuiButton-endIcon': { marginLeft: 6 }, | ||
101 | }, | 80 | }, |
102 | sizeSmall: { fontSize: '0.75rem' }, | 81 | editor: { |
103 | sizeLarge: { fontSize: '1rem' }, | 82 | fontFamily: |
104 | text: { '&.rounded': { padding: '6px 14px' } }, | 83 | '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', |
105 | textSizeSmall: { '&.rounded': { padding: '4px 8px' } }, | 84 | fontFeatureSettings: '"liga", "calt"', |
106 | textSizeLarge: { '&.rounded': { padding: '8px 20px' } }, | 85 | // `rem` for JetBrains MonoVariable make the text too large in Safari. |
107 | outlined: { '&.rounded': { padding: '5px 13px' } }, | 86 | fontSize: '16px', |
108 | outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } }, | 87 | fontWeight: 400, |
109 | outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } }, | 88 | lineHeight: 1.5, |
110 | }, | 89 | letterSpacing: 0, |
111 | }, | 90 | textRendering: 'optimizeLegibility', |
112 | MuiToggleButton: { | 91 | }, |
113 | styleOverrides: { | 92 | ...(options.typography ?? {}), |
114 | root: { '&.iconOnly': { borderRadius: '100%' } }, | ||
115 | }, | 93 | }, |
116 | }, | 94 | }); |
117 | MuiToggleButtonGroup: { | 95 | |
118 | styleOverrides: { | 96 | function shadedButtonStyle(color: string): CSSObject { |
119 | root: { | 97 | const opacity = theme.palette.action.focusOpacity; |
120 | '&.rounded .MuiToggleButtonGroup-groupedHorizontal': { | 98 | return { |
121 | ':first-of-type': { | 99 | background: alpha(color, opacity), |
122 | paddingLeft: 15, | 100 | ':hover': { |
123 | borderRadius: '50em 0 0 50em', | 101 | background: alpha(color, opacity + theme.palette.action.hoverOpacity), |
124 | }, | 102 | '@media(hover: none)': { |
125 | ':last-of-type': { | 103 | background: alpha(color, opacity), |
126 | paddingRight: 15, | 104 | }, |
127 | borderRadius: '0 50em 50em 0', | 105 | }, |
128 | }, | 106 | '&.Mui-disabled': { |
129 | '&.MuiToggleButton-sizeSmall': { | 107 | background: alpha(theme.palette.text.disabled, opacity), |
130 | ':first-of-type': { paddingLeft: 9 }, | 108 | }, |
131 | ':last-of-type': { paddingRight: 9 }, | 109 | }; |
110 | } | ||
111 | |||
112 | const themeWithComponents = createTheme(theme, { | ||
113 | components: { | ||
114 | MuiCssBaseline: { | ||
115 | styleOverrides: { | ||
116 | body: { | ||
117 | overscrollBehavior: 'contain', | ||
132 | }, | 118 | }, |
133 | '&.MuiToggleButton-sizeLarge': { | 119 | }, |
134 | ':first-of-type': { paddingLeft: 21 }, | 120 | }, |
135 | ':last-of-type': { paddingRight: 21 }, | 121 | MuiButton: { |
122 | styleOverrides: { | ||
123 | root: { | ||
124 | '&.rounded': { borderRadius: '50em' }, | ||
125 | '.MuiButton-startIcon': { marginRight: 6 }, | ||
126 | '.MuiButton-endIcon': { marginLeft: 6 }, | ||
127 | '&.shaded': { | ||
128 | ...shadedButtonStyle(theme.palette.text.primary), | ||
129 | ...( | ||
130 | [ | ||
131 | 'primary', | ||
132 | 'secondary', | ||
133 | 'error', | ||
134 | 'warning', | ||
135 | 'success', | ||
136 | 'info', | ||
137 | ] as const | ||
138 | ).reduce((accumulator: CSSObject, color) => { | ||
139 | const colorCapitalized = | ||
140 | (color[0] ?? '').toUpperCase() + color.substring(1); | ||
141 | return { | ||
142 | ...accumulator, | ||
143 | [`&.MuiButton-text${colorCapitalized}, &.MuiButton-outlined${colorCapitalized}`]: | ||
144 | shadedButtonStyle(theme.palette[color].main), | ||
145 | }; | ||
146 | }, {}), | ||
147 | }, | ||
136 | }, | 148 | }, |
149 | sizeSmall: { fontSize: '0.75rem' }, | ||
150 | sizeLarge: { fontSize: '1rem' }, | ||
151 | text: { '&.rounded': { padding: '6px 14px' } }, | ||
152 | textSizeSmall: { '&.rounded': { padding: '4px 8px' } }, | ||
153 | textSizeLarge: { '&.rounded': { padding: '8px 20px' } }, | ||
154 | outlined: { '&.rounded': { padding: '5px 13px' } }, | ||
155 | outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } }, | ||
156 | outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } }, | ||
137 | }, | 157 | }, |
138 | }, | 158 | }, |
139 | }, | 159 | MuiToggleButton: { |
140 | }, | 160 | styleOverrides: { |
141 | MuiTooltip: { | 161 | root: { '&.iconOnly': { borderRadius: '100%' } }, |
142 | styleOverrides: { | 162 | }, |
143 | tooltip: { | ||
144 | background: alpha('#212121', 0.93), | ||
145 | color: '#fff', | ||
146 | }, | 163 | }, |
147 | arrow: { | 164 | MuiToggleButtonGroup: { |
148 | color: alpha('#212121', 0.93), | 165 | styleOverrides: { |
166 | root: { | ||
167 | '&.rounded .MuiToggleButtonGroup-groupedHorizontal': { | ||
168 | ':first-of-type': { | ||
169 | paddingLeft: 15, | ||
170 | borderRadius: '50em 0 0 50em', | ||
171 | }, | ||
172 | ':last-of-type': { | ||
173 | paddingRight: 15, | ||
174 | borderRadius: '0 50em 50em 0', | ||
175 | }, | ||
176 | '&.MuiToggleButton-sizeSmall': { | ||
177 | ':first-of-type': { paddingLeft: 9 }, | ||
178 | ':last-of-type': { paddingRight: 9 }, | ||
179 | }, | ||
180 | '&.MuiToggleButton-sizeLarge': { | ||
181 | ':first-of-type': { paddingLeft: 21 }, | ||
182 | ':last-of-type': { paddingRight: 21 }, | ||
183 | }, | ||
184 | }, | ||
185 | }, | ||
186 | }, | ||
187 | }, | ||
188 | MuiTooltip: { | ||
189 | styleOverrides: { | ||
190 | tooltip: { | ||
191 | background: alpha('#212121', 0.93), | ||
192 | color: '#fff', | ||
193 | }, | ||
194 | arrow: { | ||
195 | color: alpha('#212121', 0.93), | ||
196 | }, | ||
197 | }, | ||
149 | }, | 198 | }, |
150 | }, | 199 | }, |
151 | }, | 200 | }); |
152 | }; | ||
153 | 201 | ||
154 | function createResponsiveTheme(options: ThemeOptions): Theme { | 202 | const themeWithOverrides = createTheme(themeWithComponents, overrides); |
155 | return responsiveFontSizes(createTheme(options)); | 203 | |
204 | return responsiveFontSizes(themeWithOverrides); | ||
156 | } | 205 | } |
157 | 206 | ||
158 | const lightTheme = createResponsiveTheme({ | 207 | const lightTheme = (() => { |
159 | typography, | 208 | const primaryText = '#19202b'; |
160 | components, | 209 | const disabledText = '#a0a1a7'; |
161 | palette: { | 210 | const darkBackground = '#f5f5f5'; |
162 | mode: 'light', | 211 | |
163 | primary: { main: '#038a99' }, | 212 | return createResponsiveTheme({ |
164 | secondary: { main: '#e45649' }, | 213 | palette: { |
165 | error: { main: '#ca1243' }, | 214 | mode: 'light', |
166 | warning: { main: '#c18401' }, | 215 | primary: { main: '#038a99' }, |
167 | success: { main: '#50a14f' }, | 216 | secondary: { main: '#e45649' }, |
168 | info: { main: '#4078f2' }, | 217 | error: { main: '#ca1243' }, |
169 | background: { | 218 | warning: { main: '#c18401' }, |
170 | default: '#fff', | 219 | success: { main: '#50a14f' }, |
171 | paper: '#fff', | 220 | info: { main: '#4078f2' }, |
172 | }, | 221 | background: { |
173 | text: { | 222 | default: '#fff', |
174 | primary: '#19202b', | 223 | paper: '#fff', |
175 | secondary: '#696c77', | ||
176 | disabled: '#a0a1a7', | ||
177 | }, | ||
178 | divider: alpha('#19202b', 0.16), | ||
179 | outer: { | ||
180 | background: '#f5f5f5', | ||
181 | border: '#c8c8c8', | ||
182 | }, | ||
183 | highlight: { | ||
184 | cursor: '#4078f2', | ||
185 | number: '#0084bc', | ||
186 | parameter: '#6a3e3e', | ||
187 | comment: '#a0a1a7', | ||
188 | activeLine: '#f5f5f5', | ||
189 | selection: '#c8e4fb', | ||
190 | lineNumber: '#a0a1a7', | ||
191 | foldPlaceholder: alpha('#19202b', 0.08), | ||
192 | activeLintRange: alpha('#f2a60d', 0.28), | ||
193 | occurences: { | ||
194 | read: alpha('#19202b', 0.16), | ||
195 | write: alpha('#19202b', 0.16), | ||
196 | }, | 224 | }, |
197 | search: { | 225 | text: { |
198 | match: '#00bcd4', | 226 | primary: primaryText, |
199 | selected: '#d500f9', | 227 | secondary: '#696c77', |
200 | contrastText: '#ffffff', | 228 | disabled: disabledText, |
201 | }, | 229 | }, |
202 | }, | 230 | divider: alpha(primaryText, 0.16), |
203 | }, | 231 | outer: { |
204 | }); | 232 | background: darkBackground, |
205 | 233 | border: '#c8c8c8', | |
206 | const darkTheme = createResponsiveTheme({ | 234 | }, |
207 | typography: { | 235 | highlight: { |
208 | ...typography, | 236 | number: '#0084bc', |
209 | fontWeightEditorNormal: 350, | 237 | parameter: '#6a3e3e', |
210 | fontWeightEditorBold: 650, | 238 | comment: disabledText, |
211 | }, | 239 | activeLine: darkBackground, |
212 | components: { | 240 | selection: '#c8e4fb', |
213 | ...components, | 241 | foldPlaceholder: alpha(primaryText, 0.08), |
214 | MuiSnackbarContent: { | 242 | activeLintRange: alpha('#f2a60d', 0.28), |
215 | styleOverrides: { | 243 | occurences: { |
216 | root: { | 244 | read: alpha(primaryText, 0.16), |
217 | color: '#f00', | 245 | write: alpha(primaryText, 0.16), |
218 | backgroundColor: '#000', | 246 | }, |
219 | border: `10px solid #ff0`, | 247 | search: { |
248 | match: '#00bcd4', | ||
249 | selected: '#d500f9', | ||
250 | contrastText: '#fff', | ||
220 | }, | 251 | }, |
221 | }, | 252 | }, |
222 | }, | 253 | }, |
223 | MuiTooltip: { | 254 | }); |
224 | ...(components.MuiTooltip || {}), | 255 | })(); |
225 | styleOverrides: { | 256 | |
226 | ...(components.MuiTooltip?.styleOverrides || {}), | 257 | const darkTheme = (() => { |
227 | tooltip: { | 258 | const primaryText = '#ebebff'; |
228 | ...((components.MuiTooltip?.styleOverrides?.tooltip as | 259 | const secondaryText = '#abb2bf'; |
229 | | CSSObject | 260 | const darkBackground = '#21252b'; |
230 | | undefined) || {}), | 261 | |
231 | color: '#ebebff', | 262 | return createResponsiveTheme( |
263 | { | ||
264 | typography: { | ||
265 | fontWeightEditorNormal: 350, | ||
266 | fontWeightEditorBold: 650, | ||
267 | }, | ||
268 | palette: { | ||
269 | mode: 'dark', | ||
270 | primary: { main: '#56b6c2' }, | ||
271 | secondary: { main: '#be5046' }, | ||
272 | error: { main: '#e06c75' }, | ||
273 | warning: { main: '#e5c07b' }, | ||
274 | success: { main: '#98c379' }, | ||
275 | info: { main: '#61afef' }, | ||
276 | background: { | ||
277 | default: '#282c34', | ||
278 | paper: darkBackground, | ||
279 | }, | ||
280 | text: { | ||
281 | primary: primaryText, | ||
282 | secondary: secondaryText, | ||
283 | disabled: '#5c6370', | ||
284 | }, | ||
285 | divider: alpha(secondaryText, 0.24), | ||
286 | outer: { | ||
287 | background: darkBackground, | ||
288 | border: '#181a1f', | ||
289 | }, | ||
290 | highlight: { | ||
291 | number: '#6188a6', | ||
292 | parameter: '#c8ae9d', | ||
293 | comment: '#7f848e', | ||
294 | activeLine: '#2c313c', | ||
295 | selection: '#404859', | ||
296 | foldPlaceholder: alpha(primaryText, 0.12), | ||
297 | activeLintRange: alpha('#fbc346', 0.28), | ||
298 | occurences: { | ||
299 | read: alpha(primaryText, 0.14), | ||
300 | write: alpha(primaryText, 0.14), | ||
301 | }, | ||
302 | search: { | ||
303 | match: '#33eaff', | ||
304 | selected: '#dd33fa', | ||
305 | contrastText: darkBackground, | ||
306 | }, | ||
232 | }, | 307 | }, |
233 | }, | 308 | }, |
234 | }, | 309 | }, |
235 | }, | 310 | { |
236 | palette: { | 311 | components: { |
237 | mode: 'dark', | 312 | MuiTooltip: { |
238 | primary: { main: '#56b6c2' }, | 313 | styleOverrides: { |
239 | secondary: { main: '#be5046' }, | 314 | tooltip: { |
240 | error: { main: '#e06c75' }, | 315 | color: primaryText, |
241 | warning: { main: '#e5c07b' }, | 316 | }, |
242 | success: { main: '#98c379' }, | 317 | }, |
243 | info: { main: '#61afef' }, | 318 | }, |
244 | background: { | ||
245 | default: '#282c34', | ||
246 | paper: '#21252b', | ||
247 | }, | ||
248 | text: { | ||
249 | primary: '#ebebff', | ||
250 | secondary: '#abb2bf', | ||
251 | disabled: '#5c6370', | ||
252 | }, | ||
253 | divider: alpha('#abb2bf', 0.24), | ||
254 | outer: { | ||
255 | background: '#21252b', | ||
256 | border: '#181a1f', | ||
257 | }, | ||
258 | highlight: { | ||
259 | cursor: '#61afef', | ||
260 | number: '#6188a6', | ||
261 | parameter: '#c8ae9d', | ||
262 | comment: '#7f848e', | ||
263 | activeLine: '#2c313c', | ||
264 | selection: '#404859', | ||
265 | lineNumber: '#5c6370', | ||
266 | foldPlaceholder: alpha('#ebebff', 0.12), | ||
267 | activeLintRange: alpha('#fbc346', 0.28), | ||
268 | occurences: { | ||
269 | read: alpha('#ebebff', 0.14), | ||
270 | write: alpha('#ebebff', 0.14), | ||
271 | }, | ||
272 | search: { | ||
273 | match: '#33eaff', | ||
274 | selected: '#dd33fa', | ||
275 | contrastText: '#21252b', | ||
276 | }, | 319 | }, |
277 | }, | 320 | }, |
278 | }, | 321 | ); |
279 | }); | 322 | })(); |
280 | 323 | ||
281 | const ContrastThemeContext = createContext<Theme | undefined>(undefined); | 324 | const ContrastThemeContext = createContext<Theme | undefined>(undefined); |
282 | 325 | ||