UNPKG

11.7 kBJavaScriptView Raw
1import React, { Component } from 'react';
2import { debounce } from 'lodash-es';
3import autoprefix from './autoprefix.js';
4function autoprefixes(styles) {
5 return Object.keys(styles).reduce((obj, key) => ((obj[key] = autoprefix(styles[key])), obj), {});
6}
7const styles = autoprefixes({
8 wrapper: {
9 position: 'fixed',
10 width: 0,
11 height: 0,
12 top: 0,
13 left: 0,
14 },
15 dim: {
16 position: 'fixed',
17 left: 0,
18 right: 0,
19 top: 0,
20 bottom: 0,
21 zIndex: 0,
22 background: 'rgba(0, 0, 0, 0.2)',
23 opacity: 1,
24 },
25 dimAppear: {
26 opacity: 0,
27 },
28 dimTransparent: {
29 pointerEvents: 'none',
30 },
31 dimHidden: {
32 opacity: 0,
33 },
34 dock: {
35 position: 'fixed',
36 zIndex: 1,
37 boxShadow: '0 0 4px rgba(0, 0, 0, 0.3)',
38 background: 'white',
39 left: 0,
40 top: 0,
41 width: '100%',
42 height: '100%',
43 },
44 dockHidden: {
45 opacity: 0,
46 },
47 dockResizing: {
48 transition: 'none',
49 },
50 dockContent: {
51 width: '100%',
52 height: '100%',
53 overflow: 'auto',
54 },
55 resizer: {
56 position: 'absolute',
57 zIndex: 2,
58 opacity: 0,
59 },
60});
61function getTransitions(duration) {
62 return ['left', 'top', 'width', 'height'].map((p) => `${p} ${duration / 1000}s ease-out`);
63}
64function getDockStyles({ fluid, dockStyle, dockHiddenStyle, duration, position, isVisible }, { size, isResizing, fullWidth, fullHeight }) {
65 let posStyle;
66 const absSize = fluid ? `${size * 100}%` : `${size}px`;
67 function getRestSize(fullSize) {
68 return fluid ? `${100 - size * 100}%` : `${fullSize - size}px`;
69 }
70 switch (position) {
71 case 'left':
72 posStyle = {
73 width: absSize,
74 left: isVisible ? 0 : '-' + absSize,
75 };
76 break;
77 case 'right':
78 posStyle = {
79 left: isVisible ? getRestSize(fullWidth) : fullWidth,
80 width: absSize,
81 };
82 break;
83 case 'top':
84 posStyle = {
85 top: isVisible ? 0 : '-' + absSize,
86 height: absSize,
87 };
88 break;
89 case 'bottom':
90 posStyle = {
91 top: isVisible ? getRestSize(fullHeight) : fullHeight,
92 height: absSize,
93 };
94 break;
95 }
96 const transitions = getTransitions(duration);
97 return [
98 styles.dock,
99 autoprefix({
100 transition: [
101 ...transitions,
102 !isVisible && `opacity 0.01s linear ${duration / 1000}s`,
103 ]
104 .filter((t) => t)
105 .join(','),
106 }),
107 dockStyle,
108 autoprefix(posStyle),
109 isResizing && styles.dockResizing,
110 !isVisible && styles.dockHidden,
111 !isVisible && dockHiddenStyle,
112 ];
113}
114function getDimStyles({ dimMode, dimStyle, duration, isVisible }, { isTransitionStarted }) {
115 return [
116 styles.dim,
117 autoprefix({
118 transition: `opacity ${duration / 1000}s ease-out`,
119 }),
120 dimStyle,
121 dimMode === 'transparent' && styles.dimTransparent,
122 !isVisible && styles.dimHidden,
123 isTransitionStarted && isVisible && styles.dimAppear,
124 isTransitionStarted && !isVisible && styles.dimDisappear,
125 ];
126}
127function getResizerStyles(position) {
128 let resizerStyle;
129 const size = 10;
130 switch (position) {
131 case 'left':
132 resizerStyle = {
133 right: -size / 2,
134 width: size,
135 top: 0,
136 height: '100%',
137 cursor: 'col-resize',
138 };
139 break;
140 case 'right':
141 resizerStyle = {
142 left: -size / 2,
143 width: size,
144 top: 0,
145 height: '100%',
146 cursor: 'col-resize',
147 };
148 break;
149 case 'top':
150 resizerStyle = {
151 bottom: -size / 2,
152 height: size,
153 left: 0,
154 width: '100%',
155 cursor: 'row-resize',
156 };
157 break;
158 case 'bottom':
159 resizerStyle = {
160 top: -size / 2,
161 height: size,
162 left: 0,
163 width: '100%',
164 cursor: 'row-resize',
165 };
166 break;
167 }
168 return [styles.resizer, autoprefix(resizerStyle)];
169}
170function getFullSize(position, fullWidth, fullHeight) {
171 return position === 'left' || position === 'right' ? fullWidth : fullHeight;
172}
173class Dock extends Component {
174 constructor() {
175 super(...arguments);
176 this.state = {
177 isControlled: typeof this.props.size !== 'undefined',
178 size: this.props.size || this.props.defaultSize,
179 isDimHidden: !this.props.isVisible,
180 fullWidth: window.innerWidth,
181 fullHeight: window.innerHeight,
182 isTransitionStarted: false,
183 isWindowResizing: false,
184 };
185 this.transitionEnd = () => {
186 this.setState({ isTransitionStarted: false });
187 };
188 this.hideDim = () => {
189 if (!this.props.isVisible) {
190 this.setState({ isDimHidden: true });
191 }
192 };
193 this.handleDimClick = () => {
194 if (this.props.dimMode === 'opaque') {
195 this.props.onVisibleChange && this.props.onVisibleChange(false);
196 }
197 };
198 this.handleResize = () => {
199 if (window.requestAnimationFrame) {
200 window.requestAnimationFrame(this.updateWindowSize.bind(this, true));
201 }
202 else {
203 this.updateWindowSize(true);
204 }
205 };
206 this.updateWindowSize = (windowResize) => {
207 const sizeState = {
208 fullWidth: window.innerWidth,
209 fullHeight: window.innerHeight,
210 };
211 if (windowResize) {
212 this.setState({
213 ...sizeState,
214 isResizing: true,
215 isWindowResizing: windowResize,
216 });
217 this.debouncedUpdateWindowSizeEnd();
218 }
219 else {
220 this.setState(sizeState);
221 }
222 };
223 this.updateWindowSizeEnd = () => {
224 this.setState({
225 isResizing: false,
226 isWindowResizing: false,
227 });
228 };
229 this.debouncedUpdateWindowSizeEnd = debounce(this.updateWindowSizeEnd, 30);
230 this.handleWrapperLeave = () => {
231 this.setState({ isResizing: false });
232 };
233 this.handleMouseDown = () => {
234 this.setState({ isResizing: true });
235 };
236 this.handleMouseUp = () => {
237 this.setState({ isResizing: false });
238 };
239 this.handleMouseMove = (e) => {
240 if (!this.state.isResizing || this.state.isWindowResizing)
241 return;
242 if (!e.touches)
243 e.preventDefault();
244 const { position, fluid } = this.props;
245 const { fullWidth, fullHeight, isControlled } = this.state;
246 let { clientX: x, clientY: y } = e;
247 if (e.touches) {
248 x = e.touches[0].clientX;
249 y = e.touches[0].clientY;
250 }
251 let size;
252 switch (position) {
253 case 'left':
254 size = fluid ? x / fullWidth : x;
255 break;
256 case 'right':
257 size = fluid ? (fullWidth - x) / fullWidth : fullWidth - x;
258 break;
259 case 'top':
260 size = fluid ? y / fullHeight : y;
261 break;
262 case 'bottom':
263 size = fluid ? (fullHeight - y) / fullHeight : fullHeight - y;
264 break;
265 }
266 this.props.onSizeChange && this.props.onSizeChange(size);
267 if (!isControlled) {
268 this.setState({ size });
269 }
270 };
271 }
272 componentDidMount() {
273 window.addEventListener('touchend', this.handleMouseUp);
274 window.addEventListener('mouseup', this.handleMouseUp);
275 window.addEventListener('touchmove', this.handleMouseMove);
276 window.addEventListener('mousemove', this.handleMouseMove);
277 window.addEventListener('resize', this.handleResize);
278 this.updateWindowSize();
279 }
280 componentWillUnmount() {
281 window.removeEventListener('touchend', this.handleMouseUp);
282 window.removeEventListener('mouseup', this.handleMouseUp);
283 window.removeEventListener('touchmove', this.handleMouseMove);
284 window.removeEventListener('mousemove', this.handleMouseMove);
285 window.removeEventListener('resize', this.handleResize);
286 }
287 UNSAFE_componentWillReceiveProps(nextProps) {
288 const isControlled = typeof nextProps.size !== 'undefined';
289 this.setState({ isControlled });
290 if (isControlled && nextProps.size && this.props.size !== nextProps.size) {
291 this.setState({ size: nextProps.size });
292 }
293 else if (this.props.fluid !== nextProps.fluid) {
294 this.updateSize(nextProps);
295 }
296 if (this.props.isVisible !== nextProps.isVisible) {
297 this.setState({
298 isTransitionStarted: true,
299 });
300 }
301 }
302 updateSize(props) {
303 const { fullWidth, fullHeight } = this.state;
304 this.setState({
305 size: props.fluid
306 ? this.state.size / getFullSize(props.position, fullWidth, fullHeight)
307 : getFullSize(props.position, fullWidth, fullHeight) * this.state.size,
308 });
309 }
310 componentDidUpdate(prevProps) {
311 if (this.props.isVisible !== prevProps.isVisible) {
312 if (!this.props.isVisible) {
313 window.setTimeout(() => this.hideDim(), this.props.duration);
314 }
315 else {
316 this.setState({ isDimHidden: false });
317 }
318 window.setTimeout(() => this.setState({ isTransitionStarted: false }), 0);
319 }
320 }
321 render() {
322 const { children, zIndex, dimMode, position, isVisible } = this.props;
323 const { isResizing, size, isDimHidden } = this.state;
324 const dimStyles = Object.assign({}, ...getDimStyles(this.props, this.state));
325 const dockStyles = Object.assign({}, ...getDockStyles(this.props, this.state));
326 const resizerStyles = Object.assign({}, ...getResizerStyles(position));
327 return (React.createElement("div", { style: Object.assign({}, styles.wrapper, { zIndex }) },
328 dimMode !== 'none' && !isDimHidden && (React.createElement("div", { style: dimStyles, onClick: this.handleDimClick })),
329 React.createElement("div", { style: dockStyles },
330 React.createElement("div", { style: resizerStyles, onMouseDown: this.handleMouseDown, onTouchStart: this.handleMouseDown }),
331 React.createElement("div", { style: styles.dockContent }, typeof children === 'function'
332 ? children({
333 position,
334 isResizing,
335 size,
336 isVisible,
337 })
338 : children))));
339 }
340}
341Dock.defaultProps = {
342 position: 'left',
343 zIndex: 99999999,
344 fluid: true,
345 defaultSize: 0.3,
346 dimMode: 'opaque',
347 duration: 200,
348};
349export default Dock;