├── .npmignore ├── example ├── app │ ├── assets │ │ ├── styles │ │ │ ├── _page.scss │ │ │ ├── _variable.scss │ │ │ ├── _mixin.scss │ │ │ ├── app.scss │ │ │ ├── _common.scss │ │ │ └── _region.scss │ │ └── images │ │ │ └── logo.svg │ ├── routes │ │ ├── Example1 │ │ │ └── index.js │ │ └── Example2 │ │ │ └── index.js │ ├── components │ │ ├── Footer.js │ │ ├── pages │ │ │ ├── Example1 │ │ │ │ └── index.js │ │ │ ├── Example2 │ │ │ │ └── index.js │ │ │ └── Home │ │ │ │ └── index.js │ │ ├── App.js │ │ ├── Header.js │ │ └── common │ │ │ └── Document.js │ ├── index.html │ └── app.js ├── webpack.server.js └── webpack.config.js ├── .bowerrc ├── .gitignore ├── src ├── index.js ├── tooltip.scss └── Tooltip.js ├── .eslintignore ├── .stylelintignore ├── .stylelintrc ├── .editorconfig ├── lib ├── index.js ├── tooltip.css └── Tooltip.js ├── .babelrc ├── bower.json ├── webpack.config.js ├── README.md ├── dist ├── react-tooltip-component.css └── react-tooltip-component.js ├── .eslintrc ├── package.json └── gulpfile.babel.js /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | example -------------------------------------------------------------------------------- /example/app/assets/styles/_page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | node_modules/* 3 | bower_components/* 4 | example/dist/* 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Tooltip from './Tooltip'; 2 | 3 | export default Tooltip; 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | bower_components/* 3 | dist/* 4 | example/dist/* 5 | lib/* 6 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | bower_components/* 3 | dist/* 4 | example/dist/* 5 | lib/* 6 | -------------------------------------------------------------------------------- /example/app/assets/styles/_variable.scss: -------------------------------------------------------------------------------- 1 | $black: #000; 2 | $white: #fff; 3 | $primary: $black; 4 | -------------------------------------------------------------------------------- /example/app/assets/styles/_mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix() { 2 | &::before, 3 | &::after { 4 | content: " "; 5 | display: table; 6 | } 7 | &::after { 8 | clear: both; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/app/routes/Example1/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | path: 'ex-1', 3 | getComponent(location, callback) { 4 | require.ensure([], require => { 5 | callback(null, require('components/pages/Example1')); 6 | }, 'page-ex-1'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /example/app/routes/Example2/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | path: 'ex-2', 3 | getComponent(location, callback) { 4 | require.ensure([], require => { 5 | callback(null, require('components/pages/Example2')); 6 | }, 'page-ex-2'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "at-rule-empty-line-before": [ 5 | "always", { 6 | "except": ["blockless-group", "all-nested"], 7 | "ignore": ["after-comment"] 8 | } 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /example/app/assets/styles/app.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | React component starter 3 | ========================================================================== */ 4 | @import "variable"; 5 | @import "mixin"; 6 | @import "common"; 7 | @import "region"; 8 | @import "page"; 9 | -------------------------------------------------------------------------------- /example/app/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Footer extends React.Component { 4 | render() { 5 | return ( 6 | 11 | ); 12 | } 13 | } 14 | 15 | export default Footer; 16 | 17 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _Tooltip = require('./Tooltip'); 10 | 11 | var _Tooltip2 = _interopRequireDefault(_Tooltip); 12 | 13 | exports['default'] = _Tooltip2['default']; 14 | module.exports = exports['default']; -------------------------------------------------------------------------------- /example/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React component example 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /example/app/components/pages/Example1/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Document from 'components/common/Document'; 3 | 4 | class Example1Page extends React.Component { 5 | render() { 6 | return ( 7 | 9 |

Example 1

10 |
11 | ); 12 | } 13 | } 14 | 15 | export default Example1Page; 16 | 17 | -------------------------------------------------------------------------------- /example/app/components/pages/Example2/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Document from 'components/common/Document'; 3 | 4 | class Example2Page extends React.Component { 5 | render() { 6 | return ( 7 | 9 |

Example 2

10 |
11 | ); 12 | } 13 | } 14 | 15 | export default Example2Page; 16 | 17 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "optional": [], 4 | "env": { 5 | "development": { 6 | "plugins": ["react-transform"], 7 | "extra": { 8 | "react-transform": { 9 | "transforms": [ 10 | { 11 | "transform": "react-transform-hmr", 12 | "imports": ["react"], 13 | "locals": ["module"] 14 | } 15 | ] 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tooltip-component", 3 | "version": "0.3.0", 4 | "description": "React component.", 5 | "main": ["dist/react-tooltip-component.js", "dist/react-tooltip-component.css"], 6 | "keywords": [ 7 | "react-component", 8 | "react", 9 | "component", 10 | "tooltip" 11 | ], 12 | "license": "MIT", 13 | "ignore": [ 14 | "**/.*", 15 | "node_modules", 16 | "bower_components", 17 | "test", 18 | "tests" 19 | ], 20 | "dependencies": {}, 21 | "devDependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /example/app/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './Header'; 3 | import Footer from './Footer'; 4 | 5 | class App extends React.Component { 6 | static propTypes = { 7 | children: React.PropTypes.node 8 | }; 9 | 10 | render() { 11 | return ( 12 |
13 |
14 |
15 |
16 | {this.props.children} 17 |
18 |
19 |
21 | ); 22 | } 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /example/app/assets/styles/_common.scss: -------------------------------------------------------------------------------- 1 | /* Pre Render 2 | ========================================================================== */ 3 | @keyframes spinner { 4 | 0% { 5 | transform: rotate(0deg); 6 | } 7 | 100% { 8 | transform: rotate(360deg); 9 | } 10 | } 11 | 12 | .pre-render { 13 | background: rgba(255, 255, 255, 0.7); 14 | position: fixed; 15 | top: 0; 16 | left: 0; 17 | width: 100%; 18 | height: 100%; 19 | z-index: 99999; 20 | .spinner { 21 | width: 48px; 22 | height: 48px; 23 | border: 1px solid lighten($primary, 40%); 24 | border-left-color: darken($primary, 10%); 25 | border-radius: 50%; 26 | animation: spinner 700ms infinite linear; 27 | position: absolute; 28 | top: 50%; 29 | left: 50%; 30 | margin-left: -24px; 31 | margin-top: -24px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/app/assets/styles/_region.scss: -------------------------------------------------------------------------------- 1 | /* Header 2 | ========================================================================== */ 3 | .navbar { 4 | border-radius: 0; 5 | } 6 | 7 | /* Footer 8 | ========================================================================== */ 9 | html, 10 | body, 11 | #app { 12 | height: 100%; 13 | } 14 | 15 | .layout-page { 16 | position: relative; 17 | min-height: 100%; 18 | padding-bottom: 60px; 19 | } 20 | 21 | .layout-main { 22 | margin-bottom: 30px; 23 | } 24 | 25 | .layout-footer { 26 | position: absolute; 27 | bottom: 0; 28 | width: 100%; 29 | height: 60px; 30 | background-color: #f8f8f8; 31 | padding: 20px 0; 32 | } 33 | 34 | /* Tooltip example 35 | ========================================================================== */ 36 | .tooltips-example { 37 | text-align: center; 38 | .btn { 39 | margin: 15px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/app/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | import logo from 'assets/images/logo.svg'; 5 | 6 | class Header extends React.Component { 7 | render() { 8 | return ( 9 |
10 | 25 |
26 | ); 27 | } 28 | } 29 | 30 | export default Header; 31 | 32 | -------------------------------------------------------------------------------- /example/app/components/common/Document.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Document extends React.Component { 4 | static propTypes = { 5 | title: React.PropTypes.string, 6 | className: React.PropTypes.string, 7 | children: React.PropTypes.any.isRequired 8 | }; 9 | 10 | state = { 11 | oldTitle: document.title, 12 | oldClassName: document.body.className 13 | }; 14 | 15 | componentWillMount = () => { 16 | if (this.props.title) { 17 | document.title = this.props.title; 18 | } 19 | if (this.props.className) { 20 | let className = this.state.oldClassName + ' ' + this.props.className; 21 | document.body.className = className.trim().replace(' ', ' '); 22 | } 23 | }; 24 | 25 | componentWillUnmount = () => { 26 | document.title = this.state.oldTitle; 27 | document.body.className = this.state.oldClassName; 28 | }; 29 | 30 | render() { 31 | if (this.props.children) { 32 | return React.Children.only(this.props.children); 33 | } 34 | return null; 35 | } 36 | } 37 | 38 | export default Document; 39 | -------------------------------------------------------------------------------- /example/app/app.js: -------------------------------------------------------------------------------- 1 | import 'babel-core/polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import {createHistory} from 'history'; 6 | import {Router, useRouterHistory} from 'react-router'; 7 | import App from 'components/App.js'; 8 | import {name} from '../../package.json'; 9 | 10 | import 'bootstrap/dist/css/bootstrap.css'; 11 | import 'assets/styles/app.scss'; 12 | 13 | const routes = { 14 | path: '/', 15 | component: App, 16 | indexRoute: { 17 | component: require('components/pages/Home') 18 | }, 19 | childRoutes: [ 20 | require('routes/Example1'), 21 | require('routes/Example2') 22 | ] 23 | }; 24 | 25 | const DEV = process && process.env && process.env.NODE_ENV === 'development'; 26 | const history = useRouterHistory(createHistory)({ 27 | basename: '/' + (DEV ? '' : name) 28 | }); 29 | 30 | const run = () => { 31 | ReactDOM.render( 32 | , 33 | document.getElementById('app') 34 | ); 35 | }; 36 | 37 | if (window.addEventListener) { 38 | window.addEventListener('DOMContentLoaded', run); 39 | } else { 40 | window.attachEvent('onload', run); 41 | } 42 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import pkg from './package.json'; 3 | import camelCase from 'camelcase'; 4 | 5 | const capitalizeFirstLetter = (string) => { 6 | return string.charAt(0).toUpperCase() + string.slice(1); 7 | }; 8 | 9 | const webpackConfig = { 10 | output: { 11 | filename: pkg.name + '.js', 12 | library: capitalizeFirstLetter(camelCase(pkg.name)), 13 | libraryTarget: 'umd' 14 | }, 15 | externals: { 16 | react: { 17 | root: 'React', 18 | commonjs: 'react', 19 | commonjs2: 'react', 20 | amd: 'react' 21 | }, 22 | 'react-dom': { 23 | root: 'ReactDOM', 24 | commonjs: 'react-dom', 25 | commonjs2: 'react-dom', 26 | amd: 'react-dom' 27 | } 28 | }, 29 | module: { 30 | loaders: [ 31 | { 32 | test: /\.(js|jsx)$/, 33 | exclude: /(node_modules)/, 34 | loader: 'babel-loader' 35 | } 36 | ] 37 | }, 38 | resolve: { 39 | modulesDirectories: ['node_modules', 'bower_components'], 40 | extensions: ['', '.jsx', '.js'] 41 | }, 42 | plugins: [ 43 | new webpack.DefinePlugin({ 44 | 'process.env': { 45 | NODE_ENV: JSON.stringify(process.env.NODE_ENV) 46 | } 47 | }), 48 | new webpack.optimize.UglifyJsPlugin({ 49 | sourceMap: false, 50 | compress: { 51 | warnings: false 52 | }, 53 | output: { 54 | comments: false 55 | } 56 | }), 57 | new webpack.optimize.DedupePlugin() 58 | ] 59 | }; 60 | 61 | export default webpackConfig; 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Tooltip 2 | 3 | A simple tooltip component for ReactJS. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install --save-dev react-tooltip-component 9 | ``` 10 | 11 | ## Usage 12 | 13 | ### Style 14 | 15 | #### Webpack 16 | 17 | ```js 18 | import 'react-tooltip-component/lib/tooltip.css'; 19 | //require('react-tooltip-component/lib/tooltip.css'); 20 | ``` 21 | 22 | ### Other 23 | 24 | ```html 25 | 26 | ``` 27 | 28 | ### JS 29 | 30 | ```js 31 | import Tooltip from 'react-tooltip-component'; 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | ### UMD 39 | 40 | ```html 41 | 42 | 43 | ``` 44 | 45 | ```js 46 | const Tooltip = window.ReactTooltipComponent; 47 | ``` 48 | 49 | ## Props 50 | 51 | | Name | Type | Required | Default | Description | 52 | |------|------|----------|---------|-------------| 53 | | title | string | true | | | 54 | | position | string | false | `top` | ['left', 'top', 'right', 'bottom'] | 55 | | fixed | bool | false | true | fixed or not | 56 | | container | element | false | document.body | | 57 | | children | node | true | | 58 | 59 | ## Example 60 | 61 | View [demo](http://minhtranite.github.io/react-tooltip-component) or example folder. 62 | -------------------------------------------------------------------------------- /dist/react-tooltip-component.css: -------------------------------------------------------------------------------- 1 | .tooltip{position:absolute;z-index:1070;display:block;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px}.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{left:5px}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000} -------------------------------------------------------------------------------- /example/app/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /example/webpack.server.js: -------------------------------------------------------------------------------- 1 | import url from 'url'; 2 | import express from 'express'; 3 | import webpack from 'webpack'; 4 | import webpackConfig from './webpack.config'; 5 | import webpackDevMiddleware from 'webpack-dev-middleware'; 6 | import webpackHotMiddleware from 'webpack-hot-middleware'; 7 | import fs from 'fs'; 8 | import path from 'path'; 9 | import http from 'http'; 10 | import https from 'https'; 11 | import opn from 'opn'; 12 | import httpProxy from 'http-proxy'; 13 | 14 | const devURL = 'http://localhost:3000'; 15 | const urlParts = url.parse(devURL); 16 | const proxyOptions = []; 17 | 18 | const proxy = httpProxy.createProxyServer({ 19 | changeOrigin: true, 20 | ws: true 21 | }); 22 | 23 | const compiler = webpack(webpackConfig); 24 | 25 | const app = express(); 26 | 27 | app.use(webpackDevMiddleware(compiler, { 28 | noInfo: true, 29 | publicPath: webpackConfig.output.publicPath, 30 | stats: { 31 | colors: true, 32 | hash: false, 33 | timings: false, 34 | chunks: false, 35 | chunkModules: false, 36 | modules: false, 37 | children: false, 38 | version: false, 39 | cached: false, 40 | cachedAssets: false, 41 | reasons: false, 42 | source: false, 43 | errorDetails: false 44 | } 45 | })); 46 | 47 | app.use(webpackHotMiddleware(compiler)); 48 | 49 | app.use('/assets', express.static(path.join(__dirname, 'app/assets'))); 50 | 51 | proxyOptions.forEach(option => { 52 | app.all(option.path, (req, res) => { 53 | proxy.web(req, res, option, err => { 54 | console.log(err.message); 55 | res.statusCode = 502; 56 | res.end(); 57 | }); 58 | }); 59 | }); 60 | 61 | app.get('*', (req, res, next) => { 62 | let filename = path.join(compiler.outputPath, 'index.html'); 63 | compiler.outputFileSystem.readFile(filename, (error, result) => { 64 | if (error) { 65 | return next(error); 66 | } 67 | res.set('content-type', 'text/html'); 68 | res.send(result); 69 | res.end(); 70 | }); 71 | }); 72 | 73 | let server = http.createServer(app); 74 | if (urlParts.protocol === 'https:') { 75 | server = https.createServer({ 76 | key: fs.readFileSync(path.join(__dirname, 'key.pem')), 77 | cert: fs.readFileSync(path.join(__dirname, 'cert.pem')) 78 | }, app); 79 | } 80 | 81 | server.listen(urlParts.port, () => { 82 | console.log('Listening at ' + devURL); 83 | opn(devURL); 84 | }); 85 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "blockBindings": true, 10 | "classes": true, 11 | "defaultParams": true, 12 | "destructuring": true, 13 | "forOf": true, 14 | "modules": true, 15 | "objectLiteralComputedProperties": true, 16 | "objectLiteralShorthandMethods": true, 17 | "objectLiteralShorthandProperties": true, 18 | "spread": true, 19 | "superInFunctions": true, 20 | "templateStrings": true, 21 | "unicodeCodePointEscapes": true, 22 | "jsx": true 23 | }, 24 | "rules": { 25 | "strict": 0, 26 | "curly": 0, 27 | "quotes": [2, "single", "avoid-escape"], 28 | "semi": 2, 29 | "no-underscore-dangle": 0, 30 | "no-unused-vars": 2, 31 | "camelcase": [2, {"properties": "never"}], 32 | "new-cap": 0, 33 | "accessor-pairs": 0, 34 | "brace-style": [2, "1tbs"], 35 | "consistent-return": 2, 36 | "dot-location": [2, "property"], 37 | "dot-notation": 2, 38 | "eol-last": 2, 39 | "indent": [2, 2, {"SwitchCase": 1}], 40 | "no-bitwise": 0, 41 | "no-multi-spaces": 2, 42 | "no-shadow": 2, 43 | "no-unused-expressions": 2, 44 | "space-after-keywords": 2, 45 | "space-before-blocks": 2, 46 | "jsx-quotes": [1, "prefer-double"], 47 | "react/display-name": 0, 48 | "react/jsx-boolean-value": [2, "always"], 49 | "react/jsx-no-undef": 2, 50 | "react/jsx-sort-props": 0, 51 | "react/jsx-sort-prop-types": 0, 52 | "react/jsx-uses-react": 2, 53 | "react/jsx-uses-vars": 2, 54 | "react/no-did-mount-set-state": 2, 55 | "react/no-did-update-set-state": 2, 56 | "react/no-multi-comp": [2, {"ignoreStateless": true}], 57 | "react/no-unknown-property": 2, 58 | "react/prop-types": 1, 59 | "react/react-in-jsx-scope": 2, 60 | "react/self-closing-comp": 2, 61 | "react/sort-comp": 0, 62 | "react/wrap-multilines": [2, {"declaration": false, "assignment": false}] 63 | }, 64 | "globals": { 65 | "inject": false, 66 | "module": false, 67 | "describe": false, 68 | "it": false, 69 | "before": false, 70 | "beforeEach": false, 71 | "after": false, 72 | "afterEach": false, 73 | "expect": false, 74 | "window": false, 75 | "document": false 76 | }, 77 | "plugins": [ 78 | "react" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /lib/tooltip.css: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | position: absolute; 3 | z-index: 1070; 4 | display: block; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | font-style: normal; 7 | font-weight: normal; 8 | letter-spacing: normal; 9 | line-break: auto; 10 | line-height: 1.42857143; 11 | text-align: left; 12 | text-decoration: none; 13 | text-shadow: none; 14 | text-transform: none; 15 | white-space: normal; 16 | word-break: normal; 17 | word-spacing: normal; 18 | word-wrap: normal; 19 | font-size: 12px; 20 | } 21 | 22 | .tooltip.top { 23 | margin-top: -3px; 24 | padding: 5px 0; 25 | } 26 | 27 | .tooltip.right { 28 | margin-left: 3px; 29 | padding: 0 5px; 30 | } 31 | 32 | .tooltip.bottom { 33 | margin-top: 3px; 34 | padding: 5px 0; 35 | } 36 | 37 | .tooltip.left { 38 | margin-left: -3px; 39 | padding: 0 5px; 40 | } 41 | 42 | .tooltip-inner { 43 | max-width: 200px; 44 | padding: 3px 8px; 45 | color: #fff; 46 | text-align: center; 47 | background-color: #000; 48 | border-radius: 4px; 49 | } 50 | 51 | .tooltip-arrow { 52 | position: absolute; 53 | width: 0; 54 | height: 0; 55 | border-color: transparent; 56 | border-style: solid; 57 | } 58 | 59 | .tooltip.top .tooltip-arrow { 60 | bottom: 0; 61 | left: 50%; 62 | margin-left: -5px; 63 | border-width: 5px 5px 0; 64 | border-top-color: #000; 65 | } 66 | 67 | .tooltip.top-left .tooltip-arrow { 68 | bottom: 0; 69 | right: 5px; 70 | margin-bottom: -5px; 71 | border-width: 5px 5px 0; 72 | border-top-color: #000; 73 | } 74 | 75 | .tooltip.top-right .tooltip-arrow { 76 | bottom: 0; 77 | left: 5px; 78 | margin-bottom: -5px; 79 | border-width: 5px 5px 0; 80 | border-top-color: #000; 81 | } 82 | 83 | .tooltip.right .tooltip-arrow { 84 | top: 50%; 85 | left: 0; 86 | margin-top: -5px; 87 | border-width: 5px 5px 5px 0; 88 | border-right-color: #000; 89 | } 90 | 91 | .tooltip.left .tooltip-arrow { 92 | top: 50%; 93 | right: 0; 94 | margin-top: -5px; 95 | border-width: 5px 0 5px 5px; 96 | border-left-color: #000; 97 | } 98 | 99 | .tooltip.bottom .tooltip-arrow { 100 | top: 0; 101 | left: 50%; 102 | margin-left: -5px; 103 | border-width: 0 5px 5px; 104 | border-bottom-color: #000; 105 | } 106 | 107 | .tooltip.bottom-left .tooltip-arrow { 108 | top: 0; 109 | right: 5px; 110 | margin-top: -5px; 111 | border-width: 0 5px 5px; 112 | border-bottom-color: #000; 113 | } 114 | 115 | .tooltip.bottom-right .tooltip-arrow { 116 | top: 0; 117 | left: 5px; 118 | margin-top: -5px; 119 | border-width: 0 5px 5px; 120 | border-bottom-color: #000; 121 | } 122 | -------------------------------------------------------------------------------- /src/tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | position: absolute; 3 | z-index: 1070; 4 | display: block; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | font-style: normal; 7 | font-weight: normal; 8 | letter-spacing: normal; 9 | line-break: auto; 10 | line-height: 1.42857143; 11 | text-align: left; 12 | text-decoration: none; 13 | text-shadow: none; 14 | text-transform: none; 15 | white-space: normal; 16 | word-break: normal; 17 | word-spacing: normal; 18 | word-wrap: normal; 19 | font-size: 12px; 20 | } 21 | 22 | .tooltip.top { 23 | margin-top: -3px; 24 | padding: 5px 0; 25 | } 26 | 27 | .tooltip.right { 28 | margin-left: 3px; 29 | padding: 0 5px; 30 | } 31 | 32 | .tooltip.bottom { 33 | margin-top: 3px; 34 | padding: 5px 0; 35 | } 36 | 37 | .tooltip.left { 38 | margin-left: -3px; 39 | padding: 0 5px; 40 | } 41 | 42 | .tooltip-inner { 43 | max-width: 200px; 44 | padding: 3px 8px; 45 | color: #fff; 46 | text-align: center; 47 | background-color: #000; 48 | border-radius: 4px; 49 | } 50 | 51 | .tooltip-arrow { 52 | position: absolute; 53 | width: 0; 54 | height: 0; 55 | border-color: transparent; 56 | border-style: solid; 57 | } 58 | 59 | .tooltip.top .tooltip-arrow { 60 | bottom: 0; 61 | left: 50%; 62 | margin-left: -5px; 63 | border-width: 5px 5px 0; 64 | border-top-color: #000; 65 | } 66 | 67 | .tooltip.top-left .tooltip-arrow { 68 | bottom: 0; 69 | right: 5px; 70 | margin-bottom: -5px; 71 | border-width: 5px 5px 0; 72 | border-top-color: #000; 73 | } 74 | 75 | .tooltip.top-right .tooltip-arrow { 76 | bottom: 0; 77 | left: 5px; 78 | margin-bottom: -5px; 79 | border-width: 5px 5px 0; 80 | border-top-color: #000; 81 | } 82 | 83 | .tooltip.right .tooltip-arrow { 84 | top: 50%; 85 | left: 0; 86 | margin-top: -5px; 87 | border-width: 5px 5px 5px 0; 88 | border-right-color: #000; 89 | } 90 | 91 | .tooltip.left .tooltip-arrow { 92 | top: 50%; 93 | right: 0; 94 | margin-top: -5px; 95 | border-width: 5px 0 5px 5px; 96 | border-left-color: #000; 97 | } 98 | 99 | .tooltip.bottom .tooltip-arrow { 100 | top: 0; 101 | left: 50%; 102 | margin-left: -5px; 103 | border-width: 0 5px 5px; 104 | border-bottom-color: #000; 105 | } 106 | 107 | .tooltip.bottom-left .tooltip-arrow { 108 | top: 0; 109 | right: 5px; 110 | margin-top: -5px; 111 | border-width: 0 5px 5px; 112 | border-bottom-color: #000; 113 | } 114 | 115 | .tooltip.bottom-right .tooltip-arrow { 116 | top: 0; 117 | left: 5px; 118 | margin-top: -5px; 119 | border-width: 0 5px 5px; 120 | border-bottom-color: #000; 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tooltip-component", 3 | "version": "0.3.0", 4 | "description": "React tooltip component.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "NODE_ENV=development gulp start", 8 | "eslint": "NODE_ENV=development eslint .", 9 | "stylelint": "NODE_ENV=development stylelint '**/*.?(s)@(a|c)ss'", 10 | "lint": "npm run eslint && npm run stylelint", 11 | "build": "NODE_ENV=production gulp build" 12 | }, 13 | "keywords": [ 14 | "react-component", 15 | "react", 16 | "component", 17 | "tooltip" 18 | ], 19 | "peerDependencies": { 20 | "react": "^0.14 || ^15.0.0", 21 | "react-dom": "^0.14 || ^15.0.0" 22 | }, 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "autoprefixer": "^6.3.1", 26 | "babel": "^5.8.34", 27 | "babel-core": "^5.8.29", 28 | "babel-eslint": "^4.1.3", 29 | "babel-loader": "^5.3.2", 30 | "babel-plugin-react-transform": "^1.1.1", 31 | "bootstrap": "^3.3.6", 32 | "camelcase": "^2.0.1", 33 | "css-loader": "^0.23.0", 34 | "cssnano": "^3.3.2", 35 | "del": "^2.1.0", 36 | "eslint": "^1.10.1", 37 | "eslint-loader": "^1.2.0", 38 | "eslint-plugin-react": "^3.15.0", 39 | "express": "^4.13.3", 40 | "extract-text-webpack-plugin": "^1.0.1", 41 | "file-loader": "^0.8.5", 42 | "gulp": "^3.9.0", 43 | "gulp-babel": "^5.2.1", 44 | "gulp-concat": "^2.6.0", 45 | "gulp-eslint": "^1.1.0", 46 | "gulp-filter": "^3.0.1", 47 | "gulp-postcss": "^6.1.0", 48 | "gulp-sass": "^2.2.0", 49 | "gulp-stylelint": "^2.0.2", 50 | "history": "^2.0.1", 51 | "html-loader": "^0.4.0", 52 | "html-webpack-plugin": "^2.7.1", 53 | "http-proxy": "^1.12.0", 54 | "json-loader": "^0.5.4", 55 | "node-sass": "^3.4.2", 56 | "opn": "^4.0.0", 57 | "postcss-loader": "^0.8.0", 58 | "react-dom": "^0.14 || ^15.0.0", 59 | "react-router": "^2.0.0", 60 | "react-transform-hmr": "^1.0.1", 61 | "run-sequence": "^1.1.5", 62 | "sass-loader": "^3.1.2", 63 | "style-loader": "^0.13.0", 64 | "stylelint": "^6.5.0", 65 | "stylelint-config-standard": "^8.0.0", 66 | "stylelint-webpack-plugin": "^0.2.0", 67 | "webpack": "^1.12.11", 68 | "webpack-dev-middleware": "^1.2.0", 69 | "webpack-hot-middleware": "^2.5.0", 70 | "webpack-stream": "^3.0.1" 71 | }, 72 | "repository": { 73 | "type": "git", 74 | "url": "https://github.com/vn38minhtran/react-tooltip-component.git" 75 | }, 76 | "author": "Minh Tran", 77 | "license": "MIT", 78 | "bugs": { 79 | "url": "https://github.com/vn38minhtran/react-tooltip-component/issues" 80 | }, 81 | "homepage": "https://github.com/vn38minhtran/react-tooltip-component" 82 | } 83 | -------------------------------------------------------------------------------- /example/app/components/pages/Home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Document from 'components/common/Document'; 3 | import Tooltip from 'react-tooltip-component'; 4 | 5 | class HomePage extends React.Component { 6 | render() { 7 | return ( 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 30 | 31 | 33 | 36 | 37 | 38 | 41 | 42 | 43 | 46 | 47 |
48 |
49 |
50 | 51 | 54 | 55 | 56 | 59 | 60 | 61 | 64 | 65 | 66 | 69 | 70 |
71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | export default HomePage; 78 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import {spawnSync} from 'child_process'; 3 | import del from 'del'; 4 | import stylelint from 'gulp-stylelint'; 5 | import eslint from 'gulp-eslint'; 6 | import babel from 'gulp-babel'; 7 | import webpackStream from 'webpack-stream'; 8 | import webpackConfig from './webpack.config'; 9 | import exampleWebpackConfig from './example/webpack.config'; 10 | import webpack from 'webpack'; 11 | import sass from 'gulp-sass'; 12 | import filter from 'gulp-filter'; 13 | import postcss from 'gulp-postcss'; 14 | import autoprefixer from 'autoprefixer'; 15 | import cssnano from 'cssnano'; 16 | import concat from 'gulp-concat'; 17 | import pkg from './package.json'; 18 | import runSequence from 'run-sequence'; 19 | 20 | gulp.task('start', (callback) => { 21 | let start = spawnSync('babel-node', ['example/webpack.server.js'], {stdio: 'inherit'}); 22 | if (start.stderr) { 23 | callback(start.stderr); 24 | } 25 | }); 26 | 27 | gulp.task('build:lib:clean', () => { 28 | del.sync(['lib', 'dist']); 29 | }); 30 | 31 | gulp.task('build:lib:stylelint', () => { 32 | return gulp 33 | .src(['src/**/*.{css,scss,sass}']) 34 | .pipe(stylelint({ 35 | failAfterError: true, 36 | reporters: [ 37 | {formatter: 'string', console: true} 38 | ] 39 | })); 40 | }); 41 | 42 | gulp.task('build:lib:eslint', () => { 43 | return gulp 44 | .src(['src/**/*.js']) 45 | .pipe(eslint()) 46 | .pipe(eslint.format()) 47 | .pipe(eslint.failOnError()); 48 | }); 49 | 50 | gulp.task('build:lib:babel', () => { 51 | return gulp 52 | .src(['src/**/*.js']) 53 | .pipe(babel()) 54 | .pipe(gulp.dest('lib')); 55 | }); 56 | 57 | gulp.task('build:lib:umd', () => { 58 | return gulp 59 | .src(['src/index.js']) 60 | .pipe(webpackStream(webpackConfig, webpack)) 61 | .pipe(gulp.dest('dist')); 62 | }); 63 | 64 | gulp.task('build:lib:sass', () => { 65 | let cssFilter = filter('**/*.css'); 66 | return gulp 67 | .src(['src/**/*.scss', '!src/**/_*.scss']) 68 | .pipe(sass({outputStyle: 'expanded'}).on('error', sass.logError)) 69 | .pipe(cssFilter) 70 | .pipe(gulp.dest('lib')) 71 | .pipe(concat(pkg.name + '.css')) 72 | .pipe(postcss([ 73 | autoprefixer({ 74 | browsers: [ 75 | 'ie >= 10', 76 | 'ie_mob >= 10', 77 | 'ff >= 30', 78 | 'chrome >= 34', 79 | 'safari >= 7', 80 | 'opera >= 23', 81 | 'ios >= 7', 82 | 'android >= 4.4', 83 | 'bb >= 10' 84 | ] 85 | }), 86 | cssnano({ 87 | safe: true, 88 | discardComments: {removeAll: true} 89 | }) 90 | ])) 91 | .pipe(gulp.dest('dist')); 92 | }); 93 | 94 | gulp.task('build:lib:copy', () => { 95 | return gulp 96 | .src(['src/**/*', '!src/**/*.{scss,js}']) 97 | .pipe(gulp.dest('lib')) 98 | .pipe(gulp.dest('dist')); 99 | }); 100 | 101 | gulp.task('build:lib', (callback) => { 102 | runSequence( 103 | 'build:lib:clean', 104 | 'build:lib:stylelint', 105 | 'build:lib:eslint', 106 | 'build:lib:babel', 107 | 'build:lib:umd', 108 | 'build:lib:sass', 109 | 'build:lib:copy', 110 | callback 111 | ); 112 | }); 113 | 114 | gulp.task('build:example:clean', () => { 115 | del.sync(['example/dist']); 116 | }); 117 | 118 | gulp.task('build:example:webpack', () => { 119 | return gulp 120 | .src(['example/app/app.js']) 121 | .pipe(webpackStream(exampleWebpackConfig, webpack)) 122 | .pipe(gulp.dest('example/dist')); 123 | }); 124 | 125 | gulp.task('build:example:copy', () => { 126 | return gulp 127 | .src(['example/app/*', '!example/app/*.{html,js}'], {nodir: true}) 128 | .pipe(gulp.dest('example/dist')); 129 | }); 130 | 131 | gulp.task('build:example', (callback) => { 132 | runSequence( 133 | 'build:example:clean', 134 | 'build:example:webpack', 135 | 'build:example:copy', 136 | callback 137 | ); 138 | }); 139 | 140 | gulp.task('build', (callback) => { 141 | runSequence('build:lib', 'build:example', callback); 142 | }); 143 | 144 | gulp.task('default', ['build']); 145 | -------------------------------------------------------------------------------- /dist/react-tooltip-component.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["react","react-dom"],t):"object"==typeof exports?exports.ReactTooltipComponent=t(require("react"),require("react-dom")):e.ReactTooltipComponent=t(e.React,e.ReactDOM)}(this,function(e,t){return function(e){function t(n){if(o[n])return o[n].exports;var r=o[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var o={};return t.m=e,t.c=o,t.p="",t(0)}([function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var r=o(1),i=n(r);t["default"]=i["default"],e.exports=t["default"]},function(e,t,o){"use strict";function n(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var p=function(){function e(e,t){for(var o=0;o { 105 | let processors = [ 106 | autoprefixer({ 107 | browsers: [ 108 | 'ie >= 10', 109 | 'ie_mob >= 10', 110 | 'ff >= 30', 111 | 'chrome >= 34', 112 | 'safari >= 7', 113 | 'opera >= 23', 114 | 'ios >= 7', 115 | 'android >= 4.4', 116 | 'bb >= 10' 117 | ] 118 | }) 119 | ]; 120 | if (PROD) { 121 | processors.push(cssnano({ 122 | safe: true, 123 | discardComments: { 124 | removeAll: true 125 | } 126 | })); 127 | } 128 | return processors; 129 | }, 130 | sassLoader: { 131 | includePaths: [ 132 | path.join(__dirname, '../bower_components'), 133 | path.join(__dirname, '../node_modules') 134 | ], 135 | outputStyle: PROD ? 'compressed' : 'expanded' 136 | }, 137 | node: { 138 | net: 'mock', 139 | dns: 'mock' 140 | }, 141 | debug: DEV, 142 | devtool: DEV ? '#eval' : false, 143 | stats: { 144 | children: false 145 | }, 146 | progress: PROD, 147 | profile: PROD, 148 | bail: PROD 149 | }; 150 | 151 | if (DEV) { 152 | webpackConfig.plugins = webpackConfig.plugins.concat([ 153 | new webpack.HotModuleReplacementPlugin(), 154 | new webpack.NoErrorsPlugin() 155 | ]); 156 | } 157 | 158 | if (PROD) { 159 | webpackConfig.plugins = webpackConfig.plugins.concat([ 160 | new ExtractTextPlugin('[contenthash].css'), 161 | new webpack.optimize.UglifyJsPlugin({ 162 | sourceMap: false, 163 | compress: { 164 | warnings: false 165 | }, 166 | output: { 167 | comments: false 168 | } 169 | }), 170 | new webpack.optimize.DedupePlugin() 171 | ]); 172 | } 173 | 174 | export default webpackConfig; 175 | -------------------------------------------------------------------------------- /src/Tooltip.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | class Tooltip extends React.Component { 5 | static propTypes = { 6 | container: React.PropTypes.any, 7 | children: React.PropTypes.node.isRequired, 8 | title: React.PropTypes.string.isRequired, 9 | position: React.PropTypes.oneOf(['left', 'top', 'right', 'bottom']), 10 | fixed: React.PropTypes.bool, 11 | space: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]) 12 | }; 13 | 14 | static defaultProps = { 15 | container: document.body, 16 | position: 'top', 17 | fixed: true, 18 | space: 5 19 | }; 20 | 21 | componentDidMount = () => { 22 | this.container = this.props.container || document.body; 23 | this.componentEl = ReactDOM.findDOMNode(this); 24 | this.tooltipEl = document.createElement('div'); 25 | 26 | let tooltipArrowEl = document.createElement('div'); 27 | tooltipArrowEl.className = 'tooltip-arrow'; 28 | 29 | let tooltipContentEl = document.createElement('div'); 30 | tooltipContentEl.className = 'tooltip-inner'; 31 | tooltipContentEl.textContent = this.props.title; 32 | 33 | this.tooltipEl.appendChild(tooltipArrowEl); 34 | this.tooltipEl.appendChild(tooltipContentEl); 35 | this.tooltipEl.className = 'tooltip ' + this.props.position; 36 | this.container.appendChild(this.tooltipEl); 37 | this.resetTooltip(); 38 | 39 | this.componentEl.addEventListener(this.props.fixed ? 'mouseenter' : 'mousemove', this.handleMouseMove); 40 | this.componentEl.addEventListener('mouseleave', this.handleMouseOut); 41 | }; 42 | 43 | componentDidUpdate = () => { 44 | this.tooltipEl.className = 'tooltip ' + this.props.position; 45 | this.tooltipEl.childNodes[1].textContent = this.props.title; 46 | }; 47 | 48 | 49 | componentWillUnmount = () => { 50 | this.componentEl.removeEventListener(this.props.fixed ? 'mouseenter' : 'mousemove', this.handleMouseMove); 51 | this.componentEl.removeEventListener('mouseleave', this.handleMouseOut); 52 | this.container.removeChild(this.tooltipEl); 53 | }; 54 | 55 | resetTooltip = () => { 56 | this.tooltipEl.style.transition = 'opacity 0.4s'; 57 | this.tooltipEl.style.left = '-500px'; 58 | this.tooltipEl.style.top = '-500px'; 59 | this.tooltipEl.style.opacity = 0; 60 | }; 61 | 62 | handleMouseMove = (e) => { 63 | if (this.props.title === '') { 64 | return; 65 | } 66 | 67 | let tooltipPosition = this.getTooltipPosition(e); 68 | let tooltipOffset = this.getTooltipOffset(); 69 | 70 | this.tooltipEl.style.left = tooltipPosition.x + tooltipOffset.x + 'px'; 71 | this.tooltipEl.style.top = tooltipPosition.y + tooltipOffset.y + 'px'; 72 | this.tooltipEl.style.opacity = 1; 73 | }; 74 | 75 | handleMouseOut = () => { 76 | this.resetTooltip(); 77 | }; 78 | 79 | getTooltipPosition = (e) => { 80 | let pointX; 81 | let pointY; 82 | let bodyRect = document.body.getBoundingClientRect(); 83 | let containerRect = this.container.getBoundingClientRect(); 84 | let containerOffsetX = containerRect.left - bodyRect.left; 85 | let containerOffsetY = containerRect.top - bodyRect.top; 86 | if (this.props.fixed) { 87 | let componentRect = this.componentEl.getBoundingClientRect(); 88 | let componentOffsetX = componentRect.left - containerOffsetX; 89 | let componentOffsetY = componentRect.top - containerOffsetY; 90 | let componentWidth = this.componentEl.offsetWidth; 91 | let componentHeight = this.componentEl.offsetHeight; 92 | let cOffsetX = 0; 93 | let cOffsetY = 0; 94 | switch (this.props.position) { 95 | case 'top': 96 | cOffsetX = componentWidth / 2; 97 | cOffsetY = 0; 98 | break; 99 | case 'right': 100 | cOffsetX = componentWidth; 101 | cOffsetY = componentHeight / 2; 102 | break; 103 | case 'bottom': 104 | cOffsetX = componentWidth / 2; 105 | cOffsetY = componentHeight; 106 | break; 107 | case 'left': 108 | cOffsetX = 0; 109 | cOffsetY = componentHeight / 2; 110 | break; 111 | } 112 | pointX = componentOffsetX + cOffsetX + (window.scrollX || window.pageXOffset); 113 | pointY = componentOffsetY + cOffsetY + (window.scrollY || window.pageYOffset); 114 | } else { 115 | let clientX = e.clientX; 116 | let clientY = e.clientY; 117 | pointX = clientX - containerOffsetX + (window.scrollX || window.pageXOffset); 118 | pointY = clientY - containerOffsetY + (window.scrollY || window.pageYOffset); 119 | } 120 | return { 121 | x: pointX, 122 | y: pointY 123 | }; 124 | }; 125 | 126 | getTooltipOffset = () => { 127 | let tooltipW = this.tooltipEl.offsetWidth; 128 | let tooltipH = this.tooltipEl.offsetHeight; 129 | let offsetX = 0; 130 | let offsetY = 0; 131 | switch (this.props.position) { 132 | case 'top': 133 | offsetX = -(tooltipW / 2); 134 | offsetY = -(tooltipH + Number(this.props.space)); 135 | break; 136 | case 'right': 137 | offsetX = Number(this.props.space); 138 | offsetY = -(tooltipH / 2); 139 | break; 140 | case 'bottom': 141 | offsetX = -(tooltipW / 2); 142 | offsetY = Number(this.props.space); 143 | break; 144 | case 'left': 145 | offsetX = -(tooltipW + Number(this.props.space)); 146 | offsetY = -(tooltipH / 2); 147 | break; 148 | } 149 | return { 150 | x: offsetX, 151 | y: offsetY 152 | }; 153 | }; 154 | 155 | render() { 156 | return this.props.children; 157 | } 158 | } 159 | 160 | export default Tooltip; 161 | -------------------------------------------------------------------------------- /lib/Tooltip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 12 | 13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 14 | 15 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 16 | 17 | var _react = require('react'); 18 | 19 | var _react2 = _interopRequireDefault(_react); 20 | 21 | var _reactDom = require('react-dom'); 22 | 23 | var _reactDom2 = _interopRequireDefault(_reactDom); 24 | 25 | var Tooltip = (function (_React$Component) { 26 | _inherits(Tooltip, _React$Component); 27 | 28 | function Tooltip() { 29 | var _this = this; 30 | 31 | _classCallCheck(this, Tooltip); 32 | 33 | _get(Object.getPrototypeOf(Tooltip.prototype), 'constructor', this).apply(this, arguments); 34 | 35 | this.componentDidMount = function () { 36 | _this.container = _this.props.container || document.body; 37 | _this.componentEl = _reactDom2['default'].findDOMNode(_this); 38 | _this.tooltipEl = document.createElement('div'); 39 | 40 | var tooltipArrowEl = document.createElement('div'); 41 | tooltipArrowEl.className = 'tooltip-arrow'; 42 | 43 | var tooltipContentEl = document.createElement('div'); 44 | tooltipContentEl.className = 'tooltip-inner'; 45 | tooltipContentEl.textContent = _this.props.title; 46 | 47 | _this.tooltipEl.appendChild(tooltipArrowEl); 48 | _this.tooltipEl.appendChild(tooltipContentEl); 49 | _this.tooltipEl.className = 'tooltip ' + _this.props.position; 50 | _this.container.appendChild(_this.tooltipEl); 51 | _this.resetTooltip(); 52 | 53 | _this.componentEl.addEventListener(_this.props.fixed ? 'mouseenter' : 'mousemove', _this.handleMouseMove); 54 | _this.componentEl.addEventListener('mouseleave', _this.handleMouseOut); 55 | }; 56 | 57 | this.componentDidUpdate = function () { 58 | _this.tooltipEl.className = 'tooltip ' + _this.props.position; 59 | _this.tooltipEl.childNodes[1].textContent = _this.props.title; 60 | }; 61 | 62 | this.componentWillUnmount = function () { 63 | _this.componentEl.removeEventListener(_this.props.fixed ? 'mouseenter' : 'mousemove', _this.handleMouseMove); 64 | _this.componentEl.removeEventListener('mouseleave', _this.handleMouseOut); 65 | _this.container.removeChild(_this.tooltipEl); 66 | }; 67 | 68 | this.resetTooltip = function () { 69 | _this.tooltipEl.style.transition = 'opacity 0.4s'; 70 | _this.tooltipEl.style.left = '-500px'; 71 | _this.tooltipEl.style.top = '-500px'; 72 | _this.tooltipEl.style.opacity = 0; 73 | }; 74 | 75 | this.handleMouseMove = function (e) { 76 | if (_this.props.title === '') { 77 | return; 78 | } 79 | 80 | var tooltipPosition = _this.getTooltipPosition(e); 81 | var tooltipOffset = _this.getTooltipOffset(); 82 | 83 | _this.tooltipEl.style.left = tooltipPosition.x + tooltipOffset.x + 'px'; 84 | _this.tooltipEl.style.top = tooltipPosition.y + tooltipOffset.y + 'px'; 85 | _this.tooltipEl.style.opacity = 1; 86 | }; 87 | 88 | this.handleMouseOut = function () { 89 | _this.resetTooltip(); 90 | }; 91 | 92 | this.getTooltipPosition = function (e) { 93 | var pointX = undefined; 94 | var pointY = undefined; 95 | var bodyRect = document.body.getBoundingClientRect(); 96 | var containerRect = _this.container.getBoundingClientRect(); 97 | var containerOffsetX = containerRect.left - bodyRect.left; 98 | var containerOffsetY = containerRect.top - bodyRect.top; 99 | if (_this.props.fixed) { 100 | var componentRect = _this.componentEl.getBoundingClientRect(); 101 | var componentOffsetX = componentRect.left - containerOffsetX; 102 | var componentOffsetY = componentRect.top - containerOffsetY; 103 | var componentWidth = _this.componentEl.offsetWidth; 104 | var componentHeight = _this.componentEl.offsetHeight; 105 | var cOffsetX = 0; 106 | var cOffsetY = 0; 107 | switch (_this.props.position) { 108 | case 'top': 109 | cOffsetX = componentWidth / 2; 110 | cOffsetY = 0; 111 | break; 112 | case 'right': 113 | cOffsetX = componentWidth; 114 | cOffsetY = componentHeight / 2; 115 | break; 116 | case 'bottom': 117 | cOffsetX = componentWidth / 2; 118 | cOffsetY = componentHeight; 119 | break; 120 | case 'left': 121 | cOffsetX = 0; 122 | cOffsetY = componentHeight / 2; 123 | break; 124 | } 125 | pointX = componentOffsetX + cOffsetX + (window.scrollX || window.pageXOffset); 126 | pointY = componentOffsetY + cOffsetY + (window.scrollY || window.pageYOffset); 127 | } else { 128 | var clientX = e.clientX; 129 | var clientY = e.clientY; 130 | pointX = clientX - containerOffsetX + (window.scrollX || window.pageXOffset); 131 | pointY = clientY - containerOffsetY + (window.scrollY || window.pageYOffset); 132 | } 133 | return { 134 | x: pointX, 135 | y: pointY 136 | }; 137 | }; 138 | 139 | this.getTooltipOffset = function () { 140 | var tooltipW = _this.tooltipEl.offsetWidth; 141 | var tooltipH = _this.tooltipEl.offsetHeight; 142 | var offsetX = 0; 143 | var offsetY = 0; 144 | switch (_this.props.position) { 145 | case 'top': 146 | offsetX = -(tooltipW / 2); 147 | offsetY = -(tooltipH + Number(_this.props.space)); 148 | break; 149 | case 'right': 150 | offsetX = Number(_this.props.space); 151 | offsetY = -(tooltipH / 2); 152 | break; 153 | case 'bottom': 154 | offsetX = -(tooltipW / 2); 155 | offsetY = Number(_this.props.space); 156 | break; 157 | case 'left': 158 | offsetX = -(tooltipW + Number(_this.props.space)); 159 | offsetY = -(tooltipH / 2); 160 | break; 161 | } 162 | return { 163 | x: offsetX, 164 | y: offsetY 165 | }; 166 | }; 167 | } 168 | 169 | _createClass(Tooltip, [{ 170 | key: 'render', 171 | value: function render() { 172 | return this.props.children; 173 | } 174 | }], [{ 175 | key: 'propTypes', 176 | value: { 177 | container: _react2['default'].PropTypes.any, 178 | children: _react2['default'].PropTypes.node.isRequired, 179 | title: _react2['default'].PropTypes.string.isRequired, 180 | position: _react2['default'].PropTypes.oneOf(['left', 'top', 'right', 'bottom']), 181 | fixed: _react2['default'].PropTypes.bool, 182 | space: _react2['default'].PropTypes.oneOfType([_react2['default'].PropTypes.string, _react2['default'].PropTypes.number]) 183 | }, 184 | enumerable: true 185 | }, { 186 | key: 'defaultProps', 187 | value: { 188 | container: document.body, 189 | position: 'top', 190 | fixed: true, 191 | space: 5 192 | }, 193 | enumerable: true 194 | }]); 195 | 196 | return Tooltip; 197 | })(_react2['default'].Component); 198 | 199 | exports['default'] = Tooltip; 200 | module.exports = exports['default']; --------------------------------------------------------------------------------