UNPKG

20.8 kBJavaScriptView Raw
1import _formatMuiErrorMessage from "@mui/utils/formatMuiErrorMessage";
2import deepmerge from '@mui/utils/deepmerge';
3import { unstable_createGetCssVar as systemCreateGetCssVar, createSpacing } from '@mui/system';
4import { createUnarySpacing } from '@mui/system/spacing';
5import { prepareCssVars, prepareTypographyVars, createGetColorSchemeSelector } from '@mui/system/cssVars';
6import styleFunctionSx, { unstable_defaultSxConfig as defaultSxConfig } from '@mui/system/styleFunctionSx';
7import { private_safeColorChannel as safeColorChannel, private_safeAlpha as safeAlpha, private_safeDarken as safeDarken, private_safeLighten as safeLighten, private_safeEmphasize as safeEmphasize, hslToRgb } from '@mui/system/colorManipulator';
8import createThemeNoVars from "./createThemeNoVars.js";
9import createColorScheme, { getOpacity, getOverlays } from "./createColorScheme.js";
10import defaultShouldSkipGeneratingVar from "./shouldSkipGeneratingVar.js";
11import defaultGetSelector from "./createGetSelector.js";
12import { stringifyTheme } from "./stringifyTheme.js";
13function assignNode(obj, keys) {
14 keys.forEach(k => {
15 if (!obj[k]) {
16 obj[k] = {};
17 }
18 });
19}
20function setColor(obj, key, defaultValue) {
21 if (!obj[key] && defaultValue) {
22 obj[key] = defaultValue;
23 }
24}
25function toRgb(color) {
26 if (!color || !color.startsWith('hsl')) {
27 return color;
28 }
29 return hslToRgb(color);
30}
31function setColorChannel(obj, key) {
32 if (!(`${key}Channel` in obj)) {
33 // custom channel token is not provided, generate one.
34 // if channel token can't be generated, show a warning.
35 obj[`${key}Channel`] = safeColorChannel(toRgb(obj[key]), `MUI: Can't create \`palette.${key}Channel\` because \`palette.${key}\` is not one of these formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().` + '\n' + `To suppress this warning, you need to explicitly provide the \`palette.${key}Channel\` as a string (in rgb format, for example "12 12 12") or undefined if you want to remove the channel token.`);
36 }
37}
38function getSpacingVal(spacingInput) {
39 if (typeof spacingInput === 'number') {
40 return `${spacingInput}px`;
41 }
42 if (typeof spacingInput === 'string' || typeof spacingInput === 'function' || Array.isArray(spacingInput)) {
43 return spacingInput;
44 }
45 return '8px';
46}
47const silent = fn => {
48 try {
49 return fn();
50 } catch (error) {
51 // ignore error
52 }
53 return undefined;
54};
55export const createGetCssVar = (cssVarPrefix = 'mui') => systemCreateGetCssVar(cssVarPrefix);
56function attachColorScheme(colorSchemes, scheme, restTheme, colorScheme) {
57 if (!scheme) {
58 return undefined;
59 }
60 scheme = scheme === true ? {} : scheme;
61 const mode = colorScheme === 'dark' ? 'dark' : 'light';
62 if (!restTheme) {
63 colorSchemes[colorScheme] = createColorScheme({
64 ...scheme,
65 palette: {
66 mode,
67 ...scheme?.palette
68 }
69 });
70 return undefined;
71 }
72 const {
73 palette,
74 ...muiTheme
75 } = createThemeNoVars({
76 ...restTheme,
77 palette: {
78 mode,
79 ...scheme?.palette
80 }
81 });
82 colorSchemes[colorScheme] = {
83 ...scheme,
84 palette,
85 opacity: {
86 ...getOpacity(mode),
87 ...scheme?.opacity
88 },
89 overlays: scheme?.overlays || getOverlays(mode)
90 };
91 return muiTheme;
92}
93
94/**
95 * A default `createThemeWithVars` comes with a single color scheme, either `light` or `dark` based on the `defaultColorScheme`.
96 * This is better suited for apps that only need a single color scheme.
97 *
98 * To enable built-in `light` and `dark` color schemes, either:
99 * 1. provide a `colorSchemeSelector` to define how the color schemes will change.
100 * 2. provide `colorSchemes.dark` will set `colorSchemeSelector: 'media'` by default.
101 */
102export default function createThemeWithVars(options = {}, ...args) {
103 const {
104 colorSchemes: colorSchemesInput = {
105 light: true
106 },
107 defaultColorScheme: defaultColorSchemeInput,
108 disableCssColorScheme = false,
109 cssVarPrefix = 'mui',
110 shouldSkipGeneratingVar = defaultShouldSkipGeneratingVar,
111 colorSchemeSelector: selector = colorSchemesInput.light && colorSchemesInput.dark ? 'media' : undefined,
112 rootSelector = ':root',
113 ...input
114 } = options;
115 const firstColorScheme = Object.keys(colorSchemesInput)[0];
116 const defaultColorScheme = defaultColorSchemeInput || (colorSchemesInput.light && firstColorScheme !== 'light' ? 'light' : firstColorScheme);
117 const getCssVar = createGetCssVar(cssVarPrefix);
118 const {
119 [defaultColorScheme]: defaultSchemeInput,
120 light: builtInLight,
121 dark: builtInDark,
122 ...customColorSchemes
123 } = colorSchemesInput;
124 const colorSchemes = {
125 ...customColorSchemes
126 };
127 let defaultScheme = defaultSchemeInput;
128
129 // For built-in light and dark color schemes, ensure that the value is valid if they are the default color scheme.
130 if (defaultColorScheme === 'dark' && !('dark' in colorSchemesInput) || defaultColorScheme === 'light' && !('light' in colorSchemesInput)) {
131 defaultScheme = true;
132 }
133 if (!defaultScheme) {
134 throw new Error(process.env.NODE_ENV !== "production" ? `MUI: The \`colorSchemes.${defaultColorScheme}\` option is either missing or invalid.` : _formatMuiErrorMessage(21, defaultColorScheme));
135 }
136
137 // Create the palette for the default color scheme, either `light`, `dark`, or custom color scheme.
138 const muiTheme = attachColorScheme(colorSchemes, defaultScheme, input, defaultColorScheme);
139 if (builtInLight && !colorSchemes.light) {
140 attachColorScheme(colorSchemes, builtInLight, undefined, 'light');
141 }
142 if (builtInDark && !colorSchemes.dark) {
143 attachColorScheme(colorSchemes, builtInDark, undefined, 'dark');
144 }
145 let theme = {
146 defaultColorScheme,
147 ...muiTheme,
148 cssVarPrefix,
149 colorSchemeSelector: selector,
150 rootSelector,
151 getCssVar,
152 colorSchemes,
153 font: {
154 ...prepareTypographyVars(muiTheme.typography),
155 ...muiTheme.font
156 },
157 spacing: getSpacingVal(input.spacing)
158 };
159 Object.keys(theme.colorSchemes).forEach(key => {
160 const palette = theme.colorSchemes[key].palette;
161 const setCssVarColor = cssVar => {
162 const tokens = cssVar.split('-');
163 const color = tokens[1];
164 const colorToken = tokens[2];
165 return getCssVar(cssVar, palette[color][colorToken]);
166 };
167
168 // attach black & white channels to common node
169 if (palette.mode === 'light') {
170 setColor(palette.common, 'background', '#fff');
171 setColor(palette.common, 'onBackground', '#000');
172 }
173 if (palette.mode === 'dark') {
174 setColor(palette.common, 'background', '#000');
175 setColor(palette.common, 'onBackground', '#fff');
176 }
177
178 // assign component variables
179 assignNode(palette, ['Alert', 'AppBar', 'Avatar', 'Button', 'Chip', 'FilledInput', 'LinearProgress', 'Skeleton', 'Slider', 'SnackbarContent', 'SpeedDialAction', 'StepConnector', 'StepContent', 'Switch', 'TableCell', 'Tooltip']);
180 if (palette.mode === 'light') {
181 setColor(palette.Alert, 'errorColor', safeDarken(palette.error.light, 0.6));
182 setColor(palette.Alert, 'infoColor', safeDarken(palette.info.light, 0.6));
183 setColor(palette.Alert, 'successColor', safeDarken(palette.success.light, 0.6));
184 setColor(palette.Alert, 'warningColor', safeDarken(palette.warning.light, 0.6));
185 setColor(palette.Alert, 'errorFilledBg', setCssVarColor('palette-error-main'));
186 setColor(palette.Alert, 'infoFilledBg', setCssVarColor('palette-info-main'));
187 setColor(palette.Alert, 'successFilledBg', setCssVarColor('palette-success-main'));
188 setColor(palette.Alert, 'warningFilledBg', setCssVarColor('palette-warning-main'));
189 setColor(palette.Alert, 'errorFilledColor', silent(() => palette.getContrastText(palette.error.main)));
190 setColor(palette.Alert, 'infoFilledColor', silent(() => palette.getContrastText(palette.info.main)));
191 setColor(palette.Alert, 'successFilledColor', silent(() => palette.getContrastText(palette.success.main)));
192 setColor(palette.Alert, 'warningFilledColor', silent(() => palette.getContrastText(palette.warning.main)));
193 setColor(palette.Alert, 'errorStandardBg', safeLighten(palette.error.light, 0.9));
194 setColor(palette.Alert, 'infoStandardBg', safeLighten(palette.info.light, 0.9));
195 setColor(palette.Alert, 'successStandardBg', safeLighten(palette.success.light, 0.9));
196 setColor(palette.Alert, 'warningStandardBg', safeLighten(palette.warning.light, 0.9));
197 setColor(palette.Alert, 'errorIconColor', setCssVarColor('palette-error-main'));
198 setColor(palette.Alert, 'infoIconColor', setCssVarColor('palette-info-main'));
199 setColor(palette.Alert, 'successIconColor', setCssVarColor('palette-success-main'));
200 setColor(palette.Alert, 'warningIconColor', setCssVarColor('palette-warning-main'));
201 setColor(palette.AppBar, 'defaultBg', setCssVarColor('palette-grey-100'));
202 setColor(palette.Avatar, 'defaultBg', setCssVarColor('palette-grey-400'));
203 setColor(palette.Button, 'inheritContainedBg', setCssVarColor('palette-grey-300'));
204 setColor(palette.Button, 'inheritContainedHoverBg', setCssVarColor('palette-grey-A100'));
205 setColor(palette.Chip, 'defaultBorder', setCssVarColor('palette-grey-400'));
206 setColor(palette.Chip, 'defaultAvatarColor', setCssVarColor('palette-grey-700'));
207 setColor(palette.Chip, 'defaultIconColor', setCssVarColor('palette-grey-700'));
208 setColor(palette.FilledInput, 'bg', 'rgba(0, 0, 0, 0.06)');
209 setColor(palette.FilledInput, 'hoverBg', 'rgba(0, 0, 0, 0.09)');
210 setColor(palette.FilledInput, 'disabledBg', 'rgba(0, 0, 0, 0.12)');
211 setColor(palette.LinearProgress, 'primaryBg', safeLighten(palette.primary.main, 0.62));
212 setColor(palette.LinearProgress, 'secondaryBg', safeLighten(palette.secondary.main, 0.62));
213 setColor(palette.LinearProgress, 'errorBg', safeLighten(palette.error.main, 0.62));
214 setColor(palette.LinearProgress, 'infoBg', safeLighten(palette.info.main, 0.62));
215 setColor(palette.LinearProgress, 'successBg', safeLighten(palette.success.main, 0.62));
216 setColor(palette.LinearProgress, 'warningBg', safeLighten(palette.warning.main, 0.62));
217 setColor(palette.Skeleton, 'bg', `rgba(${setCssVarColor('palette-text-primaryChannel')} / 0.11)`);
218 setColor(palette.Slider, 'primaryTrack', safeLighten(palette.primary.main, 0.62));
219 setColor(palette.Slider, 'secondaryTrack', safeLighten(palette.secondary.main, 0.62));
220 setColor(palette.Slider, 'errorTrack', safeLighten(palette.error.main, 0.62));
221 setColor(palette.Slider, 'infoTrack', safeLighten(palette.info.main, 0.62));
222 setColor(palette.Slider, 'successTrack', safeLighten(palette.success.main, 0.62));
223 setColor(palette.Slider, 'warningTrack', safeLighten(palette.warning.main, 0.62));
224 const snackbarContentBackground = safeEmphasize(palette.background.default, 0.8);
225 setColor(palette.SnackbarContent, 'bg', snackbarContentBackground);
226 setColor(palette.SnackbarContent, 'color', silent(() => palette.getContrastText(snackbarContentBackground)));
227 setColor(palette.SpeedDialAction, 'fabHoverBg', safeEmphasize(palette.background.paper, 0.15));
228 setColor(palette.StepConnector, 'border', setCssVarColor('palette-grey-400'));
229 setColor(palette.StepContent, 'border', setCssVarColor('palette-grey-400'));
230 setColor(palette.Switch, 'defaultColor', setCssVarColor('palette-common-white'));
231 setColor(palette.Switch, 'defaultDisabledColor', setCssVarColor('palette-grey-100'));
232 setColor(palette.Switch, 'primaryDisabledColor', safeLighten(palette.primary.main, 0.62));
233 setColor(palette.Switch, 'secondaryDisabledColor', safeLighten(palette.secondary.main, 0.62));
234 setColor(palette.Switch, 'errorDisabledColor', safeLighten(palette.error.main, 0.62));
235 setColor(palette.Switch, 'infoDisabledColor', safeLighten(palette.info.main, 0.62));
236 setColor(palette.Switch, 'successDisabledColor', safeLighten(palette.success.main, 0.62));
237 setColor(palette.Switch, 'warningDisabledColor', safeLighten(palette.warning.main, 0.62));
238 setColor(palette.TableCell, 'border', safeLighten(safeAlpha(palette.divider, 1), 0.88));
239 setColor(palette.Tooltip, 'bg', safeAlpha(palette.grey[700], 0.92));
240 }
241 if (palette.mode === 'dark') {
242 setColor(palette.Alert, 'errorColor', safeLighten(palette.error.light, 0.6));
243 setColor(palette.Alert, 'infoColor', safeLighten(palette.info.light, 0.6));
244 setColor(palette.Alert, 'successColor', safeLighten(palette.success.light, 0.6));
245 setColor(palette.Alert, 'warningColor', safeLighten(palette.warning.light, 0.6));
246 setColor(palette.Alert, 'errorFilledBg', setCssVarColor('palette-error-dark'));
247 setColor(palette.Alert, 'infoFilledBg', setCssVarColor('palette-info-dark'));
248 setColor(palette.Alert, 'successFilledBg', setCssVarColor('palette-success-dark'));
249 setColor(palette.Alert, 'warningFilledBg', setCssVarColor('palette-warning-dark'));
250 setColor(palette.Alert, 'errorFilledColor', silent(() => palette.getContrastText(palette.error.dark)));
251 setColor(palette.Alert, 'infoFilledColor', silent(() => palette.getContrastText(palette.info.dark)));
252 setColor(palette.Alert, 'successFilledColor', silent(() => palette.getContrastText(palette.success.dark)));
253 setColor(palette.Alert, 'warningFilledColor', silent(() => palette.getContrastText(palette.warning.dark)));
254 setColor(palette.Alert, 'errorStandardBg', safeDarken(palette.error.light, 0.9));
255 setColor(palette.Alert, 'infoStandardBg', safeDarken(palette.info.light, 0.9));
256 setColor(palette.Alert, 'successStandardBg', safeDarken(palette.success.light, 0.9));
257 setColor(palette.Alert, 'warningStandardBg', safeDarken(palette.warning.light, 0.9));
258 setColor(palette.Alert, 'errorIconColor', setCssVarColor('palette-error-main'));
259 setColor(palette.Alert, 'infoIconColor', setCssVarColor('palette-info-main'));
260 setColor(palette.Alert, 'successIconColor', setCssVarColor('palette-success-main'));
261 setColor(palette.Alert, 'warningIconColor', setCssVarColor('palette-warning-main'));
262 setColor(palette.AppBar, 'defaultBg', setCssVarColor('palette-grey-900'));
263 setColor(palette.AppBar, 'darkBg', setCssVarColor('palette-background-paper')); // specific for dark mode
264 setColor(palette.AppBar, 'darkColor', setCssVarColor('palette-text-primary')); // specific for dark mode
265 setColor(palette.Avatar, 'defaultBg', setCssVarColor('palette-grey-600'));
266 setColor(palette.Button, 'inheritContainedBg', setCssVarColor('palette-grey-800'));
267 setColor(palette.Button, 'inheritContainedHoverBg', setCssVarColor('palette-grey-700'));
268 setColor(palette.Chip, 'defaultBorder', setCssVarColor('palette-grey-700'));
269 setColor(palette.Chip, 'defaultAvatarColor', setCssVarColor('palette-grey-300'));
270 setColor(palette.Chip, 'defaultIconColor', setCssVarColor('palette-grey-300'));
271 setColor(palette.FilledInput, 'bg', 'rgba(255, 255, 255, 0.09)');
272 setColor(palette.FilledInput, 'hoverBg', 'rgba(255, 255, 255, 0.13)');
273 setColor(palette.FilledInput, 'disabledBg', 'rgba(255, 255, 255, 0.12)');
274 setColor(palette.LinearProgress, 'primaryBg', safeDarken(palette.primary.main, 0.5));
275 setColor(palette.LinearProgress, 'secondaryBg', safeDarken(palette.secondary.main, 0.5));
276 setColor(palette.LinearProgress, 'errorBg', safeDarken(palette.error.main, 0.5));
277 setColor(palette.LinearProgress, 'infoBg', safeDarken(palette.info.main, 0.5));
278 setColor(palette.LinearProgress, 'successBg', safeDarken(palette.success.main, 0.5));
279 setColor(palette.LinearProgress, 'warningBg', safeDarken(palette.warning.main, 0.5));
280 setColor(palette.Skeleton, 'bg', `rgba(${setCssVarColor('palette-text-primaryChannel')} / 0.13)`);
281 setColor(palette.Slider, 'primaryTrack', safeDarken(palette.primary.main, 0.5));
282 setColor(palette.Slider, 'secondaryTrack', safeDarken(palette.secondary.main, 0.5));
283 setColor(palette.Slider, 'errorTrack', safeDarken(palette.error.main, 0.5));
284 setColor(palette.Slider, 'infoTrack', safeDarken(palette.info.main, 0.5));
285 setColor(palette.Slider, 'successTrack', safeDarken(palette.success.main, 0.5));
286 setColor(palette.Slider, 'warningTrack', safeDarken(palette.warning.main, 0.5));
287 const snackbarContentBackground = safeEmphasize(palette.background.default, 0.98);
288 setColor(palette.SnackbarContent, 'bg', snackbarContentBackground);
289 setColor(palette.SnackbarContent, 'color', silent(() => palette.getContrastText(snackbarContentBackground)));
290 setColor(palette.SpeedDialAction, 'fabHoverBg', safeEmphasize(palette.background.paper, 0.15));
291 setColor(palette.StepConnector, 'border', setCssVarColor('palette-grey-600'));
292 setColor(palette.StepContent, 'border', setCssVarColor('palette-grey-600'));
293 setColor(palette.Switch, 'defaultColor', setCssVarColor('palette-grey-300'));
294 setColor(palette.Switch, 'defaultDisabledColor', setCssVarColor('palette-grey-600'));
295 setColor(palette.Switch, 'primaryDisabledColor', safeDarken(palette.primary.main, 0.55));
296 setColor(palette.Switch, 'secondaryDisabledColor', safeDarken(palette.secondary.main, 0.55));
297 setColor(palette.Switch, 'errorDisabledColor', safeDarken(palette.error.main, 0.55));
298 setColor(palette.Switch, 'infoDisabledColor', safeDarken(palette.info.main, 0.55));
299 setColor(palette.Switch, 'successDisabledColor', safeDarken(palette.success.main, 0.55));
300 setColor(palette.Switch, 'warningDisabledColor', safeDarken(palette.warning.main, 0.55));
301 setColor(palette.TableCell, 'border', safeDarken(safeAlpha(palette.divider, 1), 0.68));
302 setColor(palette.Tooltip, 'bg', safeAlpha(palette.grey[700], 0.92));
303 }
304
305 // MUI X - DataGrid needs this token.
306 setColorChannel(palette.background, 'default');
307
308 // added for consistency with the `background.default` token
309 setColorChannel(palette.background, 'paper');
310 setColorChannel(palette.common, 'background');
311 setColorChannel(palette.common, 'onBackground');
312 setColorChannel(palette, 'divider');
313 Object.keys(palette).forEach(color => {
314 const colors = palette[color];
315
316 // The default palettes (primary, secondary, error, info, success, and warning) errors are handled by the above `createTheme(...)`.
317
318 if (colors && typeof colors === 'object') {
319 // Silent the error for custom palettes.
320 if (colors.main) {
321 setColor(palette[color], 'mainChannel', safeColorChannel(toRgb(colors.main)));
322 }
323 if (colors.light) {
324 setColor(palette[color], 'lightChannel', safeColorChannel(toRgb(colors.light)));
325 }
326 if (colors.dark) {
327 setColor(palette[color], 'darkChannel', safeColorChannel(toRgb(colors.dark)));
328 }
329 if (colors.contrastText) {
330 setColor(palette[color], 'contrastTextChannel', safeColorChannel(toRgb(colors.contrastText)));
331 }
332 if (color === 'text') {
333 // Text colors: text.primary, text.secondary
334 setColorChannel(palette[color], 'primary');
335 setColorChannel(palette[color], 'secondary');
336 }
337 if (color === 'action') {
338 // Action colors: action.active, action.selected
339 if (colors.active) {
340 setColorChannel(palette[color], 'active');
341 }
342 if (colors.selected) {
343 setColorChannel(palette[color], 'selected');
344 }
345 }
346 }
347 });
348 });
349 theme = args.reduce((acc, argument) => deepmerge(acc, argument), theme);
350 const parserConfig = {
351 prefix: cssVarPrefix,
352 disableCssColorScheme,
353 shouldSkipGeneratingVar,
354 getSelector: defaultGetSelector(theme)
355 };
356 const {
357 vars,
358 generateThemeVars,
359 generateStyleSheets
360 } = prepareCssVars(theme, parserConfig);
361 theme.vars = vars;
362 Object.entries(theme.colorSchemes[theme.defaultColorScheme]).forEach(([key, value]) => {
363 theme[key] = value;
364 });
365 theme.generateThemeVars = generateThemeVars;
366 theme.generateStyleSheets = generateStyleSheets;
367 theme.generateSpacing = function generateSpacing() {
368 return createSpacing(input.spacing, createUnarySpacing(this));
369 };
370 theme.getColorSchemeSelector = createGetColorSchemeSelector(selector);
371 theme.spacing = theme.generateSpacing();
372 theme.shouldSkipGeneratingVar = shouldSkipGeneratingVar;
373 theme.unstable_sxConfig = {
374 ...defaultSxConfig,
375 ...input?.unstable_sxConfig
376 };
377 theme.unstable_sx = function sx(props) {
378 return styleFunctionSx({
379 sx: props,
380 theme: this
381 });
382 };
383 theme.toRuntimeSource = stringifyTheme; // for Pigment CSS integration
384
385 return theme;
386}
\No newline at end of file