primer_view_components 0.36.4 → 0.37.0
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 +26 -0
- data/app/assets/javascripts/components/primer/beta/details_toggle_element.d.ts +39 -0
- data/app/assets/javascripts/components/primer/primer.d.ts +1 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/components/primer/alpha/action_menu/action_menu_element.js +13 -3
- data/app/components/primer/alpha/action_menu/action_menu_element.ts +14 -2
- data/app/components/primer/alpha/dropdown.rb +8 -0
- data/app/components/primer/alpha/form_control.rb +47 -7
- data/app/components/primer/alpha/toggle_switch.html.erb +1 -1
- data/app/components/primer/alpha/toggle_switch.js +1 -0
- data/app/components/primer/alpha/toggle_switch.rb +14 -2
- data/app/components/primer/alpha/toggle_switch.ts +1 -0
- data/app/components/primer/beta/button.html.erb +1 -1
- data/app/components/primer/beta/button.rb +2 -1
- data/app/components/primer/beta/details.html.erb +8 -6
- data/app/components/primer/beta/details.rb +42 -0
- data/app/components/primer/beta/details_toggle_element.d.ts +39 -0
- data/app/components/primer/beta/details_toggle_element.js +60 -0
- data/app/components/primer/beta/details_toggle_element.ts +57 -0
- data/app/components/primer/beta/markdown.rb +1 -0
- data/app/components/primer/beta/nav_list.rb +1 -1
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/app/lib/primer/forms/action_menu.html.erb +1 -1
- data/app/lib/primer/forms/action_menu.rb +5 -0
- data/lib/primer/view_components/version.rb +2 -2
- data/lib/primer/yard/component_manifest.rb +11 -10
- data/lib/primer/yard/lookbook_pages_backend.rb +8 -0
- data/previews/primer/alpha/action_menu_preview/multiple_select_form.html.erb +1 -1
- data/previews/primer/alpha/form_control_preview/playground.html.erb +14 -6
- data/previews/primer/alpha/overlay_preview.rb +0 -31
- data/previews/primer/alpha/select_preview.rb +6 -6
- data/previews/primer/alpha/text_field_preview.rb +22 -22
- data/previews/primer/alpha/toggle_switch_preview.rb +4 -0
- data/previews/primer/alpha/tooltip_preview.rb +1 -1
- data/previews/primer/beta/details_preview.rb +12 -0
- data/previews/primer/beta/markdown_preview.rb +9 -9
- data/previews/primer/beta/relative_time_preview.rb +20 -10
- data/static/arguments.json +18 -0
- data/static/constants.json +4 -0
- data/static/info_arch.json +140 -70
- data/static/previews.json +27 -52
- metadata +6 -4
- data/previews/primer/alpha/overlay_preview/in_an_action_menu.html.erb +0 -13
- data/previews/primer/alpha/overlay_preview/overlay_with_header_filter.html.erb +0 -16
@@ -129,6 +129,11 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
|
|
129
129
|
}
|
130
130
|
}), "f");
|
131
131
|
observeMutationsUntilConditionMet(this, () => Boolean(this.invokerElement), () => __classPrivateFieldGet(this, _ActionMenuElement_intersectionObserver, "f").observe(this.invokerElement));
|
132
|
+
// If there's no include fragment, then no async fetching will occur and we can
|
133
|
+
// mark the component as ready.
|
134
|
+
if (!this.includeFragment) {
|
135
|
+
this.setAttribute('data-ready', 'true');
|
136
|
+
}
|
132
137
|
}
|
133
138
|
disconnectedCallback() {
|
134
139
|
__classPrivateFieldGet(this, _ActionMenuElement_abortController, "f").abort();
|
@@ -137,7 +142,9 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
|
|
137
142
|
const targetIsInvoker = this.invokerElement?.contains(event.target);
|
138
143
|
const eventIsActivation = __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isActivation).call(this, event);
|
139
144
|
if (event.type === 'toggle' && event.newState === 'open') {
|
140
|
-
|
145
|
+
window.requestAnimationFrame(() => {
|
146
|
+
__classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get)?.focus();
|
147
|
+
});
|
141
148
|
}
|
142
149
|
if (targetIsInvoker && event.type === 'mousedown') {
|
143
150
|
__classPrivateFieldSet(this, _ActionMenuElement_invokerBeingClicked, true, "f");
|
@@ -392,9 +399,10 @@ _ActionMenuElement_handleItemActivated = function _ActionMenuElement_handleItemA
|
|
392
399
|
}));
|
393
400
|
};
|
394
401
|
_ActionMenuElement_handleIncludeFragmentReplaced = function _ActionMenuElement_handleIncludeFragmentReplaced() {
|
395
|
-
|
396
|
-
__classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get).focus();
|
402
|
+
__classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get)?.focus();
|
397
403
|
__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_softDisableItems).call(this);
|
404
|
+
// async items have loaded, so component is ready
|
405
|
+
this.setAttribute('data-ready', 'true');
|
398
406
|
};
|
399
407
|
_ActionMenuElement_handleFocusOut = function _ActionMenuElement_handleFocusOut() {
|
400
408
|
__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_hide).call(this);
|
@@ -409,6 +417,8 @@ _ActionMenuElement_isOpen = function _ActionMenuElement_isOpen() {
|
|
409
417
|
return this.popoverElement?.matches(':popover-open');
|
410
418
|
};
|
411
419
|
_ActionMenuElement_setDynamicLabel = function _ActionMenuElement_setDynamicLabel() {
|
420
|
+
if (this.selectVariant !== 'single')
|
421
|
+
return;
|
412
422
|
if (!this.dynamicLabel)
|
413
423
|
return;
|
414
424
|
const invokerLabel = this.invokerLabel;
|
@@ -143,6 +143,12 @@ export class ActionMenuElement extends HTMLElement {
|
|
143
143
|
() => Boolean(this.invokerElement),
|
144
144
|
() => this.#intersectionObserver.observe(this.invokerElement!),
|
145
145
|
)
|
146
|
+
|
147
|
+
// If there's no include fragment, then no async fetching will occur and we can
|
148
|
+
// mark the component as ready.
|
149
|
+
if (!this.includeFragment) {
|
150
|
+
this.setAttribute('data-ready', 'true')
|
151
|
+
}
|
146
152
|
}
|
147
153
|
|
148
154
|
disconnectedCallback() {
|
@@ -199,7 +205,9 @@ export class ActionMenuElement extends HTMLElement {
|
|
199
205
|
const eventIsActivation = this.#isActivation(event)
|
200
206
|
|
201
207
|
if (event.type === 'toggle' && (event as ToggleEvent).newState === 'open') {
|
202
|
-
|
208
|
+
window.requestAnimationFrame(() => {
|
209
|
+
this.#firstItem?.focus()
|
210
|
+
})
|
203
211
|
}
|
204
212
|
|
205
213
|
if (targetIsInvoker && event.type === 'mousedown') {
|
@@ -365,8 +373,11 @@ export class ActionMenuElement extends HTMLElement {
|
|
365
373
|
}
|
366
374
|
|
367
375
|
#handleIncludeFragmentReplaced() {
|
368
|
-
|
376
|
+
this.#firstItem?.focus()
|
369
377
|
this.#softDisableItems()
|
378
|
+
|
379
|
+
// async items have loaded, so component is ready
|
380
|
+
this.setAttribute('data-ready', 'true')
|
370
381
|
}
|
371
382
|
|
372
383
|
// Close when focus leaves menu
|
@@ -387,6 +398,7 @@ export class ActionMenuElement extends HTMLElement {
|
|
387
398
|
}
|
388
399
|
|
389
400
|
#setDynamicLabel() {
|
401
|
+
if (this.selectVariant !== 'single') return
|
390
402
|
if (!this.dynamicLabel) return
|
391
403
|
const invokerLabel = this.invokerLabel
|
392
404
|
if (!invokerLabel) return
|
@@ -7,12 +7,20 @@ module Primer
|
|
7
7
|
class Dropdown < Primer::Component
|
8
8
|
status :alpha
|
9
9
|
|
10
|
+
ARIA_LABEL_OPEN_DEFAULT = "Close"
|
11
|
+
ARIA_LABEL_CLOSED_DEFAULT = "Open"
|
12
|
+
|
10
13
|
# Required trigger for the dropdown. Has the same arguments as <%= link_to_component(Primer::ButtonComponent) %>,
|
11
14
|
# but it is locked as a `summary` tag.
|
15
|
+
#
|
16
|
+
# @param aria_label_open [String] Defaults to "Close". Value to announce when menu is open.
|
17
|
+
# @param aria_label_closed [String] Defaults to "Open". Value to announce when menu is closed.
|
12
18
|
renders_one :button, lambda { |**system_arguments|
|
13
19
|
@button_arguments = system_arguments
|
14
20
|
@button_arguments[:button] = true
|
15
21
|
@button_arguments[:dropdown] = @with_caret
|
22
|
+
@button_arguments[:aria_label_open] = system_arguments[:aria_label_open] || ARIA_LABEL_OPEN_DEFAULT
|
23
|
+
@button_arguments[:aria_label_closed] = system_arguments[:aria_label_closed] || ARIA_LABEL_CLOSED_DEFAULT
|
16
24
|
|
17
25
|
Primer::Content.new
|
18
26
|
}
|
@@ -3,8 +3,39 @@
|
|
3
3
|
module Primer
|
4
4
|
module Alpha
|
5
5
|
# Wraps an input (or arbitrary content) with a label above and a caption and validation message beneath.
|
6
|
+
#
|
6
7
|
# NOTE: This `FormControl` component is designed for wrapping inputs that aren't supported by the Primer
|
7
8
|
# forms framework.
|
9
|
+
#
|
10
|
+
# @accessibility
|
11
|
+
# Because `FormControl` does not manage the actual `<input>` element, it cannot semantically connect
|
12
|
+
# the input and its associated label. For this and other reasons, consumers are highly encouraged to
|
13
|
+
# use Primer's pre-made form components like `TextField`, etc, ideally via the Primer forms framework.
|
14
|
+
#
|
15
|
+
# Users of the `FormControl` component will need to manually connect the label using the `for:`
|
16
|
+
# attribute, eg:
|
17
|
+
#
|
18
|
+
# ```erb
|
19
|
+
# <%= form_with(url: "/path/somewhere") do |f| %>
|
20
|
+
# <%= render(Primer::Alpha::FormControl.new(label_arguments: { for: "bar" })) do |component| %>
|
21
|
+
# <% component.with_input do |input_arguments| %>
|
22
|
+
# <%= f.text_field(:bar, **input_arguments) %>
|
23
|
+
# <% end %>
|
24
|
+
# <% end %>
|
25
|
+
# <% end %>
|
26
|
+
# ```
|
27
|
+
#
|
28
|
+
# Note that the name of the field, `:bar`, is passed to both the Rails `#text_field` method _and_
|
29
|
+
# as part of the `label_arguments` passed to the `FormControl` constructor.
|
30
|
+
#
|
31
|
+
# Similarly, `FormControl` cannot automatically connect the `<input>` element to the caption and
|
32
|
+
# validation message elements. The component attempts to mitigate this by including the correct
|
33
|
+
# `aria-describedby` attribute in the hash it yields to the block passed to `#with_input`. In the
|
34
|
+
# example above, `input_arguments[:aria][:describedby]` contains the HTML IDs for both the caption
|
35
|
+
# and validation message elements, and can be passed directly to Rails' form helper methods. If the
|
36
|
+
# input being wrapped is not generated by a Rails form helper, care must be taken to set
|
37
|
+
# `aria-describedby` manually on the input element.
|
38
|
+
#
|
8
39
|
class FormControl < Primer::Component
|
9
40
|
# Describes the field and what sorts of input it expects. Displayed below the input.
|
10
41
|
# Note that this slot takes precedence over the `caption:` argument in the constructor.
|
@@ -16,14 +47,16 @@ module Primer
|
|
16
47
|
# @param required [Boolean] Default `false`. When set to `true`, causes an asterisk (*) to appear next to the field's label indicating it is a required field. Note that this option explicitly does _not_ add a `required` HTML attribute. Doing so would enable native browser validations, which are inaccessible and inconsistent with the Primer design system.
|
17
48
|
# @param visually_hide_label [Boolean] When set to `true`, hides the label. Although the label will be hidden visually, it will still be visible to screen readers.
|
18
49
|
# @param full_width [Boolean] When set to `true`, the form control will take up all the horizontal space allowed by its container.
|
50
|
+
# @param label_arguments [Hash] HTML attributes to attach to the `<label>` element that labels the input.
|
19
51
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
20
|
-
def initialize(label:, caption: nil, validation_message: nil, required: false, visually_hide_label: false, full_width: false, **system_arguments)
|
52
|
+
def initialize(label:, caption: nil, validation_message: nil, required: false, visually_hide_label: false, full_width: false, label_arguments: {}, **system_arguments)
|
21
53
|
@label = label
|
22
54
|
@init_caption = caption
|
23
55
|
@validation_message = validation_message
|
24
56
|
@required = required
|
25
57
|
@visually_hide_label = visually_hide_label
|
26
58
|
@full_width = full_width
|
59
|
+
@label_arguments = label_arguments
|
27
60
|
@system_arguments = system_arguments
|
28
61
|
|
29
62
|
@system_arguments[:classes] = class_names(
|
@@ -32,12 +65,11 @@ module Primer
|
|
32
65
|
"FormControl--fullWidth" => full_width?
|
33
66
|
)
|
34
67
|
|
35
|
-
@label_arguments =
|
36
|
-
classes
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
}
|
68
|
+
@label_arguments[:classes] = class_names(
|
69
|
+
@label_arguments.delete(:classes),
|
70
|
+
"FormControl-label",
|
71
|
+
visually_hide_label? ? "sr-only" : nil
|
72
|
+
)
|
41
73
|
|
42
74
|
base_id = self.class.generate_id
|
43
75
|
@validation_id = "validation-#{base_id}"
|
@@ -58,14 +90,20 @@ module Primer
|
|
58
90
|
@input_block = block
|
59
91
|
end
|
60
92
|
|
93
|
+
# Whether or not this input is marked as required.
|
94
|
+
# @returns Boolean
|
61
95
|
def required?
|
62
96
|
@required
|
63
97
|
end
|
64
98
|
|
99
|
+
# Whether or not to hide the label visually. The label will still be visible to screen readers.
|
100
|
+
# @returns Boolean
|
65
101
|
def visually_hide_label?
|
66
102
|
@visually_hide_label
|
67
103
|
end
|
68
104
|
|
105
|
+
# Whether or not the form control should take up all the horizontal space allowed by its container.
|
106
|
+
# @returns Boolean
|
69
107
|
def full_width?
|
70
108
|
@full_width
|
71
109
|
end
|
@@ -85,6 +123,8 @@ module Primer
|
|
85
123
|
memo << @caption_id if @init_caption || caption?
|
86
124
|
end
|
87
125
|
|
126
|
+
@input_arguments[:aria][:required] = "true" if required?
|
127
|
+
|
88
128
|
return if ids.empty?
|
89
129
|
|
90
130
|
@input_arguments[:aria][:describedby] = ids.join(" ")
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<%= render(Primer::Box.new(classes: "ToggleSwitch-statusOff").with_content("Off")) %>
|
9
9
|
<% end %>
|
10
10
|
|
11
|
-
<%= render(Primer::BaseComponent.new(tag: :button, classes: "ToggleSwitch-track", disabled: disabled?, data: { target: "toggle-switch.switch", action: "click:toggle-switch#toggle" }, **@
|
11
|
+
<%= render(Primer::BaseComponent.new(tag: :button, classes: "ToggleSwitch-track", disabled: disabled?, data: { target: "toggle-switch.switch", action: "click:toggle-switch#toggle" }, **@button_arguments)) do %>
|
12
12
|
<%= render(Primer::Box.new(classes: "ToggleSwitch-icons", aria: { hidden: true })) do %>
|
13
13
|
<%= render(Primer::Box.new(classes: "ToggleSwitch-lineIcon")) do %>
|
14
14
|
<%= render(Primer::BaseComponent.new(
|
@@ -132,6 +132,7 @@ let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
|
|
132
132
|
let response;
|
133
133
|
const requestHeaders = {
|
134
134
|
'Requested-With': 'XMLHttpRequest',
|
135
|
+
'X-Requested-With': 'XMLHttpRequest',
|
135
136
|
};
|
136
137
|
if (this.turbo) {
|
137
138
|
requestHeaders['Accept'] = 'text/vnd.turbo-stream.html';
|
@@ -27,8 +27,19 @@ module Primer
|
|
27
27
|
# @param size [Symbol] What size toggle switch to render. <%= one_of(Primer::Alpha::ToggleSwitch::SIZE_OPTIONS) %>
|
28
28
|
# @param status_label_position [Symbol] Which side of the toggle switch to render the status label. <%= one_of(Primer::Alpha::ToggleSwitch::STATUS_LABEL_POSITION_OPTIONS) %>
|
29
29
|
# @param turbo [Boolean] Whether or not to request a turbo stream and render the response as such.
|
30
|
+
# @param autofocus [Boolean] Whether switch should be autofocused when rendered.
|
30
31
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
31
|
-
def initialize(
|
32
|
+
def initialize(
|
33
|
+
src: nil,
|
34
|
+
csrf_token: nil,
|
35
|
+
checked: false,
|
36
|
+
enabled: true,
|
37
|
+
size: SIZE_DEFAULT,
|
38
|
+
status_label_position: STATUS_LABEL_POSITION_DEFAULT,
|
39
|
+
turbo: false,
|
40
|
+
autofocus: nil,
|
41
|
+
**system_arguments
|
42
|
+
)
|
32
43
|
@src = src
|
33
44
|
@csrf_token = csrf_token
|
34
45
|
@checked = checked
|
@@ -50,12 +61,13 @@ module Primer
|
|
50
61
|
SIZE_MAPPINGS[@size]
|
51
62
|
)
|
52
63
|
|
53
|
-
@
|
64
|
+
@button_arguments = {
|
54
65
|
aria: merge_aria(
|
55
66
|
@system_arguments,
|
56
67
|
aria: { pressed: on? }
|
57
68
|
)
|
58
69
|
}
|
70
|
+
@button_arguments[:autofocus] = true if autofocus
|
59
71
|
|
60
72
|
@system_arguments[:src] = @src if @src
|
61
73
|
end
|
@@ -9,8 +9,8 @@
|
|
9
9
|
<% if trailing_visual %>
|
10
10
|
<span class="Button-visual Button-trailingVisual">
|
11
11
|
<% if @trailing_visual_counter %>
|
12
|
+
<span class="d-flex" aria-hidden="true"><%= trailing_visual %></span>
|
12
13
|
<span class="sr-only">(<%= trailing_visual %>)</span>
|
13
|
-
<%= trailing_visual %>
|
14
14
|
<% else %>
|
15
15
|
<%= trailing_visual %>
|
16
16
|
<% end %>
|
@@ -70,7 +70,7 @@ module Primer
|
|
70
70
|
label: Primer::Beta::Label,
|
71
71
|
counter: lambda { |**system_arguments|
|
72
72
|
@trailing_visual_counter = true
|
73
|
-
Primer::Beta::Counter.new(
|
73
|
+
Primer::Beta::Counter.new(**system_arguments)
|
74
74
|
}
|
75
75
|
}
|
76
76
|
|
@@ -108,6 +108,7 @@ module Primer
|
|
108
108
|
# @param align_content [Symbol] <%= one_of(Primer::Beta::Button::ALIGN_CONTENT_OPTIONS) %>
|
109
109
|
# @param tag [Symbol] (Primer::Beta::BaseButton::DEFAULT_TAG) <%= one_of(Primer::Beta::BaseButton::TAG_OPTIONS) %>
|
110
110
|
# @param type [Symbol] (Primer::Beta::BaseButton::DEFAULT_TYPE) <%= one_of(Primer::Beta::BaseButton::TYPE_OPTIONS) %>
|
111
|
+
# @param inactive [Boolean] Whether the button looks visually disabled, but can still accept all the same interactions as an enabled button.
|
111
112
|
# @param disabled [Boolean] Whether or not the button is disabled. If true, this option forces `tag:` to `:button`.
|
112
113
|
# @param label_wrap [Boolean] Whether or not the button label text wraps and the button height expands.
|
113
114
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
<% else %>
|
4
|
-
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
1
|
+
<details-toggle>
|
2
|
+
<% if disabled? %>
|
5
3
|
<%= summary %>
|
6
|
-
|
4
|
+
<% else %>
|
5
|
+
<%= render(Primer::BaseComponent.new(**@system_arguments)) do %>
|
6
|
+
<%= summary %>
|
7
|
+
<%= body %>
|
8
|
+
<% end %>
|
7
9
|
<% end %>
|
8
|
-
|
10
|
+
</details-toggle>
|
@@ -14,14 +14,47 @@ module Primer
|
|
14
14
|
:default => "details-overlay",
|
15
15
|
:dark => "details-overlay details-overlay-dark"
|
16
16
|
}.freeze
|
17
|
+
ARIA_LABEL_OPEN_DEFAULT = "Collapse"
|
18
|
+
ARIA_LABEL_CLOSED_DEFAULT = "Expand"
|
17
19
|
|
18
20
|
attr_reader :disabled
|
19
21
|
alias disabled? disabled
|
20
22
|
|
23
|
+
attr_reader :open
|
24
|
+
alias open? open
|
25
|
+
|
26
|
+
# Use the Summary slot as the target for toggling the Details content open/closed.
|
27
|
+
#
|
28
|
+
# @param button [Boolean] Whether or not to render the summary element as a button.
|
29
|
+
# @param aria_label_open [String] Defaults to "Collapse". Value to announce when details element is open.
|
30
|
+
# @param aria_label_closed [String] Defaults to "Expand". Value to announce when details element is closed.
|
21
31
|
renders_one :summary, lambda { |button: true, **system_arguments|
|
22
32
|
system_arguments[:tag] = :summary
|
23
33
|
system_arguments[:role] = "button"
|
24
34
|
|
35
|
+
aria_label_closed = system_arguments[:aria_label_closed] || ARIA_LABEL_CLOSED_DEFAULT
|
36
|
+
aria_label_open = system_arguments[:aria_label_open] || ARIA_LABEL_OPEN_DEFAULT
|
37
|
+
|
38
|
+
system_arguments[:data] = merge_data(
|
39
|
+
system_arguments, {
|
40
|
+
data: {
|
41
|
+
target: "details-toggle.summaryTarget",
|
42
|
+
action: "click:details-toggle#toggle",
|
43
|
+
aria_label_closed: aria_label_closed,
|
44
|
+
aria_label_open: aria_label_open,
|
45
|
+
}
|
46
|
+
}
|
47
|
+
)
|
48
|
+
|
49
|
+
system_arguments[:aria] = merge_aria(
|
50
|
+
system_arguments, {
|
51
|
+
aria: {
|
52
|
+
label: open? ? aria_label_open : aria_label_closed,
|
53
|
+
expanded: open?,
|
54
|
+
}
|
55
|
+
}
|
56
|
+
)
|
57
|
+
|
25
58
|
if disabled?
|
26
59
|
# rubocop:disable Primer/ComponentNameMigration
|
27
60
|
Primer::ButtonComponent.new(**system_arguments, disabled: true)
|
@@ -57,6 +90,15 @@ module Primer
|
|
57
90
|
OVERLAY_MAPPINGS[fetch_or_fallback(OVERLAY_MAPPINGS.keys, overlay, NO_OVERLAY)],
|
58
91
|
"details-reset" => reset
|
59
92
|
)
|
93
|
+
@system_arguments[:data] = merge_data(
|
94
|
+
@system_arguments, {
|
95
|
+
data: {
|
96
|
+
target: "details-toggle.detailsTarget",
|
97
|
+
}
|
98
|
+
}
|
99
|
+
)
|
100
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#open
|
101
|
+
@open = !!@system_arguments[:open]
|
60
102
|
@disabled = disabled
|
61
103
|
@summary_info = nil
|
62
104
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
/**
|
2
|
+
* A companion Catalyst element for the Details view component. This element
|
3
|
+
* ensures the <details> and <summary> elements markup is properly accessible by
|
4
|
+
* updating the aria-label and aria-expanded attributes on click.
|
5
|
+
*
|
6
|
+
* aria-label values default to "Expand" and "Collapse". To override those
|
7
|
+
* values, use the `data-aria-label-open` and `data-aria-label-closed`
|
8
|
+
* attributes on the summary target.
|
9
|
+
*
|
10
|
+
* @example
|
11
|
+
* ```html
|
12
|
+
* <details-toggle>
|
13
|
+
* <details open=true data-target="details-toggle.detailsTarget">
|
14
|
+
* <summary
|
15
|
+
* aria-expanded="true"
|
16
|
+
* aria-label="Collapse me"
|
17
|
+
* data-target="details-toggle.summaryTarget"
|
18
|
+
* data-action="click:details-toggle#toggle"
|
19
|
+
* data-aria-label-closed="Expand me"
|
20
|
+
* data-aria-label-open="Collapse me"
|
21
|
+
* >
|
22
|
+
* Click me
|
23
|
+
* </summary>
|
24
|
+
* <div>Contents</div>
|
25
|
+
* </details>
|
26
|
+
* </details-toggle>
|
27
|
+
* ```
|
28
|
+
*/
|
29
|
+
declare class DetailsToggleElement extends HTMLElement {
|
30
|
+
detailsTarget: HTMLDetailsElement;
|
31
|
+
summaryTarget: HTMLElement;
|
32
|
+
toggle(): void;
|
33
|
+
}
|
34
|
+
declare global {
|
35
|
+
interface Window {
|
36
|
+
DetailsToggleElement: typeof DetailsToggleElement;
|
37
|
+
}
|
38
|
+
}
|
39
|
+
export {};
|
@@ -0,0 +1,60 @@
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
6
|
+
};
|
7
|
+
import { controller, target } from '@github/catalyst';
|
8
|
+
/**
|
9
|
+
* A companion Catalyst element for the Details view component. This element
|
10
|
+
* ensures the <details> and <summary> elements markup is properly accessible by
|
11
|
+
* updating the aria-label and aria-expanded attributes on click.
|
12
|
+
*
|
13
|
+
* aria-label values default to "Expand" and "Collapse". To override those
|
14
|
+
* values, use the `data-aria-label-open` and `data-aria-label-closed`
|
15
|
+
* attributes on the summary target.
|
16
|
+
*
|
17
|
+
* @example
|
18
|
+
* ```html
|
19
|
+
* <details-toggle>
|
20
|
+
* <details open=true data-target="details-toggle.detailsTarget">
|
21
|
+
* <summary
|
22
|
+
* aria-expanded="true"
|
23
|
+
* aria-label="Collapse me"
|
24
|
+
* data-target="details-toggle.summaryTarget"
|
25
|
+
* data-action="click:details-toggle#toggle"
|
26
|
+
* data-aria-label-closed="Expand me"
|
27
|
+
* data-aria-label-open="Collapse me"
|
28
|
+
* >
|
29
|
+
* Click me
|
30
|
+
* </summary>
|
31
|
+
* <div>Contents</div>
|
32
|
+
* </details>
|
33
|
+
* </details-toggle>
|
34
|
+
* ```
|
35
|
+
*/
|
36
|
+
let DetailsToggleElement = class DetailsToggleElement extends HTMLElement {
|
37
|
+
toggle() {
|
38
|
+
const detailsIsOpen = this.detailsTarget.hasAttribute('open');
|
39
|
+
if (detailsIsOpen) {
|
40
|
+
const ariaLabelClosed = this.summaryTarget.getAttribute('data-aria-label-closed') || 'Expand';
|
41
|
+
this.summaryTarget.setAttribute('aria-label', ariaLabelClosed);
|
42
|
+
this.summaryTarget.setAttribute('aria-expanded', 'false');
|
43
|
+
}
|
44
|
+
else {
|
45
|
+
const ariaLabelOpen = this.summaryTarget.getAttribute('data-aria-label-open') || 'Collapse';
|
46
|
+
this.summaryTarget.setAttribute('aria-label', ariaLabelOpen);
|
47
|
+
this.summaryTarget.setAttribute('aria-expanded', 'true');
|
48
|
+
}
|
49
|
+
}
|
50
|
+
};
|
51
|
+
__decorate([
|
52
|
+
target
|
53
|
+
], DetailsToggleElement.prototype, "detailsTarget", void 0);
|
54
|
+
__decorate([
|
55
|
+
target
|
56
|
+
], DetailsToggleElement.prototype, "summaryTarget", void 0);
|
57
|
+
DetailsToggleElement = __decorate([
|
58
|
+
controller
|
59
|
+
], DetailsToggleElement);
|
60
|
+
window.DetailsToggleElement = DetailsToggleElement;
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import {controller, target} from '@github/catalyst'
|
2
|
+
|
3
|
+
/**
|
4
|
+
* A companion Catalyst element for the Details view component. This element
|
5
|
+
* ensures the <details> and <summary> elements markup is properly accessible by
|
6
|
+
* updating the aria-label and aria-expanded attributes on click.
|
7
|
+
*
|
8
|
+
* aria-label values default to "Expand" and "Collapse". To override those
|
9
|
+
* values, use the `data-aria-label-open` and `data-aria-label-closed`
|
10
|
+
* attributes on the summary target.
|
11
|
+
*
|
12
|
+
* @example
|
13
|
+
* ```html
|
14
|
+
* <details-toggle>
|
15
|
+
* <details open=true data-target="details-toggle.detailsTarget">
|
16
|
+
* <summary
|
17
|
+
* aria-expanded="true"
|
18
|
+
* aria-label="Collapse me"
|
19
|
+
* data-target="details-toggle.summaryTarget"
|
20
|
+
* data-action="click:details-toggle#toggle"
|
21
|
+
* data-aria-label-closed="Expand me"
|
22
|
+
* data-aria-label-open="Collapse me"
|
23
|
+
* >
|
24
|
+
* Click me
|
25
|
+
* </summary>
|
26
|
+
* <div>Contents</div>
|
27
|
+
* </details>
|
28
|
+
* </details-toggle>
|
29
|
+
* ```
|
30
|
+
*/
|
31
|
+
|
32
|
+
@controller
|
33
|
+
class DetailsToggleElement extends HTMLElement {
|
34
|
+
@target detailsTarget!: HTMLDetailsElement
|
35
|
+
@target summaryTarget!: HTMLElement
|
36
|
+
|
37
|
+
toggle() {
|
38
|
+
const detailsIsOpen = this.detailsTarget.hasAttribute('open')
|
39
|
+
if (detailsIsOpen) {
|
40
|
+
const ariaLabelClosed = this.summaryTarget.getAttribute('data-aria-label-closed') || 'Expand'
|
41
|
+
this.summaryTarget.setAttribute('aria-label', ariaLabelClosed)
|
42
|
+
this.summaryTarget.setAttribute('aria-expanded', 'false')
|
43
|
+
} else {
|
44
|
+
const ariaLabelOpen = this.summaryTarget.getAttribute('data-aria-label-open') || 'Collapse'
|
45
|
+
this.summaryTarget.setAttribute('aria-label', ariaLabelOpen)
|
46
|
+
this.summaryTarget.setAttribute('aria-expanded', 'true')
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
declare global {
|
52
|
+
interface Window {
|
53
|
+
DetailsToggleElement: typeof DetailsToggleElement
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
window.DetailsToggleElement = DetailsToggleElement
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Primer
|
4
4
|
module Beta
|
5
5
|
# Use `Markdown` to wrap markdown content.
|
6
|
+
# @accessibility This component is purely presentational. Consumers should handle accessibility expectations, such as ensuring that an overflowing, scrollable code block or table is keyboard accessible.
|
6
7
|
class Markdown < Primer::Component
|
7
8
|
status :beta
|
8
9
|
|
@@ -168,7 +168,7 @@ module Primer
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
-
# Lists that contain top-level items (i.e. items outside of a group) should be wrapped in a
|
171
|
+
# Lists that contain top-level items (i.e. items outside of a group) should be wrapped in a `<ul>`
|
172
172
|
def render_outer_list?
|
173
173
|
items.any? { |item| !group?(item) }
|
174
174
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%= render(FormControl.new(input: @input)) do %>
|
2
2
|
<%= render(Primer::Alpha::ActionMenu.new(**@input.input_arguments)) do |menu| %>
|
3
|
-
<% menu.with_show_button { "Select..." } %>
|
3
|
+
<% menu.with_show_button("aria-describedby": @label_id) { "Select..." } %>
|
4
4
|
<% @input.block.call(menu) if @input.block %>
|
5
5
|
<% end %>
|
6
6
|
<% end %>
|
@@ -8,6 +8,7 @@ module Primer
|
|
8
8
|
|
9
9
|
def initialize(input:)
|
10
10
|
@input = input
|
11
|
+
@input.label_arguments[:id] = label_id
|
11
12
|
|
12
13
|
@input.input_arguments[:form_arguments] = {
|
13
14
|
name: @input.name,
|
@@ -20,6 +21,10 @@ module Primer
|
|
20
21
|
@input.input_arguments[:dynamic_label] = true
|
21
22
|
end
|
22
23
|
end
|
24
|
+
|
25
|
+
def label_id
|
26
|
+
@label_id ||= "label-#{@input.base_id}"
|
27
|
+
end
|
23
28
|
end
|
24
29
|
end
|
25
30
|
end
|
@@ -86,16 +86,17 @@ module Primer
|
|
86
86
|
Primer::Alpha::ActionList::Item => { examples: false },
|
87
87
|
|
88
88
|
# Forms
|
89
|
-
Primer::Alpha::
|
90
|
-
Primer::Alpha::
|
91
|
-
Primer::Alpha::
|
92
|
-
Primer::Alpha::
|
93
|
-
Primer::Alpha::
|
94
|
-
Primer::Alpha::
|
95
|
-
Primer::Alpha::
|
96
|
-
Primer::Alpha::
|
97
|
-
Primer::Alpha::
|
98
|
-
Primer::Alpha::
|
89
|
+
Primer::Alpha::FormControl => { form_component: true },
|
90
|
+
Primer::Alpha::TextField => { form_component: true, js: true },
|
91
|
+
Primer::Alpha::TextArea => { form_component: true },
|
92
|
+
Primer::Alpha::Select => { form_component: true },
|
93
|
+
Primer::Alpha::MultiInput => { form_component: true, js: true },
|
94
|
+
Primer::Alpha::RadioButton => { form_component: true },
|
95
|
+
Primer::Alpha::RadioButtonGroup => { form_component: true },
|
96
|
+
Primer::Alpha::CheckBox => { form_component: true },
|
97
|
+
Primer::Alpha::CheckBoxGroup => { form_component: true },
|
98
|
+
Primer::Alpha::SubmitButton => { form_component: true },
|
99
|
+
Primer::Alpha::FormButton => { form_component: true }
|
99
100
|
}.freeze
|
100
101
|
|
101
102
|
include Enumerable
|