UNPKG

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