primer_view_components 0.24.1 → 0.25.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/assets/styles/primer_view_components.css +1 -1
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/toggle_switch.js +1 -1
  8. data/app/components/primer/alpha/toggle_switch.rb +16 -4
  9. data/app/components/primer/alpha/toggle_switch.ts +1 -1
  10. data/app/components/primer/beta/breadcrumbs.css +1 -1
  11. data/app/components/primer/beta/breadcrumbs.css.map +1 -1
  12. data/app/components/primer/beta/breadcrumbs.pcss +0 -1
  13. data/app/components/primer/beta/clipboard_copy.html.erb +9 -6
  14. data/app/components/primer/beta/clipboard_copy.js +15 -0
  15. data/app/components/primer/beta/clipboard_copy.rb +2 -0
  16. data/app/components/primer/beta/clipboard_copy.ts +14 -0
  17. data/app/components/primer/beta/clipboard_copy_button.rb +2 -0
  18. data/app/lib/primer/attributes_helper.rb +1 -1
  19. data/lib/primer/forms/toggle_switch.html.erb +1 -9
  20. data/lib/primer/view_components/version.rb +1 -1
  21. data/previews/primer/alpha/banner_preview.rb +1 -1
  22. data/previews/primer/alpha/toggle_switch_preview.rb +1 -1
  23. data/previews/primer/beta/breadcrumbs_preview/with_beta_truncate.html.erb +15 -0
  24. data/previews/primer/beta/breadcrumbs_preview/with_deprecated_truncate.html.erb +14 -0
  25. data/previews/primer/beta/breadcrumbs_preview.rb +12 -0
  26. data/previews/primer/beta/label_preview.rb +7 -2
  27. data/previews/primer/forms_preview/example_toggle_switch_form.html.erb +2 -2
  28. data/static/info_arch.json +28 -2
  29. data/static/previews.json +26 -0
  30. metadata +4 -2
@@ -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 +1 @@
1
- .breadcrumb-item{display:inline-block;list-style:none;margin-left:-.35em;white-space:nowrap}.breadcrumb-item:after{border-right:.1em solid var(--borderColor-neutral-emphasis);content:"";display:inline-block;height:.8em;margin:0 .5em;transform:rotate(15deg) translateY(.0625em)}.breadcrumb-item:first-child{margin-left:0}.breadcrumb-item-selected:after,.breadcrumb-item[aria-current]:not([aria-current=false]):after{content:none}.breadcrumb-item-selected a{color:var(--fgColor-default);cursor:default!important;-webkit-text-decoration:none!important;text-decoration:none!important}
1
+ .breadcrumb-item{display:inline-block;list-style:none;margin-left:-.35em}.breadcrumb-item:after{border-right:.1em solid var(--borderColor-neutral-emphasis);content:"";display:inline-block;height:.8em;margin:0 .5em;transform:rotate(15deg) translateY(.0625em)}.breadcrumb-item:first-child{margin-left:0}.breadcrumb-item-selected:after,.breadcrumb-item[aria-current]:not([aria-current=false]):after{content:none}.breadcrumb-item-selected a{color:var(--fgColor-default);cursor:default!important;-webkit-text-decoration:none!important;text-decoration:none!important}
@@ -1 +1 @@
1
- {"version":3,"sources":["breadcrumbs.pcss"],"names":[],"mappings":"AAAA,iBACE,oBAAqB,CAGrB,eAAgB,CAFhB,kBAAoB,CACpB,kBAeF,CAZE,uBAKE,2DAA6D,CAD7D,UAAW,CAHX,oBAAqB,CACrB,WAAa,CACb,aAAe,CAGf,2CACF,CAEA,6BACE,aACF,CAKA,+FACE,YACF,CAGF,4BACE,4BAA6B,CAC7B,wBAA0B,CAC1B,sCAAgC,CAAhC,8BACF","file":"breadcrumbs.css","sourcesContent":[".breadcrumb-item {\n display: inline-block;\n margin-left: -0.35em;\n white-space: nowrap;\n list-style: none;\n\n &::after {\n display: inline-block;\n height: 0.8em;\n margin: 0 0.5em;\n content: '';\n border-right: 0.1em solid var(--borderColor-neutral-emphasis);\n transform: rotate(15deg) translateY(0.0625em);\n }\n\n &:first-child {\n margin-left: 0;\n }\n}\n\n.breadcrumb-item-selected,\n.breadcrumb-item[aria-current]:not([aria-current='false']) {\n &::after {\n content: none;\n }\n}\n\n.breadcrumb-item-selected a {\n color: var(--fgColor-default);\n cursor: default !important;\n text-decoration: none !important;\n}\n"]}
1
+ {"version":3,"sources":["breadcrumbs.pcss"],"names":[],"mappings":"AAAA,iBACE,oBAAqB,CAErB,eAAgB,CADhB,kBAeF,CAZE,uBAKE,2DAA6D,CAD7D,UAAW,CAHX,oBAAqB,CACrB,WAAa,CACb,aAAe,CAGf,2CACF,CAEA,6BACE,aACF,CAKA,+FACE,YACF,CAGF,4BACE,4BAA6B,CAC7B,wBAA0B,CAC1B,sCAAgC,CAAhC,8BACF","file":"breadcrumbs.css","sourcesContent":[".breadcrumb-item {\n display: inline-block;\n margin-left: -0.35em;\n list-style: none;\n\n &::after {\n display: inline-block;\n height: 0.8em;\n margin: 0 0.5em;\n content: '';\n border-right: 0.1em solid var(--borderColor-neutral-emphasis);\n transform: rotate(15deg) translateY(0.0625em);\n }\n\n &:first-child {\n margin-left: 0;\n }\n}\n\n.breadcrumb-item-selected,\n.breadcrumb-item[aria-current]:not([aria-current='false']) {\n &::after {\n content: none;\n }\n}\n\n.breadcrumb-item-selected a {\n color: var(--fgColor-default);\n cursor: default !important;\n text-decoration: none !important;\n}\n"]}
@@ -1,7 +1,6 @@
1
1
  .breadcrumb-item {
2
2
  display: inline-block;
3
3
  margin-left: -0.35em;
4
- white-space: nowrap;
5
4
  list-style: none;
6
5
 
7
6
  &::after {
@@ -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,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 24
8
+ MINOR = 25
9
9
  PATCH = 1
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -55,7 +55,7 @@ module Primer
55
55
  # @label Dismissable
56
56
  # @snapshot
57
57
  def dismissible
58
- render(Primer::Alpha::Banner.new(dismiss_scheme: :hide, reappear: true)) { "This is a dismissable banner." }
58
+ render(Primer::Alpha::Banner.new(dismiss_scheme: :hide)) { "This is a dismissable banner." }
59
59
  end
60
60
 
61
61
  # @!group Full Width
@@ -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
@@ -0,0 +1,15 @@
1
+ <% texts = [
2
+ "Breadcrumb Item 1",
3
+ "Breadcrumb Item 2 with a really long, long, long name",
4
+ "Breadcrumb Item 3 with an extremely long, long, long name"
5
+ ] %>
6
+
7
+ <%= render(Primer::Beta::Breadcrumbs.new) do |breadcrumbs| %>
8
+ <% texts.each_with_index do |text, i| %>
9
+ <% breadcrumbs.with_item(href: "##{i}") do %>
10
+ <%= render(Primer::Beta::Truncate.new) do |truncate| %>
11
+ <% truncate.with_item(max_width: 135) { text } %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <%# erblint:counter DeprecatedComponentsCounter 1 %>
2
+ <% texts = [
3
+ "Breadcrumb Item 1",
4
+ "Breadcrumb Item 2 with a really long, long, long name",
5
+ "Breadcrumb Item 3 with an extremely long, long, long name"
6
+ ] %>
7
+
8
+ <%= render(Primer::Beta::Breadcrumbs.new) do |breadcrumbs| %>
9
+ <% texts.each_with_index do |text, i| %>
10
+ <% breadcrumbs.with_item(href: "##{i}") do %>
11
+ <%= render(Primer::Truncate.new(inline: true, max_width: 135)) { text } %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
@@ -26,6 +26,18 @@ module Primer
26
26
  end
27
27
  end
28
28
  end
29
+
30
+ # @label WithBetaTruncate
31
+ # @snapshot
32
+ def with_beta_truncate
33
+ render_with_template
34
+ end
35
+
36
+ # @label WithDeprecatedTruncate
37
+ # @snapshot
38
+ def with_deprecated_truncate
39
+ render_with_template
40
+ end
29
41
  end
30
42
  end
31
43
  end
@@ -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")) %>
@@ -10921,6 +10921,32 @@
10921
10921
  "color-contrast"
10922
10922
  ]
10923
10923
  }
10924
+ },
10925
+ {
10926
+ "preview_path": "primer/beta/breadcrumbs/with_beta_truncate",
10927
+ "name": "with_beta_truncate",
10928
+ "snapshot": "true",
10929
+ "skip_rules": {
10930
+ "wont_fix": [
10931
+ "region"
10932
+ ],
10933
+ "will_fix": [
10934
+ "color-contrast"
10935
+ ]
10936
+ }
10937
+ },
10938
+ {
10939
+ "preview_path": "primer/beta/breadcrumbs/with_deprecated_truncate",
10940
+ "name": "with_deprecated_truncate",
10941
+ "snapshot": "true",
10942
+ "skip_rules": {
10943
+ "wont_fix": [
10944
+ "region"
10945
+ ],
10946
+ "will_fix": [
10947
+ "color-contrast"
10948
+ ]
10949
+ }
10924
10950
  }
10925
10951
  ],
10926
10952
  "subcomponents": [
@@ -11630,7 +11656,7 @@
11630
11656
  {
11631
11657
  "fully_qualified_name": "Primer::Beta::ClipboardCopy",
11632
11658
  "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.",
11659
+ "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
11660
  "is_form_component": false,
11635
11661
  "is_published": true,
11636
11662
  "requires_js": true,
@@ -11797,7 +11823,7 @@
11797
11823
  {
11798
11824
  "fully_qualified_name": "Primer::Beta::ClipboardCopyButton",
11799
11825
  "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,
11826
+ "accessibility_docs": "This component has a built-in `aria-live` region that announces \"Copied!\" when the copy button is pressed.",
11801
11827
  "is_form_component": false,
11802
11828
  "is_published": true,
11803
11829
  "requires_js": false,
data/static/previews.json CHANGED
@@ -2011,6 +2011,32 @@
2011
2011
  "color-contrast"
2012
2012
  ]
2013
2013
  }
2014
+ },
2015
+ {
2016
+ "preview_path": "primer/beta/breadcrumbs/with_beta_truncate",
2017
+ "name": "with_beta_truncate",
2018
+ "snapshot": "true",
2019
+ "skip_rules": {
2020
+ "wont_fix": [
2021
+ "region"
2022
+ ],
2023
+ "will_fix": [
2024
+ "color-contrast"
2025
+ ]
2026
+ }
2027
+ },
2028
+ {
2029
+ "preview_path": "primer/beta/breadcrumbs/with_deprecated_truncate",
2030
+ "name": "with_deprecated_truncate",
2031
+ "snapshot": "true",
2032
+ "skip_rules": {
2033
+ "wont_fix": [
2034
+ "region"
2035
+ ],
2036
+ "will_fix": [
2037
+ "color-contrast"
2038
+ ]
2039
+ }
2014
2040
  }
2015
2041
  ]
2016
2042
  },
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: primer_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.1
4
+ version: 0.25.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-14 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -813,6 +813,8 @@ files:
813
813
  - previews/primer/beta/blankslate_preview/inside_flex_container.html.erb
814
814
  - previews/primer/beta/border_box_preview.rb
815
815
  - previews/primer/beta/breadcrumbs_preview.rb
816
+ - previews/primer/beta/breadcrumbs_preview/with_beta_truncate.html.erb
817
+ - previews/primer/beta/breadcrumbs_preview/with_deprecated_truncate.html.erb
816
818
  - previews/primer/beta/button_group_preview.rb
817
819
  - previews/primer/beta/button_group_preview/with_menu_button.html.erb
818
820
  - previews/primer/beta/button_preview.rb