Modals should inherit from the theme

* Fullscreen Modals have been removed
  These weren't working properly anyways, and the styling is very
  unpleasant: https://i.imgur.com/QfvGli7.png

* Added two more modal sizes: xs and xl, and added all modals sizes
  to the theme preview page.  Also updated the theme preview page
  to use fa-close instead of 'x'.

* Cleaned up Spinner css to use modal sizes properly.  Themability
  of spinner to come later.

* Confirmation Modal JavaScript was cleaned up, and contents turned
  into an easier to digest client side template. Themability of
  client side templates show cased in 'material' design.

* 'material' confirmation dialog was altered to show how themes can
  make use of overriding client-side templates.

* Moved the Bootstrap helper variables into its own file.

* Added helper variables for various modal calculations.

Change-Id: I599ad2ffcf3034a24a19bc87e6ebed3eab079f45
This commit is contained in:
Diana Whitten 2016-04-14 15:30:28 -07:00
parent 8e2b363a8f
commit 03ede4ce5e
22 changed files with 316 additions and 177 deletions

View File

@ -8,7 +8,7 @@
<div class="modal-body">
<div class="row">
<div class="col-xs-12 col-sm-3">
<div class="col-xs-12 col-sm-3 wizard-navigation">
<button type="button" data-toggle="collapse"
data-target="#wizard-side-nav" aria-expanded="false"
class="navbar-toggle btn btn-default collapsed wizard-nav-toggle">
@ -36,7 +36,7 @@
</div>
</div>
<div class="col-xs-12 col-sm-9">
<div class="col-xs-12 col-sm-9 wizard-steps">
<div class="step"
ng-repeat="step in steps"
ng-show="currentIndex===$index">
@ -46,14 +46,14 @@
</ng-include>
</div>
</div>
<help-panel>
<ng-include src="step.helpUrl"
ng-repeat="step in steps"
ng-show="currentIndex===$index"></ng-include>
</help-panel>
</div>
<help-panel class="wizard-help">
<ng-include src="step.helpUrl"
ng-repeat="step in steps"
ng-show="currentIndex===$index"></ng-include>
</help-panel>
</div>
<div class="modal-footer">

View File

@ -226,59 +226,81 @@ horizon.datatables = {
};
/* Generates a confirmation modal dialog for the given action. */
horizon.datatables.confirm = function (action) {
var $action = $(action),
$modal_parent = $(action).closest('.modal'),
name_array = [],
closest_table_id, action_string, name_string,
help_text,
title, body, modal, form;
if($action.hasClass("disabled")) {
horizon.datatables.confirm = function(action) {
var $action = $(action);
if ($action.hasClass("disabled")) {
return;
}
action_string = $action.text();
help_text = $action.attr("help_text") || "";
name_string = "";
var $modal_parent = $action.closest('.modal');
var name_array = [];
var action_string = $action.text();
var help_text = $action.attr("help_text") || "";
var name_string = "";
// Add the display name defined by table.get_object_display(datum)
closest_table_id = $(action).closest("table").attr("id");
var $closest_table = $action.closest("table");
// Check if data-display attribute is available
if ($("#"+closest_table_id+" tr[data-display]").length > 0) {
var actions_div = $(action).closest("div");
if(actions_div.hasClass("table_actions") || actions_div.hasClass("table_actions_menu")) {
var $data_display = $closest_table.find('tr[data-display]');
if ($data_display.length > 0) {
var $actions_div = $action.closest("div");
if ($actions_div.hasClass("table_actions") || $actions_div.hasClass("table_actions_menu")) {
// One or more checkboxes selected
$("#"+closest_table_id+" tr[data-display]").has(".table-row-multi-select:checked").each(function() {
$data_display.has(".table-row-multi-select:checked").each(function() {
name_array.push(" \"" + $(this).attr("data-display") + "\"");
});
name_array.join(", ");
name_string = name_array.toString();
name_string = name_array.join(", ");
} else {
// If no checkbox is selected
name_string = " \"" + $(action).closest("tr").attr("data-display") + "\"";
name_string = " \"" + $action.closest("tr").attr("data-display") + "\"";
name_array = [name_string];
}
name_string = interpolate(gettext("You have selected %s. "), [name_string]);
}
title = interpolate(gettext("Confirm %s"), [action_string]);
body = name_string + gettext("Please confirm your selection. ") + help_text;
modal = horizon.modals.create(title, body, action_string);
var title = interpolate(gettext("Confirm %s"), [action_string]);
// compose the action string using a template that can be overridden
var template = horizon.templates.compiled_templates["#confirm_modal"],
params = {
selection: name_string,
selection_list: name_array,
help: help_text
};
var body;
try {
body = $(template.render(params)).html();
} catch (e) {
body = name_string + gettext("Please confirm your selection. ") + help_text;
}
var modal = horizon.modals.create(title, body, action_string);
modal.modal();
if($modal_parent.length) {
if ($modal_parent.length) {
var child_backdrop = modal.next('.modal-backdrop');
// re-arrange z-index for these stacking modal
child_backdrop.css('z-index', $modal_parent.css('z-index')+10);
modal.css('z-index', child_backdrop.css('z-index')+10);
}
modal.find('.btn-primary').click(function () {
form = $action.closest('form');
var $form = $action.closest('form');
var el = document.createElement("input");
el.type='hidden';
el.type = 'hidden';
el.name = $action.attr('name');
el.value = $action.attr('value');
form.append(el);
form.submit();
$form
.append(el)
.submit();
modal.modal('hide');
horizon.modals.modal_spinner(gettext("Working"));
return false;
});
return modal;
};

View File

@ -1,7 +1,14 @@
/* global Hogan */
/* Namespace for core functionality related to client-side templating. */
horizon.templates = {
template_ids: ["#modal_template", "#empty_row_template", "#alert_message_template", "#spinner-modal", "#membership_template"],
template_ids: [
"#modal_template",
"#empty_row_template",
"#alert_message_template",
"#spinner-modal",
"#membership_template",
"#confirm_modal"
],
compiled_templates: {}
};

View File

@ -0,0 +1,12 @@
{% extends "horizon/client_side/template.html" %}
{% load i18n horizon %}
{% block id %}confirm_modal{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="confirm-wrapper">
<span class="confirm-list">{% trans 'You have selected:' %} [[selection]]. </span>
<span class="confirm-text">{% trans 'Please confirm your selection.'%} </span>
<span class="confirm-help">[[help]]</span>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}

View File

@ -5,10 +5,10 @@
{% block template %}{% spaceless %}{% jstemplate %}
<div class="modal loading">
<div class="modal-dialog">
<div class="modal-dialog modal-xs">
<div class="modal-content">
<div class="modal-body">
<p>[[text]]&hellip;</p>
<p class="text-center">[[text]]&hellip;</p>
</div>
</div>
</div>

View File

@ -14,7 +14,7 @@
<h3 class="modal-title">[[title]]</h3>
</div>
<div class='modal-body'>
[[body]]
[[[body]]]
</div>
<div class='modal-footer'>
<a href='#' class='btn btn-default cancel' data-dismiss='modal'>[[cancel]]</a>

View File

@ -2,4 +2,5 @@
{% include "horizon/client_side/_table_row.html" %}
{% include "horizon/client_side/_alert_message.html" %}
{% include "horizon/client_side/_loading.html" %}
{% include "horizon/client_side/_membership.html" %}
{% include "horizon/client_side/_membership.html" %}
{% include "horizon/client_side/_confirm.html" %}

View File

@ -334,21 +334,3 @@ class WorkflowsTests(test.TestCase):
flow = TestWorkflow(req, entry_point="test_action_two")
self.assertEqual("test_action_two", flow.get_entry_point())
def test_fullscreenworkflow_view(self):
view = TestFullscreenWorkflowView.as_view()
req = self.factory.get("/")
req.is_ajax = lambda: True
res = view(req)
output = res.render()
self.assertRegexpMatches(bytes(output),
b'class="[^"]*\\bfullscreen\\b[^"]*"')
def test_notfullscreenworkflow_view(self):
view = TestWorkflowView.as_view()
req = self.factory.get("/")
req.is_ajax = lambda: True
res = view(req)
output = res.render()
self.assertNotRegexpMatches(bytes(output),
b'class="[^"]*\\bfullscreen\\b[^"]*"')

View File

@ -603,13 +603,6 @@ class Workflow(html.HTMLElement):
Whether to present the workflow as a wizard, with "prev" and "next"
buttons and validation after every step.
.. attribute:: fullscreen
If the workflow is presented in a modal, and this attribute is
set to True, then the ``fullscreen`` css class will be added so
the modal can take advantage of the available screen estate.
Defaults to ``False``.
"""
slug = None
default_steps = ()
@ -620,7 +613,6 @@ class Workflow(html.HTMLElement):
redirect_param_name = "next"
multipart = False
wizard = False
fullscreen = False
_registerable_class = Step
def __str__(self):

View File

@ -107,8 +107,6 @@ class WorkflowView(hz_views.ModalBackdropMixin, generic.TemplateView):
"""
if self.request.is_ajax():
layout = ['modal', ]
if self.workflow_class.fullscreen:
layout += ['fullscreen', ]
else:
layout = ['static_page', ]

View File

@ -1305,29 +1305,7 @@
</div>
</div>
<div class="row">
<div class="col-lg-6">
<h2 translate>Modals</h2>
<div class="bs-component">
<div class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 translate class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p translate>One fine body…</p>
</div>
<div class="modal-footer">
<button translate type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button translate type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="col-lg-10 col-lg-offset-2">
<h2 translate>Popovers</h2>
<div class="bs-component">
<button translate type="button" class="btn btn-default" data-container="body" data-toggle="popover" data-placement="left" data-content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">Left</button>
@ -1344,6 +1322,131 @@
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<h2 translate>Modals</h2>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="bs-component">
<div class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="fa fa-close"></span>
</button>
<h4 translate class="modal-title">Standard Modal</h4>
</div>
<div class="modal-body">
<p translate>One fine body…</p>
</div>
<div class="modal-footer">
<button translate type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button translate type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="bs-component">
<div class="modal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="fa fa-close"></span>
</button>
<h4 translate class="modal-title">Small Modal</h4>
</div>
<div class="modal-body">
<p translate>One small body…</p>
</div>
<div class="modal-footer">
<button translate type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button translate type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-2">
<div class="bs-component">
<div class="modal">
<div class="modal-dialog modal-xs">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="fa fa-close"></span>
</button>
<h4 translate class="modal-title">XS</h4>
</div>
<div class="modal-body">
<p translate>One tiny body…</p>
</div>
<div class="modal-footer">
<button translate type="button" class="btn btn-default" data-dismiss="modal">Ok</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-10">
<div class="bs-component">
<div class="modal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="fa fa-close"></span>
</button>
<h4 translate class="modal-title">Large Modal</h4>
</div>
<div class="modal-body">
<p translate>One large body…</p>
</div>
<div class="modal-footer">
<button translate type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button translate type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="bs-component">
<div class="modal">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<span class="fa fa-close"></span>
</button>
<h4 translate class="modal-title">XL Modal</h4>
</div>
<div class="modal-body">
<p translate>One super large body…</p>
</div>
<div class="modal-footer">
<button translate type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button translate type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="source-modal" class="modal fade">

View File

@ -12,10 +12,6 @@ themepreview {
position: relative;
}
.bs-component .modal-dialog {
width: 90%;
}
.bs-component .modal {
bottom: auto;
display: block;

View File

@ -0,0 +1,32 @@
// Bootstrap Variable Helpers
// Some values in Bootstrap are hardcoded, but it would be
// useful to use their values in some calculations
$bs-button-vertical-border: 2; // _buttons.scss:18
$bs-dropdown-item-padding-vertical: 3px; // _dropdowns.scss:67
$bs-dropdown-item-padding-horizontal: 20px; // _dropdowns.scss:67
$bs-modal-margin: 30px; // _modals.scss:138
$bs-modal-margin-small-screen: 10px; // _modals.scss:47
$bs-modal-footer-vertical-border: 1; // _modals.scss:106
// Some custom Modal sizes used to create two additional modal sizes: xs & xl
$modal-xs: $modal-sm/2;
$modal-xl: 95vw;
// Some Helpful Calculations
$bs-button-height: ($font-size-base * $line-height-base) + $padding-base-vertical*2 + $bs-button-vertical-border;
// The height of a standard modal header with an H3 sized title
$bs-modal-header-height: $modal-title-padding*2 + ($font-size-h3 * $line-height-base);
// The height of a standard modal footer with a single row of buttons
$bs-modal-footer-height: $modal-inner-padding*2 + $bs-button-height + $bs-modal-footer-vertical-border;
// The height of the modal w/o taking its contents into account
$bs-modal-height: $bs-modal-margin*2 + $bs-modal-header-height + $bs-modal-footer-height;
$bs-modal-height-small-screen: $bs-modal-margin-small-screen*2 + $bs-modal-header-height + $bs-modal-footer-height;

View File

@ -1,3 +1,7 @@
/* Import the Bootstrap Helper Variables */
@import 'bootstrap_helpers';
/* When used with Horizon via Django, this value is set automatically from
settings.py and is added dynamically to the namespace through
horizon/utils/scss_filter.py */
@ -71,12 +75,6 @@ $members-list-item-width: 130px !default;
$members-list-item-max-width: 327px !default;
$members-list-roles-width: 125px !default;
// Mimics the padding in _dropdowns.scss
// Not sure why this is hardcoded :-/
// https://github.com/twbs/bootstrap/issues/13443
$dropdown-item-padding-vertical: 3px;
$dropdown-item-padding-horizontal: 20px;
// This defines the max-width for a breadcrumb item before it will be truncated
$breadcrumb-item-width: 15em !default;

View File

@ -11,7 +11,7 @@
box-shadow: none;
border-radius: 0;
margin: 0; // prevent the form-inline styles from messing with margin
padding: $dropdown-item-padding-vertical $dropdown-item-padding-horizontal;
padding: $bs-dropdown-item-padding-vertical $bs-dropdown-item-padding-horizontal;
white-space: nowrap; // prevent links from breaking onto new lines
min-width: 100%;
text-align: left;

View File

@ -0,0 +1,13 @@
.loading {
&.modal {
.spinner {
height: calc(100% - #{$font-size-base});
}
.modal-body {
height: $modal-xs;
overflow: hidden;
}
}
}

View File

@ -1,68 +1,36 @@
.modal.fullscreen .modal-dialog {
width: 90%;
margin: auto;
left: 5%;
}
.modal.loading .modal-dialog {
width: 170px;
.modal-body {
height: 170px;
}
}
.modal.loading p {
text-align: center;
position: absolute;
bottom: 0;
width: 150px;
}
.modal-body {
overflow-y: visible;
max-height: none;
overflow-y: auto;
max-height: calc(100vh - #{$bs-modal-height});
// Subtract the help-button width and add the modal inner padding
// because the element is in the place which is applied the modal inner padding.
.step-description {
width: calc(100% - (#{$padding-xs-horizontal} * 2 + #{$font-size-h3}) + #{$modal-inner-padding});
}
@media (max-width: $screen-sm-min) {
max-height: calc(100vh - #{$bs-modal-height-small-screen});
}
textarea {
resize: vertical;
}
table {
margin-bottom: 30px;
}
& ~ hr {
margin-bottom: 0;
}
& > .nav-pills {
padding-bottom: $padding-base-horizontal;
}
}
.modal-footer {
.footer-row {
margin-right: 0;
margin-left: 0;
// Custom Horizon Modal Sizes
@media (min-width: $screen-sm-min) {
.modal-xs {
width: $modal-xs;
}
}
.modal-body {
.modal-footer {
width: 670px;
margin-left: -25px;
margin-right: -15px;
@media (min-width: $screen-md-min) {
.modal-xl {
width: $modal-xl;
}
.help-block {
text-align: left;
float: left;
width: 100%;
margin-bottom: 10px;
}
// Subtract the help-button width and add the modal inner padding
// because the element is in the place which is applied the modal inner padding.
.step-description {
width: calc(100% - (#{$padding-xs-horizontal} * 2 + #{$font-size-h3}) + #{$modal-inner-padding});
}
}

View File

@ -25,6 +25,7 @@
@import "components/help_panel";
@import "components/icons";
@import "components/inline_edit";
@import "components/loader";
@import "components/login";
@import "components/membership";
@import "components/messages";

View File

@ -16,7 +16,7 @@
}
.dropdown-menu > li > a {
padding-left: $dropdown-item-padding-horizontal;
padding-left: $bs-dropdown-item-padding-horizontal;
display: flex;
}

View File

@ -1,30 +1,11 @@
// Controls the size of the "?" icon on the right of the wizards
// Position the toggle above the content a bit so it doesn't
// cover up the words
$material-toggle-offset: -$font-size-h3;
.help-toggle {
z-index: 3;
&:not(.collapsed) {
&,
&:hover,
&:active,
&:focus {
margin-top: $material-toggle-offset;
margin-right: -$padding-xs-horizontal/2;
}
}
&,
&:active:hover,
&:hover,
&:active,
&:focus {
margin-top: $material-toggle-offset;
margin-right: -$padding-xs-horizontal/2;
background: none;
box-shadow: none;
color: $brand-primary;
@ -32,8 +13,6 @@ $material-toggle-offset: -$font-size-h3;
}
#help-panel {
margin-top: $material-toggle-offset;
// Material is all about depth, lets add some
& > .well {
@extend .panel;

View File

@ -0,0 +1,22 @@
{% extends "horizon/client_side/template.html" %}
{% load i18n horizon %}
{% block id %}confirm_modal{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="confirm-wrapper">
<div class="confirm-help alert alert-warning">
<span class="fa fa-warning"></span>
<strong>{% trans 'Warning: ' %}</strong>
<span>[[help]]</span>
</div>
<div class="confirm-list">
<span>{% trans 'You have selected:' %}</span>
<ul>
[[#selection_list]]
<li>[[.]]</li>
[[/selection_list]]
</ul>
</div>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}

View File

@ -0,0 +1,13 @@
---
prelude: >
Modal sizes now inherit from Bootstrap's theme
variables.
features:
- Modal sizes now inherit their value from theme
variables. Two additional sizes are available now
for use in Horizon, extra to the standard 3 sizes
of Bootstrap Modals, modal-xs and modal-xl.
deprecations:
- Fullscreen Modals have been deprecated in favor of
modal-xl. Currently, it is set to 95% of the viewable
screen width.