-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR introduces a new set of link helpers that will ~be available as an option but will eventually become default~ be the default. The legacy 100% Rails-compatible helpers will still be available for the time being by including the helper module as described below. The current helpers are closely tied to Rails' implementation which after 18 years is showing its age. Because it pre-dates keyword arguments and can take many formats of link, the code is complex and difficult to extend. The new implementation: * is clean, all of the logic is handled by Rails' [class_names](https://www.rubydoc.info/docs/rails/ActionView%2FHelpers%2FTagHelper:class_names) helper * does minimal argument juggling * supports [opening links in new tabs](https://design-system.service.gov.uk/styles/typography/#opening-links-in-a-new-tab) * **does not support** the old-style verbose non-resource-orientated `govuk_link_to("Show profile", controller: "profiles", action: "show", id: @Profile)` format. I haven't seen much evidence of this style in use and Rails' documentation [advises against using it](https://api.rubyonrails.org/v7.0/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) ## Multiple versions of link helpers For a while there will be two versions of the link helpers: 1. the new ones `GovukLinkHelper` 2. the old ones `GovukRailsCompatibileLinkHelper` I originally planned to make choosing between them a config option but due to the order the gem is required and then the intializers run, it's a pain. Instead, the new ones will automatically be included and if the old ones are wanted they can be manually included in `ApplicationHelper`: ```ruby module ApplicationHelper include GovukRailsCompatibileLinkHelper end ``` This will make the old method variants override the new ones. I will begin adding some warnings to the old ones before they are dropped. ## Final things to do * [ ] add some tests ensuring additional arguments (both `options` and `html_options`) can still be passed through to the Rails helpers * [ ] `visually_hidden_prefix` and `visually_hidden_suffix` keyword arguments?
- Loading branch information
Showing
8 changed files
with
871 additions
and
315 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
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,157 @@ | ||
require "html_attributes_utils" | ||
|
||
module GovukRailsCompatibileLinkHelper | ||
using HTMLAttributesUtils | ||
|
||
def govuk_link_classes(*styles, default_class: "#{brand}-link") | ||
if (invalid_styles = (styles - link_styles.keys)) && invalid_styles.any? | ||
fail(ArgumentError, "invalid styles #{invalid_styles.to_sentence}. Valid styles are #{link_styles.keys.to_sentence}") | ||
end | ||
|
||
[default_class] + link_styles.values_at(*styles).compact | ||
end | ||
|
||
def govuk_button_classes(*styles, default_class: "#{brand}-button") | ||
if (invalid_styles = (styles - button_styles.keys)) && invalid_styles.any? | ||
fail(ArgumentError, "invalid styles #{invalid_styles.to_sentence}. Valid styles are #{button_styles.keys.to_sentence}") | ||
end | ||
|
||
[default_class] + button_styles.values_at(*styles).compact | ||
end | ||
|
||
def govuk_link_to(name = nil, options = nil, extra_options = {}, &block) | ||
extra_options = options if block_given? | ||
html_options = build_html_options(extra_options) | ||
|
||
if block_given? | ||
link_to(name, html_options, &block) | ||
else | ||
link_to(name, options, html_options) | ||
end | ||
end | ||
|
||
def govuk_mail_to(email_address, name = nil, extra_options = {}, &block) | ||
extra_options = name if block_given? | ||
html_options = build_html_options(extra_options) | ||
|
||
if block_given? | ||
mail_to(email_address, html_options, &block) | ||
else | ||
mail_to(email_address, name, html_options) | ||
end | ||
end | ||
|
||
def govuk_button_to(name = nil, options = nil, extra_options = {}, &block) | ||
extra_options = options if block_given? | ||
html_options = { | ||
data: { module: "govuk-button" } | ||
} | ||
|
||
if extra_options && extra_options[:prevent_double_click] | ||
html_options[:data]["prevent-double-click"] = "true" | ||
extra_options = extra_options.except(:prevent_double_click) | ||
end | ||
|
||
html_options.merge! build_html_options(extra_options, style: :button) | ||
|
||
if block_given? | ||
button_to(options, html_options, &block) | ||
else | ||
button_to(name, options, html_options) | ||
end | ||
end | ||
|
||
def govuk_button_link_to(name = nil, options = nil, extra_options = {}, &block) | ||
extra_options = options if block_given? | ||
html_options = { | ||
data: { module: "#{brand}-button" }, | ||
draggable: 'false', | ||
role: 'button', | ||
}.merge build_html_options(extra_options, style: :button) | ||
|
||
if block_given? | ||
link_to(name, html_options, &block) | ||
else | ||
link_to(name, options, html_options) | ||
end | ||
end | ||
|
||
def govuk_breadcrumb_link_to(name = nil, options = nil, extra_options = {}, &block) | ||
extra_options = options if block_given? | ||
html_options = build_html_options(extra_options, style: :breadcrumb) | ||
|
||
if block_given? | ||
link_to(name, html_options, &block) | ||
else | ||
link_to(name, options, html_options) | ||
end | ||
end | ||
|
||
private | ||
|
||
def brand | ||
Govuk::Components.brand | ||
end | ||
|
||
def link_styles | ||
{ | ||
inverse: "#{brand}-link--inverse", | ||
muted: "#{brand}-link--muted", | ||
no_underline: "#{brand}-link--no-underline", | ||
no_visited_state: "#{brand}-link--no-visited-state", | ||
text_colour: "#{brand}-link--text-colour", | ||
} | ||
end | ||
|
||
def button_styles | ||
{ | ||
disabled: "#{brand}-button--disabled", | ||
secondary: "#{brand}-button--secondary", | ||
warning: "#{brand}-button--warning", | ||
inverse: "#{brand}-button--inverse", | ||
} | ||
end | ||
|
||
def build_html_options(provided_options, style: :link) | ||
element_styles = { link: link_styles, button: button_styles }.fetch(style, {}) | ||
|
||
# we need to take a couple of extra steps here because we don't want the style | ||
# params (inverse, muted, etc) to end up as extra attributes on the link. | ||
|
||
remaining_options = remove_styles_from_provided_options(element_styles, provided_options) | ||
|
||
style_classes = build_style_classes(style, extract_styles_from_provided_options(element_styles, provided_options)) | ||
|
||
combine_attributes(remaining_options, class_name: style_classes) | ||
end | ||
|
||
def build_style_classes(style, provided_options) | ||
keys = *provided_options&.keys | ||
|
||
case style | ||
when :link then govuk_link_classes(*keys) | ||
when :button then govuk_button_classes(*keys) | ||
when :breadcrumb then "#{brand}-breadcrumbs__link" | ||
end | ||
end | ||
|
||
def combine_attributes(attributes, class_name:) | ||
attributes ||= {} | ||
|
||
attributes.with_indifferent_access.tap do |attrs| | ||
attrs[:class] = Array.wrap(attrs[:class]).prepend(class_name).flatten.join(" ") | ||
end | ||
end | ||
|
||
def extract_styles_from_provided_options(styles, provided_options) | ||
return {} if provided_options.blank? | ||
|
||
provided_options.slice(*styles.keys) | ||
end | ||
|
||
def remove_styles_from_provided_options(styles, provided_options) | ||
return {} if provided_options.blank? | ||
|
||
provided_options&.except(*styles.keys) | ||
end | ||
end |
Oops, something went wrong.