forked from opencollective/opencollective-frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCollectiveThemeProvider.js
94 lines (87 loc) · 3.11 KB
/
CollectiveThemeProvider.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import React from 'react';
import PropTypes from 'prop-types';
import { clamp, get, throttle } from 'lodash';
import memoizeOne from 'memoize-one';
import { darken, getContrast, getLuminance, setLightness } from 'polished';
import { ThemeProvider } from 'styled-components';
import { isHexColor } from 'validator';
import defaultTheme, { generateTheme } from '../lib/theme';
/**
* A special `ThemeProvider` that plugs the custom collective theme, defined by the color
* from `collective.settings.collectivePage.primaryColor`.
*/
export default class CollectiveThemeProvider extends React.PureComponent {
static propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
collective: PropTypes.shape({
settings: PropTypes.shape({
collectivePage: PropTypes.shape({
primaryColor: PropTypes.string,
}),
}),
}),
};
state = { newPrimaryColor: null };
/**
* Ensures that the contrast is at least 7/1, as recommended by the [W3c](https://webaim.org/articles/contrast)
*/
adjustColorContrast = color => {
const contrast = getContrast(color, '#fff');
if (contrast >= 7) {
return color;
} else {
const contrastDiff = (7 - contrast) / 21;
return darken(contrastDiff, color);
}
};
getTheme = memoizeOne(primaryColor => {
if (!primaryColor) {
return defaultTheme;
} else if (!isHexColor(primaryColor)) {
// eslint-disable-next-line no-console
console.warn(`Invalid custom color: ${primaryColor}`);
return defaultTheme;
} else {
const adjustedPrimary = this.adjustColorContrast(primaryColor);
const luminance = getLuminance(adjustedPrimary);
// Allow a deviation to up to 20% of the default luminance. Don't apply this to really
// dark colors (luminance < 0.05)
const luminanceAdjustment = luminance < 0.05 ? -0.1 : luminance / 5;
const adjustLuminance = value => setLightness(clamp(value + luminanceAdjustment, 0, 0.97), adjustedPrimary);
return generateTheme({
colors: {
...defaultTheme.colors,
primary: {
900: adjustLuminance(0.1),
800: adjustLuminance(0.2),
700: adjustLuminance(0.3),
600: adjustLuminance(0.42),
500: adjustLuminance(0.5),
400: adjustLuminance(0.6),
300: adjustLuminance(0.65),
200: adjustLuminance(0.72),
100: adjustLuminance(0.92),
50: adjustLuminance(0.97),
base: primaryColor,
},
},
});
}
});
onPrimaryColorChange = throttle(newPrimaryColor => {
this.setState({ newPrimaryColor });
}, 2000);
render() {
const { collective, children } = this.props;
const primaryColor = this.state.newPrimaryColor || get(collective, 'settings.collectivePage.primaryColor');
return (
<ThemeProvider theme={this.getTheme(primaryColor)}>
{typeof children === 'function' ? (
children({ onPrimaryColorChange: this.onPrimaryColorChange })
) : (
<React.Fragment>{children}</React.Fragment>
)}
</ThemeProvider>
);
}
}