primer_view_components 0.24.1 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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