Skip to content

Commit

Permalink
feat: create sub navigation view component
Browse files Browse the repository at this point in the history
  • Loading branch information
TobyRet committed Oct 7, 2024
1 parent 3213a88 commit 1ce96fe
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 0 deletions.
51 changes: 51 additions & 0 deletions app/components/navigation/structures/admin_sub_navigation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module Navigation
module Structures
class AdminSubNavigation < Navigation::Structures::BaseSubNavigation
def get
[
Node.new(
name: "Admin",
href: "#",
prefix: "/admin",
nodes: [
Node.new(
name: "Admin 1.1",
href: '#',
prefix: "/admin/1.1"
),
Node.new(
name: "Admin 1.2",
href: '#',
prefix: "/admin/1.2"
)
]
),
Node.new(
name: "Sub Nav 2",
href: '#',
prefix: "/sub-nav-2",
nodes: [
Node.new(
name: "Sub Nav 2.1",
href: '#',
prefix: "/sub-nav-2.1"
),
]
),
Node.new(
name: "Sub Nav 3",
href: '#',
prefix: "/sub-nav-3",
nodes: [
Node.new(
name: "Sub Nav 3.1",
href: '#',
prefix: "/sub-nav-3.1"
),
]
),
]
end
end
end
end
19 changes: 19 additions & 0 deletions app/components/navigation/structures/base_sub_navigation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Navigation
module Structures
class BaseSubNavigation
include Rails.application.routes.url_helpers

# A Node is an entry in a navigation list, it contains:
#
# * name - the hyperlink text which appears in the list
# * href - the hyperlink href
# * prefix - the beginning of a path which will trigger the node to be marked
# 'current' and highlighted in the nav
# * current - a boolean value where the result of the prefix match is stored,
# this isn't done on the fly so we can pass the value along from
# the primary nav to the sub nav
# * nodes - a list of nodes that sit under this one in the structure
Node = Struct.new(:name, :href, :prefix, :nodes)
end
end
end
20 changes: 20 additions & 0 deletions app/components/navigation/sub_navigation_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<nav class="x-govuk-sub-navigation" aria-labelledby="sub-navigation-heading">
<h2 class="govuk-visually-hidden" id="sub-navigation-heading">Navigation</h2>
<ul class="x-govuk-sub-navigation__section">
<% structure.each do |section| %>
<%= tag.li(class: navigation_item_classes(section)) do %>
<%= navigation_link(section) %>
<% if section.nodes.present? %>
<ul class="x-govuk-sub-navigation__section x-govuk-sub-navigation__section--nested">
<% section.nodes.each do |node| %>
<li class="x-govuk-sub-navigation__section-item">
<%= navigation_link(node) %>
</li>
<% end %>
</ul>
<% end %>
<% end %>
<% end %>
</ul>
</nav>
40 changes: 40 additions & 0 deletions app/components/navigation/sub_navigation_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'debug'

module Navigation
class SubNavigationComponent < ViewComponent::Base
attr_accessor :current_path, :current_section, :structure

def initialize(current_path, structure:)
@current_path = current_path
@structure = structure
end

def render?
structure.present?
end

def navigation_link(section)
link_to(
section.name,
section.href,
class: "x-govuk-sub-navigation__link",
aria: { current: current?(section.prefix) },
)
end

def navigation_item_classes(section)
class_names(
"x-govuk-sub-navigation__section-item",
"x-govuk-sub-navigation__section-item--current" => current?(section.prefix),
)
end

private

def current?(prefix)
# return nil instead of false so Rails' link helper drops the
# attribute rather than setting "current='false'"
current_path.start_with?(prefix) || nil
end
end
end
5 changes: 5 additions & 0 deletions app/helpers/admin_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module AdminHelper
def admin_sub_navigation_structure
@admin_navigation_structure ||= Navigation::Structures::AdminSubNavigation.new.get
end
end
10 changes: 10 additions & 0 deletions app/views/admin/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@
{ key: { text: "Session expires" }, value: { text: session_manager.expires_at.localtime.to_fs(:govuk) } },
]
) %>
<%= content_for(:sidebar) do %>
<%=
render Navigation::SubNavigationComponent.new(
request.path,
structure: admin_sub_navigation_structure
)
%>
<% end %>

146 changes: 146 additions & 0 deletions spec/components/navigation/sub_navigation_component_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
require "rails_helper"

class TestSubNavigationStructureTwoLevels < Navigation::Structures::BaseSubNavigation
def get
[
Node.new(
name: "This is the Sub Nav 1 page",
href: "/sub-nav-1",
prefix: "/sub-nav-1",
nodes: [
Node.new(
name: "This is the Sub Nav 1.1 page",
href: "/sub-nav-1.1",
prefix: "/sub-nav-1.1"
),
]
),
Node.new(
name: "This is the Sub Nav 2 page",
href: "/sub-nav-2",
prefix: "/sub-nav-2",
nodes: [
Node.new(
name: "This is the Sub Nav 2.1 page",
href: '/sub-nav-2.1',
prefix: "/sub-nav-2.1"
),
]
),
]
end
end

class TestSubNavigationStructureOneLevel < Navigation::Structures::BaseSubNavigation
def get
[
Node.new(
name: "This is the Sub Nav 1 page",
href: '/sub-nav-1',
prefix: "/sub-nav-1",
)
]
end
end

RSpec.describe Navigation::SubNavigationComponent, type: :component do
describe "a nested navigation structure with two levels" do
let(:current_path) { "/some-path" }
let(:structure) { TestSubNavigationStructureTwoLevels.new }

subject do
Navigation::SubNavigationComponent.new(current_path, structure: structure.get)
end

it "renders a visually hidden h2 heading" do
render_inline(subject)

expect(rendered_content).to have_css("h2.govuk-visually-hidden", text: "Navigation")
end

it "renders top level navigation items" do
render_inline(subject)

selector = "li.x-govuk-sub-navigation__section-item > a.x-govuk-sub-navigation__link"

expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 1 page")
expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 2 page")
end

it "renders second level navigation items" do
render_inline(subject)

selector = %w[
li.x-govuk-sub-navigation__section-item
ul.x-govuk-sub-navigation__section--nested
li.x-govuk-sub-navigation__section-item
a.x-govuk-sub-navigation__link
].join(" > ")

expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 1.1 page")
expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 2.1 page")
end
end

describe "a navigation structure with only one level" do
let(:structure) { TestSubNavigationStructureOneLevel.new }
let(:current_path) { "/some-path" }

subject do
Navigation::SubNavigationComponent.new(current_path, structure: structure.get)
end

it "renders top level navigation items" do
render_inline(subject)

selector = "li.x-govuk-sub-navigation__section-item > a.x-govuk-sub-navigation__link"

expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 1 page")
end
end

describe "highlighting the current top level nav item" do
context "when the prefix matches the start of the current path" do
let(:current_path) { "/sub-nav-1" }
let(:structure) { TestSubNavigationStructureTwoLevels.new }

subject do
Navigation::SubNavigationComponent.new(current_path, structure: structure.get)
end

it "marks only the nav item with the matching prefix as 'current'" do
render_inline(subject)

selector = "li.x-govuk-sub-navigation__section-item--current"

expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 1 page")
expect(rendered_content).to have_css(selector, count: 1)
end
end
end

describe "highlighting the current second level nav item" do
context "when the prefix matches the start of the current path" do
let(:current_path) { "/sub-nav-1.1" }
let(:structure) { TestSubNavigationStructureTwoLevels.new }

subject do
Navigation::SubNavigationComponent.new(current_path, structure: structure.get)
end

it "marks only the section as current" do
render_inline(subject)

selector = %w[
li.x-govuk-sub-navigation__section-item
ul.x-govuk-sub-navigation__section--nested
li.x-govuk-sub-navigation__section-item
a.x-govuk-sub-navigation__link[aria-current="true"]
].join(" > ")

expect(rendered_content).to have_css(selector, text: "This is the Sub Nav 1.1 page")
expect(rendered_content).to have_css(selector, count: 1)
end
end
end
end

0 comments on commit 1ce96fe

Please sign in to comment.