├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── demo.gif ├── lib ├── main.js ├── rehover-context.js └── rehover.js ├── package-lock.json ├── package.json ├── src ├── main.js ├── rehover-context.js └── rehover.js └── test ├── __snapshots__ └── index.test.js.snap └── index.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-app", "es2015"], 3 | "plugins": [["transform-class-properties"]] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | install: 5 | - npm install 6 | script: 7 | - npm run test 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Paul Rosset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rehover 👐 2 | 3 | #### React hovering on two elements made simpler! 4 | 5 | [![Travis CI Build Status](https://travis-ci.org/PaulRosset/rehover.svg?branch=master)](https://travis-ci.org/PaulRosset/rehover) 6 | [![npm version](https://badge.fury.io/js/rehover.svg)](https://badge.fury.io/js/rehover) 7 | 8 | ![](demo.gif) 9 | 10 | ## Install 11 | 12 | ```sh 13 | yarn add rehover 14 | ``` 15 | 16 | ## Usage 17 | 18 | > React v16.0 required, [rendering arrays in render method.](https://reactjs.org/blog/2017/09/26/react-v16.0.html) 19 | 20 | ### To build something quickly... 21 | 22 | If you don't need to build something very complex, `Rehover` component is what you need, like the example below: 23 | 24 | * **Pros**: 25 | * Easy to get started 26 | * Very light to write (not verbose) 27 | * **Cons**: 28 | * Not very customizable 29 | * Restricted to 2 children only 30 | 31 | ```js 32 | import React from "react"; 33 | import { Rehover } from "rehover"; 34 | ``` 35 | 36 | ```jsx 37 | 38 | 39 |
40 | A 41 | B 42 | C 43 |
44 |
45 | ``` 46 | 47 | ### To go further... 48 | 49 | However, if you want to write something more customizable and complex, you should try the example below, which is using the [**new Context API from React**](https://reactjs.org/docs/context.html). 50 | 51 | * **Pros**: 52 | * You are free to customize, you are not anymore restricted to two childrens. 53 | * You are using the new context API 🔥 54 | * **Cons**: 55 | 56 | * Much more verbose. 57 | * Longer to write. 58 | 59 | ```js 60 | import React from "react"; 61 | import { RehoverProvider, RehoverConsumer } from "rehover"; 62 | ``` 63 | 64 | ```jsx 65 | 66 | 67 | {({ states, actions }) => ( 68 |
69 | 75 | {states.isOnSource || states.isOnTarget ? ( 76 |
77 |

List of items:

78 |
82 |
Item1
83 |
Item2
84 |
Item3
85 |
86 |
87 | ) : null} 88 |
89 | )} 90 |
91 |
92 | ``` 93 | 94 | > The new React context Api is available since the 16.3.0 version. 95 | 96 | ## API 97 | 98 | * Delay: Number `delay` 99 | 100 | * To let you the time to go to the target. 101 | 102 | * States: Function `states` 103 | * Function with `isOpen`, `isOntarget` and `isOnSource` as parameter `Boolean`, to let you construct animation for example. 104 | 105 | > If you want to pass a **_React Component_** as a source or destination, make sure that they got a onMouseEnter props and a onMouseLeave props at their root. 106 | 107 | ## Live demo/Sandbox 108 | 109 | [![Edit j2rjln010w](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/0o78oxx3w0) 110 | 111 | ## License 112 | 113 | MIT Paul Rosset 114 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulRosset/rehover/3f93a53f55b1f0adc74387d56be887d61b8cac78/demo.gif -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _rehoverContext = require("./rehover-context"); 8 | 9 | Object.defineProperty(exports, "RehoverProvider", { 10 | enumerable: true, 11 | get: function get() { 12 | return _interopRequireDefault(_rehoverContext).default; 13 | } 14 | }); 15 | Object.defineProperty(exports, "RehoverConsumer", { 16 | enumerable: true, 17 | get: function get() { 18 | return _rehoverContext.RehoverConsumer; 19 | } 20 | }); 21 | 22 | var _rehover = require("./rehover"); 23 | 24 | Object.defineProperty(exports, "Rehover", { 25 | enumerable: true, 26 | get: function get() { 27 | return _interopRequireDefault(_rehover).default; 28 | } 29 | }); 30 | 31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/rehover-context.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.RehoverConsumer = undefined; 7 | 8 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 9 | 10 | var _react = require("react"); 11 | 12 | var _react2 = _interopRequireDefault(_react); 13 | 14 | var _propTypes = require("prop-types"); 15 | 16 | var _propTypes2 = _interopRequireDefault(_propTypes); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 21 | 22 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 23 | 24 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 25 | 26 | var _React$createContext = _react2.default.createContext({ 27 | states: { 28 | isOnSource: false, 29 | isOnTarget: false, 30 | isOpen: false 31 | }, 32 | actions: { 33 | onMouseEnterSource: function onMouseEnterSource() {}, 34 | onMouseLeaveSource: function onMouseLeaveSource() {}, 35 | onMouseEnterTarget: function onMouseEnterTarget() {}, 36 | onMouseLeaveTarget: function onMouseLeaveTarget() {}, 37 | onKeyBoardSource: function onKeyBoardSource() {} 38 | } 39 | }), 40 | Provider = _React$createContext.Provider, 41 | Consumer = _React$createContext.Consumer; 42 | 43 | var RehoverConsumer = exports.RehoverConsumer = Consumer; 44 | 45 | var RehoverProvider = function (_Component) { 46 | _inherits(RehoverProvider, _Component); 47 | 48 | function RehoverProvider(props) { 49 | _classCallCheck(this, RehoverProvider); 50 | 51 | var _this = _possibleConstructorReturn(this, (RehoverProvider.__proto__ || Object.getPrototypeOf(RehoverProvider)).call(this, props)); 52 | 53 | _this.onMouseEnterSource = function () { 54 | _this.setState(function (prevState) { 55 | return { 56 | isOpen: true, 57 | isOnSource: true 58 | }; 59 | }, function () { 60 | return _this.props.states(_this.state); 61 | }); 62 | }; 63 | 64 | _this.onMouseLeaveSource = function () { 65 | setTimeout(function () { 66 | _this.setState(function (prevState) { 67 | return { 68 | isOnSource: false, 69 | isOpen: !prevState.isOnTarget ? false : true 70 | }; 71 | }, function () { 72 | return _this.props.states(_this.state); 73 | }); 74 | }, _this.props.delay); 75 | }; 76 | 77 | _this.onMouseEnterTarget = function () { 78 | _this.setState(function (prevState) { 79 | return { 80 | isOnSource: false, 81 | isOnTarget: true, 82 | isOpen: true 83 | }; 84 | }, function () { 85 | return _this.props.states(_this.state); 86 | }); 87 | }; 88 | 89 | _this.onMouseLeaveTarget = function () { 90 | setTimeout(function () { 91 | _this.setState(function (prevState) { 92 | return { 93 | isOnTarget: false, 94 | isOpen: !prevState.isOnSource ? false : true 95 | }; 96 | }, function () { 97 | return _this.props.states(_this.state); 98 | }); 99 | }, _this.props.delay); 100 | }; 101 | 102 | _this.onKeyBoardSource = function (e) { 103 | switch (e.keyCode) { 104 | case 40: 105 | _this.setState({ 106 | isOpen: true, 107 | isOnTarget: true 108 | }, function () { 109 | return _this.props.states(_this.state); 110 | }); 111 | break; 112 | case 38: 113 | _this.setState({ 114 | isOpen: false, 115 | isOnTarget: false 116 | }, function () { 117 | return _this.props.states(_this.state); 118 | }); 119 | break; 120 | default: 121 | break; 122 | } 123 | }; 124 | 125 | _this.state = { 126 | isOnTarget: false, 127 | isOnSource: false, 128 | isOpen: false 129 | }; 130 | _this.props.states(_this.state); 131 | return _this; 132 | } 133 | 134 | _createClass(RehoverProvider, [{ 135 | key: "render", 136 | value: function render() { 137 | return _react2.default.createElement( 138 | Provider, 139 | { 140 | value: { 141 | states: Object.assign({}, this.state), 142 | actions: { 143 | onMouseEnterSource: this.onMouseEnterSource, 144 | onMouseLeaveSource: this.onMouseLeaveSource, 145 | onMouseEnterTarget: this.onMouseEnterTarget, 146 | onMouseLeaveTarget: this.onMouseLeaveTarget, 147 | onKeyBoardSource: this.onKeyBoardSource 148 | } 149 | } 150 | }, 151 | this.props.children 152 | ); 153 | } 154 | }]); 155 | 156 | return RehoverProvider; 157 | }(_react.Component); 158 | 159 | exports.default = RehoverProvider; 160 | 161 | 162 | RehoverProvider.defaultProps = { 163 | delay: 0, 164 | states: function states() {} 165 | }; 166 | 167 | RehoverProvider.propTypes = { 168 | delay: _propTypes2.default.number, 169 | states: _propTypes2.default.func 170 | }; -------------------------------------------------------------------------------- /lib/rehover.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require("react"); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _propTypes = require("prop-types"); 14 | 15 | var _propTypes2 = _interopRequireDefault(_propTypes); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 22 | 23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 24 | 25 | var ReHover = function (_Component) { 26 | _inherits(ReHover, _Component); 27 | 28 | function ReHover(props) { 29 | _classCallCheck(this, ReHover); 30 | 31 | var _this = _possibleConstructorReturn(this, (ReHover.__proto__ || Object.getPrototypeOf(ReHover)).call(this, props)); 32 | 33 | _this.onMouseEnterSource = function () { 34 | _this.setState({ 35 | isOpen: true, 36 | isOnSource: true 37 | }, function () { 38 | return _this.props.states(_this.state); 39 | }); 40 | }; 41 | 42 | _this.onMouseLeaveSource = function () { 43 | setTimeout(function () { 44 | _this.setState(function (prevState) { 45 | return { 46 | isOnSource: false, 47 | isOpen: !prevState.isOnTarget ? false : true 48 | }; 49 | }, function () { 50 | return _this.props.states(_this.state); 51 | }); 52 | }, _this.props.delay); 53 | }; 54 | 55 | _this.onMouseEnterTarget = function () { 56 | _this.setState({ 57 | isOnSource: false, 58 | isOnTarget: true, 59 | isOpen: true 60 | }, function () { 61 | return _this.props.states(_this.state); 62 | }); 63 | }; 64 | 65 | _this.onMouseLeaveTarget = function () { 66 | setTimeout(function () { 67 | _this.setState(function (prevState) { 68 | return { 69 | isOnTarget: false, 70 | isOpen: !prevState.isOnSource ? false : true 71 | }; 72 | }, function () { 73 | return _this.props.states(_this.state); 74 | }); 75 | }, _this.props.delay); 76 | }; 77 | 78 | _this.onKeyBoardSource = function (e) { 79 | switch (e.keyCode) { 80 | case 40: 81 | _this.setState({ 82 | isOpen: true, 83 | isOnTarget: true 84 | }, function () { 85 | return _this.props.states(_this.state); 86 | }); 87 | break; 88 | case 38: 89 | _this.setState({ 90 | isOpen: false, 91 | isOnTarget: false 92 | }, function () { 93 | return _this.props.states(_this.state); 94 | }); 95 | break; 96 | default: 97 | break; 98 | } 99 | }; 100 | 101 | _this.state = { 102 | isOnTarget: false, 103 | isOnSource: false, 104 | isOpen: false 105 | }; 106 | _this.props.states(_this.state); 107 | return _this; 108 | } 109 | 110 | _createClass(ReHover, [{ 111 | key: "render", 112 | value: function render() { 113 | var _this2 = this; 114 | 115 | var ChildrenWithMouseEvent = _react2.default.Children.map(this.props.children, function (child) { 116 | return child.props.source ? _react2.default.cloneElement(child, { 117 | onMouseEnter: _this2.onMouseEnterSource, 118 | onMouseLeave: _this2.onMouseLeaveSource, 119 | onKeyDown: _this2.onKeyBoardSource, 120 | role: "source", 121 | tabIndex: 0, 122 | "aria-hidden": false 123 | }) : child.props.destination ? _react2.default.cloneElement(child, { 124 | onMouseEnter: _this2.onMouseEnterTarget, 125 | onMouseLeave: _this2.onMouseLeaveTarget, 126 | role: "destination", 127 | "aria-hidden": !_this2.state.isOpen 128 | }) : null; 129 | }); 130 | return this.state.isOnSource || this.state.isOnTarget ? ChildrenWithMouseEvent.slice(0, 2) : ChildrenWithMouseEvent.splice(ChildrenWithMouseEvent.findIndex(function (elem) { 131 | return elem.props.source; 132 | }), 1); 133 | } 134 | }]); 135 | 136 | return ReHover; 137 | }(_react.Component); 138 | 139 | exports.default = ReHover; 140 | 141 | 142 | ReHover.defaultProps = { 143 | delay: 0, 144 | states: function states() {} 145 | }; 146 | 147 | ReHover.propTypes = { 148 | delay: _propTypes2.default.number, 149 | states: _propTypes2.default.func 150 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rehover", 3 | "version": "0.4.0", 4 | "description": "React hovering on two elements made simpler!", 5 | "main": "./lib/main.js", 6 | "scripts": { 7 | "dev": "BABEL_ENV=development ./node_modules/.bin/babel --watch ./src/ --out-dir ./lib", 8 | "build": "BABEL_ENV=production ./node_modules/.bin/babel ./src/ --out-dir ./lib", 9 | "test": "./node_modules/.bin/eslint src/ && ./node_modules/.bin/jest --coverage" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/PaulRosset/rehover.git" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "hover" 18 | ], 19 | "author": "Paul Rosset ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/PaulRosset/rehover/issues" 23 | }, 24 | "homepage": "https://github.com/PaulRosset/rehover#readme", 25 | "dependencies": { 26 | "prop-types": "^15.6.1", 27 | "react": "^16.3.2" 28 | }, 29 | "devDependencies": { 30 | "babel-cli": "^6.26.0", 31 | "babel-eslint": "^8.2.2", 32 | "babel-plugin-transform-class-properties": "^6.24.1", 33 | "babel-preset-es2015": "^6.24.1", 34 | "babel-preset-react-app": "^3.1.1", 35 | "eslint": "^4.19.0", 36 | "eslint-plugin-prettier": "^2.6.0", 37 | "jest": "^22.4.3", 38 | "prettier": "^1.11.1", 39 | "react-dom": "^16.3.2", 40 | "react-test-renderer": "^16.3.1", 41 | "react-testing-library": "^1.9.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | export { default as RehoverProvider, RehoverConsumer } from "./rehover-context"; 2 | export { default as Rehover } from "./rehover"; 3 | -------------------------------------------------------------------------------- /src/rehover-context.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const { Provider, Consumer } = React.createContext({ 5 | states: { 6 | isOnSource: false, 7 | isOnTarget: false, 8 | isOpen: false 9 | }, 10 | actions: { 11 | onMouseEnterSource: () => {}, 12 | onMouseLeaveSource: () => {}, 13 | onMouseEnterTarget: () => {}, 14 | onMouseLeaveTarget: () => {}, 15 | onKeyBoardSource: () => {} 16 | } 17 | }); 18 | 19 | export const RehoverConsumer = Consumer; 20 | 21 | export default class RehoverProvider extends Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | isOnTarget: false, 26 | isOnSource: false, 27 | isOpen: false 28 | }; 29 | this.props.states(this.state); 30 | } 31 | 32 | onMouseEnterSource = () => { 33 | this.setState( 34 | prevState => ({ 35 | isOpen: true, 36 | isOnSource: true 37 | }), 38 | () => this.props.states(this.state) 39 | ); 40 | }; 41 | 42 | onMouseLeaveSource = () => { 43 | setTimeout(() => { 44 | this.setState( 45 | prevState => ({ 46 | isOnSource: false, 47 | isOpen: !prevState.isOnTarget ? false : true 48 | }), 49 | () => this.props.states(this.state) 50 | ); 51 | }, this.props.delay); 52 | }; 53 | 54 | onMouseEnterTarget = () => { 55 | this.setState( 56 | prevState => ({ 57 | isOnSource: false, 58 | isOnTarget: true, 59 | isOpen: true 60 | }), 61 | () => this.props.states(this.state) 62 | ); 63 | }; 64 | 65 | onMouseLeaveTarget = () => { 66 | setTimeout(() => { 67 | this.setState( 68 | prevState => ({ 69 | isOnTarget: false, 70 | isOpen: !prevState.isOnSource ? false : true 71 | }), 72 | () => this.props.states(this.state) 73 | ); 74 | }, this.props.delay); 75 | }; 76 | 77 | onKeyBoardSource = e => { 78 | switch (e.keyCode) { 79 | case 40: 80 | this.setState( 81 | { 82 | isOpen: true, 83 | isOnTarget: true 84 | }, 85 | () => this.props.states(this.state) 86 | ); 87 | break; 88 | case 38: 89 | this.setState( 90 | { 91 | isOpen: false, 92 | isOnTarget: false 93 | }, 94 | () => this.props.states(this.state) 95 | ); 96 | break; 97 | default: 98 | break; 99 | } 100 | }; 101 | 102 | render() { 103 | return ( 104 | 118 | {this.props.children} 119 | 120 | ); 121 | } 122 | } 123 | 124 | RehoverProvider.defaultProps = { 125 | delay: 0, 126 | states: () => {} 127 | }; 128 | 129 | RehoverProvider.propTypes = { 130 | delay: PropTypes.number, 131 | states: PropTypes.func 132 | }; 133 | -------------------------------------------------------------------------------- /src/rehover.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | export default class ReHover extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | isOnTarget: false, 9 | isOnSource: false, 10 | isOpen: false 11 | }; 12 | this.props.states(this.state); 13 | } 14 | 15 | onMouseEnterSource = () => { 16 | this.setState( 17 | { 18 | isOpen: true, 19 | isOnSource: true 20 | }, 21 | () => this.props.states(this.state) 22 | ); 23 | }; 24 | 25 | onMouseLeaveSource = () => { 26 | setTimeout(() => { 27 | this.setState( 28 | prevState => ({ 29 | isOnSource: false, 30 | isOpen: !prevState.isOnTarget ? false : true 31 | }), 32 | () => this.props.states(this.state) 33 | ); 34 | }, this.props.delay); 35 | }; 36 | 37 | onMouseEnterTarget = () => { 38 | this.setState( 39 | { 40 | isOnSource: false, 41 | isOnTarget: true, 42 | isOpen: true 43 | }, 44 | () => this.props.states(this.state) 45 | ); 46 | }; 47 | 48 | onMouseLeaveTarget = () => { 49 | setTimeout(() => { 50 | this.setState( 51 | prevState => ({ 52 | isOnTarget: false, 53 | isOpen: !prevState.isOnSource ? false : true 54 | }), 55 | () => this.props.states(this.state) 56 | ); 57 | }, this.props.delay); 58 | }; 59 | 60 | onKeyBoardSource = e => { 61 | switch (e.keyCode) { 62 | case 40: 63 | this.setState( 64 | { 65 | isOpen: true, 66 | isOnTarget: true 67 | }, 68 | () => this.props.states(this.state) 69 | ); 70 | break; 71 | case 38: 72 | this.setState( 73 | { 74 | isOpen: false, 75 | isOnTarget: false 76 | }, 77 | () => this.props.states(this.state) 78 | ); 79 | break; 80 | default: 81 | break; 82 | } 83 | }; 84 | 85 | render() { 86 | const ChildrenWithMouseEvent = React.Children.map( 87 | this.props.children, 88 | child => { 89 | return child.props.source 90 | ? React.cloneElement(child, { 91 | onMouseEnter: this.onMouseEnterSource, 92 | onMouseLeave: this.onMouseLeaveSource, 93 | onKeyDown: this.onKeyBoardSource, 94 | role: "source", 95 | tabIndex: 0, 96 | "aria-hidden": false 97 | }) 98 | : child.props.destination 99 | ? React.cloneElement(child, { 100 | onMouseEnter: this.onMouseEnterTarget, 101 | onMouseLeave: this.onMouseLeaveTarget, 102 | role: "destination", 103 | "aria-hidden": !this.state.isOpen 104 | }) 105 | : null; 106 | } 107 | ); 108 | return this.state.isOnSource || this.state.isOnTarget 109 | ? ChildrenWithMouseEvent.slice(0, 2) 110 | : ChildrenWithMouseEvent.splice( 111 | ChildrenWithMouseEvent.findIndex(elem => elem.props.source), 112 | 1 113 | ); 114 | } 115 | } 116 | 117 | ReHover.defaultProps = { 118 | delay: 0, 119 | states: () => {} 120 | }; 121 | 122 | ReHover.propTypes = { 123 | delay: PropTypes.number, 124 | states: PropTypes.func 125 | }; 126 | -------------------------------------------------------------------------------- /test/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Testing Rehover with Children Compound Testing the normal working Snapshot testing... Shot Only Source, when MouseLeaving 1`] = ` 4 |

13 | Source React element 14 |

15 | `; 16 | 17 | exports[`Testing Rehover with Children Compound Testing the normal working Snapshot testing... Shot Props passed 1`] = ` 18 | Object { 19 | "children": Array [ 20 |

23 | Source React element 24 |

, 25 |
28 | Destination React element 29 |
, 30 | ], 31 | "delay": 150, 32 | "states": [Function], 33 | } 34 | `; 35 | 36 | exports[`Testing Rehover with Children Compound Testing the normal working Snapshot testing... Shot both children nodes, when mouseEntering 1`] = ` 37 | Array [ 38 |

47 | Source React element 48 |

, 49 |
56 | Destination React element 57 |
, 58 | ] 59 | `; 60 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, Simulate, wait } from "react-testing-library"; 3 | import { Rehover } from "../lib/main"; 4 | import ReactTestRenderer from "react-test-renderer"; 5 | 6 | let testingUtilities; 7 | let Source; 8 | let ShotRenderer; 9 | 10 | describe("Testing Rehover with Children Compound", () => { 11 | describe("Testing the normal working", () => { 12 | beforeAll(() => { 13 | testingUtilities = render( 14 | 15 |

Source React element

16 |
Destination React element
17 |
18 | ); 19 | Source = testingUtilities.getByText("Source React element"); 20 | }); 21 | 22 | test("Check Source react element presence and check destination is not present", () => { 23 | expect(Source.textContent).toEqual("Source React element"); 24 | expect( 25 | testingUtilities.queryByText("Destination React element") 26 | ).toBeNull(); 27 | }); 28 | 29 | test("Check presence of destination when MouseEntering", () => { 30 | Simulate.mouseEnter(Source); 31 | expect( 32 | testingUtilities.getByText("Destination React element").textContent 33 | ).toEqual("Destination React element"); 34 | }); 35 | 36 | test("Check when MouseEntering the destination", () => { 37 | //Simulate.mouseLeave(Source); 38 | Simulate.mouseEnter( 39 | testingUtilities.getByText("Destination React element") 40 | ); 41 | expect( 42 | testingUtilities.getByText("Destination React element").textContent 43 | ).toEqual("Destination React element"); 44 | }); 45 | 46 | test("Aria Compatibility, close with Arrow Up", () => { 47 | Simulate.keyDown(Source, { keyCode: 38 }); 48 | expect( 49 | testingUtilities.queryByText("Destination React element") 50 | ).toBeNull(); 51 | }); 52 | 53 | test("Aria Compatibility, Open with Arrow Down", () => { 54 | Simulate.keyDown(Source, { keyCode: 40 }); 55 | expect( 56 | testingUtilities.getByText("Destination React element").textContent 57 | ).toEqual("Destination React element"); 58 | }); 59 | 60 | describe("Snapshot testing...", () => { 61 | beforeAll(() => { 62 | ShotRenderer = ReactTestRenderer.create( 63 | 64 |

Source React element

65 |
Destination React element
66 |
67 | ); 68 | }); 69 | 70 | test("Shot both children nodes, when mouseEntering", () => { 71 | ShotRenderer.root.findByType("p").props.onMouseEnter(); 72 | expect(ShotRenderer.toJSON()).toMatchSnapshot(); 73 | }); 74 | 75 | test("Shot Only Source, when MouseLeaving", () => { 76 | ShotRenderer.root.instance.setState({ isOnSource: false }); 77 | expect(ShotRenderer.toJSON()).toMatchSnapshot(); 78 | }); 79 | 80 | test("Shot Props passed", () => { 81 | expect(ShotRenderer.root.instance.props).toMatchSnapshot(); 82 | }); 83 | }); 84 | }); 85 | }); 86 | 87 | describe("Testing Rehover with Context compound", () => {}); 88 | --------------------------------------------------------------------------------