/
Grommet.js
170 lines (147 loc) · 5 KB
/
Grommet.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import React, { Component } from 'react';
import { createGlobalStyle } from 'styled-components';
import {
ResponsiveContext,
ThemeContext,
ContainerTargetContext,
} from '../../contexts';
import {
backgroundIsDark,
deepMerge,
getBreakpoint,
getDeviceBreakpoint,
normalizeColor,
} from '../../utils';
import { base as baseTheme } from '../../themes';
import { StyledGrommet } from './StyledGrommet';
const FullGlobalStyle = createGlobalStyle`
body { margin: 0; }
`;
class Grommet extends Component {
static displayName = 'Grommet';
static getDerivedStateFromProps(nextProps, prevState) {
const {
background: backgroundProp,
dir,
theme = {},
themeMode,
} = nextProps;
const { theme: stateTheme, themeProp, themeModeProp } = prevState;
const nextTheme = deepMerge(baseTheme, theme);
if (!stateTheme || theme !== themeProp || themeMode !== themeModeProp) {
const {
colors: { background: themeBackground },
} = nextTheme.global;
// get initial value for dark so we can normalize background color
nextTheme.dark = (themeMode || theme.defaultMode) === 'dark';
const color = normalizeColor(
backgroundProp || themeBackground,
nextTheme,
);
// After normalizing, we set nextTheme.dark once more.
// It is necessary that we set it twice. We have to handle two cases:
// 1. Caller passes in a color object or a color name that resolves an
// object. In this case, we want to set dark as line 38 shows. The
// second set, in line 46, is a no-op.
// 2. Caller passes a specific color value or a color name that resolves
// to a specific color value. In this case, we want dark to be set
// based on that color, which line 46 will do.
// The double set of nextTheme.dark allows us to handle both cases here
// without having to duplicate color object + name + dark mode detection
// code here that is already in normalizeColor and backgroundIsDark.
nextTheme.dark = backgroundIsDark(color, nextTheme);
nextTheme.baseBackground = backgroundProp || themeBackground;
if (dir) {
nextTheme.dir = dir;
}
return {
theme: nextTheme,
themeProp: theme,
themeModeProp: themeMode,
};
}
return null;
}
state = {};
componentDidMount() {
window.addEventListener('resize', this.onResize);
this.onResize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
onResize = () => {
const { theme, responsive } = this.state;
let breakpoint;
// responsive would be undefined in the case of SSR or initial page load
if (!responsive) {
// In the case of SSR we'll need to use the user agent breakpoint
breakpoint = this.deviceResponsive();
}
// Initial page load where both responsive and breakpoint are undefined
if (!breakpoint) breakpoint = getBreakpoint(window.innerWidth, theme);
if (breakpoint !== responsive) {
this.setState({ responsive: breakpoint });
}
};
deviceResponsive() {
const { userAgent } = this.props;
const { theme } = this.state;
/*
* Regexes provided for mobile and tablet detection are meant to replace
* a full-featured specific library due to contributing a considerable size
* into the bundle.
*
* User agents found https://deviceatlas.com/blog/list-of-user-agent-strings
*/
if (userAgent) {
if (
/(tablet|ipad|playbook|silk)|(android(?!.*mobile))/i.test(userAgent)
) {
return getDeviceBreakpoint('tablet', theme);
}
if (/Mobile|iPhone|Android/.test(userAgent)) {
return getDeviceBreakpoint('phone', theme);
}
return getDeviceBreakpoint('computer', theme);
}
return undefined;
}
render() {
const {
children,
full,
containerTarget = typeof document === 'object'
? document.body
: undefined,
...rest
} = this.props;
delete rest.theme;
const { theme, responsive: stateResponsive } = this.state;
// Value from state should be correct once we resize
// On first render we try to guess otherwise set the default as a tablet
const responsive =
stateResponsive ||
this.deviceResponsive() ||
theme.global.deviceBreakpoints.tablet;
return (
<ThemeContext.Provider value={theme}>
<ResponsiveContext.Provider value={responsive}>
<ContainerTargetContext.Provider value={containerTarget}>
<StyledGrommet full={full} {...rest}>
{children}
</StyledGrommet>
{full && <FullGlobalStyle />}
</ContainerTargetContext.Provider>
</ResponsiveContext.Provider>
</ThemeContext.Provider>
);
}
}
let GrommetDoc;
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line global-require
GrommetDoc = require('./doc').doc(Grommet);
}
const GrommetWrapper = GrommetDoc || Grommet;
export { GrommetWrapper as Grommet };