Skip to content

Instantly share code, notes, and snippets.

@Jeet-Metapercept
Created July 19, 2017 22:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jeet-Metapercept/d7c427132fcc3b1da1f1a006bc0f50b4 to your computer and use it in GitHub Desktop.
Save Jeet-Metapercept/d7c427132fcc3b1da1f1a006bc0f50b4 to your computer and use it in GitHub Desktop.
Steps-states Indicators
<body ng-app="app" ng-controller="main" ng-class="{'show-steps': showSteps}">
<main>
<button ng-class="{'dismiss': showSteps}" ng-click="showSteps = !showSteps"><i class="fa fa-2x fa-fw fa-{{(showSteps?'times':'life-ring')}}"></i></button>
</main>
<aside>
<header>
<ol data-title="Steps of Things" ng-class="{'started': active}">
<li ng-repeat="item in items" ng-click="click($index)" ng-class="{'seen': item.seen, 'done': item.done, 'active': item == active}"><span data-title="Step">{{item.step}}</span></li>
</ol>
</header>
<article>
<button ng-click="next()" class="center" ng-if="!active"><i class="fa fa-play-circle fa-fw fa-lg"></i> Ready? Go!</button>
<section ng-repeat="item in items" ng-if="item === active">
<!-- <h3>{{item.step}}</h3> -->
<ol>
<li ng-repeat="step in item.substeps"><span>{{step}}</span></li>
</ol>
</section>
<!-- <pre>{{items | json}}</pre> -->
<!-- <pre>{{active | json}}</pre> -->
</article>
<footer ng-if="active">
<button ng-click="reset()"><i class="fa fa-undo fa-fw fa-lg"></i> Reset</button>
<button ng-click="prev()"><i class="fa fa-chevron-circle-left fa-fw fa-lg"></i> Back</button>
<button ng-click="next(true)"><i class="fa fa-chevron-circle-right fa-fw fa-lg"></i> Skip</button>
<button ng-click="next()"><i class="fa fa-check-circle fa-fw fa-lg"></i> {{(isLast() ? 'Done' : 'Next')}}</button>
</footer>
</aside>
</body>
(function(angular) {
'use strict';
angular.module('app', [])
.controller('main', ['$scope', function($scope) {
var randArray = function(max, min) {
return Array.apply(null, Array(Math.floor(Math.random() * (max - min)) + min)).map(function() {
return chance.sentence();
});
};
$scope.items = [{
step: 'The first thing',
substeps: randArray(15, 1)
}, {
step: 'Number two',
substeps: randArray(15, 1)
}, {
step: 'This is the third one',
substeps: randArray(15, 1)
}, {
step: 'Do the fourth thing',
substeps: randArray(15, 1)
}, {
step: 'The Last - a.k.a. part five',
substeps: randArray(15, 1)
}];
$scope.showSteps = true;
// return the index for first !item.done
var _bestIndex = function(field) {
return $scope.items.map(function(item, index) {
if (!item[field]) {
return index;
}
}).filter(isFinite)[0];
};
$scope.click = function(index) {
$scope.active = $scope.items[index];
$scope.active.seen = true;
$scope.index = index;
};
$scope.reset = function() {
angular.forEach($scope.items, function(item) {
item.seen = false;
item.done = false;
item.substeps = randArray(15, 1);
});
$scope.index = -1;
$scope.active = undefined;
console.clear();
};
$scope.isLast = function() {
return $scope.index === $scope.items.length - 1;
};
$scope.next = function(skip) {
var index = $scope.index;
if ($scope.items[index] && !skip) {
$scope.items[index].done = true;
}
// determine proper index
if (index > $scope.items.length - 1) {
index = _bestIndex('done') || 0;
} else {
index += 1;
}
// set active & seen
$scope.active = $scope.items[index];
$scope.index = index;
if ($scope.items[index]) {
$scope.items[index].seen = true;
}
};
$scope.prev = function() {
$scope.items[$scope.index].seen = true;
$scope.index = $scope.index - 1;
$scope.active = $scope.items[$scope.index];
};
$scope.reset();
}])
.directive('ngChance', function() {
return function(scope, element, attrs) {
console.log(chance[attrs.ngChance]());
element.text(chance[attrs.ngChance]());
}
});
})(window.angular)
<script src="//cdnjs.cloudflare.com/ajax/libs/chance/1.0.2/chance.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.2/angular.min.js"></script>

Steps-states Indicators

Uses Chance.js for content generation, CSS counters, and some Angular controls to show and manage the state for each of a series of steps. If there are undone steps, it will select this first undone step the next time it starts. Based on a design from @zherring

A Pen by Jase on CodePen.

License.

// colors
$dark: #1d1f21;
$light: #fff;
$ui: mix($dark, $light);
$active: #0ebeff;
$pending: #e08438;
$success: #9ccf5e;
// icons
$check: '\f00c';
$ellipsis-h: '\f141';
$clock-o: '\f017';
$circle: '\f111';
// plumbing: step numbers
header ol {
counter-reset: step;
> li {
counter-increment: step;
&::after {
content: counter(step);
}
> span::before {
content: attr(data-title) ' ' counter(step) ' ';
}
}
}
// mechanical
header ol {
margin: 0;
padding: 2.5em 1em 1em 1em;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
font-weight: 300;
&:not(.started)::after {
content: attr(data-title);
position: absolute;
z-index: 0;
bottom: 50%;
left: 50%;
transform: translate(-50%, -70%);
}
&::before {
content: '';
position: absolute;
height: .5em;
bottom: 1.5em;
left: 2em;
right: 2em;
transform: translateY(-50%);
}
> li {
flex: 0 0 auto;
z-index: 1;
width: 2em;
height: 2em;
padding: 0;
margin: 0;
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 2em;
border: .3em solid;
margin: 0 .15em;
cursor: pointer;
transition: .3s ease-out;
> span {
opacity: 0;
position: absolute;
white-space: nowrap;
text-align: center;
pointer-events: none;
bottom: 65%;
left: 0;
width: 100%;
font-size: 90%;
transition: inherit;
}
&::before {
font-family: FontAwesome;
line-height: 1;
}
&.seen:not(.done):not(.active)::before {
content: $circle;
transform: translate(3em, -1.2em);
font-size: 50%;
margin: 0 -.425em;
}
&.done::before {
content: $check;
}
&.done::after {
display: none;
}
&.active::before {
content: $ellipsis-h;
margin-top: .1em;
}
&.active.done::before {
content: $check;
margin-top: 0;
}
&.active::after {
display: none;
}
&.active > span {
opacity: 1;
}
}
}
// porcelain
header ol {
background: $dark;
&::before {
background: $ui;
}
li {
color: mix($ui, $light);
background: mix($ui, $dark);
border-color: $ui;
&.seen:not(.done):not(.active)::before {
color: $pending;
}
&.done {
color: $light;
background: $success;
border-color: $dark;
}
&.active {
color: $active;
background: $light;
border-color: inherit;
}
> span {
color: mix($ui, $light);
&::before {
opacity: .5;
}
}
}
}
// content
section {
position: relative;
line-height: 1.5;
font-size: .725em;
ol {
margin: 0;
padding: 0;
list-style: none;
counter-reset: step;
> li {
counter-increment: step;
&::before {
content: counter(step, lower-alpha);
}
}
}
li {
padding: 2em;
position: relative;
display: flex;
align-items: baseline;
span {
flex: 1;
padding-left: 1em;
}
&::before {
width: 2em;
height: 2em;
line-height: 2;
text-align: center;
background: mix($dark, $ui);
border-radius: 1em;
border: 1px solid mix($dark, $ui);
}
+ li {
border-top: 1px solid mix($dark, $ui);
}
}
}
// utils
.center {
position: absolute;
bottom: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// boring
html {
height: 100%;
overflow: hidden; // ew!
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
height: 100%;
font-size: 2.1vmin;
display: flex;
overflow-x: hidden; // ew!
flex-direction: row;
font-family: helvetica, arial, sans-serif;
&:not(.show-steps) aside {
margin-right: -20em;
> * {
display: none;
}
}
}
main {
flex: 1;
background: $light;
color: $dark;
padding: 1em;
display: flex;
flex-direction: column;
align-items: flex-end;
button {
border-radius: 2em;
padding: 0;
width: 3em;
height: 3em;
}
}
aside {
background: mix($light, $dark, 5%);
color: $light;
width: 20em;
margin-right: 0;
flex-direction: column;
display: flex;
transform: translateZ(0);
transition: margin-right .3s ease-out;
}
header {
flex: 0 0 auto;
}
article {
position: relative;
flex: 1; //0 1 100%;
overflow: auto;
border-top: 1px solid mix($dark, $ui);
border-bottom: 1px solid mix($dark, $ui);
}
footer {
flex: 0 0 auto;
display: flex;
justify-content: center;
align-items: center;
background: $dark;
padding: 1em;
z-index: 1;
button {
line-height: 1.5;
}
}
button {
font-size: 70%;
text-transform: capitalize;
line-height: 0;
border: 0;
border-radius: .3em;
background: $ui;
color: rgba($light, .85);
padding: 1em;
margin: 0 .5em;
outline: none;
&.dismiss {
background: rgba($dark, .2);
color: $ui;
}
&[disabled] {
opacity: .5;
}
&:not(.dismiss):first-child {
background: $pending;
}
&:not(.dismiss):last-child {
background: $active;
}
&:active {
background: mix($active, $light);
color: $light;
}
}
}
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.1/css/font-awesome.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment