From 20a0caa647892315b72a7fbd24380f245373f0bc Mon Sep 17 00:00:00 2001 From: beeps Date: Fri, 9 May 2025 12:27:22 +0100 Subject: [PATCH 01/12] Implement breadcrumbs --- lib/nunjucks/globals.js | 55 +++++++++++++++++++++++++++++++++ views/layouts/_generic.njk | 2 ++ views/partials/_breadcrumbs.njk | 10 ++++++ 3 files changed, 67 insertions(+) create mode 100644 views/partials/_breadcrumbs.njk diff --git a/lib/nunjucks/globals.js b/lib/nunjucks/globals.js index 701508dd08..888aae822c 100644 --- a/lib/nunjucks/globals.js +++ b/lib/nunjucks/globals.js @@ -106,4 +106,59 @@ exports.getHTMLCode = function (path) { }) } +/** + * This helper function traverses the navigation object to find an item with a + * given `url`, and returns the ancestors of that item. + * + * NOTE: This is used to assemble breadcrumb navigation, so outputs the result + * with keys and values used by that component. These differ from those used + * internally. + * + * @param {string} targetUrl - The URL to look for, without leading `/` + * @returns {AncestorPage[]} Array of ancestor pages to this one + */ +exports.getAncestorPages = function (targetUrl) { + // Get navigation object from the template context + const navigationContext = this.lookup('navigation') ?? [] + + // Create a stack to store our levels of hierarchy in. + // The homepage doesn't appear in the `navigation` object, + // so it needs adding manually + const ancestors = [{ text: 'Home', href: '/' }] + + // Create a recursive function we can use to navigate the nested objects + const traverse = function (navItemArray) { + // Using a for instead of a forEach so that we can break the loop once done. + for (let i = 0; i < navItemArray.length; i++) { + const navItem = navItemArray[i] + + // If the target URL and item URL match, it's the current page. It doesn't + // get added to the stack, but it implies that we've reached our target + // so there's no reason to continue searching. + if (targetUrl === navItem.url) { + break + } + + // If the target URL *begins* with the current item URL, it's an ancestor + // of the target page. Add it to the stack and start looking through it's + // child items. + if (navItem.items && targetUrl.startsWith(navItem.url)) { + ancestors.push({ text: navItem.label, href: `/${navItem.url}` }) + traverse(navItem.items) + } + } + } + + // Call the recursive function + traverse(navigationContext) + + return ancestors +} + exports.getMacroOptions = getMacroOptions + +/** + * @typedef {object} AncestorPage + * @property {string} href - The URL of the ancestor page + * @property {string} text - The title of the ancestor page + */ diff --git a/views/layouts/_generic.njk b/views/layouts/_generic.njk index 84850c14e8..83d343a1be 100644 --- a/views/layouts/_generic.njk +++ b/views/layouts/_generic.njk @@ -46,6 +46,8 @@ {% include "_header.njk" %} {% include "_navigation.njk" %} {% include "_banner.njk" %} + {% include "_breadcrumbs.njk" %} + {% block body %} {{ contents | safe }} {% endblock %} diff --git a/views/partials/_breadcrumbs.njk b/views/partials/_breadcrumbs.njk new file mode 100644 index 0000000000..c6453fa835 --- /dev/null +++ b/views/partials/_breadcrumbs.njk @@ -0,0 +1,10 @@ +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} + +{# Only render breadcrumbs on pages that aren't the homepage. This is a bit +hacky, but works as the homepage has an empty string for a permalink. #} +{% if permalink %} + {{ govukBreadcrumbs({ + classes: "app-width-container", + items: getAncestorPages(permalink) + }) }} +{% endif %} From bef66aa0b6ac05a03864378a39031cd289219717 Mon Sep 17 00:00:00 2001 From: beeps Date: Fri, 16 May 2025 08:41:21 +0100 Subject: [PATCH 02/12] Move breadcrumbs to above page title --- src/stylesheets/main.scss | 22 +++++++++++++++++++--- views/layouts/_generic.njk | 1 - views/layouts/layout-pane.njk | 1 + views/partials/_breadcrumbs.njk | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/stylesheets/main.scss b/src/stylesheets/main.scss index 9e7baae46a..f05f5f4c0e 100644 --- a/src/stylesheets/main.scss +++ b/src/stylesheets/main.scss @@ -75,12 +75,28 @@ $app-code-color: #d13118; @include govuk-width-container(1100px); } +.app-breadcrumbs, .app-content { - padding: govuk-spacing(3) govuk-spacing(0) govuk-spacing(4); + padding-top: govuk-spacing(3); + padding-left: govuk-spacing(3); + + @include govuk-media-query($from: tablet) { + padding-top: govuk-spacing(6); + padding-left: govuk-spacing(6); + } +} + +.app-breadcrumbs { + // match top padding of the first item in the pane navigation so that the + // breadcrumbs line up nicely + margin-top: govuk-spacing(1); + margin-bottom: 0; +} + +.app-content { + padding-bottom: govuk-spacing(4); @include govuk-media-query($from: tablet) { - padding: govuk-spacing(6); - padding-right: 0; padding-bottom: govuk-spacing(8); } diff --git a/views/layouts/_generic.njk b/views/layouts/_generic.njk index 83d343a1be..0b4d1a85a8 100644 --- a/views/layouts/_generic.njk +++ b/views/layouts/_generic.njk @@ -46,7 +46,6 @@ {% include "_header.njk" %} {% include "_navigation.njk" %} {% include "_banner.njk" %} - {% include "_breadcrumbs.njk" %} {% block body %} {{ contents | safe }} diff --git a/views/layouts/layout-pane.njk b/views/layouts/layout-pane.njk index b6c8f98df3..a1100492b3 100644 --- a/views/layouts/layout-pane.njk +++ b/views/layouts/layout-pane.njk @@ -10,6 +10,7 @@ {% include "_subnav.njk" %}
+ {% include "_breadcrumbs.njk" %}
{% if not includeThemeInPageHeading %} diff --git a/views/partials/_breadcrumbs.njk b/views/partials/_breadcrumbs.njk index c6453fa835..3338cae0a6 100644 --- a/views/partials/_breadcrumbs.njk +++ b/views/partials/_breadcrumbs.njk @@ -4,7 +4,7 @@ hacky, but works as the homepage has an empty string for a permalink. #} {% if permalink %} {{ govukBreadcrumbs({ - classes: "app-width-container", + classes: "app-breadcrumbs", items: getAncestorPages(permalink) }) }} {% endif %} From 7dc3f2a6b64ae406a45e3c8be8dfcc75136aa83a Mon Sep 17 00:00:00 2001 From: beeps Date: Mon, 19 May 2025 11:40:02 +0100 Subject: [PATCH 03/12] Remove theme from page titles Remove theme from page titles, except for those on patterns, so that they don't duplicate the last item in the breadcrumbs. --- views/layouts/layout-pane.njk | 5 ----- 1 file changed, 5 deletions(-) diff --git a/views/layouts/layout-pane.njk b/views/layouts/layout-pane.njk index a1100492b3..f277e0a23f 100644 --- a/views/layouts/layout-pane.njk +++ b/views/layouts/layout-pane.njk @@ -12,11 +12,6 @@
{% include "_breadcrumbs.njk" %}
- {% if not includeThemeInPageHeading %} - - {{ theme if theme else section }} - - {% endif %}

{% if includeThemeInPageHeading %} From d20f34cddef6702d6e81c3d495b229a93795325e Mon Sep 17 00:00:00 2001 From: beeps Date: Mon, 19 May 2025 09:29:23 +0100 Subject: [PATCH 04/12] Add breadcrumbs to all layouts --- views/layouts/_generic.njk | 2 ++ views/layouts/layout-pane.njk | 5 ++++- views/layouts/layout-single-page.njk | 6 +++++- views/layouts/layout.njk | 17 ++++++++++++----- views/partials/_breadcrumbs.njk | 10 ---------- 5 files changed, 23 insertions(+), 17 deletions(-) delete mode 100644 views/partials/_breadcrumbs.njk diff --git a/views/layouts/_generic.njk b/views/layouts/_generic.njk index 0b4d1a85a8..37b9ef74ac 100644 --- a/views/layouts/_generic.njk +++ b/views/layouts/_generic.njk @@ -1,4 +1,6 @@ {% extends "govuk/template.njk" %} + +{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} {% from "_cookie-banner.njk" import cookieBanner %} {% set assetUrl = 'https://design-system.service.gov.uk/assets' %} diff --git a/views/layouts/layout-pane.njk b/views/layouts/layout-pane.njk index f277e0a23f..4da9dc7e05 100644 --- a/views/layouts/layout-pane.njk +++ b/views/layouts/layout-pane.njk @@ -10,7 +10,10 @@ {% include "_subnav.njk" %}

- {% include "_breadcrumbs.njk" %} + {{ govukBreadcrumbs({ + classes: "app-breadcrumbs", + items: getAncestorPages(permalink) + }) }}

{% if includeThemeInPageHeading %} diff --git a/views/layouts/layout-single-page.njk b/views/layouts/layout-single-page.njk index cd8f0c72dc..4cc5d6c546 100644 --- a/views/layouts/layout-single-page.njk +++ b/views/layouts/layout-single-page.njk @@ -1,6 +1,11 @@ {% extends "_generic.njk" %} {% block body %} + {{ govukBreadcrumbs({ + classes: "app-width-container", + items: getAncestorPages(permalink) + }) }} +
{{ contents | safe }} @@ -8,5 +13,4 @@
{% include "_footer.njk" %} - {% endblock %} diff --git a/views/layouts/layout.njk b/views/layouts/layout.njk index da639bead9..fae4e2317f 100644 --- a/views/layouts/layout.njk +++ b/views/layouts/layout.njk @@ -1,11 +1,18 @@ {% extends "_generic.njk" %} {% block body %} + {# Only render breadcrumbs on pages that aren't the homepage. This is a bit + hacky, but works as the homepage has an empty string for a permalink. #} + {% if permalink %} + {{ govukBreadcrumbs({ + classes: "app-width-container", + items: getAncestorPages(permalink) + }) }} + {% endif %} -
- {{ contents | safe }} -
- -{% include "_footer.njk" %} +
+ {{ contents | safe }} +
+ {% include "_footer.njk" %} {% endblock %} diff --git a/views/partials/_breadcrumbs.njk b/views/partials/_breadcrumbs.njk deleted file mode 100644 index 3338cae0a6..0000000000 --- a/views/partials/_breadcrumbs.njk +++ /dev/null @@ -1,10 +0,0 @@ -{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} - -{# Only render breadcrumbs on pages that aren't the homepage. This is a bit -hacky, but works as the homepage has an empty string for a permalink. #} -{% if permalink %} - {{ govukBreadcrumbs({ - classes: "app-breadcrumbs", - items: getAncestorPages(permalink) - }) }} -{% endif %} From e789972db34be17a58cf8b421efa757d7b8e61a8 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 23 May 2025 14:10:45 +0100 Subject: [PATCH 05/12] Align first subnav heading baseline with breadcrumbs' --- src/stylesheets/components/_subnav.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/stylesheets/components/_subnav.scss b/src/stylesheets/components/_subnav.scss index 250f3d9a17..b55081c32e 100644 --- a/src/stylesheets/components/_subnav.scss +++ b/src/stylesheets/components/_subnav.scss @@ -57,4 +57,10 @@ margin: 0; padding: govuk-spacing(2) govuk-spacing(3) govuk-spacing(2) 0; color: govuk-colour("dark-grey"); + + // Avoid padding on the first heading so its baseline is close + // enough to the breadcrumbs' baseline that they look aligned + &:first-of-type { + padding-top: 0; + } } From 445159096c9a5958955932dea6a857a08c853e6e Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 23 May 2025 14:24:20 +0100 Subject: [PATCH 06/12] Move control of overall padding of side pane content The fact that we needed to apply padding to both `app-breadcrumbs` and `app-content` was a sign that the padding was not applied to the right elements and should instead be applied to their parent. This means that whatever gets added inside `app-side-pane__content`, the right amount of space will surround it. --- src/stylesheets/components/_split-pane.scss | 7 +++++++ src/stylesheets/main.scss | 17 ----------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/stylesheets/components/_split-pane.scss b/src/stylesheets/components/_split-pane.scss index 0c8e1b06b8..fddd5389ee 100644 --- a/src/stylesheets/components/_split-pane.scss +++ b/src/stylesheets/components/_split-pane.scss @@ -30,9 +30,16 @@ $toc-width-tablet: 210px; } .app-split-pane__content { + padding-top: govuk-spacing(3); + padding-bottom: govuk-spacing(4); + @include govuk-media-query($from: tablet) { display: flex; min-width: 0; + + padding-top: govuk-spacing(6); + padding-bottom: govuk-spacing(8); + padding-left: govuk-spacing(6); flex: 1 1 100%; flex-direction: column; } diff --git a/src/stylesheets/main.scss b/src/stylesheets/main.scss index f05f5f4c0e..8bb77a7ef1 100644 --- a/src/stylesheets/main.scss +++ b/src/stylesheets/main.scss @@ -75,17 +75,6 @@ $app-code-color: #d13118; @include govuk-width-container(1100px); } -.app-breadcrumbs, -.app-content { - padding-top: govuk-spacing(3); - padding-left: govuk-spacing(3); - - @include govuk-media-query($from: tablet) { - padding-top: govuk-spacing(6); - padding-left: govuk-spacing(6); - } -} - .app-breadcrumbs { // match top padding of the first item in the pane navigation so that the // breadcrumbs line up nicely @@ -94,12 +83,6 @@ $app-code-color: #d13118; } .app-content { - padding-bottom: govuk-spacing(4); - - @include govuk-media-query($from: tablet) { - padding-bottom: govuk-spacing(8); - } - h1 { max-width: 15em; } From 535e8ea1e61f23104345370934ef8ebdba82bd1f Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 23 May 2025 15:14:36 +0100 Subject: [PATCH 07/12] Make spacing between breadcrumbs and heading consistent Make the spacing on single page layouts consistent with the spacing on the side page pages. This includes moving the `app-width-container` outside of `
` so that the horizontal spacing is more easily applied to both the breadcrumbs and `
` --- src/stylesheets/main.scss | 3 +-- views/layouts/layout-single-page.njk | 15 +++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/stylesheets/main.scss b/src/stylesheets/main.scss index 8bb77a7ef1..e52dc4cee9 100644 --- a/src/stylesheets/main.scss +++ b/src/stylesheets/main.scss @@ -78,8 +78,7 @@ $app-code-color: #d13118; .app-breadcrumbs { // match top padding of the first item in the pane navigation so that the // breadcrumbs line up nicely - margin-top: govuk-spacing(1); - margin-bottom: 0; + margin: govuk-spacing(1) 0; } .app-content { diff --git a/views/layouts/layout-single-page.njk b/views/layouts/layout-single-page.njk index 4cc5d6c546..9716584d38 100644 --- a/views/layouts/layout-single-page.njk +++ b/views/layouts/layout-single-page.njk @@ -1,16 +1,15 @@ {% extends "_generic.njk" %} {% block body %} - {{ govukBreadcrumbs({ - classes: "app-width-container", - items: getAncestorPages(permalink) - }) }} +
+ {{ govukBreadcrumbs({ + items: getAncestorPages(permalink) + }) }} -
-
+
{{ contents | safe }} -
-
+
+

{% include "_footer.njk" %} {% endblock %} From b5c99b112d3a493833b3b5d5d38816441ee44644 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 30 May 2025 11:06:34 +0100 Subject: [PATCH 08/12] Further normalise how vertical padding of site's contents is applied Moves the vertical padding in the white area where the content is displayed to a `app-main-wrapper` class. This ensures the spacing is consistent between `layout-pane` and `layout-single-page` so the breadcrumbs end up at the same level on all pages. The padding is applied as its own class in a similar fashion that we have `govuk-main-wrapper` in GOV.UK Frontend, but is made to match the spacing designed for the `layout-pane`. It also fixes the bottom padding having disappeared after the content on the pages using `layout-single-page`. --- src/stylesheets/components/_split-pane.scss | 5 ----- src/stylesheets/components/_subnav.scss | 2 +- src/stylesheets/main.scss | 10 ++++++++++ views/layouts/layout-pane.njk | 2 +- views/layouts/layout-single-page.njk | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/stylesheets/components/_split-pane.scss b/src/stylesheets/components/_split-pane.scss index fddd5389ee..6f833cc88f 100644 --- a/src/stylesheets/components/_split-pane.scss +++ b/src/stylesheets/components/_split-pane.scss @@ -30,15 +30,10 @@ $toc-width-tablet: 210px; } .app-split-pane__content { - padding-top: govuk-spacing(3); - padding-bottom: govuk-spacing(4); - @include govuk-media-query($from: tablet) { display: flex; min-width: 0; - padding-top: govuk-spacing(6); - padding-bottom: govuk-spacing(8); padding-left: govuk-spacing(6); flex: 1 1 100%; flex-direction: column; diff --git a/src/stylesheets/components/_subnav.scss b/src/stylesheets/components/_subnav.scss index b55081c32e..c005c89711 100644 --- a/src/stylesheets/components/_subnav.scss +++ b/src/stylesheets/components/_subnav.scss @@ -2,7 +2,7 @@ // Since the back to top link is outside the flow of the document we need to create a space for it. // This number is magic and was determined by manually judging the visual spacing. margin-bottom: 100px; - padding: govuk-spacing(6) govuk-spacing(3) 0 0; + padding-right: govuk-spacing(3); @include govuk-font(16); } diff --git a/src/stylesheets/main.scss b/src/stylesheets/main.scss index e52dc4cee9..eac8fb010e 100644 --- a/src/stylesheets/main.scss +++ b/src/stylesheets/main.scss @@ -75,6 +75,16 @@ $app-code-color: #d13118; @include govuk-width-container(1100px); } +.app-main-wrapper { + padding-top: govuk-spacing(3); + padding-bottom: govuk-spacing(4); + + @include govuk-media-query($from: tablet) { + padding-top: govuk-spacing(6); + padding-bottom: govuk-spacing(8); + } +} + .app-breadcrumbs { // match top padding of the first item in the pane navigation so that the // breadcrumbs line up nicely diff --git a/views/layouts/layout-pane.njk b/views/layouts/layout-pane.njk index 4da9dc7e05..bd28678dca 100644 --- a/views/layouts/layout-pane.njk +++ b/views/layouts/layout-pane.njk @@ -5,7 +5,7 @@ {% set includeThemeInPageHeading = theme and "…" in theme %} {% block body %} -
+
{% include "_subnav.njk" %}
diff --git a/views/layouts/layout-single-page.njk b/views/layouts/layout-single-page.njk index 9716584d38..f8a6637dbd 100644 --- a/views/layouts/layout-single-page.njk +++ b/views/layouts/layout-single-page.njk @@ -1,8 +1,9 @@ {% extends "_generic.njk" %} {% block body %} -
+
{{ govukBreadcrumbs({ + classes: "app-breadcrumbs", items: getAncestorPages(permalink) }) }} From 05b4487d690e7d4702b50c4fbf2aaf8f271e5ea5 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 16 May 2025 11:15:48 +0100 Subject: [PATCH 09/12] Render subnavigation in a `