openproject-primer_view_components 0.32.1 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,7 +20,7 @@ let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
20
20
  }
21
21
  get csrf() {
22
22
  const csrfElement = this.querySelector('[data-csrf]');
23
- return this.getAttribute('csrf') || (csrfElement instanceof HTMLInputElement && csrfElement.value) || null;
23
+ return this.getAttribute('data-csrf') || (csrfElement instanceof HTMLInputElement && csrfElement.value) || null;
24
24
  }
25
25
  get csrfField() {
26
26
  // the authenticity token is passed into the element and is not generated in js land
@@ -56,10 +56,6 @@ module Primer
56
56
  }
57
57
 
58
58
  @system_arguments[:src] = @src if @src
59
-
60
- return unless @src && @csrf_token
61
-
62
- @system_arguments[:csrf] = @csrf_token
63
59
  end
64
60
 
65
61
  def on?
@@ -73,6 +69,22 @@ module Primer
73
69
  def disabled?
74
70
  !enabled?
75
71
  end
72
+
73
+ private
74
+
75
+ def before_render
76
+ @csrf_token ||= view_context.form_authenticity_token(
77
+ form_options: {
78
+ method: :post,
79
+ action: @src
80
+ }
81
+ )
82
+
83
+ @system_arguments[:data] = merge_data(
84
+ @system_arguments,
85
+ { data: { csrf: @csrf_token } }
86
+ )
87
+ end
76
88
  end
77
89
  end
78
90
  end
@@ -19,7 +19,7 @@ class ToggleSwitchElement extends HTMLElement {
19
19
 
20
20
  get csrf(): string | null {
21
21
  const csrfElement = this.querySelector('[data-csrf]')
22
- return this.getAttribute('csrf') || (csrfElement instanceof HTMLInputElement && csrfElement.value) || null
22
+ return this.getAttribute('data-csrf') || (csrfElement instanceof HTMLInputElement && csrfElement.value) || null
23
23
  }
24
24
 
25
25
  get csrfField(): string {
@@ -1,8 +1,11 @@
1
- <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
- <% if content.present? %>
3
- <%= content %>
4
- <% else %>
5
- <%= render Primer::Beta::Octicon.new(:copy) %>
6
- <%= render Primer::Beta::Octicon.new(:check, color: :success, style: "display: none;") %>
1
+ <%= render Primer::BaseComponent.new(tag: :span) do %>
2
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
3
+ <% if content.present? %>
4
+ <%= content %>
5
+ <% else %>
6
+ <%= render Primer::Beta::Octicon.new(:copy) %>
7
+ <%= render Primer::Beta::Octicon.new(:check, color: :success, style: "display: none;") %>
8
+ <% end %>
7
9
  <% end %>
10
+ <div aria-live="polite" aria-atomic="true" class="sr-only" data-clipboard-copy-feedback></div>
8
11
  <% end %>
@@ -29,12 +29,27 @@ document.addEventListener('clipboard-copy', ({ target }) => {
29
29
  if (!target.hasAttribute('data-view-component'))
30
30
  return;
31
31
  const currentTimeout = clipboardCopyElementTimers.get(target);
32
+ const clipboardCopyLiveRegion = target.parentNode?.querySelector('[data-clipboard-copy-feedback]');
33
+ const copiedAnnouncement = 'Copied!';
32
34
  if (currentTimeout) {
33
35
  clearTimeout(currentTimeout);
34
36
  clipboardCopyElementTimers.delete(target);
35
37
  }
36
38
  else {
37
39
  showCheck(target);
40
+ if (clipboardCopyLiveRegion) {
41
+ if (clipboardCopyLiveRegion.textContent === copiedAnnouncement) {
42
+ /* This is a hack due to the way the aria live API works.
43
+ A screen reader will not read a live region again
44
+ if the text is the same. Adding a space character tells
45
+ the browser that the live region has updated,
46
+ which will cause it to read again, but with no audible difference. */
47
+ clipboardCopyLiveRegion.textContent = `${copiedAnnouncement}\u00A0`;
48
+ }
49
+ else {
50
+ clipboardCopyLiveRegion.textContent = copiedAnnouncement;
51
+ }
52
+ }
38
53
  }
39
54
  clipboardCopyElementTimers.set(target, setTimeout(() => {
40
55
  showCopy(target);
@@ -10,6 +10,8 @@ module Primer
10
10
  #
11
11
  # @accessibility
12
12
  # Always set an accessible label to help the user interact with the component.
13
+ #
14
+ # This component has a built-in `aria-live` region that announces "Copied!" when the copy element is pressed.
13
15
  class ClipboardCopy < Primer::Component
14
16
  status :beta
15
17
 
@@ -37,12 +37,26 @@ document.addEventListener('clipboard-copy', ({target}) => {
37
37
  if (!target.hasAttribute('data-view-component')) return
38
38
 
39
39
  const currentTimeout = clipboardCopyElementTimers.get(target)
40
+ const clipboardCopyLiveRegion = target.parentNode?.querySelector<HTMLElement>('[data-clipboard-copy-feedback]')
41
+ const copiedAnnouncement = 'Copied!'
40
42
 
41
43
  if (currentTimeout) {
42
44
  clearTimeout(currentTimeout)
43
45
  clipboardCopyElementTimers.delete(target)
44
46
  } else {
45
47
  showCheck(target)
48
+ if (clipboardCopyLiveRegion) {
49
+ if (clipboardCopyLiveRegion.textContent === copiedAnnouncement) {
50
+ /* This is a hack due to the way the aria live API works.
51
+ A screen reader will not read a live region again
52
+ if the text is the same. Adding a space character tells
53
+ the browser that the live region has updated,
54
+ which will cause it to read again, but with no audible difference. */
55
+ clipboardCopyLiveRegion.textContent = `${copiedAnnouncement}\u00A0`
56
+ } else {
57
+ clipboardCopyLiveRegion.textContent = copiedAnnouncement
58
+ }
59
+ }
46
60
  }
47
61
 
48
62
  clipboardCopyElementTimers.set(
@@ -5,6 +5,8 @@ module Primer
5
5
  # `ClipboardCopyButton` uses the `ClipboardCopy` component to copy text to the clipboard,
6
6
  # styled as a Primer button. It can be used wherever a button is desired, and works well
7
7
  # with components like `ButtonGroup`.
8
+ # @accessibility
9
+ # This component has a built-in `aria-live` region that announces "Copied!" when the copy button is pressed.
8
10
  class ClipboardCopyButton < Primer::Beta::Button
9
11
  # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %> and <%= link_to_component(Primer::Beta::ClipboardCopy) %>.
10
12
  def initialize(**system_arguments)
@@ -54,7 +54,7 @@ module Primer
54
54
  # It's designed to be used to normalize and merge data information from system_arguments
55
55
  # hashes. Consider using this pattern in component initializers:
56
56
  #
57
- # @system_arguments[:data] = merge_aria(
57
+ # @system_arguments[:data] = merge_data(
58
58
  # @system_arguments,
59
59
  # { data: { foo: "bar" } }
60
60
  # )
@@ -10,13 +10,5 @@
10
10
 
11
11
  <div><%= render(Caption.new(input: @input)) %></div>
12
12
  </span>
13
- <%
14
- csrf = @input.csrf || @view_context.form_authenticity_token(
15
- form_options: {
16
- method: :post,
17
- action: @input.src
18
- }
19
- )
20
- %>
21
- <%= render(Primer::Alpha::ToggleSwitch.new(src: @input.src, csrf: csrf, **@input.input_arguments)) %>
13
+ <%= render(Primer::Alpha::ToggleSwitch.new(src: @input.src, csrf_token: @input.csrf, **@input.input_arguments)) %>
22
14
  <% end %>
@@ -5,8 +5,8 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 32
9
- PATCH = 1
8
+ MINOR = 33
9
+ PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
12
12
  end
@@ -52,7 +52,7 @@ module Primer
52
52
  end
53
53
 
54
54
  def with_csrf_token
55
- render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.toggle_switch_index_path, csrf_token: "let_me_in"))
55
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.toggle_switch_index_path))
56
56
  end
57
57
 
58
58
  def with_bad_csrf_token
@@ -9,8 +9,13 @@ module Primer
9
9
  # @param size [Symbol] select [medium, large]
10
10
  # @param tag [Symbol] select [span, summary, a, div]
11
11
  # @param inline [Boolean] toggle
12
- def playground(size: :medium, tag: :span, inline: false)
13
- render(Primer::Beta::Label.new(tag: tag, size: size, inline: inline)) { "Label" }
12
+ # @param href [String] URL to be used with an anchor tag
13
+ def playground(size: :medium, tag: :span, inline: false, href: nil)
14
+ if tag == :a
15
+ render(Primer::Beta::Label.new(tag: tag, size: size, inline: inline, href: href || "#")) { "Link label" }
16
+ else
17
+ render(Primer::Beta::Label.new(tag: tag, size: size, inline: inline)) { "Label" }
18
+ end
14
19
  end
15
20
 
16
21
  # @label Default Options
@@ -1,3 +1,3 @@
1
- <%= render(ExampleToggleSwitchForm.new(csrf: "let_me_in", label: "Good example", src: toggle_switch_index_path, id: "success-toggle")) %>
1
+ <%= render(ExampleToggleSwitchForm.new(label: "Good example", src: toggle_switch_index_path, id: "success-toggle")) %>
2
2
  <hr>
3
- <%= render(ExampleToggleSwitchForm.new(csrf: "a_bad_value", label: "Bad example", src: toggle_switch_index_path, id: "error-toggle")) %>
3
+ <%= render(ExampleToggleSwitchForm.new(label: "Bad example", src: toggle_switch_index_path(fail: true), id: "error-toggle")) %>
@@ -11630,7 +11630,7 @@
11630
11630
  {
11631
11631
  "fully_qualified_name": "Primer::Beta::ClipboardCopy",
11632
11632
  "description": "Use `ClipboardCopy` to copy element text content or input values to the clipboard.\n\nThis component by itself is not styled as a button, and can therefore only be used in limited circumstances.\nIf you're looking for a button, consider using {{#link_to_component}}Primer::Beta::ClipboardCopyButton{{/link_to_component}}\ninstead.",
11633
- "accessibility_docs": "Always set an accessible label to help the user interact with the component.",
11633
+ "accessibility_docs": "Always set an accessible label to help the user interact with the component.\n\nThis component has a built-in `aria-live` region that announces \"Copied!\" when the copy element is pressed.",
11634
11634
  "is_form_component": false,
11635
11635
  "is_published": true,
11636
11636
  "requires_js": true,
@@ -11797,7 +11797,7 @@
11797
11797
  {
11798
11798
  "fully_qualified_name": "Primer::Beta::ClipboardCopyButton",
11799
11799
  "description": "`ClipboardCopyButton` uses the `ClipboardCopy` component to copy text to the clipboard,\nstyled as a Primer button. It can be used wherever a button is desired, and works well\nwith components like `ButtonGroup`.",
11800
- "accessibility_docs": null,
11800
+ "accessibility_docs": "This component has a built-in `aria-live` region that announces \"Copied!\" when the copy button is pressed.",
11801
11801
  "is_form_component": false,
11802
11802
  "is_published": true,
11803
11803
  "requires_js": false,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openproject-primer_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.1
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-05-31 00:00:00.000000000 Z
12
+ date: 2024-06-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionview