Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove fixed layout, introduce back to top component #687

Merged
merged 3 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions __tests__/back-to-top.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-env jest */
const configPaths = require('../config/paths.json')
const PORT = configPaths.testPort

let browser
let page
let baseUrl = 'http://localhost:' + PORT

beforeAll(async (done) => {
browser = global.browser
page = await browser.newPage()
await page.evaluateOnNewDocument(() => {
window.__TESTS_RUNNING = true
})
done()
})

afterAll(async (done) => {
await page.close()
done()
})

const BACK_TO_TOP_LINK_SELECTOR = '[data-module="app-back-to-top"] a'

describe('Back to top', () => {
it('is always visible when JavaScript is disabled', async () => {
await page.setJavaScriptEnabled(false)
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
const isBackToTopVisible = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true })
expect(isBackToTopVisible).toBeTruthy()
})
it('is hidden when at the top of the page', async () => {
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
const isBackToTopHidden = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: false })
expect(isBackToTopHidden).toBeTruthy()
})
it('is visible when at the bottom of the page', async () => {
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
// Scroll to the bottom of the page
await page.evaluate(() => window.scrollBy(0, document.body.scrollHeight))
const isBackToTopVisible = await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true })
expect(isBackToTopVisible).toBeTruthy()
})
it('goes back to the top of the page when interacted with', async () => {
await page.goto(`${baseUrl}/styles/colour/`, { waitUntil: 'load' })
// Scroll to the bottom of the page
await page.evaluate(() => window.scrollBy(0, document.body.scrollHeight))
// Make sure the back to top component is available to click
await page.waitForSelector(BACK_TO_TOP_LINK_SELECTOR, { visible: true })
await page.click(BACK_TO_TOP_LINK_SELECTOR)
const isAtTopOfPage = await page.evaluate(() => window.scrollY === 0)
expect(isAtTopOfPage).toBeTruthy()
})
})
6 changes: 6 additions & 0 deletions src/javascripts/application.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import BackToTop from './components/back-to-top.js'
import common from 'govuk-frontend/common'
import CookieBanner from './components/cookie-banner.js'
import Example from './components/example.js'
Expand Down Expand Up @@ -39,3 +40,8 @@ new MobileNav().init()
// Initialise search
var $searchContainer = document.querySelector('[data-module="app-search"]')
new Search($searchContainer).init()

// Initialise back to top
var $backToTop = document.querySelector('[data-module="app-back-to-top"]')
var $observedElement = document.querySelector('.app-subnav')
new BackToTop($backToTop, { $observedElement: $observedElement }).init()
62 changes: 62 additions & 0 deletions src/javascripts/components/back-to-top.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'govuk-frontend/vendor/polyfills/Function/prototype/bind'

function BackToTop ($module, options) {
this.$module = $module
this.$observedElement = options.$observedElement
this.intersectionRatio = 0
}

BackToTop.prototype.init = function () {
var $observedElement = this.$observedElement

// If there's no element for the back to top to follow, exit early.
if (!$observedElement) {
return
}

if (!('IntersectionObserver' in window)) {
// If there's no support fallback to regular sticky behaviour
return this.update()
}

// Create new IntersectionObserver
var observer = new window.IntersectionObserver(function (entries) {
// Available data when an intersection happens
// Back to top visibility
// Element enters the viewport
if (entries[0].intersectionRatio !== 0) {
// How much of the element is visible
this.intersectionRatio = entries[0].intersectionRatio
// Element leaves the viewport
} else {
this.intersectionRatio = 0
}
this.update()
}.bind(this), {
// Call the observer, when the element enters the viewport,
// when 25%, 50%, 75% and the whole element are visible
threshold: [0, 0.25, 0.5, 0.75, 1]
})

observer.observe($observedElement)
}

BackToTop.prototype.update = function () {
var thresholdPercent = (this.intersectionRatio * 100)

if (thresholdPercent === 100) {
this.hide()
} else if (thresholdPercent < 90) {
this.show()
}
}

BackToTop.prototype.hide = function () {
this.$module.classList.add('app-back-to-top--hidden')
}

BackToTop.prototype.show = function () {
this.$module.classList.remove('app-back-to-top--hidden')
}

export default BackToTop
21 changes: 21 additions & 0 deletions src/stylesheets/components/_back-to-top.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.app-back-to-top {
position: -webkit-sticky; // Needed for Safari on OSX
position: sticky; // sass-lint:disable-line no-duplicate-properties
top: govuk-spacing(6);
margin-bottom: govuk-spacing(6);
}

.app-back-to-top__icon {
display: inline-block;
width: .8em;
height: 1em;
margin-top: -(govuk-spacing(1));
margin-right: govuk-spacing(2);
vertical-align: middle;
}

@supports (position: sticky) {
.js-enabled .app-back-to-top--hidden {
display: none;
}
}
12 changes: 12 additions & 0 deletions src/stylesheets/components/_banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.app-phase-banner {
@include govuk-media-query($until: tablet) {
margin-right: 0;
margin-left: 0;
padding-right: govuk-spacing(3);
padding-left: govuk-spacing(3);
}

@include govuk-media-query($from: tablet) {
border-bottom: 0;
}
}
6 changes: 0 additions & 6 deletions src/stylesheets/components/_cookie-banner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,14 @@
width: 100%;

padding-top: govuk-spacing(3);
padding-right: govuk-spacing(3);
padding-bottom: govuk-spacing(3);
padding-left: govuk-spacing(3);
background-color: lighten(desaturate(govuk-colour("light-blue"), 8.46), 42.55);
}

.app-cookie-banner {
display: none;
}

.app-cookie-banner__message {
margin: 0;
}

@include govuk-media-query($media-type: print) {
.app-cookie-banner {
display: none !important;
Expand Down
7 changes: 0 additions & 7 deletions src/stylesheets/components/_footer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@
// GOV.UK Frontend footer adapted for full width

@include govuk-exports("app-footer") {
.app-footer {
@include govuk-media-query($from: tablet) {
display: flex;
flex-direction: column;
flex: 1 0 auto;
}
}

.app-width-container--full {
min-width: calc(100% - #{$govuk-gutter * 2});
Expand Down
7 changes: 6 additions & 1 deletion src/stylesheets/components/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
@include govuk-exports("app-header") {

.app-header {
padding: govuk-spacing(2) govuk-spacing(3);
box-sizing: border-box;
width: 100%;
border-bottom: 10px solid govuk-colour("blue");
color: govuk-colour("white");
background: govuk-colour("black");
@include govuk-clearfix;

@include govuk-media-query($from: desktop) {
padding: govuk-spacing(2) 0;
}
}

.app-header__logotype {
Expand Down
10 changes: 6 additions & 4 deletions src/stylesheets/components/_navigation.scss
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
.app-navigation {
$navigation-height: 53px;
padding-right: govuk-spacing(3);
padding-left: govuk-spacing(3);
background-color: $app-light-grey;
box-sizing: border-box;
@include govuk-font(19, $weight: bold);
width: 100%;

@include govuk-media-query($until: tablet) {
display: none;
}

@include govuk-media-query($from: tablet) {
margin-left: -(govuk-spacing(3));
}

&__list {
margin: 0;
padding: 0;
list-style: none;

Expand Down
37 changes: 7 additions & 30 deletions src/stylesheets/components/_pane.scss
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
@include govuk-exports("app-pane") {
$toc-width: 300px;
$toc-width: 260px;
$toc-width-tablet: 210px;

.app-pane.app-pane--enabled {
$pane-height: 100vh;
overflow: hidden;

@include govuk-media-query($from: tablet) {
display: flex;
flex-direction: column;
}

@include govuk-media-query($from: tablet, $and: "(orientation: portrait)") {
height: $pane-height;
}

@include govuk-media-query($from: desktop) {
height: $pane-height;
}
}

.app-pane__header {
Expand All @@ -35,6 +24,7 @@
.app-pane__nav {
@include govuk-media-query($from: tablet) {
display: flex;
background-color: $app-light-grey;
flex-direction: column;
flex: 1 0 auto;
}
Expand All @@ -45,22 +35,17 @@
display: flex;
position: relative;
min-height: 0;
overflow: hidden;
flex: 1 1 100%;
overflow: inherit;
}

> * {
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}
@include govuk-media-query(1160px) {
width: 100%;
}
}

.app-pane__subnav {
border-right: 1px solid $govuk-border-colour;
@include govuk-media-query($from: tablet) {
width: $toc-width-tablet;
border-right: 1px solid $govuk-border-colour;
flex: 0 0 auto;
}
@include govuk-media-query($from: desktop) {
Expand All @@ -72,15 +57,8 @@
@include govuk-media-query($from: tablet) {
display: flex;
min-width: 0;
margin-left: auto;
flex: 1 1 auto;
flex: 1 1 100%;
flex-direction: column;

// Stick footer to bottom of screen if content is shorter than viewport
main {
display: block;
flex: 1 0 auto;
}
}
}

Expand All @@ -105,7 +83,6 @@
.app-pane__content {
margin-left: -1px;
overflow-x: hidden;
border-left: 1px solid $govuk-border-colour;
}
}
}
6 changes: 5 additions & 1 deletion src/stylesheets/components/_subnav.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
@include govuk-exports("app-subnav") {

.app-subnav {
padding: govuk-spacing(3);
padding: govuk-spacing(6) govuk-spacing(3) 0 0;
@include govuk-font(16);

@include govuk-media-query($from: tablet) {
margin-left: -(govuk-spacing(3));
}
}

.app-subnav__section {
Expand Down
26 changes: 10 additions & 16 deletions src/stylesheets/main.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
$govuk-page-width: 1100px !default;

@import "govuk-frontend/all";

// App-specific variables
$app-light-grey: #f8f8f8;
$app-code-color: #dd1144;

// App-specific components
@import "components/back-to-top";
@import "components/banner";
@import "components/contact-panel";
@import "components/cookie-banner";
@import "components/example";
Expand Down Expand Up @@ -46,21 +50,6 @@ body {
}
}

// Mirrors Bootstrap 4 - open for discussion
$app-breakpoint-widescreen: 1200px;

// This will be coming to FE later but making it app specific for now
.app-site-width-container {
@media (min-width: $app-breakpoint-widescreen) {
max-width: 1100px;
}
}

// This layout is currently used on error pages like 404
.app-site-width-container--constraint {
max-width: 960px;
}

// This is consistent with Elements - will be changed in FE
[class*="govuk-grid-column"] {
@include govuk-media-query($until: desktop) {
Expand All @@ -79,7 +68,12 @@ $app-breakpoint-widescreen: 1200px;

.app-content {

@include govuk-responsive-padding(6);
padding: govuk-spacing(3) govuk-spacing(0);

@include govuk-media-query($from: tablet) {
padding: govuk-spacing(6);
padding-right: 0;
}

h1 {
max-width: 15em;
Expand Down
Loading