├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── prism.js ├── src ├── Examples.js ├── Select.js ├── ToolTipController.js ├── Tooltips │ ├── ToolTip.js │ └── ToolTipAdvanced.js ├── index.js └── styles │ ├── Tooltip.styl │ ├── base.styl │ ├── examples.styl │ ├── main.css │ ├── main.styl │ └── prism.css └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dogacan Bilgili 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 | # React-Tooltip-Controller 2 | 3 | This is a feature-rich React component for controlling tooltips. Not only for tooltips, but you can use it for various interaction requirements. 4 | 5 | It seamlessly integrates into your markup without breaking it. 6 | 7 | Visit the [examples page](https://dbilgili.github.io/React-Tooltip-Controller/) to discover the functionalities. 8 | 9 | 10 | | Basic Tooltip | Animated Tooltip | Advanced Tooltip | 11 | |---|---|---| 12 | | ![screen7](https://user-images.githubusercontent.com/22943912/48679619-e6849000-eb92-11e8-99e7-35e147b5fcc1.gif) | ![screen8](https://user-images.githubusercontent.com/22943912/48679620-e6849000-eb92-11e8-8a8b-0499ff333046.gif) | ![screen6](https://user-images.githubusercontent.com/22943912/48679621-e6849000-eb92-11e8-8e9a-a8d709b96f82.gif) | 13 | 14 | 15 | #### Highlights 16 | - Supports `click`, `hover`, `hover-hold` and `hover-interact` detections. 17 | - Each tooltip can be animated individually. 18 | - Set whether the tooltip closes when clicked on it. 19 | - Close the tooltip manually by assigning a variable. 20 | - Retrieve the state of the tooltip (whether open or not). 21 | - Set a timeout to automatically close the tooltip. 22 | - Position the tooltip relative to the triggering element. 23 | - Automatically center the tooltip along the X axis for dynamically sized elements. 24 | 25 | ## Installing 26 | 27 | `npm install react-tooltip-controller` 28 | 29 | After installing the module, import the following components: 30 | 31 | ```javascript 32 | import {ToolTipController, Select} from 'react-tooltip-controller' 33 | ``` 34 | 35 | ## Basic Usage 36 | 37 | ```html 38 | 42 | 43 | // Selects the element controlling the tooltip 44 | 47 | 48 | // Custom tooltip component 49 | 50 | 51 | 52 | ``` 53 | 54 | Anything, but `` component, `` is attached to ` 191 | 192 | 193 | 194 | class Example extends Component { 195 | 196 | state = { 197 | trigger: false 198 | } 199 | 200 | close = () => { 201 | this.setState({trigger: true}) 202 | } 203 | 204 | render() { 205 | return ( 206 |
207 | 208 | 212 | 213 | 216 | 217 | 218 | 219 | 220 | 221 |
222 | 223 | ) 224 | } 225 | } 226 | 227 | export default Example 228 | ``` 229 | 230 | By using the `triggerClose` prop, the tooltip can be closed manually. To do so, variable passed to `triggerClose` prop should be set to `true`. 231 | 232 | This example demonstrates how to close the tooltip by setting the state of the triggering variable to `true`. To prevent the other click events on the tooltip from closing it, `closeOnClick` is set to `false`. Note that clicking outside of the tooltip closes it independent of the `triggerClose` prop. 233 | 234 | ### Use of `returnState` prop 235 | 236 | ```javascript 237 | import React, { Component } from 'react' 238 | import {ToolTipController, Select} from 'react-tooltip-controller' 239 | 240 | const ToolTip = (props) => 241 |
242 | Tooltip 243 |
244 | 245 | class Example extends Component { 246 | 247 | state = { 248 | tooltipState: false 249 | } 250 | 251 | getTooltipState = (data) => { 252 | this.setState({tooltipState: data}) 253 | } 254 | 255 | render() { 256 | return ( 257 |
258 | 259 | 263 | 264 | 267 | 268 | 269 | 270 | 271 | 272 |
273 | 274 | ) 275 | } 276 | } 277 | 278 | export default Example 279 | ``` 280 | 281 | You can pass a function as a prop through `returnState` in order to get the state of the tooltip, whether it's open or not. 282 | 283 | ### License 284 | 285 | MIT License 286 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React-Tooltip-Controller", 3 | "homepage": "https://dbilgili.github.io/React-Tooltip-Controller", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@material-ui/core": "^3.5.1", 8 | "immer": "^1.7.4", 9 | "react": "^16.6.3", 10 | "react-dom": "^16.6.3", 11 | "react-scripts": "2.1.1" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts-with-stylus start src/styles/main.styl", 15 | "build": "react-scripts-with-stylus build src/styles/main.styl", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject", 18 | "predeploy": "npm run build", 19 | "deploy": "gh-pages -d build" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ], 30 | "devDependencies": { 31 | "create-react-app-stylus": "^1.1.1", 32 | "gh-pages": "^2.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbilgili/React-Tooltip-Controller/0c8407b7480601acc46cd4b4829a4a341bd15d8c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | React-Tooltip-Controller | Examples Page 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/prism.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.15.0 2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+clike+javascript+jsx+stylus */ 3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof r?new r(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){if(m&&b!=t.length-1){h.lastIndex=k;var _=h.exec(e);if(!_)break;for(var j=_.index+(d?_[1].length:0),P=_.index+_[0].length,A=b,x=k,O=t.length;O>A&&(P>x||!t[A].type&&!t[A-1].greedy);++A)x+=t[A].length,j>=x&&(++b,k=x);if(t[b]instanceof s)continue;I=A-b,w=e.slice(k,x),_.index-=k}else{h.lastIndex=0;var _=h.exec(w),I=1}if(_){d&&(p=_[1]?_[1].length:0);var j=_.index+p,_=_[0].slice(p),P=j+_.length,N=w.slice(0,j),S=w.slice(P),C=[b,I];N&&(++b,k+=N.length,C.push(N));var E=new s(u,f?n.tokenize(_,f):_,y,_,m);if(C.push(E),S&&C.push(S),Array.prototype.splice.apply(t,C),1!=I&&n.matchGrammar(e,t,r,b,k,!0,u),i)break}else if(i)break}}}}},tokenize:function(e,t){var r=[e],a=t.rest;if(a){for(var l in a)t[l]=a[l];delete t.rest}return n.matchGrammar(e,r,t,0,0,!1),r},hooks:{all:{},add:function(e,t){var r=n.hooks.all;r[e]=r[e]||[],r[e].push(t)},run:function(e,t){var r=n.hooks.all[e];if(r&&r.length)for(var a,l=0;a=r[l++];)a(t)}}},r=n.Token=function(e,t,n,r,a){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length,this.greedy=!!a};if(r.stringify=function(e,t,a){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return r.stringify(n,t,e)}).join("");var l={type:e.type,content:r.stringify(e.content,t,a),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:a};if(e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(n.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),r=t.language,a=t.code,l=t.immediateClose;_self.postMessage(n.highlight(a,n.languages[r],r)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return a&&(n.filename=a.src,n.manual||a.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 4 | Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/(^|[^\\])["']/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(?:true|false)\b/,"function":/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},/\b(?:as|async|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/],number:/\b(?:(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+)n?|\d+n|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,"function":/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\(|\.(?:apply|bind|call)\()/,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[[^\]\r\n]+]|\\.|[^\/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:function\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,alias:"function"},constant:/\b[A-Z][A-Z\d_]*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\${[^}]+}/,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript",greedy:!0}}),Prism.languages.js=Prism.languages.javascript; 7 | !function(t){var n=t.util.clone(t.languages.javascript);t.languages.jsx=t.languages.extend("markup",n),t.languages.jsx.tag.pattern=/<\/?(?:[\w.:-]+\s*(?:\s+(?:[\w.:-]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s{'">=]+|\{(?:\{(?:\{[^}]*\}|[^{}])*\}|[^{}])+\}))?|\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}))*\s*\/?)?>/i,t.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/i,t.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">]+)/i,t.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*$/,t.languages.insertBefore("inside","attr-name",{spread:{pattern:/\{\.{3}[a-z_$][\w$]*(?:\.[a-z_$][\w$]*)*\}/,inside:{punctuation:/\.{3}|[{}.]/,"attr-value":/\w+/}}},t.languages.jsx.tag),t.languages.insertBefore("inside","attr-value",{script:{pattern:/=(\{(?:\{(?:\{[^}]*\}|[^}])*\}|[^}])+\})/i,inside:{"script-punctuation":{pattern:/^=(?={)/,alias:"punctuation"},rest:t.languages.jsx},alias:"language-javascript"}},t.languages.jsx.tag);var e=function(t){return t?"string"==typeof t?t:"string"==typeof t.content?t.content:t.content.map(e).join(""):""},a=function(n){for(var s=[],g=0;g0&&s[s.length-1].tagName===e(i.content[0].content[1])&&s.pop():"/>"===i.content[i.content.length-1].content||s.push({tagName:e(i.content[0].content[1]),openedBraces:0}):s.length>0&&"punctuation"===i.type&&"{"===i.content?s[s.length-1].openedBraces++:s.length>0&&s[s.length-1].openedBraces>0&&"punctuation"===i.type&&"}"===i.content?s[s.length-1].openedBraces--:o=!0),(o||"string"==typeof i)&&s.length>0&&0===s[s.length-1].openedBraces){var p=e(i);g0&&("string"==typeof n[g-1]||"plain-text"===n[g-1].type)&&(p=e(n[g-1])+p,n.splice(g-1,1),g--),n[g]=new t.Token("plain-text",p,null,p)}i.content&&"string"!=typeof i.content&&a(i.content)}};t.hooks.add("after-tokenize",function(t){("jsx"===t.language||"tsx"===t.language)&&a(t.tokens)})}(Prism); 8 | !function(n){var t={url:/url\((["']?).*?\1\)/i,string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:if|else|for|return|unless)(?=\s+|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,number:/\b\d+(?:\.\d+)?%?/,"boolean":/\b(?:true|false)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.+|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],punctuation:/[{}()\[\];:,]/};t.interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^{|}$/,alias:"punctuation"},rest:t}},t.func={pattern:/[\w-]+\([^)]*\).*/,inside:{"function":/^[^(]+/,rest:t}},n.languages.stylus={comment:{pattern:/(^|[^\\])(\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},"atrule-declaration":{pattern:/(^\s*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:t}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:(?:\{[^}]*\}|.+)|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:t}},statement:{pattern:/(^[ \t]*)(?:if|else|for|return|unless)[ \t]+.+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:t}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)[^{\r\n]*(?:;|[^{\r\n,](?=$)(?!(\r?\n|\r)(?:\{|\2[ \t]+)))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:t.interpolation}},rest:t}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\))?|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\))?|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t]+)))/m,lookbehind:!0,inside:{interpolation:t.interpolation,punctuation:/[{},]/}},func:t.func,string:t.string,interpolation:t.interpolation,punctuation:/[{}()\[\];:.]/}}(Prism); 9 | -------------------------------------------------------------------------------- /src/Examples.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ToolTipController from './ToolTipController' 3 | import Select from './Select' 4 | import ToolTip from './Tooltips/ToolTip' 5 | import ToolTipAdvanced from './Tooltips/ToolTipAdvanced' 6 | import Button from '@material-ui/core/Button' 7 | import './styles/prism.css' 8 | 9 | class Examples extends React.Component{ 10 | 11 | state={ 12 | tooltipState: false, 13 | tooltipState2: false, 14 | trigger: false 15 | } 16 | 17 | getTooltipState = (data) => { 18 | this.setState({tooltipState: data}) 19 | } 20 | 21 | getOtherTooltipState = (data) => { 22 | this.setState({tooltipState2: data}) 23 | } 24 | 25 | close = () => { 26 | this.setState({trigger: true}) 27 | } 28 | 29 | componentDidUpdate(prevProp, prevState){ 30 | if(prevState.tooltipState2 !== this.state.tooltipState2){ 31 | this.setState({trigger: false}) 32 | } 33 | 34 | } 35 | 36 | render(){ 37 | return( 38 |
39 | 40 |

React-Tooltip-Controller | Examples Page

41 |
42 | GitHub 43 | NPM 44 |
45 | 46 |
47 | 48 |
{`
 49 |             
 53 |               
 56 |               
 57 |             
 58 |           `}
59 | 60 | 64 | 67 | 68 | 69 |
70 | 71 |
72 | 73 |
74 | 75 |
{`
 76 |             
 80 |               
 83 |               
 84 |             
 85 |           `}
86 | 87 | 91 | 94 | 95 | 96 |
97 | 98 |
99 | 100 |
101 | 102 |
{`
103 |             
107 |               
110 |               
111 |             
112 |           `}
113 | 114 |
115 |
116 |

Button won't close when the cursor is moved out

117 |
118 | 119 | 123 | 126 | 127 | 128 |
129 |
130 | 131 |
132 | 133 |
134 | 135 |
{`
136 |             
142 |               
145 |               
146 |             
147 |           `}
148 | 149 |
150 |

151 | The cursor can be moved over the tooltip before it closes 152 |
This option should be used with timeOut prop 153 |

154 | 160 | 163 | 164 | 165 |
166 |
167 | 168 |
169 | 170 |
171 | 172 |
{`
173 |             
182 |               
185 |               
186 |             
187 |           `}
188 | 189 |
{`
190 |             .react-tooltip-5
191 |               opacity: 0
192 |               transform: translateY(10px)
193 |               &.fadeIn
194 |                 opacity: 1
195 |                 transform: translateY(0)
196 |           `}
197 | 198 |
199 |

Tooltip has an animation class

200 | 209 | 212 | 213 | 214 |
215 |
216 | 217 |
218 | 219 |
220 | 221 |
{`
222 |             
227 |               
230 |               
231 |             
232 |           `}
233 | 234 |
235 |

Tooltip will disapear after 1000ms

236 | 241 | 244 | 245 | 246 |
247 |
248 | 249 |
250 | 251 |
252 | 253 |
{`
254 |             
259 |               
262 |               
263 |             
264 |           `}
265 | 266 |
267 |

Tooltip is interactable (Does not close when clicked on)

268 | 273 | 276 | 277 | 278 |
279 | 280 | 281 |
282 | 283 |
284 | 285 |
286 | 287 |
{`
288 |             
294 |               
297 |               
298 |             
299 |           `}
300 | 301 |
{`
302 |             state={
303 |               tooltipState: false
304 |             }
305 | 
306 |             getTooltipState = (data) => {
307 |               this.setState({tooltipState: data})
308 |             }
309 |           `}
310 | 311 |
312 |

Tooltip State: {this.state.tooltipState.toString()}

313 | 319 | 322 | 323 | 324 |
325 | 326 |
327 | 328 |
329 | 330 |
331 | 332 |
{`
333 |             
340 |               
343 |               
344 |             
345 |           `}
346 | 347 |
{`
348 |             state={
349 |               trigger: false
350 |             }
351 | 
352 |             close = () => {
353 |               this.setState({trigger: true})
354 |             }
355 |           `}
356 | 357 |
358 |

Button in the tooltip triggers the close action

359 | 366 | 369 | 370 | 371 |
372 | 373 |
374 | 375 |
376 | ) 377 | } 378 | } 379 | 380 | export default Examples 381 | -------------------------------------------------------------------------------- /src/Select.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | class Select extends React.Component{ 5 | static displayName = 'Select' 6 | 7 | constructor(props) { 8 | super(props) 9 | this.selector = React.createRef() 10 | } 11 | 12 | componentDidMount(){ 13 | this.resize() 14 | window.addEventListener('resize', this.resize) 15 | } 16 | 17 | componentWillUnmount(){ 18 | window.removeEventListener('resize', this.resize) 19 | } 20 | 21 | componentDidUpdate(){ 22 | this.resize() 23 | } 24 | 25 | resize = () => { 26 | this.props.resize(this.getElementBounding(), this.refs.selector) 27 | } 28 | 29 | getElementBounding = () => { 30 | const rect = ReactDOM.findDOMNode(this.selector.current).getBoundingClientRect() 31 | return {left: rect.left, top: rect.top, height: rect.height, width: rect.width} 32 | } 33 | 34 | render(){ 35 | const { detect, openMenu, closeMenu, timeOutFunc, children } = this.props 36 | if(detect === "click"){ 37 | return React.cloneElement(children, {ref: this.selector, onClick: openMenu, onTouchEnd: openMenu}) 38 | } 39 | else if(detect === 'hover'){ 40 | return React.cloneElement(children, {ref: this.selector, onMouseEnter: openMenu, onMouseLeave: closeMenu, onTouchEnd: openMenu}) 41 | } 42 | else if(detect === 'hover-hold'){ 43 | return React.cloneElement(children, {ref: this.selector, onMouseEnter: openMenu, onTouchEnd: openMenu}) 44 | } 45 | else if(detect === 'hover-interact'){ 46 | return React.cloneElement(children, {ref: this.selector, onMouseEnter: openMenu, onMouseLeave: timeOutFunc, onTouchEnd: openMenu}) 47 | } 48 | return null 49 | } 50 | } 51 | 52 | export default Select 53 | -------------------------------------------------------------------------------- /src/ToolTipController.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import produce from 'immer' 3 | import ReactDOM from 'react-dom' 4 | 5 | class ToolTipController extends React.PureComponent{ 6 | constructor(props) { 7 | super(props) 8 | this.tooltip = React.createRef() 9 | } 10 | 11 | static defaultProps = { 12 | offsetX: 0, 13 | offsetY: 0, 14 | detect: "click", 15 | closeOnClick: true, 16 | timeOut: null, 17 | animation: "", 18 | duration: "", 19 | timing: "", 20 | properties: [], 21 | returnState: null, 22 | id: "" 23 | 24 | } 25 | state = { 26 | divStyle: { 27 | position: "absolute", 28 | top: 0, 29 | left: 0, 30 | transitionDuration: this.props.duration, 31 | transitionTimingFunction: this.props.timing, 32 | transitionProperty: this.props.properties 33 | }, 34 | isOpen: false, 35 | animate: false, 36 | timeOutID: null, 37 | tooltipWidth: 0, 38 | trigger: this.props.triggerClose 39 | } 40 | 41 | openMenu = (e) => { 42 | e.preventDefault() 43 | if(!this.state.animate){ 44 | 45 | //Set timeout for menu to close automatically 46 | if(this.props.timeOut !== null && this.props.detect !== "hover-interact"){ 47 | const timeOutID = setTimeout(() => this.closeMenu(), this.props.timeOut) 48 | this.setState({timeOutID}) 49 | } 50 | 51 | if(this.props.returnState !== null){ 52 | // Return menu status 53 | this.setState({isOpen: true},() => this.props.returnState(this.state.isOpen)) 54 | } 55 | else{ 56 | // Just open the menu 57 | this.setState({isOpen: true}) 58 | 59 | } 60 | 61 | //Turn on the animation > adds the specific animation class 62 | if(this.props.animation !== ""){ 63 | setTimeout(() => { 64 | this.setState({animate: true}) 65 | //Add the pointer events from all the active DIVs 66 | this.tooltip.current.style.pointerEvents = "auto" 67 | },0) 68 | } 69 | } 70 | } 71 | 72 | closeMenu = () => { 73 | // Clear time out 74 | if(this.props.timeOut !== null && this.props.detect !== "hover-interact"){ 75 | clearTimeout(this.state.timeOutID) 76 | } 77 | //Turn off the animation > removes the specific animation class 78 | if(this.state.isOpen){ 79 | if(this.props.animation !== ""){ 80 | // Return menu status 81 | if(this.props.returnState !== null){ 82 | this.setState({animate: false}, () => this.props.returnState(this.state.animate)) 83 | } 84 | else{ 85 | this.setState({animate: false}) 86 | } 87 | //Remove pointer events from all the active DIVs 88 | this.tooltip.current.style.pointerEvents = "none" 89 | } 90 | else{ 91 | if(this.props.returnState !== null){ 92 | this.setState({isOpen: false}, () => this.props.returnState(this.state.isOpen)) 93 | } 94 | else{ 95 | this.setState({isOpen: false}) 96 | } 97 | } 98 | } 99 | } 100 | 101 | timeOutFunc = () => { 102 | const timeOutID = setTimeout(() => this.closeMenu(), this.props.timeOut) 103 | this.setState({timeOutID}) 104 | } 105 | 106 | clearTimeoutFunc = () => { 107 | clearTimeout(this.state.timeOutID) 108 | } 109 | 110 | resize = (getElementBounding, selector) => { 111 | const {offsetX, offsetY} = this.props 112 | 113 | if(offsetX === "center" || offsetX === "centre"){ 114 | this.setState(produce(draft => { 115 | draft.divStyle.left = getElementBounding.left + (getElementBounding.width - this.state.tooltipWidth)/2 116 | draft.divStyle.top = getElementBounding.top + getElementBounding.height + offsetY + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0) 117 | })) 118 | } 119 | else{ 120 | this.setState(produce(draft => { 121 | draft.divStyle.left = getElementBounding.left + offsetX 122 | draft.divStyle.top = getElementBounding.top + getElementBounding.height + offsetY + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0) 123 | })) 124 | } 125 | } 126 | 127 | componentDidUpdate(prevProp, prevState){ 128 | if(this.state.isOpen){ 129 | this.setState({tooltipWidth: this.tooltip.current.getBoundingClientRect().width}) 130 | } 131 | 132 | if(prevProp.triggerClose !== this.props.triggerClose){ 133 | this.closeMenu() 134 | } 135 | 136 | setTimeout(() => { 137 | if(this.props.animation !== ""){ 138 | if(this.state.animate){ 139 | window.addEventListener('click', this.closeMenu) 140 | window.addEventListener('touchend', this.closeMenu) 141 | } 142 | else{ 143 | window.removeEventListener('click', this.closeMenu) 144 | window.removeEventListener('touchend', this.closeMenu) 145 | } 146 | } 147 | else{ 148 | if(this.state.isOpen){ 149 | window.addEventListener('click', this.closeMenu) 150 | window.addEventListener('touchend', this.closeMenu) 151 | } 152 | else{ 153 | window.removeEventListener('click', this.closeMenu) 154 | window.removeEventListener('touchend', this.closeMenu) 155 | } 156 | } 157 | },0) 158 | } 159 | 160 | componentWillUnmount() { 161 | window.removeEventListener('click', this.closeMenu) 162 | window.removeEventListener('touchstart', this.closeMenu) 163 | 164 | } 165 | 166 | static getDerivedStateFromProps(nextProps, prevState){ 167 | if(nextProps.triggerClose !== prevState.trigger){ 168 | return { trigger: nextProps.triggerClose } 169 | } 170 | else{ 171 | return false 172 | } 173 | } 174 | 175 | render(){ 176 | const{id, children, animation, closeOnClick, detect} = this.props 177 | const{isOpen, animate, divStyle} = this.state 178 | 179 | const inputChildren = React.Children.map(children, (child, index) => { 180 | if(child.type.displayName === "Select"){ 181 | return React.cloneElement(child, {detect, openMenu: this.openMenu, closeMenu: this.closeMenu, timeOutFunc: this.timeOutFunc, resize: this.resize}) 182 | } 183 | else{ 184 | return( 185 | isOpen && ReactDOM.createPortal( 186 | e.stopPropagation()} 191 | onTouchEnd={e => e.stopPropagation()} 192 | onMouseEnter={detect === "hover-interact" ? this.clearTimeoutFunc : undefined} 193 | onMouseLeave={detect === "hover-interact" ? this.closeMenu : undefined}> 194 | {React.cloneElement(child)} 195 | 196 | , document.body) 197 | ) 198 | } 199 | }) 200 | return inputChildren 201 | } 202 | } 203 | 204 | export default ToolTipController; 205 | -------------------------------------------------------------------------------- /src/Tooltips/ToolTip.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/main.css' 3 | 4 | const ToolTip = (props) => 5 |
6 | Tooltip 7 |
8 | 9 | 10 | export default ToolTip 11 | -------------------------------------------------------------------------------- /src/Tooltips/ToolTipAdvanced.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FormControl from '@material-ui/core/FormControl' 3 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 4 | import FormLabel from '@material-ui/core/FormLabel' 5 | import RadioGroup from '@material-ui/core/RadioGroup' 6 | import Radio from '@material-ui/core/Radio' 7 | import Button from '@material-ui/core/Button' 8 | import '../styles/main.css' 9 | 10 | class ToolTipAdvanced extends React.Component{ 11 | 12 | state = { 13 | radio: "Radio 1", 14 | } 15 | 16 | handleRadio = event => { 17 | this.setState({ radio: event.target.value }); 18 | } 19 | 20 | render(){ 21 | return( 22 |
23 | 24 | Radio Buttons 25 | 28 | } label="Radio 1" /> 29 | } label="Radio 2" /> 30 | } label="Radio 3" /> 31 | 32 | 33 | 34 | 35 |
36 | ) 37 | } 38 | } 39 | 40 | export default ToolTipAdvanced 41 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Examples from './Examples' 4 | 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /src/styles/Tooltip.styl: -------------------------------------------------------------------------------- 1 | .ToolTip 2 | display: flex 3 | justify-content: center 4 | align-items: center 5 | color: rgb(255, 107, 0) 6 | width: 130px 7 | height: 50px 8 | background-color: white 9 | position: relative 10 | box-shadow: 0px 3px 4px 0px rgba(235,235,235,1) 11 | border-radius: 3px 12 | 13 | &::before 14 | content:'' 15 | position: absolute 16 | left: 58px 17 | top: -10px 18 | height: 20px 19 | width: 20px 20 | background-color: white 21 | transform: rotate(45deg) 22 | 23 | &.advanced 24 | display: block 25 | height: initial 26 | padding: 20px 27 | 28 | &::before 29 | left: 75px 30 | -------------------------------------------------------------------------------- /src/styles/base.styl: -------------------------------------------------------------------------------- 1 | body 2 | background-color: #F5F6FA 3 | margin: 0 4 | padding: 0 5 | font-family: 'Roboto', sans-serif 6 | -------------------------------------------------------------------------------- /src/styles/examples.styl: -------------------------------------------------------------------------------- 1 | .Examples 2 | margin-bottom: 200px 3 | 4 | h1 5 | text-align: center 6 | margin-top: 50px 7 | 8 | .links 9 | display: flex 10 | justify-content: center 11 | margin-bottom: 50px 12 | 13 | a 14 | color: rgb(154, 154, 154) 15 | &:hover 16 | color: rgb(115, 115, 115) 17 | &:first-of-type 18 | margin-right: 20px 19 | 20 | .example 21 | display: flex 22 | margin-left: 20px 23 | align-items: center 24 | 25 | pre 26 | &:nth-child(1) 27 | margin-right: 100px 28 | 29 | pre > code 30 | white-space: pre 31 | margin-left: -7.00em 32 | display: block 33 | font-size: 0.9rem 34 | 35 | 36 | .md 37 | border 1px grey solid 38 | border-radius: 4px 39 | padding-left: 4px 40 | padding-right: 4px 41 | background-color: rgb(235, 235, 235) 42 | 43 | .margin-right 44 | margin-right: 20px !important 45 | 46 | .react-tooltip-5 47 | opacity: 0 48 | transform: translateY(10px) 49 | &.fadeIn 50 | opacity: 1 51 | transform: translateY(0) 52 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f5f6fa; 3 | margin: 0; 4 | padding: 0; 5 | font-family: 'Roboto', sans-serif; 6 | } 7 | .ToolTip { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | color: #ff6b00; 12 | width: 130px; 13 | height: 50px; 14 | background-color: #fff; 15 | position: relative; 16 | box-shadow: 0px 3px 4px 0px #ebebeb; 17 | border-radius: 3px; 18 | } 19 | .ToolTip::before { 20 | content: ''; 21 | position: absolute; 22 | left: 58px; 23 | top: -10px; 24 | height: 20px; 25 | width: 20px; 26 | background-color: #fff; 27 | transform: rotate(45deg); 28 | } 29 | .ToolTip.advanced { 30 | display: block; 31 | height: initial; 32 | padding: 20px; 33 | } 34 | .ToolTip.advanced::before { 35 | left: 75px; 36 | } 37 | .Examples { 38 | margin-bottom: 200px; 39 | } 40 | .Examples h1 { 41 | text-align: center; 42 | margin-top: 50px; 43 | } 44 | .Examples .links { 45 | display: flex; 46 | justify-content: center; 47 | margin-bottom: 50px; 48 | } 49 | .Examples .links a { 50 | color: #9a9a9a; 51 | } 52 | .Examples .links a:hover { 53 | color: #737373; 54 | } 55 | .Examples .links a:first-of-type { 56 | margin-right: 20px; 57 | } 58 | .Examples .example { 59 | display: flex; 60 | margin-left: 20px; 61 | align-items: center; 62 | } 63 | .Examples .example pre:nth-child(1) { 64 | margin-right: 100px; 65 | } 66 | pre > code { 67 | white-space: pre; 68 | margin-left: -7em; 69 | display: block; 70 | font-size: 0.9rem; 71 | } 72 | .md { 73 | border: 1px #808080 solid; 74 | border-radius: 4px; 75 | padding-left: 4px; 76 | padding-right: 4px; 77 | background-color: #ebebeb; 78 | } 79 | .margin-right { 80 | margin-right: 20px !important; 81 | } 82 | .react-tooltip-5 { 83 | opacity: 0; 84 | transform: translateY(10px); 85 | } 86 | .react-tooltip-5.fadeIn { 87 | opacity: 1; 88 | transform: translateY(0); 89 | } 90 | -------------------------------------------------------------------------------- /src/styles/main.styl: -------------------------------------------------------------------------------- 1 | @import "base" 2 | @import "Tooltip" 3 | @import "examples" 4 | -------------------------------------------------------------------------------- /src/styles/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.15.0 2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript */ 3 | /** 4 | * okaidia theme for JavaScript, CSS and HTML 5 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 6 | * @author ocodia 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: #f8f8f2; 12 | background: none; 13 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | text-align: left; 16 | white-space: pre; 17 | word-spacing: normal; 18 | word-break: normal; 19 | word-wrap: normal; 20 | line-height: 1.5; 21 | 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | 26 | -webkit-hyphens: none; 27 | -moz-hyphens: none; 28 | -ms-hyphens: none; 29 | hyphens: none; 30 | } 31 | 32 | /* Code blocks */ 33 | pre[class*="language-"] { 34 | padding: 1em; 35 | margin: .5em 0; 36 | overflow: auto; 37 | border-radius: 0.3em; 38 | } 39 | 40 | :not(pre) > code[class*="language-"], 41 | pre[class*="language-"] { 42 | background: #272822; 43 | } 44 | 45 | /* Inline code */ 46 | :not(pre) > code[class*="language-"] { 47 | padding: .1em; 48 | border-radius: .3em; 49 | white-space: normal; 50 | } 51 | 52 | .token.comment, 53 | .token.prolog, 54 | .token.doctype, 55 | .token.cdata { 56 | color: slategray; 57 | } 58 | 59 | .token.punctuation { 60 | color: #f8f8f2; 61 | } 62 | 63 | .namespace { 64 | opacity: .7; 65 | } 66 | 67 | .token.property, 68 | .token.tag, 69 | .token.constant, 70 | .token.symbol, 71 | .token.deleted { 72 | color: #f92672; 73 | } 74 | 75 | .token.boolean, 76 | .token.number { 77 | color: #ae81ff; 78 | } 79 | 80 | .token.selector, 81 | .token.attr-name, 82 | .token.string, 83 | .token.char, 84 | .token.builtin, 85 | .token.inserted { 86 | color: #a6e22e; 87 | } 88 | 89 | .token.operator, 90 | .token.entity, 91 | .token.url, 92 | .language-css .token.string, 93 | .style .token.string, 94 | .token.variable { 95 | color: #f8f8f2; 96 | } 97 | 98 | .token.atrule, 99 | .token.attr-value, 100 | .token.function, 101 | .token.class-name { 102 | color: #e6db74; 103 | } 104 | 105 | .token.keyword { 106 | color: #66d9ef; 107 | } 108 | 109 | .token.regex, 110 | .token.important { 111 | color: #fd971f; 112 | } 113 | 114 | .token.important, 115 | .token.bold { 116 | font-weight: bold; 117 | } 118 | .token.italic { 119 | font-style: italic; 120 | } 121 | 122 | .token.entity { 123 | cursor: help; 124 | } 125 | --------------------------------------------------------------------------------