Socket
Socket
Sign inDemoInstall

react-native-walkthrough-tooltip

Package Overview
Dependencies
2
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.0-beta.0 to 1.2.0

2

package.json
{
"name": "react-native-walkthrough-tooltip",
"version": "1.2.0-beta.0",
"version": "1.2.0",
"description": "An inline wrapper for calling out React Native components via tooltip",

@@ -5,0 +5,0 @@ "main": "src/tooltip.js",

# React Native Walkthrough Tooltip [![npm](https://img.shields.io/npm/v/react-native-walkthrough-tooltip.svg)](https://www.npmjs.com/package/react-native-walkthrough-tooltip) [![npm](https://img.shields.io/npm/dm/react-native-walkthrough-tooltip.svg)](https://www.npmjs.com/package/react-native-walkthrough-tooltip)
> Much credit belongs to [@jeanregisser](https://github.com/jeanregisser) and the [react-native-popover](https://github.com/jeanregisser/react-native-popover) library. Most of the animations and geometry computation belong to his library. Please check it out! It was an invaluable resource.
## Tooltip
React Native Walkthrough Tooltip is a fullscreen modal that highlights whichever element it wraps.\
When not visible, the wrapped element is displayed normally.
> 🎉 Now available 🎉 [`react-native-walkthrough`](https://github.com/jasongaare/react-native-walkthrough): a lightweight walkthrough library for React Native using react-native-walkthrough-tooltip
*Used by* [`react-native-walkthrough`](https://github.com/jasongaare/react-native-walkthrough): a lightweight walkthrough library for React Native using react-native-walkthrough-tooltip
## Breaking Changes in Version 1.0
### Table of Contents
For Version 1.0, the library was refactored and simplified.
- [Installation](#installation)
- [Breaking Changes in Version 1.0](#breaking-changes-in-version-10)
- [Example Usage](#example-usage)
- [Screenshot](#screenshot)
- [How it works](#how-it-works)
- [Props](#props)
- [Style Props](#style-props)
- [Class definitions for props](#class-definitions-for-props)
- [TooltipChildrenContext](#tooltipchildrencontext)
### Installation
```bash
yarn add react-native-walkthrough-tooltip
```
### Breaking Changes in Version 1.0
For Version 1.0, the library was refactored and simplified.
- **No more `animated` prop** - if you want to have your tooltips animated, use the last stable version: `0.6.1`. Hopefully animations can be added again in the sure (great idea for a PR!)

@@ -29,12 +43,6 @@ - **No more `displayArea` and `childlessPlacementPadding` props** - these have been replaced with the `displayInsets` prop, which allows you to simply declare how many pixels in from each side of the screen to inset the area the tooltip may display.

### Example Usage
### Installation
To see an expo snack example, click [here](https://snack.expo.io/@matthewliuhello/react-native-walkthrough-tooltip-example)
```
yarn add react-native-walkthrough-tooltip
```
### Example Usage
```js

@@ -68,2 +76,3 @@ import Tooltip from 'react-native-walkthrough-tooltip';

| ---------------- | ---------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| accessible | bool| true | Set this to `false` if you do not want the root touchable element to be accessible. [See docs on accessible here](https://reactnative.dev/docs/accessibility#accessibility-properties)
| allowChildInteraction | bool| true | By default, the user can touch and interact with the child element. When this prop is false, the user cannot interact with the child element while the tooltip is visible. |

@@ -74,4 +83,6 @@ | arrowSize | `Size` | { width: 16, height: 8 } | The dimensions of the arrow on the bubble pointing to the highlighted element |

| closeOnChildInteraction | bool | true | When child interaction is allowed, this prop determines if `onClose` should be called when the user interacts with the child element. Default is true (usually means the tooltip will dismiss once the user touches the element highlighted) |
| closeOnContentInteraction | bool | true | this prop determines if `onClose` should be called when the user interacts with the content element. Default is true (usually means the tooltip will dismiss once the user touches the content element) |
| content | function/Element | `<View />` | This is the view displayed in the tooltip popover bubble |
| displayInsets | object | { top: 24, bottom: 24, left: 24, right: 24 } | The number of pixels to inset the tooltip on the screen (think of it like padding). The tooltip bubble should never render outside of these insets, so you may need to adjust your `content` accordingly |
| disableShadow | bool | false | When true, tooltips will not appear elevated. Disabling shadows will remove the warning: `RCTView has a shadow set but cannot calculate shadow efficiently` on IOS devices. |
| isVisible | bool | false | When true, tooltip is displayed | |

@@ -82,2 +93,3 @@ | onClose | function | null | Callback fired when the user taps the tooltip background overlay |

| supportedOrientations | array | ["portrait", "landscape"] | This prop allows you to control the supported orientations the tooltip modal can be displayed. It correlates directly with [the prop for React Native's Modal component](https://facebook.github.io/react-native/docs/modal#supportedorientations) (has no effect if `useReactNativeModal` is false) |
| topAdjustment | number | 0 | Value which provides additional vertical offest for the child element displayed in a tooltip. Commonly set to: `Platform.OS === 'android' ? -StatusBar.currentHeight : 0` due to an issue with React Native's measure function on Android
| useInteractionManager | bool | false | Set this to true if you want the tooltip to wait to become visible until the callback for `InteractionManager.runAfterInteractions` is executed. Can be useful if you need to wait for navigation transitions to complete, etc. [See docs on InteractionManager here](https://facebook.github.io/react-native/docs/interactionmanager)

@@ -90,8 +102,9 @@ | useReactNativeModal | bool| true | By default, this library uses a `<Modal>` component from React Native. If you need to disable this, and simply render an absolutely positioned full-screen view, set `useReactNativeModal={false}`. This is especially useful if you desire to render a Tooltip while you have a different `Modal` rendered.

| Prop name | Effect |
| --------------- | ------------------------------------------------------------------------------- |
| arrowStyle | Styles the triangle that points to the called out element |
| backgroundStyle | Styles the overlay view that sits behind the tooltip, but over the current view |
| contentStyle | Styles the content wrapper that surrounds the `content` element |
| tooltipStyle | Styles the tooltip that wraps the arrow and content elements |
| Prop name | Effect |
| -------------------- | ------------------------------------------------------------------------------- |
| arrowStyle | Styles the triangle that points to the called out element |
| backgroundStyle | Styles the overlay view that sits behind the tooltip, but over the current view |
| childrenWrapperStyle | Styles the view that wraps cloned children |
| contentStyle | Styles the content wrapper that surrounds the `content` element |
| tooltipStyle | Styles the tooltip that wraps the arrow and content elements |

@@ -105,2 +118,3 @@ ### Class definitions for props

[React Context](https://reactjs.org/docs/context.html) that can be used to distinguish "real" children rendered inside parent's layout from their copies rendered inside tooltip's modal. The duplicate child rendered in the tooltip modal is wrapped in a Context.Provider which provides object with prop `tooltipDuplicate` set to `true`, so informed decisions may be made, if necessary, based on where the child rendered.
```js

@@ -107,0 +121,0 @@ import Tooltip, { TooltipChildrenContext } from 'react-native-walkthrough-tooltip';

@@ -26,44 +26,2 @@ class Point {

const nonNegativeContentHeight = (adjustedContentSize, contentSize) =>
adjustedContentSize.height > 0
? adjustedContentSize.height
: contentSize.height;
const nonNegativeContentWidth = (adjustedContentSize, contentSize) =>
adjustedContentSize.width > 0 ? adjustedContentSize.width : contentSize.width;
const adjustedContentMeasured = (adjustedContentSize, contentSize) => {
console.log({ adjustedContentSize, contentSize });
// this content is bounded in width, but can grow in height
const adjustedWidthFinished =
adjustedContentSize.width > 0 &&
adjustedContentSize.height === -1 &&
adjustedContentSize.width === contentSize.width;
// this content is bounded in height, but can grow in width
const adjustedHeightFinished =
adjustedContentSize.height > 0 &&
adjustedContentSize.width === -1 &&
adjustedContentSize.height === contentSize.height;
// this content is either bounded in both dimensions,
// or fits inside the displayInsets with adjustment
const contentEqualsAdjusted =
adjustedContentSize.height === contentSize.height &&
adjustedContentSize.width === contentSize.width;
// special centered case where neight dimension is bounded and the view
// fits with center justification and alignment
const unboundedCenteredFinished =
adjustedContentSize.width === -1 && adjustedContentSize.height === -1;
return (
adjustedWidthFinished ||
adjustedHeightFinished ||
contentEqualsAdjusted ||
unboundedCenteredFinished
);
};
const makeChildlessRect = ({ displayInsets, windowDims, placement }) => {

@@ -154,6 +112,3 @@ switch (placement) {

displayInsets.left,
childRect.x +
(childRect.width -
nonNegativeContentWidth(adjustedContentSize, contentSize)) /
2,
childRect.x + (childRect.width - adjustedContentSize.width) / 2,
),

@@ -194,5 +149,3 @@ Math.max(

tooltipOrigin.x =
displayInsets.left +
(maxWidth - nonNegativeContentWidth(adjustedContentSize, contentSize)) /
2;
windowDims.width - displayInsets.right - adjustedContentSize.width;
}

@@ -229,6 +182,3 @@

displayInsets.left,
childRect.x +
(childRect.width -
nonNegativeContentWidth(adjustedContentSize, contentSize)) /
2,
childRect.x + (childRect.width - adjustedContentSize.width) / 2,
),

@@ -270,5 +220,3 @@ Math.min(

tooltipOrigin.x =
displayInsets.left +
(maxWidth - nonNegativeContentWidth(adjustedContentSize, contentSize)) /
2;
windowDims.width - displayInsets.right - adjustedContentSize.width;
}

@@ -297,3 +245,3 @@

contentSize.width,
contentSize.height >= maxHeight ? maxHeight : -1,
Math.min(maxHeight, contentSize.height),
);

@@ -310,3 +258,3 @@

displayInsets.top,
childRect.y + (childRect.height - contentSize.height) / 2,
childRect.y + (childRect.height - adjustedContentSize.height) / 2,
),

@@ -343,5 +291,3 @@ );

tooltipOrigin.y =
windowDims.height -
displayInsets.bottom -
nonNegativeContentHeight(adjustedContentSize, contentSize);
windowDims.height - displayInsets.bottom - adjustedContentSize.height;
}

@@ -370,3 +316,3 @@

contentSize.width,
contentSize.height >= maxHeight ? maxHeight : -1,
Math.min(maxHeight, contentSize.height),
);

@@ -383,3 +329,3 @@

displayInsets.top,
childRect.y + (childRect.height - contentSize.height) / 2,
childRect.y + (childRect.height - adjustedContentSize.height) / 2,
),

@@ -418,5 +364,3 @@ );

tooltipOrigin.y =
windowDims.height -
displayInsets.bottom -
nonNegativeContentHeight(adjustedContentSize, contentSize);
windowDims.height - displayInsets.bottom - adjustedContentSize.height;
}

@@ -438,3 +382,2 @@

makeChildlessRect,
adjustedContentMeasured,
computeCenterGeometry,

@@ -441,0 +384,0 @@ computeTopGeometry,

@@ -6,5 +6,9 @@ import { StyleSheet } from 'react-native';

...StyleSheet.absoluteFillObject,
opacity: 0,
backgroundColor: 'transparent',
zIndex: 500,
},
containerVisible: {
opacity: 1,
},
background: {

@@ -15,4 +19,5 @@ ...StyleSheet.absoluteFillObject,

backgroundColor: 'transparent',
opacity: 0,
position: 'absolute',
},
shadow: {
shadowColor: 'black',

@@ -23,5 +28,2 @@ shadowOffset: { width: 0, height: 2 },

},
tooltipVisible: {
opacity: 1,
},
content: {

@@ -98,3 +100,3 @@ borderRadius: 4,

const tooltipPlacementStyles = ({ arrowSize, placement, tooltipOrigin }) => {
const { height: arrowHeight } = arrowSize;
const { height } = arrowSize;

@@ -104,4 +106,4 @@ switch (placement) {

return {
paddingTop: arrowHeight,
top: tooltipOrigin.y - arrowHeight,
paddingTop: height,
top: tooltipOrigin.y - height,
left: tooltipOrigin.x,

@@ -111,3 +113,3 @@ };

return {
paddingBottom: arrowHeight,
paddingBottom: height,
top: tooltipOrigin.y,

@@ -118,9 +120,9 @@ left: tooltipOrigin.x,

return {
paddingLeft: arrowHeight,
paddingLeft: height,
top: tooltipOrigin.y,
left: tooltipOrigin.x - arrowHeight,
left: tooltipOrigin.x - height,
};
case 'left':
return {
paddingRight: arrowHeight,
paddingRight: height,
top: tooltipOrigin.y,

@@ -145,2 +147,3 @@ left: tooltipOrigin.x,

placement,
topAdjustment,
} = styleGeneratorProps;

@@ -181,9 +184,21 @@

],
containerStyle: styles.container,
containerStyle: [
styles.container,
StyleSheet.compose(
adjustedContentSize.width !== 0 &&
measurementsFinished &&
styles.containerVisible,
topAdjustment !== 0 && {
top: topAdjustment,
},
),
],
contentStyle,
tooltipStyle: [
styles.tooltip,
StyleSheet.compose(
styles.tooltip,
ownProps.disableShadow ? {} : styles.shadow,
),
tooltipPlacementStyles(styleGeneratorProps),
ownProps.tooltipStyle,
measurementsFinished && styles.tooltipVisible,
],

@@ -190,0 +205,0 @@ };

@@ -44,2 +44,5 @@ // Type definitions for react-native-walkthrough-tooltip 1.0.0

tooltipStyle?: StyleProp<ViewStyle>;
// Styles the View element that wraps the children to clone it
childrenWrapperStyle?: StyleProp<ViewStyle>;
}

@@ -60,2 +63,5 @@

// When true (default), onClose prop is called when user touches content element
closeOnContentInteraction?: boolean;
// This is the view displayed in the tooltip popover bubble

@@ -67,2 +73,6 @@ content?: React.ReactElement;

// When true, tooltip shadow aren't displayed
// Fix: https://github.com/jasongaare/react-native-walkthrough-tooltip/issues/81
disableShadow?: boolean;
// When true, tooltip is displayed

@@ -100,2 +110,21 @@ isVisible?: boolean;

useReactNativeModal?: boolean;
/**
*The distance between the tooltip-rendered child and the arrow pointing to it
*/
childContentSpacing?: number;
/**
*The top value to set for the container. This is useful to fix the issue with StatusBar in Android.
```js
// Usage Example
<Tooltip topAdjustment={Platform.OS === 'android' ? -StatusBar.currentHeight : 0} />
```
*/
topAdjustment?: number;
/**
*Set this to false if you want to override the default accessible on the root TouchableWithoutFeedback
*/
accessible?: boolean;
}

@@ -102,0 +131,0 @@

@@ -17,3 +17,2 @@ import React, { Component } from 'react';

makeChildlessRect,
adjustedContentMeasured,
computeCenterGeometry,

@@ -63,8 +62,10 @@ computeTopGeometry,

closeOnChildInteraction: true,
closeOnContentInteraction: true,
content: <View />,
displayInsets: {},
disableShadow: false,
isVisible: false,
onClose: () => {
console.warn(
'[react-native-walkthrough-tooltip] onClose prop no provided',
'[react-native-walkthrough-tooltip] onClose prop not provided',
);

@@ -77,2 +78,4 @@ },

useReactNativeModal: true,
topAdjustment: 0,
accessible: true,
};

@@ -90,2 +93,3 @@

closeOnChildInteraction: PropTypes.bool,
closeOnContentInteraction: PropTypes.bool,
content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),

@@ -98,2 +102,3 @@ displayInsets: PropTypes.shape({

}),
disableShadow: PropTypes.bool,
isVisible: PropTypes.bool,

@@ -106,2 +111,4 @@ onClose: PropTypes.func,

useReactNativeModal: PropTypes.bool,
topAdjustment: PropTypes.number,
accessible: PropTypes.bool,
};

@@ -115,2 +122,3 @@

this.isMeasuringChild = false;
this.interactionPromise = null;

@@ -139,24 +147,5 @@ this.childWrapper = React.createRef();

componentDidMount() {
Dimensions.addEventListener('change', this.resetMeasurements);
Dimensions.addEventListener('change', this.updateWindowDims);
}
resetMeasurements = (dims = null) => {
this.setState(
prevState => ({
contentSize: new Size(0, 0),
adjustedContentSize: new Size(0, 0),
anchorPoint: new Point(0, 0),
tooltipOrigin: new Point(0, 0),
childRect: new Rect(0, 0, 0, 0),
measurementsFinished: false,
windowDims: dims ? dims.window : prevState.windowDims,
}),
() => {
setTimeout(() => {
this.measureChildRect();
}, 500);
},
);
};
componentDidUpdate(prevProps, prevState) {

@@ -171,6 +160,6 @@ const { content, isVisible, placement } = this.props;

if (contentChanged || placementChanged || insetsChanged) {
this.resetMeasurements();
} else if (becameVisible) {
this.measureChildRect();
if (contentChanged || placementChanged || becameVisible || insetsChanged) {
setTimeout(() => {
this.measureChildRect();
});
}

@@ -180,3 +169,6 @@ }

componentWillUnmount() {
Dimensions.removeEventListener('change', this.resetMeasurements);
Dimensions.removeEventListener('change', this.updateWindowDims);
if (this.interactionPromise) {
this.interactionPromise.cancel();
}
}

@@ -205,5 +197,4 @@

if (prevState.measurementsFinished && !nextProps.isVisible) {
nextState.measurementsFinished = false;
nextState.adjustedContentSize = new Size(0, 0);
nextState.contentSize = new Size(0, 0);
nextState.measurementsFinished = false;
}

@@ -218,2 +209,21 @@

updateWindowDims = dims => {
this.setState(
{
windowDims: dims.window,
contentSize: new Size(0, 0),
adjustedContentSize: new Size(0, 0),
anchorPoint: new Point(0, 0),
tooltipOrigin: new Point(0, 0),
childRect: new Rect(0, 0, 0, 0),
measurementsFinished: false,
},
() => {
setTimeout(() => {
this.measureChildRect();
}, 500); // give the rotation a moment to finish
},
);
};
doChildlessPlacement = () => {

@@ -232,17 +242,4 @@ this.onChildMeasurementComplete(

const contentSize = new Size(width, height);
const nextState = { contentSize };
const contentSizeChanged = !rfcIsEqual(contentSize, this.state.contentSize);
if (
contentSizeChanged &&
rfcIsEqual(this.state.adjustedContentSize, this.state.contentSize)
) {
nextState.adjustedContentSize = new Size(0, 0);
}
this.setState(nextState, () => {
if (contentSizeChanged) {
this.computeGeometry();
}
this.setState({ contentSize }, () => {
this.computeGeometry();
});

@@ -259,3 +256,5 @@ };

this.isMeasuringChild = false;
this.computeGeometry();
if (this.state.contentSize.width) {
this.computeGeometry();
}
},

@@ -266,24 +265,32 @@ );

measureChildRect = () => {
const doMeasurement = () =>
setTimeout(() => {
if (!this.isMeasuringChild) {
this.isMeasuringChild = true;
if (
this.childWrapper.current &&
typeof this.childWrapper.current.measure === 'function'
) {
this.childWrapper.current.measure(
(x, y, width, height, pageX, pageY) => {
const childRect = new Rect(pageX, pageY, width, height);
const doMeasurement = () => {
if (!this.isMeasuringChild) {
this.isMeasuringChild = true;
if (
this.childWrapper.current &&
typeof this.childWrapper.current.measure === 'function'
) {
this.childWrapper.current.measure(
(x, y, width, height, pageX, pageY) => {
const childRect = new Rect(pageX, pageY, width, height);
if (
Object.values(childRect).every(value => value !== undefined)
) {
this.onChildMeasurementComplete(childRect);
},
);
} else {
this.doChildlessPlacement();
}
} else {
this.doChildlessPlacement();
}
},
);
} else {
this.doChildlessPlacement();
}
}, 1000);
}
};
if (this.props.useInteractionManager) {
InteractionManager.runAfterInteractions(() => {
if (this.interactionPromise) {
this.interactionPromise.cancel();
}
this.interactionPromise = InteractionManager.runAfterInteractions(() => {
doMeasurement();

@@ -345,7 +352,2 @@ });

const measurementsFinished =
!!childRect.width &&
!!contentSize.width &&
adjustedContentMeasured(adjustedContentSize, contentSize);
this.setState({

@@ -355,3 +357,3 @@ tooltipOrigin,

placement,
measurementsFinished,
measurementsFinished: childRect.width && contentSize.width,
adjustedContentSize,

@@ -375,11 +377,14 @@ });

pointerEvents={this.props.allowChildInteraction ? 'box-none' : 'none'}
style={{
position: 'absolute',
height,
width,
top: y,
left: x,
alignItems: 'center',
justifyContent: 'center',
}}
style={[
{
position: 'absolute',
height,
width,
top: y,
left: x,
alignItems: 'center',
justifyContent: 'center',
},
this.props.childrenWrapperStyle,
]}
>

@@ -402,10 +407,18 @@ {this.props.children}

tooltipOrigin: this.state.tooltipOrigin,
topAdjustment: this.props.topAdjustment,
});
const hasChildren = React.Children.count(this.props.children) > 0;
const shouldShowChildren =
this.state.measurementsFinished && this.props.showChildInTooltip;
const onPressContent = () => {
if (this.props.closeOnContentInteraction) {
this.props.onClose();
}
};
return (
<TouchableWithoutFeedback onPress={this.props.onClose}>
<TouchableWithoutFeedback
onPress={this.props.onClose}
accessible={this.props.accessible}
>
<View style={generatedStyles.containerStyle}>

@@ -419,7 +432,12 @@ <View style={[generatedStyles.backgroundStyle]}>

>
{this.props.content}
<TouchableWithoutFeedback
onPress={onPressContent}
accessible={this.props.accessible}
>
{this.props.content}
</TouchableWithoutFeedback>
</View>
</View>
</View>
{hasChildren && shouldShowChildren
{hasChildren && this.props.showChildInTooltip
? this.renderChildInTooltip()

@@ -426,0 +444,0 @@ : null}

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc