primer_view_components 0.36.5 → 0.37.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -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/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/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 +12 -0
- data/static/constants.json +4 -0
- data/static/info_arch.json +134 -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
|
@@ -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
|
@@ -165,6 +165,12 @@ module Primer
|
|
165
165
|
class LookbookPagesBackend < Backend
|
166
166
|
attr_reader :registry, :manifest
|
167
167
|
|
168
|
+
IGNORED_COMPONENTS = [
|
169
|
+
Primer::Alpha::FormControl
|
170
|
+
]
|
171
|
+
|
172
|
+
IGNORED_COMPONENTS.freeze
|
173
|
+
|
168
174
|
def initialize(registry, manifest)
|
169
175
|
@registry = registry
|
170
176
|
@manifest = manifest
|
@@ -172,6 +178,8 @@ module Primer
|
|
172
178
|
|
173
179
|
def generate
|
174
180
|
each_component do |component_ref|
|
181
|
+
next if IGNORED_COMPONENTS.include?(component_ref.klass)
|
182
|
+
|
175
183
|
page_for(component_ref).generate
|
176
184
|
end
|
177
185
|
generate_system_args_docs
|