diff --git a/app/helpers/govuk_link_helper.rb b/app/helpers/govuk_link_helper.rb index 9b0e06db..f8e393e2 100644 --- a/app/helpers/govuk_link_helper.rb +++ b/app/helpers/govuk_link_helper.rb @@ -3,44 +3,32 @@ module GovukLinkHelper using HTMLAttributesUtils - 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) + def govuk_link_to(name, href = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **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) + link_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block) - if block_given? - link_to(block.call, href, **link_args) - else - link_to(name, href, **link_args) - end + link_to(link_text, href, **link_args) end - 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) + 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, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **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) + link_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block) - if block_given? - mail_to(email_address, block.call, **link_args) - else - mail_to(email_address, name, **link_args) - end + mail_to(email_address, link_text, **link_args) end - def govuk_button_to(name, href = nil, disabled: false, inverse: false, secondary: false, warning: false, **kwargs, &block) + def govuk_button_to(name, href = nil, disabled: false, inverse: false, secondary: false, warning: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **kwargs, &block) button_args = extract_button_args(new_tab: false, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs) + button_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block) - if block_given? - button_to(block.call, href, **button_args) - else - button_to(name, href, **button_args) - end + button_to(button_text, href, **button_args) end - 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) + def govuk_button_link_to(name, href = nil, new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **kwargs, &block) + button_args = extract_button_link_args(new_tab: new_tab, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs) + button_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block) - if block_given? - link_to(block.call, href, **button_args) - else - link_to(name, href, **button_args) - end + link_to(button_text, href, **button_args) end def govuk_breadcrumb_link_to(name, href = nil, **kwargs, &block) @@ -92,33 +80,56 @@ def button_attributes(disabled) end def extract_link_args(new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs) + link_classes = extract_link_classes(inverse: inverse, muted: muted, no_underline: no_underline, no_visited_state: no_visited_state, text_colour: text_colour) + + { **link_classes, **new_tab_args(new_tab) }.deep_merge_html_attributes(kwargs) + end + + def extract_button_link_args(new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs) + button_classes = extract_button_classes(inverse: inverse, secondary: secondary, warning: warning) + + { **button_classes, **button_attributes(disabled), **new_tab_args(new_tab) }.deep_merge_html_attributes(kwargs) + end + + def extract_button_args(disabled: false, inverse: false, secondary: false, warning: false, **kwargs) + button_classes = extract_button_classes(inverse: inverse, secondary: secondary, warning: warning) + + { **button_classes, **button_attributes(disabled) }.deep_merge_html_attributes(kwargs) + end + + def extract_link_classes(inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false) { 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) + text_colour: text_colour, + ) + } end - def extract_button_args(new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs) + def extract_button_classes(inverse: false, secondary: false, warning: false) { class: govuk_button_classes( inverse: inverse, secondary: secondary, warning: warning - ), - **button_attributes(disabled), - **new_tab_args(new_tab) - }.deep_merge_html_attributes(kwargs) + ) + } end def brand Govuk::Components.brand end + + def build_text(original, visually_hidden_prefix:, visually_hidden_suffix:, &block) + prefix = (visually_hidden_prefix.present?) ? visually_hidden_prefix + " " : nil + text = (block_given?) ? block.call : original + suffix = (visually_hidden_suffix.present?) ? " " + visually_hidden_suffix : nil + + safe_join([govuk_visually_hidden(prefix), text, govuk_visually_hidden(suffix)].compact) + end end ActiveSupport.on_load(:action_view) { include GovukLinkHelper } diff --git a/app/helpers/govuk_visually_hidden_helper.rb b/app/helpers/govuk_visually_hidden_helper.rb new file mode 100644 index 00000000..b2b4381c --- /dev/null +++ b/app/helpers/govuk_visually_hidden_helper.rb @@ -0,0 +1,13 @@ +module GovukVisuallyHiddenHelper + def govuk_visually_hidden(text = nil, focusable: false, &block) + content = (block_given?) ? block.call : text + + return if content.blank? + + visually_hidden_class = focusable ? "govuk-visually-hidden-focusable" : "govuk-visually-hidden" + + tag.span(content, class: visually_hidden_class) + end +end + +ActiveSupport.on_load(:action_view) { include GovukSkipLinkHelper } diff --git a/guide/content/helpers/link.slim b/guide/content/helpers/link.slim index 30df0038..8a079cdb 100644 --- a/guide/content/helpers/link.slim +++ b/guide/content/helpers/link.slim @@ -28,6 +28,23 @@ markdown: caption: "Other link styles", code: govuk_link_other_styles) +== render('/partials/example.*', + caption: "Links with visually hidden text", + code: govuk_link_with_visually_hidden_text) do + + markdown: + When space is limited we sometimes rely on the link's position to provide + context about its target. An example of this is in admin interfaces where rows + in a table often have a 'View' link, or a 'Delete' button. + + Omitting the extra text entirely leaves users of assistive technologies like + screen readers at a disadvantage, as they can't infer that context. We can + solve this problem using visually hidden text that isn't visible on the screen + but will be read out by screen readers. + + The keyword arguments automatically add a space between the visually + hidden and regular text. + hr.govuk-section-break.govuk-section-break--xl.govuk-section-break--visible == render('/partials/example.*', diff --git a/guide/content/helpers/visually-hidden-text.slim b/guide/content/helpers/visually-hidden-text.slim new file mode 100644 index 00000000..d936a870 --- /dev/null +++ b/guide/content/helpers/visually-hidden-text.slim @@ -0,0 +1,25 @@ +--- +title: Visually hidden text +--- + +markdown: + Most of the time content should be both visible on the screen and availble to + screen readers. However, somtimes when space is at a premium, we may want + to hide text that would clutter the screen. This puts users of assistive + technology like screen readers at a disadvantage. + + We can hide content but make it available to users of assistive technology + using the `govuk-visually-hidden` class. This library provides a helper method + which makes hiding content easier. + +== render('/partials/example.*', + caption: "Setting visually hidden text with an argument", + code: visually_hidden_text_via_argument) + +== render('/partials/example.*', + caption: "Setting visually hidden text with a block", + code: visually_hidden_text_via_block) + +== render('/partials/example.*', + caption: "Focusable visually hidden text", + code: focusable_visually_hidden_text) diff --git a/guide/layouts/partials/links.slim b/guide/layouts/partials/links.slim index faceb8d3..c3d8eeda 100644 --- a/guide/layouts/partials/links.slim +++ b/guide/layouts/partials/links.slim @@ -19,6 +19,7 @@ section#links.govuk-width-container li== govuk_link_to('Skip link', '/helpers/skip-link') li== govuk_link_to('Back to top link', '/helpers/back-to-top-link') li== govuk_link_to('Title with error prefix', '/helpers/title-with-error-prefix') + li== govuk_link_to('Visually hidden text', '/helpers/visually-hidden-text') .govuk-grid-column-two-thirds h2.govuk-heading-m Components diff --git a/guide/lib/examples/link_helpers.rb b/guide/lib/examples/link_helpers.rb index 98a1a394..33436345 100644 --- a/guide/lib/examples/link_helpers.rb +++ b/guide/lib/examples/link_helpers.rb @@ -50,6 +50,12 @@ def govuk_button_inverse BUTTON end + def govuk_link_with_visually_hidden_text + <<~VISUALLY_HIDDEN_LINK + = govuk_link_to('View', '#', visually_hidden_suffix: 'account') + VISUALLY_HIDDEN_LINK + end + def govuk_button_other_styles <<~BUTTONS .govuk-button-group diff --git a/guide/lib/examples/visually_hidden_helpers.rb b/guide/lib/examples/visually_hidden_helpers.rb new file mode 100644 index 00000000..8d33de17 --- /dev/null +++ b/guide/lib/examples/visually_hidden_helpers.rb @@ -0,0 +1,37 @@ +module Examples + module VisuallyHiddenHelpers + def visually_hidden_text_via_argument + <<~SNIPPET + p Regular text + + = govuk_visually_hidden("This content is visually hidden") + + p More regular text + SNIPPET + end + + def visually_hidden_text_via_block + <<~SNIPPET + p Regular text + + = govuk_visually_hidden do + p This paragraph is visually hidden + + p More regular text + SNIPPET + end + + def focusable_visually_hidden_text + <<~SNIPPET + p Regular text + + p + a href="#" + | Some link + = govuk_visually_hidden("Focus on me", focusable: true) + + p More regular text + SNIPPET + end + end +end diff --git a/guide/lib/helpers.rb b/guide/lib/helpers.rb index 4f0d21ea..68bad78b 100644 --- a/guide/lib/helpers.rb +++ b/guide/lib/helpers.rb @@ -87,7 +87,9 @@ class Application < Rails::Application; end require 'components/govuk_component/warning_text_component' require 'helpers/govuk_link_helper' +require 'helpers/govuk_visually_hidden_helper' +use_helper GovukVisuallyHiddenHelper use_helper GovukLinkHelper use_helper GovukComponentsHelper use_helper Examples::LinkHelpers @@ -115,5 +117,7 @@ class Application < Rails::Application; end use_helper Examples::CommonOptionsHelpers use_helper Examples::BackToTopLinkHelpers use_helper Examples::TitleWithErrorPrefixHelpers +use_helper Examples::VisuallyHiddenHelpers +ActiveSupport.on_load(:action_view) { include GovukVisuallyHiddenHelper } ActiveSupport.on_load(:action_view) { include GovukLinkHelper } diff --git a/spec/helpers/govuk_link_helper_spec.rb b/spec/helpers/govuk_link_helper_spec.rb index 86e776c9..2dd27176 100644 --- a/spec/helpers/govuk_link_helper_spec.rb +++ b/spec/helpers/govuk_link_helper_spec.rb @@ -86,6 +86,38 @@ end end + context "when visually_hidden_prefix: 'some text'" do + let(:visually_hidden_prefix) { "some prefix" } + let(:visually_hidden_prefix_with_trailing_space) { "some prefix " } + let(:kwargs) { { visually_hidden_prefix: visually_hidden_prefix } } + + specify "the prefix is present and visually hidden" do + expect(subject).to have_tag("a", text: /hello/, with: { href: "/world", class: "govuk-link" }) do + with_tag("span", text: visually_hidden_prefix_with_trailing_space) + end + end + + specify "the prefix is before the text" do + expect(subject).to match(%(#{visually_hidden_prefix_with_trailing_space}.*hello)) + end + end + + context "when visually_hidden_suffix: 'some text'" do + let(:visually_hidden_suffix) { "some suffix" } + let(:visually_hidden_suffix_with_leading_space) { " some suffix" } + let(:kwargs) { { visually_hidden_suffix: visually_hidden_suffix } } + + specify "the suffix is present and visually hidden" do + expect(subject).to have_tag("a", text: /hello/, with: { href: "/world", class: "govuk-link" }) do + with_tag("span", text: visually_hidden_suffix_with_leading_space, class: "govuk-visually-hidden") + end + end + + specify "the suffix is after the text" do + expect(subject).to match(%(hello.*#{visually_hidden_suffix_with_leading_space})) + end + end + # the link modifiers text_colour, inverse, muted all change the link's text colour # and shouldn't be used together describe "invalid combinations" do @@ -173,6 +205,38 @@ end end + context "when visually_hidden_prefix: 'some text'" do + let(:visually_hidden_prefix) { "some prefix" } + let(:visually_hidden_prefix_with_trailing_space) { "some prefix " } + let(:kwargs) { { visually_hidden_prefix: visually_hidden_prefix } } + + specify "the prefix is present and visually hidden" do + expect(subject).to have_tag("a", text: /hello/, with: { href: "mailto:world@solar.system", class: "govuk-link" }) do + with_tag("span", text: visually_hidden_prefix_with_trailing_space) + end + end + + specify "the prefix is before the text" do + expect(subject).to match(%(#{visually_hidden_prefix_with_trailing_space}.*hello)) + end + end + + context "when visually_hidden_suffix: 'some text'" do + let(:visually_hidden_suffix) { "some suffix" } + let(:visually_hidden_suffix_with_leading_space) { " some suffix" } + let(:kwargs) { { visually_hidden_suffix: visually_hidden_suffix } } + + specify "the suffix is present and visually hidden" do + expect(subject).to have_tag("a", text: /hello/, with: { href: "mailto:world@solar.system", class: "govuk-link" }) do + with_tag("span", text: visually_hidden_suffix_with_leading_space, class: "govuk-visually-hidden") + end + end + + specify "the suffix is after the text" do + expect(subject).to match(%(hello.*#{visually_hidden_suffix_with_leading_space})) + end + end + # the link modifiers text_colour, inverse, muted all change the link's text colour # and shouldn't be used together describe "invalid combinations" do @@ -274,6 +338,38 @@ end end + context "when visually_hidden_prefix: 'some text'" do + let(:visually_hidden_prefix) { "some prefix" } + let(:visually_hidden_prefix_with_trailing_space) { "some prefix " } + let(:kwargs) { { visually_hidden_prefix: visually_hidden_prefix } } + + specify "the prefix is present and visually hidden" do + expect(subject).to have_tag("a", text: /hello/, with: { href: "/world", class: "govuk-button" }) do + with_tag("span", text: visually_hidden_prefix_with_trailing_space) + end + end + + specify "the prefix is before the text" do + expect(subject).to match(%(#{visually_hidden_prefix_with_trailing_space}.*hello)) + end + end + + context "when visually_hidden_suffix: 'some text'" do + let(:visually_hidden_suffix) { "some suffix" } + let(:visually_hidden_suffix_with_leading_space) { " some suffix" } + let(:kwargs) { { visually_hidden_suffix: visually_hidden_suffix } } + + specify "the suffix is present and visually hidden" do + expect(subject).to have_tag("a", text: /hello/, with: { href: "/world", class: "govuk-button" }) do + with_tag("span", text: visually_hidden_suffix_with_leading_space, class: "govuk-visually-hidden") + end + end + + specify "the suffix is after the text" do + expect(subject).to match(%(hello.*#{visually_hidden_suffix_with_leading_space})) + end + end + # a button can be disabled in combination with other styles but cannot # be called with more than one of eitehr warning, inverse or secondary describe "invalid combinations" do @@ -377,6 +473,42 @@ end end + context "when visually_hidden_prefix: 'some text'" do + let(:visually_hidden_prefix) { "some prefix" } + let(:visually_hidden_prefix_with_trailing_space) { "some prefix " } + let(:kwargs) { { visually_hidden_prefix: visually_hidden_prefix } } + + specify "the prefix is present and visually hidden" do + expect(subject).to have_tag("form", with: { method: "post", action: "/world" }) do + with_tag("button", text: /hello/, with: { class: %w[govuk-button] }) do + with_tag("span", text: visually_hidden_prefix_with_trailing_space) + end + end + end + + specify "the prefix is before the text" do + expect(subject).to match(%(#{visually_hidden_prefix_with_trailing_space}.*hello)) + end + end + + context "when visually_hidden_suffix: 'some text'" do + let(:visually_hidden_suffix) { "some suffix" } + let(:visually_hidden_suffix_with_leading_space) { " some suffix" } + let(:kwargs) { { visually_hidden_suffix: visually_hidden_suffix } } + + specify "the suffix is present and visually hidden" do + expect(subject).to have_tag("form", with: { method: "post", action: "/world" }) do + with_tag("button", text: /hello/, with: { class: %w[govuk-button] }) do + with_tag("span", text: visually_hidden_suffix_with_leading_space, class: "govuk-visually-hidden") + end + end + end + + specify "the suffix is after the text" do + expect(subject).to match(%(hello.*#{visually_hidden_suffix_with_leading_space})) + end + end + # a button can be disabled in combination with other styles but cannot # be called with more than one of eitehr warning, inverse or secondary describe "invalid combinations" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 34f8faf9..f01e02bf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,6 +12,7 @@ SimpleCov.start end +include GovukVisuallyHiddenHelper include GovukLinkHelper include GovukBackToTopLinkHelper include GovukSkipLinkHelper