├── .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 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 
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 | [](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 |
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 |

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 |

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 |

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 |
--------------------------------------------------------------------------------