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

View File

@ -226,59 +226,81 @@ horizon.datatables = {
}; };
/* Generates a confirmation modal dialog for the given action. */ /* Generates a confirmation modal dialog for the given action. */
horizon.datatables.confirm = function (action) { horizon.datatables.confirm = function(action) {
var $action = $(action), var $action = $(action);
$modal_parent = $(action).closest('.modal'),
name_array = [], if ($action.hasClass("disabled")) {
closest_table_id, action_string, name_string,
help_text,
title, body, modal, form;
if($action.hasClass("disabled")) {
return; return;
} }
action_string = $action.text();
help_text = $action.attr("help_text") || ""; var $modal_parent = $action.closest('.modal');
name_string = ""; 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) // 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 // Check if data-display attribute is available
if ($("#"+closest_table_id+" tr[data-display]").length > 0) { var $data_display = $closest_table.find('tr[data-display]');
var actions_div = $(action).closest("div"); if ($data_display.length > 0) {
if(actions_div.hasClass("table_actions") || actions_div.hasClass("table_actions_menu")) { var $actions_div = $action.closest("div");
if ($actions_div.hasClass("table_actions") || $actions_div.hasClass("table_actions_menu")) {
// One or more checkboxes selected // 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.push(" \"" + $(this).attr("data-display") + "\"");
}); });
name_array.join(", "); name_string = name_array.join(", ");
name_string = name_array.toString();
} else { } else {
// If no checkbox is selected // 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; var title = interpolate(gettext("Confirm %s"), [action_string]);
modal = horizon.modals.create(title, body, 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(); modal.modal();
if($modal_parent.length) {
if ($modal_parent.length) {
var child_backdrop = modal.next('.modal-backdrop'); var child_backdrop = modal.next('.modal-backdrop');
// re-arrange z-index for these stacking modal // re-arrange z-index for these stacking modal
child_backdrop.css('z-index', $modal_parent.css('z-index')+10); child_backdrop.css('z-index', $modal_parent.css('z-index')+10);
modal.css('z-index', child_backdrop.css('z-index')+10); modal.css('z-index', child_backdrop.css('z-index')+10);
} }
modal.find('.btn-primary').click(function () { modal.find('.btn-primary').click(function () {
form = $action.closest('form'); var $form = $action.closest('form');
var el = document.createElement("input"); var el = document.createElement("input");
el.type='hidden'; el.type = 'hidden';
el.name = $action.attr('name'); el.name = $action.attr('name');
el.value = $action.attr('value'); el.value = $action.attr('value');
form.append(el); $form
form.submit(); .append(el)
.submit();
modal.modal('hide'); modal.modal('hide');
horizon.modals.modal_spinner(gettext("Working")); horizon.modals.modal_spinner(gettext("Working"));
return false; return false;
}); });
return modal; return modal;
}; };

View File

@ -1,7 +1,14 @@
/* global Hogan */ /* global Hogan */
/* Namespace for core functionality related to client-side templating. */ /* Namespace for core functionality related to client-side templating. */
horizon.templates = { 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: {} 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 %} {% block template %}{% spaceless %}{% jstemplate %}
<div class="modal loading"> <div class="modal loading">
<div class="modal-dialog"> <div class="modal-dialog modal-xs">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
<p>[[text]]&hellip;</p> <p class="text-center">[[text]]&hellip;</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -14,7 +14,7 @@
<h3 class="modal-title">[[title]]</h3> <h3 class="modal-title">[[title]]</h3>
</div> </div>
<div class='modal-body'> <div class='modal-body'>
[[body]] [[[body]]]
</div> </div>
<div class='modal-footer'> <div class='modal-footer'>
<a href='#' class='btn btn-default cancel' data-dismiss='modal'>[[cancel]]</a> <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/_table_row.html" %}
{% include "horizon/client_side/_alert_message.html" %} {% include "horizon/client_side/_alert_message.html" %}
{% include "horizon/client_side/_loading.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") flow = TestWorkflow(req, entry_point="test_action_two")
self.assertEqual("test_action_two", flow.get_entry_point()) 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" Whether to present the workflow as a wizard, with "prev" and "next"
buttons and validation after every step. 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 slug = None
default_steps = () default_steps = ()
@ -620,7 +613,6 @@ class Workflow(html.HTMLElement):
redirect_param_name = "next" redirect_param_name = "next"
multipart = False multipart = False
wizard = False wizard = False
fullscreen = False
_registerable_class = Step _registerable_class = Step
def __str__(self): def __str__(self):

View File

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

View File

@ -1305,29 +1305,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-10 col-lg-offset-2">
<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">
<h2 translate>Popovers</h2> <h2 translate>Popovers</h2>
<div class="bs-component"> <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> <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>
</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>
<div id="source-modal" class="modal fade"> <div id="source-modal" class="modal fade">

View File

@ -12,10 +12,6 @@ themepreview {
position: relative; position: relative;
} }
.bs-component .modal-dialog {
width: 90%;
}
.bs-component .modal { .bs-component .modal {
bottom: auto; bottom: auto;
display: block; 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 /* When used with Horizon via Django, this value is set automatically from
settings.py and is added dynamically to the namespace through settings.py and is added dynamically to the namespace through
horizon/utils/scss_filter.py */ horizon/utils/scss_filter.py */
@ -71,12 +75,6 @@ $members-list-item-width: 130px !default;
$members-list-item-max-width: 327px !default; $members-list-item-max-width: 327px !default;
$members-list-roles-width: 125px !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 // This defines the max-width for a breadcrumb item before it will be truncated
$breadcrumb-item-width: 15em !default; $breadcrumb-item-width: 15em !default;

View File

@ -11,7 +11,7 @@
box-shadow: none; box-shadow: none;
border-radius: 0; border-radius: 0;
margin: 0; // prevent the form-inline styles from messing with margin 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 white-space: nowrap; // prevent links from breaking onto new lines
min-width: 100%; min-width: 100%;
text-align: left; 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 { .modal-body {
overflow-y: visible; overflow-y: auto;
max-height: none; 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 { textarea {
resize: vertical; resize: vertical;
} }
table {
margin-bottom: 30px;
}
& ~ hr {
margin-bottom: 0;
}
& > .nav-pills { & > .nav-pills {
padding-bottom: $padding-base-horizontal; padding-bottom: $padding-base-horizontal;
} }
} }
.modal-footer { // Custom Horizon Modal Sizes
.footer-row { @media (min-width: $screen-sm-min) {
margin-right: 0; .modal-xs {
margin-left: 0; width: $modal-xs;
} }
} }
.modal-body { @media (min-width: $screen-md-min) {
.modal-footer { .modal-xl {
width: 670px; width: $modal-xl;
margin-left: -25px;
margin-right: -15px;
} }
.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/help_panel";
@import "components/icons"; @import "components/icons";
@import "components/inline_edit"; @import "components/inline_edit";
@import "components/loader";
@import "components/login"; @import "components/login";
@import "components/membership"; @import "components/membership";
@import "components/messages"; @import "components/messages";

View File

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

View File

@ -1,30 +1,11 @@
// Controls the size of the "?" icon on the right of the wizards // 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 { .help-toggle {
z-index: 3;
&:not(.collapsed) {
&,
&:hover,
&:active,
&:focus {
margin-top: $material-toggle-offset;
margin-right: -$padding-xs-horizontal/2;
}
}
&, &,
&:active:hover, &:active:hover,
&:hover, &:hover,
&:active, &:active,
&:focus { &:focus {
margin-top: $material-toggle-offset;
margin-right: -$padding-xs-horizontal/2;
background: none; background: none;
box-shadow: none; box-shadow: none;
color: $brand-primary; color: $brand-primary;
@ -32,8 +13,6 @@ $material-toggle-offset: -$font-size-h3;
} }
#help-panel { #help-panel {
margin-top: $material-toggle-offset;
// Material is all about depth, lets add some // Material is all about depth, lets add some
& > .well { & > .well {
@extend .panel; @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.