Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New link helpers #419

Merged
merged 4 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def call
private

def default_attributes
link_classes = govuk_link_classes.append(classes).flatten
link_classes = safe_join([govuk_link_classes, classes], " ")

{ class: link_classes }
end
Expand Down
177 changes: 72 additions & 105 deletions app/helpers/govuk_link_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,156 +3,123 @@
module GovukLinkHelper
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)
def govuk_link_to(name, href = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs, &block)
link_args = extract_link_args(new_tab: new_tab, inverse: inverse, muted: muted, no_underline: no_underline, no_visited_state: no_visited_state, text_colour: text_colour, **kwargs)

if block_given?
link_to(name, html_options, &block)
link_to(block.call, href, **link_args)
else
link_to(name, options, html_options)
link_to(name, href, **link_args)
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)
def govuk_mail_to(email_address, name = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs, &block)
link_args = extract_link_args(new_tab: new_tab, inverse: inverse, muted: muted, no_underline: no_underline, no_visited_state: no_visited_state, text_colour: text_colour, **kwargs)

if block_given?
mail_to(email_address, html_options, &block)
mail_to(email_address, block.call, **link_args)
else
mail_to(email_address, name, html_options)
mail_to(email_address, name, **link_args)
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)
def govuk_button_to(name, href = nil, disabled: false, inverse: false, secondary: false, warning: false, **kwargs, &block)
button_args = extract_button_args(new_tab: false, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs)

if block_given?
button_to(options, html_options, &block)
button_to(block.call, href, **button_args)
else
button_to(name, options, html_options)
button_to(name, href, **button_args)
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)
def govuk_button_link_to(name, href = nil, new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs, &block)
button_args = extract_button_args(new_tab: new_tab, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs)

if block_given?
link_to(name, html_options, &block)
link_to(block.call, href, **button_args)
else
link_to(name, options, html_options)
link_to(name, href, **button_args)
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)
def govuk_breadcrumb_link_to(name, href = nil, **kwargs, &block)
link_args = { class: "#{brand}-breadcrumbs--link" }.deep_merge_html_attributes(kwargs)

if block_given?
link_to(name, html_options, &block)
link_to(block.call, href, **link_args)
else
link_to(name, options, html_options)
link_to(name, href, **link_args)
end
end

private
def govuk_link_classes(inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false)
if [text_colour, inverse, muted].count(true) > 1
fail("links can be only be one of text_colour, inverse or muted")
end

def brand
Govuk::Components.brand
class_names(
"#{brand}-link",
"#{brand}-link--inverse" => inverse,
"#{brand}-link--muted" => muted,
"#{brand}-link--no-underline" => no_underline,
"#{brand}-link--no-visited-state" => no_visited_state,
"#{brand}-link--text-colour" => text_colour,
)
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 govuk_button_classes(disabled: false, inverse: false, secondary: false, warning: false)
if [inverse, secondary, warning].count(true) > 1
fail("buttons can only be one of inverse, secondary or warning")
end

def button_styles
{
disabled: "#{brand}-button--disabled",
secondary: "#{brand}-button--secondary",
warning: "#{brand}-button--warning",
inverse: "#{brand}-button--inverse",
}
class_names(
"#{brand}-button",
"#{brand}-button--disabled" => disabled,
"#{brand}-button--inverse" => inverse,
"#{brand}-button--secondary" => secondary,
"#{brand}-button--warning" => warning,
)
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))
private

combine_attributes(remaining_options, class_name: style_classes)
def new_tab_args(new_tab)
new_tab ? { target: "_blank", rel: "noreferrer noopener" } : {}
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
def button_attributes(disabled)
disabled ? { disabled: true, aria: { disabled: true } } : {}
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
def extract_link_args(new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs)
{
class: govuk_link_classes(
inverse: inverse,
muted: muted,
no_underline: no_underline,
no_visited_state: no_visited_state,
text_colour: text_colour
),
**new_tab_args(new_tab)
}.deep_merge_html_attributes(kwargs)
end

def extract_styles_from_provided_options(styles, provided_options)
return {} if provided_options.blank?

provided_options.slice(*styles.keys)
def extract_button_args(new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs)
{
class: govuk_button_classes(
disabled: disabled,
inverse: inverse,
secondary: secondary,
warning: warning
),
**button_attributes(disabled),
**new_tab_args(new_tab)
}.deep_merge_html_attributes(kwargs)
end

def remove_styles_from_provided_options(styles, provided_options)
return {} if provided_options.blank?

provided_options&.except(*styles.keys)
def brand
Govuk::Components.brand
end
end

Expand Down
157 changes: 157 additions & 0 deletions app/helpers/govuk_rails_compatibile_link_helper.rb
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
Comment on lines +6 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be private methods? Can't think of why you'd use them outside of a link or button helper.

If not, maybe move them below the other methods, and add a brief comment to explain what they do? It threw me a bit seeing these alongside the more obvious helper methods below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So these are exposed to make it easier for devs to use Rails' other link helpers, like link_to_if and link_to_unless, link_to_unless_current, etc.


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
Comment on lines +23 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can avoid the if conditions here and always pass block on to the rails helpers, as those do their own if block_given? checks?

Suggested change
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
extra_options = options if block_given?
html_options = build_html_options(extra_options) || nil
link_to(name, options, html_options, &block)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to leave these as they're in the legacy implementation which will soon be dropped.

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
Loading