├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── index.js ├── package.json ├── view.gif └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | view.gif 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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-native-tooltip-view 2 | A dead simple tooltip view that you can populate yourself 3 | 4 | ## Installation 5 | ``` 6 | $ yarn add react-native-tooltip-view 7 | ``` 8 | Be aware that this package depends on recompose and styled-components are peer dependencies. 9 | So if you aren't using them install with: 10 | ``` 11 | $ yarn add recompose styled-components 12 | ``` 13 | And you should totally give a try with those awesome packages! 14 | 15 | ## Demo 16 | ![alt text](https://github.com/Astrocoders/react-native-tooltip-view/raw/master/view.gif "React Native ToolTipView") 17 | 18 | ## Usage 19 | 20 | ```js 21 | import TooltipView from 'react-native-tooltip-view' 22 | ... 23 | 24 | function Foo() { 25 | return ( 26 | } 29 | // you can pass an optional width for the tooltip 30 | width={200} 31 | // and also an optional background 32 | backgroundColor="#f9f9f9" 33 | // `triangleStyle`={{}} to style the little triangle 34 | // and any valid View props 35 | > 36 | {setMenuVisibility => ( 37 | // anything you want in here 38 | )} 39 | 40 | ) 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Platform, Dimensions, View, Modal, InteractionManager } from 'react-native' 3 | import PropTypes from 'prop-types' 4 | import styled from 'styled-components/native' 5 | import { compose, pure, withStateHandlers, withHandlers } from 'recompose' 6 | 7 | const WINDOW_DIMENSIONS = Dimensions.get('window') 8 | 9 | const TooltipWrapper = styled.View.attrs({ 10 | style: { 11 | shadowOffset: { height: 1.8 }, 12 | }, 13 | })` 14 | width: ${props => props.width}; 15 | position: absolute; 16 | shadowRadius: 1.62; 17 | shadowOpacity: 0.1845; 18 | bottom: ${props => props.bottom}; 19 | left: ${props => props.left}; 20 | background-color: ${props => props.backgroundColor || '#fff'}; 21 | ` 22 | 23 | const Triangle = styled.View` 24 | position: absolute; 25 | bottom: ${props => props.bottom - props.size}; 26 | left: ${props => props.left}; 27 | width: ${props => props.size}; 28 | height: ${props => props.size}; 29 | background-color: transparent; 30 | border-style: solid; 31 | border-top-width: ${props => props.size}; 32 | border-right-width: ${props => props.size}; 33 | border-bottom-width: 0; 34 | border-left-width: ${props => props.size}; 35 | border-top-color: ${props => props.backgroundColor || '#fff'}; 36 | border-right-color: transparent; 37 | border-bottom-color: transparent; 38 | border-left-color: transparent; 39 | ` 40 | 41 | const OverlayTouchable = styled.TouchableWithoutFeedback` 42 | flex: 1; 43 | ` 44 | 45 | const OverlayContentWrapper = styled.View`flex: 1;` 46 | 47 | function TooltipOptionsMenu({ 48 | isMenuActive, 49 | setMenuVisibility, 50 | showMenu, 51 | hideMenu, 52 | children, 53 | trigger, 54 | tooltipCoords, 55 | initPositioning, 56 | triangleProps, 57 | setWrapperRef, 58 | ...props 59 | }) { 60 | return ( 61 | setWrapperRef(ref)}> 62 | 63 | 64 | 65 | 66 | 67 | {children(setMenuVisibility)} 68 | 69 | 70 | 71 | 72 | {trigger(showMenu)} 73 | 74 | ) 75 | } 76 | 77 | TooltipOptionsMenu.propTypes = { 78 | isMenuActive: PropTypes.bool.isRequired, 79 | tooltipCoords: PropTypes.object.isRequired, 80 | initPositioning: PropTypes.func.isRequired, 81 | setMenuVisibility: PropTypes.func.isRequired, 82 | trigger: PropTypes.func.isRequired, 83 | triangleProps: PropTypes.object, 84 | children: PropTypes.func.isRequired, 85 | } 86 | 87 | TooltipOptionsMenu.defaultProps = { 88 | triangleProps: {}, 89 | } 90 | 91 | export default compose( 92 | withStateHandlers({ 93 | isMenuActive: false, 94 | tooltipCoords: {}, 95 | wrapperRef: {}, 96 | }, { 97 | setMenuVisibility: () => value => ({ isMenuActive: value }), 98 | hideMenu: () => () => ({ isMenuActive: false }), 99 | showMenu: () => () => ({ isMenuActive: true }), 100 | setTooltipCoords: () => tooltipCoords => ({ tooltipCoords }), 101 | setWrapperRef: () => wrapperRef => ({ wrapperRef }), 102 | }), 103 | withHandlers({ 104 | initPositioning: props => layoutEvent => { 105 | const { layout } = layoutEvent.nativeEvent 106 | 107 | // If the TooltipView is used within a tabs container or a navigator 108 | // with transitions we would get different values for pageX and pageY all the time. 109 | // So here we ensure we are going to get our sweet values only after those transitions have ended. 110 | InteractionManager.runAfterInteractions(() => { 111 | props.wrapperRef.measure((x, y, width, height, pageX, pageY) => { 112 | props.setTooltipCoords({ 113 | bottom: WINDOW_DIMENSIONS.height - (layout.height + pageY) + Platform.select({android: 0, ios: 30}), 114 | left: pageX + layout.width - 10, 115 | width: WINDOW_DIMENSIONS.width - layout.x - pageX - 10, 116 | }) 117 | }) 118 | }) 119 | }, 120 | }), 121 | pure, 122 | )(TooltipOptionsMenu) 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-tooltip-view", 3 | "version": "2.1.0", 4 | "description": "A dead simple tooltip view that you pass your own children", 5 | "main": "index.js", 6 | "repository": "https://github.com/Astrocoders/react-native-tooltip-view.git", 7 | "author": "Gabriel R. Abreu ", 8 | "license": "MIT", 9 | "peerDependencies": { 10 | "prop-types": "^15.5.10", 11 | "react": "^15.6.1", 12 | "react-native": "^0.45.1", 13 | "recompose": "^0.23.5", 14 | "styled-components": "^2.1.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocoders/react-native-tooltip-view/5f58d7504e9be8a39681e5dfec6166dc5a24df60/view.gif -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | asap@~2.0.3: 6 | version "2.0.5" 7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" 8 | 9 | change-emitter@^0.1.2: 10 | version "0.1.6" 11 | resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" 12 | 13 | core-js@^1.0.0: 14 | version "1.2.7" 15 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 16 | 17 | encoding@^0.1.11: 18 | version "0.1.12" 19 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 20 | dependencies: 21 | iconv-lite "~0.4.13" 22 | 23 | fbjs@^0.8.1: 24 | version "0.8.12" 25 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" 26 | dependencies: 27 | core-js "^1.0.0" 28 | isomorphic-fetch "^2.1.1" 29 | loose-envify "^1.0.0" 30 | object-assign "^4.1.0" 31 | promise "^7.1.1" 32 | setimmediate "^1.0.5" 33 | ua-parser-js "^0.7.9" 34 | 35 | hoist-non-react-statics@^1.0.0: 36 | version "1.2.0" 37 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" 38 | 39 | iconv-lite@~0.4.13: 40 | version "0.4.18" 41 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" 42 | 43 | is-stream@^1.0.1: 44 | version "1.1.0" 45 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 46 | 47 | isomorphic-fetch@^2.1.1: 48 | version "2.2.1" 49 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 50 | dependencies: 51 | node-fetch "^1.0.1" 52 | whatwg-fetch ">=0.10.0" 53 | 54 | js-tokens@^3.0.0: 55 | version "3.0.2" 56 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 57 | 58 | loose-envify@^1.0.0: 59 | version "1.3.1" 60 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 61 | dependencies: 62 | js-tokens "^3.0.0" 63 | 64 | node-fetch@^1.0.1: 65 | version "1.7.1" 66 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" 67 | dependencies: 68 | encoding "^0.1.11" 69 | is-stream "^1.0.1" 70 | 71 | object-assign@^4.1.0: 72 | version "4.1.1" 73 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 74 | 75 | promise@^7.1.1: 76 | version "7.3.1" 77 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 78 | dependencies: 79 | asap "~2.0.3" 80 | 81 | recompose@^0.23.5: 82 | version "0.23.5" 83 | resolved "https://registry.yarnpkg.com/recompose/-/recompose-0.23.5.tgz#72ac8261246bec378235d187467d02a721e8b1de" 84 | dependencies: 85 | change-emitter "^0.1.2" 86 | fbjs "^0.8.1" 87 | hoist-non-react-statics "^1.0.0" 88 | symbol-observable "^1.0.4" 89 | 90 | setimmediate@^1.0.5: 91 | version "1.0.5" 92 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 93 | 94 | symbol-observable@^1.0.4: 95 | version "1.0.4" 96 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" 97 | 98 | ua-parser-js@^0.7.9: 99 | version "0.7.13" 100 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" 101 | 102 | whatwg-fetch@>=0.10.0: 103 | version "2.0.3" 104 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 105 | --------------------------------------------------------------------------------