Skip to content

Commit 981fbb9

Browse files
committed
[WIP] push rough draft of nested nav
1 parent e6c05f9 commit 981fbb9

10 files changed

Lines changed: 239 additions & 13 deletions

File tree

.nycrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"public/assets/frontend/dependencies-*",
1515
"public/assets/frontend/components/map/defra-*",
1616
"public/assets/frontend/components/map/map*",
17+
"public/assets/frontend/components/nested-navigation*",
1718
"public/assets/frontend/views/landing_page/map/leaflet-*",
1819
"public/assets/frontend/views/landing_page/map/data/cdc.geojson-*",
1920
"public/assets/frontend/views/landing_page/map/data/hub.geojson-*",

app/assets/config/manifest.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//= link main.js
44
//= link dependencies.js
55
//= link test-dependencies.js
6+
//= link components/nested-navigation.js
67
//= link components/map.js
78
//= link components/map/defra-map.js
89
//= link components/map/map-style-liberty.js
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
window.GOVUK = window.GOVUK || {}
2+
window.GOVUK.Modules = window.GOVUK.Modules || {};
3+
4+
(function (Modules) {
5+
class NestedNavigation {
6+
constructor (module) {
7+
this.module = module
8+
}
9+
10+
init () {
11+
const buttons = this.module.querySelectorAll('[data-app-c-nested-navigation-button]')
12+
13+
for (const button of buttons) {
14+
console.log(button)
15+
button.addEventListener('click', this.toggleMenu)
16+
}
17+
}
18+
19+
toggleMenu (e) {
20+
const button = e.target
21+
22+
const menu = button.ariaControlsElements[0]
23+
menu.classList.toggle('js-app-c-nested-navigation--hidden')
24+
const ariaExpanded = button.getAttribute('aria-expanded') === 'true'
25+
button.setAttribute('aria-expanded', `${!ariaExpanded}`)
26+
}
27+
}
28+
Modules.NestedNavigation = NestedNavigation
29+
})(window.GOVUK.Modules)

app/assets/stylesheets/application.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
@import "components/calendar";
7272
@import "components/download-link";
7373
@import "components/map";
74+
@import "components/nested-navigation";
7475

7576
@import "govuk_web_banners/dependencies";
7677

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
.js-enabled {
2+
.js-app-c-nested-navigation--hidden {
3+
display: none;
4+
}
5+
6+
.app-c-nested-navigation__sub-list {
7+
position: absolute;
8+
}
9+
}
10+
11+
.app-c-nested-navigation {
12+
background-color: $govuk-blue-tint-95;
13+
border-bottom: 1px solid govuk-colour("blue");
14+
position: relative;
15+
}
16+
17+
.app-c-nested-navigation__button {
18+
background: none;
19+
border: 0;
20+
color: govuk-colour("blue");
21+
cursor: pointer;
22+
padding: 0;
23+
text-decoration: none;
24+
position: relative;
25+
@include govuk-link-style-default;
26+
@include govuk-font($size: 19);
27+
}
28+
29+
.app-c-nested-navigation__button[aria-expanded="false"] {
30+
background: none;
31+
}
32+
33+
.app-c-nested-navigation__button[aria-expanded="true"] {
34+
background-color: govuk-colour("white");
35+
}
36+
37+
.app-c-nested-navigation__button:focus[aria-expanded="true"], .app-c-nested-navigation__button:focus[aria-expanded="false"] {
38+
background-color: $govuk-focus-colour;
39+
}
40+
41+
42+
43+
.app-c-nested-navigation__link {
44+
text-decoration: none;
45+
}
46+
47+
// Shared styles for the expanded and collapsed arrow icon. Based off of the super navigation menu button.
48+
.app-c-nested-navigation__button::after {
49+
border-bottom: 2px solid govuk-colour("blue");
50+
border-right: 2px solid govuk-colour("blue");
51+
content: "";
52+
display: inline-block;
53+
height: 8px;
54+
margin-left: 10px;
55+
vertical-align: middle;
56+
width: 8px;
57+
}
58+
59+
.app-c-nested-navigation__button:focus::after {
60+
border-color: govuk-colour("black");
61+
}
62+
63+
.app-c-nested-navigation__button[aria-expanded="false"]::after {
64+
// Collapsed arrow icon
65+
transform: translateY(-35%) rotate(45deg) scale(1);
66+
}
67+
68+
.app-c-nested-navigation__button[aria-expanded="true"]::after {
69+
transform: translateY(1px) rotate(225deg) scale(1);
70+
}
71+
72+
73+
// .app-c-nested-navigation__button[aria-expanded=false]::after {
74+
// content: "▼"
75+
// }
76+
77+
// .app-c-nested-navigation__button[aria-expanded=true]::after {
78+
// content: "▲"
79+
// }
80+
81+
.app-c-nested-navigation__service-name:link {
82+
color: govuk-colour("black");
83+
font-weight: bold;
84+
text-decoration: none;
85+
}
86+
87+
.app-c-nested-navigation__sub-list {
88+
padding-left: 0;
89+
background-color: govuk-colour("white");
90+
}
91+
92+
.app-c-nested-navigation__sub-list-item {
93+
padding-top: 15px;
94+
padding-left: 15px;
95+
padding-right: 15px;
96+
list-style: none;
97+
@include govuk-font($size: 19);
98+
}
99+
100+
.app-c-nested-navigation__list-item {
101+
display: inline-block;
102+
vertical-align: top;
103+
@include govuk-font($size: 19);
104+
padding: 15px;
105+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
<%
3+
items ||= nil
4+
5+
# I have to clone the array here, otherwise when the array is manipulated the component guide renders the manipulated array instead of the original one passed through.
6+
cloned_items = items.clone
7+
first_item = cloned_items.shift if cloned_items
8+
%>
9+
10+
<% unless !items %>
11+
<%= javascript_include_tag "components/nested-navigation.js", integrity: false, type: "module" %>
12+
<ul data-module="nested-navigation" class="app-c-nested-navigation">
13+
<li class="app-c-nested-navigation__list-item"> <%= tag.a(href: first_item["href"], class: "govuk-link app-c-nested-navigation__service-name") { first_item["text"] } %> </li>
14+
<% cloned_items.each do | item | %>
15+
<% if item["links"] %>
16+
<li class="app-c-nested-navigation__list-item">
17+
<% menu_id = "nested-navigation-#{SecureRandom.hex(4)}" %>
18+
<%= tag.button data: { app_c_nested_navigation_button: ""}, aria: { controls: menu_id, expanded: false }, class: "app-c-nested-navigation__button" do %> <%= item["text"] %> <% end %>
19+
<ul class="js-app-c-nested-navigation--hidden app-c-nested-navigation__sub-list" id="<%= menu_id %>">
20+
<% item["links"].each do | sub_item | %>
21+
<li class="app-c-nested-navigation__sub-list-item"> <%= tag.a(href: sub_item["href"], class: "govuk-link app-c-nested-navigation__link") { sub_item["text"] } %></li>
22+
<% end %>
23+
</ul>
24+
</li>
25+
<% else %>
26+
<li class="app-c-nested-navigation__list-item"> <%= tag.a(href: item["href"], class: "govuk-link app-c-nested-navigation__link") { item["text"] } %> </li>
27+
<% end %>
28+
<% end %>
29+
</ul>
30+
<% end %>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Nested navigation (experimental)
2+
description: Renders a navigation that can contain a nested menu
3+
body: |
4+
5+
shared_accessibility_criteria:
6+
- sub_navigation_menu
7+
8+
accessibility_criteria: |
9+
To do
10+
11+
uses_component_wrapper_helper: true
12+
examples:
13+
default:
14+
data:
15+
items:
16+
- text: "Service Name"
17+
href: "/service-name"
18+
- text: "A basic link"
19+
href: "/something"
20+
- text: "Nested Heading"
21+
links:
22+
- text: "Example Link"
23+
href: "/hello-world"
24+
- text: "Example Link 2"
25+
href: "/hello-world"
26+
- text: "Example Link 3"
27+
href: "/hello-world"
28+
- text: "This is a very very very long link"
29+
href: "/hello-world"
30+
- text: "Another link that has a lot of characters"
31+
href: "/hello-world"
32+
- text: "Third link containing a lot of text"
33+
href: "/hello-world"
34+
- text: "Third Link"
35+
href: "/example1"
36+
- text: "Fourth Link"
37+
href: "/example1"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
RSpec.describe "NestedNavigationComponent", type: :view do
2+
def component_name
3+
"nested_navigation"
4+
end
5+
6+
items = [{ "text" => "Service Name", "href" => "/service-name" }, { "text" => "A basic link", "href" => "/something" }, { "text" => "Nested Heading", "links" => [{ "text" => "Example Link", "href" => "/hello-world" }, { "text" => "Example Link 2", "href" => "/hello-world" }, { "text" => "Example Link 3", "href" => "/hello-world" }, { "text" => "This is a very very very long link", "href" => "/hello-world" }, { "text" => "Another link that has a lot of characters", "href" => "/hello-world" }, { "text" => "Third link containing a lot of text", "href" => "/hello-world" }] }, { "text" => "Third Link", "href" => "/example1" }, { "text" => "Fourth Link", "href" => "/example1" }]
7+
8+
it "renders nothing without config" do
9+
render_component({})
10+
expect(rendered).not_to have_css(".app-c-nested-navigation")
11+
end
12+
13+
it "renders the basic component" do
14+
render_component({ items: items })
15+
expect(rendered).to have_css(".app-c-nested-navigation")
16+
expect(rendered).to have_css("ul[data-module=nested-navigation]")
17+
expect(rendered).to have_css(".app-c-nested-navigation__service-name", text: "Service Name")
18+
expect(rendered).to have_css("button", text: " Nested Heading ")
19+
expect(rendered).to have_css(".app-c-nested-navigation__sub-list-item", text: "Example Link")
20+
expect(rendered).to have_css("li:last-of-type", text: "Fourth Link")
21+
end
22+
end
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// This file runs in the browser.
22

33
// Ensure window.__coverage__ is always present.
4-
window.__coverage__ ??= {};
4+
window.__coverage__ ??= {}
55

66
class NycTestCoverageReporter {
77
// The following method runs after every Jasmine describe block.
8-
static suiteDone(result) {
8+
static suiteDone (result) {
99
// Transfer test coverage information from the browser to node.js.
10-
jasmine.getEnv().setSuiteProperty("__coverage__", window.__coverage__);
10+
jasmine.getEnv().setSuiteProperty('__coverage__', window.__coverage__)
1111
}
1212
}
1313

14-
jasmine.getEnv().addReporter(NycTestCoverageReporter);
14+
jasmine.getEnv().addReporter(NycTestCoverageReporter)
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// This file runs in node.js.
22

3-
import fsPromises from 'node:fs/promises';
4-
import {URL} from 'node:url';
5-
import path from 'node:path';
3+
import fsPromises from 'node:fs/promises'
4+
import { URL } from 'node:url'
5+
import path from 'node:path'
66

77
export default class NycTestCoverageReporter {
88
// The following method runs after every Jasmine describe block.
9-
suiteDone({properties}) {
10-
this.coverage = properties?.__coverage__ || this.coverage;
9+
suiteDone ({ properties }) {
10+
this.coverage = properties?.__coverage__ || this.coverage
1111
}
1212

13-
jasmineDone() {
13+
jasmineDone () {
1414
const rootDirname = path.dirname(
1515
new URL(import.meta.resolve('#root/package.json')).pathname
16-
);
17-
const nycCoverageFile = path.join(rootDirname, 'tmp', 'nyc_output', 'out.json');
18-
return fsPromises.writeFile(nycCoverageFile, JSON.stringify(this.coverage));
16+
)
17+
const nycCoverageFile = path.join(rootDirname, 'tmp', 'nyc_output', 'out.json')
18+
return fsPromises.writeFile(nycCoverageFile, JSON.stringify(this.coverage))
1919
}
2020
}

0 commit comments

Comments
 (0)