primer_view_components 0.11.0 → 0.13.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 +50 -0
- data/app/assets/javascripts/app/components/primer/alpha/tool_tip.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/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/action_bar_element.js +8 -3
- data/app/components/primer/alpha/action_bar_element.ts +8 -1
- data/app/components/primer/alpha/action_list/form_wrapper.html.erb +4 -2
- data/app/components/primer/alpha/action_list/form_wrapper.rb +20 -9
- data/app/components/primer/alpha/action_menu/action_menu_element.js +1 -10
- data/app/components/primer/alpha/action_menu/action_menu_element.ts +0 -11
- data/app/components/primer/alpha/action_menu.rb +120 -3
- data/app/components/primer/alpha/modal_dialog.js +10 -13
- data/app/components/primer/alpha/modal_dialog.ts +10 -13
- data/app/components/primer/alpha/segmented_control/item.html.erb +1 -8
- data/app/components/primer/alpha/segmented_control/item.rb +38 -4
- data/app/components/primer/alpha/segmented_control.css +1 -1
- data/app/components/primer/alpha/segmented_control.css.json +14 -13
- data/app/components/primer/alpha/segmented_control.css.map +1 -1
- data/app/components/primer/alpha/segmented_control.pcss +75 -66
- data/app/components/primer/alpha/segmented_control.rb +10 -0
- data/app/components/primer/alpha/text_field.css +1 -1
- data/app/components/primer/alpha/tool_tip.d.ts +1 -0
- data/app/components/primer/alpha/tool_tip.js +26 -93
- data/app/components/primer/alpha/tool_tip.ts +25 -91
- data/app/components/primer/beta/base_button.rb +4 -0
- data/app/components/primer/beta/button.css +1 -1
- data/app/components/primer/beta/button.css.map +1 -1
- data/app/components/primer/beta/button.pcss +6 -2
- data/lib/primer/accessibility.rb +3 -1
- data/lib/primer/static/generate_info_arch.rb +86 -5
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/action_menu_preview/single_select_form_items.html.erb +31 -0
- data/previews/primer/alpha/action_menu_preview/with_actions.html.erb +1 -1
- data/previews/primer/alpha/action_menu_preview.rb +8 -2
- data/previews/primer/alpha/check_box_preview.rb +1 -0
- data/previews/primer/alpha/dialog_preview/autofocus_element.html.erb +8 -0
- data/previews/primer/alpha/dialog_preview.rb +5 -0
- data/previews/primer/alpha/radio_button_preview.rb +1 -0
- data/previews/primer/alpha/segmented_control_preview.rb +35 -0
- data/previews/primer/alpha/tooltip_preview/tooltip_with_dialog_moving_focus_to_input.html.erb +23 -0
- data/previews/primer/alpha/tooltip_preview.rb +6 -1
- data/previews/primer/beta/button_group_preview.rb +6 -6
- data/previews/primer/beta/button_preview.rb +20 -2
- data/previews/primer/beta/icon_button_preview.rb +3 -0
- data/static/arguments.json +6 -0
- data/static/classes.json +12 -0
- data/static/constants.json +12 -1
- data/static/info_arch.json +125 -160
- data/static/previews.json +91 -172
- metadata +5 -310
@@ -15,7 +15,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
15
15
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
16
16
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
17
17
|
};
|
18
|
-
var _ActionBarElement_instances, _ActionBarElement_initialBarWidth, _ActionBarElement_previousBarWidth, _ActionBarElement_focusZoneAbortController, _ActionBarElement_itemGap, _ActionBarElement_availableSpace, _ActionBarElement_menuSpace, _ActionBarElement_shrink, _ActionBarElement_grow, _ActionBarElement_showItem, _ActionBarElement_hideItem, _ActionBarElement_menuItems_get;
|
18
|
+
var _ActionBarElement_instances, _ActionBarElement_initialBarWidth, _ActionBarElement_previousBarWidth, _ActionBarElement_focusZoneAbortController, _ActionBarElement_isVisible, _ActionBarElement_itemGap, _ActionBarElement_availableSpace, _ActionBarElement_menuSpace, _ActionBarElement_shrink, _ActionBarElement_grow, _ActionBarElement_showItem, _ActionBarElement_hideItem, _ActionBarElement_menuItems_get;
|
19
19
|
import { controller, targets, target } from '@github/catalyst';
|
20
20
|
import { focusZone, FocusKeys } from '@primer/behaviors';
|
21
21
|
const instersectionObserver = new IntersectionObserver(entries => {
|
@@ -94,12 +94,17 @@ let ActionBarElement = class ActionBarElement extends HTMLElement {
|
|
94
94
|
bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd,
|
95
95
|
focusOutBehavior: 'wrap',
|
96
96
|
focusableElementFilter: element => {
|
97
|
-
return
|
97
|
+
return __classPrivateFieldGet(this, _ActionBarElement_instances, "m", _ActionBarElement_isVisible).call(this, element);
|
98
98
|
}
|
99
99
|
}), "f");
|
100
100
|
}
|
101
101
|
};
|
102
|
-
_ActionBarElement_initialBarWidth = new WeakMap(), _ActionBarElement_previousBarWidth = new WeakMap(), _ActionBarElement_focusZoneAbortController = new WeakMap(), _ActionBarElement_instances = new WeakSet(),
|
102
|
+
_ActionBarElement_initialBarWidth = new WeakMap(), _ActionBarElement_previousBarWidth = new WeakMap(), _ActionBarElement_focusZoneAbortController = new WeakMap(), _ActionBarElement_instances = new WeakSet(), _ActionBarElement_isVisible = function _ActionBarElement_isVisible(element) {
|
103
|
+
// Safari doesn't support `checkVisibility` yet.
|
104
|
+
if (typeof element.checkVisibility === 'function')
|
105
|
+
return element.checkVisibility();
|
106
|
+
return Boolean(element.offsetParent || element.offsetWidth || element.offsetHeight);
|
107
|
+
}, _ActionBarElement_itemGap = function _ActionBarElement_itemGap() {
|
103
108
|
var _a;
|
104
109
|
return parseInt((_a = window.getComputedStyle(this.itemContainer)) === null || _a === void 0 ? void 0 : _a.columnGap, 10) || 0;
|
105
110
|
}, _ActionBarElement_availableSpace = function _ActionBarElement_availableSpace() {
|
@@ -86,11 +86,18 @@ class ActionBarElement extends HTMLElement {
|
|
86
86
|
bindKeys: FocusKeys.ArrowHorizontal | FocusKeys.HomeAndEnd,
|
87
87
|
focusOutBehavior: 'wrap',
|
88
88
|
focusableElementFilter: element => {
|
89
|
-
return
|
89
|
+
return this.#isVisible(element)
|
90
90
|
}
|
91
91
|
})
|
92
92
|
}
|
93
93
|
|
94
|
+
#isVisible(element: HTMLElement): boolean {
|
95
|
+
// Safari doesn't support `checkVisibility` yet.
|
96
|
+
if (typeof element.checkVisibility === 'function') return element.checkVisibility()
|
97
|
+
|
98
|
+
return Boolean(element.offsetParent || element.offsetWidth || element.offsetHeight)
|
99
|
+
}
|
100
|
+
|
94
101
|
#itemGap(): number {
|
95
102
|
return parseInt(window.getComputedStyle(this.itemContainer)?.columnGap, 10) || 0
|
96
103
|
}
|
@@ -1,7 +1,9 @@
|
|
1
1
|
<% if form_required? %>
|
2
2
|
<%= form_with(url: @action, method: @http_method, **@form_arguments) do %>
|
3
|
-
<% if
|
4
|
-
|
3
|
+
<% if render_inputs? %>
|
4
|
+
<% @inputs.each do |input_arguments| %>
|
5
|
+
<%= render(Primer::BaseComponent.new(tag: :input, **input_arguments)) %>
|
6
|
+
<% end %>
|
5
7
|
<% end %>
|
6
8
|
<%= content %>
|
7
9
|
<% end %>
|
@@ -24,14 +24,25 @@ module Primer
|
|
24
24
|
|
25
25
|
name = @form_arguments.delete(:name)
|
26
26
|
value = @form_arguments.delete(:value) || name
|
27
|
+
inputs = @form_arguments.delete(:inputs) || []
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
# For the older version of this component that only allowed you to
|
30
|
+
# specify a single input
|
31
|
+
if inputs.empty?
|
32
|
+
inputs << {
|
33
|
+
name: name,
|
34
|
+
value: value,
|
35
|
+
**(@form_arguments.delete(:input_arguments) || {})
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
@inputs = inputs.map do |input_data|
|
40
|
+
input_data = input_data.dup
|
41
|
+
input_data[:type] ||= :hidden
|
42
|
+
input_data[:data] ||= {}
|
43
|
+
input_data[:data][:list_item_input] = true
|
44
|
+
input_data
|
45
|
+
end
|
35
46
|
end
|
36
47
|
|
37
48
|
def get?
|
@@ -42,8 +53,8 @@ module Primer
|
|
42
53
|
@action && !get?
|
43
54
|
end
|
44
55
|
|
45
|
-
def
|
46
|
-
@
|
56
|
+
def render_inputs?
|
57
|
+
@inputs.present?
|
47
58
|
end
|
48
59
|
|
49
60
|
private
|
@@ -109,7 +109,7 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
|
|
109
109
|
__classPrivateFieldGet(this, _ActionMenuElement_abortController, "f").abort();
|
110
110
|
}
|
111
111
|
handleEvent(event) {
|
112
|
-
var _a
|
112
|
+
var _a;
|
113
113
|
const targetIsInvoker = (_a = this.invokerElement) === null || _a === void 0 ? void 0 : _a.contains(event.target);
|
114
114
|
const eventIsActivation = __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isActivation).call(this, event);
|
115
115
|
if (targetIsInvoker && event.type === 'mousedown') {
|
@@ -127,10 +127,6 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
|
|
127
127
|
__classPrivateFieldSet(this, _ActionMenuElement_invokerBeingClicked, false, "f");
|
128
128
|
return;
|
129
129
|
}
|
130
|
-
// Ignore events within dialogs within menus
|
131
|
-
if (((_b = event.target) === null || _b === void 0 ? void 0 : _b.closest('dialog')) || ((_c = event.target) === null || _c === void 0 ? void 0 : _c.closest('modal-dialog'))) {
|
132
|
-
return;
|
133
|
-
}
|
134
130
|
if (event.type === 'focusout') {
|
135
131
|
if (__classPrivateFieldGet(this, _ActionMenuElement_invokerBeingClicked, "f"))
|
136
132
|
return;
|
@@ -249,11 +245,6 @@ _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalL
|
|
249
245
|
item.setAttribute('aria-checked', `${checked}`);
|
250
246
|
}
|
251
247
|
__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_updateInput).call(this);
|
252
|
-
if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
|
253
|
-
// prevent buttons from being clicked twice
|
254
|
-
event.preventDefault();
|
255
|
-
return;
|
256
|
-
}
|
257
248
|
}, _ActionMenuElement_activateItem = function _ActionMenuElement_activateItem(event, item) {
|
258
249
|
const eventWillActivateByDefault = (event instanceof MouseEvent && event.type === 'click') ||
|
259
250
|
(event instanceof KeyboardEvent &&
|
@@ -172,11 +172,6 @@ export class ActionMenuElement extends HTMLElement {
|
|
172
172
|
return
|
173
173
|
}
|
174
174
|
|
175
|
-
// Ignore events within dialogs within menus
|
176
|
-
if ((event.target as Element)?.closest('dialog') || (event.target as Element)?.closest('modal-dialog')) {
|
177
|
-
return
|
178
|
-
}
|
179
|
-
|
180
175
|
if (event.type === 'focusout') {
|
181
176
|
if (this.#invokerBeingClicked) return
|
182
177
|
|
@@ -283,12 +278,6 @@ export class ActionMenuElement extends HTMLElement {
|
|
283
278
|
}
|
284
279
|
|
285
280
|
this.#updateInput()
|
286
|
-
|
287
|
-
if (event instanceof KeyboardEvent && event.target instanceof HTMLButtonElement) {
|
288
|
-
// prevent buttons from being clicked twice
|
289
|
-
event.preventDefault()
|
290
|
-
return
|
291
|
-
}
|
292
281
|
}
|
293
282
|
|
294
283
|
#activateItem(event: Event, item: Element) {
|
@@ -3,14 +3,131 @@
|
|
3
3
|
|
4
4
|
module Primer
|
5
5
|
module Alpha
|
6
|
-
# ActionMenu is used for actions, navigation, to display secondary options, or single/multi select lists. They appear when
|
6
|
+
# ActionMenu is used for actions, navigation, to display secondary options, or single/multi select lists. They appear when
|
7
|
+
# users interact with buttons, actions, or other controls.
|
7
8
|
#
|
8
9
|
# The only allowed elements for the `Item` components are: `:a`, `:button`, and `:clipboard-copy`. The default is `:button`.
|
9
10
|
#
|
11
|
+
# ### Select variants
|
12
|
+
#
|
13
|
+
# While `ActionMenu`s default to a list of buttons that can link to other pages, copy text to the clipboard, etc, they also support
|
14
|
+
# `single` and `multiple` select variants. The single select variant allows a single item to be "selected" (i.e. marked "active")
|
15
|
+
# when clicked, which will cause a check mark to appear to the left of the item text. When the `multiple` select variant is chosen,
|
16
|
+
# multiple items may be selected and check marks will appear next to each selected item.
|
17
|
+
#
|
18
|
+
# Use the `select_variant:` option to control which variant the `ActionMenu` uses. For more information, see the documentation on
|
19
|
+
# supported arguments below.
|
20
|
+
#
|
21
|
+
# ### Dynamic labels
|
22
|
+
#
|
23
|
+
# When using the `single` select variant, an optional label indicating the selected item can be displayed inside the menu button.
|
24
|
+
# Dynamic labels can also be prefixed with custom text.
|
25
|
+
#
|
26
|
+
# Pass `dynamic_label: true` to enable dynamic label behavior, and pass `dynamic_label_prefix: "<string>"` to set a custom prefix.
|
27
|
+
# For more information, see the documentation on supported arguments below.
|
28
|
+
#
|
29
|
+
# ### `ActionMenu`s as form inputs
|
30
|
+
#
|
31
|
+
# When using either the `single` or `multiple` select variants, `ActionMenu`s can be used as form inputs. They behave very
|
32
|
+
# similarly to how HTML `<select>` boxes behave, and play nicely with Rails' built-in form mechanisms. Pass arguments via the
|
33
|
+
# `form_arguments:` argument, including the Rails form builder object and the name of the field:
|
34
|
+
#
|
35
|
+
# ```erb
|
36
|
+
# <% form_with(url: update_merge_strategy_path) do |f| %>
|
37
|
+
# <%= render(Primer::Alpha::ActionMenu.new(form_arguments: { builder: f, name: "merge_strategy" })) do |menu| %>
|
38
|
+
# <% menu.with_item(label: "Fast forward", data: { value: "fast_forward" }) %>
|
39
|
+
# <% menu.with_item(label: "Recursive", data: { value: "recursive" }) %>
|
40
|
+
# <% menu.with_item(label: "Ours", data: { value: "ours" }) %>
|
41
|
+
# <% menu.with_item(label: "Theirs", data: { value: "theirs" }) %>
|
42
|
+
# <% end %>
|
43
|
+
# <% end %>
|
44
|
+
# ```
|
45
|
+
#
|
46
|
+
# The value of the `data: { value: ... }` argument is sent to the server on submit, keyed using the name provided above
|
47
|
+
# (eg. `"merge_strategy"`). If no value is provided for an item, the value of that item is the item's label. Here's the
|
48
|
+
# corresponding `MergeStrategyController` that might be written to handle the form above:
|
49
|
+
#
|
50
|
+
# ```ruby
|
51
|
+
# class MergeStrategyController < ApplicationController
|
52
|
+
# def update
|
53
|
+
# puts "You chose #{merge_strategy_params[:merge_strategy]}"
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# private
|
57
|
+
#
|
58
|
+
# def merge_strategy_params
|
59
|
+
# params.permit(:merge_strategy)
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# ```
|
63
|
+
#
|
64
|
+
# ### `ActionMenu` items that submit forms
|
65
|
+
#
|
66
|
+
# Whereas `ActionMenu` items normally permit navigation via `<a>` tags which make HTTP `get` requests, `ActionMenu` items
|
67
|
+
# also permit navigation via `POST` requests. To enable this behavior, include the `href:` argument as normal, but also pass
|
68
|
+
# the `form_arguments:` argument to the appropriate item:
|
69
|
+
#
|
70
|
+
# ```erb
|
71
|
+
# <%= render(Primer::Alpha::ActionMenu.new) do |menu| %>
|
72
|
+
# <% menu.with_item(
|
73
|
+
# label: "Repository",
|
74
|
+
# href: update_repo_grouping_path,
|
75
|
+
# form_arguments: {
|
76
|
+
# method: :post,
|
77
|
+
# name: "group_by",
|
78
|
+
# value: "repository"
|
79
|
+
# }
|
80
|
+
# ) %>
|
81
|
+
# <% end %>
|
82
|
+
# ```
|
83
|
+
#
|
84
|
+
# Make sure to specify `method: :post`, as the default is `:get`. When clicked, the list item will submit a POST request to
|
85
|
+
# the URL passed in the `href:` argument, including a parameter named `"group_by"` with a value of `"repository"`. If no value
|
86
|
+
# is given, the name, eg. `"group_by"`, will be used as the value.
|
87
|
+
#
|
88
|
+
# It is possible to include multiple fields on submit. Instead of passing the `name:` and `value:` arguments, pass an array via
|
89
|
+
# the `inputs:` argument:
|
90
|
+
#
|
91
|
+
# ```erb
|
92
|
+
# <%= render(Primer::Alpha::ActionMenu.new) do |menu| %>
|
93
|
+
# <% menu.with_show_button { "Group By" } %>
|
94
|
+
# <% menu.with_item(
|
95
|
+
# label: "Repository",
|
96
|
+
# href: update_repo_grouping_path,
|
97
|
+
# form_arguments: {
|
98
|
+
# method: :post,
|
99
|
+
# inputs: [{
|
100
|
+
# name: "group_by",
|
101
|
+
# value: "repository"
|
102
|
+
# }, {
|
103
|
+
# name: "some_other_field",
|
104
|
+
# value: "some value",
|
105
|
+
# }],
|
106
|
+
# }
|
107
|
+
# ) %>
|
108
|
+
# <% end %>
|
109
|
+
# ```
|
110
|
+
#
|
111
|
+
# ### Form arguments
|
112
|
+
#
|
113
|
+
# The following table summarizes the arguments allowed in the `form_arguments:` hash mentioned above.
|
114
|
+
#
|
115
|
+
# |Name |Type |Default|Description|
|
116
|
+
# |:----------------|:-------------|:------|:----------|
|
117
|
+
# |`method` |`Symbol` |`:get` |The HTTP request method to use to submit the form. One of `:get`, `:post`, `:patch`, `:put`, `:delete`, or `:head`|
|
118
|
+
# |`name` |`String` |`nil` |The name of the field that will be sent to the server on submit.|
|
119
|
+
# |`value` |`String` |`nil` |The value of the field that will be sent to the server on submit.|
|
120
|
+
# |`input_arguments`|`Hash` |`{}` |Additional key/value pairs to emit as HTML attributes on the `<input type="hidden">` element.|
|
121
|
+
# |`inputs` |`Array<Hash>` |`[]` |An array of hashes representing HTML `<input type="hidden">` elements. Must contain at least `name:` and `value:` keys. If additional key/value pairs are provided, they are emitted as HTML attributes on the `<input>` element. This argument supercedes the `name:`, `value:`, and `:input_arguments` arguments listed above.|
|
122
|
+
#
|
123
|
+
# The elements of the `inputs:` array will be emitted as HTML `<input type="hidden">` elements.
|
124
|
+
#
|
10
125
|
# @accessibility
|
11
|
-
# The action for the menu item needs to be on the element with `role="menuitem"`. Semantics are removed for everything
|
126
|
+
# The action for the menu item needs to be on the element with `role="menuitem"`. Semantics are removed for everything
|
127
|
+
# nested inside of it. When a menu item is selected, the menu will close immediately.
|
12
128
|
#
|
13
|
-
# Additional information around the keyboard functionality and implementation can be found on the
|
129
|
+
# Additional information around the keyboard functionality and implementation can be found on the
|
130
|
+
# [WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.2/#menu).
|
14
131
|
class ActionMenu < Primer::Component
|
15
132
|
status :alpha
|
16
133
|
|
@@ -38,19 +38,16 @@ function clickHandler(event) {
|
|
38
38
|
return;
|
39
39
|
}
|
40
40
|
}
|
41
|
-
|
42
|
-
const topLevelDialog = overlayStack[overlayStack.length - 1];
|
43
|
-
if (!topLevelDialog)
|
41
|
+
if (!overlayStack.length)
|
44
42
|
return;
|
45
|
-
dialogId = button.getAttribute('data-close-dialog-id');
|
46
|
-
if (dialogId
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
topLevelDialog.close(true);
|
43
|
+
dialogId = button.getAttribute('data-close-dialog-id') || button.getAttribute('data-submit-dialog-id');
|
44
|
+
if (dialogId) {
|
45
|
+
const dialog = document.getElementById(dialogId);
|
46
|
+
if (dialog instanceof ModalDialogElement) {
|
47
|
+
const dialogIndex = overlayStack.findIndex(ele => ele.id === dialogId);
|
48
|
+
overlayStack.splice(dialogIndex, 1);
|
49
|
+
dialog.close(button.hasAttribute('data-submit-dialog-id'));
|
50
|
+
}
|
54
51
|
}
|
55
52
|
}
|
56
53
|
function keydownHandler(event) {
|
@@ -107,7 +104,7 @@ export class ModalDialogElement extends HTMLElement {
|
|
107
104
|
if (__classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").signal.aborted) {
|
108
105
|
__classPrivateFieldSet(this, _ModalDialogElement_focusAbortController, new AbortController(), "f");
|
109
106
|
}
|
110
|
-
focusTrap(this,
|
107
|
+
focusTrap(this, this.querySelector('[autofocus]'), __classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").signal);
|
111
108
|
overlayStack.push(this);
|
112
109
|
}
|
113
110
|
else {
|
@@ -30,20 +30,17 @@ function clickHandler(event: Event) {
|
|
30
30
|
return
|
31
31
|
}
|
32
32
|
}
|
33
|
-
// Find the top level dialog that is open.
|
34
|
-
const topLevelDialog = overlayStack[overlayStack.length - 1]
|
35
|
-
if (!topLevelDialog) return
|
36
33
|
|
37
|
-
|
38
|
-
if (dialogId === topLevelDialog.id) {
|
39
|
-
overlayStack.pop()
|
40
|
-
topLevelDialog.close()
|
41
|
-
}
|
34
|
+
if (!overlayStack.length) return
|
42
35
|
|
43
|
-
dialogId = button.getAttribute('data-submit-dialog-id')
|
44
|
-
if (dialogId
|
45
|
-
|
46
|
-
|
36
|
+
dialogId = button.getAttribute('data-close-dialog-id') || button.getAttribute('data-submit-dialog-id')
|
37
|
+
if (dialogId) {
|
38
|
+
const dialog = document.getElementById(dialogId)
|
39
|
+
if (dialog instanceof ModalDialogElement) {
|
40
|
+
const dialogIndex = overlayStack.findIndex(ele => ele.id === dialogId)
|
41
|
+
overlayStack.splice(dialogIndex, 1)
|
42
|
+
dialog.close(button.hasAttribute('data-submit-dialog-id'))
|
43
|
+
}
|
47
44
|
}
|
48
45
|
}
|
49
46
|
|
@@ -107,7 +104,7 @@ export class ModalDialogElement extends HTMLElement {
|
|
107
104
|
if (this.#focusAbortController.signal.aborted) {
|
108
105
|
this.#focusAbortController = new AbortController()
|
109
106
|
}
|
110
|
-
focusTrap(this,
|
107
|
+
focusTrap(this, this.querySelector('[autofocus]') as HTMLElement, this.#focusAbortController.signal)
|
111
108
|
overlayStack.push(this)
|
112
109
|
} else {
|
113
110
|
if (!this.open) return
|
@@ -2,12 +2,5 @@
|
|
2
2
|
"SegmentedControl-item",
|
3
3
|
"SegmentedControl-item--selected": @selected
|
4
4
|
) %>" role="listitem" data-targets="segmented-control.items">
|
5
|
-
|
6
|
-
<%= render Primer::Beta::IconButton.new(icon: @icon, "aria-label": @label, **@system_arguments) %>
|
7
|
-
<% else %>
|
8
|
-
<%= render Primer::Beta::Button.new(**@system_arguments) do |button| %>
|
9
|
-
<% button.with_leading_visual_icon(icon: @icon) unless @icon.nil? %>
|
10
|
-
<%= @label %>
|
11
|
-
<% end %>
|
12
|
-
<% end %>
|
5
|
+
<%= render @button %>
|
13
6
|
</li>
|
@@ -13,16 +13,50 @@ module Primer
|
|
13
13
|
# @param selected [Boolean] Whether the item is selected
|
14
14
|
# @param icon [Symbol] The icon to use
|
15
15
|
# @param hide_labels [Symbol] Whether to only show the icon
|
16
|
-
def initialize(
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def initialize(
|
17
|
+
label:,
|
18
|
+
selected: false,
|
19
|
+
icon: nil,
|
20
|
+
hide_labels: false,
|
21
|
+
**system_arguments
|
22
|
+
)
|
20
23
|
@selected = selected
|
21
24
|
|
22
25
|
@system_arguments = system_arguments
|
23
26
|
@system_arguments[:"data-action"] = "click:segmented-control#select" if system_arguments[:href].nil?
|
24
27
|
@system_arguments[:"aria-current"] = selected
|
25
28
|
@system_arguments[:scheme] = :invisible
|
29
|
+
|
30
|
+
if hide_labels
|
31
|
+
@button = Primer::Beta::IconButton.new(
|
32
|
+
icon: icon,
|
33
|
+
"aria-label": label,
|
34
|
+
**@system_arguments
|
35
|
+
)
|
36
|
+
else
|
37
|
+
@button = Primer::Beta::Button.new(**@system_arguments)
|
38
|
+
@button.with_leading_visual_icon(icon: icon) if icon
|
39
|
+
@button.with_content(label)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @!parse
|
44
|
+
# # Optional trailing Label
|
45
|
+
# #
|
46
|
+
# # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %>'s `with_trailing_visual_label` slot.
|
47
|
+
# renders_one(:trailing_visual_label)
|
48
|
+
|
49
|
+
# Optional trailing label.
|
50
|
+
#
|
51
|
+
# @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Primer::Beta::Button) %>'s `with_trailing_visual_label` slot.
|
52
|
+
def with_trailing_visual_label(**system_arguments, &block)
|
53
|
+
@button.with_trailing_visual_label(**system_arguments, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def before_render
|
59
|
+
content
|
26
60
|
end
|
27
61
|
end
|
28
62
|
end
|
@@ -1 +1 @@
|
|
1
|
-
.SegmentedControl{background-color:var(--controlTrack-bgColor-rest,var(--color-segmented-control-bg));border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;list-style:none}.SegmentedControl-item{border:var(--borderWidth-thin,max(1px,.0625rem)) solid #0000;border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;
|
1
|
+
.SegmentedControl{--segmentedControl-item-padding:var(--control-small-paddingBlock,0.25rem);background-color:var(--controlTrack-bgColor-rest,var(--color-segmented-control-bg));border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;list-style:none}.SegmentedControl--iconOnly .Button--iconOnly.Button--large,.SegmentedControl--iconOnly .Button--iconOnly.Button--medium,.SegmentedControl--iconOnly .Button--iconOnly.Button--small{padding-inline:0!important;width:100%}.SegmentedControl--small{--segmentedControl-item-padding:var(--control-xsmall-paddingBlock,0.125rem)}.SegmentedControl--small .SegmentedControl-item{height:var(--control-small-size,1.75rem)}.SegmentedControl--small .SegmentedControl-item .Button{padding-inline:calc(var(--control-xsmall-paddingInline-normal,.5rem) - var(--segmentedControl-item-padding))}.SegmentedControl--small.SegmentedControl--iconOnly .SegmentedControl-item{width:var(--control-small-size,1.75rem)}.SegmentedControl--medium .SegmentedControl-item{height:var(--control-medium-size,2rem)}.SegmentedControl--medium.SegmentedControl--iconOnly .SegmentedControl-item{width:var(--control-medium-size,2rem)}.SegmentedControl--large .SegmentedControl-item{height:var(--control-large-size,2.5rem)}.SegmentedControl--large .SegmentedControl-item .Button{padding-inline:calc(var(--control-large-paddingInline-normal,.75rem) - var(--segmentedControl-item-padding))}.SegmentedControl--large.SegmentedControl--iconOnly .SegmentedControl-item{width:var(--control-large-size,2.5rem)}.SegmentedControl-item{border:var(--borderWidth-thin,max(1px,.0625rem)) solid #0000;border-radius:var(--borderRadius-medium,.375rem);display:inline-flex;height:var(--control-medium-size,2rem);justify-content:center;padding:var(--segmentedControl-item-padding);position:relative}.SegmentedControl-item .Button-withTooltip{width:100%}.SegmentedControl-item .Button--invisible:hover:not(:disabled){background-color:var(--controlTrack-bgColor-hover,var(--color-action-list-item-default-hover-bg))}.SegmentedControl-item .Button--invisible:active:not(:disabled){background-color:var(--controlTrack-bgColor-active,var(--color-action-list-item-default-active-bg))}.SegmentedControl-item.SegmentedControl-item--selected{background-color:var(--controlKnob-bgColor-rest,var(--color-segmented-control-button-bg));border-color:var(--controlKnob-borderColor-rest,var(--color-segmented-control-button-selected-border))}.SegmentedControl-item.SegmentedControl-item--selected .Button{font-weight:var(--base-text-weight-semibold,600)}.SegmentedControl-item.SegmentedControl-item--selected .Button:hover{background-color:initial}.SegmentedControl-item.SegmentedControl-item--selected:before{border-color:#0000!important}.SegmentedControl-item.SegmentedControl-item--selected+.SegmentedControl-item:before{border-color:#0000}.SegmentedControl-item .Button-label[data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold,600);height:0;visibility:hidden}.SegmentedControl-item:not(:first-child):before{border-left:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-default,var(--color-border-default));content:"";inset:0 0 0 -1px;margin-bottom:var(--control-medium-paddingBlock,.375rem);margin-top:var(--control-medium-paddingBlock,.375rem);position:absolute}.SegmentedControl-item .Button{border:0;border-radius:calc(var(--borderRadius-medium,.375rem) - var(--segmentedControl-item-padding)/2);font-weight:var(--base-text-weight-normal,400);height:100%;padding-inline:calc(var(--control-medium-paddingInline-normal,.75rem) - var(--segmentedControl-item-padding));width:100%}.SegmentedControl-item .Button:focus-visible{border-radius:calc(var(--borderRadius-medium,.375rem) - var(--segmentedControl-item-padding)/1);outline-offset:calc(var(--segmentedControl-item-padding) - var(--borderWidth-thin,max(1px, .0625rem)))}.SegmentedControl-item .Button--invisible.Button--invisible-noVisuals .Button-label{color:var(--button-default-fgColor-rest,var(--color-btn-text))}.SegmentedControl--fullWidth{display:flex}.SegmentedControl--fullWidth .SegmentedControl-item{flex:1;justify-content:center}
|
@@ -2,7 +2,20 @@
|
|
2
2
|
"name": "alpha/segmented_control",
|
3
3
|
"selectors": [
|
4
4
|
".SegmentedControl",
|
5
|
+
".SegmentedControl--iconOnly .Button--iconOnly.Button--large",
|
6
|
+
".SegmentedControl--iconOnly .Button--iconOnly.Button--medium",
|
7
|
+
".SegmentedControl--iconOnly .Button--iconOnly.Button--small",
|
8
|
+
".SegmentedControl--small",
|
9
|
+
".SegmentedControl--small .SegmentedControl-item",
|
10
|
+
".SegmentedControl--small .SegmentedControl-item .Button",
|
11
|
+
".SegmentedControl--small.SegmentedControl--iconOnly .SegmentedControl-item",
|
12
|
+
".SegmentedControl--medium .SegmentedControl-item",
|
13
|
+
".SegmentedControl--medium.SegmentedControl--iconOnly .SegmentedControl-item",
|
14
|
+
".SegmentedControl--large .SegmentedControl-item",
|
15
|
+
".SegmentedControl--large .SegmentedControl-item .Button",
|
16
|
+
".SegmentedControl--large.SegmentedControl--iconOnly .SegmentedControl-item",
|
5
17
|
".SegmentedControl-item",
|
18
|
+
".SegmentedControl-item .Button-withTooltip",
|
6
19
|
".SegmentedControl-item .Button--invisible:hover:not(:disabled)",
|
7
20
|
".SegmentedControl-item .Button--invisible:active:not(:disabled)",
|
8
21
|
".SegmentedControl-item.SegmentedControl-item--selected",
|
@@ -14,20 +27,8 @@
|
|
14
27
|
".SegmentedControl-item:not(:first-child):before",
|
15
28
|
".SegmentedControl-item .Button",
|
16
29
|
".SegmentedControl-item .Button:focus-visible",
|
17
|
-
".SegmentedControl-item .Button--small",
|
18
|
-
".SegmentedControl-item .Button--small.Button--iconOnly",
|
19
|
-
".SegmentedControl-item .Button--small.Button--iconOnly:before",
|
20
|
-
".SegmentedControl-item .Button--medium",
|
21
|
-
".SegmentedControl-item .Button--medium.Button--iconOnly",
|
22
|
-
".SegmentedControl-item .Button--medium.Button--iconOnly:before",
|
23
|
-
".SegmentedControl-item .Button--large",
|
24
|
-
".SegmentedControl-item .Button--large.Button--iconOnly",
|
25
|
-
".SegmentedControl-item .Button--large.Button--iconOnly:before",
|
26
|
-
".SegmentedControl-item .Button--iconOnly",
|
27
30
|
".SegmentedControl-item .Button--invisible.Button--invisible-noVisuals .Button-label",
|
28
31
|
".SegmentedControl--fullWidth",
|
29
|
-
".SegmentedControl--fullWidth .SegmentedControl-item"
|
30
|
-
".SegmentedControl--fullWidth .Button--iconOnly",
|
31
|
-
".SegmentedControl--fullWidth .Button-withTooltip"
|
32
|
+
".SegmentedControl--fullWidth .SegmentedControl-item"
|
32
33
|
]
|
33
34
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["segmented_control.pcss"
|
1
|
+
{"version":3,"sources":["segmented_control.pcss"],"names":[],"mappings":"AAEA,kBACE,yEAAkE,CAIlE,mFAAqF,CACrF,gDAAyC,CAHzC,mBAAoB,CACpB,eAGF,CAGE,qLAIE,0BAA4B,CAD5B,UAEF,CAKF,yBACE,2EAeF,CAbE,gDACE,wCAKF,CAHE,wDACE,4GACF,CAIA,2EACE,uCACF,CAKF,iDACE,sCACF,CAGE,4EACE,qCACF,CAKF,gDACE,uCAKF,CAHE,wDACE,4GACF,CAIA,2EACE,sCACF,CAMJ,uBAIE,4DAAiD,CACjD,gDAAyC,CAHzC,mBAAoB,CAIpB,sCAAkC,CAHlC,sBAAuB,CAIvB,4CAA6C,CAN7C,iBAoFF,CA5EE,2CACE,UACF,CAIE,+DACE,iGACF,CAEA,gEACE,mGACF,CAIF,uDACE,yFAA2F,CAC3F,sGAiBF,CAfE,+DACE,gDAKF,CAHE,qEACE,wBACF,CAGF,8DACE,4BACF,CAEA,qFACE,kBACF,CAIF,0DAKE,0BAA2B,CAJ3B,aAAc,CAEd,gDAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,gDAME,kHAAqE,CADrE,UAAW,CAHX,gBAAiB,CAEjB,wDAAiD,CADjD,qDAA8C,CAF9C,iBAMF,CAIF,+BAGE,QAAS,CAET,+FAA0F,CAD1F,8CAA2C,CAH3C,WAAY,CAKZ,6GAAuG,CAJvG,UAUF,CAJE,6CAEE,+FAA0F,CAD1F,sGAEF,CAGF,oFACE,8DACF,CAIF,6BACE,YAMF,CAJE,oDACE,MAAO,CACP,sBACF","file":"segmented_control.css","sourcesContent":["/* SegmentedControl */\n\n.SegmentedControl {\n --segmentedControl-item-padding: var(--control-small-paddingBlock);\n\n display: inline-flex;\n list-style: none;\n background-color: var(--controlTrack-bgColor-rest, var(--color-segmented-control-bg));\n border-radius: var(--borderRadius-medium);\n}\n\n.SegmentedControl--iconOnly {\n & .Button--iconOnly.Button--small,\n & .Button--iconOnly.Button--medium,\n & .Button--iconOnly.Button--large {\n width: 100%;\n padding-inline: 0 !important;\n }\n}\n\n/* sizes */\n\n.SegmentedControl--small {\n --segmentedControl-item-padding: var(--control-xsmall-paddingBlock);\n\n & .SegmentedControl-item {\n height: var(--control-small-size);\n\n & .Button {\n padding-inline: calc(var(--control-xsmall-paddingInline-normal) - var(--segmentedControl-item-padding));\n }\n }\n\n &.SegmentedControl--iconOnly {\n & .SegmentedControl-item {\n width: var(--control-small-size);\n }\n }\n}\n\n.SegmentedControl--medium {\n & .SegmentedControl-item {\n height: var(--control-medium-size);\n }\n\n &.SegmentedControl--iconOnly {\n & .SegmentedControl-item {\n width: var(--control-medium-size);\n }\n }\n}\n\n.SegmentedControl--large {\n & .SegmentedControl-item {\n height: var(--control-large-size);\n\n & .Button {\n padding-inline: calc(var(--control-large-paddingInline-normal) - var(--segmentedControl-item-padding));\n }\n }\n\n &.SegmentedControl--iconOnly {\n & .SegmentedControl-item {\n width: var(--control-large-size);\n }\n }\n}\n\n/* item */\n\n.SegmentedControl-item {\n position: relative;\n display: inline-flex;\n justify-content: center;\n border: var(--borderWidth-thin) solid transparent;\n border-radius: var(--borderRadius-medium);\n height: var(--control-medium-size);\n padding: var(--segmentedControl-item-padding);\n\n & .Button-withTooltip {\n width: 100%;\n }\n\n /* button color overrides */\n & .Button--invisible {\n &:hover:not(:disabled) {\n background-color: var(--controlTrack-bgColor-hover, var(--color-action-list-item-default-hover-bg));\n }\n\n &:active:not(:disabled) {\n background-color: var(--controlTrack-bgColor-active, var(--color-action-list-item-default-active-bg));\n }\n }\n\n /* Selected ---------------------------------------- */\n &.SegmentedControl-item--selected {\n background-color: var(--controlKnob-bgColor-rest, var(--color-segmented-control-button-bg));\n border-color: var(--controlKnob-borderColor-rest, var(--color-segmented-control-button-selected-border));\n\n & .Button {\n font-weight: var(--base-text-weight-semibold);\n\n &:hover {\n background-color: transparent;\n }\n }\n\n &::before {\n border-color: transparent !important;\n }\n\n & + .SegmentedControl-item::before {\n border-color: transparent;\n }\n }\n\n /* renders a visibly hidden \"copy\" of the text in bold, reserving box space for when text becomes bold on selected */\n & .Button-label[data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* Separator lines */\n &:not(:first-child) {\n &::before {\n position: absolute;\n inset: 0 0 0 -1px;\n margin-top: var(--control-medium-paddingBlock);\n margin-bottom: var(--control-medium-paddingBlock);\n content: '';\n border-left: var(--borderWidth-thin) solid var(--borderColor-default);\n }\n }\n\n /* Button ----------------------------------------- */\n & .Button {\n height: 100%;\n width: 100%;\n border: 0;\n font-weight: var(--base-text-weight-normal);\n border-radius: calc(var(--borderRadius-medium) - var(--segmentedControl-item-padding) / 2);\n padding-inline: calc(var(--control-medium-paddingInline-normal) - var(--segmentedControl-item-padding));\n\n &:focus-visible {\n outline-offset: calc(var(--segmentedControl-item-padding) - var(--borderWidth-thin));\n border-radius: calc(var(--borderRadius-medium) - var(--segmentedControl-item-padding) / 1);\n }\n }\n\n & .Button--invisible.Button--invisible-noVisuals .Button-label {\n color: var(--button-default-fgColor-rest);\n }\n}\n\n/* fullWidth */\n.SegmentedControl--fullWidth {\n display: flex;\n\n & .SegmentedControl-item {\n flex: 1;\n justify-content: center;\n }\n}\n"]}
|