UNPKG

9.92 kBJavaScriptView Raw
1import _formatMuiErrorMessage from "@mui/utils/formatMuiErrorMessage";
2import deepmerge from '@mui/utils/deepmerge';
3import { darken, getContrastRatio, lighten } from '@mui/system/colorManipulator';
4import common from "../colors/common.js";
5import grey from "../colors/grey.js";
6import purple from "../colors/purple.js";
7import red from "../colors/red.js";
8import orange from "../colors/orange.js";
9import blue from "../colors/blue.js";
10import lightBlue from "../colors/lightBlue.js";
11import green from "../colors/green.js";
12function getLight() {
13 return {
14 // The colors used to style the text.
15 text: {
16 // The most important text.
17 primary: 'rgba(0, 0, 0, 0.87)',
18 // Secondary text.
19 secondary: 'rgba(0, 0, 0, 0.6)',
20 // Disabled text have even lower visual prominence.
21 disabled: 'rgba(0, 0, 0, 0.38)'
22 },
23 // The color used to divide different elements.
24 divider: 'rgba(0, 0, 0, 0.12)',
25 // The background colors used to style the surfaces.
26 // Consistency between these values is important.
27 background: {
28 paper: common.white,
29 default: common.white
30 },
31 // The colors used to style the action elements.
32 action: {
33 // The color of an active action like an icon button.
34 active: 'rgba(0, 0, 0, 0.54)',
35 // The color of an hovered action.
36 hover: 'rgba(0, 0, 0, 0.04)',
37 hoverOpacity: 0.04,
38 // The color of a selected action.
39 selected: 'rgba(0, 0, 0, 0.08)',
40 selectedOpacity: 0.08,
41 // The color of a disabled action.
42 disabled: 'rgba(0, 0, 0, 0.26)',
43 // The background color of a disabled action.
44 disabledBackground: 'rgba(0, 0, 0, 0.12)',
45 disabledOpacity: 0.38,
46 focus: 'rgba(0, 0, 0, 0.12)',
47 focusOpacity: 0.12,
48 activatedOpacity: 0.12
49 }
50 };
51}
52export const light = getLight();
53function getDark() {
54 return {
55 text: {
56 primary: common.white,
57 secondary: 'rgba(255, 255, 255, 0.7)',
58 disabled: 'rgba(255, 255, 255, 0.5)',
59 icon: 'rgba(255, 255, 255, 0.5)'
60 },
61 divider: 'rgba(255, 255, 255, 0.12)',
62 background: {
63 paper: '#121212',
64 default: '#121212'
65 },
66 action: {
67 active: common.white,
68 hover: 'rgba(255, 255, 255, 0.08)',
69 hoverOpacity: 0.08,
70 selected: 'rgba(255, 255, 255, 0.16)',
71 selectedOpacity: 0.16,
72 disabled: 'rgba(255, 255, 255, 0.3)',
73 disabledBackground: 'rgba(255, 255, 255, 0.12)',
74 disabledOpacity: 0.38,
75 focus: 'rgba(255, 255, 255, 0.12)',
76 focusOpacity: 0.12,
77 activatedOpacity: 0.24
78 }
79 };
80}
81export const dark = getDark();
82function addLightOrDark(intent, direction, shade, tonalOffset) {
83 const tonalOffsetLight = tonalOffset.light || tonalOffset;
84 const tonalOffsetDark = tonalOffset.dark || tonalOffset * 1.5;
85 if (!intent[direction]) {
86 if (intent.hasOwnProperty(shade)) {
87 intent[direction] = intent[shade];
88 } else if (direction === 'light') {
89 intent.light = lighten(intent.main, tonalOffsetLight);
90 } else if (direction === 'dark') {
91 intent.dark = darken(intent.main, tonalOffsetDark);
92 }
93 }
94}
95function getDefaultPrimary(mode = 'light') {
96 if (mode === 'dark') {
97 return {
98 main: blue[200],
99 light: blue[50],
100 dark: blue[400]
101 };
102 }
103 return {
104 main: blue[700],
105 light: blue[400],
106 dark: blue[800]
107 };
108}
109function getDefaultSecondary(mode = 'light') {
110 if (mode === 'dark') {
111 return {
112 main: purple[200],
113 light: purple[50],
114 dark: purple[400]
115 };
116 }
117 return {
118 main: purple[500],
119 light: purple[300],
120 dark: purple[700]
121 };
122}
123function getDefaultError(mode = 'light') {
124 if (mode === 'dark') {
125 return {
126 main: red[500],
127 light: red[300],
128 dark: red[700]
129 };
130 }
131 return {
132 main: red[700],
133 light: red[400],
134 dark: red[800]
135 };
136}
137function getDefaultInfo(mode = 'light') {
138 if (mode === 'dark') {
139 return {
140 main: lightBlue[400],
141 light: lightBlue[300],
142 dark: lightBlue[700]
143 };
144 }
145 return {
146 main: lightBlue[700],
147 light: lightBlue[500],
148 dark: lightBlue[900]
149 };
150}
151function getDefaultSuccess(mode = 'light') {
152 if (mode === 'dark') {
153 return {
154 main: green[400],
155 light: green[300],
156 dark: green[700]
157 };
158 }
159 return {
160 main: green[800],
161 light: green[500],
162 dark: green[900]
163 };
164}
165function getDefaultWarning(mode = 'light') {
166 if (mode === 'dark') {
167 return {
168 main: orange[400],
169 light: orange[300],
170 dark: orange[700]
171 };
172 }
173 return {
174 main: '#ed6c02',
175 // closest to orange[800] that pass 3:1.
176 light: orange[500],
177 dark: orange[900]
178 };
179}
180export default function createPalette(palette) {
181 const {
182 mode = 'light',
183 contrastThreshold = 3,
184 tonalOffset = 0.2,
185 ...other
186 } = palette;
187 const primary = palette.primary || getDefaultPrimary(mode);
188 const secondary = palette.secondary || getDefaultSecondary(mode);
189 const error = palette.error || getDefaultError(mode);
190 const info = palette.info || getDefaultInfo(mode);
191 const success = palette.success || getDefaultSuccess(mode);
192 const warning = palette.warning || getDefaultWarning(mode);
193
194 // Use the same logic as
195 // Bootstrap: https://github.com/twbs/bootstrap/blob/1d6e3710dd447de1a200f29e8fa521f8a0908f70/scss/_functions.scss#L59
196 // and material-components-web https://github.com/material-components/material-components-web/blob/ac46b8863c4dab9fc22c4c662dc6bd1b65dd652f/packages/mdc-theme/_functions.scss#L54
197 function getContrastText(background) {
198 const contrastText = getContrastRatio(background, dark.text.primary) >= contrastThreshold ? dark.text.primary : light.text.primary;
199 if (process.env.NODE_ENV !== 'production') {
200 const contrast = getContrastRatio(background, contrastText);
201 if (contrast < 3) {
202 console.error([`MUI: The contrast ratio of ${contrast}:1 for ${contrastText} on ${background}`, 'falls below the WCAG recommended absolute minimum contrast ratio of 3:1.', 'https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast'].join('\n'));
203 }
204 }
205 return contrastText;
206 }
207 const augmentColor = ({
208 color,
209 name,
210 mainShade = 500,
211 lightShade = 300,
212 darkShade = 700
213 }) => {
214 color = {
215 ...color
216 };
217 if (!color.main && color[mainShade]) {
218 color.main = color[mainShade];
219 }
220 if (!color.hasOwnProperty('main')) {
221 throw new Error(process.env.NODE_ENV !== "production" ? `MUI: The color${name ? ` (${name})` : ''} provided to augmentColor(color) is invalid.\n` + `The color object needs to have a \`main\` property or a \`${mainShade}\` property.` : _formatMuiErrorMessage(11, name ? ` (${name})` : '', mainShade));
222 }
223 if (typeof color.main !== 'string') {
224 throw new Error(process.env.NODE_ENV !== "production" ? `MUI: The color${name ? ` (${name})` : ''} provided to augmentColor(color) is invalid.\n` + `\`color.main\` should be a string, but \`${JSON.stringify(color.main)}\` was provided instead.\n` + '\n' + 'Did you intend to use one of the following approaches?\n' + '\n' + 'import { green } from "@mui/material/colors";\n' + '\n' + 'const theme1 = createTheme({ palette: {\n' + ' primary: green,\n' + '} });\n' + '\n' + 'const theme2 = createTheme({ palette: {\n' + ' primary: { main: green[500] },\n' + '} });' : _formatMuiErrorMessage(12, name ? ` (${name})` : '', JSON.stringify(color.main)));
225 }
226 addLightOrDark(color, 'light', lightShade, tonalOffset);
227 addLightOrDark(color, 'dark', darkShade, tonalOffset);
228 if (!color.contrastText) {
229 color.contrastText = getContrastText(color.main);
230 }
231 return color;
232 };
233 let modeHydrated;
234 if (mode === 'light') {
235 modeHydrated = getLight();
236 } else if (mode === 'dark') {
237 modeHydrated = getDark();
238 }
239 if (process.env.NODE_ENV !== 'production') {
240 if (!modeHydrated) {
241 console.error(`MUI: The palette mode \`${mode}\` is not supported.`);
242 }
243 }
244 const paletteOutput = deepmerge({
245 // A collection of common colors.
246 common: {
247 ...common
248 },
249 // prevent mutable object.
250 // The palette mode, can be light or dark.
251 mode,
252 // The colors used to represent primary interface elements for a user.
253 primary: augmentColor({
254 color: primary,
255 name: 'primary'
256 }),
257 // The colors used to represent secondary interface elements for a user.
258 secondary: augmentColor({
259 color: secondary,
260 name: 'secondary',
261 mainShade: 'A400',
262 lightShade: 'A200',
263 darkShade: 'A700'
264 }),
265 // The colors used to represent interface elements that the user should be made aware of.
266 error: augmentColor({
267 color: error,
268 name: 'error'
269 }),
270 // The colors used to represent potentially dangerous actions or important messages.
271 warning: augmentColor({
272 color: warning,
273 name: 'warning'
274 }),
275 // The colors used to present information to the user that is neutral and not necessarily important.
276 info: augmentColor({
277 color: info,
278 name: 'info'
279 }),
280 // The colors used to indicate the successful completion of an action that user triggered.
281 success: augmentColor({
282 color: success,
283 name: 'success'
284 }),
285 // The grey colors.
286 grey,
287 // Used by `getContrastText()` to maximize the contrast between
288 // the background and the text.
289 contrastThreshold,
290 // Takes a background color and returns the text color that maximizes the contrast.
291 getContrastText,
292 // Generate a rich color object.
293 augmentColor,
294 // Used by the functions below to shift a color's luminance by approximately
295 // two indexes within its tonal palette.
296 // E.g., shift from Red 500 to Red 300 or Red 700.
297 tonalOffset,
298 // The light and dark mode object.
299 ...modeHydrated
300 }, other);
301 return paletteOutput;
302}
\No newline at end of file