mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-28 04:23:56 +03:00
parent
d8da887da8
commit
f99c76f9d3
4
packages/react-dock/.babelrc
Normal file
4
packages/react-dock/.babelrc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env", "@babel/preset-react"],
|
||||||
|
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-export-default-from", "react-hot-loader/babel"]
|
||||||
|
}
|
22
packages/react-dock/LICENSE
Normal file
22
packages/react-dock/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Alexander Kuznetsov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
44
packages/react-dock/README.md
Normal file
44
packages/react-dock/README.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# react-dock
|
||||||
|
|
||||||
|
Resizable dockable react component.
|
||||||
|
|
||||||
|
#### Demo
|
||||||
|
|
||||||
|
[http://alexkuz.github.io/react-dock/demo/](http://alexkuz.github.io/react-dock/demo/)
|
||||||
|
|
||||||
|
#### Install
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm i -S react-dock
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Dock position='right' isVisible={this.state.isVisible}>
|
||||||
|
{/* you can pass a function as a child here */}
|
||||||
|
<div onClick={() => this.setState({ isVisible: !this.state.isVisible })}>X</div>
|
||||||
|
</Dock>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Dock Props
|
||||||
|
|
||||||
|
| Prop Name | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| position | Side to dock (`left`, `right`, `top` or `bottom`). Default is `left`. |
|
||||||
|
| fluid | If `true`, resize dock proportionally on window resize. |
|
||||||
|
| size | Size of dock panel (width or height, depending on `position`). If this prop is set, `Dock` is considered as a controlled component, so you need to use `onSizeChange` to track dock resizing. Value is a fraction of window width/height, if `fluid` is true, or pixels otherwise |
|
||||||
|
| defaultSize | Default size of dock panel (used for uncontrolled `Dock` component) |
|
||||||
|
| isVisible | If `true`, dock is visible |
|
||||||
|
| dimMode | If `none` - content is not dimmed, if `transparent` - pointer events are disabled (so you can click through it), if `opaque` - click on dim area closes the dock. Default is `opaque` |
|
||||||
|
| duration | Animation duration. Should be synced with transition animation in style properties |
|
||||||
|
| dimStyle | Style for dim area |
|
||||||
|
| dockStyle | Style for dock |
|
||||||
|
| zIndex | Z-index for wrapper |
|
||||||
|
| onVisibleChange | Fires when `Dock` wants to change `isVisible` (when opaque dim is clicked, in particular) |
|
||||||
|
| onSizeChange | Fires when `Dock` wants to change `size` |
|
||||||
|
| children | Dock content - react elements or function that returns an element. Function receives an object with these state values: `{ position, isResizing, size, isVisible }` |
|
13
packages/react-dock/demo/index.html
Normal file
13
packages/react-dock/demo/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>React Dock</title>
|
||||||
|
<link href="http://fonts.googleapis.com/css?family=Noto+Sans|Roboto:400,300,500" rel="stylesheet" type="text/css">
|
||||||
|
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.5/paper/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="https://github.com/alexkuz/react-dock"><img style="z-index: 999999999; position: fixed; top: 0; left: 0; border: 0;" src="https://camo.githubusercontent.com/121cd7cbdc3e4855075ea8b558508b91ac463ac2/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f6c6566745f677265656e5f3030373230302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_left_green_007200.png"></a>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="static/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
186
packages/react-dock/demo/src/App.jsx
Normal file
186
packages/react-dock/demo/src/App.jsx
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { hot } from 'react-hot-loader/root';
|
||||||
|
import Button from 'react-bootstrap/Button';
|
||||||
|
import Form from 'react-bootstrap/Form';
|
||||||
|
import { BsX } from 'react-icons/bs';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import Dock from '../../src/Dock';
|
||||||
|
|
||||||
|
const Root = styled.div`
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
height: 100vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Main = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 150%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 30vh;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DockContent = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Remove = styled(BsX)`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const positions = ['left', 'top', 'right', 'bottom'];
|
||||||
|
const dimModes = ['transparent', 'none', 'opaque'];
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
positionIdx: 0,
|
||||||
|
dimModeIdx: 0,
|
||||||
|
isVisible: true,
|
||||||
|
fluid: true,
|
||||||
|
customAnimation: false,
|
||||||
|
slow: false,
|
||||||
|
size: 0.25,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const duration = this.state.slow ? 2000 : 200;
|
||||||
|
const dur = duration / 1000;
|
||||||
|
const transitions = ['left', 'top', 'width', 'height']
|
||||||
|
.map((p) => `${p} ${dur}s cubic-bezier(0, 1.5, 0.5, 1)`)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root>
|
||||||
|
<Main>
|
||||||
|
<h1>Main Content</h1>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
Position: {positions[this.state.positionIdx]}
|
||||||
|
<Button
|
||||||
|
onClick={this.handlePositionClick}
|
||||||
|
style={{ marginLeft: '20px' }}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Dim Mode: {dimModes[this.state.dimModeIdx]}
|
||||||
|
<Button
|
||||||
|
onClick={this.handleDimModeClick}
|
||||||
|
style={{ marginLeft: '20px' }}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Form.Check
|
||||||
|
label="is visible"
|
||||||
|
checked={this.state.isVisible}
|
||||||
|
onChange={() =>
|
||||||
|
this.setState({
|
||||||
|
isVisible: !this.state.isVisible,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Check
|
||||||
|
label="custom animation"
|
||||||
|
checked={this.state.customAnimation}
|
||||||
|
onChange={() =>
|
||||||
|
this.setState({
|
||||||
|
customAnimation: !this.state.customAnimation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Check
|
||||||
|
label="slow"
|
||||||
|
checked={this.state.slow}
|
||||||
|
onChange={() =>
|
||||||
|
this.setState({
|
||||||
|
slow: !this.state.slow,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Check
|
||||||
|
label="fluid"
|
||||||
|
checked={this.state.fluid}
|
||||||
|
onChange={() =>
|
||||||
|
this.setState({
|
||||||
|
fluid: !this.state.fluid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Main>
|
||||||
|
<Dock
|
||||||
|
position={positions[this.state.positionIdx]}
|
||||||
|
size={this.state.size}
|
||||||
|
dimMode={dimModes[this.state.dimModeIdx]}
|
||||||
|
isVisible={this.state.isVisible}
|
||||||
|
onVisibleChange={this.handleVisibleChange}
|
||||||
|
onSizeChange={this.handleSizeChange}
|
||||||
|
fluid={this.state.fluid}
|
||||||
|
dimStyle={{ background: 'rgba(0, 0, 100, 0.2)' }}
|
||||||
|
dockStyle={
|
||||||
|
this.state.customAnimation ? { transition: transitions } : null
|
||||||
|
}
|
||||||
|
dockHiddenStyle={
|
||||||
|
this.state.customAnimation
|
||||||
|
? {
|
||||||
|
transition: [
|
||||||
|
transitions,
|
||||||
|
`opacity 0.01s linear ${dur}s`,
|
||||||
|
].join(','),
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
duration={duration}
|
||||||
|
>
|
||||||
|
{({ position, isResizing }) => (
|
||||||
|
<DockContent>
|
||||||
|
<h1>Dock Content</h1>
|
||||||
|
<div>Position: {position}</div>
|
||||||
|
<div>Resizing: {isResizing ? 'true' : 'false'}</div>
|
||||||
|
<Remove onClick={() => this.setState({ isVisible: false })} />
|
||||||
|
</DockContent>
|
||||||
|
)}
|
||||||
|
</Dock>
|
||||||
|
</Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVisibleChange = (isVisible) => {
|
||||||
|
this.setState({ isVisible });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSizeChange = (size) => {
|
||||||
|
this.setState({ size });
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePositionClick = () => {
|
||||||
|
this.setState({ positionIdx: (this.state.positionIdx + 1) % 4 });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDimModeClick = () => {
|
||||||
|
this.setState({ dimModeIdx: (this.state.dimModeIdx + 1) % 3 });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hot(App);
|
5
packages/react-dock/demo/src/index.js
vendored
Normal file
5
packages/react-dock/demo/src/index.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
64
packages/react-dock/package.json
Normal file
64
packages/react-dock/package.json
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"name": "react-dock",
|
||||||
|
"version": "0.2.4",
|
||||||
|
"description": "Resizable dockable react component",
|
||||||
|
"scripts": {
|
||||||
|
"build-lib": "babel src --out-dir lib",
|
||||||
|
"build-demo": "NODE_ENV=production webpack -p",
|
||||||
|
"stats": "webpack --profile --json > stats.json",
|
||||||
|
"start": "webpack-dev-server",
|
||||||
|
"preversion": "npm run lint && npm run test",
|
||||||
|
"version": "npm run build-demo && git add -A .",
|
||||||
|
"postversion": "git push",
|
||||||
|
"prepublish": "npm run build-lib",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"react",
|
||||||
|
"reactjs",
|
||||||
|
"dock",
|
||||||
|
"sidebar"
|
||||||
|
],
|
||||||
|
"author": "Alexander <alexkuz@gmail.com> (http://kuzya.org/)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.10.5",
|
||||||
|
"@babel/core": "^7.11.1",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||||
|
"@babel/plugin-proposal-export-default-from": "^7.10.4",
|
||||||
|
"@babel/preset-env": "^7.11.0",
|
||||||
|
"@babel/preset-react": "^7.10.4",
|
||||||
|
"babel-loader": "^8.1.0",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-bootstrap": "^1.3.0",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"react-hot-loader": "^4.12.21",
|
||||||
|
"react-icons": "^3.10.0",
|
||||||
|
"react-pure-render": "^1.0.2",
|
||||||
|
"react-test-renderer": "^16.13.1",
|
||||||
|
"styled-components": "^5.1.1",
|
||||||
|
"webpack": "^4.44.1",
|
||||||
|
"webpack-cli": "^3.3.12",
|
||||||
|
"webpack-dev-server": "^3.11.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
}
|
432
packages/react-dock/src/Dock.js
vendored
Normal file
432
packages/react-dock/src/Dock.js
vendored
Normal file
|
@ -0,0 +1,432 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
|
import autoprefix from './autoprefix';
|
||||||
|
|
||||||
|
function autoprefixes(styles) {
|
||||||
|
return Object.keys(styles).reduce(
|
||||||
|
(obj, key) => ((obj[key] = autoprefix(styles[key])), obj),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = autoprefixes({
|
||||||
|
wrapper: {
|
||||||
|
position: 'fixed',
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
dim: {
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 0,
|
||||||
|
background: 'rgba(0, 0, 0, 0.2)',
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
dimAppear: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
dimTransparent: {
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
dimHidden: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
dock: {
|
||||||
|
position: 'fixed',
|
||||||
|
zIndex: 1,
|
||||||
|
boxShadow: '0 0 4px rgba(0, 0, 0, 0.3)',
|
||||||
|
background: 'white',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
|
||||||
|
dockHidden: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
dockResizing: {
|
||||||
|
transition: 'none',
|
||||||
|
},
|
||||||
|
|
||||||
|
dockContent: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
},
|
||||||
|
|
||||||
|
resizer: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 2,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTransitions(duration) {
|
||||||
|
return ['left', 'top', 'width', 'height'].map(
|
||||||
|
(p) => `${p} ${duration / 1000}s ease-out`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDockStyles(
|
||||||
|
{ fluid, dockStyle, dockHiddenStyle, duration, position, isVisible },
|
||||||
|
{ size, isResizing, fullWidth, fullHeight }
|
||||||
|
) {
|
||||||
|
let posStyle;
|
||||||
|
const absSize = fluid ? size * 100 + '%' : size + 'px';
|
||||||
|
|
||||||
|
function getRestSize(fullSize) {
|
||||||
|
return fluid ? 100 - size * 100 + '%' : fullSize - size + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'left':
|
||||||
|
posStyle = {
|
||||||
|
width: absSize,
|
||||||
|
left: isVisible ? 0 : '-' + absSize,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
posStyle = {
|
||||||
|
left: isVisible ? getRestSize(fullWidth) : fullWidth,
|
||||||
|
width: absSize,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
posStyle = {
|
||||||
|
top: isVisible ? 0 : '-' + absSize,
|
||||||
|
height: absSize,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
posStyle = {
|
||||||
|
top: isVisible ? getRestSize(fullHeight) : fullHeight,
|
||||||
|
height: absSize,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitions = getTransitions(duration);
|
||||||
|
|
||||||
|
return [
|
||||||
|
styles.dock,
|
||||||
|
autoprefix({
|
||||||
|
transition: [
|
||||||
|
...transitions,
|
||||||
|
!isVisible && `opacity 0.01s linear ${duration / 1000}s`,
|
||||||
|
]
|
||||||
|
.filter((t) => t)
|
||||||
|
.join(','),
|
||||||
|
}),
|
||||||
|
dockStyle,
|
||||||
|
autoprefix(posStyle),
|
||||||
|
isResizing && styles.dockResizing,
|
||||||
|
!isVisible && styles.dockHidden,
|
||||||
|
!isVisible && dockHiddenStyle,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDimStyles(
|
||||||
|
{ dimMode, dimStyle, duration, isVisible },
|
||||||
|
{ isTransitionStarted }
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
styles.dim,
|
||||||
|
autoprefix({
|
||||||
|
transition: `opacity ${duration / 1000}s ease-out`,
|
||||||
|
}),
|
||||||
|
dimStyle,
|
||||||
|
dimMode === 'transparent' && styles.dimTransparent,
|
||||||
|
!isVisible && styles.dimHidden,
|
||||||
|
isTransitionStarted && isVisible && styles.dimAppear,
|
||||||
|
isTransitionStarted && !isVisible && styles.dimDisappear,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getResizerStyles(position) {
|
||||||
|
let resizerStyle;
|
||||||
|
const size = 10;
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'left':
|
||||||
|
resizerStyle = {
|
||||||
|
right: -size / 2,
|
||||||
|
width: size,
|
||||||
|
top: 0,
|
||||||
|
height: '100%',
|
||||||
|
cursor: 'col-resize',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
resizerStyle = {
|
||||||
|
left: -size / 2,
|
||||||
|
width: size,
|
||||||
|
top: 0,
|
||||||
|
height: '100%',
|
||||||
|
cursor: 'col-resize',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
resizerStyle = {
|
||||||
|
bottom: -size / 2,
|
||||||
|
height: size,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
cursor: 'row-resize',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
resizerStyle = {
|
||||||
|
top: -size / 2,
|
||||||
|
height: size,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
cursor: 'row-resize',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [styles.resizer, autoprefix(resizerStyle)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFullSize(position, fullWidth, fullHeight) {
|
||||||
|
return position === 'left' || position === 'right' ? fullWidth : fullHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Dock extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isControlled: typeof props.size !== 'undefined',
|
||||||
|
size: props.size || props.defaultSize,
|
||||||
|
isDimHidden: !props.isVisible,
|
||||||
|
fullWidth: typeof window !== 'undefined' && window.innerWidth,
|
||||||
|
fullHeight: typeof window !== 'undefined' && window.innerHeight,
|
||||||
|
isTransitionStarted: false,
|
||||||
|
isWindowResizing: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
position: PropTypes.oneOf(['left', 'right', 'top', 'bottom']),
|
||||||
|
zIndex: PropTypes.number,
|
||||||
|
fluid: PropTypes.bool,
|
||||||
|
size: PropTypes.number,
|
||||||
|
defaultSize: PropTypes.number,
|
||||||
|
dimMode: PropTypes.oneOf(['none', 'transparent', 'opaque']),
|
||||||
|
isVisible: PropTypes.bool,
|
||||||
|
onVisibleChange: PropTypes.func,
|
||||||
|
onSizeChange: PropTypes.func,
|
||||||
|
dimStyle: PropTypes.object,
|
||||||
|
dockStyle: PropTypes.object,
|
||||||
|
duration: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
position: 'left',
|
||||||
|
zIndex: 99999999,
|
||||||
|
fluid: true,
|
||||||
|
defaultSize: 0.3,
|
||||||
|
dimMode: 'opaque',
|
||||||
|
duration: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('mouseup', this.handleMouseUp);
|
||||||
|
window.addEventListener('mousemove', this.handleMouseMove);
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
|
||||||
|
if (!window.fullWidth) {
|
||||||
|
this.updateWindowSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||||
|
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
|
const isControlled = typeof nextProps.size !== 'undefined';
|
||||||
|
|
||||||
|
this.setState({ isControlled });
|
||||||
|
|
||||||
|
if (isControlled && this.props.size !== nextProps.size) {
|
||||||
|
this.setState({ size: nextProps.size });
|
||||||
|
} else if (this.props.fluid !== nextProps.fluid) {
|
||||||
|
this.updateSize(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.isVisible !== nextProps.isVisible) {
|
||||||
|
this.setState({
|
||||||
|
isTransitionStarted: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSize(props) {
|
||||||
|
const { fullWidth, fullHeight } = this.state;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
size: props.fluid
|
||||||
|
? this.state.size / getFullSize(props.position, fullWidth, fullHeight)
|
||||||
|
: getFullSize(props.position, fullWidth, fullHeight) * this.state.size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.isVisible !== prevProps.isVisible) {
|
||||||
|
if (!this.props.isVisible) {
|
||||||
|
window.setTimeout(() => this.hideDim(), this.props.duration);
|
||||||
|
} else {
|
||||||
|
this.setState({ isDimHidden: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setTimeout(() => this.setState({ isTransitionStarted: false }), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitionEnd = () => {
|
||||||
|
this.setState({ isTransitionStarted: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
hideDim = () => {
|
||||||
|
if (!this.props.isVisible) {
|
||||||
|
this.setState({ isDimHidden: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, zIndex, dimMode, position, isVisible } = this.props;
|
||||||
|
const { isResizing, size, isDimHidden } = this.state;
|
||||||
|
|
||||||
|
const dimStyles = Object.assign(
|
||||||
|
{},
|
||||||
|
...getDimStyles(this.props, this.state)
|
||||||
|
);
|
||||||
|
const dockStyles = Object.assign(
|
||||||
|
{},
|
||||||
|
...getDockStyles(this.props, this.state)
|
||||||
|
);
|
||||||
|
const resizerStyles = Object.assign({}, ...getResizerStyles(position));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={Object.assign({}, styles.wrapper, { zIndex })}>
|
||||||
|
{dimMode !== 'none' && !isDimHidden && (
|
||||||
|
<div style={dimStyles} onClick={this.handleDimClick} />
|
||||||
|
)}
|
||||||
|
<div style={dockStyles}>
|
||||||
|
<div style={resizerStyles} onMouseDown={this.handleMouseDown} />
|
||||||
|
<div style={styles.dockContent}>
|
||||||
|
{typeof children === 'function'
|
||||||
|
? children({
|
||||||
|
position,
|
||||||
|
isResizing,
|
||||||
|
size,
|
||||||
|
isVisible,
|
||||||
|
})
|
||||||
|
: children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDimClick = () => {
|
||||||
|
if (this.props.dimMode === 'opaque') {
|
||||||
|
this.props.onVisibleChange && this.props.onVisibleChange(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleResize = () => {
|
||||||
|
if (window.requestAnimationFrame) {
|
||||||
|
window.requestAnimationFrame(this.updateWindowSize.bind(this, true));
|
||||||
|
} else {
|
||||||
|
this.updateWindowSize(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWindowSize = (windowResize) => {
|
||||||
|
const sizeState = {
|
||||||
|
fullWidth: window.innerWidth,
|
||||||
|
fullHeight: window.innerHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (windowResize) {
|
||||||
|
this.setState({
|
||||||
|
...sizeState,
|
||||||
|
isResizing: true,
|
||||||
|
isWindowResizing: windowResize,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.debouncedUpdateWindowSizeEnd();
|
||||||
|
} else {
|
||||||
|
this.setState(sizeState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWindowSizeEnd = () => {
|
||||||
|
this.setState({
|
||||||
|
isResizing: false,
|
||||||
|
isWindowResizing: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
debouncedUpdateWindowSizeEnd = debounce(this.updateWindowSizeEnd, 30);
|
||||||
|
|
||||||
|
handleWrapperLeave = () => {
|
||||||
|
this.setState({ isResizing: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseDown = () => {
|
||||||
|
this.setState({ isResizing: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseUp = () => {
|
||||||
|
this.setState({ isResizing: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseMove = (e) => {
|
||||||
|
if (!this.state.isResizing || this.state.isWindowResizing) return;
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const { position, fluid } = this.props;
|
||||||
|
const { fullWidth, fullHeight, isControlled } = this.state;
|
||||||
|
const { clientX: x, clientY: y } = e;
|
||||||
|
let size;
|
||||||
|
|
||||||
|
switch (position) {
|
||||||
|
case 'left':
|
||||||
|
size = fluid ? x / fullWidth : x;
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
size = fluid ? (fullWidth - x) / fullWidth : fullWidth - x;
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
size = fluid ? y / fullHeight : y;
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
size = fluid ? (fullHeight - y) / fullHeight : fullHeight - y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onSizeChange && this.props.onSizeChange(size);
|
||||||
|
|
||||||
|
if (!isControlled) {
|
||||||
|
this.setState({ size });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
57
packages/react-dock/src/autoprefix.js
vendored
Normal file
57
packages/react-dock/src/autoprefix.js
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Same as https://github.com/SimenB/react-vendor-prefixes/blob/master/src/index.js,
|
||||||
|
// but dumber
|
||||||
|
|
||||||
|
const vendorSpecificProperties = [
|
||||||
|
'animation',
|
||||||
|
'animationDelay',
|
||||||
|
'animationDirection',
|
||||||
|
'animationDuration',
|
||||||
|
'animationFillMode',
|
||||||
|
'animationIterationCount',
|
||||||
|
'animationName',
|
||||||
|
'animationPlayState',
|
||||||
|
'animationTimingFunction',
|
||||||
|
'appearance',
|
||||||
|
'backfaceVisibility',
|
||||||
|
'backgroundClip',
|
||||||
|
'borderImage',
|
||||||
|
'borderImageSlice',
|
||||||
|
'boxSizing',
|
||||||
|
'boxShadow',
|
||||||
|
'contentColumns',
|
||||||
|
'transform',
|
||||||
|
'transformOrigin',
|
||||||
|
'transformStyle',
|
||||||
|
'transition',
|
||||||
|
'transitionDelay',
|
||||||
|
'transitionDuration',
|
||||||
|
'transitionProperty',
|
||||||
|
'transitionTimingFunction',
|
||||||
|
'perspective',
|
||||||
|
'perspectiveOrigin',
|
||||||
|
'userSelect',
|
||||||
|
];
|
||||||
|
|
||||||
|
const prefixes = ['Moz', 'Webkit', 'ms', 'O'];
|
||||||
|
|
||||||
|
function prefixProp(key, value) {
|
||||||
|
return prefixes.reduce(
|
||||||
|
(obj, pre) => (
|
||||||
|
(obj[pre + key[0].toUpperCase() + key.substr(1)] = value), obj
|
||||||
|
),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function autoprefix(style) {
|
||||||
|
return Object.keys(style).reduce(
|
||||||
|
(obj, key) =>
|
||||||
|
vendorSpecificProperties.indexOf(key) !== -1
|
||||||
|
? {
|
||||||
|
...obj,
|
||||||
|
...prefixProp(key, style[key]),
|
||||||
|
}
|
||||||
|
: obj,
|
||||||
|
style
|
||||||
|
);
|
||||||
|
}
|
1
packages/react-dock/src/index.js
vendored
Normal file
1
packages/react-dock/src/index.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export default from './Dock';
|
23
packages/react-dock/test/index.test.js
Normal file
23
packages/react-dock/test/index.test.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||||
|
import Dock from '../src/Dock';
|
||||||
|
|
||||||
|
describe('Dock component', function () {
|
||||||
|
it('should have shallow rendering', function () {
|
||||||
|
const shallowRenderer = new ShallowRenderer();
|
||||||
|
const DockEl = <Dock />;
|
||||||
|
shallowRenderer.render(DockEl);
|
||||||
|
|
||||||
|
const result = shallowRenderer.getRenderOutput();
|
||||||
|
|
||||||
|
expect(DockEl.props).toEqual({
|
||||||
|
position: 'left',
|
||||||
|
zIndex: 99999999,
|
||||||
|
fluid: true,
|
||||||
|
defaultSize: 0.3,
|
||||||
|
dimMode: 'opaque',
|
||||||
|
duration: 200,
|
||||||
|
});
|
||||||
|
expect(result.type).toBe('div');
|
||||||
|
});
|
||||||
|
});
|
50
packages/react-dock/webpack.config.js
Normal file
50
packages/react-dock/webpack.config.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
var isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: isProduction ? 'production' : 'development',
|
||||||
|
devtool: 'eval',
|
||||||
|
entry: isProduction
|
||||||
|
? ['./demo/src/index']
|
||||||
|
: [
|
||||||
|
'webpack-dev-server/client?http://localhost:3000',
|
||||||
|
'webpack/hot/only-dev-server',
|
||||||
|
'./demo/src/index',
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'demo/static'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
publicPath: isProduction ? 'static/' : '/static/',
|
||||||
|
},
|
||||||
|
plugins: isProduction ? [] : [new webpack.HotModuleReplacementPlugin()],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.jsx'],
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
include: [
|
||||||
|
path.join(__dirname, 'src'),
|
||||||
|
path.join(__dirname, 'demo/src'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devServer: isProduction
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
quiet: true,
|
||||||
|
publicPath: '/static/',
|
||||||
|
port: 3000,
|
||||||
|
contentBase: './demo/',
|
||||||
|
hot: true,
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
},
|
||||||
|
historyApiFallback: true,
|
||||||
|
},
|
||||||
|
};
|
20
yarn.lock
20
yarn.lock
|
@ -10472,11 +10472,6 @@ lodash-es@^4.17.15, lodash-es@^4.17.4:
|
||||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
|
||||||
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
|
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
|
||||||
|
|
||||||
lodash._getnative@^3.0.0:
|
|
||||||
version "3.9.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
|
||||||
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
|
|
||||||
|
|
||||||
lodash._reinterpolate@^3.0.0:
|
lodash._reinterpolate@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||||
|
@ -10492,13 +10487,6 @@ lodash.curry@^4.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
|
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
|
||||||
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
|
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
|
||||||
|
|
||||||
lodash.debounce@^3.1.1:
|
|
||||||
version "3.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-3.1.1.tgz#812211c378a94cc29d5aa4e3346cf0bfce3a7df5"
|
|
||||||
integrity sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=
|
|
||||||
dependencies:
|
|
||||||
lodash._getnative "^3.0.0"
|
|
||||||
|
|
||||||
lodash.debounce@^4.0.8:
|
lodash.debounce@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
@ -13053,14 +13041,6 @@ react-docgen@^3.0.0:
|
||||||
node-dir "^0.1.10"
|
node-dir "^0.1.10"
|
||||||
recast "^0.16.0"
|
recast "^0.16.0"
|
||||||
|
|
||||||
react-dock@^0.2.4:
|
|
||||||
version "0.2.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-dock/-/react-dock-0.2.4.tgz#e727dc7550b3b73116635dcb9c0e04d0b7afe17c"
|
|
||||||
integrity sha1-5yfcdVCztzEWY13LnA4E0Lev4Xw=
|
|
||||||
dependencies:
|
|
||||||
lodash.debounce "^3.1.1"
|
|
||||||
prop-types "^15.5.8"
|
|
||||||
|
|
||||||
react-dom@^16.13.1, react-dom@^16.7.0:
|
react-dom@^16.13.1, react-dom@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user