diff --git a/packages/redux-devtools-chart-monitor/package.json b/packages/redux-devtools-chart-monitor/package.json
index 4adcb87b..4e654eda 100644
--- a/packages/redux-devtools-chart-monitor/package.json
+++ b/packages/redux-devtools-chart-monitor/package.json
@@ -7,7 +7,7 @@
"clean": "rimraf lib",
"build": "babel src --out-dir lib",
"prepare": "npm run build",
- "prepublishOnly": "npm run lint && npm run clean && npm run build"
+ "prepublishOnly": "npm run clean && npm run build"
},
"files": [
"lib",
diff --git a/packages/redux-devtools-dock-monitor/.babelrc b/packages/redux-devtools-dock-monitor/.babelrc
new file mode 100644
index 00000000..645cc56e
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["@babel/preset-env", "@babel/preset-react"],
+ "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-export-default-from"]
+}
diff --git a/packages/redux-devtools-dock-monitor/README.md b/packages/redux-devtools-dock-monitor/README.md
new file mode 100644
index 00000000..ffeac664
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/README.md
@@ -0,0 +1,63 @@
+# Redux DevTools Dock Monitor
+
+A resizable and movable dock for [Redux DevTools](https://github.com/reduxjs/redux-devtools).
+Powered by [React Dock](https://github.com/alexkuz/react-dock).
+
+![](http://i.imgur.com/QbNzNW4.gif)
+
+### Installation
+
+```
+yarn add redux-devtools-dock-monitor
+```
+
+### Usage
+
+Wrap any other Redux DevTools monitor in `DockMonitor` to make it dockable to different screen edges.
+For example, you can use it together with [`LogMonitor`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-log-monitor):
+
+##### `containers/DevTools.js`
+
+```js
+import React from 'react';
+import { createDevTools } from 'redux-devtools';
+import LogMonitor from 'redux-devtools-log-monitor';
+import SliderMonitor from 'redux-slider-monitor';
+import DockMonitor from 'redux-devtools-dock-monitor';
+
+export default createDevTools(
+
+
+
+
+);
+```
+
+[Read how to start using Redux DevTools.](https://github.com/reduxjs/redux-devtools)
+
+#### Multiple Monitors
+
+You can put more than one monitor inside ``. There will still be a single dock, but you will be able to switch between different monitors by pressing a key specified as `changeMonitorKey` prop.
+
+### Props
+
+| Name | Description |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `children` | Any valid Redux DevTools monitor. Required. |
+| `toggleVisibilityKey` | A key or a key combination that toggles the dock visibility. Must be recognizable by [parse-key](https://github.com/thlorenz/parse-key) (for example, `'ctrl-h'`). Required. |
+| `changePositionKey` | A key or a key combination that toggles the dock position. Must be recognizable by [parse-key](https://github.com/thlorenz/parse-key) (for example, `'ctrl-w'`). Required. |
+| `changeMonitorKey` | A key or a key combination that switches the currently visible monitor. Must be recognizable by [parse-key](https://github.com/thlorenz/parse-key) (for example, `'ctrl-m'`). Required if you use more than one monitor. |
+| `fluid` | When `true`, the dock size is a fraction of the window size, fixed otherwise. Optional. By default set to `true`. |
+| `defaultSize` | Size of the dock. When `fluid` is `true`, a float (`0.5` means half the window size). When `fluid` is `false`, a width in pixels. Optional. By default set to `0.3` (3/10th of the window size). |
+| `defaultPosition` | Where the dock appears on the screen. Valid values: `'left'`, `'top'`, `'right'`, `'bottom'`. Optional. By default set to `'right'`. |
+| `defaultIsVisible` | Defines whether dock should be open by default. A value of `true` means that it's open when the page/app loads. |
+
+The current size and the position are persisted between sessions with `persistState()` enhancer from Redux DevTools.
+
+### License
+
+MIT
diff --git a/packages/redux-devtools-dock-monitor/package.json b/packages/redux-devtools-dock-monitor/package.json
new file mode 100644
index 00000000..fcf2d4ee
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "redux-devtools-dock-monitor",
+ "version": "1.1.3",
+ "description": "A resizable and movable dock for Redux DevTools monitors",
+ "main": "lib/index.js",
+ "files": [
+ "lib",
+ "src"
+ ],
+ "scripts": {
+ "clean": "rimraf lib",
+ "build": "babel src --out-dir lib",
+ "prepare": "npm run build",
+ "prepublishOnly": "npm run test && npm run clean && npm run build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/reduxjs/redux-devtools.git"
+ },
+ "keywords": [
+ "redux",
+ "devtools",
+ "flux",
+ "react",
+ "hot reloading",
+ "time travel",
+ "live edit"
+ ],
+ "author": "Dan Abramov (http://github.com/gaearon)",
+ "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.0",
+ "@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",
+ "rimraf": "^2.7.1"
+ },
+ "peerDependencies": {
+ "react": "^0.14.9 || ^15.3.0 || ^16.0.0",
+ "redux-devtools": "^3.4.0"
+ },
+ "dependencies": {
+ "babel-runtime": "^6.26.0",
+ "parse-key": "^0.2.1",
+ "prop-types": "^15.7.2",
+ "react-dock": "^0.2.4",
+ "react-pure-render": "^1.0.2"
+ }
+}
diff --git a/packages/redux-devtools-dock-monitor/src/DockMonitor.js b/packages/redux-devtools-dock-monitor/src/DockMonitor.js
new file mode 100644
index 00000000..572715d4
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/src/DockMonitor.js
@@ -0,0 +1,162 @@
+import React, { cloneElement, Children, Component } from 'react';
+import PropTypes from 'prop-types';
+import Dock from 'react-dock';
+import { POSITIONS } from './constants';
+import {
+ toggleVisibility,
+ changeMonitor,
+ changePosition,
+ changeSize
+} from './actions';
+import reducer from './reducers';
+import parseKey from 'parse-key';
+
+export default class DockMonitor extends Component {
+ static update = reducer;
+
+ static propTypes = {
+ defaultPosition: PropTypes.oneOf(POSITIONS),
+ defaultIsVisible: PropTypes.bool.isRequired,
+ defaultSize: PropTypes.number.isRequired,
+ toggleVisibilityKey: PropTypes.string.isRequired,
+ changePositionKey: PropTypes.string.isRequired,
+ changeMonitorKey: PropTypes.string,
+ fluid: PropTypes.bool,
+
+ dispatch: PropTypes.func,
+ monitorState: PropTypes.shape({
+ position: PropTypes.oneOf(POSITIONS).isRequired,
+ size: PropTypes.number.isRequired,
+ isVisible: PropTypes.bool.isRequired,
+ childMonitorState: PropTypes.any
+ })
+ };
+
+ static defaultProps = {
+ defaultIsVisible: true,
+ defaultPosition: 'right',
+ defaultSize: 0.3,
+ fluid: true
+ };
+
+ constructor(props) {
+ super(props);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.handleSizeChange = this.handleSizeChange.bind(this);
+
+ const childrenCount = Children.count(props.children);
+ if (childrenCount === 0) {
+ // eslint-disable-next-line no-console
+ console.error(
+ ' requires at least one monitor inside. ' +
+ 'Why don’t you try ? You can get it at ' +
+ 'https://github.com/gaearon/redux-devtools-log-monitor.'
+ );
+ } else if (childrenCount > 1 && !props.changeMonitorKey) {
+ // eslint-disable-next-line no-console
+ console.error(
+ 'You specified multiple monitors inside ' +
+ 'but did not provide `changeMonitorKey` prop to change them. ' +
+ 'Try specifying ' +
+ 'and then press Ctrl-M.'
+ );
+ }
+ }
+
+ componentDidMount() {
+ window.addEventListener('keydown', this.handleKeyDown);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('keydown', this.handleKeyDown);
+ }
+
+ matchesKey(key, event) {
+ if (!key) {
+ return false;
+ }
+
+ const charCode = event.keyCode || event.which;
+ const char = String.fromCharCode(charCode);
+ return (
+ key.name.toUpperCase() === char.toUpperCase() &&
+ key.alt === event.altKey &&
+ key.ctrl === event.ctrlKey &&
+ key.meta === event.metaKey &&
+ key.shift === event.shiftKey
+ );
+ }
+
+ handleKeyDown(e) {
+ // Ignore regular keys when focused on a field
+ // and no modifiers are active.
+ if (
+ !e.ctrlKey &&
+ !e.metaKey &&
+ !e.altKey &&
+ (e.target.tagName === 'INPUT' ||
+ e.target.tagName === 'SELECT' ||
+ e.target.tagName === 'TEXTAREA' ||
+ e.target.isContentEditable)
+ ) {
+ return;
+ }
+
+ const visibilityKey = parseKey(this.props.toggleVisibilityKey);
+ const positionKey = parseKey(this.props.changePositionKey);
+
+ let monitorKey;
+ if (this.props.changeMonitorKey) {
+ monitorKey = parseKey(this.props.changeMonitorKey);
+ }
+
+ if (this.matchesKey(visibilityKey, e)) {
+ e.preventDefault();
+ this.props.dispatch(toggleVisibility());
+ } else if (this.matchesKey(positionKey, e)) {
+ e.preventDefault();
+ this.props.dispatch(changePosition());
+ } else if (this.matchesKey(monitorKey, e)) {
+ e.preventDefault();
+ this.props.dispatch(changeMonitor());
+ }
+ }
+
+ handleSizeChange(requestedSize) {
+ this.props.dispatch(changeSize(requestedSize));
+ }
+
+ renderChild(child, index, otherProps) {
+ const { monitorState } = this.props;
+ const { childMonitorIndex, childMonitorStates } = monitorState;
+
+ if (index !== childMonitorIndex) {
+ return null;
+ }
+
+ return cloneElement(child, {
+ monitorState: childMonitorStates[index],
+ ...otherProps
+ });
+ }
+
+ render() {
+ const { monitorState, children, fluid, ...rest } = this.props;
+ const { position, isVisible, size } = monitorState;
+
+ return (
+
+ {Children.map(children, (child, index) =>
+ this.renderChild(child, index, rest)
+ )}
+
+ );
+ }
+}
diff --git a/packages/redux-devtools-dock-monitor/src/actions.js b/packages/redux-devtools-dock-monitor/src/actions.js
new file mode 100644
index 00000000..d253293c
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/src/actions.js
@@ -0,0 +1,20 @@
+export const TOGGLE_VISIBILITY =
+ '@@redux-devtools-log-monitor/TOGGLE_VISIBILITY';
+export function toggleVisibility() {
+ return { type: TOGGLE_VISIBILITY };
+}
+
+export const CHANGE_POSITION = '@@redux-devtools-log-monitor/CHANGE_POSITION';
+export function changePosition() {
+ return { type: CHANGE_POSITION };
+}
+
+export const CHANGE_SIZE = '@@redux-devtools-log-monitor/CHANGE_SIZE';
+export function changeSize(size) {
+ return { type: CHANGE_SIZE, size: size };
+}
+
+export const CHANGE_MONITOR = '@@redux-devtools-log-monitor/CHANGE_MONITOR';
+export function changeMonitor() {
+ return { type: CHANGE_MONITOR };
+}
diff --git a/packages/redux-devtools-dock-monitor/src/constants.js b/packages/redux-devtools-dock-monitor/src/constants.js
new file mode 100644
index 00000000..e935427f
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/src/constants.js
@@ -0,0 +1 @@
+export const POSITIONS = ['left', 'top', 'right', 'bottom'];
diff --git a/packages/redux-devtools-dock-monitor/src/index.js b/packages/redux-devtools-dock-monitor/src/index.js
new file mode 100644
index 00000000..5b553f1d
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/src/index.js
@@ -0,0 +1 @@
+export default from './DockMonitor';
diff --git a/packages/redux-devtools-dock-monitor/src/reducers.js b/packages/redux-devtools-dock-monitor/src/reducers.js
new file mode 100644
index 00000000..12bf5d4d
--- /dev/null
+++ b/packages/redux-devtools-dock-monitor/src/reducers.js
@@ -0,0 +1,68 @@
+import {
+ CHANGE_MONITOR,
+ CHANGE_POSITION,
+ CHANGE_SIZE,
+ TOGGLE_VISIBILITY
+} from './actions';
+import { POSITIONS } from './constants';
+import { Children } from 'react';
+
+function position(props, state = props.defaultPosition, action) {
+ return action.type === CHANGE_POSITION
+ ? POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length]
+ : state;
+}
+
+function size(props, state = props.defaultSize, action) {
+ return action.type === CHANGE_SIZE ? action.size : state;
+}
+
+function isVisible(props, state = props.defaultIsVisible, action) {
+ return action.type === TOGGLE_VISIBILITY ? !state : state;
+}
+
+function childMonitorStates(props, state = [], action) {
+ return Children.map(props.children, (child, index) =>
+ child.type.update(child.props, state[index], action)
+ );
+}
+
+function childMonitorIndex(props, state = 0, action) {
+ switch (action.type) {
+ case CHANGE_MONITOR:
+ return (state + 1) % Children.count(props.children);
+ default:
+ return state;
+ }
+}
+
+export default function reducer(props, state = {}, action) {
+ if (!state.childMonitorStates) {
+ Children.forEach(props.children, (child, index) => {
+ if (typeof child.type.update !== 'function') {
+ // eslint-disable-next-line no-console
+ console.error(
+ `Child of with the index ${index} ` +
+ `(${child.type.displayName || child.type.name || child.type}) ` +
+ 'does not appear to be a valid Redux DevTools monitor.'
+ );
+ }
+ });
+ }
+
+ return {
+ position: position(props, state.position, action),
+ isVisible: isVisible(props, state.isVisible, action),
+ size: size(props, state.size, action),
+ childMonitorIndex: childMonitorIndex(
+ props,
+ state.childMonitorIndex,
+ action
+ ),
+ childMonitorStates: childMonitorStates(
+ props,
+ state.childMonitorStates,
+ action
+ )
+ };
+}