├── .babelrc ├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .prettierrc.json ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── .eslintrc ├── ReactHover.js ├── example │ ├── Example.js │ ├── HoverComponent.js │ ├── TriggerComponent.js │ ├── codeblocks.js │ ├── component.css │ ├── react-hover-new.gif │ └── styles.css ├── index.d.ts ├── index.js └── lib │ ├── Hover.js │ └── Trigger.js ├── test ├── .eslintrc └── index.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "css-modules-transform", 8 | "@babel/plugin-proposal-class-properties", 9 | "@babel/plugin-syntax-object-rest-spread" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | node: circleci/node@1.1.4 5 | 6 | jobs: 7 | build: 8 | executor: 9 | name: node/default 10 | tag: lts 11 | steps: 12 | - checkout 13 | - node/install-yarn 14 | - node/with-cache: 15 | steps: 16 | - run: yarn install --frozen-lockfile 17 | - run: npm run test 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "quotes": 0, 5 | "no-trailing-spaces": 0, 6 | "eqeqeq": 0, 7 | "no-underscore-dangle": 0, 8 | "no-undef": 0, 9 | "no-extra-boolean-cast": 0, 10 | "no-mixed-spaces-and-tabs": 0, 11 | "no-alert": 0, 12 | "no-shadow": 0, 13 | "no-empty": 0, 14 | "no-irregular-whitespace": 0, 15 | "no-multi-spaces": 0, 16 | "no-new": 0, 17 | "no-unused-vars": 2, // disallow declaration of variables that are not used in the code 18 | "no-redeclare": 2, // disallow declaring the same variable more then once 19 | "new-cap": 0, 20 | // 21 | // eslint-plugin-react 22 | // 23 | // React specific linting rules for ESLint 24 | // 25 | //"react/display-name": 0, // Prevent missing displayName in a React component definition 26 | "react/jsx-boolean-value": [2, "always"], // Enforce boolean attributes notation in JSX 27 | "react/jsx-no-undef": 2, // Disallow undeclared variables in JSX 28 | "react/jsx-quotes": 0, 29 | "react/jsx-sort-prop-types": 0, // Enforce propTypes declarations alphabetical sorting 30 | "react/jsx-sort-props": 0, // Enforce props alphabetical sorting 31 | "react/jsx-uses-react": 2, // Prevent React to be incorrectly marked as unused 32 | "react/jsx-uses-vars": 2, // Prevent variables used in JSX to be incorrectly marked as unused 33 | //"react/no-did-mount-set-state": 2, // Prevent usage of setState in componentDidMount 34 | "react/no-did-update-set-state": 2, // Prevent usage of setState in componentDidUpdate 35 | "react/no-multi-comp": 0, // Prevent multiple component definition per file 36 | "react/no-unknown-property": 2, // Prevent usage of unknown DOM property 37 | "react/prop-types": 2, // Prevent missing props validation in a React component definition 38 | "react/react-in-jsx-scope": 2, // Prevent missing React when using JSX 39 | "react/self-closing-comp": 2, // Prevent extra closing tags for components without children 40 | }, 41 | "globals": { 42 | "jQuery": true, 43 | "$": true, 44 | "reveal": true, 45 | "Pikaday": true, 46 | "NProgress": true, 47 | "cytoscape": true 48 | }, 49 | "plugins": ["react"], 50 | "ecmaFeatures": { 51 | "arrowFunctions": true, 52 | "binaryLiterals": true, 53 | "blockBindings": true, 54 | "classes": true, 55 | "defaultParams": true, 56 | "destructuring": true, 57 | "forOf": true, 58 | "generators": true, 59 | "modules": true, 60 | "objectLiteralComputedProperties": true, 61 | "objectLiteralDuplicateProperties": true, 62 | "objectLiteralShorthandMethods": true, 63 | "objectLiteralShorthandProperties": true, 64 | "octalLiterals": true, 65 | "regexUFlag": true, 66 | "regexYFlag": true, 67 | "spread": true, 68 | "superInFunctions": true, 69 | "templateStrings": true, 70 | "unicodeCodePointEscapes": true, 71 | "globalReturn": true, 72 | "jsx": true 73 | }, 74 | "env": { 75 | "browser": true, 76 | "es6": true 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | /dist/ 11 | 12 | # Dependency directory 13 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 14 | node_modules 15 | 16 | # Precommit hook 17 | .jshint* 18 | 19 | /example/ 20 | .coveralls.yml 21 | /reports/ 22 | gulpfile.js 23 | coverage 24 | 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "semi": false, 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | - "13" 5 | - "14" 6 | after_script: 7 | - 'npm run coveralls' 8 | - 'npm run coveralls' 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Robert Chang 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

React Hover --- Turn anything to a 'hoverable' object

2 | 3 |

4 | 5 | Circle CI 7 | 8 | 9 | 10 | NPM Version 12 | 13 | 14 | 15 | Coverage Status 16 | 17 | 18 | 19 | Build Status 21 | 22 | 23 | 24 | Downloads 26 | 27 | 28 | 29 | License 31 | 32 |

33 | 34 |

35 | 36 |

37 | 38 | ![React hover](src/example/react-hover-new.gif) 39 | 40 | ## Installation 41 | 42 | ### npm 43 | 44 | ``` 45 | $ npm install --save react-hover 46 | ``` 47 | 48 | ## Codesandbox Demo 49 | 50 | [Codesandbox example](https://codesandbox.io/s/charming-snyder-3hund?fontsize=14&hidenavigation=1&theme=dark) 51 | 52 | ## Demo 53 | 54 | [Demo](http://cht8687.github.io/react-hover/example/) 55 | 56 | 57 | ## Usage 58 | 59 | You can turn plain HTML or your custom trigger/hover components in React-hover. 60 | 61 | Below is the example of custom components: 62 | 63 | ```js 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ``` 73 | 74 | Or plain HTML element: 75 | 76 | ```js 77 | 78 | 79 |

Hover on me

80 |
81 | 82 |

I am hover HTML

83 |
84 |
85 | ``` 86 | 87 | ## Options 88 | 89 | #### `options`: PropTypes.object.isRequired 90 | 91 | Set the options. 92 | 93 | ```js 94 | const options = { 95 | followCursor: true, 96 | shiftX: 20, 97 | shiftY: 0, 98 | } 99 | ``` 100 | 101 | `followCursor`: define if hover object follow mouse cursor 102 | `shiftX`: left-right shift the hover object to the mouse cursor 103 | `shiftY`: up-down shift the hover object to the mouse cursor 104 | 105 | ## type 106 | 107 | #### `type`: PropTypes.string 108 | 109 | Set the type. 110 | 111 | ```js 112 | 113 | 114 | 115 | 116 | ``` 117 | 118 | This prop defines the type name. It must be declared as above if you minify your code in production. 119 | 120 | ## Development 121 | 122 | ``` 123 | $ git clone git@github.com:cht8687/react-hover.git 124 | $ cd react-hover 125 | $ npm install 126 | $ npm run dev 127 | ``` 128 | 129 | Then 130 | 131 | ``` 132 | open http://localhost:8080/webpack-dev-server/ 133 | ``` 134 | 135 | ## Want to buy me a coffee? 136 | 137 | [![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/X8X71IORB) 138 | 139 | ## License 140 | 141 | MIT 142 | 143 | ## Contributors 144 | 145 | Thanks to these wonderful developers for helping this project: 146 | 147 |

148 | 149 | 150 | 151 | 152 |

153 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hover", 3 | "version": "3.0.1", 4 | "description": "A handy hover tool for React", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server -d --history-api-fallback --hot --inline --progress --colors --port 8080", 8 | "prepublishOnly": "parallelshell -w \"npm run build:dist -s\" \"npm run build:example -s\" \"npm run build:bower -s\"", 9 | "prebuild": "rimraf dist example build", 10 | "build:dist": "babel src --out-dir dist --source-maps --ignore src/example", 11 | "build:example": "webpack --config webpack.config.js", 12 | "postbuild": "npm run test -s", 13 | "test": "babel-node test/index.js | tnyan", 14 | "coverage": "babel-node node_modules/isparta/bin/isparta cover test/index.js", 15 | "coveralls": "npm run coverage -s && coveralls < coverage/lcov.info", 16 | "postcoveralls": "rimraf ./coverage" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/cht8687/react-hover.git" 21 | }, 22 | "keywords": [ 23 | "react", 24 | "react-component", 25 | "component", 26 | "react-tooltip", 27 | "tooltip", 28 | "hover", 29 | "react hover" 30 | ], 31 | "files": [ 32 | "dist" 33 | ], 34 | "author": "Robert Chang ", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/cht8687/react-hover/issues" 38 | }, 39 | "homepage": "https://github.com/cht8687/react-hover#readme", 40 | "devDependencies": { 41 | "@babel/cli": "^7.0.0", 42 | "@babel/core": "^7.0.1", 43 | "@babel/node": "^7.0.0", 44 | "@babel/plugin-proposal-class-properties": "^7.0.0", 45 | "@babel/plugin-syntax-object-rest-spread": "^7.0.0", 46 | "@babel/preset-env": "^7.0.0", 47 | "@babel/preset-react": "^7.0.0", 48 | "babel-eslint": "^9.0.0", 49 | "babel-loader": "^8.0.2", 50 | "babel-plugin-css-modules-transform": "^1.2.7", 51 | "babel-tape-runner": "^3.0.0", 52 | "classnames": "^2.2.5", 53 | "codecov.io": "^0.1.6", 54 | "coveralls": "^3.0.2", 55 | "css-loader": "^3.2.0", 56 | "enzyme": "^3.7.0", 57 | "enzyme-adapter-react-16": "^1.6.0", 58 | "eslint": "^5.6.0", 59 | "eslint-loader": "^2.1.0", 60 | "eslint-plugin-react": "^7.11.1", 61 | "extract-text-webpack-plugin": "^3.0.2", 62 | "faucet": "0.0.1", 63 | "html-webpack-plugin": "^3.2.0", 64 | "isparta": "^4.0.0", 65 | "mini-css-extract-plugin": "^0.4.2", 66 | "parallelshell": "^3.0.0", 67 | "prettier": "^2.0.5", 68 | "react": "^16.13.1", 69 | "react-code-blocks": "0.0.9-0", 70 | "react-dom": "^16.13.1", 71 | "react-hot-loader": "^3.1.1", 72 | "react-test-renderer": "^16.13.1", 73 | "rimraf": "^2.4.3", 74 | "sinon": "^1.17.3", 75 | "snazzy": "^8.0.0", 76 | "tap-nyan": "0.0.2", 77 | "tap-xunit": "^2.3.0", 78 | "tape": "^4.5.1", 79 | "webpack": "^4.19.0", 80 | "webpack-cli": "^3.1.0", 81 | "webpack-dev-server": "^3.1.14" 82 | }, 83 | "dependencies": { 84 | "prop-types": "^15.5.10" 85 | }, 86 | "husky": { 87 | "hooks": { 88 | "pre-commit": "lint-staged" 89 | } 90 | }, 91 | "lint-staged": { 92 | "*.{js,ts,tsx,json,css,md}": [ 93 | "prettier --write", 94 | "git add" 95 | ] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "quotes": 0, 5 | "no-trailing-spaces": 0, 6 | "eqeqeq": 0, 7 | "no-underscore-dangle": 0, 8 | "no-undef": 0, 9 | "no-extra-boolean-cast": 0, 10 | "no-mixed-spaces-and-tabs": 0, 11 | "no-alert": 0, 12 | "no-shadow": 0, 13 | "no-empty": 0, 14 | "no-irregular-whitespace": 0, 15 | "no-multi-spaces": 0, 16 | "no-new": 0, 17 | "no-unused-vars": 2, // disallow declaration of variables that are not used in the code 18 | "no-redeclare": 2, // disallow declaring the same variable more then once 19 | "new-cap": 0, 20 | // 21 | // eslint-plugin-react 22 | // 23 | // React specific linting rules for ESLint 24 | // 25 | //"react/display-name": 0, // Prevent missing displayName in a React component definition 26 | "react/jsx-boolean-value": [2, "always"], // Enforce boolean attributes notation in JSX 27 | "react/jsx-no-undef": 2, // Disallow undeclared variables in JSX 28 | "react/jsx-quotes": 0, 29 | "react/jsx-sort-prop-types": 0, // Enforce propTypes declarations alphabetical sorting 30 | "react/jsx-sort-props": 0, // Enforce props alphabetical sorting 31 | "react/jsx-uses-react": 2, // Prevent React to be incorrectly marked as unused 32 | "react/jsx-uses-vars": 2, // Prevent variables used in JSX to be incorrectly marked as unused 33 | //"react/no-did-mount-set-state": 2, // Prevent usage of setState in componentDidMount 34 | "react/no-did-update-set-state": 2, // Prevent usage of setState in componentDidUpdate 35 | "react/no-multi-comp": 0, // Prevent multiple component definition per file 36 | "react/no-unknown-property": 2, // Prevent usage of unknown DOM property 37 | "react/prop-types": 2, // Prevent missing props validation in a React component definition 38 | "react/react-in-jsx-scope": 2, // Prevent missing React when using JSX 39 | "react/self-closing-comp": 2, // Prevent extra closing tags for components without children 40 | }, 41 | "globals": { 42 | "jQuery": true, 43 | "$": true, 44 | "reveal": true, 45 | "Pikaday": true, 46 | "NProgress": true, 47 | "cytoscape": true 48 | }, 49 | "plugins": ["react"], 50 | "ecmaFeatures": { 51 | "arrowFunctions": true, 52 | "binaryLiterals": true, 53 | "blockBindings": true, 54 | "classes": true, 55 | "defaultParams": true, 56 | "destructuring": true, 57 | "forOf": true, 58 | "generators": true, 59 | "modules": true, 60 | "objectLiteralComputedProperties": true, 61 | "objectLiteralDuplicateProperties": true, 62 | "objectLiteralShorthandMethods": true, 63 | "objectLiteralShorthandProperties": true, 64 | "octalLiterals": true, 65 | "regexUFlag": true, 66 | "regexYFlag": true, 67 | "spread": true, 68 | "superInFunctions": true, 69 | "templateStrings": true, 70 | "unicodeCodePointEscapes": true, 71 | "globalReturn": true, 72 | "jsx": true 73 | }, 74 | "env": { 75 | "browser": true, 76 | "es6": true 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ReactHover.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Hover from './lib/Hover' 3 | import Trigger from './lib/Trigger' 4 | import PropTypes from 'prop-types' 5 | 6 | const propTypes = { 7 | children: PropTypes.array.isRequired, 8 | options: PropTypes.object.isRequired, 9 | className: PropTypes.string, 10 | } 11 | 12 | function renderItem(item, index) { 13 | if (item.type.name === 'Trigger' || item.props.type === 'trigger') { 14 | return {item} 15 | } else if (item.type.name === 'Hover' || item.props.type === 'hover') { 16 | return {item} 17 | } 18 | } 19 | 20 | function ReactHover(props) { 21 | let [hoverComponentStyle, updateHoverComponentStyle] = useState({ 22 | display: 'none', 23 | position: 'absolute', 24 | }) 25 | 26 | const setVisibility = flag => { 27 | let updatedStyles = null 28 | if (flag) { 29 | updatedStyles = { ...hoverComponentStyle, display: 'block' } 30 | } else { 31 | updatedStyles = { ...hoverComponentStyle, display: 'none' } 32 | } 33 | updateHoverComponentStyle(updatedStyles) 34 | } 35 | 36 | const getCursorPos = e => { 37 | const cursorX = e.pageX 38 | const cursorY = e.pageY 39 | let { 40 | options: { followCursor, shiftX, shiftY }, 41 | } = props 42 | let updatedStyles = null 43 | if (!followCursor) { 44 | return 45 | } 46 | if (isNaN(shiftX)) { 47 | shiftX = 0 48 | } 49 | if (isNaN(shiftY)) { 50 | shiftY = 0 51 | } 52 | updatedStyles = { 53 | ...hoverComponentStyle, 54 | top: cursorY + shiftY, 55 | left: cursorX + shiftX, 56 | } 57 | updateHoverComponentStyle(updatedStyles) 58 | } 59 | 60 | let childrenWithProps = [] 61 | for (let child of props.children) { 62 | if (child.props) { 63 | if (child.type.name === 'Trigger' || child.props.type === 'trigger') { 64 | childrenWithProps.push( 65 | React.cloneElement(child, { 66 | setVisibility: setVisibility, 67 | getCursorPos: getCursorPos, 68 | }), 69 | ) 70 | } else if (child.type.name === 'Hover' || child.props.type === 'hover') { 71 | childrenWithProps.push( 72 | React.cloneElement(child, { 73 | styles: hoverComponentStyle, 74 | setVisibility: setVisibility, 75 | getCursorPos: getCursorPos, 76 | }), 77 | ) 78 | } 79 | } 80 | } 81 | 82 | return ( 83 |
{childrenWithProps.map((item, index) => renderItem(item, index))}
84 | ) 85 | } 86 | 87 | ReactHover.propTypes = propTypes 88 | 89 | export { Trigger, Hover } 90 | export default ReactHover 91 | -------------------------------------------------------------------------------- /src/example/Example.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { render } from 'react-dom' 3 | import { CopyBlock, nord } from 'react-code-blocks' 4 | import { customComponentSnippet, plainCodeSnippet } from './codeblocks' 5 | import ReactHover, { Trigger, Hover } from '..' 6 | import HoverComponent from './HoverComponent' 7 | import TriggerComponent from './TriggerComponent' 8 | import './styles.css' 9 | import './component.css' 10 | 11 | const optionsCursorTrueWithMargin = { 12 | followCursor: true, 13 | shiftX: 20, 14 | shiftY: 0, 15 | } 16 | 17 | class App extends Component { 18 | render() { 19 | return ( 20 |
21 |
22 | {' '} 23 | React-hover 24 |
25 | Github 26 |
27 |
28 |
29 |

30 | {' '} 31 | Use custom components as trigger and hover{' '} 32 |

33 |
34 |
35 | 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 |
55 |

Use HTML as trigger and hover

56 |
57 |
58 | 66 |
67 |
68 | 69 | 70 |

77 | {' '} 78 |

Hover on me

79 |

80 |
81 | 82 |
83 | Albert Einstein 88 |
89 | {' '} 90 | Two things are infinite: the universe and human stupidity; 91 | and I'm not sure about the universe.{' '} 92 |
93 |

--Albert Einstein

94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | ) 102 | } 103 | } 104 | 105 | const appRoot = document.createElement('div') 106 | appRoot.id = 'app' 107 | document.body.appendChild(appRoot) 108 | 109 | render(, appRoot) 110 | -------------------------------------------------------------------------------- /src/example/HoverComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './component.css' 3 | 4 | export default class HoverComponent extends Component { 5 | render() { 6 | return ( 7 |
8 | Albert Einstein 13 |
14 | {' '} 15 | Two things are infinite: the universe and human stupidity; and I'm not 16 | sure about the universe.{' '} 17 |
18 |

--Albert Einstein

19 |
20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/example/TriggerComponent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './component.css' 3 | 4 | export default class TriggerComponent extends Component { 5 | render() { 6 | return ( 7 |

8 |

Hover on me

9 |

10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/example/codeblocks.js: -------------------------------------------------------------------------------- 1 | export const customComponentSnippet = ` 3 | 4 | 5 | 6 | 13 |

Hover on me

14 |
15 | 16 |
17 | Albert Einstein 18 |
Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.
19 |

--Albert Einstein

20 |
21 |
22 | 23 | ` 24 | -------------------------------------------------------------------------------- /src/example/component.css: -------------------------------------------------------------------------------- 1 | .hover { 2 | background-color: papayawhip; 3 | width: 300px; 4 | height: 150px; 5 | border-style: dashed; 6 | border-color: chocolate; 7 | } 8 | 9 | .trigger { 10 | background-color: #44b39d; 11 | color: white; 12 | width: 200px; 13 | } 14 | 15 | .quote { 16 | padding: 15px 25px; 17 | } 18 | 19 | .thumbnail { 20 | float: left; 21 | margin-top: 40px; 22 | } 23 | 24 | .people { 25 | float: right; 26 | margin-top: -23px; 27 | } 28 | -------------------------------------------------------------------------------- /src/example/react-hover-new.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cht8687/react-hover/abb0def792f5ad55de9fa18e1aa71be92288f17e/src/example/react-hover-new.gif -------------------------------------------------------------------------------- /src/example/styles.css: -------------------------------------------------------------------------------- 1 | .container { 2 | } 3 | 4 | .title { 5 | color: #fff; 6 | text-align: center; 7 | background-color: #44b39d; 8 | position: relative; 9 | font-size: 16px; 10 | line-height: 1.5; 11 | text-align: center; 12 | padding: 5rem; 13 | margin-bottom: 2rem; 14 | font-size: 3.25rem; 15 | } 16 | 17 | .main { 18 | padding: 0 7rem; 19 | font-size: 1.1rem; 20 | } 21 | 22 | .description { 23 | color: rgba(255, 255, 255, 0.7); 24 | background-color: rgba(255, 255, 255, 0.08); 25 | -webkit-transition: color 0.2s, background-color 0.2s, border-color 0.2s; 26 | transition: color 0.2s, background-color 0.2s, border-color 0.2s; 27 | text-decoration: none; 28 | text-align: center; 29 | padding: 0.6rem 0.9rem; 30 | font-size: 0.9rem; 31 | width: 50px; 32 | margin: 0 auto; 33 | } 34 | 35 | .description a { 36 | color: rgba(255, 255, 255, 0.7); 37 | } 38 | 39 | .subtitle { 40 | margin-top: 2rem; 41 | margin-bottom: 1rem; 42 | font-weight: normal; 43 | color: #44b39d; 44 | text-align: center; 45 | } 46 | 47 | .subcontainer { 48 | display: flex; 49 | -ms-flex-align: stretch; 50 | align-items: stretch; 51 | -ms-flex-pack: justify; 52 | justify-content: space-between; 53 | background-color: #eae9e9; 54 | } 55 | 56 | .subleft { 57 | width: calc(50% - 30px); 58 | overflow-x: auto; 59 | border: 1px dashed rgb(68 179 157); 60 | } 61 | 62 | .subright { 63 | width: calc(50% - 30px); 64 | border: 1px dashed rgb(68 179 157); 65 | display: flex; 66 | justify-content: center; 67 | align-items: center; 68 | } 69 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | declare module 'react-hover' { 4 | export interface ReactHoverOptions{ 5 | followCursor: boolean, 6 | shiftX: number, 7 | shiftY: number 8 | } 9 | 10 | export interface ReactHoverProps { 11 | options: ReactHoverOptions 12 | } 13 | 14 | export interface TriggerProps { 15 | type: string 16 | } 17 | 18 | export interface HoverProps { 19 | type: string 20 | } 21 | 22 | export class Trigger extends React.Component { } 23 | export class Hover extends React.Component { } 24 | export class ReactHover extends React.Component{ } 25 | 26 | export default ReactHover; 27 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReactHover, { Trigger, Hover } from './ReactHover' 2 | export { Trigger, Hover } 3 | export default ReactHover 4 | -------------------------------------------------------------------------------- /src/lib/Hover.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const propTypes = { 5 | type: PropTypes.string, 6 | children: PropTypes.object, 7 | styles: PropTypes.object, 8 | setVisibility: PropTypes.func, 9 | getCursorPos: PropTypes.func, 10 | } 11 | 12 | function Hover(props){ 13 | const { setVisibility, getCursorPos, styles } = props.children.props; 14 | 15 | return( 16 |
setVisibility(true)} 18 | onMouseOut={() => setVisibility(false)} 19 | onMouseMove={(e) => getCursorPos(e)} 20 | style={styles} 21 | > 22 | {props.children.props.children} 23 |
24 | ) 25 | } 26 | 27 | Hover.propTypes = propTypes; 28 | 29 | export default Hover; -------------------------------------------------------------------------------- /src/lib/Trigger.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import ReactDom from 'react-dom' 3 | import PropTypes from 'prop-types' 4 | 5 | const propTypes = { 6 | type: PropTypes.string, 7 | children: PropTypes.object, 8 | setVisibility: PropTypes.func, 9 | getCursorPos: PropTypes.func, 10 | } 11 | 12 | function Trigger(props){ 13 | const [styles, setStyles] = useState({}); 14 | const { setVisibility, getCursorPos } = props.children.props; 15 | const triggerContainerRef = useRef(null) 16 | 17 | useEffect(() => { 18 | let childStyles = window.getComputedStyle( 19 | ReactDom.findDOMNode(triggerContainerRef.current.children[0]), 20 | ) 21 | setStyles({ 22 | width: childStyles.getPropertyValue('width'), 23 | height: childStyles.getPropertyValue('height'), 24 | margin: childStyles.getPropertyValue('margin') 25 | }) 26 | }, []) 27 | 28 | return( 29 |
setVisibility(true)} 31 | onMouseOut={() => setVisibility(false)} 32 | onMouseMove={(e) => getCursorPos(e)} 33 | onTouchStart={() => setVisibility(true)} 34 | onTouchEnd={() => setVisibility(false)} 35 | ref={triggerContainerRef} 36 | style={styles} 37 | > 38 | {props.children.props.children} 39 |
40 | ) 41 | } 42 | 43 | Trigger.propTypes = propTypes; 44 | 45 | export default Trigger; -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../src/.eslintrc", 3 | 4 | "env": { 5 | "jasmine": true 6 | }, 7 | 8 | "rules": { 9 | "one-var": 0, 10 | "no-undefined": 0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import test from 'tape' 3 | import ReactHover, { Trigger, Hover } from '../src/ReactHover' 4 | import HoverComponent from '../src/example/HoverComponent' 5 | import TriggerComponent from '../src/example/TriggerComponent' 6 | import Enzyme, { shallow } from 'enzyme' 7 | import Adapter from 'enzyme-adapter-react-16' 8 | 9 | Enzyme.configure({ adapter: new Adapter() }) 10 | 11 | test('----- React Component Tests: ReactHover -----', t => { 12 | t.plan(3) 13 | t.ok(ReactHover instanceof Function, 'should be function') 14 | const optionsCursorTrueWithMargin = { 15 | followCursor: true, 16 | shiftX: 20, 17 | shiftY: 0, 18 | } 19 | const wrapperShallow = shallow( 20 | 21 | 22 | 23 | 24 | 25 | 26 | {' '} 27 | , 28 | ) 29 | t.equal(1, wrapperShallow.find('TriggerComponent').length) 30 | t.equal(1, wrapperShallow.find('HoverComponent').length) 31 | t.end() 32 | }) 33 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var HtmlWebpackPlugin = require('html-webpack-plugin') 3 | var path = require('path') 4 | var env = process.env.NODE_ENV || 'development' 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 6 | 7 | module.exports = { 8 | devtool: 'source-map', 9 | entry: [ 10 | './src/example/Example.js', 11 | 'webpack-dev-server/client?http://localhost:8080', 12 | 'webpack/hot/only-dev-server', 13 | ], 14 | output: { filename: 'bundle.js', path: path.resolve('example') }, 15 | plugins: [ 16 | new HtmlWebpackPlugin({ 17 | title: 'React-Hover', 18 | }), 19 | new webpack.DefinePlugin({ 20 | 'process.env': { 21 | NODE_ENV: '"' + env + '"', 22 | }, 23 | }), 24 | new webpack.HotModuleReplacementPlugin(), 25 | new MiniCssExtractPlugin({ 26 | // Options similar to the same options in webpackOptions.output 27 | // both options are optional 28 | filename: '[name].css', 29 | chunkFilename: '[id].css', 30 | }), 31 | ], 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.js$/, 36 | loaders: ['babel-loader'], 37 | include: [path.resolve('src')], 38 | }, 39 | { 40 | test: /\.css$/, 41 | use: [ 42 | { 43 | loader: MiniCssExtractPlugin.loader, 44 | options: { 45 | // you can specify a publicPath here 46 | // by default it use publicPath in webpackOptions.output 47 | publicPath: '../', 48 | }, 49 | }, 50 | 'css-loader', 51 | ], 52 | }, 53 | { 54 | enforce: 'pre', 55 | test: /\.js$/, 56 | loaders: ['eslint-loader'], 57 | include: [path.resolve('src')], 58 | }, 59 | ], 60 | }, 61 | resolve: { extensions: ['.js'] }, 62 | stats: { colors: true }, 63 | devServer: { 64 | hot: true, 65 | historyApiFallback: true, 66 | stats: { 67 | chunkModules: false, 68 | colors: true, 69 | }, 70 | }, 71 | } 72 | --------------------------------------------------------------------------------