🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

react-tour-guide

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-tour-guide - npm Package Compare versions

Comparing version
0.0.1
to
0.0.2
+52
lib/js/Indicator.js
/**
* @jsx React.DOM
*/
'use strict';
var React = require('react/addons');
var Indicator = React.createClass({
propTypes: {
cssPosition: React.PropTypes.string.isRequired,
size: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]).isRequired,
xPos: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]).isRequired,
yPos: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]).isRequired,
handleIndicatorClick: React.PropTypes.func.isRequired
},
getDefaultProps: function() {
return {
cssPosition: 'absolute',
size: '30px',
xPos: 0,
yPos: 0
};
},
render: function() {
var styles = {
'position': this.props.cssPosition === 'fixed' ? 'fixed' : 'absolute',
'top': this.props.yPos,
'left': this.props.xPos,
'width': this.props.size,
'height': this.props.size
};
return (
<div className="tour-indicator" style={styles} onClick={this.props.handleIndicatorClick} />
);
}
});
module.exports = React.createFactory(Indicator);
/**
* @jsx React.DOM
*/
'use strict';
var React = require('react/addons');
var $ = require('jquery');
var Indicator = require('./Indicator');
var Tooltip = require('./Tooltip');
window.jQuery = $;
module.exports = function(settings, done) {
var mixin = {
settings: $.extend({
scrollToSteps: true,
indicatorSize: 30,
tooltipWidth: 250,
steps: []
}, settings),
completionCallback: done || function() {},
getInitialState: function() {
return {
currentIndex: 0,
showTooltip: false
};
},
_renderLayer: function() {
// By calling this method in componentDidMount() and componentDidUpdate(), you're effectively
// creating a "wormhole" that funnels React's hierarchical updates through to a DOM node on an
// entirely different part of the page.
React.render(this.renderCurrentStep(), this._target);
},
_unrenderLayer: function() {
React.unmountComponentAtNode(this._target);
},
componentDidUpdate: function() {
if ( this.settings.steps[this.state.currentIndex] ) {
this._renderLayer();
} else {
this.completionCallback();
this._unrenderLayer();
}
},
componentDidMount: function() {
// Appending to the body is easier than managing the z-index of everything on the page.
// It's also better for accessibility and makes stacking a snap (since components will stack
// in mount order).
if ( this.settings.steps[this.state.currentIndex] ) {
this._target = document.createElement('div');
document.body.appendChild(this._target);
this._renderLayer();
}
},
componentWillUnmount: function() {
this._unrenderLayer();
document.body.removeChild(this._target);
},
preventWindowOverflow: function(type, value, axis) {
var winWidth = parseInt($(window).width());
var docHeight = parseInt($(document).height());
var sizeVariable = type.toLowerCase() === 'indicator' ? this.settings.indicatorSize : this.settings.tooltipWidth;
if ( axis.toLowerCase() === 'x' ) {
if ( value + sizeVariable > winWidth ) {
value = winWidth - sizeVariable;
} else if ( value - sizeVariable/2 < 0 ) {
value = 0;
}
} else if ( axis.toLowerCase() === 'y' ) {
if ( value + sizeVariable/2 > docHeight ) {
value = docHeight - sizeVariable;
} else if ( value - sizeVariable/2 < 0 ) {
value = 0;
}
}
return value;
},
calculatePlacement: function(step, $element) {
var offset = $element.offset();
var width = $element.width();
var height = $element.height();
var position = step.position.toLowerCase();
var topRegex = new RegExp('top', 'gi');
var bottomRegex = new RegExp('bottom', 'gi');
var leftRegex = new RegExp('left', 'gi');
var rightRegex = new RegExp('right', 'gi');
var placement = {
indicator: {
x: 0,
y: 0
},
tooltip: {
x: 0,
y: 0
}
};
// Calculate x positions
if ( leftRegex.test(position) ) {
placement.indicator.x = offset.left - this.settings.indicatorSize/2;
placement.tooltip.y = offset.top - this.settings.tooltipWidth/4;
} else if ( rightRegex.test(position) ) {
placement.indicator.x = offset.left + width - this.settings.indicatorSize/2;
placement.tooltip.x = offset.left + width - this.settings.tooltipWidth/2;
} else {
placement.indicator.x = offset.left + width/2 - this.settings.indicatorSize/2;
placement.tooltip.x = offset.left + width/2 - this.settings.tooltipWidth/2;
}
// Calculate y positions
if ( topRegex.test(position) ) {
placement.indicator.y = offset.top - this.settings.indicatorSize/2;
placement.tooltip.y = offset.top - this.settings.tooltipWidth/4;
} else if ( bottomRegex.test(position) ) {
placement.indicator.y = offset.top + height - this.settings.indicatorSize/2;
placement.tooltip.y = offset.top + height - this.settings.tooltipWidth/4;
} else {
placement.indicator.y = offset.top + height/2 - this.settings.indicatorSize/2;
placement.tooltip.y = offset.top + height/2 - this.settings.tooltipWidth/4;
}
return {
indicator: {
x: this.preventWindowOverflow('indicator', placement.indicator.x, 'x'),
y: this.preventWindowOverflow('indicator', placement.indicator.y, 'y')
},
tooltip: {
x: this.preventWindowOverflow('tooltip', placement.tooltip.x, 'x'),
y: this.preventWindowOverflow('tooltip', placement.tooltip.y, 'y')
}
};
},
handleIndicatorClick: function(evt) {
evt.preventDefault();
this.setState({ showTooltip: true });
},
closeTooltip: function(evt) {
evt.preventDefault();
this.setState({
showTooltip: false,
currentIndex: this.state.currentIndex + 1
}, this.scrollToNextStep);
},
scrollToNextStep: function() {
var $nextIndicator = $('.tour-indicator');
if ( $nextIndicator && $nextIndicator.length && this.settings.scrollToSteps ) {
$('html, body').animate({
'scrollTop': $nextIndicator.offset().top - $(window).height()/2
}, 500);
}
},
renderCurrentStep: function() {
var element = null;
var currentStep = this.settings.steps[this.state.currentIndex];
var $target = $(currentStep.element);
var cssPosition = $target.css('position');
var placement = this.calculatePlacement(currentStep, $target);
if ( this.state.showTooltip ) {
element = (
<Tooltip cssPosition={cssPosition}
xPos={placement.tooltip.x}
yPos={placement.tooltip.y}
width={this.settings.tooltipWidth}
text={currentStep.text}
closeTooltip={this.closeTooltip} />
);
} else {
element = (
<Indicator cssPosition={cssPosition}
xPos={placement.indicator.x}
yPos={placement.indicator.y}
size={this.settings.indicatorSize}
handleIndicatorClick={this.handleIndicatorClick} />
);
}
return element;
}
};
return mixin;
};
/**
* @jsx React.DOM
*/
'use strict';
var React = require('react/addons');
var Tooltip = React.createClass({
propTypes: {
cssPosition: React.PropTypes.string.isRequired,
xPos: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]).isRequired,
yPos: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]).isRequired,
width: React.PropTypes.oneOfType([
React.PropTypes.number,
React.PropTypes.string
]).isRequired,
text: React.PropTypes.string.isRequired,
closeTooltip: React.PropTypes.func.isRequired
},
getDefaultProps: function() {
return {
cssPosition: 'absolute',
xPos: 0,
yPos: 0,
text: ''
};
},
render: function() {
var styles = {
'position': this.props.cssPosition === 'fixed' ? 'fixed' : 'absolute',
'top': this.props.yPos,
'left': this.props.xPos,
'width': this.props.width
};
return (
<div>
<div className="tour-backdrop" onClick={this.props.closeTooltip} />
<div className="tour-tooltip" style={styles}>
<p>{this.props.text}</p>
<div className="tour-btn close" onClick={this.props.closeTooltip}>Close</div>
</div>
</div>
);
}
});
module.exports = React.createFactory(Tooltip);
.tour-indicator {
height: 30px;
width: 30px;
-webkit-border-radius: 100%;
-moz-border-radius: 100%;
border-radius: 100%;
background-color: #3f94d0;
cursor: pointer;
/* Animate */
-webkit-animation-name: pulse;
-webkit-animation-duration: 1.5s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: ease-out;
-moz-animation-name: pulse;
-moz-animation-duration: 1.5s;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: ease-out;
-o-animation-name: pulse;
-o-animation-duration: 1.5s;
-o-animation-iteration-count: infinite;
-o-animation-timing-function: ease-out;
animation-name: pulse;
animation-duration: 1.5s;
animation-iteration-count: infinite;
animation-timing-function: ease-out;
}
/* WebKit/Safari and Chrome */
@-webkit-keyframes pulse {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
80% {
-webkit-transform: scale(1.5);
transform: scale(1.5);
}
100% {
-webkit-transform: scale(2.5);
transform: scale(2.5);
opacity: 0;
}
}
/* Gecko/Firefox */
@-moz-keyframes pulse {
0% {
-moz-transform: scale(0.3);
transform: scale(0.3);
opacity: 0.5;
}
80% {
-moz-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
100% {
-moz-transform: scale(2.5);
transform: scale(2.5);
opacity: 0;
}
}
/* Presto/Opera */
@-o-keyframes pulse {
0% {
-o-transform: scale(0.3);
transform: scale(0.3);
opacity: 0.5;
}
80% {
-o-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
100% {
-o-transform: scale(2.5);
transform: scale(2.5);
opacity: 0;
}
}
/* Standard */
@keyframes pulse {
0% {
transform: scale(0.3);
opacity: 0.5;
}
80% {
transform: scale(1.5);
opacity: 0;
}
100% {
transform: scale(2.5);
opacity: 0;
}
}
.tour-backdrop {
z-index: 998; /* one below z-index of tour-toolip (999) */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
text-align: center;
background: rgba(0, 0, 0, 0.45);
}
.tour-tooltip {
width: 200px;
background-color: #ffffff;
color: #545454;
border: 1px solid #dddddd;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
padding: 5px;
-webkit-box-shadow: 0 8px 6px -6px rgba(0,0,0,0.4);
-moz-box-shadow: 0 8px 6px -6px rgba(0,0,0,0.4);
box-shadow: 0 8px 6px -6px rgba(0,0,0,0.4);
}
.tour-tooltip p {
margin: 5px 3px;
}
.tour-tooltip .tour-btn.close {
cursor: pointer;
display: block;
width: auto;
margin: 0 auto;
background-color: #ffffff;
border: 1px solid #dddddd;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
padding: 3px;
text-align: center;
}
.tour-tooltip .tour-btn.close:hover {
border-color: #3f94d0;
color: #3f94d0;
}
+4
-3

@@ -0,1 +1,2 @@

'use strict';

@@ -5,8 +6,8 @@

Mixin: require('./lib/Mixin'),
Mixin: require('./lib/js/Mixin'),
Indicator: require('./lib/Indicator'),
Indicator: require('./lib/js/Indicator'),
Tooltip: require('./lib/Tooltip')
Tooltip: require('./lib/js/Tooltip')
};
{
"name": "react-tour-guide",
"version": "0.0.1",
"version": "0.0.2",
"author": "Jake Marsh <jakemmarsh@gmail.com>",

@@ -15,7 +15,12 @@ "description": "A ReactJS mixin to give new users a popup-based tour of your application.",

"react",
"tour"
"tour",
"walkthrough",
"guide"
],
"engines": {
"node": ">=0.12.x"
},
"dependencies": {
"jquery": "^2.1.3"
}
}
+47
-11

@@ -1,3 +0,3 @@

react-tour-guide
=================
react-tour-guide [![npm version](https://badge.fury.io/js/react-tour-guide.svg)](http://badge.fury.io/js/react-tour-guide)
==========================================================================================================================

@@ -15,10 +15,27 @@ A ReactJS mixin to give new users a popup-based tour of your application.

var TourGuideMixin = require('react-tour-guide').Mixin;
var tour = [
var tour = {
scrollToSteps: true,
indicatorSize: 30,
tooltipWidth: 250,
steps: [
{
text: 'This is the first step in the tour.',
element: 'header',
position: 'bottom'
},
{
text: 'This is the second step in the tour.',
element: '.navigation',
position: 'right'
}
]
};
var cb = function() {
console.log('User has completed tour!');
};
];
var App = React.createClass({
var InnerApp = React.createClass({
mixins: [TourGuideMixin(tour, cb)],
mixins: [TourGuideMixin(tour)],
...

@@ -33,4 +50,10 @@

An `array` of "steps" is passed to the TourGuideMixin. Each "step" represents one indicator and tooltip that a user must click through in the guided tour. A step has the following structure:
A Javascript object is passed to the `TourGuideMixin` to specify options, as well as the steps of your tour as an array. The options are:
- `scrollToSteps` (bool): if true, the page will be automatically scrolled to the next indicator (if one exists) after a tooltip is dismissed. Defaults to `true`.
- `indicatorSize` (int): the size (in pixels) that you want the Indicator to be. Defaults to `30`.
- `tooltipWidth` (int): the width (in pixels) that you want each tooltip to be. Defaults to `250`.
Each "step" in the array represents one indicator and tooltip that a user must click through in the guided tour. A step has the following structure:
```json

@@ -48,4 +71,12 @@ {

### HTML Structure
### Completion Callback
An optional callback may be passed as the second parameter to `TourGuideMixin`, which will be called once the current user has completed all the steps of your tour.
---
### Styling
Some basic styling is provided in `/lib/styles/tour-guide.css`. This can either be included directly in your project, or used as a base for your own custom styles. Below, the HTML structure of the tour is also outlined for custom styling.
The guided tour consists of two main elements for each step: an `indicator` and a `tooltip`. An indicator is a flashing element positioned on a specific element on the page, cueing the user to click. Upon click, the associated tooltip is triggered which the user must then read and dismiss.

@@ -56,3 +87,3 @@

```html
<div />
<div class="tour-indicator"></div>
```

@@ -63,3 +94,8 @@

```html
<div />
<div class="tour-backdrop">
<div class="tour-tooltip">
<p>{The step's text goes here.}</p>
<div class="tour-btn close">Close</div>
</div>
</div>
```
/**
* @jsx React.DOM
*/
'use strict';
var React = require('react/addons');
var Tooltip = React.createClass({
render: function() {
return (
<div />
);
}
});
module.exports = React.createFactory(Tooltip);
'use strict';
module.exports = {
componentDidMount: function() {
}
};
/**
* @jsx React.DOM
*/
'use strict';
var React = require('react/addons');
var Tooltip = React.createClass({
render: function() {
return (
<div />
);
}
});
module.exports = React.createFactory(Tooltip);