feat(popup): Added popup support

This commit is contained in:
Max Lynch
2014-03-13 14:57:48 -05:00
parent 53a426f802
commit a30b0b7d4f
12 changed files with 1049 additions and 98 deletions

View File

@@ -51,7 +51,6 @@ module.exports = {
'js/views/loadingView.js',
'js/views/modalView.js',
'js/views/navBarView.js',
'js/views/popupView.js',
'js/views/sideMenuView.js',
'js/views/sliderView.js',
'js/views/tabBarView.js',

View File

@@ -0,0 +1,63 @@
(function() {
'use strict';
angular.module('ionic.ui.popup', [])
/**
* @private
*/
.directive('ionPopupBackdrop', function() {
return {
restrict: 'E',
replace: true,
template: '<div class="popup-backdrop"></div>'
}
})
/**
* @private
*/
.directive('ionPopup', ['$ionicBind', function($ionicBind) {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: true,
link: function($scope, $element, $attr) {
$ionicBind($scope, $attr, {
title: '@',
buttons: '=',
$onButtonTap: '&onButtonTap',
$onClose: '&onClose'
});
$scope._buttonTapped = function(button, event) {
var result = button.onTap && button.onTap(event);
// A way to return false
if(event.defaultPrevented) {
return $scope.$onClose({button: button, result: false, event: event });
}
// Truthy test to see if we should close the window
if(result) {
return $scope.$onClose({button: button, result: result, event: event });
}
$scope.$onButtonTap({button: button, event: event});
}
},
template: '<div class="popup">' +
'<div class="popup-head">' +
'<h3 class="popup-title" ng-bind-html="title"></h3>' +
'<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
'</div>' +
'<div class="popup-body" ng-transclude>' +
'</div>' +
'<div class="popup-buttons row">' +
'<button ng-repeat="button in buttons" ng-click="_buttonTapped(button, $event)" class="button col" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>' +
'</div>' +
'</div>'
};
}]);
})();

View File

@@ -42,7 +42,8 @@ angular.module('ionic.ui', [
'ionic.ui.checkbox',
'ionic.ui.toggle',
'ionic.ui.radio',
'ionic.ui.touch'
'ionic.ui.touch',
'ionic.ui.popup'
]);

View File

@@ -1,57 +1,529 @@
(function(ionic) {
'use strict';
angular.module('ionic.service.popup', ['ionic.service.templateLoad'])
/**
* @ngdoc service
* @name $ionicPopup
* @module ionic
* @restrict E
* @codepen jsHjf
* @description
*
* The Ionic Popup service makes it easy to programatically create and show popup
* windows that require the user to respond in order to continue:
*
* ![Popup](http://ionicframework.com.s3.amazonaws.com/docs/controllers/popup.png)
*
* The popup system has support for nicer versions of the built in `alert()` `prompt()` and `confirm()` functions
* you are used to in the browser, but with more powerful support for customizing input types in the case of
* prompt, or customizing the look of the window.
*
* But the true power of the Popup is when a built-in popup just won't cut it. Luckily, the popup window
* has full support for arbitrary popup content, and a simple promise-based system for returning data
* entered by the user.
*
* @usage
* To trigger a Popup in your code, use the $ionicPopup service in your angular controllers:
*
* ```js
* angular.module('mySuperApp', ['ionic'])
* .controller(function($scope, $ionicPopup) {
*
* // Triggered on a button click, or some other target
$scope.showPopup = function() {
$scope.data = {}
.factory('$ionicPopup', ['$rootScope', '$document', '$compile', 'TemplateLoader', function($rootScope, $document, $compile, TemplateLoader) {
// An elaborate, custom popup
$ionicPopup.show({
templateUrl: 'popup-template.html',
title: 'Enter Wi-Fi Password',
subTitle: 'Please use normal things',
scope: $scope,
buttons: [
{ text: 'Cancel', onTap: function(e) { return true; } },
{
text: '<b>Save</b>',
type: 'button-positive',
onTap: function(e) {
return $scope.data.wifi;
}
},
]
}).then(function(res) {
console.log('Tapped!', res);
}, function(err) {
console.log('Err:', err);
}, function(msg) {
console.log('message:', msg);
});
var getPopup = function() {
// Make sure there is only one loading element on the page at one point in time
var existing = angular.element($document[0].querySelector('.popup'));
if(existing.length) {
var scope = existing.scope();
if(scope.popup) {
return scope;
}
}
};
return {
alert: function(message, $scope) {
// If there is an existing popup, just show that one
var existing = getPopup();
if(existing) {
return existing.popup.alert(message);
}
var defaults = {
title: message,
animation: 'fade-in',
// A confirm dialog
$scope.showConfirm = function() {
$ionicPopup.confirm({
title: 'Consume Ice Cream',
content: 'Are you sure you want to eat this ice cream?'
}).then(function(res) {
if(res) {
console.log('You are sure');
} else {
console.log('You are not sure');
}
});
};
opts = angular.extend(defaults, opts);
// A prompt dialog
$scope.showPrompt = function() {
$ionicPopup.prompt({
title: 'ID Check',
content: 'What is your name?'
}).then(function(res) {
console.log('Your name is', res);
});
};
var scope = $scope && $scope.$new() || $rootScope.$new(true);
angular.extend(scope, opts);
// A prompt with password input dialog
$scope.showPasswordPrompt = function() {
$ionicPopup.prompt({
title: 'Password Check',
content: 'Enter your secret password',
inputType: 'password',
inputPlaceholder: 'Your password'
}).then(function(res) {
console.log('Your password is', res);
});
};
// Compile the template
var element = $compile('<popup>' + opts.content + '</popup>')(scope);
$document[0].body.appendChild(element[0]);
// An alert dialog
$scope.showAlert = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
content: 'It might taste good'
}).then(function(res) {
console.log('Thank you for not eating my delicious ice cream cone');
});
};
};
});
```
var popup = new ionic.views.Popup({el: element[0] });
popup.alert(message);
*/
.factory('$ionicPopup', ['$rootScope', '$q', '$document', '$compile', '$timeout', '$ionicTemplateLoader',
function($rootScope, $q, $document, $compile, $timeout, $ionicTemplateLoader) {
scope.popup = popup;
// TODO: Make this configurable
var popupOptions = {
// How long to wait after a popup is already shown to show another one
stackPushDelay: 50
}
return popup;
},
confirm: function(cb) {
},
prompt: function(cb) {
},
show: function(data) {
// data.title
// data.template
// data.buttons
// Center the given popup
var positionPopup = function(popup) {
popup.el.style.marginLeft = (-popup.el.offsetWidth) / 2 + 'px';
popup.el.style.marginTop = (-popup.el.offsetHeight) / 2 + 'px';
};
// Hide the body of the given popup if it's empty
var hideBody = function(popup) {
var bodyEl = popup.el.querySelector('.popup-body');
if(bodyEl && bodyEl.innerHTML.trim() == '') {
bodyEl.style.display = 'none';
}
};
// Show a single popup
var showSinglePopup = function(popup, opts) {
var _this = this;
ionic.requestAnimationFrame(function() {
hideBody(popup);
positionPopup(popup);
popup.el.classList.remove('popup-hidden');
popup.el.classList.add('popup-showing');
popup.el.classList.add('active');
});
};
// Show a popup that was already shown at one point in the past
var reshowSinglePopup = function(popup) {
ionic.requestAnimationFrame(function() {
popup.el.classList.remove('popup-hidden');
popup.el.classList.add('popup-showing');
popup.el.classList.add('active');
});
};
// Hide a single popup
var hideSinglePopup = function(popup) {
ionic.requestAnimationFrame(function() {
popup.el.classList.remove('active');
popup.el.classList.add('popup-hidden');
});
};
// Remove a popup once and for all
var removeSinglePopup = function(popup) {
// Force a reflow so the animation will actually run
popup.el.offsetWidth;
popup.el.classList.remove('active');
popup.el.classList.add('popup-hidden');
$timeout(function() {
popup.el.remove();
}, 400);
};
/**
* Popup stack and directive
*/
var popupStack = [];
var backdropEl = null;
// Show the backdrop element
var showBackdrop = function() {
var el = $compile('<ion-popup-backdrop></ion-popup-backdrop>')($rootScope.$new(true));
$document[0].body.appendChild(el[0]);
backdropEl = el;
};
// Remove the backdrop element
var removeBackdrop = function() {
backdropEl.remove();
};
// Push the new popup onto the stack with the given data and scope.
// If this is the first one in the stack, show the backdrop, otherwise don't.
var pushAndShow = function(popup, data) {
var lastPopup = popupStack[popupStack.length-1];
popupStack.push(popup);
// If this is the first popup, show the backdrop
if(popupStack.length == 1) {
showBackdrop();
}
// If we have an existing popup, add a delay between hiding and showing it
if(lastPopup) {
hideSinglePopup(lastPopup);
$timeout(function() {
showSinglePopup(popup);
}, popupOptions.stackPushDelay);
} else {
// Otherwise, immediately show it
showSinglePopup(popup);
}
};
// Pop the current popup off the stack. If there are other popups, show them
// otherwise hide the backdrop.
var popAndRemove = function(popup) {
var lastPopup = popupStack.pop();
var nextPopup = popupStack[popupStack.length-1];
removeSinglePopup(lastPopup);
if(nextPopup) {
reshowSinglePopup(nextPopup);
} else {
removeBackdrop();
}
};
// Append the element to the screen, create the popup view,
// and add the popup to the scope
var constructPopupOnScope = function(element, scope) {
var popup = {
el: element[0],
scope: scope
};
scope.popup = popup;
return popup;
}
var buildPopupTemplate = function(opts, content) {
return '<ion-popup title="' + opts.title + '" buttons="buttons" on-button-tap="onButtonTap(button, event)" on-close="onClose(button, result, event)">'
+ (content || '') +
'</ion-popup>';
};
// Given an options object, build a new popup window and return a promise
// which will contain the constructed popup at a later date. Perhaps at a later
// year even. At this point, it's hard to say.
var createPopup = function(opts, responseDeferred) {
var q = $q.defer();
// Create some defaults
var defaults = {
title: '',
animation: 'fade-in',
};
opts = angular.extend(defaults, opts);
// Create a new scope, and bind some of the options stuff to that scope
var scope = opts.scope && opts.scope.$new() || $rootScope.$new(true);
angular.extend(scope, opts);
scope.onClose = function(button, result, event) {
popAndRemove(scope.popup);
responseDeferred.resolve(result);
};
// Check if we need to load a template for the content of the popup
if(opts.templateUrl) {
// Load the template externally
$ionicTemplateLoader.load(opts.templateUrl).then(function(templateString) {
var popupTemplate = buildPopupTemplate(opts, templateString);
var element = $compile(popupTemplate)(scope);
$document[0].body.appendChild(element[0]);
q.resolve(constructPopupOnScope(element, scope));
}, function(err) {
// Error building the popup
q.reject(err);
});
} else {
// Compile the template
var popupTemplate = buildPopupTemplate(opts, opts.content);
var element = $compile(popupTemplate)(scope);
$document[0].body.appendChild(element[0]);
q.resolve(constructPopupOnScope(element, scope));
}
return q.promise;
};
// Public API
return {
showPopup: function(data) {
var q = $q.defer();
createPopup(data, q).then(function(popup, scope) {
// We constructed the popup, push it on the stack and show it
pushAndShow(popup, data);
}, function(err) {
console.error('Unable to load popup:', err);
});
return q.promise;
},
/**
* @ngdoc method
* @name $ionicPopup#show
* @description show a complex popup. This is the master show function for all popups
* @param {data} object The options for showing a popup, of the form:
*
* ```
* {
* content: '', // String. The content of the popup
* title: '', // String. The title of the popup
* subTitle: '', // String (optional). The sub-title of the popup
* templateUrl: '', // URL String (optional). The URL of a template to load as the content (instead of the `content` field)
* scope: null, // Scope (optional). A scope to apply to the popup content (for using ng-model in a template, for example)
* buttons:
* [
* {
* text: 'Cancel',
* type: 'button-default',
* onTap: function(e) {
* // e.preventDefault() is the only way to return a false value
* e.preventDefault();
* }
* },
* {
* text: 'OK',
* type: 'button-positive',
* onTap: function(e) {
* // When the user taps one of the buttons, you need to return the
* // Data you want back to the popup service which will then resolve
* // the promise waiting for a response.
* //
* // To return "false", call e.preventDefault();
* return scope.data.response;
* }
* }
* ]
*
* }
* ```
*/
show: function(data) {
return this.showPopup(data);
},
/**
* @ngdoc method
* @name $ionicPopup#alert
* @description show a simple popup with one button that the user has to tap
*
* Show a simple alert dialog
*
* ```javascript
* $ionicPopup.alert({
* title: 'Hey!;,
* content: 'Don\'t do that!'
* }).then(function(res) {
* // Accepted
* });
* ```
*
* @returns {Promise} that resolves when the alert is accepted
* @param {data} object The options for showing an alert, of the form:
*
* ```
* {
* content: '', // String. The content of the popup
* title: '', // String. The title of the popup
* okText: '', // String. The text of the OK button
* okType: '', // String (default: button-positive). The type of the OK button
* }
* ```
*/
alert: function(opts) {
return this.showPopup({
content: opts.content || '',
title: opts.title || '',
buttons: [
{
text: opts.okText || 'OK',
type: opts.okType || 'button-positive',
onTap: function(e) {
return true;
}
}
]
});
},
/**
* @ngdoc method
* @name $ionicPopup#confirm
* @description
* Show a simple confirm popup with a cancel and accept button:
*
* ```javascript
* $ionicPopup.confirm({
* title: 'Consume Ice Cream',
* content: 'Are you sure you want to eat this ice cream?'
* }).then(function(res) {
* if(res) {
* console.log('You are sure');
* } else {
* console.log('You are not sure');
* }
* });
* ```
*
* @returns {Promise} that resolves with the chosen option
* @param {data} object The options for showing a confirm dialog, of the form:
*
* ```
* {
* content: '', // String. The content of the popup
* title: '', // String. The title of the popup
* cancelText: '', // String. The text of the Cancel button
* cancelType: '', // String (default: button-default). The type of the kCancel button
* okText: '', // String. The text of the OK button
* okType: '', // String (default: button-positive). The type of the OK button
* }
* ```
*/
confirm: function(opts) {
return this.showPopup({
content: opts.content || '',
title: opts.title || '',
buttons: [
{
text: opts.cancelText || 'Cancel' ,
type: opts.cancelType || 'button-default',
onTap: function(e) { e.preventDefault(); }
},
{
text: opts.okText || 'OK',
type: opts.okType || 'button-positive',
onTap: function(e) {
return true;
}
}
]
});
},
/**
* @ngdoc method
* @name $ionicPopup#prompt
* @description show a simple prompt dialog.
*
* ```javascript
* $ionicPopup.prompt({
* title: 'Password Check',
* content: 'Enter your secret password',
* inputType: 'password',
* inputPlaceholder: 'Your password'
* }).then(function(res) {
* console.log('Your password is', res);
* });
* ```
*
* @returns {Promise} that resolves with the entered data
* @param {data} object The options for showing a prompt dialog, of the form:
*
* ```
* {
* content: // String. The content of the popup
* title: // String. The title of the popup
* subTitle: // String. The sub title of the popup
* inputType: // String (default: "text"). The type of input to use
* inputPlaceholder: // String (default: ""). A placeholder to use for the input.
* cancelText: // String. The text of the Cancel button
* cancelType: // String (default: button-default). The type of the kCancel button
* okText: // String. The text of the OK button
* okType: // String (default: button-positive). The type of the OK button
* }
* ```
*/
prompt: function(opts) {
var scope = $rootScope.$new(true);
scope.data = {};
return this.showPopup({
content: opts.content || '<input ng-model="data.response" type="' + (opts.inputType || 'text') + '" placeholder="' + (opts.inputPlaceholder || '') + '">',
title: opts.title || '',
subTitle: opts.subTitle || '',
scope: scope,
buttons: [
{
text: opts.cancelText || 'Cancel',
type: opts.cancelType|| 'button-default',
onTap: function(e) { e.preventDefault(); }
},
{
text: opts.okText || 'OK',
type: opts.okType || 'button-positive',
onTap: function(e) {
return scope.data.response;
}
}
]
});
}
};
}]);
})(ionic);

View File

@@ -0,0 +1,97 @@
'use strict';
describe('Ionic Popup Directive', function() {
var compile, scope, popupEl;
beforeEach(module('ionic'));
beforeEach(inject(function($compile, $rootScope, $controller) {
compile = $compile;
scope = $rootScope;
}));
it('Should build', function() {
popupEl = angular.element('<ion-popup></ion-popup>');
popupEl = compile(popupEl)(scope);
expect(popupEl[0].classList.contains('popup')).toBe(true);
});
it('Set scope', function() {
popupEl = angular.element('<ion-popup title="Cats"></ion-popup>');
var buttons = [
{
type: 'button-whack',
text: 'Whacky'
}
];
scope.buttons = buttons;
popupEl = compile(popupEl)(scope);
scope.$apply();
scope = popupEl.scope();
expect(scope.title).toEqual('Cats');
expect(scope.buttons).toBe(buttons);
expect(scope.buttons[0].text).toEqual('Whacky');
expect(scope.buttons[0].type).toEqual('button-whack');
expect(popupEl[0].querySelector('button').classList.contains('button-whack')).toBe(true);
expect(popupEl[0].classList.contains('popup')).toBe(true);
});
it('Does call callbacks', function() {
popupEl = angular.element('<ion-popup title="Cats" on-button-tap="onTap(button, $event)" on-close="onClose(button, $event)"></ion-popup>');
// For spy
/* Not sure how to trigger these
var cb = {
onTap: function(e) {},
onClose: function(e) {}
};
*/
var buttons = [
{
type: 'button-whack',
text: 'Whacky',
onTap: function(e) {}
}
];
scope.buttons = buttons;
/*
scope.onTap = cb.onTap;
scope.onClose = cb.onClose;
*/
popupEl = compile(popupEl)(scope);
scope.$apply();
spyOn(buttons[0], 'onTap');
/*
spyOn(cb, 'onTap');
spyOn(cb, 'onClose');
*/
var button = popupEl[0].querySelector('button');
ionic.trigger('click', {
target: button
});
/*
expect(cb.onTap).toHaveBeenCalled();
expect(cb.onClose).toHaveBeenCalled();
*/
expect(buttons[0].onTap).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,107 @@
<html ng-app="ionic.example">
<head>
<meta charset="utf-8">
<title>Popups</title>
<!-- Sets initial viewport load and disables zooming -->
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="stylesheet" href="../../../../dist/css/ionic.css">
<script src="../../../../dist/js/ionic.bundle.js"></script>
</head>
<body ng-controller="PopupCtrl">
<button class="button button-dark" ng-click="showPopup()">Generic</button>
<button class="button button-primary" ng-click="showConfirm()">Confirm</button>
<button class="button button-balanced" ng-click="showPrompt()">Prompt</button>
<button class="button button-balanced" ng-click="showPasswordPrompt()">Password Prompt</button>
<button class="button button-positive" ng-click="showAlert()">Alert</button>
<script id="popup-template.html" type="text/ng-template">
<input ng-model="data.wifi" type="text" placeholder="Password">
</script>
<script>
angular.module('ionic.example', ['ionic'])
.controller('PopupCtrl', function($scope, $timeout, $q, $ionicPopup) {
$scope.showPopup = function() {
$scope.data = {}
$ionicPopup.show({
templateUrl: 'popup-template.html',
title: 'Enter Wi-Fi Password',
subTitle: 'Please use normal things',
scope: $scope,
buttons: [
{ text: 'Cancel', onTap: function(e) { return true; } },
{
text: '<b>Save</b>',
type: 'button-positive',
onTap: function(e) {
return $scope.data.wifi;
}
},
]
}).then(function(res) {
console.log('Tapped!', res);
}, function(err) {
console.log('Err:', err);
}, function(msg) {
console.log('message:', msg);
});
$timeout(function() {
$ionicPopup.confirm({
title: 'Do you like ice cream?',
cancelText: 'No',
okText: 'Yes, of course'
}).then(function(res) {
console.log('Your love for ice cream:', res);
});
}, 1000);
};
$scope.showConfirm = function() {
$ionicPopup.confirm({
title: 'Consume Ice Cream',
content: 'Are you sure you want to eat this ice cream?'
}).then(function(res) {
if(res) {
console.log('You are sure');
} else {
console.log('You are not sure');
}
});
};
$scope.showPrompt = function() {
$ionicPopup.prompt({
title: 'ID Check',
subTitle: 'What is your name?'
}).then(function(res) {
console.log('Your name is', res);
});
};
$scope.showPasswordPrompt = function() {
$ionicPopup.prompt({
title: 'Password Check',
subTitle: 'Enter your secret password',
inputType: 'password',
inputPlaceholder: 'Your password'
}).then(function(res) {
console.log('Your name is', res);
});
};
$scope.showAlert = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
content: 'It might taste good'
}).then(function(res) {
console.log('Thank you for not eating my delicious ice cream cone');
});
};
});
</script>
</body>
</html>

View File

@@ -0,0 +1,70 @@
describe('Ionic Popup', function() {
var popup, timeout, scope;
beforeEach(module('ionic'));
beforeEach(inject(function($ionicPopup, $rootScope, $timeout) {
ionic.requestAnimationFrame = function(cb) { cb(); }
scope = $rootScope;
popup = $ionicPopup;
timeout = $timeout;
}));
it('Should show popup', function() {
popup.show({
title: 'Cats',
content: 'Dogs',
buttons: [
{
text: 'Okay',
type: 'button-balanced',
onTap: function(e) {}
}
]
});
timeout.flush();
var popupBackdropEl = document.body.querySelector('.popup-backdrop');
expect(popupBackdropEl).not.toEqual(null);
var popupEl = document.body.querySelector('.popup');
expect(popupEl.classList.contains('active')).toBe(true);
expect(popupEl.classList.contains('popup-showing')).toBe(true);
popup.show({
title: 'Cats',
content: 'Dogs',
buttons: [
{
text: 'Okay',
type: 'button-balanced',
onTap: function(e) {}
}
]
});
timeout.flush();
// Make sure there are two popups
expect(document.body.querySelectorAll('.popup').length).toEqual(2);
});
it('Should set correct element data', function() {
popup.show({
title: 'Cats',
content: 'Dogs',
buttons: [
{
text: 'Okay',
type: 'button-balanced',
onTap: function(e) {}
}
]
});
var popupEl = document.body.querySelector('.popup');
expect(popupEl.querySelector('.popup-title').innerText).toEqual('Cats');
expect(popupEl.querySelector('.popup-body').innerText).toEqual('Dogs');
});
});

View File

@@ -1,38 +0,0 @@
(function(ionic) {
'use strict';
/**
* An ActionSheet is the slide up menu popularized on iOS.
*
* You see it all over iOS apps, where it offers a set of options
* triggered after an action.
*/
ionic.views.Popup = ionic.views.View.inherit({
initialize: function(opts) {
var _this = this;
this.el = opts.el;
},
setTitle: function(title) {
var titleEl = el.querySelector('.popup-title');
if(titleEl) {
titleEl.innerHTML = title;
}
},
alert: function(message) {
var _this = this;
ionic.requestAnimationFrame(function() {
_this.setTitle(message);
_this.el.classList.add('active');
});
},
hide: function() {
// Force a reflow so the animation will actually run
this.el.offsetWidth;
this.el.classList.remove('active');
}
});
})(ionic);

View File

@@ -1,3 +1,4 @@
@charset "UTF-8";
/*!
* Copyright 2014 Drifty Co.
* http://drifty.com/
@@ -3175,15 +3176,39 @@ a.subdued {
* --------------------------------------------------
*/
.popup {
position: fixed; }
position: fixed;
top: 50%;
left: 50%;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.9); }
.popup-content {
.popup-head {
padding: 15px 0px;
text-align: center;
border-bottom: 1px solid #eee; }
.popup-title {
font-size: 14px;
margin: 0;
padding: 0; }
.popup-body {
padding: 10px; }
.loading-backdrop {
-webkit-transition: visibility 0s linear 0.3s;
-moz-transition: visibility 0s linear 0.3s;
transition: visibility 0s linear 0.3s;
.popup-buttons.row {
padding: 0; }
.popup-buttons .button {
line-height: 30px;
border: none;
border-right: 1px solid #eee;
border-radius: 0px 0px 8px 8px; }
.popup-buttons .button:last-child {
border-right: none; }
.popup-backdrop.enabled {
background-color: rgba(0, 0, 0, 0.4); }
.loading-backdrop, .popup-backdrop {
position: fixed;
top: 0;
left: 0;
@@ -3191,14 +3216,19 @@ a.subdued {
visibility: hidden;
width: 100%;
height: 100%; }
.loading-backdrop.enabled {
background-color: rgba(0, 0, 0, 0.7); }
.loading-backdrop.active {
.loading-backdrop.active, .popup-backdrop.active {
-webkit-transition-delay: 0s;
-moz-transition-delay: 0s;
transition-delay: 0s;
visibility: visible; }
.loading-backdrop {
-webkit-transition: visibility 0s linear 0.3s;
-moz-transition: visibility 0s linear 0.3s;
transition: visibility 0s linear 0.3s; }
.loading-backdrop.enabled {
background-color: rgba(0, 0, 0, 0.7); }
.loading {
position: fixed;
top: 50%;

View File

@@ -184,6 +184,56 @@ $slide-in-up-function: cubic-bezier(.1, .7, .1, 1);
to { background-color: rgba(0,0,0,0); }
}
// Scale Out
// Scale from hero (1 in this case) to zero
// -------------------------------
@-webkit-keyframes scaleOut {
from { -webkit-transform: scale(1); opacity: 1; }
to { -webkit-transform: scale(0.8); opacity: 0; }
}
@-moz-keyframes scaleOut {
from { -moz-transform: scale(1); opacity: 1; }
to { -moz-transform: scale(0.8); opacity: 0; }
}
@keyframes scaleOut {
from { transform: scale(1); opacity: 1; }
to { transform: scale(0.8); opacity: 0; }
}
// Scale In
// Scale from 0 to hero (1 in this case)
// -------------------------------
@-webkit-keyframes scaleIn {
from { -webkit-transform: scale(0); }
to { -webkit-transform: scale(1); }
}
@-moz-keyframes scaleIn {
from { -moz-transform: scale(0); }
to { -moz-transform: scale(1); }
}
@keyframes scaleIn {
from { transform: scale(0); }
to { transform: scale(1); }
}
// Super Scale In
// Scale from super (1.x) to duper (1 in this case)
// -------------------------------
@-webkit-keyframes superScaleIn {
from { -webkit-transform: scale(1.2); opacity: 0; }
to { -webkit-transform: scale(1); opacity: 1 }
}
@-moz-keyframes superScaleIn {
from { -moz-transform: scale(1.2); opacity: 0; }
to { -moz-transform: scale(1); opacity: 1; }
}
@keyframes superScaleIn {
from { transform: scale(1.2); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
// Spin
// -------------------------------

View File

@@ -5,14 +5,95 @@
.popup {
position: fixed;
top: 50%;
left: 50%;
z-index: 11;
// Start hidden
visibility: hidden;
width: $popup-width;
border-radius: $popup-border-radius;
background-color: $popup-background-color;
&.popup-hidden {
@include animation-name(scaleOut);
@include animation-duration($popup-leave-animation-duration);
@include animation-timing-function(ease-in-out);
@include animation-fill-mode(both);
}
&.popup-showing {
visibility: visible;
}
&.active {
@include animation-name(superScaleIn);
@include animation-duration($popup-enter-animation-duration);
@include animation-timing-function(ease-in-out);
@include animation-fill-mode(both);
}
}
.popup-content {
.popup-head {
padding: 15px 0px;
text-align: center;
border-bottom: 1px solid #eee;
}
.popup-title {
font-size: 15px;
margin: 0;
padding: 0;
}
.popup-sub-title {
font-size: 11px;
margin: 5px 0 0 0;
padding: 0;
font-weight: normal;
}
.popup-body {
padding: 10px;
}
.popup-buttons {
&.row {
padding: 10px 10px;
}
.button {
margin: 0px 5px;
min-height: $popup-button-min-height;
line-height: $popup-button-line-height;
border-radius: $popup-button-border-radius;
&:first-child {
margin-left: 0px;
}
&:last-child {
margin-right: 0px;
}
}
}
.popup-backdrop {
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
@include animation-name(fadeIn);
@include animation-duration($popup-backdrop-fadein-duration);
@include animation-timing-function(linear);
@include animation-fill-mode(both);
}
.loading-backdrop {
@include transition(visibility 0s linear 0.3s);
position: fixed;
top: 0;
left: 0;
@@ -22,16 +103,19 @@
width: 100%;
height: 100%;
&.enabled {
background-color: rgba(0,0,0,0.7);
}
&.active {
@include transition-delay(0s);
visibility: visible;
}
}
.loading-backdrop {
@include transition(visibility 0s linear 0.3s);
&.active {
background-color: rgba(0,0,0,0.7);
}
}
.loading {
position: fixed;
top: 50%;

View File

@@ -548,6 +548,22 @@ $sheet-border-radius: 3px 3px 3px 3px !default;
$sheet-border-radius-top: 3px 3px 0px 0px !default;
$sheet-border-radius-bottom: 0px 0px 3px 3px !default;
// Popups
// -------------------------------
$popup-backdrop-fadein-duration: 0.1s;
$popup-width: 250px;
$popup-enter-animation: superScaleIn;
$popup-enter-animation-duration: 0.2s;
$popup-leave-animation-duration: 0.1s;
$popup-border-radius: 0px;
$popup-background-color: rgba(255,255,255,0.9);
$popup-button-border-radius: 2px;
$popup-button-line-height: 20px;
$popup-button-min-height: 45px;
// Badges
// -------------------------------