-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The service navigation component sits beneath the header at the top of the page and contains the service's name and it's primary navigation. This implementation follows the upstream one very closely but has some additional functionality: * it accepts a `current_path` argument * when navigation items match `current_path` exactly they are marked as current * for pages nested within a section, it accepts a `active_when` argument * when navigation items begin with the active_when value they are marked as 'active' * if a regular expression is provided for `active_when`, any positive match results in the node being marked 'active'
- Loading branch information
Showing
15 changed files
with
698 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
app/components/govuk_component/service_navigation_component.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
class GovukComponent::ServiceNavigationComponent < GovukComponent::Base | ||
renders_one :start_slot | ||
renders_one :end_slot | ||
|
||
renders_one :service_name, "GovukComponent::ServiceNavigationComponent::ServiceNameComponent" | ||
renders_many :navigation_items, ->(text:, href: nil, current_path: nil, active_when: nil, current: false, active: false, classes: [], html_attributes: {}) do | ||
GovukComponent::ServiceNavigationComponent::NavigationItemComponent.new( | ||
text:, | ||
href:, | ||
current_path: current_path || @current_path, | ||
active_when:, | ||
current:, | ||
active:, | ||
classes:, | ||
html_attributes: | ||
) | ||
end | ||
|
||
attr_reader :aria_label_text, :navigation_id | ||
|
||
def initialize(service_name: nil, service_url: nil, navigation_items: [], current_path: nil, aria_label: "Service information", navigation_id: 'navigation', classes: [], html_attributes: {}) | ||
@service_name_text = service_name | ||
@service_url = service_url | ||
@current_path = current_path | ||
@aria_label_text = aria_label | ||
@navigation_id = navigation_id | ||
|
||
if @service_name_text.present? | ||
with_service_name(service_name: @service_name_text, service_url:) | ||
end | ||
|
||
navigation_items.each { |ni| with_navigation_item(current_path:, **ni) } | ||
|
||
super(classes:, html_attributes:) | ||
end | ||
|
||
def call | ||
outer_element do | ||
tag.div(class: "#{brand}-width_container") do | ||
safe_join( | ||
[ | ||
start_slot, | ||
tag.div(class: "#{brand}-service-navigation__container") do | ||
safe_join([service_name, navigation].compact) | ||
end, | ||
end_slot | ||
] | ||
) | ||
end | ||
end | ||
end | ||
|
||
def navigation | ||
return unless navigation_items? | ||
|
||
tag.nav(aria: { label: "Menu" }, class: "#{brand}-service-navigation__wrapper") do | ||
safe_join([menu_button, navigation_list]) | ||
end | ||
end | ||
|
||
def navigation_list | ||
tag.ul(safe_join(navigation_items), id: navigation_id, class: "#{brand}-service-navigation__list") | ||
end | ||
|
||
private | ||
|
||
def outer_element(&block) | ||
if service_name? | ||
tag.section(**aria_attributes, **html_attributes, &block) | ||
else | ||
tag.div(**html_attributes, &block) | ||
end | ||
end | ||
|
||
def default_attributes | ||
{ class: "#{brand}-service-navigation", data: { module: "#{brand}-service-navigation" } } | ||
end | ||
|
||
def aria_attributes | ||
{ aria: { label: aria_label_text } } | ||
end | ||
|
||
def menu_button | ||
tag.button( | ||
"Menu", | ||
type: 'button', | ||
class: ["#{brand}-service-navigation__toggle", "#{brand}-js-service-navigation-toggle"], | ||
aria: { controls: navigation_id }, | ||
hidden: true | ||
) | ||
end | ||
end |
83 changes: 83 additions & 0 deletions
83
app/components/govuk_component/service_navigation_component/navigation_item_component.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
class GovukComponent::ServiceNavigationComponent::NavigationItemComponent < GovukComponent::Base | ||
attr_reader :text, :href, :current_path, :active_when, :current, :active | ||
|
||
def initialize(text:, href: nil, current_path: nil, active_when: nil, current: false, active: false, classes: [], html_attributes: {}) | ||
@current = current | ||
@active = active | ||
@text = text | ||
@href = href | ||
|
||
@current_path = current_path | ||
@active_when = active_when | ||
|
||
super(classes:, html_attributes:) | ||
end | ||
|
||
def call | ||
tag.li(**html_attributes) do | ||
if href.present? | ||
wrap_link(link_to(text, href, class: "#{brand}-service-navigation__link", **aria_current)) | ||
else | ||
tag.span(text, class: "#{brand}-service-navigation__text") | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def wrap_link(link) | ||
if current_or_active? | ||
tag.strong(link, class: "#{brand}-service-navigation__active-fallback") | ||
else | ||
link | ||
end | ||
end | ||
|
||
def current? | ||
current || auto_current? | ||
end | ||
|
||
def active? | ||
active || auto_active? | ||
end | ||
|
||
def current_or_active? | ||
current? || active? | ||
end | ||
|
||
def auto_current? | ||
return if current_path.blank? | ||
|
||
current_path == href | ||
end | ||
|
||
def auto_active? | ||
return if current_path.blank? | ||
|
||
case active_when | ||
when Regexp | ||
active_when.match?(current_path) | ||
when String | ||
current_path.start_with?(active_when) | ||
when Array | ||
active_when.any? { |p| current_path.start_with?(p) } | ||
else | ||
false | ||
end | ||
end | ||
|
||
def default_attributes | ||
{ | ||
class: class_names( | ||
"#{brand}-service-navigation__item", | ||
"#{brand}-service-navigation__item--active" => current_or_active? | ||
) | ||
} | ||
end | ||
|
||
def aria_current | ||
current = (current?) ? 'page' : true | ||
|
||
{ aria: { current: } } | ||
end | ||
end |
30 changes: 30 additions & 0 deletions
30
app/components/govuk_component/service_navigation_component/service_name_component.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
class GovukComponent::ServiceNavigationComponent::ServiceNameComponent < GovukComponent::Base | ||
attr_reader :service_name, :service_url | ||
|
||
def initialize(service_name:, service_url: nil, classes: [], html_attributes: {}) | ||
@service_name = service_name | ||
@service_url = service_url | ||
|
||
super(classes:, html_attributes:) | ||
end | ||
|
||
def call | ||
text = (service_url.present?) ? build_link : build_span | ||
|
||
tag.span(text, **html_attributes) | ||
end | ||
|
||
private | ||
|
||
def build_link | ||
link_to(service_name, service_url, class: "#{brand}-service-navigation__link") | ||
end | ||
|
||
def build_span | ||
tag.span(service_name, class: "#{brand}-service-navigation__text") | ||
end | ||
|
||
def default_attributes | ||
{ class: "#{brand}-service-navigation__service-name" } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
--- | ||
title: Service navigation | ||
--- | ||
|
||
p | ||
| Service navigation helps users understand that they’re using your service | ||
and lets them navigate around your service. | ||
|
||
== render('/partials/example.*', | ||
caption: "With only a service name", | ||
code: service_navigation_with_only_service_name) do | ||
|
||
markdown: | ||
The Design System recommendation is [to display the service name in | ||
the service navigation instead of in the header](https://design-system.service.gov.uk/components/service-navigation/#showing-your-service-name-only). On basic | ||
services this means the service navigation can be rendered without any navigation links. | ||
|
||
== render('/partials/example.*', | ||
caption: "With navigation items", | ||
code: service_navigation_with_a_current_page, | ||
data: service_navigation_with_a_current_page_data) do | ||
|
||
markdown: | ||
Show navigation links to let users navigate to different parts of your | ||
service and find useful links and tools. | ||
|
||
When you are on the linked page you can use `current: true` to mark it. If | ||
you're on a page within the section but not on the page itself, use | ||
`active: true` instead. | ||
|
||
== render('/partials/example.*', | ||
caption: "Automatically detecting the current page", | ||
code: service_navigation_with_navigation_items, | ||
data: service_navigation_with_navigation_items_data) do | ||
|
||
markdown: | ||
Having to work out which page is current and adjust the parameters manually | ||
would be complicated. If you pass in the `current_path`, when navigation | ||
nodes that have a matching `href` are rendered, they'll be rendered as | ||
the current page. | ||
|
||
== render('/partials/example.*', | ||
caption: "Automatic active page matching", | ||
code: service_navigation_with_matching_subpages, | ||
data: service_navigation_with_matching_subpages_data) do | ||
|
||
markdown: | ||
Often we have pages deeply nested within sections of the site and want | ||
the navigation to show which section we're currently in. We can do this | ||
using `active_when`. | ||
|
||
When `active_when` is a string the component will check if the beginning of | ||
the `current_path` matches it. For more complex scenarios a regular expression | ||
can be used instead. | ||
|
||
== render('/partials/example.*', | ||
caption: "Manaully building the service navigation", | ||
code: service_navigation_manual) do | ||
|
||
markdown: | ||
For extra customisation the service navigation can be assembled manually. We can | ||
use the `start_slot` and `end_slot` to add custom HTML at the beginning and | ||
end of the service navigation. This content needs its own custom styling or it will | ||
be rendered above and below the other content. | ||
|
||
== render('/partials/related-navigation.*', links: service_navigation_info) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.