├── .npmignore
├── .gitignore
├── .babelrc
├── src
├── viewer.js
├── index.html
├── Viewer.jsx
└── Popups.jsx
├── LICENSE
├── webpack.config.js
├── package.json
├── README.md
└── .eslintrc
/.npmignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | dist
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "react",
5 | ],
6 | "plugins": [
7 | "transform-class-properties",
8 | ]
9 | }
--------------------------------------------------------------------------------
/src/viewer.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { render } from "react-dom"
3 | import Viewer from "./Viewer.jsx"
4 |
5 | render (, document.getElementById ("app"))
6 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Reslav Hollos
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
13 | all 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
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const packageJson = require(path.resolve(__dirname, "package.json"))
3 | const HtmlWebPackPlugin = require("html-webpack-plugin")
4 |
5 | const mainFile = "Popups.jsx"
6 |
7 | module.exports = {
8 | entry: {
9 | main: path.resolve(__dirname, "src", mainFile),
10 | },
11 |
12 | output: {
13 | filename: "[name].js",
14 | library: mainFile.substring (0, mainFile.indexOf(".")),
15 | libraryTarget: "umd",
16 | },
17 |
18 | externals: process.env.NODE_ENV == "development" ? [] :
19 | Object.keys(packageJson.peerDependencies),
20 |
21 | devtool: "source-map",
22 |
23 | module: {
24 | rules: [
25 | {
26 | test: /\.jsx?$/,
27 | exclude: [/node_modules/],
28 | use: {
29 | loader: "babel-loader",
30 | },
31 | },
32 | {
33 | test: /\.html$/,
34 | use: [
35 | {
36 | loader: "html-loader",
37 | options: { minimize: true },
38 | },
39 | ],
40 | },
41 | {
42 | enforce: "pre",
43 | test: /\.jsx?$/,
44 | exclude: /node_modules/,
45 | use: [
46 | {
47 | loader: "eslint-loader",
48 | options: { fix: true }
49 | },
50 | ],
51 | },
52 | ],
53 | },
54 |
55 | plugins: [
56 | new HtmlWebPackPlugin({
57 | template: path.resolve (__dirname, "src", "index.html"),
58 | })
59 | ],
60 | }
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-popups",
3 | "version": "1.3.1",
4 | "description": "Reactjs component for spawning custom popups at mouse position.",
5 | "author": "Reslav Hollos (http://radivarig.github.io)",
6 | "license": "MIT",
7 | "main": "./dist/main.js",
8 | "scripts": {
9 | "prepublish": "npm run build",
10 | "dev": "NODE_ENV=development webpack-dev-server --mode development ./src/viewer.js",
11 | "build": "webpack --mode production"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git@github.com:Radivarig/react-popups.git"
16 | },
17 | "keywords": [
18 | "react",
19 | "reactjs",
20 | "react-component",
21 | "popup",
22 | "popups",
23 | "spawn",
24 | "mouse"
25 | ],
26 | "devDependencies": {
27 | "babel-core": "^6.26.0",
28 | "babel-eslint": "^8.2.2",
29 | "babel-loader": "^7.1.4",
30 | "babel-plugin-transform-class-properties": "^6.24.1",
31 | "babel-preset-env": "^1.6.1",
32 | "babel-preset-react": "^6.24.1",
33 | "css-loader": "^0.28.10",
34 | "eslint": "^4.18.2",
35 | "eslint-loader": "^2.0.0",
36 | "eslint-plugin-jsx-control-statements": "^2.2.0",
37 | "eslint-plugin-react": "^7.7.0",
38 | "html-loader": "^0.5.5",
39 | "html-webpack-plugin": "^3.0.6",
40 | "style-loader": "^0.20.2",
41 | "webpack": "^4.1.0",
42 | "webpack-cli": "^2.0.10",
43 | "webpack-dev-server": "^3.1.0"
44 | },
45 | "peerDependencies": {
46 | "prop-types": "^15.5.0",
47 | "react-dom": "^15.5.0",
48 | "react": "^15.5.0"
49 | },
50 | "dependencies": {
51 | "react-onclickoutside": "^6.7.1"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-popups
2 |
3 | Try it - [Live Example](https://radivarig.github.io/#/react-popups)
4 |
5 | 
6 |
7 | ### Install
8 |
9 | `npm install --save react-popups`
10 |
11 | ### Demo
12 |
13 | Check out [Live Example](https://radivarig.github.io/#/react-popups) and the [example code](https://github.com/Radivarig/react-popups/blob/master/src/PopupsViewer.jsx), or run locally
14 | ```bash
15 | git clone git@github.com:Radivarig/react-popups.git
16 | npm install
17 | npm run build
18 | npm run start
19 | ```
20 |
21 | ### Features
22 |
23 | - create custom popup components on custom event
24 | - pass data to them
25 | - detect screen quadrant (safe to click near edges)
26 | - close all front popups on click
27 |
28 | ### Basic Usage
29 |
30 | ```javascript
31 | // ...
32 | var Popups = require('react-popups')
33 |
34 | var PopupHandler = React.createClass({
35 | render: function() {
36 | console.log('received: ', this.props.data) // received: clicked element identifier
37 | var Popup =
38 | switch(this.props.data) {
39 | case 'clicked element identifier': Popup = ; break
40 | // ...
41 | // var something = this.props.popupProps.something
42 | }
43 | }
44 | return ({Popup})
45 | })
46 |
47 | var App = React.createClass({
48 | render: function() {
49 | var linkIfNoMatch = '/your-url' // for no action use 'javaScript:void(0)'
50 | return (
51 |
52 |
56 | //event='someOtherEvent'
57 | //popupProps={something: ..} // will be passed to PopupHandler
58 |
59 | Some
demo text.
60 |
61 | )
62 | }
63 | })
64 |
65 | require('react-dom').render(, document.body)
66 | ```
67 |
68 | ### License
69 |
70 | MIT
71 |
--------------------------------------------------------------------------------
/src/Viewer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Popups from "./Popups.jsx"
3 |
4 | class Popup extends React.Component {
5 | render = () => {
6 | const popup_style = {
7 | "height": "auto",
8 | "width": "auto",
9 | "backgroundColor": "#ABC",
10 | "borderStyle": "solid",
11 | "borderColor": "#567",
12 | }
13 | let ToDisplay =
14 | if (this.props.data === "universe") {ToDisplay = }
15 |
16 | return (
17 |
18 | {ToDisplay}
19 |
20 | )
21 | }
22 | }
23 |
24 | const PopupLink = (props) => {
25 | const link_style = {
26 | "cursor": "pointer",
27 | "color": "#00F",
28 | }
29 | return (
30 |
31 | {props.children}
32 |
33 | )
34 | }
35 |
36 | const DefaultPopup = (props) => (
37 |
38 |
info: {props.info}
39 |
other:
40 |
41 | - Universe
42 | - planets
43 | - stars
44 | - galaxies
45 | - intergalactic space
46 | - dark matter
47 | - dark energy
48 |
49 |
50 | )
51 |
52 | const Universe = () => (
53 |
54 | The Universe is all of time and space and its contents.
55 | The Universe includes planets
56 | , stars
57 | , galaxies
58 | , the contents of intergalactic space
59 | , the smallest subatomic particles, and all matter and energy.
60 | The majority of matter and energy is most likely in the form
61 | of dark matter and dark energy.
62 |
63 | )
64 |
65 | export default () =>
66 |
70 |
--------------------------------------------------------------------------------
/src/Popups.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import onClickOutside from "react-onclickoutside"
3 |
4 | class Popups extends React.Component {
5 | state = { "popups": [] }
6 |
7 | componentDidMount = () => {
8 | if (this.props.clickButtons) document.addEventListener ("click", this.spawnLinkedDiv)
9 | if (this.props.event) document.addEventListener (this.props.event, this.spawnLinkedDiv)
10 | }
11 |
12 | componentWillUnmount = () => {
13 | if (this.props.clickButtons) document.removeEventListener ("click", this.spawnLinkedDiv)
14 | if (this.props.event) document.removeEventListener (this.props.event, this.spawnLinkedDiv)
15 | }
16 |
17 | handleClickOutside = () => {
18 | this.setState ({ "popups": [] })
19 | }
20 |
21 | handleClickInside = (e) => {
22 | let t = e.target
23 | while (t) {
24 | if (t.dataset && t.dataset.popupkey) {
25 | const popupkey = t.dataset.popupkey
26 | const popups = this.state.popups
27 | let ind = -1
28 | popups.forEach ((x, i) => {
29 | //TODO get popupkey instead of key
30 | if (x.key === popupkey)
31 | ind = i
32 | })
33 | this.setState ({ "popups": popups.slice (0, ind + 1) })
34 | return
35 | }
36 | t = t.parentNode
37 | }
38 | }
39 |
40 | spawnLinkedDiv = (e) => {
41 | this.handleClickInside (e)
42 |
43 | const data = e.target.attributes[this.props.dataName || "data"]
44 | if (!data) return
45 |
46 | if(this.props.clickButtons) {
47 | if (this.props.clickButtons.indexOf (e.button) > -1)
48 | e.preventDefault ()
49 | else return
50 | }
51 | else e.preventDefault ()
52 |
53 | const popups = this.state.popups
54 |
55 | const half_w = 0.5 * window.innerWidth
56 | const half_h = 0.5 * window.innerHeight
57 | const x = e.pageX
58 | const y = e.pageY
59 |
60 | let translateXY = "(-100%, 0%)"
61 | if (x < half_w && y < half_h) translateXY = "(0%, 0%)"
62 | else if (x < half_w && y > half_h) translateXY = "(0%, -100%)"
63 | else if (x > half_w && y > half_h) translateXY = "(-100%, -100%)"
64 |
65 | const s = {
66 | "position": "fixed",
67 | "left": e.pageX,
68 | "top": e.pageY,
69 | "transform": `translate${translateXY}`,
70 | }
71 | const id = Math.random ()
72 | popups.push (
73 |
74 |
78 |
79 | )
80 | this.setState ({ popups })
81 | }
82 |
83 | render = () => ({this.state.popups}
)
84 | }
85 |
86 | export default onClickOutside (Popups)
87 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "sourceType": "module",
5 | "ecmaFeatures": {
6 | "jsx": true
7 | }
8 | },
9 | "env": {
10 | "es6": true,
11 | "browser": true,
12 | "commonjs": true,
13 | },
14 | "plugins": [
15 | "react",
16 | "jsx-control-statements",
17 | ],
18 | "extends": [
19 | "eslint:recommended",
20 | "plugin:react/recommended",
21 | "plugin:jsx-control-statements/recommended",
22 | ],
23 | "rules": {
24 | "object-curly-spacing": [2, "always"],
25 | "no-console": 1,
26 | "no-unused-vars": 1,
27 | "quotes": 2,
28 | "quote-props": 2,
29 | "func-call-spacing": [2, "always"],
30 | "strict": [2, "global"],
31 | "semi": [2, "never"],
32 | "no-tabs": 2,
33 | "no-template-curly-in-string": 2,
34 | "no-unsafe-negation": 2,
35 | "accessor-pairs": [2, {"getWithoutSet": true}],
36 | "array-callback-return": 2,
37 | "block-scoped-var": 2,
38 | "class-methods-use-this": [2, { "exceptMethods": [] }],
39 | "eqeqeq": 2,
40 | "no-prototype-builtins": 2,
41 | "no-else-return": 2,
42 | "no-global-assign": [2, {"exceptions": []}],
43 | "no-implicit-globals": 2,
44 | "no-loop-func": 2,
45 | "no-fallthrough": 2,
46 | "no-floating-decimal": 2,
47 | "no-return-assign": 2,
48 | "no-sequences": 2,
49 | "no-unmodified-loop-condition": 2,
50 | "no-unused-expressions": [2, { "allowShortCircuit": true, "allowTernary": true }],
51 | "no-useless-call": 2,
52 | "no-useless-concat": 2,
53 | "no-useless-escape": 2,
54 | "no-useless-return": 2,
55 | "no-void": 2,
56 | "no-with": 2,
57 | "yoda": [2, "never", { "exceptRange": true }],
58 | "no-shadow-restricted-names": 2,
59 | "no-shadow": [2, { "builtinGlobals": true, "hoist": "all", "allow": [] }],
60 | "no-use-before-define": ["error", { "functions": true, "classes": true }],
61 | "comma-dangle": [2, "always-multiline"],
62 | "comma-style": [2, "last", { "exceptions": {} }],
63 | "eol-last": 2,
64 | "indent": [2, 2],
65 | "key-spacing": 2,
66 | "new-cap": 2,
67 | "new-parens": 2,
68 | "no-array-constructor": 2,
69 | "no-lonely-if": 2,
70 | "no-mixed-operators": 2,
71 | "no-multiple-empty-lines": [2, {max: 1}],
72 | "no-nested-ternary": 2,
73 | "no-unneeded-ternary": 2,
74 | "no-trailing-spaces": 2,
75 | "no-whitespace-before-property": 2,
76 | "operator-assignment": 2,
77 | "space-before-function-paren": 2,
78 | "space-before-blocks": 2,
79 | "space-in-parens": 2,
80 | "space-infix-ops": 2,
81 | "wrap-regex": 2,
82 | "arrow-body-style": 2,
83 | "arrow-parens": 2,
84 | "arrow-spacing": 2,
85 | "no-confusing-arrow": 2,
86 | "no-duplicate-imports": 2,
87 | "no-useless-computed-key": 2,
88 | "no-useless-rename": 2,
89 | "no-var": 2,
90 | "object-shorthand": 2,
91 | "prefer-arrow-callback": 2,
92 | "prefer-const": 2,
93 | "prefer-rest-params": 2,
94 | "prefer-spread": 2,
95 | "prefer-template": 2,
96 | "rest-spread-spacing": 2,
97 | "symbol-description": 2,
98 | "template-curly-spacing": 2,
99 |
100 | "react/display-name": 0,
101 | "react/forbid-component-props": [2, { "forbid": [
102 | "className",
103 | "style",
104 | ] }],
105 | "react/no-children-prop": 2,
106 | "react/no-danger": 2,
107 | "react/no-danger-with-children": 2,
108 | "react/no-did-mount-set-state": [2, "disallow-in-func"],
109 | "react/no-did-update-set-state": 2,
110 | "react/no-direct-mutation-state": 2,
111 | "react/no-find-dom-node": 0,
112 | "react/no-is-mounted": 2,
113 | "react/no-render-return-value": 2,
114 | "react/no-string-refs": 2,
115 | "react/no-unescaped-entities": 2,
116 | "react/no-unknown-property": 2,
117 | "react/no-unused-prop-types": 2,
118 | "react/prop-types": 0,
119 | "react/react-in-jsx-scope": 2,
120 | "react/require-render-return": 2,
121 | "react/self-closing-comp": 2,
122 | "react/style-prop-object": 2,
123 |
124 | "react/jsx-closing-bracket-location": 2,
125 | "react/jsx-curly-spacing": 2,
126 | "react/jsx-equals-spacing": 2,
127 | "react/jsx-filename-extension": 0,
128 | "react/jsx-first-prop-new-line": [2, "multiline-multiprop"],
129 | "react/jsx-indent": [2, 2],
130 | "react/jsx-indent-props": [2, 2],
131 | "react/jsx-key": 2,
132 | "react/jsx-max-props-per-line": [2, {maximum: 3}],
133 | "react/jsx-no-comment-textnodes": 2,
134 | "react/jsx-no-duplicate-props": 2,
135 | "react/jsx-no-target-blank": 2,
136 | "react/jsx-pascal-case": 2,
137 | "react/jsx-tag-spacing": 2,
138 | "react/jsx-uses-react": 2,
139 | "react/jsx-uses-vars": 2,
140 |
141 | "react/jsx-no-undef": 0,
142 | "jsx-control-statements/jsx-jcs-no-undef": 2,
143 | },
144 | }
145 |
--------------------------------------------------------------------------------