primer_view_components 0.13.1 → 0.14.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 +41 -0
- data/README.md +1 -1
- data/app/assets/javascripts/app/components/primer/primer.d.ts +0 -1
- 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.css +1 -1
- data/app/components/primer/alpha/action_bar.css.json +1 -4
- data/app/components/primer/alpha/action_bar.css.map +1 -1
- data/app/components/primer/alpha/action_bar.pcss +1 -17
- data/app/components/primer/alpha/action_bar_element.js +21 -9
- data/app/components/primer/alpha/action_list/item.rb +0 -2
- data/app/components/primer/alpha/action_list.css +1 -1
- data/app/components/primer/alpha/action_list.css.map +1 -1
- data/app/components/primer/alpha/action_list.rb +0 -1
- data/app/components/primer/alpha/action_menu/action_menu_element.js +52 -26
- data/app/components/primer/alpha/action_menu/action_menu_element.ts +9 -6
- data/app/components/primer/alpha/action_menu/list.rb +0 -9
- data/app/components/primer/alpha/action_menu.html.erb +20 -18
- data/app/components/primer/alpha/auto_complete.css +1 -1
- data/app/components/primer/alpha/auto_complete.css.map +1 -1
- data/app/components/primer/alpha/button_marketing.css +1 -1
- data/app/components/primer/alpha/button_marketing.css.map +1 -1
- data/app/components/primer/alpha/dialog/header.rb +1 -1
- data/app/components/primer/alpha/dropdown.css +1 -1
- data/app/components/primer/alpha/dropdown.css.map +1 -1
- data/app/components/primer/alpha/layout.css +1 -1
- data/app/components/primer/alpha/layout.css.map +1 -1
- data/app/components/primer/alpha/menu.css +1 -1
- data/app/components/primer/alpha/menu.css.map +1 -1
- data/app/components/primer/alpha/nav_list/divider.rb +1 -0
- data/app/components/primer/alpha/nav_list/group.rb +1 -0
- data/app/components/primer/alpha/nav_list/heading.rb +1 -0
- data/app/components/primer/alpha/nav_list/item.rb +1 -0
- data/app/components/primer/alpha/nav_list.rb +1 -0
- data/app/components/primer/alpha/octicon_symbols.html.erb +1 -1
- data/app/components/primer/alpha/overlay.css +1 -1
- data/app/components/primer/alpha/overlay.css.json +0 -2
- data/app/components/primer/alpha/overlay.css.map +1 -1
- data/app/components/primer/alpha/overlay.pcss +0 -12
- data/app/components/primer/alpha/segmented_control.css +1 -1
- data/app/components/primer/alpha/segmented_control.css.json +0 -1
- data/app/components/primer/alpha/segmented_control.css.map +1 -1
- data/app/components/primer/alpha/segmented_control.js +2 -1
- data/app/components/primer/alpha/segmented_control.pcss +0 -4
- data/app/components/primer/alpha/tab_nav.css +1 -1
- data/app/components/primer/alpha/tab_nav.css.map +1 -1
- data/app/components/primer/alpha/text_field.css +1 -1
- data/app/components/primer/alpha/text_field.css.json +11 -5
- data/app/components/primer/alpha/text_field.css.map +1 -1
- data/app/components/primer/alpha/text_field.pcss +10 -1
- data/app/components/primer/alpha/toggle_switch.css +1 -1
- data/app/components/primer/alpha/toggle_switch.css.map +1 -1
- data/app/components/primer/alpha/underline_nav.css +1 -1
- data/app/components/primer/alpha/underline_nav.css.map +1 -1
- data/app/components/primer/alpha/x_banner.js +2 -1
- data/app/components/primer/anchored_position.js +2 -1
- data/app/components/primer/beta/auto_complete/item.rb +1 -1
- data/app/components/primer/beta/avatar.rb +1 -1
- data/app/components/primer/beta/base_button.rb +1 -1
- data/app/components/primer/beta/blankslate.css +1 -1
- data/app/components/primer/beta/blankslate.css.json +1 -0
- data/app/components/primer/beta/blankslate.css.map +1 -1
- data/app/components/primer/beta/blankslate.html.erb +16 -14
- data/app/components/primer/beta/blankslate.pcss +52 -2
- data/app/components/primer/beta/border_box.css +1 -1
- data/app/components/primer/beta/border_box.css.map +1 -1
- data/app/components/primer/beta/button.css +1 -1
- data/app/components/primer/beta/button.css.json +0 -1
- data/app/components/primer/beta/button.css.map +1 -1
- data/app/components/primer/beta/button.html.erb +18 -20
- data/app/components/primer/beta/button.pcss +0 -5
- data/app/components/primer/beta/button.rb +3 -0
- data/app/components/primer/beta/button_group.css +1 -1
- data/app/components/primer/beta/button_group.css.json +2 -4
- data/app/components/primer/beta/button_group.css.map +1 -1
- data/app/components/primer/beta/button_group.html.erb +3 -1
- data/app/components/primer/beta/button_group.pcss +2 -4
- data/app/components/primer/beta/button_group.rb +41 -12
- data/app/components/primer/beta/clipboard_copy.rb +4 -0
- data/app/components/primer/beta/clipboard_copy_button.rb +25 -0
- data/app/components/primer/beta/counter.rb +1 -1
- data/app/components/primer/beta/flash.html.erb +1 -1
- data/app/components/primer/beta/icon_button.html.erb +4 -6
- data/app/components/primer/beta/icon_button.rb +1 -3
- data/app/components/primer/beta/label.css +1 -1
- data/app/components/primer/beta/label.css.map +1 -1
- data/app/components/primer/beta/link.css +1 -1
- data/app/components/primer/beta/link.css.map +1 -1
- data/app/components/primer/beta/nav_list.js +14 -7
- data/app/components/primer/beta/subhead.css +1 -1
- data/app/components/primer/beta/subhead.css.json +2 -0
- data/app/components/primer/beta/subhead.css.map +1 -1
- data/app/components/primer/beta/subhead.pcss +8 -1
- data/app/components/primer/beta/subhead.rb +9 -1
- data/app/components/primer/focus_group.js +2 -1
- data/app/components/primer/primer.d.ts +0 -1
- data/app/components/primer/primer.js +0 -1
- data/app/components/primer/primer.pcss +0 -2
- data/app/components/primer/primer.ts +0 -1
- data/app/forms/action_menu_form.rb +20 -0
- data/app/forms/immediate_validation_form.rb +2 -2
- data/app/lib/primer/fetch_or_fallback_helper.rb +0 -1
- data/app/lib/primer/octicon/cache.rb +1 -1
- data/lib/primer/classify.rb +0 -2
- data/lib/primer/forms/action_menu.html.erb +6 -0
- data/lib/primer/forms/action_menu.rb +25 -0
- data/lib/primer/forms/acts_as_component.rb +0 -3
- data/lib/primer/forms/base.rb +0 -1
- data/lib/primer/forms/base_component.rb +0 -2
- data/lib/primer/forms/dsl/action_menu_input.rb +36 -0
- data/lib/primer/forms/dsl/input.rb +8 -1
- data/lib/primer/forms/dsl/input_methods.rb +9 -0
- data/lib/primer/forms/dsl/text_field_input.rb +8 -0
- data/lib/primer/forms/primer_base_component_wrapper.rb +0 -2
- data/lib/primer/forms/primer_text_field.js +40 -5
- data/lib/primer/forms/primer_text_field.ts +39 -7
- data/lib/primer/forms/validation_message.html.erb +2 -1
- data/lib/primer/static/generate_info_arch.rb +7 -3
- data/lib/primer/static/generate_previews.rb +0 -2
- data/lib/primer/view_components/engine.rb +5 -1
- data/lib/primer/view_components/linters/base_linter.rb +3 -2
- data/lib/primer/view_components/linters/deprecated_components_counter.rb +1 -1
- data/lib/primer/view_components/linters/disallow_action_list.rb +1 -0
- data/lib/primer/view_components/linters/severity_schema.rb +1 -0
- data/lib/primer/view_components/version.rb +2 -3
- data/lib/primer/yard/lookbook_pages_backend.rb +0 -2
- data/lib/rubocop/cop/primer/base_cop.rb +1 -1
- data/lib/rubocop/cop/primer/deprecated_arguments.rb +0 -2
- data/lib/rubocop/cop/primer/deprecated_components.rb +1 -1
- data/lib/rubocop/cop/primer/deprecated_label_schemes.rb +1 -1
- data/lib/rubocop/cop/primer/deprecated_label_variants.rb +1 -1
- data/previews/primer/alpha/octicon_symbols_preview/default.html.erb +6 -0
- data/previews/primer/alpha/octicon_symbols_preview/playground.html.erb +13 -0
- data/previews/primer/alpha/octicon_symbols_preview.rb +21 -0
- data/previews/primer/alpha/text_field_preview.rb +5 -0
- data/previews/primer/beta/avatar_preview.rb +6 -0
- data/previews/primer/beta/button_group_preview.rb +11 -0
- data/previews/primer/beta/clipboard_copy_button_preview.rb +29 -0
- data/previews/primer/beta/subhead_preview.rb +32 -4
- data/previews/primer/forms_preview/action_menu_form.html.erb +3 -0
- data/previews/primer/forms_preview.rb +4 -0
- data/static/arguments.json +75 -13
- data/static/audited_at.json +2 -0
- data/static/classes.json +9 -3
- data/static/constants.json +16 -1
- data/static/info_arch.json +335 -32
- data/static/previews.json +146 -0
- data/static/statuses.json +2 -0
- metadata +12 -2
@@ -171,12 +171,14 @@ let NavListElement = class NavListElement extends HTMLElement {
|
|
171
171
|
}
|
172
172
|
}
|
173
173
|
};
|
174
|
-
_NavListElement_instances = new WeakSet()
|
174
|
+
_NavListElement_instances = new WeakSet();
|
175
|
+
_NavListElement_parseHTML = function _NavListElement_parseHTML(document, html) {
|
175
176
|
const template = document.createElement('template');
|
176
177
|
// eslint-disable-next-line github/no-inner-html
|
177
178
|
template.innerHTML = html;
|
178
179
|
return document.importNode(template.content, true);
|
179
|
-
}
|
180
|
+
};
|
181
|
+
_NavListElement_findSelectedNavItemById = function _NavListElement_findSelectedNavItemById(itemId) {
|
180
182
|
var _a;
|
181
183
|
// First we compare the selected link to data-item-id for each nav item
|
182
184
|
for (const navItem of this.items) {
|
@@ -189,16 +191,19 @@ _NavListElement_instances = new WeakSet(), _NavListElement_parseHTML = function
|
|
189
191
|
}
|
190
192
|
}
|
191
193
|
return null;
|
192
|
-
}
|
194
|
+
};
|
195
|
+
_NavListElement_findSelectedNavItemByHref = function _NavListElement_findSelectedNavItemByHref(href) {
|
193
196
|
// If we didn't find a match, we compare the selected link to the href of each nav item
|
194
197
|
const selectedNavItem = this.querySelector(`.ActionListContent[href="${href}"]`);
|
195
198
|
if (selectedNavItem) {
|
196
199
|
return selectedNavItem.closest('.ActionListItem');
|
197
200
|
}
|
198
201
|
return null;
|
199
|
-
}
|
202
|
+
};
|
203
|
+
_NavListElement_findSelectedNavItemByCurrentLocation = function _NavListElement_findSelectedNavItemByCurrentLocation() {
|
200
204
|
return __classPrivateFieldGet(this, _NavListElement_instances, "m", _NavListElement_findSelectedNavItemByHref).call(this, window.location.pathname);
|
201
|
-
}
|
205
|
+
};
|
206
|
+
_NavListElement_select = function _NavListElement_select(navItem) {
|
202
207
|
const currentlySelectedItem = this.querySelector('.ActionListItem--navActive');
|
203
208
|
if (currentlySelectedItem)
|
204
209
|
__classPrivateFieldGet(this, _NavListElement_instances, "m", _NavListElement_deselect).call(this, currentlySelectedItem);
|
@@ -211,7 +216,8 @@ _NavListElement_instances = new WeakSet(), _NavListElement_parseHTML = function
|
|
211
216
|
this.expandItem(parentMenu);
|
212
217
|
parentMenu.classList.add('ActionListContent--hasActiveSubItem');
|
213
218
|
}
|
214
|
-
}
|
219
|
+
};
|
220
|
+
_NavListElement_deselect = function _NavListElement_deselect(navItem) {
|
215
221
|
navItem.classList.remove('ActionListItem--navActive');
|
216
222
|
if (navItem.children.length > 0) {
|
217
223
|
navItem.children[0].removeAttribute('aria-current');
|
@@ -221,7 +227,8 @@ _NavListElement_instances = new WeakSet(), _NavListElement_parseHTML = function
|
|
221
227
|
this.collapseItem(parentMenu);
|
222
228
|
parentMenu.classList.remove('ActionListContent--hasActiveSubItem');
|
223
229
|
}
|
224
|
-
}
|
230
|
+
};
|
231
|
+
_NavListElement_findParentMenu = function _NavListElement_findParentMenu(navItem) {
|
225
232
|
var _a;
|
226
233
|
if (!navItem.classList.contains('ActionListItem--subItem'))
|
227
234
|
return null;
|
@@ -1 +1 @@
|
|
1
|
-
.Subhead{border-bottom:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-muted,var(--color-border-muted));display:flex;flex-flow:row wrap;justify-content:flex-end;margin-bottom:var(--stack-gap-normal,1rem);padding-bottom:var(--stack-padding-condensed,.5rem)}.Subhead--spacious{margin-top:var(--base-size-40,2.5rem)}.Subhead-heading{flex:1 1 auto;font-
|
1
|
+
.Subhead{border-bottom:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-muted,var(--color-border-muted));display:flex;flex-flow:row wrap;justify-content:flex-end;margin-bottom:var(--stack-gap-normal,1rem);padding-bottom:var(--stack-padding-condensed,.5rem)}.Subhead--spacious{margin-top:var(--base-size-40,2.5rem)}.Subhead-heading{flex:1 1 auto;font-weight:var(--base-text-weight-normal,400);order:0}.Subhead-heading--large{font-size:var(--base-size-24,1.5rem)}.Subhead-heading--medium{font-size:var(--text-title-size-medium,1.25rem)}.Subhead-heading--danger{color:var(--fgColor-danger,var(--color-danger-fg));font-weight:var(--base-text-weight-semibold,600)}.Subhead-description{color:var(--fgColor-muted,var(--color-fg-muted));flex:1 100%;font-size:var(--text-body-size-medium,.875rem);order:2}.Subhead-actions{align-self:center;justify-content:flex-end;margin:var(--base-size-4,.25rem) 0 var(--base-size-4,.25rem) var(--base-size-4,.25rem);order:1}.Subhead-actions+.Subhead-description{margin-top:var(--base-size-4,.25rem)}
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["subhead.pcss"],"names":[],"mappings":"AAEA,SAIE,gHAAqE,CAHrE,YAAa,CAIb,kBAAmB,CACnB,wBAAyB,CAHzB,0CAAsC,CADtC,mDAKF,CAGA,mBACE,qCACF,CAGA,
|
1
|
+
{"version":3,"sources":["subhead.pcss"],"names":[],"mappings":"AAEA,SAIE,gHAAqE,CAHrE,YAAa,CAIb,kBAAmB,CACnB,wBAAyB,CAHzB,0CAAsC,CADtC,mDAKF,CAGA,mBACE,qCACF,CAGA,iBAEE,aAAc,CADd,8CAA2C,CAE3C,OACF,CAEA,wBACE,oCACF,CAEA,yBACE,+CACF,CAGA,yBAEE,kDAA4B,CAD5B,gDAEF,CAGA,qBAEE,gDAA2B,CAC3B,WAAY,CAFZ,8CAAuC,CAGvC,OACF,CAGA,iBAEE,iBAAkB,CAClB,wBAAyB,CAFzB,sFAAkE,CAGlE,OAKF,CAHE,sCACE,oCACF","file":"subhead.css","sourcesContent":["/* Subhead */\n\n.Subhead {\n display: flex;\n padding-bottom: var(--stack-padding-condensed);\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);\n flex-flow: row wrap;\n justify-content: flex-end; /* Keep actions right aligned. */\n}\n\n/* Modifier class to give a lot of breathing room between sections of content. */\n.Subhead--spacious {\n margin-top: var(--base-size-40);\n}\n\n/* <h2> sized heading with normal font weight */\n.Subhead-heading {\n font-weight: var(--base-text-weight-normal);\n flex: 1 1 auto;\n order: 0;\n}\n\n.Subhead-heading--large {\n font-size: var(--base-size-24);\n}\n\n.Subhead-heading--medium {\n font-size: var(--text-title-size-medium);\n}\n\n/* Make the text bold and red for dangerous content */\n.Subhead-heading--danger {\n font-weight: var(--base-text-weight-semibold);\n color: var(--fgColor-danger);\n}\n\n/* One-liner of supporting text */\n.Subhead-description {\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-muted);\n flex: 1 100%;\n order: 2;\n}\n\n/* Add 1 or 2 buttons to the right of the heading */\n.Subhead-actions {\n margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);\n align-self: center;\n justify-content: flex-end;\n order: 1;\n\n & + .Subhead-description {\n margin-top: var(--base-size-4);\n }\n}\n"]}
|
@@ -16,12 +16,19 @@
|
|
16
16
|
|
17
17
|
/* <h2> sized heading with normal font weight */
|
18
18
|
.Subhead-heading {
|
19
|
-
font-size: 24px;
|
20
19
|
font-weight: var(--base-text-weight-normal);
|
21
20
|
flex: 1 1 auto;
|
22
21
|
order: 0;
|
23
22
|
}
|
24
23
|
|
24
|
+
.Subhead-heading--large {
|
25
|
+
font-size: var(--base-size-24);
|
26
|
+
}
|
27
|
+
|
28
|
+
.Subhead-heading--medium {
|
29
|
+
font-size: var(--text-title-size-medium);
|
30
|
+
}
|
31
|
+
|
25
32
|
/* Make the text bold and red for dangerous content */
|
26
33
|
.Subhead-heading--danger {
|
27
34
|
font-weight: var(--base-text-weight-semibold);
|
@@ -17,16 +17,24 @@ module Primer
|
|
17
17
|
DEFAULT_HEADING_TAG = :div
|
18
18
|
HEADING_TAG_OPTIONS = [DEFAULT_HEADING_TAG, :h1, :h2, :h3, :h4, :h5, :h6].freeze
|
19
19
|
|
20
|
+
DEFAULT_HEADING_SIZE = :large
|
21
|
+
HEADING_SIZE_MAP = {
|
22
|
+
DEFAULT_HEADING_SIZE => "Subhead-heading--large",
|
23
|
+
:medium => "Subhead-heading--medium"
|
24
|
+
}.freeze
|
25
|
+
HEADING_SIZE_OPTIONS = HEADING_SIZE_MAP.keys.freeze
|
26
|
+
|
20
27
|
# The heading
|
21
28
|
#
|
22
29
|
# @param tag [Symbol] <%= one_of(Primer::Beta::Subhead::HEADING_TAG_OPTIONS)%>
|
23
30
|
# @param danger [Boolean] Whether to style the heading as dangerous.
|
24
31
|
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
25
|
-
renders_one :heading, lambda { |tag: DEFAULT_HEADING_TAG, danger: false, **system_arguments|
|
32
|
+
renders_one :heading, lambda { |tag: DEFAULT_HEADING_TAG, danger: false, size: DEFAULT_HEADING_SIZE, **system_arguments|
|
26
33
|
system_arguments[:tag] = fetch_or_fallback(HEADING_TAG_OPTIONS, tag, DEFAULT_HEADING_TAG)
|
27
34
|
system_arguments[:classes] = class_names(
|
28
35
|
system_arguments[:classes],
|
29
36
|
"Subhead-heading",
|
37
|
+
HEADING_SIZE_MAP[fetch_or_fallback(HEADING_SIZE_OPTIONS, size, DEFAULT_HEADING_SIZE)],
|
30
38
|
"Subhead-heading--danger": danger
|
31
39
|
)
|
32
40
|
|
@@ -15,7 +15,7 @@ const validSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role
|
|
15
15
|
const menuItemSelector = validSelectors.map(selector => `:not([hidden]) > ${selector}`).join(', ');
|
16
16
|
const getMnemonicFor = (item) => { var _a; return (_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim()[0].toLowerCase(); };
|
17
17
|
const printable = /^\S$/;
|
18
|
-
|
18
|
+
class FocusGroupElement extends HTMLElement {
|
19
19
|
constructor() {
|
20
20
|
super(...arguments);
|
21
21
|
_FocusGroupElement_instances.add(this);
|
@@ -159,6 +159,7 @@ export default class FocusGroupElement extends HTMLElement {
|
|
159
159
|
_FocusGroupElement_retainSignal = new WeakMap(), _FocusGroupElement_abortController = new WeakMap(), _FocusGroupElement_instances = new WeakSet(), _FocusGroupElement_items_get = function _FocusGroupElement_items_get() {
|
160
160
|
return this.querySelectorAll(menuItemSelector);
|
161
161
|
};
|
162
|
+
export default FocusGroupElement;
|
162
163
|
if (!customElements.get('focus-group')) {
|
163
164
|
window.FocusGroupElement = FocusGroupElement;
|
164
165
|
customElements.define('focus-group', FocusGroupElement);
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :nodoc:
|
4
|
+
class ActionMenuForm < ApplicationForm
|
5
|
+
form do |action_menu_form|
|
6
|
+
action_menu_form.action_menu(name: "city", label: "Favorite city", caption: "Select your favorite!") do |city_list|
|
7
|
+
city_list.with_item(label: "Lopez Island", data: { value: "lopez_island" }) do |item|
|
8
|
+
item.with_leading_visual_icon(icon: :log)
|
9
|
+
end
|
10
|
+
city_list.with_item(label: "Bellevue", data: { value: "bellevue" }) do |item|
|
11
|
+
item.with_leading_visual_icon(icon: :paste)
|
12
|
+
end
|
13
|
+
city_list.with_item(label: "Seattle", data: { value: "seattle" }) do |item|
|
14
|
+
item.with_leading_visual_icon(icon: :"device-camera")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
action_menu_form.submit(name: :submit, label: "Submit")
|
19
|
+
end
|
20
|
+
end
|
@@ -21,8 +21,8 @@ class ImmediateValidationForm < ApplicationForm
|
|
21
21
|
|
22
22
|
validation_form.text_field(
|
23
23
|
name: :random_error,
|
24
|
-
label: "Random error",
|
25
|
-
caption: "Server checks will randomly respond with errors",
|
24
|
+
label: "Random error or success",
|
25
|
+
caption: "Server checks will randomly respond with errors or success",
|
26
26
|
auto_check_src: @view_context.example_check_random_path
|
27
27
|
)
|
28
28
|
end
|
@@ -4,7 +4,7 @@ module Primer
|
|
4
4
|
module Octicon
|
5
5
|
# :nodoc:
|
6
6
|
class Cache
|
7
|
-
LOOKUP = {}
|
7
|
+
LOOKUP = {}
|
8
8
|
# Preload the top 20 used icons.
|
9
9
|
PRELOADED_ICONS = [:alert, :check, :"chevron-down", :paste, :clock, :"dot-fill", :info, :"kebab-horizontal", :link, :lock, :mail, :pencil, :plus, :question, :repo, :search, :"shield-lock", :star, :trash, :x].freeze
|
10
10
|
|
data/lib/primer/classify.rb
CHANGED
@@ -75,7 +75,6 @@ module Primer
|
|
75
75
|
# are about 30% faster than Hash#dig. It also ensures validate is
|
76
76
|
# only called when necessary, i.e. when the class can't be found
|
77
77
|
# in the lookup table.
|
78
|
-
# rubocop:disable Style/RescueModifier
|
79
78
|
found = (LOOKUP[key][item][brk] rescue nil) || validate(key, item, brk)
|
80
79
|
# rubocop:enable Style/RescueModifier
|
81
80
|
result << found if found
|
@@ -93,7 +92,6 @@ module Primer
|
|
93
92
|
end.join(" ")
|
94
93
|
|
95
94
|
# This is much faster than Rails' presence method.
|
96
|
-
# rubocop:disable Rails/Blank
|
97
95
|
{
|
98
96
|
class: !classes || classes.empty? ? nil : classes,
|
99
97
|
style: !style || style.empty? ? nil : style
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module Forms
|
5
|
+
# :nodoc:
|
6
|
+
class ActionMenu < BaseComponent
|
7
|
+
delegate :builder, :form, to: :@input
|
8
|
+
|
9
|
+
def initialize(input:)
|
10
|
+
@input = input
|
11
|
+
|
12
|
+
@input.input_arguments[:form_arguments] = {
|
13
|
+
name: @input.name,
|
14
|
+
builder: builder
|
15
|
+
}
|
16
|
+
|
17
|
+
@input.input_arguments[:select_variant] ||= :single
|
18
|
+
|
19
|
+
unless @input.input_arguments.include?(:dynamic_label)
|
20
|
+
@input.input_arguments[:dynamic_label] = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -36,7 +36,6 @@ module Primer
|
|
36
36
|
def before_render; end
|
37
37
|
|
38
38
|
# :nocov:
|
39
|
-
# rubocop:disable Naming/AccessorMethodName
|
40
39
|
def set_original_view_context(view_context)
|
41
40
|
@view_context = view_context
|
42
41
|
end
|
@@ -98,8 +97,6 @@ module Primer
|
|
98
97
|
end
|
99
98
|
|
100
99
|
def define_template_method(template_path, method_name)
|
101
|
-
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
102
|
-
# rubocop:disable Style/EvalWithLocation
|
103
100
|
class_eval <<-RUBY, template_path, 0
|
104
101
|
private def #{method_name}
|
105
102
|
capture { #{compile_template(template_path)} }
|
data/lib/primer/forms/base.rb
CHANGED
@@ -120,7 +120,6 @@ module Primer
|
|
120
120
|
private
|
121
121
|
|
122
122
|
def form_object
|
123
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
124
123
|
@__pf_form_object ||= Primer::Forms::Dsl::FormObject.new(builder: @builder, form: self).tap do |obj|
|
125
124
|
# compile before adding inputs so caption templates are identified
|
126
125
|
self.class.compile!
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module Forms
|
5
|
+
module Dsl
|
6
|
+
# :nodoc:
|
7
|
+
class ActionMenuInput < Input
|
8
|
+
attr_reader :name, :label, :block
|
9
|
+
|
10
|
+
def initialize(name:, label:, **system_arguments, &block)
|
11
|
+
@name = name
|
12
|
+
@label = label
|
13
|
+
@block = block
|
14
|
+
|
15
|
+
super(**system_arguments)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_component
|
19
|
+
ActionMenu.new(input: self)
|
20
|
+
end
|
21
|
+
|
22
|
+
# :nocov:
|
23
|
+
def type
|
24
|
+
:action_menu
|
25
|
+
end
|
26
|
+
# :nocov:
|
27
|
+
|
28
|
+
# :nocov:
|
29
|
+
def focusable?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
# :nocov:
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -81,7 +81,6 @@ module Primer
|
|
81
81
|
# methods. These methods will use the passed name if provided instead
|
82
82
|
# of generating a scoped one.
|
83
83
|
#
|
84
|
-
# rubocop:disable Style/IfUnlessModifier
|
85
84
|
unless @input_arguments.delete(:scope_name_to_model) { true }
|
86
85
|
@input_arguments[:name] = name
|
87
86
|
end
|
@@ -279,6 +278,14 @@ module Primer
|
|
279
278
|
{}
|
280
279
|
end
|
281
280
|
|
281
|
+
def validation_success_icon_target
|
282
|
+
""
|
283
|
+
end
|
284
|
+
|
285
|
+
def validation_error_icon_target
|
286
|
+
""
|
287
|
+
end
|
288
|
+
|
282
289
|
private
|
283
290
|
|
284
291
|
def input_data
|
@@ -90,6 +90,15 @@ module Primer
|
|
90
90
|
add_input SelectInput.new(builder: builder, form: form, **options, &block)
|
91
91
|
end
|
92
92
|
|
93
|
+
# Adds an <%= link_to_component(Primer::Alpha::ActionMenu) %> to this form.
|
94
|
+
#
|
95
|
+
# @param options [Hash] The options accepted by the <%= link_to_component(Primer::Alpha::ActionMenu) %> component.
|
96
|
+
# @param block [Proc] The block passed to `#render` when the <%= link_to_component(Primer::Alpha::ActionMenu) %> is rendered. This block is passed an instance of <%= link_to_component(Primer::Alpha::ActionMenu) %>, which can be used to add items, dividers, etc.
|
97
|
+
def action_menu(**options, &block)
|
98
|
+
options = decorate_options(**options)
|
99
|
+
add_input ActionMenuInput.new(builder: builder, form: form, **options, &block)
|
100
|
+
end
|
101
|
+
|
93
102
|
# END select input methods
|
94
103
|
|
95
104
|
# START button input methods
|
@@ -69,6 +69,14 @@ module Primer
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
def validation_success_icon_target
|
73
|
+
"primer-text-field.validationSuccessIcon"
|
74
|
+
end
|
75
|
+
|
76
|
+
def validation_error_icon_target
|
77
|
+
"primer-text-field.validationErrorIcon"
|
78
|
+
end
|
79
|
+
|
72
80
|
def validation_message_arguments
|
73
81
|
if auto_check_src.present?
|
74
82
|
super.merge(
|
@@ -30,8 +30,14 @@ class PrimerTextFieldElement extends HTMLElement {
|
|
30
30
|
var _a;
|
31
31
|
(_a = __classPrivateFieldGet(this, _PrimerTextFieldElement_abortController, "f")) === null || _a === void 0 ? void 0 : _a.abort();
|
32
32
|
const { signal } = (__classPrivateFieldSet(this, _PrimerTextFieldElement_abortController, new AbortController(), "f"));
|
33
|
-
this.inputElement.addEventListener('auto-check-success', () => {
|
34
|
-
|
33
|
+
this.inputElement.addEventListener('auto-check-success', async (event) => {
|
34
|
+
const message = await event.detail.response.text();
|
35
|
+
if (message && message.length > 0) {
|
36
|
+
this.setSuccess(message);
|
37
|
+
}
|
38
|
+
else {
|
39
|
+
this.clearError();
|
40
|
+
}
|
35
41
|
}, { signal });
|
36
42
|
this.inputElement.addEventListener('auto-check-error', async (event) => {
|
37
43
|
const errorMessage = await event.detail.response.text();
|
@@ -49,12 +55,35 @@ class PrimerTextFieldElement extends HTMLElement {
|
|
49
55
|
clearError() {
|
50
56
|
this.inputElement.removeAttribute('invalid');
|
51
57
|
this.validationElement.hidden = true;
|
52
|
-
this.validationMessageElement.
|
58
|
+
this.validationMessageElement.replaceChildren();
|
59
|
+
}
|
60
|
+
setValidationMessage(message) {
|
61
|
+
const template = document.createElement('template');
|
62
|
+
// eslint-disable-next-line github/no-inner-html
|
63
|
+
template.innerHTML = message;
|
64
|
+
const fragment = document.importNode(template.content, true);
|
65
|
+
this.validationMessageElement.replaceChildren(fragment);
|
66
|
+
}
|
67
|
+
toggleValidationStyling(isError) {
|
68
|
+
if (isError) {
|
69
|
+
this.validationElement.classList.remove('FormControl-inlineValidation--success');
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
this.validationElement.classList.add('FormControl-inlineValidation--success');
|
73
|
+
}
|
74
|
+
this.validationSuccessIcon.hidden = isError;
|
75
|
+
this.validationErrorIcon.hidden = !isError;
|
76
|
+
this.inputElement.setAttribute('invalid', isError ? 'true' : 'false');
|
77
|
+
}
|
78
|
+
setSuccess(message) {
|
79
|
+
this.toggleValidationStyling(false);
|
80
|
+
this.setValidationMessage(message);
|
81
|
+
this.validationElement.hidden = false;
|
53
82
|
}
|
54
83
|
setError(message) {
|
55
|
-
this.
|
84
|
+
this.toggleValidationStyling(true);
|
85
|
+
this.setValidationMessage(message);
|
56
86
|
this.validationElement.hidden = false;
|
57
|
-
this.inputElement.setAttribute('invalid', 'true');
|
58
87
|
}
|
59
88
|
};
|
60
89
|
_PrimerTextFieldElement_abortController = new WeakMap();
|
@@ -67,6 +96,12 @@ __decorate([
|
|
67
96
|
__decorate([
|
68
97
|
target
|
69
98
|
], PrimerTextFieldElement.prototype, "validationMessageElement", void 0);
|
99
|
+
__decorate([
|
100
|
+
target
|
101
|
+
], PrimerTextFieldElement.prototype, "validationSuccessIcon", void 0);
|
102
|
+
__decorate([
|
103
|
+
target
|
104
|
+
], PrimerTextFieldElement.prototype, "validationErrorIcon", void 0);
|
70
105
|
PrimerTextFieldElement = __decorate([
|
71
106
|
controller
|
72
107
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
@@ -8,6 +8,8 @@ class PrimerTextFieldElement extends HTMLElement {
|
|
8
8
|
@target inputElement: HTMLInputElement
|
9
9
|
@target validationElement: HTMLElement
|
10
10
|
@target validationMessageElement: HTMLElement
|
11
|
+
@target validationSuccessIcon: HTMLElement
|
12
|
+
@target validationErrorIcon: HTMLElement
|
11
13
|
|
12
14
|
#abortController: AbortController | null
|
13
15
|
|
@@ -17,10 +19,15 @@ class PrimerTextFieldElement extends HTMLElement {
|
|
17
19
|
|
18
20
|
this.inputElement.addEventListener(
|
19
21
|
'auto-check-success',
|
20
|
-
() => {
|
21
|
-
|
22
|
+
async (event: any) => {
|
23
|
+
const message = await event.detail.response.text()
|
24
|
+
if (message && message.length > 0) {
|
25
|
+
this.setSuccess(message)
|
26
|
+
} else {
|
27
|
+
this.clearError()
|
28
|
+
}
|
22
29
|
},
|
23
|
-
{signal}
|
30
|
+
{signal},
|
24
31
|
)
|
25
32
|
|
26
33
|
this.inputElement.addEventListener(
|
@@ -29,7 +36,7 @@ class PrimerTextFieldElement extends HTMLElement {
|
|
29
36
|
const errorMessage = await event.detail.response.text()
|
30
37
|
this.setError(errorMessage)
|
31
38
|
},
|
32
|
-
{signal}
|
39
|
+
{signal},
|
33
40
|
)
|
34
41
|
}
|
35
42
|
|
@@ -45,12 +52,37 @@ class PrimerTextFieldElement extends HTMLElement {
|
|
45
52
|
clearError(): void {
|
46
53
|
this.inputElement.removeAttribute('invalid')
|
47
54
|
this.validationElement.hidden = true
|
48
|
-
this.validationMessageElement.
|
55
|
+
this.validationMessageElement.replaceChildren()
|
56
|
+
}
|
57
|
+
|
58
|
+
setValidationMessage(message: string): void {
|
59
|
+
const template = document.createElement('template')
|
60
|
+
// eslint-disable-next-line github/no-inner-html
|
61
|
+
template.innerHTML = message
|
62
|
+
const fragment = document.importNode(template.content, true)
|
63
|
+
this.validationMessageElement.replaceChildren(fragment)
|
64
|
+
}
|
65
|
+
|
66
|
+
toggleValidationStyling(isError: boolean): void {
|
67
|
+
if (isError) {
|
68
|
+
this.validationElement.classList.remove('FormControl-inlineValidation--success')
|
69
|
+
} else {
|
70
|
+
this.validationElement.classList.add('FormControl-inlineValidation--success')
|
71
|
+
}
|
72
|
+
this.validationSuccessIcon.hidden = isError
|
73
|
+
this.validationErrorIcon.hidden = !isError
|
74
|
+
this.inputElement.setAttribute('invalid', isError ? 'true' : 'false')
|
75
|
+
}
|
76
|
+
|
77
|
+
setSuccess(message: string): void {
|
78
|
+
this.toggleValidationStyling(false)
|
79
|
+
this.setValidationMessage(message)
|
80
|
+
this.validationElement.hidden = false
|
49
81
|
}
|
50
82
|
|
51
83
|
setError(message: string): void {
|
52
|
-
this.
|
84
|
+
this.toggleValidationStyling(true)
|
85
|
+
this.setValidationMessage(message)
|
53
86
|
this.validationElement.hidden = false
|
54
|
-
this.inputElement.setAttribute('invalid', 'true')
|
55
87
|
}
|
56
88
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<%= content_tag(:div, **@input.validation_arguments) do %>
|
2
|
-
<span class="FormControl-inlineValidation--visual"><%= render(Primer::Beta::Octicon.new(icon: :"
|
2
|
+
<span class="FormControl-inlineValidation--visual" data-target="<%= @input.validation_success_icon_target %>" hidden><%= render(Primer::Beta::Octicon.new(icon: :"check-circle-fill", size: :xsmall, aria: { hidden: true })) %></span>
|
3
|
+
<span class=" FormControl-inlineValidation--visual" data-target="<%= @input.validation_error_icon_target %>"><%= render(Primer::Beta::Octicon.new(icon: :"alert-fill", size: :xsmall, aria: { hidden: true })) %></span>
|
3
4
|
<%= content_tag(:span, @input.invalid? ? @input.validation_messages.first : "", **@input.validation_message_arguments) %>
|
4
5
|
<% end %>
|
@@ -33,7 +33,6 @@ module Primer
|
|
33
33
|
|
34
34
|
{
|
35
35
|
"name" => slot_method.name,
|
36
|
-
# rubocop:disable Style/IfUnlessModifier
|
37
36
|
"description" =>
|
38
37
|
if slot_method.base_docstring.to_s.present?
|
39
38
|
render_erb_ignoring_markdown_code_fences(slot_method.base_docstring)
|
@@ -70,10 +69,15 @@ module Primer
|
|
70
69
|
render_erb_ignoring_markdown_code_fences(docs.base_docstring)
|
71
70
|
end
|
72
71
|
|
72
|
+
accessibility_docs =
|
73
|
+
if (accessibility_tag_text = docs.tags(:accessibility)&.first&.text)
|
74
|
+
render_erb_ignoring_markdown_code_fences(accessibility_tag_text)
|
75
|
+
end
|
76
|
+
|
73
77
|
memo[component] = {
|
74
78
|
"fully_qualified_name" => component.name,
|
75
79
|
"description" => description,
|
76
|
-
"accessibility_docs" =>
|
80
|
+
"accessibility_docs" => accessibility_docs,
|
77
81
|
"is_form_component" => docs.manifest_entry.form_component?,
|
78
82
|
"is_published" => docs.manifest_entry.published?,
|
79
83
|
"requires_js" => docs.manifest_entry.requires_js?,
|
@@ -85,7 +89,7 @@ module Primer
|
|
85
89
|
}
|
86
90
|
end
|
87
91
|
|
88
|
-
statuses = Primer::Status::Dsl::STATUSES.keys.map
|
92
|
+
statuses = Primer::Status::Dsl::STATUSES.keys.map { |k| k.to_s.capitalize }
|
89
93
|
|
90
94
|
Primer::Component.descendants.each do |component|
|
91
95
|
fq_class = component.name.to_s.split("::")
|
@@ -16,11 +16,9 @@ module Primer
|
|
16
16
|
|
17
17
|
component = preview.components.first&.component_class
|
18
18
|
|
19
|
-
# rubocop:disable Style/IfUnlessModifier
|
20
19
|
unless component
|
21
20
|
raise "Could not determine which component `#{preview.preview_class}` is designed to preview. Please add a `@component` annotation."
|
22
21
|
end
|
23
|
-
# rubocop:enable Style/IfUnlessModifier
|
24
22
|
|
25
23
|
_, _, class_name = Primer::Yard::DocsHelper.status_module_and_short_name(component)
|
26
24
|
|
@@ -44,7 +44,11 @@ module Primer
|
|
44
44
|
|
45
45
|
initializer "primer.forms.helpers" do
|
46
46
|
ActiveSupport.on_load :action_controller_base do
|
47
|
-
|
47
|
+
begin
|
48
|
+
require "primer/form_helper"
|
49
|
+
rescue LoadError
|
50
|
+
end
|
51
|
+
|
48
52
|
helper Primer::FormHelper
|
49
53
|
|
50
54
|
# make primer_form_with available to view components also
|