Skip to content

Add sr-only option to spinner #2884

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

Merged
merged 4 commits into from
Jun 11, 2024
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
5 changes: 5 additions & 0 deletions .changeset/blue-insects-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/view-components': minor
---

Adds default sr-only text to spinner component
3 changes: 3 additions & 0 deletions app/components/primer/beta/spinner.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none" />
<path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke" />
<% end %>
<% if no_aria_label? %>
<span class="sr-only"><%= @sr_text %></span>
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to be announced in a live region. You can use https://github.com/primer/live-region-element

This was discussed a while ago in this discussion: https://github.com/github/primer/discussions/2976

Let me know if I misunderstood and I'll revise primer/react#4140 to align with this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mperrotti the idea is to eventually move to an announced version, however this first step will resolve the audit issue.

I felt like the announcement API needs some consideration (for example we don't want many Spinners on one page to all announce individually) and since the audit issues them. We could certainly add announcements (on this PR if we need to do so to get it released) however I would not feel comfortable if the Spinner announced by default, I think that's too risky given the number of Spinners we have in the wild already.

<% end %>
16 changes: 15 additions & 1 deletion app/components/primer/beta/spinner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Spinner < Primer::Component
DEFAULT_SIZE => 32,
:large => 64
}.freeze
DEFAULT_SR_TEXT = "Loading"

SIZE_OPTIONS = SIZE_MAPPINGS.keys

Expand All @@ -22,7 +23,7 @@ class Spinner < Primer::Component
# @param size [Symbol] <%= one_of(Primer::Beta::Spinner::SIZE_MAPPINGS) %>
# @param style [String] Custom element styles.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(size: DEFAULT_SIZE, style: DEFAULT_STYLE, **system_arguments)
def initialize(size: DEFAULT_SIZE, style: DEFAULT_STYLE, sr_text: DEFAULT_SR_TEXT, **system_arguments)
@system_arguments = deny_tag_argument(**system_arguments)
@system_arguments[:tag] = :svg
@system_arguments[:style] ||= style
Expand All @@ -31,6 +32,19 @@ def initialize(size: DEFAULT_SIZE, style: DEFAULT_STYLE, **system_arguments)
@system_arguments[:height] = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)]
@system_arguments[:viewBox] = "0 0 16 16"
@system_arguments[:fill] = :none
@system_arguments[:aria] ||= {}
@sr_text = sr_text

if no_aria_label?
@system_arguments[:aria][:hidden] = true
else
@system_arguments[:role] = "img"
end
end

def no_aria_label?
!@system_arguments[:aria][:label] && !@system_arguments[:"aria-label"] &&
!@system_arguments[:aria][:labelledby] && !@system_arguments[:"aria-labelledby"]
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions previews/primer/beta/spinner_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class SpinnerPreview < ViewComponent::Preview
# @label Playground
#
# @param size [Symbol] select [small, medium, large]
def playground(size: :medium)
render(Primer::Beta::Spinner.new(size: size))
def playground(size: :medium, sr_text: "Loading content...")
render(Primer::Beta::Spinner.new(size: size, sr_text: sr_text))
end

# @label Default Options
Expand Down
6 changes: 6 additions & 0 deletions static/arguments.json
Original file line number Diff line number Diff line change
Expand Up @@ -4288,6 +4288,12 @@
"default": "`box-sizing: content-box; color: var(--color-icon-primary);`",
"description": "Custom element styles."
},
{
"name": "sr_text",
"type": "String",
"default": "Loading",
"description": "Screen reader only text, added unless aria-label is set."
},
{
"name": "system_arguments",
"type": "Hash",
Expand Down
48 changes: 48 additions & 0 deletions test/components/beta/spinner_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,52 @@ def test_no_box_sizing_style
def test_status
assert_component_state(Primer::Beta::Spinner, :beta)
end

def test_adds_default_sr_span
render_inline(Primer::Beta::Spinner.new)

assert_selector("span.sr-only", text: "Loading")
end

def test_adds_custom_sr_span
render_inline(Primer::Beta::Spinner.new(sr_text: "Custom Loading"))

assert_selector("span.sr-only", text: "Custom Loading")
end

def test_no_custom_sr_span_with_aria_label
render_inline(Primer::Beta::Spinner.new("aria-label": "Aria label"))

assert_no_selector("span.sr-only")
end

def test_no_custom_sr_span_with_aria_label_hash
render_inline(Primer::Beta::Spinner.new(aria: { label: "Aria label"}))

assert_no_selector("span.sr-only")
end

def test_no_custom_sr_span_with_aria_labelledby
render_inline(Primer::Beta::Spinner.new("aria-labelledby": "my_id"))

assert_no_selector("span.sr-only")
end

def test_no_custom_sr_span_with_aria_labelledby_hash
render_inline(Primer::Beta::Spinner.new(aria: { labelledby: "my_id"}))

assert_no_selector("span.sr-only")
end

def test_adds_img_role_with_aria_label
render_inline(Primer::Beta::Spinner.new("aria-label": "Aria label"))

assert_selector("svg[role=img]")
end

def test_adds_aria_hidden_with_no_aria_label
render_inline(Primer::Beta::Spinner.new)

assert_selector("svg[aria-hidden=true]")
end
end
Loading