mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-28 06:11:03 -05:00
Move service and user nav to proposition header
This commit moves user-related navigation into the proposition header (the black bar) at the top of the site. It adds some custom SASS to override GOV.UK template and align these navigation items to the right (because it looks better). It then removes the service chooser dropdown (and its associated SASS and JS) in favour of a link alongside the user-related navigation items. ‘Switch service’ is the best language for this that we’ve come up with so far. This means that the only way of adding a new service is from the `/services` page. So this commit removes the redirect if you land on this page with only one service (else it would prevent you from ever being able to add more).
This commit is contained in:
@@ -1,199 +0,0 @@
|
||||
// Copied from:
|
||||
// https://github.com/alphagov/govuk_elements/blob/4926897dc7734db2fc5e5ebb6acdc97f86e22e50/public/javascripts/vendor/details.polyfill.js
|
||||
|
||||
// When this is moved to GOV.UK Frontend Toolkit, we should import it from there
|
||||
// instead
|
||||
|
||||
// <details> polyfill
|
||||
// http://caniuse.com/#feat=details
|
||||
|
||||
// FF Support for HTML5's <details> and <summary>
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=591737
|
||||
|
||||
// http://www.sitepoint.com/fixing-the-details-element/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var NATIVE_DETAILS = typeof document.createElement('details').open === 'boolean';
|
||||
|
||||
// Add event construct for modern browsers or IE
|
||||
// which fires the callback with a pre-converted target reference
|
||||
function addEvent(node, type, callback) {
|
||||
if (node.addEventListener) {
|
||||
node.addEventListener(type, function (e) {
|
||||
callback(e, e.target);
|
||||
}, false);
|
||||
} else if (node.attachEvent) {
|
||||
node.attachEvent('on' + type, function (e) {
|
||||
callback(e, e.srcElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cross-modal click events
|
||||
function addClickEvent(node, callback) {
|
||||
// Prevent space(32) from scrolling the page
|
||||
addEvent(node, 'keypress', function (e, target) {
|
||||
if (target.nodeName === 'SUMMARY') {
|
||||
if (e.keyCode === 32) {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
e.returnValue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// When the key comes up - check if it is enter(13) or space(32)
|
||||
addEvent(node, 'keyup', function (e, target) {
|
||||
if (e.keyCode === 13 || e.keyCode === 32) { callback(e, target); }
|
||||
});
|
||||
addEvent(node, 'mouseup', function (e, target) {
|
||||
callback(e, target);
|
||||
});
|
||||
}
|
||||
|
||||
// Get the nearest ancestor element of a node that matches a given tag name
|
||||
function getAncestor(node, match) {
|
||||
do {
|
||||
if (!node || node.nodeName.toLowerCase() === match) {
|
||||
break;
|
||||
}
|
||||
} while (node = node.parentNode);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Create a started flag so we can prevent the initialisation
|
||||
// function firing from both DOMContentLoaded and window.onload
|
||||
var started = false;
|
||||
|
||||
// Initialisation function
|
||||
function addDetailsPolyfill(list) {
|
||||
|
||||
// If this has already happened, just return
|
||||
// else set the flag so it doesn't happen again
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
started = true;
|
||||
|
||||
// Get the collection of details elements, but if that's empty
|
||||
// then we don't need to bother with the rest of the scripting
|
||||
if ((list = document.getElementsByTagName('details')).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// else iterate through them to apply their initial state
|
||||
var n = list.length, i = 0;
|
||||
for (i; i < n; i++) {
|
||||
var details = list[i];
|
||||
|
||||
// Save shortcuts to the inner summary and content elements
|
||||
details.__summary = details.getElementsByTagName('summary').item(0);
|
||||
details.__content = details.getElementsByTagName('div').item(0);
|
||||
|
||||
// If the content doesn't have an ID, assign it one now
|
||||
// which we'll need for the summary's aria-controls assignment
|
||||
if (!details.__content.id) {
|
||||
details.__content.id = 'details-content-' + i;
|
||||
}
|
||||
|
||||
// Add ARIA role="group" to details
|
||||
details.setAttribute('role', 'group');
|
||||
|
||||
// Add role=button to summary
|
||||
details.__summary.setAttribute('role', 'button');
|
||||
|
||||
// Add aria-controls
|
||||
details.__summary.setAttribute('aria-controls', details.__content.id);
|
||||
|
||||
// Set tabIndex so the summary is keyboard accessible for non-native elements
|
||||
// http://www.saliences.com/browserBugs/tabIndex.html
|
||||
if (!NATIVE_DETAILS) {
|
||||
details.__summary.tabIndex = 0;
|
||||
}
|
||||
|
||||
// Detect initial open state
|
||||
var openAttr = details.getAttribute('open') !== null;
|
||||
if (openAttr === true) {
|
||||
details.__summary.setAttribute('aria-expanded', 'true');
|
||||
details.__content.setAttribute('aria-hidden', 'false');
|
||||
} else {
|
||||
details.__summary.setAttribute('aria-expanded', 'false');
|
||||
details.__content.setAttribute('aria-hidden', 'true');
|
||||
if (!NATIVE_DETAILS) {
|
||||
details.__content.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Create a circular reference from the summary back to its
|
||||
// parent details element, for convenience in the click handler
|
||||
details.__summary.__details = details;
|
||||
|
||||
// If this is not a native implementation, create an arrow
|
||||
// inside the summary
|
||||
if (!NATIVE_DETAILS) {
|
||||
|
||||
var twisty = document.createElement('i');
|
||||
|
||||
if (openAttr === true) {
|
||||
twisty.className = 'arrow arrow-open';
|
||||
twisty.appendChild(document.createTextNode('\u25bc'));
|
||||
} else {
|
||||
twisty.className = 'arrow arrow-closed';
|
||||
twisty.appendChild(document.createTextNode('\u25ba'));
|
||||
}
|
||||
|
||||
details.__summary.__twisty = details.__summary.insertBefore(twisty, details.__summary.firstChild);
|
||||
details.__summary.__twisty.setAttribute('aria-hidden', 'true');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Define a statechange function that updates aria-expanded and style.display
|
||||
// Also update the arrow position
|
||||
function statechange(summary) {
|
||||
|
||||
var expanded = summary.__details.__summary.getAttribute('aria-expanded') === 'true';
|
||||
var hidden = summary.__details.__content.getAttribute('aria-hidden') === 'true';
|
||||
|
||||
summary.__details.__summary.setAttribute('aria-expanded', (expanded ? 'false' : 'true'));
|
||||
summary.__details.__content.setAttribute('aria-hidden', (hidden ? 'false' : 'true'));
|
||||
|
||||
if (!NATIVE_DETAILS) {
|
||||
summary.__details.__content.style.display = (expanded ? 'none' : '');
|
||||
|
||||
var hasOpenAttr = summary.__details.getAttribute('open') !== null;
|
||||
if (!hasOpenAttr) {
|
||||
summary.__details.setAttribute('open', 'open');
|
||||
} else {
|
||||
summary.__details.removeAttribute('open');
|
||||
}
|
||||
}
|
||||
|
||||
if (summary.__twisty) {
|
||||
summary.__twisty.firstChild.nodeValue = (expanded ? '\u25ba' : '\u25bc');
|
||||
summary.__twisty.setAttribute('class', (expanded ? 'arrow arrow-closed' : 'arrow arrow-open'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bind a click event to handle summary elements
|
||||
addClickEvent(document, function(e, summary) {
|
||||
if (!(summary = getAncestor(summary, 'summary'))) {
|
||||
return true;
|
||||
}
|
||||
return statechange(summary);
|
||||
});
|
||||
}
|
||||
|
||||
// Bind two load events for modern and older browsers
|
||||
// If the first one fires it will set a flag to block the second one
|
||||
// but if it's not supported then the second one will fire
|
||||
addEvent(document, 'DOMContentLoaded', addDetailsPolyfill);
|
||||
addEvent(window, 'load', addDetailsPolyfill);
|
||||
|
||||
})();
|
||||
@@ -21,8 +21,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.phase-banner-beta {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.phase-tag {
|
||||
@include phase-tag(beta);
|
||||
@include phase-tag(beta);
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
@@ -44,6 +48,18 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@include media(desktop) {
|
||||
|
||||
#proposition-menu {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#global-header .header-proposition #proposition-links li {
|
||||
padding: 0 0 0 15px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: $link-colour;
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
%dropdown,
|
||||
.dropdown {
|
||||
|
||||
position: relative;
|
||||
|
||||
&-toggle {
|
||||
|
||||
color: $link-colour;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
outline-offset: 2px;
|
||||
|
||||
&:hover {
|
||||
color: $link-hover-colour;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
padding-left: 20px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
.management-navigation {
|
||||
|
||||
@extend %site-width-container;
|
||||
@include core-16();
|
||||
border-bottom: 1px solid $border-colour;
|
||||
padding: 10px 0 8px;
|
||||
|
||||
a {
|
||||
|
||||
&:link,
|
||||
&:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $link-hover-colour;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-account {
|
||||
|
||||
@include media(tablet) {
|
||||
|
||||
text-align: right;
|
||||
|
||||
a {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
padding: 3px 0 0 0;
|
||||
|
||||
li {
|
||||
@include core-16();
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
ul, h2 {
|
||||
@include core-16;
|
||||
border-bottom: 1px solid $border-colour;
|
||||
margin: 10px 20px 0 0;
|
||||
list-style-type: none;
|
||||
padding: 0 0 8px 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 7px 0 0 0;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
@import 'components/banner';
|
||||
@import 'components/textbox';
|
||||
@import 'components/browse-list';
|
||||
@import 'components/management-navigation';
|
||||
@import 'components/dropdown';
|
||||
@import 'components/email-message';
|
||||
@import 'components/api-key';
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.main.forms import AddServiceForm
|
||||
def add_service():
|
||||
form = AddServiceForm(services_dao.find_all_service_names)
|
||||
services = services_dao.get_services(current_user.id)
|
||||
if len(services) > 0:
|
||||
if len(services) == 0:
|
||||
heading = 'Set up notifications for your service'
|
||||
else:
|
||||
heading = 'Add a new service'
|
||||
|
||||
@@ -8,12 +8,6 @@ from app.main import main
|
||||
@login_required
|
||||
def choose_service():
|
||||
services = services_dao.get_services(current_user.id)
|
||||
# If there is only one service redirect
|
||||
# to the service dashboard.
|
||||
if len(services['data']) == 1:
|
||||
session['service_name'] = services['data'][0]['name']
|
||||
return redirect(url_for(
|
||||
'main.service_dashboard', service_id=services['data'][0]['id']))
|
||||
return render_template(
|
||||
'views/choose-service.html',
|
||||
services=[services_dao.ServicesBrowsableItem(x) for x in services['data']])
|
||||
|
||||
@@ -28,10 +28,26 @@
|
||||
{% block header_class %}with-proposition{% endblock %}
|
||||
{% block proposition_header %}
|
||||
<div class="header-proposition">
|
||||
<div class='content'>
|
||||
<div class="content">
|
||||
<div class="phase-banner-beta">
|
||||
<strong class="phase-tag">BETA</strong>
|
||||
</div>
|
||||
{% if current_user.is_authenticated() %}
|
||||
<a href="#proposition-links" class="js-header-toggle menu">Menu</a>
|
||||
<nav id="proposition-menu">
|
||||
<ul id="proposition-links">
|
||||
<li>
|
||||
<a href="{{ url_for('main.user_profile') }}">{{ current_user.name }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('main.choose_service') }}">Switch service</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('main.sign_out')}}">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -48,28 +64,6 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if current_user.is_authenticated() and session['service_name'] != None %}
|
||||
<nav class="management-navigation">
|
||||
<div class="grid-row">
|
||||
<div class="column-half">
|
||||
<details class="dropdown">
|
||||
<summary class="dropdown-toggle">
|
||||
{{ session.get('service_name', 'Choose service') }}
|
||||
</summary>
|
||||
<div>
|
||||
<a href="{{ url_for('main.choose_service') }}">Switch service</a>
|
||||
<a href="{{ url_for('main.add_service') }}">Add a new service to GOV.UK Notify</a>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="column-half management-navigation-account">
|
||||
<a href="{{ url_for('main.user_profile') }}">{{ current_user.name }}</a>
|
||||
<a href="{{ url_for('main.sign_out')}}">Sign out</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<main id="content" role="main" class="page-container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<nav class="navigation">
|
||||
<h2 class="navigation-service-name">
|
||||
{{ session.get('service_name', 'Choose service') }}
|
||||
</h2>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.service_dashboard', service_id=service_id) }}">Dashboard</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -34,7 +34,6 @@ gulp.task('javascripts', () => gulp
|
||||
.src([
|
||||
paths.npm + 'govuk_frontend_toolkit/javascripts/govuk/modules.js',
|
||||
paths.npm + 'govuk_frontend_toolkit/javascripts/govuk/selection-buttons.js',
|
||||
paths.src + 'javascripts/detailsPolyfill.js',
|
||||
paths.src + 'javascripts/apiKey.js',
|
||||
paths.src + 'javascripts/highlightTags.js',
|
||||
paths.src + 'javascripts/main.js'
|
||||
|
||||
@@ -14,7 +14,7 @@ def test_get_should_render_add_service_template(app_,
|
||||
client.login(api_user_active)
|
||||
response = client.get(url_for('main.add_service'))
|
||||
assert response.status_code == 200
|
||||
assert 'Set up notifications for your service' in response.get_data(as_text=True)
|
||||
assert 'Add a new service' in response.get_data(as_text=True)
|
||||
|
||||
|
||||
def test_should_add_service_and_redirect_to_next_page(app_,
|
||||
|
||||
Reference in New Issue
Block a user