openproject-primer_view_components 0.32.1 → 0.33.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/toggle_switch.js +1 -1
- data/app/components/primer/alpha/toggle_switch.rb +16 -4
- data/app/components/primer/alpha/toggle_switch.ts +1 -1
- data/app/components/primer/beta/clipboard_copy.html.erb +9 -6
- data/app/components/primer/beta/clipboard_copy.js +15 -0
- data/app/components/primer/beta/clipboard_copy.rb +2 -0
- data/app/components/primer/beta/clipboard_copy.ts +14 -0
- data/app/components/primer/beta/clipboard_copy_button.rb +2 -0
- data/app/components/primer/open_project/page_header.css +1 -1
- data/app/components/primer/open_project/page_header.css.json +3 -3
- data/app/components/primer/open_project/page_header.css.map +1 -1
- data/app/components/primer/open_project/page_header.pcss +6 -6
- data/app/components/primer/open_project/page_header.rb +1 -1
- data/app/lib/primer/attributes_helper.rb +1 -1
- data/lib/primer/forms/toggle_switch.html.erb +1 -9
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/toggle_switch_preview.rb +1 -1
- data/previews/primer/beta/label_preview.rb +7 -2
- data/previews/primer/forms_preview/example_toggle_switch_form.html.erb +2 -2
- data/static/classes.json +1 -4
- data/static/info_arch.json +2 -2
- metadata +2 -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,8 +1,11 @@
|
|
1
|
-
<%= render Primer::BaseComponent.new(
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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)
|
@@ -1 +1 @@
|
|
1
|
-
.PageHeader{border-bottom:var(--borderWidth-thin) solid var(--borderColor-muted);display:flex;flex-flow:column;margin-bottom:var(--stack-gap-normal);padding-bottom:var(--stack-padding-condensed)}.PageHeader--
|
1
|
+
.PageHeader{border-bottom:var(--borderWidth-thin) solid var(--borderColor-muted);display:flex;flex-flow:column;margin-bottom:var(--stack-gap-normal);padding-bottom:var(--stack-padding-condensed)}.PageHeader--withTabNav{border-bottom:none;margin-bottom:0;padding-bottom:0}.PageHeader-contextBar{margin-bottom:var(--base-size-8)}.PageHeader-contextBar,.PageHeader-titleBar{align-items:center;display:flex;flex-flow:row;justify-content:flex-end}.PageHeader-titleBar{margin-bottom:var(--space-xsmall)}.PageHeader-title{flex:1 1 auto;font-size:var(--text-title-size-medium);font-weight:var(--base-text-weight-normal)}.PageHeader-title--large{font-size:var(--text-title-size-large)}.PageHeader-description{color:var(--fgColor-muted);flex:1 100%;font-size:var(--text-body-size-medium)}.PageHeader--withTabNav .PageHeader-description{margin-bottom:var(--space-xlarge)}.PageHeader-actions{align-items:center;display:flex;justify-content:flex-end}.PageHeader-breadcrumbs{display:block;width:100%}.PageHeader-leadingAction{margin-right:var(--base-size-4);margin-top:2px}.PageHeader-parentLink{flex:1 1 auto}
|
@@ -2,16 +2,16 @@
|
|
2
2
|
"name": "open_project/page_header",
|
3
3
|
"selectors": [
|
4
4
|
".PageHeader",
|
5
|
-
".PageHeader--
|
5
|
+
".PageHeader--withTabNav",
|
6
6
|
".PageHeader-contextBar",
|
7
7
|
".PageHeader-titleBar",
|
8
8
|
".PageHeader-title",
|
9
9
|
".PageHeader-title--large",
|
10
10
|
".PageHeader-description",
|
11
|
+
".PageHeader--withTabNav .PageHeader-description",
|
11
12
|
".PageHeader-actions",
|
12
13
|
".PageHeader-breadcrumbs",
|
13
14
|
".PageHeader-leadingAction",
|
14
|
-
".PageHeader-parentLink"
|
15
|
-
".PageHeader-tabNav"
|
15
|
+
".PageHeader-parentLink"
|
16
16
|
]
|
17
17
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["page_header.pcss"],"names":[],"mappings":"AAEA,YAIE,oEAAqE,CAHrE,YAAa,CAIb,gBAAiB,CAFjB,qCAAsC,CADtC,6CAIF,CAEA,
|
1
|
+
{"version":3,"sources":["page_header.pcss"],"names":[],"mappings":"AAEA,YAIE,oEAAqE,CAHrE,YAAa,CAIb,gBAAiB,CAFjB,qCAAsC,CADtC,6CAIF,CAEA,wBACE,kBAAmB,CAEnB,eAAgB,CADhB,gBAEF,CAEA,uBAKE,gCACF,CAEA,4CAJE,kBAAmB,CAHnB,YAAa,CACb,aAAc,CACd,wBAWF,CANA,qBAKE,iCACF,CAEA,kBAGE,aAAc,CAFd,uCAAwC,CACxC,0CAEF,CAEA,yBACE,sCACF,CAGA,wBAEE,0BAA2B,CAC3B,WAAY,CAFZ,sCAGF,CAEA,gDACE,iCACF,CAEA,oBAGE,kBAAmB,CADnB,YAAa,CADb,wBAGF,CAEA,wBACE,aAAc,CACd,UACF,CAEA,0BAEE,+BAAgC,CADhC,cAEF,CAEA,uBACE,aACF","file":"page_header.css","sourcesContent":["/* OP PageHeader */\n\n.PageHeader {\n display: flex;\n padding-bottom: var(--stack-padding-condensed);\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);\n flex-flow: column;\n}\n\n.PageHeader--withTabNav {\n border-bottom: none;\n padding-bottom: 0;\n margin-bottom: 0;\n}\n\n.PageHeader-contextBar {\n display: flex;\n flex-flow: row;\n justify-content: flex-end;\n align-items: center;\n margin-bottom: var(--base-size-8);\n}\n\n.PageHeader-titleBar {\n display: flex;\n flex-flow: row;\n justify-content: flex-end;\n align-items: center; /* Keep back button vertically aligned. */\n margin-bottom: var(--space-xsmall);\n}\n\n.PageHeader-title {\n font-size: var(--text-title-size-medium);\n font-weight: var(--base-text-weight-normal);\n flex: 1 1 auto;\n}\n\n.PageHeader-title--large {\n font-size: var(--text-title-size-large);\n}\n\n/* One-liner of supporting text */\n.PageHeader-description {\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-muted);\n flex: 1 100%;\n}\n\n.PageHeader--withTabNav .PageHeader-description {\n margin-bottom: var(--space-xlarge);\n}\n\n.PageHeader-actions {\n justify-content: flex-end;\n display: flex;\n align-items: center;\n}\n\n.PageHeader-breadcrumbs {\n display: block;\n width: 100%;\n}\n\n.PageHeader-leadingAction {\n margin-top: 2px; /* to center align with label */\n margin-right: var(--base-size-4);\n}\n\n.PageHeader-parentLink {\n flex: 1 1 auto;\n}\n"]}
|
@@ -8,9 +8,10 @@
|
|
8
8
|
flex-flow: column;
|
9
9
|
}
|
10
10
|
|
11
|
-
.PageHeader--
|
11
|
+
.PageHeader--withTabNav {
|
12
12
|
border-bottom: none;
|
13
13
|
padding-bottom: 0;
|
14
|
+
margin-bottom: 0;
|
14
15
|
}
|
15
16
|
|
16
17
|
.PageHeader-contextBar {
|
@@ -46,6 +47,10 @@
|
|
46
47
|
flex: 1 100%;
|
47
48
|
}
|
48
49
|
|
50
|
+
.PageHeader--withTabNav .PageHeader-description {
|
51
|
+
margin-bottom: var(--space-xlarge);
|
52
|
+
}
|
53
|
+
|
49
54
|
.PageHeader-actions {
|
50
55
|
justify-content: flex-end;
|
51
56
|
display: flex;
|
@@ -65,8 +70,3 @@
|
|
65
70
|
.PageHeader-parentLink {
|
66
71
|
flex: 1 1 auto;
|
67
72
|
}
|
68
|
-
|
69
|
-
.PageHeader-tabNav {
|
70
|
-
margin-top: var(--stack-gap-normal);
|
71
|
-
margin-bottom: 0;
|
72
|
-
}
|
@@ -209,7 +209,7 @@ module Primer
|
|
209
209
|
#
|
210
210
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
211
211
|
renders_one :tab_nav, lambda { |**system_arguments, &block|
|
212
|
-
@system_arguments[:classes] = class_names(@system_arguments[:classes], "PageHeader--
|
212
|
+
@system_arguments[:classes] = class_names(@system_arguments[:classes], "PageHeader--withTabNav")
|
213
213
|
|
214
214
|
system_arguments = deny_tag_argument(**system_arguments)
|
215
215
|
system_arguments[:tag] = :div
|
@@ -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] =
|
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 %>
|
@@ -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
|
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
|
-
|
13
|
-
|
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(
|
1
|
+
<%= render(ExampleToggleSwitchForm.new(label: "Good example", src: toggle_switch_index_path, id: "success-toggle")) %>
|
2
2
|
<hr>
|
3
|
-
<%= render(ExampleToggleSwitchForm.new(
|
3
|
+
<%= render(ExampleToggleSwitchForm.new(label: "Bad example", src: toggle_switch_index_path(fail: true), id: "error-toggle")) %>
|
data/static/classes.json
CHANGED
@@ -429,7 +429,7 @@
|
|
429
429
|
"PageHeader": [
|
430
430
|
"Primer::OpenProject::PageHeader"
|
431
431
|
],
|
432
|
-
"PageHeader--
|
432
|
+
"PageHeader--withTabNav": [
|
433
433
|
"Primer::OpenProject::PageHeader"
|
434
434
|
],
|
435
435
|
"PageHeader-actions": [
|
@@ -450,9 +450,6 @@
|
|
450
450
|
"PageHeader-parentLink": [
|
451
451
|
"Primer::OpenProject::PageHeader"
|
452
452
|
],
|
453
|
-
"PageHeader-tabNav": [
|
454
|
-
"Primer::OpenProject::PageHeader"
|
455
|
-
],
|
456
453
|
"PageHeader-title": [
|
457
454
|
"Primer::OpenProject::PageHeader"
|
458
455
|
],
|
data/static/info_arch.json
CHANGED
@@ -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":
|
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.
|
4
|
+
version: 0.33.1
|
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
|
12
|
+
date: 2024-06-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: actionview
|