Added basic onboarding steps

This commit is contained in:
Stefan Zermatten
2017-09-27 16:19:00 +02:00
parent 44da62a962
commit f6b2dde479
29 changed files with 2036 additions and 16 deletions

View File

@@ -191,6 +191,7 @@ Schemas.Character = new SimpleSchema({
"settings.exportFeatures": {type: Boolean, defaultValue: true},
"settings.exportAttacks": {type: Boolean, defaultValue: true},
"settings.exportDescription": {type: Boolean, defaultValue: true},
"settings.newUserExperience": {type: Boolean, optional: true},
});
Characters.attachSchema(Schemas.Character);
@@ -554,6 +555,10 @@ if (Meteor.isServer){
});
Characters.before.insert(function(userId, doc) {
doc.urlName = getSlug(doc.name, {maintainCase: true}) || "-";
// The first character a user creates should have the new user experience
if (!Characters.find({owner: userId}).count()){
doc.settings.newUserExperience = true;
}
});
}

View File

@@ -0,0 +1,17 @@
@keyframes bounce {
from {
transform: translate(0px,0px);
}
to {
transform: translate(0px,-16px);
}
}
.bounce{
animation-name: bounce;
animation-duration: 0.3s;
animation-direction: alternate;
animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
animation-delay: 0s;
animation-iteration-count: infinite;
}

View File

@@ -44,17 +44,18 @@
</div>
<div bottom-item>
<paper-tabs id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}">
<paper-tab name="stats">Stats</paper-tab>
<paper-tab name="features">Features</paper-tab>
<paper-tab name="stats" class="{{#if shouldBounce 0}}bounce{{/if}}">Stats</paper-tab>
<paper-tab name="features" class="{{#if shouldBounce 1}}bounce{{/if}}">Features</paper-tab>
<paper-tab name="inventory">Inventory</paper-tab>
{{#unless hideSpellcasting}}
<paper-tab name="spells">Spells</paper-tab>
{{/unless}}
<paper-tab name="persona">Persona</paper-tab>
<paper-tab name="journal">Journal</paper-tab>
<paper-tab name="journal" class="{{#if shouldBounce 5}}bounce{{/if}}">Journal</paper-tab>
</paper-tabs>
</div>
</app-toolbar>
{{#if session "showNewUserExperience"}}{{> newUserStepper}}{{/if}}
</app-header>
<div class="flex" style="position: relative;">
<iron-pages id="tabPages" class="fit" selected={{selectedTab}}>

View File

@@ -29,7 +29,17 @@ Template.characterSheet.onRendered(function() {
tabFabMenus = _.times(6, (n) =>
tabPages[n].find(".mini-holder")
);
})
});
// New user experience starts on the features tab
var settings = Characters.findOne(this.data._id, {
fields: {settings: 1}
}).settings;
if (settings && settings.newUserExperience){
Session.set(this.data._id + ".selectedTab", "1");
Session.set("showNewUserExperience", true);
Session.set("newUserExperienceStep", 0);
}
//watch this character and make sure their encumbrance is updated
//trackEncumbranceConditions(this.data._id, this);
@@ -172,6 +182,20 @@ Template.characterSheet.helpers({
var char = Characters.findOne(this._id);
return char && char.settings.hideSpellcasting;
},
newUserExperience: function(){
var char = Characters.findOne(this._id);
return char && char.settings.newUserExperience;
},
shouldBounce: function(tab){
console.log(this._id);
const selected = Session.get(this._id + ".selectedTab")
const step = Session.get("newUserExperienceStep");
console.log({selected, step, tab});
if (selected == tab) return false;
return (tab === 1 && step === 0) ||
(tab === 5 && step === 1) ||
(tab === 0 && step === 2);
},
});
Template.characterSheet.events({

View File

@@ -42,9 +42,20 @@
</template>
<template name="featureEdit">
{{#if showNewUserExperience}}
{{# infoBox}}
<p>
Features represent all the permanent things your character can do.
</p><p>
A feature can change a character's stats with effects,
or give the character proficiencies, attacks, and buffs.
</p><p>
Give the feature a name, and close it to continue.
</p>
{{/infoBox}}
{{/if}}
<!--name-->
<paper-input id="featureNameInput" class="fullwidth" label="Name" value={{name}}></paper-input>
<div class="layout horizontal center wrap justified">
<paper-dropdown-menu class=flex label="Enable Feature" style="flex-basis: 150px; max-width: 200px;">
<dicecloud-selector selected={{enabledSelection}} class="dropdown-content enabled-dropdown">

View File

@@ -47,6 +47,9 @@ Template.featureDetails.events({
});
Template.featureEdit.helpers({
showNewUserExperience: function(){
return Session.get("newUserExperienceStep") === 0;
},
usesSet: function(){
return _.isString(this.uses);
},

View File

@@ -102,7 +102,7 @@
</div>
{{#if canEditCharacter _id}}
<paper-fab id="addFeature"
class="floatyButton"
class="floatyButton {{#if shouldFloatyButtonBounce}}bounce{{/if}}"
icon="add">
<paper-tooltip position="left">Add Feature</paper-tooltip>
</paper-fab>

View File

@@ -59,6 +59,10 @@ Template.features.helpers({
hasCharacters: function(string){
return string && string.match(/\S/);
},
shouldFloatyButtonBounce: function(){
const step = Session.get("newUserExperienceStep");
return step === 0 && Features.find({charId: this._id}).count() <= 1;
},
});
Template.features.events({

View File

@@ -53,7 +53,7 @@
</div>
<div class="bottom list">
<div class="item-slot">
<div class="item race layout horizontal center">
<div class="item race layout horizontal center {{#if shouldRaceBounce}}bounce{{/if}}">
{{race}}
</div>
</div>

View File

@@ -50,6 +50,9 @@ Template.journal.helpers({
var char = Characters.findOne(this._id, {fields: {race: 1}});
return char && char.race;
},
shouldRaceBounce: function(){
return Session.get("newUserExperienceStep") === 1;
},
});
Template.journal.events({

View File

@@ -0,0 +1,12 @@
.newUserStepper {
height: 300px !important;
}
.newUserStepper paper-step .invalid-step-message {
color: #d13b2e;
visibility: hidden;
}
.newUserStepper paper-step[invalid] .invalid-step-message {
visibility: visible;
}

View File

@@ -0,0 +1,26 @@
<template name="newUserStepper">
<paper-stepper linear selected="0" class="newUserStepper">
<paper-step id="step0" label="Add a feature">
<p>
To get started, add a feature
</p>
</paper-step>
<paper-step id="step1" label="Add an effect">
<p>
Add a racial effect to set your speed
</p>
</paper-step>
<paper-step id="step2" label="See the effect in action">
<p>
View your speed stat
</p>
</paper-step>
<paper-step id="step3" label="Finish">
If you get stuck, be sure to check out the guide, or ask for help using the feedback form.
</paper-step>
</paper-stepper>
</template>
<template name="newUserStepperPlaceholder">
<div style="height: 300px"></div>
</template>

View File

@@ -0,0 +1,41 @@
Template.newUserStepper.onRendered(function(){
let stepper = this.find("paper-stepper");
this.autorun((c) => {
var step = Session.get("newUserExperienceStep");
var hasFeatures = Features.find({charId: this.data._id}).count() > 1
if (step === 0 && hasFeatures){
stepper.continue();
c.stop();
}
});
this.autorun((c) => {
var step = Session.get("newUserExperienceStep");
var hasEffect = !!Effects.find({
charId: this.data._id,
stat: "speed",
}).count();
if (step === 1 && hasEffect){
stepper.continue();
c.stop();
}
});
this.autorun((c) => {
var step = Session.get("newUserExperienceStep");
if (step === 2 && Session.get("viewedSpeed")){
stepper.continue();
c.stop();
}
});
});
Template.newUserStepper.events({
"paper-stepper-progressed paper-stepper": function(event, template){
const step = template.find("paper-stepper").selected;
Session.set("newUserExperienceStep", step);
},
"paper-stepper-completed paper-stepper": function(event, template){
Session.set("newUserExperienceStep", undefined);
Session.set("showNewUserExperience", undefined);
Characters.update(this._id, {$unset: {"settings.newUserExperience": 1}});
},
});

View File

@@ -0,0 +1,15 @@
.infoBox iron-icon {
color: #747474;
color: rgba(0,0,0,0.54);
height: 32px;
width: 32px;
margin-right: 12px;
}
.infoBox > div > p {
margin: 0;
}
.infoBox > div > p + p {
margin-top: 10px;
}

View File

@@ -0,0 +1,10 @@
<template name="infoBox">
<div class="layout horizontal center infoBox">
<div>
<iron-icon icon="info-outline"></iron-icon>
</div>
<div class="flex">
{{> Template.contentBlock}}
</div>
</div>
</template>

View File

@@ -49,6 +49,7 @@
"/custom_components/dicecloud-wrapper/dicecloud-wrapper.html",
"/custom_components/paper-checkbox/paper-checkbox.html",
"/custom_components/paper-diff-slider/paper-diff-slider.html",
"/custom_components/paper-stepper/paper-stepper.html",
"/custom_components/app-theme.html"
]
}

View File

@@ -2,7 +2,7 @@ AccountsTemplates.configure({
//behaviour
confirmPassword: true,
enablePasswordChange: true,
enforceEmailVerification: true,
enforceEmailVerification: false,
overrideLoginErrors: false,
sendVerificationEmail: true,
lowercaseUsername: true,
@@ -21,35 +21,35 @@ AccountsTemplates.configure({
AccountsTemplates.configureRoute("changePwd", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("enrollAccount", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("forgotPwd", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("resetPwd", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("signIn", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("signUp", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("verifyEmail", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
AccountsTemplates.configureRoute("resendVerificationEmail", {
template: "titledAtForm",
layoutTemplate: 'layout',
layoutTemplate: "layout",
});
if (Meteor.isServer){

View File

@@ -0,0 +1,45 @@
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../../../components/polymer/polymer.html">
<link rel="import" href="../../../components/neon-animation/neon-animation-behavior.html">
<link rel="import" href="../../../components/neon-animation/web-animations.html">
<script>
Polymer({
is: 'fade-in-slide-from-left-animation',
behaviors: [
Polymer.NeonAnimationBehavior
],
configure: function(config) {
var node = config.node;
this._effect = new KeyframeEffect(node, [
{'transform': 'translateX(-100%)', 'opacity': '0'},
{'transform': 'translateX(-50%)', 'opacity': '0'},
{'transform': 'none', 'opacity': '1'}
], this.timingFromConfig(config));
if (config.transformOrigin) {
this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin);
} else {
this.setPrefixedProperty(node, 'transformOrigin', '0 50%');
}
return this._effect;
}
});
</script>

View File

@@ -0,0 +1,45 @@
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../../../components/polymer/polymer.html">
<link rel="import" href="../../../components/neon-animation/neon-animation-behavior.html">
<link rel="import" href="../../../components/neon-animation/web-animations.html">
<script>
Polymer({
is: 'fade-in-slide-from-right-animation',
behaviors: [
Polymer.NeonAnimationBehavior
],
configure: function(config) {
var node = config.node;
this._effect = new KeyframeEffect(node, [
{'transform': 'translateX(100%)', 'opacity': '0'},
{'transform': 'translateX(50%)', 'opacity': '0'},
{'transform': 'none', 'opacity': '1'}
], this.timingFromConfig(config));
if (config.transformOrigin) {
this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin);
} else {
this.setPrefixedProperty(node, 'transformOrigin', '0 50%');
}
return this._effect;
}
});
</script>

View File

@@ -0,0 +1,45 @@
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../../../components/polymer/polymer.html">
<link rel="import" href="../../../components/neon-animation/neon-animation-behavior.html">
<link rel="import" href="../../../components/neon-animation/web-animations.html">
<script>
Polymer({
is: 'fade-out-slide-left-animation',
behaviors: [
Polymer.NeonAnimationBehavior
],
configure: function(config) {
var node = config.node;
this._effect = new KeyframeEffect(node, [
{'transform': 'none', 'opacity': '1'},
{'transform': 'translateX(-50%)', 'opacity': '0'},
{'transform': 'translateX(-100%)', 'opacity': '0'},
], this.timingFromConfig(config));
if (config.transformOrigin) {
this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin);
} else {
this.setPrefixedProperty(node, 'transformOrigin', '0 50%');
}
return this._effect;
}
});
</script>

View File

@@ -0,0 +1,45 @@
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../../../components/polymer/polymer.html">
<link rel="import" href="../../../components/neon-animation/neon-animation-behavior.html">
<link rel="import" href="../../../components/neon-animation/web-animations.html">
<script>
Polymer({
is: 'fade-out-slide-right-animation',
behaviors: [
Polymer.NeonAnimationBehavior
],
configure: function(config) {
var node = config.node;
this._effect = new KeyframeEffect(node, [
{'transform': 'none', 'opacity': '1'},
{'transform': 'translateX(50%)', 'opacity': '1'},
{'transform': 'translateX(100%)', 'opacity': '0'}
], this.timingFromConfig(config));
if (config.transformOrigin) {
this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin);
} else {
this.setPrefixedProperty(node, 'transformOrigin', '0 50%');
}
return this._effect;
}
});
</script>

View File

@@ -0,0 +1,48 @@
{
"name": "paper-stepper",
"version": "2.0-beta.5",
"authors": [
"Zecat <jullienfelix@gmail.com>"
],
"description": "Material paper-stepper element.",
"keywords": [
"web-component",
"polymer",
"seed"
],
"main": "paper-stepper.html",
"license": "http://polymer.github.io/LICENSE.txt",
"homepage": "https://github.com/zecat/paper-stepper/",
"ignore": [
"/.*",
"/test/"
],
"dependencies": {
"polymer": "Polymer/polymer#^1.2.0",
"paper-button": "PolymerElements/paper-button#^1.0.11",
"iron-icons": "PolymerElements/iron-icons#^1.1.3",
"paper-styles": "PolymerElements/paper-styles#^1.1.4",
"paper-ripple": "PolymerElements/paper-ripple#^1.0.5",
"iron-selector": "PolymerElements/iron-selector#^1.3.0",
"iron-icon": "PolymerElements/iron-icon#^1.0.8",
"iron-flex-layout": "PolymerElements/iron-flex-layout#^1.3.1",
"neon-animation": "PolymerElements/neon-animation#^1.1.1",
"iron-validatable-behavior": "PolymerElements/iron-validatable-behavior#^1.0.5",
"iron-collapse": "PolymerElements/iron-collapse#^1.2.0"
},
"devDependencies": {
"paper-input": "PolymerElements/paper-input#^1.1.10",
"paper-material": "PolymerElements/paper-material#^1.0.6",
"iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
"web-component-tester": "*",
"iron-form": "PolymerElements/iron-form#^1.0.16",
"iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.2.2",
"paper-toggle-button": "PolymerElements/paper-toggle-button#^1.2.0",
"app-layout": "PolymerElements/app-layout#^0.10.2",
"paper-menu": "PolymerElements/paper-menu#^1.2.2",
"iron-scroll-spy": "Zecat/iron-scroll-spy#^2.1.0",
"paper-item": "PolymerElements/paper-item#^1.2.1",
"paper-toast": "PolymerElements/paper-toast#^1.3.0",
"paper-checkbox": "PolymerElements/paper-checkbox#^1.4.0"
}
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 225 126" enable-background="new 0 0 225 126" xml:space="preserve">
<g id="background" display="none">
<rect display="inline" fill="#B0BEC5" width="225" height="126"/>
</g>
<g id="label">
</g>
<g id="art">
<g id="ic_x5F_add_x0D_">
</g>
<circle cx="78" cy="98" r="4"/>
<circle cx="171" cy="90" r="4"/>
<circle cx="132" cy="61" r="4"/>
<circle cx="53" cy="61" r="4"/>
<circle cx="126" cy="28" r="4"/>
<circle cx="91" cy="67" r="4"/>
<circle cx="132" cy="90" r="4"/>
<circle cx="65" cy="32" r="4"/>
<path d="M77.7,99.4L51.9,61.1L64.3,31l64.1-4.2L92.6,66.7l16.1,9l23.3-16L174,91h-42.3l-23-12.9L77.7,99.4z M54.1,60.9l24.1,35.7
L106.8,77l-17.4-9.8l34.2-38.1L65.7,33L54.1,60.9z M132.3,89H168l-36-26.8l-21.4,14.6L132.3,89z"/>
</g>
<g id="Guides">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,424 @@
<link rel="import" href="../../components/iron-validatable-behavior/iron-validatable-behavior.html">
<link rel="import" href="../../components/paper-item/paper-item-behavior.html">
<link rel="import" href="../../components/paper-behaviors/paper-ripple-behavior.html">
<link rel="import" href="../../components/neon-animation/neon-animatable-behavior.html">
<link rel="import" href="../../components/paper-styles/default-theme.html">
<link rel="import" href="step-horizontal-label.html">
<link rel="import" href="step-vertical.html">
<link rel="import" href="../../components/polymer/polymer.html">
<!--
Missing Doc
@element paper-step
@demo demo/index.html
@hero hero.svg
-->
<dom-module id="paper-step">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
outline: 0;
}
:host(:not([vertical])) {
overflow: hidden;
@apply(--layout);
@apply(--layout-flex);
}
:host(:not([vertical])[opened]){
overflow: visible;
}
:host(:not([opened]):not(.neon-animating)) #slideshowViewport{
display: none;
}
#slideshowViewport {
position: absolute;
bottom: 42px;
left: 0;
right: 0;
overflow: hidden;
}
#contentWrapper {
padding: 16px 0 16px 16px;
@apply(--layout-scroll);
@apply(--layout-fit);
pointer-events: auto;
}
:host(:focus:not([opened]):not([saved])) step-horizontal-label ::content #badge {
background: var(--paper-step-selectable-hovered-badge-background, --paper-grey-500);
}
</style>
<!-- horizontal step layout -->
<template is="dom-if" if="[[!vertical]]">
<step-horizontal-label id="horizontalStepLabel" editable="[[editable]]" label="[[label]]"
alternative-label="[[_alternativeLabel]]" optional="[[optional]]"
opened="[[opened]]" selectable="[[selectable]]" index="[[index]]"
saved="[[saved]]" stepper-data="[[_stepperData]]">
</step-horizontal-label>
<div id="slideshowViewport">
<div id="contentWrapper">
</div>
</div>
</template>
<!-- vertical step layout -->
<template is="dom-if" if="[[vertical]]">
<step-vertical id="verticalStepLabel" editable="[[editable]]" label="[[label]]"
optional="[[optional]]" opened="[[opened]]"
selectable="[[selectable]]" stepper-data="[[_stepperData]]" index="[[index]]"
saved="[[saved]]" _attr-for-primary-button-text="[[_attrForPrimaryButtonText]]"
can-skip="[[_canSkip]]" has-back-step="[[_hasBackStep]]">
</step-vertical>
</template>
</template>
<script>
Polymer({
is: 'paper-step',
behaviors: [
Polymer.IronValidatableBehavior,
Polymer.NeonAnimatableBehavior,
Polymer.PaperItemBehavior,
Polymer.PaperRippleBehavior
],
/**
* Fired when the step has been saved.
*
* @event paper-step-saved
*/
/**
* Fired when the step has been updated
*
* @event paper-step-updated
*/
properties: {
/**
* MISSING Doc
*/
saved: {
type: Boolean,
value: false,
notify: true,
readOnly: true
},
/**
* Missing Doc
*/
editable: {
type: Boolean,
value: false
},
/**
* Missing Doc
*/
index: {
type: Number,
notify: true,
readOnly: true
},
/**
* Missing Doc
*/
_previousSaved: {
type: Boolean,
readOnly: true
},
/**
* Missing Doc
*/
optional: {
type: Boolean,
value: false
},
/**
* Missing Doc
*/
selectable: {
type: Boolean,
computed: '_computeSelectable(_stepperData.linear, saved, editable, _previousSaved)',
reflectToAttribute: true,
notify: true
},
/**
* Missing Doc
*/
disabled: {
computed: '_computeDisabled(selectable)'
},
/**
* Missing Doc
*/
label: {
type: String,
value: ''
},
/**
* Missing Doc
*/
opened: {
type: Boolean,
value: false
},
/**
* Missing Doc
*/
animationConfig: {
readOnly: true
},
/**
* Missing Doc
*/
entryAnimation: {
readOnly: true
},
/**
* Missing Doc
*/
exitAnimation: {
readOnly: true
},
/**
* Missing Doc
*/
vertical: {
type: Boolean,
readOnly: true,
reflectToAttribute: true
},
/**
* Missing Doc
*/
horizontalHigherEntryAnimation: {
type: String
},
/**
* Missing Doc
*/
horizontalHigherExitAnimation: {
type: String
},
/**
* Missing Doc
*/
horizontalLowerEntryAnimation: {
type: String
},
/**
* Missing Doc
*/
horizontalLowerExitAnimation: {
type: String
},
/**
* Missing Doc
*/
_alternativeLabel: {
type: Boolean,
readOnly: true
},
/**
* Missing Doc
*/
_optionalText: {
type: Boolean,
readOnly: true
},
/**
* Missing Doc
*/
_attrForPrimaryButtonText: {
type: String,
readOnly: true
},
/**
* A reference to the parent stepper
*/
_stepper: {
type: Object,
readOnly: true
},
_stepperData: {
type: Object,
readOnly: true
},
_canSkip: {
type: Boolean,
readOnly: true
},
_hasBackStep: {
type: Boolean,
readOnly: true
}
},
listeners: {
'paper-step-vertical-skip-tapped': 'skip',
'paper-step-vertical-back-tapped': 'back',
'paper-step-vertical-continue-tapped': 'continue',
'tap': '_tapHandler'
},
observers: [
'_toggleClassPosition(index, _stepperData.stepNumber, vertical)',
'_updateSlideshowViewportTop(optional, _alternativeLabel, vertical)',
'_verticalChange(vertical)',
'_focusedChanged(receivedFocusFromKeyboard)',
'_labelElementChanged(_labelElement)'
],
_focusedChanged: function(receivedFocusFromKeyboard) {
if (receivedFocusFromKeyboard) {
this.ensureRipple();
// generate a ripple effect from the center of the badge
var badge = Polymer.dom(this._rippleContainer).querySelector('#badge');
var badgePos = badge.getBoundingClientRect();
var rippleX = badgePos.left + 12;
var rippleY = badgePos.top + 12;
this._ripple.downAction({detail: {x: rippleX, y: rippleY}});
}
if (this.hasRipple()) {
this._ripple.holdDown = receivedFocusFromKeyboard;
}
},
_tapHandler: function(e) {
var rootTarget = Polymer.dom(e).rootTarget;
if (rootTarget !== this && rootTarget !== this._rippleContainer) {
e.stopImmediatePropagation();
}
},
/**
* Missing Doc
*/
skip: function() {
// would it be better to send an event?
this._stepper.progress();
},
/**
* Missing Doc
*/
back: function() {
// would it be better to send an event?
this._stepper.back();
},
/**
* Mark the step as saved and fire `paper-step-saved` if it is valid.
* @return {Boolean} True if the step has been saved.
*/
save: function() {
if ((!this.saved || this.editable) && this.validate()) {
if (this.saved) {
this.fire('paper-step-updated');
} else {
this._setSaved(true);
this.fire('paper-step-saved');
}
return true;
}
return false;
},
/**
* Atempte to save the step and select and to progress into the stepper.
* @return {Boolean} True if the step is valid for saving.
*/
continue: function() {
if (this.save()) {
// would it be better to send an event?
this._stepper.progress();
return true;
}
return false;
},
_removeAnimatingClass: function() {
if (this._animationCanceled) {
this._set_animationCanceled(false);
} else {
this.toggleClass('neon-animating', false);
}
},
_cancelAnimation: function() {
this._set_animationCanceled(true);
this.toggleClass('neon-animating', false);
},
_updateSlideshowViewportTop: function(optional, _alternativeLabel, vertical) {
if (!vertical) {
this.async(function() {
this.$$('#slideshowViewport').style.top = this.clientHeight+'px';
this.fire('step-horizontal-label-resize');
})
}
},
_toggleClassPosition: function(index, stepNumber, vertical) {
this.async(function() {
var stepLabel = this.$$(vertical ? '#verticalStepLabel' : '#horizontalStepLabel');
this.toggleClass('first-step', (index == 1), stepLabel);
this.toggleClass('last-step', (index == stepNumber), stepLabel);
});
},
_updateAnimationConfig: function() {
/* TODO: call this method when horizontalHigher/LowerEntry/ExitAnimation change */
var animatedNode = this.$$('#contentWrapper');
this._setAnimationConfig({
'higher-step-entry': {
node: animatedNode,
name: this.horizontalHigherEntryAnimation
},
'higher-step-exit': {
node: animatedNode,
name: this.horizontalHigherExitAnimation
},
'lower-step-entry': {
node: animatedNode,
name: this.horizontalLowerEntryAnimation
},
'lower-step-exit': {
node: animatedNode,
name: this.horizontalLowerExitAnimation
}
});
},
_verticalChange: function(vertical) {
this.async(function() {
// move or create the content tag
Polymer.dom(this.$$(vertical ? '#verticalStepLabel' : '#contentWrapper')).appendChild(this.$$('content') || this.create('content'));
// reset the ripple
this._ripple = false;
this._rippleContainer = vertical ? this.$$('#verticalStepLabel').$.label : this.$$('#horizontalStepLabel').$.label;
if (!vertical) {
this._updateAnimationConfig();
}
}.bind(this));
},
_computeSelectable: function(linear, saved, editable, previousSaved) {
// TODO: factorize the expression
return (!linear || previousSaved) && (!saved || editable) || (editable && saved);
},
_computeDisabled: function(selectable) {
// disabled is used by IronMenuBehavior in paper-stepper
// TODO: remove selectable attribute and use disabled instead
return !selectable;
}
});
</script>
</dom-module>

View File

@@ -0,0 +1,735 @@
<link rel="import" href="../../components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../../components/iron-selector/iron-selectable.html">
<link rel="import" href="../../components/iron-menu-behavior/iron-menu-behavior.html">
<link rel="import" href="../../components/neon-animation/neon-animation-runner-behavior.html">
<link rel="import" href="../../components/paper-button/paper-button.html">
<link rel="import" href="../../components/paper-styles/default-theme.html">
<link rel="import" href="../../components/paper-styles/shadow.html">
<link rel="import" href="../../components/iron-collapse/iron-collapse.html">
<link rel="import" href="../../components/iron-resizable-behavior/iron-resizable-behavior.html">
<link rel="import" href="../../components/polymer/polymer.html">
<link rel="import" href="animations/fade-in-slide-from-right-animation.html">
<link rel="import" href="animations/fade-out-slide-right-animation.html">
<link rel="import" href="animations/fade-in-slide-from-left-animation.html">
<link rel="import" href="animations/fade-out-slide-left-animation.html">
<link rel="import" href="paper-step.html">
<!--
Missing Doc
## Styling
The default color values respect the material specifications. For basic configuration, just defines the `--primary-color` for your app.
The stepper has a complexe interface so many custom properties are available, use them this way :
```html
<style is="custom-style">
paper-stepper {
--paper-stepper-custom-property: value;
}
paper-step {
--paper-step-custom-property: value;
}
</style>
<paper-stepper>
<paper-step></paper-step>
<paper-step></paper-step>
</paper-stepper>
```
Note, for sizing the stepper and steps depending on the layout, use the following instead of setting 'height' and 'max-height' :
- `--paper-stepper-horizontal-opened-height`
- `--paper-stepper-vertical-max-height`
- `--paper-vertical-step-max-height`
### Stepper
Custom property | Description | Default
------|-------------|----------
`--paper-stepper-horizontal-opened-height` | The horizontal opened stepper height | 450px
`--paper-stepper-horizontal-opening-transition-duration`| The horizontal stepper opening transition duration (ms) | 500
`--paper-stepper-vertical-max-height`| The vertical stepper max height so it has a scrollable area | undefined
### Step
Custom property | Description | Default
------|-------------|----------
`--paper-step-connector-line-background` | The connector lines background color. | `--divider-color`
`--paper-step-label-hover-background` | The steps label background when hovered. | rgba(0,0,0,0.04)
`--paper-step-ink-color` | The steps ripple effect ink color. | `--divider-color`
### Step text label
Custom property | Description | Default
------|-------------|----------
`--paper-step-disabled-label-text-color` | The no-selectable steps label text color. | `--paper-grey-400`
`--paper-step-selectable-label-text-color` | The selectable steps label text color. | `--paper-grey-600`
`--paper-step-opened-label-text-color` | The opened steps label text color. | `--light-theme-text-color`
`--paper-step-label-text-font-size` | The steps label text font size. | 14px
`--paper-step-label-optional-text-font-size` | The steps label optional text font size. | 12px
### Step badge
Custom property | Description | Default
------|-------------|----------
`--paper-step-badge-background` | The badge background. | `--paper-grey-300`
`--paper-step-badge-color` | The badge color. | `--dark-theme-text-color`
`--paper-step-badge-icon-width` | The badge icon width. | 16px
`--paper-step-badge-icon-height` | The badge icon height. | 16px
`--paper-step-opened-badge-background` | The opened steps badge background. | `--primary-color`
`--paper-step-saved-badge-background` | The saved steps badge background. | `--primary-color`
`--paper-step-selectable-hovered-badge-background` | The no-opened no-saved selectable steps badge background. | `--paper-grey-500`
### Vertical step
Custom property | Description | Default
------|-------------|----------
`--paper-vertical-step-continue-button-background` | The continue button background. | `--primary-color`
`--paper-vertical-step-continue-button-color` | The continue button background. | `--dark-theme-text-color`
`--paper-vertical-step-max-height` | The unrolled step content max-height. | 400px
`--paper-vertical-step-transition-duration` | The step opening transition duration | 500ms
@element paper-stepper
@demo demo/index.html
@hero hero.svg
-->
<dom-module id="paper-stepper">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
@apply(--layout-vertical);
@apply(--shadow-elevation-2dp);
background: white;
border-radius: 1px;
}
#verticalResponsiveWidth {
width: var(--paper-stepper-vertical-responsive-width, 0px);
visibility: hidden;
}
/* Horizontal layout styles */
:host([vertical]) {
padding: 4px 0;
@apply(--layout-scroll);
max-height: var(--paper-stepper-vertical-max-height);
}
:host(:not([vertical])) {
position: relative;
/* Note: this variable is updated by the stepper itself, don't use it on user side */
height: var(--label-wrapper-height);
max-height: var(--label-wrapper-height);
transition: max-height var(--paper-stepper-horizontal-opening-transition-duration, 300ms), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
:host(:not([vertical]):not([opened]):not(.collapsing)) {
@apply(--shadow-none);
}
:host(:not([vertical])[opened]) {
max-height: var(--paper-stepper-horizontal-opened-height, 450px);
}
:host(:not([vertical])[opened]), :host(:not([vertical]).collapsing) {
height: var(--paper-stepper-horizontal-opened-height, 450px);
}
:host(:not([vertical])) #content-wrapper {
@apply(--shadow-elevation-2dp);
@apply(--layout-horizontal);
@apply(--layout-flex-none);
@apply(--layout-justified);
background: var(--paper-stepper-horizontal-label-wrapper-background, --primary-background-color);
border-radius: 1px;
overflow: hidden;
}
.flex {
@apply(--layout-flex);
}
#buttonsWrapper {
@apply(--layout-horizontal);
overflow: hidden;
}
#continueButton:not([disabled]) {
color: var(--primary-color);
}
paper-button[disabled] {
background-color: transparent;
}
[hidden] {
display: none;
}
:host(:not[vertical]) #content-wrapper ::content paper-step[opened] {
overflow: visible;
}
</style>
<!-- Hidden block with width equals to the responsive threshold -->
<div id="verticalResponsiveWidth"></div>
<!-- This wrapper contains the horizontal stepper header or the full vertical stepper -->
<div id="content-wrapper">
<content select="[[selectable]]"></content>
</div>
<!-- Slideshow and buttons area for horizontal layout -->
<template is="dom-if" if="[[!vertical]]">
<div class="flex"></div>
<div id="buttonsWrapper">
<paper-button hidden$="[[!hasBackButton]]" disabled=[[!_hasBackStep]] on-tap="back">[[backText]]</paper-button>
<span class="flex"></span>
<paper-button hidden$="[[!hasSkipButton]]" disabled="[[!_canSkip]]" on-tap="progress">[[skipText]]</paper-button>
<!--
<paper-button id="continueButton" on-tap="continue">{{_choosePrimaryButtonText(_attrForSelectedStepPrimaryButtonText, finishText, continueText, updateText)}}</paper-button>
-->
</div>
</template>
</template>
<script>
'use strict';
Polymer({
is: 'paper-stepper',
behaviors: [
Polymer.IronMenuBehavior,
Polymer.NeonAnimationRunnerBehavior,
Polymer.IronResizableBehavior
],
/**
* Fired when the stepper progress.
*
* @event paper-stepper-progressed
*/
/**
* Fired when all the steps are saved.
*
* @event paper-stepper-completed
*/
properties: {
opened: {
type: Boolean,
computed: '_computeOpened(_selectedIndex)',
observer: '_openedChanged',
notify: true,
reflectToAttribute: true
},
alternativeLabel: {
type: Boolean,
value: false
},
vertical: {
type: Boolean,
value: false,
reflectToAttribute: true
},
backText: {
type: String,
value: 'BACK'
},
finishText: {
type: String,
value: 'FINISH'
},
continueText: {
type: String,
value: 'CONTINUE'
},
skipText: {
type: String,
value: 'SKIP'
},
optionalText: {
type: String,
value: 'Optional'
},
updateText: {
type: String,
value: 'UPDATE'
},
linear: {
type: Boolean,
value: false
},
completed: {
type: Boolean,
value: false,
notify: true,
computed: '_computeCompleted(stepNumber, savedStepNumber)'
},
hasSkipButton: {
type: Boolean,
value: false
},
hasBackButton: {
type: Boolean,
value: false
},
stepNumber: {
type: Number,
notify: true,
computed: '_computeStepNumber(items.length)'
},
savedStepNumber: {
type: Number,
notify: true,
readOnly: true
},
selectedAttribute: {
value: 'opened',
readOnly: true
},
/**
* Note: if you decide to change this attribute, take care to only include `<paper-step>` elements in your `<paper-stepper>`
*/
selectable: {
value: 'paper-step'
},
/**
* Multi mode is not allowed for now in paper-stepper.
*/
mutli: {
value: false,
readOnly: true
},
responsiveCheckFrequence: {
type: Number,
value: 200
},
animateInitialSelection: {
type: Boolean,
value: false
},
horizontalHigherEntryAnimation: {
type: String,
value: 'fade-in-slide-from-right-animation'
},
horizontalHigherExitAnimation: {
type: String,
value: 'fade-out-slide-right-animation'
},
horizontalLowerEntryAnimation: {
type: String,
value: 'fade-in-slide-from-left-animation'
},
horizontalLowerExitAnimation: {
type: String,
value: 'fade-out-slide-left-animation'
},
_skipStepIndex: {
type: Number,
computed: '_compute_skipStepIndex(_selectedIndex)'
},
_canSkip: {
type: Boolean,
notify: true,
computed: '_isntNull(_skipStepIndex)'
},
_backStepIndex: {
type: Number,
computed: '_compute_backStepIndex(_selectedIndex)'
},
_hasBackStep: {
type: Boolean,
computed: '_isntNull(_backStepIndex)'
},
_selectedIndex: {
type: Number,
observer: '_selectedIndexChanged',
readOnly: true,
value: -1
},
_attrForSelectedStepPrimaryButtonText: {
type: String,
computed: '_compute__attrForSelectedStepPrimaryButtonText(_selectedIndex, stepNumber)'
},
_previousAnimatedStep: {
type: Object,
value: null,
readOnly: true
},
_previousSelected: {
type: Object,
readOnly: true
}
},
keyBindings: {
'left': '_onLeftKey',
'right': '_onRightKey'
},
listeners: {
'iron-items-changed': '_initializeSteps',
'paper-step-saved': '_stepSaved',
'transitionend': '_transitionEnd',
'step-horizontal-label-resize': '_updateStepperClosedMaxHeight',
'iron-resize': '_resizeHandler',
'neon-animation-finish': '_onNeonAnimationFinish'
},
observers: [
'_forwardCanSkip(_canSkip, selectedItem)',
'_forwardHasBackStep(_hasBackStep, selectedItem)',
'_forwardVertical(vertical)',
'_forwardAlternativeLabel(alternativeLabel)',
'_forwardStepperData(linear, backText, optionalText, finishText, continueText, skipText, updateText, hasSkipButton, hasBackButton)'
],
attached: function() {
this._responsiveCheck();
},
/**
* Missing Doc
*/
back: function() {
this.selectIndex(this._backStepIndex);
},
/**
* @return {Boolean} Try to continue the current step (if no step opened, use the first one).
*/
continue: function() {
if (this.selectedItem) {
if (this.selectedItem.save()) {
this.progress();
}
}
},
/**
* Loops around the steps from the current (if no step opened, from the first one)
* in order to open the next selectable unsaved step. Returns true if a step has been opened.
*/
progress: function() {
if (!this.stepNumber) {
return false;
}
if (this.completed) {
this.selected = null;
return true;
}
for (var i = (this._selectedIndex+1)%this.stepNumber; i != this._selectedIndex; i = (i+1)%this.stepNumber) {
if (this.items[i].selectable && !this.items[i].saved) {
this.selectIndex(i);
this.fire('paper-stepper-progressed');
return true;
}
}
return false;
},
/* Deselect and set the steps as unsaved*/
reset: function() {
this._setSavedStepNumber(0);
this.selected = null;
if (!this.items.length) {
return;
}
this.items.map(function(step) {
step._setSaved(false);
step._set_previousSaved(false);
});
this.items[0]._set_previousSaved(true);
},
get _isRTL() {
return window.getComputedStyle(this)['direction'] === 'rtl';
},
_onLeftKey: function(event) {
if (this._isRTL) {
this._focusNext();
} else {
this._focusPrevious();
}
event.detail.keyboardEvent.preventDefault();
},
_onRightKey: function(event) {
if (this._isRTL) {
this._focusPrevious();
} else {
this._focusNext();
}
event.detail.keyboardEvent.preventDefault();
},
/**
* Work around: Override the method from IronSelectableBehavior to only allow the selection of selectable step. https://github.com/PolymerElements/iron-selector/issues/99
*/
_selectSelected: function(selected) {
var item = this._valueToItem(this.selected);
if (item) {
var selectable = item.selectable;
if (selectable == undefined) {
// if selectable isn't define it means the step is not yet ready for selection
// and this method will be recalled by the initialization method.
return;
} else if (!selectable) {
//reset previous selected if non null and selectable or deselect
if (this._previousSelected && this._previousSelected.selectable) {
this.selected = this._valueForItem(this.previousSelected);
} else {
this.selected = null;
}
this._set_previousSelected(null);
return;
}
}
this._selection.select(item);
this._set_previousSelected(item);
this._set_selectedIndex(this.indexOf(item));
// Check for items, since this array is populated only when attached
// Since Number(0) is falsy, explicitly check for undefined
if (this.fallbackSelection && this.items.length && (this._selection.get() === undefined)) {
this.selected = this.fallbackSelection;
}
},
_updateStepperClosedMaxHeight: function() {
this.debounce('updateStepperClosedMaxHeight', function() {
this.customStyle['--label-wrapper-height'] = this.$$('#content-wrapper').clientHeight + 'px';
this.updateStyles('--label-wrapper-height');
});
},
_openedChanged: function(newValue, oldValue) {
if (!this.vertical && oldValue != undefined) {
this.toggleClass('collapsing', true);
}
},
_transitionEnd: function(e) {
// check to ignore event fired by paper-ripple
if (e.propertyName == 'max-height') {
this.toggleClass('collapsing', false);
}
},
_computeOpened: function(_selectedIndex) {
return _selectedIndex >= 0;
},
_stepSaved: function(e) {
var previousStep = this.items[this.indexOf(e.target)+1];
if (previousStep) {
previousStep._set_previousSaved(true);
}
this._setSavedStepNumber(this.savedStepNumber+1);
},
_forwardVertical: function(vertical) {
if (this.stepNumber) {
this.items.map(function(step) {
step._setVertical(vertical);
});
}
this.setAttribute('role', vertical ? 'menu': 'menubar');
},
_forwardStepperData: function(linear, backText, optionalText, finishText, continueText, skipText, updateText, hasSkipButton, hasBackButton) {
if (this.stepNumber) {
this.items.map(function(step) {
step._set_stepperData({
linear: linear,
backText: backText,
optionalText: optionalText,
finishText: finishText,
continueText: continueText,
skipText: skipText,
updateText: updateText,
hasSkipButton: hasSkipButton,
hasBackButton: hasBackButton,
stepNumber: this.stepNumber
});
}.bind(this));
}
},
_forwardAlternativeLabel: function(alternativeLabel) {
if (this.stepNumber) {
this.items.map(function(step) {
step._set_alternativeLabel(alternativeLabel);
});
}
},
_computeStepNumber: function(length) {
return length;
},
_selectedIndexChanged: function(newValue, oldValue) {
if (!this.vertical && newValue >=0 && oldValue >= 0) {
var oldStep = this.items[oldValue], newStep = this.items[newValue];
if (newStep.classList.contains('neon-animating')) {
this.cancelAnimation();
}
if (this._previousAnimatedStep && this._previousAnimatedStep.classList.contains('neon-animating')) {
this.cancelAnimation();
this.toggleClass('neon-animating', false, this._previousAnimatedStep);
}
var forward = newValue - oldValue > 0;
this.animationConfig = {
'new-step-entry': {
animatable: newStep,
type: forward ?
newStep.horizontalHigherEntryAnimation && 'higher-step-entry' :
newStep.horizontalLowerEntryAnimation && 'lower-step-entry'
},
'old-step-exit': {
animatable: oldStep,
type: forward ?
oldStep.horizontalLowerExitAnimation && 'lower-step-exit' :
oldStep.horizontalHigherExitAnimation && 'higher-step-exit'
}
};
if (this.animationConfig['new-step-entry'].type) {
this.playAnimation('new-step-entry', {step: newStep});
this.toggleClass('neon-animating', true, newStep);
}
if (this.animationConfig['old-step-exit'].type) {
this.playAnimation('old-step-exit', {step: oldStep});
this.toggleClass('neon-animating', true, oldStep);
}
this._set_previousAnimatedStep(oldStep);
}
},
_onNeonAnimationFinish: function(event) {
var step = event.detail.step;
if (step) {
this.toggleClass('neon-animating', false, step);
}
},
_forwardCanSkip: function(_canSkip, selectedItem) {
selectedItem._set_canSkip(_canSkip);
},
_forwardHasBackStep: function(_hasBackStep, selectedItem) {
selectedItem._set_hasBackStep(_hasBackStep);
},
_compute__attrForSelectedStepPrimaryButtonText: function(selectedIndex) {
/* TODO: compute from selectedItem when https://github.com/PolymerElements/iron-selector/issues/118 is fixed*/
if (selectedIndex < 0) {
return null;
}
var _attrForPrimaryButtonText = this.selectedItem.saved ? 'updateText' :
( (this.stepNumber - this.savedStepNumber) == 1 ? 'finishText' : 'continueText' );
this.selectedItem._set_attrForPrimaryButtonText(_attrForPrimaryButtonText);
return _attrForPrimaryButtonText;
},
_initializeSteps: function() {
var savedStepNumber = 0;
var data = {
linear: this.linear,
backText: this.backText,
optionalText: this.optionalText,
finishText: this.finishText,
continueText: this.continueText,
skipText: this.skipText,
updateText: this.updateText,
hasSkipButton: this.hasSkipButton,
hasBackButton: this.hasBackButton,
stepNumber: this.stepNumber
};
this.items.map(function(step, i) {
if (this.horizontalHigherEntryAnimation && !step.horizontalHigherEntryAnimation) {
step.horizontalHigherEntryAnimation = this.horizontalHigherEntryAnimation;
}
if (this.horizontalHigherExitAnimation && !step.horizontalHigherExitAnimation) {
step.horizontalHigherExitAnimation = this.horizontalHigherExitAnimation;
}
if (this.horizontalLowerEntryAnimation && !step.horizontalLowerEntryAnimation) {
step.horizontalLowerEntryAnimation = this.horizontalLowerEntryAnimation;
}
if (this.horizontalLowerExitAnimation && !step.horizontalLowerExitAnimation) {
step.horizontalLowerExitAnimation = this.horizontalLowerExitAnimation;
}
step._setIndex(i + 1);
step._set_stepper(this);
step._setVertical(this.vertical);
step._set_alternativeLabel(this.alternativeLabel);
step._set_stepperData(data);
// true for index 0
step._set_previousSaved(!i);
if (step.saved) {
savedStepNumber++;
}
}.bind(this));
this._setSavedStepNumber(savedStepNumber);
// method from IronSelectableBehavior
this._updateSelected();
},
_compute_skipStepIndex: function(_selectedIndex) {
if (_selectedIndex >= 0 && !this.completed) {
for (var i=(_selectedIndex+1)%this.stepNumber; i!=_selectedIndex; i=(i+1)%this.stepNumber) {
if (this.items[i].selectable && !this.items[i].saved) {
return i;
}
}
}
return null;
},
_compute_backStepIndex: function(_selectedIndex) {
if (_selectedIndex >= 0) {
for (var i=_selectedIndex - 1; i >= 0; i--) {
if (this.items[i].selectable) {
return i;
}
}
}
return null
},
_isntNull: function(n) {
return n != null;
},
_computeCompleted: function(savedStepNumber, stepNumber) {
var completed = stepNumber == savedStepNumber;
if (completed) {
this.fire('paper-stepper-completed');
return true;
}
return false;
},
_choosePrimaryButtonText: function(_attrForSelectedStepPrimaryButtonText) {
return this[_attrForSelectedStepPrimaryButtonText];
},
_resizeHandler: function() {
this.debounce('paper-stepper-responsive-check', function() {
this._responsiveCheck();
}, this.responsiveCheckFrequence);
},
_responsiveCheck: function() {
var verticalResponsiveWidth = this.$.verticalResponsiveWidth.clientWidth;
if (verticalResponsiveWidth) {
this.vertical = !(this.clientWidth > verticalResponsiveWidth);
}
}
});
</script>
</dom-module>

View File

@@ -0,0 +1,124 @@
<link rel="import" href="../../components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../../components/iron-icons/iron-icons.html">
<link rel="import" href="../../components/iron-icons/editor-icons.html">
<link rel="import" href="../../components/paper-styles/color.html">
<link rel="import" href="../../components/paper-styles/typography.html">
<link rel="import" href="../../components/paper-styles/default-theme.html">
<link rel="import" href="../../components/polymer/polymer.html">
<link rel="import" href="step-label-behavior.html">
<link rel="import" href="step-label-shared-styles.html">
<dom-module id="step-horizontal-label">
<template>
<style include="step-label-shared-styles">
:host{
overflow: hidden;
}
:host([alternative-label]) {
@apply(--layout);
}
#textWrapper {
@apply(--layout-vertical);
padding-right: 8px;
overflow: hidden;
}
#textLabel, #optional {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
#badge {
margin: 0 8px;
}
#badgeWrapper, #textWrapper {
pointer-events: none;
/* to be above paper-ripple*/
z-index: 1;
}
:host(:not([alternative-label])) #label {
@apply(--layout-horizontal);
@apply(--layout-center);
height: 72px;
}
:host(.firstStep:not([alternative-label])) #badge {
margin-left: 24px;
}
:host(.lastStep:not([alternative-label])) #textWrapper {
padding-right: 24px;
}
:host(:not([alternative-label]):not(.first-step)) #label::before,
:host(:not([alternative-label]):not(.last-step)) #label::after,
:host([alternative-label]) #badgeWrapper::before,
:host([alternative-label]) #badgeWrapper::after {
height: 1px;
min-width: 16px;
background: var(--paper-step-connector-line-color, --divider-color);
@apply(--layout-flex);
content: '';
}
:host([alternative-label].first-step) #badgeWrapper::before,
:host([alternative-label].last-step) #badgeWrapper::after {
visibility: hidden;
}
:host([alternative-label]) #textWrapper{
padding: 0 16px;
@apply(--layout-vertical);
@apply(--layout-center);
}
:host([alternative-label]) #textLabel, :host([alternative-label]) #optional{
text-align: center;
@apply(--layout-self-stretch);
}
:host([alternative-label]) #label{
@apply(--layout-vertical);
@apply(--layout-self-stretch);
padding: 24px 0;
width: 100%;
}
:host([alternative-label]) #badgeWrapper {
@apply(--layout-horizontal);
@apply(--layout-center);
padding-bottom: 16px;
}
:host(.first-step:not([alternative-label])) #label {
padding-left: 16px;
}
:host(.last-step:not([alternative-label])) #label {
padding-right: 16px;
}
</style>
<!-- use a "label" wrapper to use the same shared css rules with step-vertical -->
<div id="label">
<div id="badgeWrapper">
<div id="badge">
<iron-icon hidden$="{{!_computeIsIconBadge(icon)}}" icon="{{icon}}"></iron-icon>
<span hidden$="{{_computeIsIconBadge(icon)}}">{{index}}</span>
</div>
</div>
<div id="textWrapper">
<span id="textLabel">[[label]]</span>
<template is="dom-if" if="[[optional]]">
<span id="optional">[[stepperData.optionalText]]</span>
</template>
</div>
</div>
</template>
<script>
Polymer({
is: 'step-horizontal-label',
behaviors: [
Stepper.StepLabelBehavior
],
properties: {
alternativeLabel: {
type: Boolean,
value: false,
reflectToAttribute: true
}
},
});
</script>
</dom-module>

View File

@@ -0,0 +1,58 @@
<link rel="import" href="../../components/polymer/polymer.html">
<script>
window.Stepper = window.Stepper || {};
/*
* @polymerBehavior Stepper.StepLabelBehavior
*/
Stepper.StepLabelBehavior = {
properties: {
icon: {
type: String,
computed: '_computeIcon(saved, editable)'
},
opened: {
type: Boolean,
reflectToAttribute: true
},
selectable: {
type: Boolean,
reflectToAttribute: true
},
editable: {
type: Boolean,
reflectToAttribute: true,
},
label: {
type: String,
notify: true
},
optional: {
type: Boolean,
notify: true
},
saved: {
type: Boolean,
reflectToAttribute: true
},
index: {
type: Number
},
stepperData: {
type: Object
}
},
_computeIcon: function(saved, editable) {
return saved ? ( editable ? 'editor:mode-edit' : 'done' ) : '';
},
_computeIsIconBadge: function(icon) {
return icon.length > 0;
}
};
</script>

View File

@@ -0,0 +1,74 @@
<link rel="import" href="../../components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../../components/paper-styles/default-theme.html">
<link rel="import" href="../../components/paper-styles/color.html">
<link rel="import" href="../../components/paper-styles/typography.html">
<link rel="import" href="../../components/polymer/polymer.html">
<dom-module id="step-label-shared-styles">
<template>
<style>
:host {
display: block;
box-sizing: border-box;
@apply(--paper-font-common-base);
@apply(--layout-flex);
}
#textWrapper {
color: var(--paper-step-disabled-label-text-color, --paper-grey-400);
}
#textLabel {
font-size: var(--paper-step-label-text-font-size, 14px);
}
#optional{
font-size: var(--paper-step-label-optional-text-font-size, 12px);
}
:host([selectable]) #textWrapper {
color: var(--paper-step-selectable-label-text-color, --paper-grey-600);
}
:host([opened]) #textLabel {
color: var(--paper-step-opened-label-text-color, --light-theme-text-color);
font-weight: 500;
}
#label {
cursor: default;
pointer-events: none;
/* For paper-ripple */
position: relative;
}
:host([selectable]) #label {
cursor: pointer;
pointer-events: auto;
}
#label:hover {
/* using alpha chanel for .connectorLine to grow dark */
background: var(--paper-step-label-hover-background, rgba(0,0,0,0.04));
}
#badge {
width: 24px;
height: 24px;
background: var(--paper-step-badge-background, --paper-grey-300);
border-radius: 50%;
color: var(--paper-step-badge-color, --dark-theme-text-color);
font-size: 12px;
@apply(--layout);
@apply(--layout-center-center);
}
#badge iron-icon {
--iron-icon-height: var(--paper-step-badge-icon-width, 16px);
--iron-icon-width: var(--paper-step-badge-icon-height, 16px);
}
:host([opened]) #badge {
background: var(--paper-step-opened-badge-background, --primary-color);
}
:host([saved]) #badge {
background: var(--paper-step-saved-badge-background, --primary-color);
}
:host([selectable]:not([opened]):not([saved])) #label:hover #badge {
background: var(--paper-step-selectable-hovered-badge-background, --paper-grey-500);
}
paper-ripple {
color: var(--paper-step-ink-color, --paper-grey-400);
}
</style>
</template>
</dom-module>

View File

@@ -0,0 +1,177 @@
<link rel="import" href="../../components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../../components/paper-button/paper-button.html">
<link rel="import" href="../../components/paper-styles/color.html">
<link rel="import" href="../../components/paper-styles/default-theme.html">
<link rel="import" href="../../components/paper-styles/typography.html">
<link rel="import" href="../../components/polymer/polymer.html">
<link rel="import" href="../../components/iron-icons/iron-icons.html">
<link rel="import" href="../../components/iron-icon/iron-icon.html">
<link rel="import" href="../../components/iron-icons/editor-icons.html">
<link rel="import" href="../../components/iron-collapse/iron-collapse.html">
<link rel="import" href="step-label-behavior.html">
<link rel="import" href="step-label-shared-styles.html">
<dom-module id="step-vertical">
<template>
<style include="step-label-shared-styles">
:host {
@apply(--layout-vertical);
}
#connectedBadge, #textWrapper {
pointer-events: none;
/* to be above paper-ripple*/
z-index: 1;
}
#collapse {
--iron-collapse-transition-duration: var(--paper-vertical-step-transition-duration, 500ms);
@apply(--layout-horizontal);
}
/**
* Content
*/
#connectedStep {
@apply(--layout-horizontal);
}
#contentConnectorLine {
width: 1px;
background: var(--divider-color, --paper-grey-300);
margin-left: 36px;
margin-right: 24px;
}
#stepWrapper {
@apply(--layout-flex);
/*should be 48px on large screen?*/
padding-right: 24px;
}
#paperStepWrapper {
max-height: calc(var(--paper-vertical-step-max-height, 400px) - 92px);
@apply(--layout-scroll);
}
/**
* Buttons
*/
#buttonsWrapper {
height: 48px;
@apply(--layout-horizontal);
@apply(--layout-center);
@apply(--layout-flex-none);
}
#buttonsWrapper > paper-button {
margin-right: 8px;
margin-left: 0;
}
#continueButton {
--paper-button: {
background: var(--paper-vertical-step-continue-button-background, --primary-color);
color: var(--paper-vertical-step-continue-button-color, --dark-theme-text-color);
};
}
/**
* Label
*/
#textWrapper {
@apply(--layout-vertical);
padding: 8px 0 8px 8px;
}
#label {
@apply(--layout-horizontal);
}
#connectedBadge {
@apply(--layout-vertical);
@apply(--layout-center);
margin-left: 24px;
}
#beforeConnectorLine, #afterConnectorLine {
width: 1px;
background: var(--paper-step-connector-line-color, --divider-color);
}
#beforeConnectorLine {
height: 10px;
}
#afterConnectorLine {
@apply(--layout-flex);
}
#badge {
margin: 8px 0;
}
:host(.first-step) #beforeConnectorLine, :host(.lastStep) #contentConnectorLine{
visibility: hidden;
}
:host(.last-step:not([opened])) #afterConnectorLine {
display: none;
}
</style>
<div id="label">
<div id="connectedBadge">
<div id="beforeConnectorLine"></div>
<div id="badge">
<iron-icon hidden$="{{!_computeIsIconBadge(icon)}}" icon="{{icon}}"></iron-icon>
<span hidden$="{{_computeIsIconBadge(icon)}}">{{index}}</span>
</div>
<div id="afterConnectorLine" class="connectorLine"></div>
</div>
<div id="textWrapper">
<span id="textLabel">[[label]]</span>
<template is="dom-if" if="[[optional]]">
<span id="optional">[[stepperData.optionalText]]</span>
</template>
</div>
</div>
<iron-collapse id="collapse" opened="[[opened]]">
<div id="contentConnectorLine"></div>
<div id="stepWrapper">
<div id="paperStepWrapper">
<content></content>
</div>
<div id="buttonsWrapper">
<paper-button id="continueButton" on-tap="continue">{{choosePrimaryButtonText(_attrForPrimaryButtonText, stepperData.continueText, stepperData.finishText, stepperData.updateText)}}</paper-button>
<paper-button id="backButton" disabled="[[!hasBackStep]]" on-tap="back" hidden$="[[!stepperData.hasBackButton]]">
[[stepperData.backText]]
</paper-button>
<paper-button id="skipButton" disabled="[[!canSkip]]" on-tap="skip" hidden$="[[!stepperData.hasSkipButton]]">
[[stepperData.skipText]]
</paper-button>
</div>
</div>
</iron-collapse>
</template>
<script>
Polymer({
is: 'step-vertical',
properties: {
canSkip: {
type: Boolean
},
_attrForPrimaryButtonText: {
type: String,
value: false
},
hasBackStep: Boolean
},
behaviors: [
Stepper.StepLabelBehavior
],
skip: function() {
this.fire('paper-step-vertical-skip-tapped');
},
back: function() {
this.fire('paper-step-vertical-back-tapped');
},
continue: function() {
this.fire('paper-step-vertical-continue-tapped');
},
choosePrimaryButtonText: function(_attrForPrimaryButtonText) {
return this.stepperData[_attrForPrimaryButtonText];
}
});
</script>
</dom-module>